【问题标题】:How should I rewrite a very large compound if statement in C#?我应该如何在 C# 中重写一个非常大的复合 if 语句?
【发布时间】:2010-11-01 05:50:30
【问题描述】:

在我的 C# 代码中,我有一个很天真地开始的 if 语句:

if((something == -1) && (somethingelse == -1) && (etc == -1)) {
    // ...
}

它正在增长。我想现在肯定有20个子句。

我应该如何处理这个?

【问题讨论】:

  • 我应该提供更多信息,我明白了。我对回复的数量感到有点震惊,并且需要一些时间来仔细研究所有这些。我正在检查是否可以运行我程序这一部分的主要代码。除非我正在查看的字符串中有这样那样的字符串。我是凭经验发现这些东西的。每次找到一个,我都会添加一个子句。再次感谢您的所有帮助...
  • 在这种情况下,为什么不以某种方式创建一个 List (配置文件?)并在 foreach 中进行比较?你可以把它作为一个保护子句,一旦它发现你不喜欢的东西就返回。

标签: c# refactoring structure if-statement guard-clause


【解决方案1】:

尽可能使用大门。

if 语句

if(bailIfIEqualZero != 0 && 
   !string.IsNullOrEmpty(shouldNeverBeEmpty) &&
   betterNotBeNull != null &&
   !betterNotBeNull.RunAwayIfTrue &&
   //yadda

重构后的版本

if(bailIfIEqualZero == 0)
  return;

if(string.IsNullOrEmpty(shouldNeverBeEmpty))
  return;

if(betterNotBeNull == null || betterNotBeNull.RunAwayIfTrue)
  return;

//yadda

【讨论】:

  • 恕我直言,这是目前所有解决方案中的最佳解决方案。 +1
  • @Mystere:在这种情况下,没关系。
  • @Mystere - 摆脱困境。反正我从来不喜欢这条规则。
  • 据我所知,它与我的答案相同,只是跳过了使使用 return 语句可行的 factor-into-a-function 部分......
  • @Mystere - 该规则导致 Arrowhead 反模式。坏事。
【解决方案2】:

将其分解为一个函数,并使每个条件成为一个保护子句:

int maybe_do_something(...) {
    if(something != -1)
        return 0;
    if(somethingelse != -1)
        return 0;
    if(etc != -1)
        return 0;
    do_something();
    return 1;
}

【讨论】:

  • 在代码方面,这与嵌套的 if 语句方法没有什么不同。相反,我会尝试将标准分离成更易于管理的方法,其中每个方法只检查几个标准,然后“最里面”的方法将执行实际代码。这将使代码更易于重用和维护。
  • 冗余,是的。但是,当您编程时,您不仅是为机器编程,而且是为其他可能不得不阅读/维护您编写的内容的可怜人编程。所以为了可读性,以上是一个改进。
  • 我不这么认为。在多行中打破原始 if 语句时,它看起来要好得多。
  • 查看我对 Brian Postow 的回答。
  • 冗余?因为它有不止一个“回报”?我想你不知道这个词是什么意思。
【解决方案3】:

假设所有这些条件确实是必要的,您可以将这些条件合并到一个或多个超级布尔值或函数调用中以增强可读性。

例如,

bool TeamAIsGoForLaunch = BobSaysGo && BillSaysGo;
bool TeamBIsGoForLaunch = JillSaysGo && JackSaysGo;

if (TeamAIsGoForLaunch && TeamBIsGoForLaunch && TeamC.isGoForLaunch())

【讨论】:

  • 这个。 if 中应该有语义选项组。看看您是否可以重写代码围绕 if 以允许这样做,然后在可能/必要的情况下使它们成为子函数(例如,如果它们将被重用)。
  • 我通常采用这种方法。这比任何其他解决方案都更具可读性,因为您正在命名条件并且它提供了更容易调试,因为您可以在点击 if 语句之前查看每个条件值。
  • @aehiilrs:等等……不是吗?哦……该死的我的阅读障碍! B-)
【解决方案4】:

要考虑的一件事是为什么你有这么多子句。正如 SWITCH 语句通常表明您应该将选择转移到子类中一样,大型复杂的 IF 语句链可能表明您在一个地方组合了太多概念(以及决策)。

作为一个例子,我将使用计算佣金的例子。在我建立的一个系统中,佣金率取决于给予批发商的佣金金额,批发商将一些佣金转给零售商。批发商给零售商的金额取决于零售商和批发商之间的具体协议(基于合同)。批发商获得的金额同样取决于产品线、特定产品和销售产品的数量。

除此之外,还有基于客户状态、特定产品定制等的额外“例外”规则。这可以全部在复杂的 IF 语句链中制定,但我们改为制定应用程序数据驱动。

