【问题标题】:Should you always write code for else cases that "can never happen"?您是否应该始终为“永远不会发生”的其他情况编写代码?
【发布时间】:2011-01-29 06:52:45
【问题描述】:

拿一些类似的代码

if (person.IsMale()) {
    doGuyStuff();
} else {
    doGirlStuff();
}

是否应该写这个来明确检查是否person.isFemale(),然后添加一个抛出异常的新else?也许您正在检查枚举中的值,或者类似的东西。你认为没有人会在枚举中添加新元素,但谁知道呢? “不可能发生”听起来像是著名的遗言。

【问题讨论】:

  • 永不言败:我最近读到一个新故事,讲述了一个处于不确定状态“Down Under”的人成功申请了“未指定”的官方性别指定
  • 是的。如果您正在为 2010 年人口普查工作,请处理好,不要扔,男性、女性和其他。对于其他情况和其他枚举,最好把询问的时间花在写“throw”上。
  • Gender 并不是真正的 0(女性)或 1(男性),而是 0.0 到 1.0 的范围。事实上,这也可能是错误的,性别是一个介于 i、i^2 和 7 之间的复数。
  • 实际上,ISO (5218) 规定:0 = 未知,1 = 男性,2 = 女性,9 = 不适用。 @朱丽叶-哈哈!
  • 如果值存储在一位中,那么无论如何只有 0 和 1 是可能的。

标签: language-agnostic conditional


【解决方案1】:

我想你已经回答了你自己的问题。如果您知道,您将永远不会看到额外的价值:

bool isSet = ...
if (isSet)
{
    return foo;
}
return bar;

...那就别打扰了。但是,如果有可能有两个以上的可能值,请保护好自己。您(或两年后的维护程序员)会为此感激不尽。

【讨论】:

  • 我同意这样的警告,即为了让您“知道”您永远不会看到额外的值,必须不可能让某人添加意想不到的值.布尔值就是一个很好的例子,因为无论如何,没有人可以创建一个既不真也不假的布尔值。另一方面,Enum 值将来可以由其他程序员(或您!)修改,这意味着您永远无法 100% 确定不会添加新值。
  • @Joe Carnahan - 你永远不知道。如果有人最终实现了三元分布,我们可以看到,对,错,也许?
【解决方案2】:

我发现“永远不会发生”听起来不错,直到几个月后,在同事添加代码、破坏了您的原始代码之后,它让您大吃一惊。所以,就我自己而言,即使看起来不可能,我也会确保我的 if 是可靠的。

