我刚开始用Clojure进行攻击,虽然我喜欢这种语言,但我不明白如何习惯性地做某些事情。
使用compojure编写Web应用程序,这是我的控制器操作之一:
(defn create [session params]
(let [user (user/find-by-email (params :email))]
(if user
(if (user/authenticate user (params :password))
(do (sign-in session user)
(resp/redirect "/home?signed-in=true"))
(resp/redirect "/?error=incorrect-password"))
(let [new-user (user/create params)]
(sign-in session new-user)
(resp/redirect "/home?new-user=true")))))
我正以非常迫切的方式写这篇文章。使用这么多let
s / if
s / do
,我不禁想到我做错了什么。我如何在功能上写这个?
这是我正在尝试做的伪代码
look if user exists
if user exists, try to sign user in using password provided
if password is wrong, redirect to "/?error=incorrect-password"
if password is correct, sign user in and redirect to "/home?signed-in=true"
else create user, sign user in, and redirect to "/home?new-user=true"
非常感谢!
答案 0 :(得分:8)
if
没有任何不起作用的东西 - 它是一个非常好的,纯函数的构造,可以有条件地评估表达式。但是,在一个地方有太多if
可能是一个警示标志,你应该使用不同的结构(例如cond?polymorphism with protocols?multimethods?一个高阶函数的组合?)
do
更棘手:如果你做了,那么它意味着你正在为副作用做一些事情,这肯定是无效的。在您的情况下,sign-in
和user/create
似乎是这里的副作用罪魁祸首。
你应该怎么做副作用?好吧,它们有时是必要的,所以挑战在于如何构建代码以确保副作用受到控制和管理(理想情况下重构为特殊的状态处理危险区域,以便其余代码保持干净和纯粹的功能)。
在您的情况下,您可以考虑:
答案 1 :(得分:5)
函数式编程风格鼓励使用更高级别的函数,如map,reduce和filter,它们也会强制您在大多数时间处理不可变数据结构。到目前为止,您的代码没有任何问题,因为您没有破坏函数式编程中的任何单一规则。但是,您可以稍微改进一下代码,例如组合let和if if-let。
答案 2 :(得分:3)
这看起来很合理,而且由于你唯一的副作用是“必要的”,即加入会话,我不会称之为非常必要。我会做三个更改,但第三个是可选的:
if
/ let
对更改为单个if-let
。(:foo bar)
进行关键字查询,而不是(bar :foo)
。那就是standard way to do it。new-user
创建本地,因为您只使用一次;你可以内联它。我不会做出另一个改变,因为我认为它会降低可读性。然而,这是风格和判断的问题,所以我会提到它作为你想到的东西。注意if
迷宫的每个分支如何在resp/redirect
的呼叫中结束:您可以将所有这些呼叫拉到顶层。然后决定传递给它的参数。结合其他变化,它看起来像:
(defn create [session params]
(resp/redirect (if-let [user (user/find-by-email (:email params))]
(if (user/authenticate user (:password params))
(do (sign-in session user)
"/home?signed-in=true")
"/?error=incorrect-password")
(do (sign-in session (user/create params))
"/home?new-user=true"))))