如何在C#中重写一个非常大的复合if语句?

Mic*_*hat 23 c# refactoring if-statement structure guard-clause

在我的C#代码中,我有一个非常开始的if语句:

if((something == -1) && (somethingelse == -1) && (etc == -1)) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

它正在增长.我认为现在必须有20个条款.

应该怎么处理这个?

小智 29

尽可能使用大门.

if语句

if(bailIfIEqualZero != 0 && 
   !string.IsNullOrEmpty(shouldNeverBeEmpty) &&
   betterNotBeNull != null &&
   !betterNotBeNull.RunAwayIfTrue &&
   //yadda
Run Code Online (Sandbox Code Playgroud)

重构的版本

if(bailIfIEqualZero == 0)
  return;

if(string.IsNullOrEmpty(shouldNeverBeEmpty))
  return;

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

//yadda
Run Code Online (Sandbox Code Playgroud)

  • @Mystere - 很好的解脱.反正我从来都不喜欢这条规则. (7认同)
  • @Mystere - 这个规则导致箭头反模式.坏ju-ju. (4认同)

cha*_*aos 19

将其分解为函数并使每个条件成为保护条款:

int maybe_do_something(...) {
    if(something != -1)
        return 0;
    if(somethingelse != -1)
        return 0;
    if(etc != -1)
        return 0;
    do_something();
    return 1;
}
Run Code Online (Sandbox Code Playgroud)

  • 冗余,是的.当你编程时,你不仅仅是为机器编程,而是为了其他可能需要阅读/维护你所写内容的可怜人.因此,为了便于阅读,以上是一项改进. (5认同)

ste*_*r25 16

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

例如,

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

if (TeamAIsGoForLaunch && TeamBIsGoForLaunch && TeamC.isGoForLaunch())
Run Code Online (Sandbox Code Playgroud)


God*_*eke 9

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

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

除此之外,还有基于客户状态,特定产品定制等的额外"异常"规则.这可以在复杂的IF语句链中解决,但我们改为驱动应用程序数据.

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

这将一个巨大的逻辑毛球转变为一个简单的四步过程,每个步骤只需进行数据库查询即可找到要应用的正确规则.

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


J.W*_*.W. 8

将其重构为函数.

bool Check()
{
  return (something == -1) && (somethingelse == -1) && (etc == -1);
}
Run Code Online (Sandbox Code Playgroud)

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


ang*_*son 6

有很多方法可以解决这个问题,但是让我选一些.

首先,存在所有标准(if语句中的所有AND标准)+如果它们都是真的则执行的代码是一次性情况的情况.

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

换句话说,而不是这样:

if (a && b && c && d && ......)
    DoSomething();
Run Code Online (Sandbox Code Playgroud)

...你重写了类似的东西:

if (!a) return;
if (!b) return;
if (!c) return;
if (!d) return;
if (!...) return;
DoSomething();
Run Code Online (Sandbox Code Playgroud)

为什么?因为一旦你开始在混合中引入OR标准,就很难阅读代码并弄清楚会发生什么.在上面的代码中,您在每个AND运算符(&&)上拆分条件,因此代码变得更容易阅读.基本上你重写代码来说"如果这个和那个,或那个其他东西和那个第三件事,或者其他东西,然后做某事"是"如果这个,然后退出;如果那个其他的东西;然后退出;如果一些其他的事情;然后退出;如果没有上述,做一些事情".

但是,在许多情况下,您还具有可重用性的情况.如果其中一些标准出现在其他地方,但实际执行的代码(DoSomething)并不相同,那么我会再次寻求其他人已经提出的建议.将条件重写为返回Boolean结果的方法,具体取决于评估条件的结果.

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

if (a && b && c && d && e && f && (h || i) && (j || k) || l)
Run Code Online (Sandbox Code Playgroud)

或这个:

if (CanAccessStream() && CanWriteToStream())
Run Code Online (Sandbox Code Playgroud)

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

在这种情况下,我会采用一些标准并加入这些方法,并为标准选择合适的名称.

第三个选项是代码中的几个位置的条件不同,但实际执行的代码是相同的.

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

例如,你可以这样写:

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

或者你可以这样写:

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();
}
Run Code Online (Sandbox Code Playgroud)

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