【问题标题】:Sequential logic and readability顺序逻辑和可读性
【发布时间】:2017-04-01 18:19:55
【问题描述】:

这是一个抽象的问题,没有真正的代码(也可能不是最好的伪代码),所以希望它足够有意义,不会被审核致死。但这是一个一遍又一遍地向我提出的问题,因为我正在从事的项目是一个非常线性的,取决于先前的条件和过程。所以……

给定一系列逻辑任务,每个任务都依赖于前一个任务,我尝试了两种方式来构造代码。 一个依赖于proceed 变量,像这样

Proceed = True

If Task1 Not Successful Then
    Proceed = False
End If

If Proceed Then 
    If Task2 Not Successful Then 
        Proceed = False
    End If
End If

等等

但是我在许多地方读到了 cmets,大意是这种运行 Proceed 变量的方法并不理想。所以,或者我可以有

If Task1 Succcesful Then 
    If Task2 Successful
       Then Etc
    Else
        Error Condition
    End If
Else
    Error Condition
End If

在我看来,前者更具可读性,逻辑非常明显。而且,当序列中的任务数量变大(超过 3 个或真的)时,嵌套的 If 真的变得笨拙。 所以,我的问题是,不使用第一种方法的原因是什么? 是否有更好的方法来构建第二个示例中的逻辑以提高可读性? 或者是否有第三种方法可以解决前者的问题(无论它们是什么),以及后者的可读性问题? 还是第一种方法实际上很好,当一个确实有一系列顺序相关的任务时?

【问题讨论】:

    标签: logic


    【解决方案1】:

    这真的取决于你手头的情况。没有任何进一步的细节,并坚持使用与您的原始块严格等效的代码,我将首先避免不必要的嵌套:

    Proceed = False;
    
    if Task1 successful and Task2 successful Then
        Proceed = True
    end if
    

    在大多数语言中,您可以直接将布尔值放入变量中。

    Proceed = task1 successful and task2 successful
    

    如果你真的更喜欢避免 AND,那么也许可以这样:

    Proceed = True;
    
    If Task1 not successful then
        Proceed = False
    Else if Task2 not successful then
        Proceed = False
    Else if Task3 not successful then
        Proceed = False
    End if
    

    到目前为止,在所有情况下,您都避免了嵌套。为了可读性,这很重要。

    除此之外,您的候补还有两个变化。首先是“Proceed”变量被完全删除的事实。我同意这一点。原始代码的读者必须克服的是:

    初始化 x ... 现在,如果 x 则为 y 准备好 ... 好的,现在做 y

    如果您可以将其更改为:

    如果 x 则做 y

    这在概念上更容易。

    所以我会这样做:

    If Task1 successful and Task2 successful and Task3 successful then
        Do your thing
    End if
    

    或者

    If Task1 not successful then
        Do something
    else If Task2 not successful then
        Do some other thing
    else 
        Do your main thing
    end if
    

    第二个区别是您输入了“错误条件”。特别是,您提到“错误”。如果您正在阅读的文档说您应该处理错误(例如通过停止执行并打印错误消息),那么这是正确的。虽然如果你有机会,你应该在进入 if/then 语句之前处理它们,例如在 Task1 过程中。这不仅简化了 if/then 语句,而且对于编码人员来说也是一种更好的体验,因为任何像样的 IDE 都会导致他们失败,并且最好在失败出现时立即处理,以便开发人员没有很长的路要走,看看最终的来源是什么。

    【讨论】:

      【解决方案2】:

      两者都很常见。第一个在处理需要按顺序调用多个方法的连贯 API 时尤其常见:

      API_THING *thing = NULL;
      API_RESULT result = CreateThing(&thing, ...);
      if (API_OK == result) result = InitializeThing(&thing, ...);
      if (API_OK == result) result = DoThingToThing(&thing, ...);
      // ...
      if (thing)
      {
          ReleaseThing(&thing, ...);
          thing = NULL;
      }
      

      如果您不需要嵌套超过 2-3 层的深度,则另一种很常见(尤其是在两种情况都处理的情况下):

      另一个未被建议的是goto 和/或例外:

      API_THING *thing = NULL;
      if (API_OK != CreateThing(&thing, ...)) goto CLEANUP;
      if (API_OK != InitializeThing(&thing, ...)) goto CLEANUP;
      if (API_OK != DoThingToThing(&thing, ...)) goto CLEANUP;
      //...
      CLEANUP:
      if (thing)
      {
          ReleaseThing(&thing, ...);
          thing = NULL;
      }
      

      如果使用异常,您可能会像上面的 goto 示例一样并在每一行上抛出,或者您可以将 API 包装在抛出的方法中:

      void DoCreateThing(API_THING **thing, ...)
      {
          if (API_OK != CreateThing(thing, ...))
              throw new ApiException("CreateThing Failed!");
      }
      
      //...
      
      API_THING *thing = NULL;
      try
      {
          DoCreateThing(&thing, ...);
      }
      catch (ApiException e)
      {
         // ...
      }
      // ...
      finally
      {
          if (thing)
          {
              ReleaseThing(&thing, ...);
              thing = NULL;
          }
      }
      

      请放心:如果您的编程正确,那么您在此处做出的任何决定都不会像您如何从高级架构中封装这些行为那么重要。

      【讨论】:

        猜你喜欢
        • 2016-01-31
        • 1970-01-01
        • 2010-09-05
        • 2011-05-12
        • 2017-08-12
        • 2019-09-13
        • 1970-01-01
        • 1970-01-01
        • 2012-01-10
        相关资源
        最近更新 更多