通过将所有这些信息放入表格中,我们就能够传递数据规则。首先会触发批发商的通用规则,然后会触发任何覆盖规则。然后,有了基础批发商佣金,我们将运行批发商到零售商的通用规则,然后是这些规则的例外。

这将一个庞大的逻辑毛球变成了一个简单的四步流程,每一步都简单地进行数据库查询以找到要应用的正确规则。

这当然可能不适用于您的情况,但通常这样的大型复合体实际上意味着没有足够的类来划分责任(我们问题的另一种解决方案可能是包含特定规则的工厂类和覆盖),或者功能应该是数据驱动的。

【讨论】:

  • 编辑:如果您不想使用完整的规则系统或类集,Steamer 的建议是一个很好的建议:将各种小条件组合成“超级条件”可以真正清理代码并使其成为可能后面的 if 语句更容易阅读。
  • 这将一个巨大的逻辑毛球变成了一个简单的四步过程——我宁愿说这只是将巨大的逻辑毛球从代码移动到规则表中.
  • @AntonTykhyy 为什么要这么说?通过将逻辑划分为四个通道(每个通道分为默认规则和潜在的覆盖规则),每个通道对于维护佣金系统的人来说更容易理解,因为它现在反映了系统用户使用的心理模型.现在用户可以查询场景的规则系统,查看触发的规则并了解它们触发的为什么。在大量基于代码的逻辑中,这些都不可用。显然,一个构建不佳的规则系统将无济于事,而且可能会使事情变得更糟。不要那样做。
  • 我明白了。我没有考虑触发可见角。谢谢!
【解决方案5】:

将其重构为一个函数。

bool Check()
{
  return (something == -1) && (somethingelse == -1) && (etc == -1);
}

或者,您可以在 Check 函数中构建更具可读性的代码/逻辑。

【讨论】:

  • 那么“我应该如何处理我的大 if 语句?”的答案。是把它移开吗?现在这听起来不像是一个解决方案,是吗?处理像这样的大标准块的正确方法是将它们分成可管理的块。
  • @Lasse- 这取决于。有时问题是这个布尔逻辑是做什么的?使用提取方法使布尔逻辑的功能自我描述-refactoring.com/catalog/extractMethod.html
【解决方案6】:

有很多方法可以解决这个问题,但让我挑几个。

首先,所有条件(if 语句中的所有 AND 条件)+如果它们都为真则要执行的代码是一次性的情况。

在这种情况下,请使用您拥有的代码。您可能想要执行其他几个人已经建议的操作,重写以使用 Guard 子句类型的代码。

换句话说,不是这样:

if (a && b && c && d && ......)
    DoSomething();

...你改写成类似这样的东西:

if (!a) return;
if (!b) return;
if (!c) return;
if (!d) return;
if (!...) return;
DoSomething();

为什么?因为一旦你开始将 OR 标准引入到组合中,就很难阅读代码并弄清楚会发生什么。在上面的代码中,您在每个 AND 运算符 (&&) 上拆分条件,因此代码变得更易于阅读。基本上,您将代码从说“如果这个和那个,或者那个其他事情和那个第三个事情,或者其他事情,然后做某事”改写为“如果这个,然后退出;如果那个其他事情;然后退出;如果一些其他的;然后退出;如果以上都不是,做点什么”。

但是,在许多情况下,您也有可重用性的情况。如果其中一些标准出现在其他地方,但实际执行的代码(DoSomething)不一样,那么我会再次选择其他人已经建议的内容。根据评估条件的结果,将条件重写为返回 Boolean 结果的方法。

例如,什么更容易阅读,这个?

if (a && b && c && d && e && f && (h || i) && (j || k) || l)

或者这个:

if (CanAccessStream() && CanWriteToStream())

假设所有这些字母都可以分为这两个标准。

在这种情况下,我会采用一些标准并将其放入这些方法中,并为标准选择一个合适的名称。

第三个选项是代码中的几个地方的标准不同,但实际执行的代码是相同的。

在这种情况下,我会重写,以便您将条件组合在一起并将方法分层,以便调用一个方法将检查某些条件,然后调用另一个方法,该方法将检查一些其他条件,等等。

例如,你可以这样写:

if (stream != null && buffer != null && inBuffer > 0 && stream.CanWrite)
  stream.Write(buffer, 0, inBuffer);
else
    throw new InvalidOperationException();

或者你可以这样写:

if (inBuffer > 0)
{
    Debug.Assert(buffer != null);
    WriteToStream(buffer, inBuffer);
}

...

private void WriteToStream(Byte[] buffer, Int32 count)
{
    if (stream.CanWrite)
        stream.Write(buffer, 0, count);
    else
        throw new InvalidOperationException();
}

