【问题标题】:Coding style - Input validation编码风格 - 输入验证
【发布时间】:2009-01-23 19:03:22
【问题描述】:

这是验证传递给函数的输入的最佳方法,即在进行类似操作之前是否验证所有输入

class A;
void fun(A* p)
{
  if(! p)
  {
    return;
  }

 B* pB = p->getB();
  if(! pB)
  {
    return;
  }

.......

}

还是这样写:

void fun(A* p)
{
  if(p)
  {
    B* pB = p->getB();
    if(pB)
    {
      .....
    }
  }
}

我问这个是因为,如果我使用第一种样式,那么我的代码中会有多个返回语句,很多人说它们不好(不知道为什么),如果我使用第二种样式,那么会有我的代码中嵌套的级别太多。

【问题讨论】:

    标签: c++ coding-style


    【解决方案1】:

    与第二种方法相比,第一种方法更容易阅读,也更简单(按深度)。在第二个中,复杂性和深度随着参数数量的增加而增加。但在第一个示例中,它只是线性的。

    【讨论】:

    • 说多重回报不好的人是“结构化编程”嬉皮士。假设有多个返回语句会使代码难以阅读。我认为这个例子突出了一个事实,这并不总是正确的。
    【解决方案2】:

    多重回报:人员和编码标准是双向的。在 C++ 中,imo 没有理由不喜欢多个返回(和异常),如果您使用的是 RAII 等。许多 C 编码标准强制执行单入口单出口以确保执行所有清理代码,因为没有RAII 和基于范围的销毁/资源清理。

    Should a function have only one return statement?

    【讨论】:

      【解决方案3】:

      我更喜欢预先进行输入验证,但通常会一次执行所有测试

      if(!p || !p->getB()){
          return;
      }
      

      如果函数需要输入,并且语言支持它,我会抛出 Argument Exceptions(请参阅 .NET ArgumentNullException、ArgumentException),以便代码在无效状态的情况下不会执行干净的返回。这让调用者知道参数不足以完成操作。

      【讨论】:

      • 这和 NullPointerException 有什么不同?
      • 输入先决条件不限于指针非空的情况
      【解决方案4】:

      我更喜欢第一个,因为我有一组宏来处理常见情况(并且通常同时断言用于调试目的)。这种方法的缺点是您的函数返回类型必须相当统一才能使您的宏使用统一;好处是用法/效果非常清楚。因此,例如,我的代码可能是:

      class A;
      void fun(A* p)
      {
        ASSERT_NONZERO_RETURN_ON_FAIL( p );
      
        B* pB = p->getB();
        ASSERT_NONZERO_RETURN_ON_FAIL( pB );
      
        .......
      }
      

      这会产生更易读的代码,同时也会提醒您注意错误。此外,作为额外的好处,如果您发现您认为边际速度增加的价值超过运行时检查的价值,您可以轻松地禁用签入发布版本。

      补充说明:根据我的经验,有些人说函数的多个返回点不好的原因是因为他们在函数退出之前明确地进行资源取消分配,因此您不想重复取消分配代码.但是,如果您正确且统一地使用 RIIA,这应该不是问题。因为这也是我一直尝试做的事情,所以多个返回点对我来说比嵌套更可取。

      【讨论】:

        【解决方案5】:

        我倾向于在方法的开头使用这些类型的检查和返回。 所以我有点效仿 Eiffel 喜欢 design by contract preconditionsassertions, 如中所述 Bertrand Meyer'sObject-Oriented Software Construction, second edition, Prentice Hall.

        对于您的示例,我不会返回 void,而是返回 识别违规的枚举,例如:

        enum Violation { inviolate = 0, p_unspecified_unavailable, p_B_unspecified_unavailable,..... };
        
        Violation fun(A* p)
        {
        //__Preconditions:___________________________________
          if (! p)         return p_unspecified_unavailable:
          if (! p->getB()) return pB_unspecified_unavailable;
        //__Body:____________________________________________
           ....
        //__Postconditions: _________________________________
           ..(if any)..
           return inviolate;
        }
        

        我的意思是,我考虑前置条件(和任何后置条件)验证 成为方法/函数主体的实现的包装,并倾向于 将控制条件逻辑流与主体表达式区分开来。

        【讨论】:

        • assert() 将中止程序。简单地不继续执行该函数可能是完全有效的(在这种情况下,显然空指针表示没有要操作的对象,而不是错误)。我的意思是返回一些原因。在这种情况下,有两个可能的原因。
        【解决方案6】:

        我使用您发布的第一种方法,在大多数情况下,我可以向用户提供:有意义的返回值或消息:

        
        btnNext_OnClick(...)
        {
        if(!ValidateData())
         return;
        
        // do things here
        }
        
        
        boolean ValidateData()
        {
        if(!condition)
        {
         MessageBox.Show("Message", "Title", MessageBoxButtons.OK, MessageBoxIcons.Information);
         return false;
        }
        
        return true;
        }
        

        这样,当我添加一个需要表单级别验证的字段时,我可以在验证方法中添加另一个 if 语句检查。

        【讨论】:

          【解决方案7】:

          理想情况下,您会通过引用传递,然后您就不必担心这一点。不过,这并不总是一种选择。否则,这是一个偏好问题。

          我个人更喜欢第一个,因为我对多次退货没有太大问题。我认为函数基本上应该适合一两个屏幕,所以只要你有语法突出显示它应该不难看到所有函数的退出点。

          【讨论】:

            猜你喜欢
            • 2010-10-03
            • 1970-01-01
            • 1970-01-01
            • 2016-03-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多