我刚刚遇到了这个关于初始化局部变量的问题。许多答案都在讨论简单性/可读性与鲁棒性。作为(远程部署的)嵌入式系统的开发人员,我总是喜欢健壮性并且倾向于遵循几个看似相互矛盾的规则:
以允许设备继续运行的方式,尽最大努力处理每个错误。
将代码设计为在发生编程或致命错误后尽快失败。
我们都接受过验证输入的培训,以防止设备因用户(或其他外部)输入而损坏;始终假设数据可能无效并相应地对其进行测试。
您还遵循哪些其他具体做法来确保稳健性? 示例很有帮助,但我也对普遍适用的技术感兴趣。
我刚刚遇到了这个关于初始化局部变量的问题。许多答案都在讨论简单性/可读性与鲁棒性。作为(远程部署的)嵌入式系统的开发人员,我总是喜欢健壮性并且倾向于遵循几个看似相互矛盾的规则:
以允许设备继续运行的方式,尽最大努力处理每个错误。
将代码设计为在发生编程或致命错误后尽快失败。
我们都接受过验证输入的培训,以防止设备因用户(或其他外部)输入而损坏;始终假设数据可能无效并相应地对其进行测试。
您还遵循哪些其他具体做法来确保稳健性? 示例很有帮助,但我也对普遍适用的技术感兴趣。
我喜欢第二种方法:在我编写并测试了一些关键代码之后,我有时会要求同事专门审查它,以寻找破解它的方法。
以这种方式激发人们的创造力很有趣。:-)
听起来您已经掌握了这两个:
快速失败。http://en.wikipedia.org/wiki/Fail-fast
Fail Safe。http://en.wikipedia.org/wiki/Fail-safe
这三个可能比任何其他都更适合我:
尽可能避免状态。创建和使用不可变对象——它们更容易测试并且不太可能背叛你。
不要编写不必要的代码。这个很难。查看最近大量关于Strunk 和 White的“ The Elements of Style ”与编程相关的文章。
每 10 分钟左右问问自己:“这很愚蠢吗?” 说实话。这个更难。
-杰森
我尽量使用契约式设计。但我发现这在我的工作领域很少实用。
我喜欢不初始化局部变量。它们应该在需要时设置。否则,阅读您的代码的程序员可能会像在"hmm why is this 0 at the beginning..."
. 如果你不初始化它,很明显它还没有被使用。
我实施了多种方法来防止错误并从错误中恢复:
1)处理所有异常。正如您所说,“处理每个错误”。如果用户单击表单上的按钮,则应用程序应该不可能从未处理的异常中消失(“噗”)。出于这个原因,我用一个通用的 try catch 包装了事件处理程序。
2) 使用完整的堆栈跟踪记录错误。当我重新抛出异常时,我总是创建一个新异常并将捕获的异常添加为内部异常。我的日志记录代码递归地解包消息,这提供了比其他方式更多的细节。
3) 决定你的类是否可以重用。如果不记录它。考虑在你的代码中实现一个接口,比如IRestartable
or IReusable
。任何没有实现它的对象都必须在使用一次后丢弃。
4) 永远不要假设线程安全。我已经了解到 .NET 是极其多线程的。许多事件在任意线程上处理。用 .NET 编写的给定应用程序可以同时执行许多线程,并且没有一行代码显式创建一个。
5)保持变量范围尽可能窄。在使用对象的位置附近实例化对象,而不是在方法开头的大块中实例化对象。您可能会缩短对象的寿命,并且您不会忘记位于类或方法顶部的巨大块中的不需要或重用的变量。
5)简单,但我仍然看到它发生。避免像瘟疫这样的全局变量。我见过包含数百个未使用/重用变量的代码。弄清楚和重构是一团糟。
我喜欢...在 (java)doc 中记录限制值。(参数可以为空吗?为空?)
这样,当我的代码被使用时(或者当我使用我自己的代码时),我知道我可以期待什么。简单的建议,但很少实施;)
该文档还包括对静态异常和运行时异常的明确区分。这样,如果程序必须尽快失败以提高健壮性,我知道它是否由于可预见的异常(静态,必须在编码时通过捕获或重新抛出处理)而失败,或者如果这是因为参数不正确(运行时异常,仅在应用程序生命周期内检测到)。
如果两种类型的异常都被清楚地记录下来(尤其是在参数的限制值方面),那么整体的稳健性就更容易实施。
如果可能的话,我会询问专门从事其他事情的人的意见。这通常会揭示一种或多种全新的破坏方式。
偏执是程序员的生存特征。不断问自己这怎么会失败?然后试着弄清楚如何防止这种失败。
我编写的系统(用 C 语言)对性能和可靠性有很高的要求。为失败而设计很好。当涉及到安全设计时,事情变得更加复杂。
硬件总是会发生故障,除此之外,您还会遇到从外部进入系统的无效数据的问题。我在这些领域的解决方案通常是精心设计的(为安全而设计),而在几乎所有其他领域的设计中,基本上都是通过极其简单的代码和根本没有数据验证来设计失败的。
所以你可以说我在风险小的时候为失败而设计,在风险高的时候为安全而设计。
对于调试,我有时会编写不应该输入的条件代码,其中包含除以零或一些这样的内容,这样当处理进入那里时,调试器将立即被调用。
我通常不会初始化本地堆栈变量,因为编译器会告诉我哪些需要初始化,哪些可以完全省略。
代码审查很棒,但并非万无一失。我曾经花了几个小时查看一个小的(无限的)循环(很尴尬,不是吗?)并且看不到索引没有增加。我的同事也没有看到。当我最终查看生成的代码清单时,我看到循环测试已被优化为单个无条件跳转,然后我才明白问题出在哪里。