【讨论】:

    【解决方案3】:

    我想这取决于您的对象类型。如果它是严格的布尔值(或一般的二进制),那么正如您已经猜到的那样,第二个测试是多余的。任何其他情况,将进行第二次测试。

    但是,即使在这种情况下,您也可以有一个“心理捷径”来防止对象域的未来扩展 - 只需假设只有一个值是“真”值,而所有其他值都默认为“假”值。

    【讨论】:

      【解决方案4】:

      如果你仔细阅读一些形式化方法的书籍,他们建议你做这种事情。

      1. 定义后置条件

        isMale && doGuyStuff || isFemale && doGirlStuff.
        
      2. 导出一些将导致此后置条件的候选语句

        if isMale: doGuyStuff
        

        这可证明会导致一些后置条件

        if isFemale: doGirlStuff
        

        这可证明会导致一些后置条件

        请注意,顺序无关紧要。实际上,如果您删除任何排序假设,它会更简单。

      3. 你会得到以下结果:

        if isMale: doGuyStuff
        elif isFemale: doGirlStuff
        

        请注意,else 子句没有适当的用途。您永远不会 - 在正式推导中 - 派生 else 子句。您将始终拥有积极陈述的条件:a && b || c && d 之类的东西。很少会是a && b || !a && c,但即便如此,您通常也会以明确的!a 条件结束。

      形式上,“不可能的其他”子句应仅限于执行以下操作。

          if isMale: doGuyStuff
          elif isFemale: doGirlStuff
          else:
              raise HorrifyingSituationError
      

      如果你提出了 HorrifyingSituationError,这意味着你做错了数学并且不正确地从条件中导出了语句。或者您首先不正确地定义了后置条件。

      无论哪种方式,该程序的设计错误都是深刻而绝对的。一般来说,这并不奇怪。当您第一次尝试测试它时,它通常会失败。除非(经常发生)您选择的测试数据反映了您对后置条件的原始定义中的错误。即使这样,一旦遇到此异常,您也可以轻松追踪并永久修复它。

      【讨论】:

      • 您也可以使用前置条件或按合同设计来正式表达这一点,指定前置条件为.isMale() || .isFemale()。检查先决条件通常是个好主意。
      【解决方案5】:

      @Michael Petrotta - 您的代码 sn-p 不正确,因为在执行 DoY() 操作时忽略了条件的真值。

      (抱歉,还不能添加 cmets...)

      【讨论】:

      • 对,谢谢。固定的。这是一个赞成票;欢迎来到 cmets。
      【解决方案6】:

      我不会为我的公司编写代码,但在很多情况下,我们的程序员已经为这种情况编写了一些永远不会发生的事情,并且在尝试解决客户报告的问题时它会派上用场。我们从海外项目中获得的代码似乎经常发生这种情况。

      当客户致电并说“嘿,我收到“类型不允许的错误”并且它说“类型鸭子不允许”时,我们很快就找到了问题的原因并能够解决它。

      【讨论】:

        【解决方案7】:

        如果它永远不会发生,那么你就不需要为它编写代码。

        【讨论】:

          【解决方案8】:

          请记住,C# 中的枚举可能会咬你一口:

          enum SwitchPosition 
          {
              Off = 0,
              On = 1
          }
          
          void SetLightStatus(SwitchPosition pos)
          {
              switch (pos)
              {
                  case On: 
                      TurnLightOn(); 
                      break;
                  case Off: 
                      TurnLightOff(); 
                      break;
                  default:
                      // Fugedaboudit -- will never happen, right?
              }
          }
          

          错了!调用SetLightPosition(2); 是合法的,并且将通过该 switch 语句中的情况。最好按照 S. Lott 的建议发送 HorrifyingSituationError

          【讨论】:

          • 你不必担心像 Java 这样的语言实际上是类型安全的,其中枚举是一等对象,而不仅仅是 int 的语法糖
          • 好点,我已经编辑指定 C# 作为我警告的语言。
          【解决方案9】:

          就个人而言,我会将else 行写为:

          } else /*if (person.IsFemale())*/ {
          

          这避免了必须运行该函数(如果不需要其结果,我不想浪费时间运行它),但为未来的开发人员留下了重要的文档。现在很清楚,条件的这个分支涵盖了IsFemale 的情况(具体而言),而不是!IsMale 的情况(一般而言)。您实际上是在“大声”提出您的假设,这使得未来的更改不太可能误解您正在做的事情并破坏您的代码。

          在我们的嵌入式系统中,分析和调试故障可能很困难,因此我们经常包含“不可能的”else 语句,并使它们吐出错误消息并引发异常。这通常仅限于开发版本,一旦代码稳定,它们就会被删除。

          【讨论】:

            【解决方案10】:

            如果你想指出一个“不可能发生”的代码块,使用

            assert (false);
            

            【讨论】:

              【解决方案11】:

              如果它不能在生产中发生(但可能在开发过程中发生),我使用 assert

              如果它不应该在生产中发生,但它可能发生,我要么返回错误抛出异常。 p>

              顺便说一句,这是我在某处学到的一个巧妙的 C++ 小技巧;由于如果断言不正确,许多 ASSERT 宏将在消息框中显示其表达式,因此您可以将以下形式用于永远不应执行的分支:

              if (person.IsMale())
              {
                  AdmitEntranceToManCave();
              }
              else
              {
                  ASSERT(!"A female has gotten past our defenses. EVERYBODY PANIC!");
              }
              

              字符串文字的计算结果为(非 NULL)地址,该地址在逻辑上为 TRUE。因此,使用逻辑 NOT 运算符使其为 FALSE。

              【讨论】:

                【解决方案12】:

                这个问题取决于你的上下文——还有其他人能够扩展你的 Person 对象吗?当机器人成为合法人时,你可能会发现 isMale() 和 isFemale() 都可能是假的。

                如果您编写的代码是一个将由您的团队以外的人使用的模块,则这一点尤其重要。当然,在这种情况下,您可以更进一步,跳过硬编码的 if 测试,并在适当的工厂创建的对象上调用 doGenderStuff()...

                【讨论】:

                  【解决方案13】:

                  既然有这么多答案,我就给你展示一下它看起来像什么:(在 C++ 中)

                  if(!DoOperation())
                  {
                      // Allocate for a message
                      char * pMsg = new char[256];
                      // Make sure the memory was allocated
                      if(!pMsg)
                      {
                          cout << PREDEFINED_OUT_OF_MEMORY_MESSAGE << endl;
                          exit(0);
                      }
                  
                      if(!strcpy(pMsg,"Operation failed!"))
                      {
                          cout << PREDEFINED_STRCPY_FAILED_MESSAGE << endl;
                          exit(0);
                      }
                  
                      // Now let the user know there was an error
                      ErrorDetailStructure * pError = new ErrorDetailStructure();
                      if(!pError)
                      {
                          cout << PREDEFINED_OUT_OF_MEMORY_MESSAGE << endl;
                          exit(0);
                      }
                  
                      // Copy the message to the error structure
                      if(!strcpy(pError->pMessage,pMsg))
                      {
                          cout << PREDEFINED_STRCPY_FAILED_MESSAGE << endl;
                          exit(0);
                      }
                  
                      // Alert the user - yes, ErrorDetailsStructure
                      // overloads operator<<
                      cout << pError;
                  
                      delete pError;  // the destructor frees pMessage member
                  
                      // Now we need to log this error
                      some_file_pointer * pFile = OpenAFilePointer("/root/logfile");
                  
                      if(!some_file_pointer)
                      {
                          cout << PREDEFINED_OPEN_FILE_ERROR_MESSAGE << endl;
                          exit(0);
                      }
                  
                      some_file_pointer->WriteError("Something went wrong. Guess what it was.");
                  
                      // Don't forget to free some_file_pointer
                      delete some_file_pointer;
                  
                      exit(0);  // Just quit. Give up.
                  }
                  

                  我最终从中获得了很多乐趣。这有很多问题(而且设计太糟糕了),写下来我笑得很开心。

                  【讨论】:

                    【解决方案14】:

                    我会尽量保持代码简洁简洁,然后在不影响整洁的地方添加断言:)

                    if(male)
                    {
                      ...
                    }
                    else
                    {
                      ...
                    }
                    
                    // other stuff that nobody cares about...
                    assert(male||female);
                    

                    【讨论】:

                      猜你喜欢
                      • 2012-10-21
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-01-09
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-05-08
                      相关资源
                      最近更新 更多