我对默认参数和零值有一般性问题。假设我有两个功能。一个调用另一个(这是一个辅助函数)。两者都有一个可选参数。
辅助函数只是将列表连接到带有连接符的字符串。将joiner传递给opts关键字列表中的第一个函数。 joiner的传递是可选的,默认为"AND"
defmodule ParamTest do
def func_1(list, opts \\ []) do
helper(list, opts[:joiner])
# Do something else with the result
end
defp helper(list, joiner \\ "AND") do
Enum.join(list, " #{joiner} ")
end
end
# Example 1
["el 1", "el 2"]
|> ParamTest.func_1(joiner: "AND")
# Result "el 1 AND el 2"
# Example 2
["el 1", "el 2"]
|> ParamTest.func_1
# Result: "el 1 el 2"
# But it should be also "el 1 AND el 2"
问题是:在第二个例子中,opts [:joiner]将为nil。但它仍然存在,因此不会使用默认值。
一种可能的解决方案是使用case
:
defmodule ParamTest do
def func_1(list, opts \\ []) do
case is_nil(opts[:joiner]) do
true -> helper(list)
false -> helper(list, opts[:joiner])
end
# Do something else with the result
end
defp helper(list, joiner \\ "AND") do
Enum.join(list, " #{joiner} ")
end
end
另一种方法是为helper使用两个函数定义并使用模式匹配:
defmodule ParamTest do
def func_1(list, opts \\ []) do
case is_nil(opts[:joiner]) do
true -> helper(list)
false -> helper(list, opts[:joiner])
end
end
defp helper(list, nil) do
Enum.join(list, " AND ")
end
defp helper(list, joiner \\ "AND") do
Enum.join(list, " #{joiner} ")
end
end
但我觉得这不是很优雅,在更复杂的情况下会变得混乱。
对于这种情况,什么是更好的解决方案?
答案 0 :(得分:7)
最好的解决方案是在帮助程序中强制使用joiner,并在func_1
中提供默认选项。
def func_1(list, opts \\ [joiner: "AND"]) do
helper(list, opts[:joiner])
...
end
defp helper(list, joiner) do
...
end
始终尝试将您的疑虑分开。 helper
不属于您的公共API,因此您始终可以传递所有选项。让它只是做它的工作而不用担心默认值。
func_1
是您的公共API,它应该担心默认值。你想指定" AND"默认情况下是joiner,所以这样做而不是默认为空选项列表。当有人正在阅读您的代码时,他不需要更深入地检查" AND"来自并且很容易弄明白,他可以通过这个选项而无需阅读文档甚至函数体。
通常一个好主意只是为了方便顶级函数(API)而使默认值只是明确地传递所有内容。否则,您必须在每个级别检查是否通过选项,就像您在case
示例中所做的那样。这很容易出错。
答案 1 :(得分:4)
没有比我认为的更好的解决方案。就个人而言,我会问自己以下问题:
helper/2
私人功能的默认参数吗?我对此没有信心,但我觉得私有函数的默认\\
args可能是某种代码味道。\\
默认参数,我想在哪里处理复杂性? :)如果我必须选择,在这种特殊情况下,我可能会根据helper/1
选项的存在单独调用helper/2
和:joiner
:
defmodule ParamTest do
def func_1(list, opts \\ []) do
if joiner = opts[:joiner] do
helper(list, joiner)
else
helper(list)
end
end
defp helper(list, joiner \\ "AND") do
Enum.join(list, " #{joiner} ")
end
end
然而,正如我上面所说,由于helper/2
是一个私人功能,它可能是有意义的(取决于你的用例,这个太小,我们无法做出更周到的决定:P)移动可选的连接器完全到“系统”的边界,即通过使用选项的默认值只到func_1/2
:
defmodule ParamTest do
def func_1(list, opts \\ []) do
helper(list, opts[:joiner] || "AND")
end
defp helper(list, joiner) do
Enum.join(list, " #{joiner} ")
end
end
同样,在您的使用案例中,这可能无法很好地扩展,但我觉得这对我们从问题中获得的信息是最好的:)。