我会说第二种方式比第一种方式更容易阅读,并且更可重复使用。

【讨论】:

    【解决方案7】:

    对您的值列表执行聚合操作。

    if (new[] { something, somethingelse, ... }.All(x => x == -1)) {
    }
    

    *编辑:给数据一个额外的行:

    var Data = new[] { something, somethingelse, ... };
    if (Data.All(x => x == -1)) {
    }
    

    【讨论】:

    • 我可能会把它放在一个函数或宏中,以避免在 if 条件中调用 new...
    【解决方案8】:

    看起来您有 3 条信息共同代表应用程序中的特定状态。与其打开这 3 个状态,不如创建一个封装它们的值?然后,您可以在创建时使用对象层次结构或委托来绑定您尝试运行的操作。

    【讨论】:

      【解决方案9】:

      您可以将它重构为一个函数,并返回一个 Enum 值,该值代表正确的情况:

      if(something != -1)
          return MyEnum.Something;
      if(somethingelse != -1)
          return MyEnum.SomethingElse;
      if(etc != -1)
          return MyEnum.SomethingElseEntirely;
      return MyEnum.None;
      

      【讨论】:

        【解决方案10】:

        您也可以将状态分解到一个类中。

        class mystate
        {
            int something;
            int somethingelse;
            int etc;
        
            bool abletodostuff()
            {
                return (something == -1) && (somethingelse == -1) && (etc == -1);
            }
        }
        

        【讨论】:

          【解决方案11】:

          我不懂 C#,但它似乎包括 conditional operator。如果你的条件很短,你可以用漂亮的表格结构替换长的 if/elsif/else 链,如下所示:

          return   something == 0      ? 0
                 : somethingelse == -1 ? 1
                 : yetanotherthing > 2 ? 2
                 :                       3; # default
          

          【讨论】:

          • 他的 && 链很长。将每个条件放在一个单独的位置就足够了。除了其他问题外,这种模式会让从未见过的人感到困惑。
          • 虽然我不认为该模式与一些项目混淆,但它会在 20 次比较之后全部嵌套。
          • 我喜欢这个答案没有被否决,而将嵌套的 if 语句分开的答案是,这是重构为单独方法的第一步。同侪压力 FTW!
          • 嗯,你是对的。另一个只会制作 20 个嵌套的 if 语句。如果它是“重构的第一步”,那么其余的步骤应该已经被记录下来了。这也不是答案。
          • 我所说的“重构的第一步”实际上是分离出标准,以便您获得可重用、易于维护的方法。如果这些方法每个只检查几个标准,但命名得当,并且标准在逻辑上属于一起,那么是的,这就是我要走的路。
          【解决方案12】:

          您可能会探索的另一个途径是使用复合表达式。这些表达式(这是 LINQ 在 .NET 框架中工作的基础)允许您根据所有这些条件创建表达式树,然后业务逻辑代码可以简单地对顶级表达式进行操作以获取真/假结果.

          要评估表达式,您可以使用visitor pattern

          这使您可以轻松地组合条件树。这些树甚至可以序列化,让您坚持做出决定的条件。这里有很多机会。

          【讨论】:

            【解决方案13】:

            在一个“if”中看到这么多子句并不常见。当您需要执行某些行而不管某些条件的真实性时,您通常会发现您需要嵌套“ifs”以获得所需的逻辑。我不是说如果你不需要嵌套它们,如果它们都需要同时进行测试。只有当有一些共同的功能。 另一个考虑是设置一个布尔变量,其中包含一些这些条件的结果,这可能更容易理解。 如果你的变量是一个数组或集合,你能遍历它们吗?您是否针对 -1 对它们进行了测试?

            【讨论】:

              【解决方案14】:

              虽然我喜欢达里奥的解决方案(正如我评论的那样,我将它放在一个布尔函数中,所以我不必在 if 的条件部分添加一个新的...)我不确定出了什么问题与:

              if((something == -1) &&
                 (somethingElse == -1) &&
                 (elseElse == -1) &&
                 ...
                )
              

              我认为这可能比我必须处理的许多 ((A && B) || (C &&(D||E))) 更容易阅读......

              【讨论】:

              • 它的主要问题是你不能用缩进做任何不糟糕的事情。像您正在做的那样,在三个空格或其他一些非真实缩进级别上对齐是纯粹的邪恶,并且达到完整缩进级别会使条件与包含的块在视觉上难以区分。
              • 并且走出 两个 缩进级别,虽然可能是我见过的条件延续最不糟糕的事情,但仍然非常疯狂。
              • 为什么这么邪恶? Emacs 会自动为我做到这一点,所以我什至没有考虑过......
              • 1) 我不喜欢为了与括号对齐而打破逻辑约定的想法,无论它们碰巧落在哪里 2) 现在你不能使用制表符进行缩进,只能使用空格——要么你有混合制表符和空格,这远远超出了普通的邪恶。
              • 奇怪,我一直在混合制表符和空格,没有问题...我的意思是,只要您在空格后没有制表符,应该没有问题...
              【解决方案15】:

              我这样做的方式除了我之外没人喜欢。我不在乎代码是否“看起来不错”。如果我在那个条件下获得了 >1 个测试,这意味着我可能想要更多,或者我可能想要评论其中一些或评论,并且我想简化这些更改的制作。

              所以我这样编码:

              if (true
                && test_1
                && test_2
                ...
                )
              {
                ...
              }
              

              这使得注释测试或添加新测试变得容易,如 1 行编辑。

              但我会第一个承认,它并不声称漂亮。

              【讨论】:

                【解决方案16】:

                如果你真的必须检查所有这些条件,你就无法摆脱它们。您只能重构它并使其更具可读性,但逻辑保持不变。

                我很惊讶没有人提到任何关于重新设计代码的事情。你真的需要 20 种不同的状态吗?根据我的经验,很多状态通常依赖于其他状态,因此通常在逻辑上是多余的。

                重构代码可能有助于您更好地理解代码以及状态如何相互耦合。如果我是你,我会先从这里开始:)

                【讨论】:

                • “我很惊讶没有人提到任何关于重新设计代码的事情。” Godeke 和 JaredPar 都建议这样做。
                • 对不起,我的错。他们迷失在所有这些 if 语句中:)
                • 确实如此。 :)
                【解决方案17】:

                还有这样的事情

                我会进一步解释一下。 (并修复愚蠢的错误:S)

                //Interface to be able to which classes are able to give a boolean result used in the if stuff
                public interface IResultable
                {
                    bool Result();
                }
                
                //A list that is IResultable itself it gathers the results of the IResultables inside.
                public class ComparatorList<T> : List<T>, IResultable where T : IResultable
                {
                    public bool Result()
                    {
                        bool retval = true;
                        foreach (T t in this)
                        {
                            if (!t.Result())
                            {
                                retval = false;
                            }
                        }
                        return retval;
                    }
                }
                
                //Or two bools
                public class OrComparator : IResultable
                {
                    bool left;
                    bool right;
                
                    public OrComparator(bool left, bool right)
                    {
                        this.left = left;
                        this.right = right;
                    }
                    #region IResultable Members
                
                    public bool Result()
                    {
                        return (left || right);
                    }
                
                    #endregion
                }
                
                // And two bools
                public class AndComparator : IResultable
                {
                    bool left;
                    bool right;
                
                    public AndComparator(bool left, bool right)
                    {
                        this.left = left;
                        this.right = right;
                    }
                    #region IResultable Members
                
                    public bool Result()
                    {
                        return (left && right);
                    }
                
                    #endregion
                }
                
                // compare two ints
                public class IntIsComparator : IResultable
                {
                    int left;
                    int right;
                
                    public IntIsComparator(int left, int right)
                    {
                        this.left = left;
                        this.right = right;
                    }
                    #region IResultable Members
                
                    public bool Result()
                    {
                        return (left == right);
                    }
                
                    #endregion
                }
                

                你有很多 if 语句吗,这可能很酷:)

                所以我们有这个结构来处理很多比较。我将给出一个小示例实现。

                //list of ands
                ComparatorList<AndComparator> ands = new ComparatorList<AndComparator>();    
                ands.Add(new AndComparator(true,true));
                
                //list of ors
                ComparatorList<OrComparator> ors = new ComparatorList<OrComparator>();
                ors.Add(new OrComparator(false, true));
                
                //list of intiss
                ComparatorList<IntIsComparator> ints = new ComparatorList<IntIsComparator>();
                ints.Add(new IntIsComparator(1, 1));
                
                //list of all things :)
                ComparatorList<IResultable> collected = new ComparatorList<IResultable>();
                collected.Add(ands);
                collected.Add(ors);
                collected.Add(ints);
                
                // if all things are as they must be :)
                if (collected.Result())
                {
                    //Evertything is as it schould be :)
                }
                

                【讨论】:

                  【解决方案18】:

                  您说您正在查看字符串,那么有人已经评论过这样的事情怎么样。

                          var items = new List<string>();
                  
                          items.Add("string1");
                          items.Add("string2");
                  
                          if (items.Contains("string2"))
                          {
                              // do something
                          }
                  

                  您甚至可以从某种配置文件中获取值来填充列表。

                  【讨论】:

                    猜你喜欢
                    • 2020-02-13
                    • 2013-09-04
                    • 2021-02-21
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多