当 Elixir 中的函数只有一个定义时,使用守卫是惯用的做法吗? [已关闭]

问题描述 投票:0回答:4

当你只有一个函数定义时,使用守卫是惯用的做法吗?

例如。

defmodule Math do
    def add(a, b) when is_integer(a) and is_integer(b) do
        a + b
    end
end

defmodule Math do
    def add(a, b) do
        a + b
    end
end

哪个是首选?

elixir
4个回答
1
投票

一般来说,守卫和模式匹配的主要好处之一是尽早失败(erlang/elixir 著名的“让它崩溃”哲学的一部分),并且如果输入不正确,则完全阻止底层逻辑被调用符合你的假设。

虽然您的

add
示例一开始并不真正需要它,但执行实际业务逻辑的不那么琐碎的函数(例如来自 phoenix 上下文的公共函数)可以从守卫/更具限制性的模式中受益。 如果使用无效数据调用它,可能是:

  • 当它在嵌套调用中更深入地失败时,调试起来会更困难
  • 未发生故障时不可预测且存在潜在危险

当然,警卫只能进行表面检查并检测一些明显的错误,它们不能取代验证不受信任的用户输入(例如使用 ecto)。

这个关于守卫和无效数据的部分很好地描述了它。这是 来自在 Hex 上编写库的指南,但它特别指出这种哲学也适用于常规的 Elixir 代码。


1
投票

是的,即使只有一个函数子句,使用保护子句也是惯用的。为什么?因为使用守卫有助于更好地传达你的意图。

编码简单;沟通很难。

虽然添加

@spec
@doc
也有帮助,但守卫明确说明了你的函数接受的内容,并且正如其他人指出的那样,
FunctionClauseError 
错误消息确实在你面前 - 我认为它们是比当您向函数传递意外值时可能出现的任何奇怪的不可预测的行为更容易调试。


0
投票

这取决于您想要参数的具体程度。我见过

guard
子句在函数中使用,而在开源项目中只有一个定义,例如
Oban
。例如

  @doc false
  @spec validate!(Keyword.t()) :: :ok
  def validate!(opts) when is_list(opts) do
    Enum.each(opts, &validate_opt!/1)
  end

0
投票

单子句函数上的这样的保护基本上是对参数类型的断言。在你的情况下,像这样调用函数:

Math.add(7.2, 3.2)

结果:

** (FunctionClauseError) no function clause matching in Math.add/2

    The following arguments were given to Math.add/2:

        # 1
        7.2

        # 2
        3.2

所以它基本上定义了

+
的纯整数版本。

如果您认为该函数可能会使用不正确的参数调用,从而导致模糊或难以检测的错误,那么该机制可能会很有用。添加这样的保护子句可以通过指示问题所在的确切调用来简化调试,并有可能尽早捕获错误。

© www.soinside.com 2019 - 2024. All rights reserved.