【问题标题】:How to refactor case statements with excessive nesting如何重构带有过度嵌套的 case 语句
【发布时间】:2019-02-05 17:32:15
【问题描述】:

在许多情况下,我发现自己需要对结构本身及其字段进行模式匹配,并在某些中间结果不为空时继续执行一些进一步的操作。

但是,结构本身可能首先是nil。这导致我编写了多个嵌套匹配,例如

experiment = Repo.get(Experiment, experiment_id)

case experiment do
  nil ->
    # Error 1

  _ ->
    case experiment.active do
      false -> # Error 2
      true -> 
        case Repo.all(assoc(experiment, :experiment_results)) do
          [] -> # Error 3
          results -> # Do stuffs
    end
end

理想情况下,我希望编写没有太多嵌套的代码。

如何重构代码?


(请注意,我最初的问题是关于结构上的模式匹配,而它可能是nil。我的实际用例比我最初提出的问题更广泛,因此我更新了这个问题。)

AlekseiMatiushkin 和 Sheharyar 的答案适用的原始代码:

experiment = Repo.get(Experiment, experiment_id)

case experiment do
  nil ->
    :error

  _ ->
    case experiment.active do
      false -> :error
      true -> # Do stuffs
    end
end

【问题讨论】:

    标签: functional-programming pattern-matching elixir phoenix-framework


    【解决方案1】:

    我会使用直接模式匹配。

    Experiment
    |> Repo.get(experiment_id)
    |> case do
      %Experiment{active: true, other_attribute: :value} ->
        # do stuff
      _ ->
        :error
    end
    

    【讨论】:

      【解决方案2】:

      && 短路运算符怎么样?

      if experiment && experiment.active && experiment.other do
        # do something
      else
        :error
      end
      

      您也可以使用cond添加更多案例:

      cond do
        experiment && experiment.active && experiment.other ->
          # do something
      
        !experiment.active ->
          {:error, :inactive}
      
        is_nil(experiment) ->
          {:error, :experiment_is_nil}
      
        true ->
          {:error, :unknown}
      end
      

      【讨论】:

        【解决方案3】:

        另一种方法是使用with 语法。它本质上规定了“幸福路径”应该是什么,即使该路径需要进行多次相对复杂的检查:

        with experiment <- Repo.get(Experiment, id),
             {:nil_experiment, false} <- {:nil_experiment, is_nil(experiment)},
             experiment_results <- Repo.all(assoc(experiment, :experiment_results)),
             {:empty_results, false} <- {:empty_results, Enum.empty?(experiment_results)} do
        do
          # Do stuffs with `experiment_results`
        else
          {:nil_experiment, true} ->
             # Error message 1
        
          {:empty_results, true} ->
             # Error message 2
        
          _ ->
             # Unknown error
        end
        

        【讨论】:

        • 这是在滥用Kernel.SpecialForms.with/1 来执行case 与大量冗余局部变量/原子的模式匹配。它显然是一种反模式,永远不应该使用它。经验法则是“如果你发现自己在处理with 中的else 子句,那么就出了问题。”相反,请使用我的 sn-p 中的case 和许多子句。
        • @AlekseiMatiushkin 对。您的意思是withelse 部分通常只能与一个子句而不是多个子句一起使用吗?我不认为这就是它的设计用途。另外,如果代码仅限于检查experiment,我同意您的解决方案,但我的用例实际上更像这样。查看我更新的代码。我想知道如何使用case 语句处理这种情况,因为我的快乐之路不仅仅涉及experiment 结构。我想听听你的建议。
        • @AlekseiMatiushkin 甚至 Michael Muskala 显然也有一个例子,其中else 部分包含多个子句:请参阅relistan.com/elixir-thoughts-on-the-with-statement 的顶部评论
        • @AlekseiMatiushkin 我认为我最初的问题对于我的用例来说并不完全准确/广泛。我已经更新了这个问题。在提出以下问题后,我遇到了一个主张在过多管道上使用 with 的演讲:youtube.com/…,我认为它通常更普遍地适用于此类用例,而 case 语句可能适用于更窄的情况.
        • 好吧,现在它是with monadic 行为的完美用例。不过,我不是一个算命的人来读你的想法:)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-12
        • 2016-12-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多