【问题标题】:Best Practices for Robustness [closed]稳健性的最佳实践 [关闭]
【发布时间】:2010-09-21 22:33:47
【问题描述】:

我刚刚遇到this question 关于初始化局部变量的信息。许多答案都在讨论简单性/可读性vs. 健壮性。作为(远程部署的)嵌入式系统的开发人员,我总是喜欢健壮性并且倾向于遵循一些看似相互矛盾的规则:

  • 以允许设备继续运行的方式尽最大努力处理每个错误。

  • 将代码设计为在发生编程或致命错误后尽快失败。

我们都接受过验证输入的培训,以防止设备因用户(或其他外部)输入而损坏;始终假设数据可能无效并相应地对其进行测试。

您还遵循哪些其他特定实践来确保稳健性?示例很有帮助,但我也对普遍适用的技术感兴趣适用。

【问题讨论】:

    标签: robustness


    【解决方案1】:

    我尽量使用Design by contract。但我发现它在我的工作领域很少实用。

    【讨论】:

    • 由一个开发人员负责时很难执行此操作,但我们尝试先定义我们的接口。如果不出意外,它可以轻松编写测试用例和独立开发模块。
    • DBC 也最好与原生支持它的语言一起使用,这在许多人的生产环境中并不常见。
    • 遗憾的是,我发现 DbC 仅在某些情况下有用并用于发现一些问题。现在我对 GUI 进行编程,然后程序不会因为输入错误而真正失败。但我确实发现 DbC 可以很好地记录应该如何使用代码,即使没有原生支持。
    【解决方案2】:

    我是“The Pragmatic Programmer”中描述的技术的粉丝。我也使用TDD,而不是 DBC,因为我发现它更灵活、更高效。例如,“pragprog”中描述的一些技术包括:

    • 经常测试。尽早测试。自动测试
    • 不要重复自己
    • 使用破坏者来测试您的测试
    • 对异常问题使用异常
    • 不要忍受破碎的窗户
    • 不要使用手动程序

    它们似乎都是常识,但令人惊讶的是,团队在面临最后期限时偏离这些基本原则的速度之快。

    【讨论】:

    • 我正在游说采用 TDD 和基于证据的调度,因为它们都迫使我们与现实合作,而不是假设。谢谢指点。
    【解决方案3】:

    我喜欢初始化局部变量。它们应该在需要时设置。否则,阅读您的代码的程序员可能会像"hmm why is this 0 at the beginning..." 一样感到困惑。如果你不初始化它,很明显它还没有被使用。

    【讨论】:

    • 我会抓住这个机会 - 并通过评论来缓解它 - 因为我花了太多时间来寻找由未初始化变量导致的“跳弹”错误。
    • 正确测试应该可以避免初始化错误。
    • 我同意。不要将变量初始化为从未使用过的值。未使用的初始值是不必要的代码,可能会引起混淆。
    【解决方案4】:

    我实施了多种方法来预防和从错误中恢复:

    1) 处理所有异常。正如您所说,“处理每个错误”。如果用户单击表单上的按钮,则应用程序不可能从未处理的异常中消失(“噗”)。出于这个原因,我用通用的 try catch 包装了事件处理程序。

    2) 使用完整堆栈跟踪记录错误。当我重新抛出异常时,我总是创建一个新异常并将捕获的异常添加为内部异常。我的日志记录代码递归地解包消息,这提供了比其他方式更详细的信息。

    3) 决定你的类是否可以重用。如果不记录它。考虑在您的代码中实现一个接口,例如IRestartableIReusable。任何没有实现它的对象在使用一次后必须丢弃。

    4) 永远不要假设线程安全。我已经了解到 .NET 是极其多线程的。许多事件在任意线程上处理。用 .NET 编写的给定应用程序可以同时执行许多线程,并且没有一行代码显式创建一个。

    5) 尽可能缩小变量范围。在使用对象的位置附近实例化对象,而不是在方法开头的大块中实例化对象。您可能会缩短对象的生命周期,并且不会忘记位于类或方法顶部的大块中的不需要或重用的变量。

    5) 很简单,但我仍然看到它正在发生。避免像瘟疫这样的全局变量。我见过包含数百个未使用/重用变量的代码。弄清楚和重构是一团糟。

    【讨论】:

    • 感谢您提供的一些非常有用的建议,您显然已经通过艰辛的方式学到了!
    • 奇怪的是,您在事件处理程序中搜索哪个异常?
    • @Jim,如果 try/catch 的目的是防止未处理的异常,我会捕获 System.Exception。如果我试图过滤掉特定的异常,因为我要么想忽略它们,要么做其他事情,我把它们放在第一位。关键是,我不希望在用户按下按钮时导致程序崩溃的未处理异常...我知道有些人声称捕获 System.Exception 是错误的形式,我不总是 同意。
    【解决方案5】:

    听起来你已经掌握了这两个:
    快速失败。 http://en.wikipedia.org/wiki/Fail-fast
    故障保险。 http://en.wikipedia.org/wiki/Fail-safe

    这三个可能比任何其他都更适合我:

    尽可能避免状态。创建和使用不可变对象——它们更容易测试并且不太可能背叛你。

    不要编写不必要的代码。这个很难。查看最近大量关于 Strunk 和 White 的“风格元素”与编程相关的文章。

    每隔 10 分钟左右问问自己:“这很愚蠢吗?”说实话。这个更难。

    -杰森

    【讨论】:

    • 感谢您的好建议。我的导师喜欢说,“当你通过删除代码来改进它时,你知道你的软件已经开始成熟了。”
    【解决方案6】:

    我喜欢... 记录limit values in (java)doc。 (参数可以为空吗?为空?)

    这样,当我的代码被使用时(或当我使用我自己的代码时),我知道我可以期待什么。简单的建议,但很少实施;)

    该文档还包括static and runtime exceptions 的明确分隔。这样,如果程序必须尽快失败以提高健壮性,我知道它是否由于可预见的异常(静态,必须在编码时通过捕获或重新抛出处理)而失败,或者如果这是因为参数不正确(运行时异常,仅在应用程序生命周期内检测到)。

    如果清楚地记录了这两种异常(尤其是在涉及参数的限制值时),则整体稳健性更容易实施。

    【讨论】:

    • 我仍然惊讶于如此简单的技术很少使用,尤其是当您认为它们本质上是“免费的”并且使用它们的成本可能会毁掉一家公司。他们说工程师通常不是赌徒! :-)
    • 是的......我看到公共函数的 javadoc 的次数(甚至没有仔细记录限制值或异常)仍然非常有限(叹息)。
    • 记录值的限制和例程的任何其他限制,但无论如何都要验证这些值。
    【解决方案7】:

    我喜欢第二双眼睛方法:在我编写并测试了一些关键代码之后,我有时会要求同事专门审查它以寻找打破它的方法。

    以这种方式激发人们的创造力很有趣。 :-)

    【讨论】:

    • 代码审查在我的工作中是强制性的。
    【解决方案8】:

    如果可能的话,我会询问专门从事其他工作的人的意见。这通常会发现一种或多种全新的破坏方式。

    【讨论】:

      【解决方案9】:

      偏执是程序员的生存特质。不断问自己这怎么会失败?然后试着弄清楚如何防止这种失败。

      【讨论】:

        【解决方案10】:

        我(用 C 语言)编写的系统对性能和可靠性有很高的要求。为失败而设计很好。当涉及到安全设计时,事情变得更加复杂。

        硬件总是会出现故障,除此之外,您还会遇到无效数据从外部进入系统的问题。我在这些领域的解决方案通常是精心设计的(为安全而设计),而在几乎所有其他领域的设计中,基本上都是通过极其简单的代码和根本没有数据验证来设计失败。

        所以你可以说我在风险小的时候设计失败,在风险高的时候设计安全。

        为了调试,我有时会编写不应该输入的条件代码,其中包含除以零或一些这样的内容,这样当处理进入那里时,调试器将立即被调用。

        我通常不初始化本地堆栈变量,因为编译器会告诉我哪些需要初始化,哪些可以完全省略。

        代码审查很棒,但并非万无一失。我曾经花了几个小时查看一个小的(无限的)循环(很尴尬,不是吗?)并且看不到索引没有增加。我的同事也没有看到。当我最终查看生成的代码清单时,我发现循环测试已优化为单个无条件跳转,然后我才明白问题所在。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-11-10
          • 2013-10-02
          • 2010-10-06
          • 2011-10-28
          • 2010-09-10
          • 2011-10-11
          • 1970-01-01
          • 2014-04-26
          相关资源
          最近更新 更多