编写健壮的代码与过度设计


33

你们怎么知道您正在编写最强大的代码而又不会过度设计?

我发现自己对我的代码可以采用的每条可能路径都考虑得太多了,有时感觉就像是在浪费时间。我想这取决于您正在编写的程序类型,但是我不想花太多时间考虑永远不会发生的情况。


2
在Agda2中进行编码
-SK-logic

一个具体的例子将极大地帮助你阐明观点。:)
若昂·波特拉

我能否仅检查一下您是否真正在询问鲁棒性,即“在无效输入或压力环境条件下系统继续工作的能力”,因为有些答案似乎认为您在谈论可扩展性。
DJClayworth 2011年

我在疯狂的期限内工作,而且它是用于演示软件的,所以我可以在没有完美瘫痪的情况下愉快地快速破解。
工作

1
下面是一篇文章,关于专题讲座:code-tag.com/2017/04/02/...

Answers:


39

你们怎么知道您正在编写最强大的代码而又不会过度设计?

您认为健壮的代码是什么?代码已经过时了,而且功能强大到可以处理任何情况?错误的,没有人能预测未来!再犯一次,因为这将是一个复杂的,难以维护的混乱。

我遵循各种原则:首先是YAGNI(尚未)和KISS,所以我不会编写不必要的代码。这也有效地防止了过度设计。当需要扩展时,我可以重构应用程序。现代的重构工具使您可以轻松地创建接口,并在以后需要时交换实现。

然后,我尝试使我编写的代码尽可能健壮,其中包括消除程序可能采取的尽可能多的路径(以及声明状态)以及一些Spartan编程。一个很大的帮助是“原子”功能/方法,它们不依赖外部状态,或者至少在失败时不会使程序处于不一致状态。如果做得好,最终也不会出现意粉代码,这也是可维护性的一大福音。同样,在面向对象的设计中,SOLID原理是鲁棒代码的重要指南。

我真的发现,通常您可以通过深思如何将其设计为尽可能最直的路径来降低复杂性,例如程序路径或状态的组合爆炸。通过选择子例程的最佳顺序并为此目的进行设计,尝试将可能的组合保持在最低限度。

健壮的代码通常总是简单干净的代码,但是简单性是一个不容易实现的特征。但是,您应该为此而努力。总是尽可能地编写最简单的代码,并且在别无选择时只会增加复杂性。

简单性很强,复杂性很脆弱。

复杂性杀死。


2
因为它没有大量的类,工厂和抽象。这是一个悖论,但有些人喜欢这种东西。不知道为什么。
编码器

5
这是斯巴达!!!
Tom Squires

4
二十年来没有这样做的人不知道复杂性会如何杀死您。他们认为自己很聪明。他们笨,不聪明。这种复杂性将使您丧命。
PeterAllenWebb 2011年

1
健壮性不是为了适应未来的发展,而是要在无效输入或压力环境下继续发挥作用-代码完整p464。
DJClayworth 2011年

5
除非发问者使用的“鲁棒性”与我理解的含义不同,否则您将回答一个不同的问题。他不是在问“我是否应该编写代码以适应将来的需求”,而是在问“我应该处理哪些异常输入情况”。YAGNI,KISS和SOLID均不相关。您是否需要允许一百万个用户尝试同时登录?如果登录名以反斜杠开头会怎样?YAGNI不会回答这些问题。
DJClayworth 2011年

8

我努力保持平衡,专注于

  • 处理现有用例中的所有可能的执行路径(这是“稳健性”部分),
  • 启用功能/需求我很确定在可预​​见的将来会实现这些功能,并且
  • 我从经验中知道,代码库的长期可维护性(即保持代码干净和可测试)将需要这些知识。

这是一个模糊的边界区域-有时我设法做一些不必要的工作,有时我做不到的事后来证明是必要的。如果错过的机会不大,我很好。无论如何,我都努力从错误中学习。


这里有一个很好的例子,它可以处理现有用例中的所有可能执行路径
CodeYogi

5

健壮和过度工程之间的区别是优雅地处理所有可能的用例之间的差异,即使是应该避免的奇异和附带的用例也是如此。当我优雅地讲时,用户输入一个奇怪的异常案例或遇到一种情况,即需要一个未定义的不受支持或未指定的功能,并且该代码可以正常退出而不崩溃或通知用户不支持的功能。

另一方面,过度设计可能落入完全实现不需要或不需要的功能的领域(客户确实需要但从未要求过某些功能!),或者可以通过得出过于复杂的设计或过于复杂的方式来定义它代码来处理一个相对简单的问题。


4

1)获取要求。

2)编写最少的代码以满足要求。如果有歧义,请进行有根据的猜测。如果超级模棱两可,请返回1。

3)发送到测试。

4)如果测试人员表示良好,则记录批准。如果出现问题,请返回1。

专注于通过测试,而不是预测测试。如果您没有测试人员...请获取测试人员!它们不仅对验证代码的正确性至关重要,而且对于整个开发过程也至关重要。


1
+1表示专注于通过测试,而不是预测测试,但是由于缺乏强大的业务分析人员,因此像我这样的许多开发人员都希望两者都能做。
maple_shaft

@maple_shaft-非常正确。关键是这些问题是由于其他人的无能而引起的。强调别人的工作是倦怠的道路。如果我的公司愚蠢到足以让我做该月的应收帐款,但如果结果不好的话,我对自己也不会太失望。定义需求通常只需要描述您的日常工作,以便可以将其自动化。如果没有一个员工能做到这一点,那么……公司可能会遇到麻烦。
Morgan Herlocker 2011年

3

首先,请尽可能保持数据标准化(非冗余)。如果对数据进行了完全规范化,则对数据的任何一次更新都不会使其不一致。

您无法始终保持数据规范化,换句话说,您可能无法消除冗余,在这种情况下,冗余状态可能会不一致。然后要做的是容忍这种不一致,并使用某种程序对其进行定期修复并对其进行修补,以对其进行定期修复。

强烈倾向于通过通知来紧密管理冗余。这些不仅很难确定它们是正确的,但可能会导致巨大的效率低下。(写通知的部分诱惑是因为在OOP中实际上鼓励这样做。)

通常,任何依赖于事件,消息等的时间顺序的事物都将很容易受到攻击,并且需要大量的防御性编码。事件和消息是具有冗余性的数据的特征,因为它们将更改从一个部分传递到另一部分,以防止不一致。

就像我说的那样,如果您必须有冗余(并且机会一定很不错),那么最好是能够a)容忍,并b)对其进行修复。如果您试图仅通过消息,通知,触发器等来防止不一致,则很难使其健壮。


3
  • 编写以供重用。
  • 编写测试。琐碎的,非琐碎的,有些荒诞的,复杂的,看看它在这种情况下如何处理。测试也将帮助您确定界面的形式。
  • 编写程序以使其失败(例如,断言)。我的代码具有大量的重用性,并且我测试了大量的情况-比实际实现(基于行数)的错误检查/处理更多。
  • 重用。
  • 立即修复出问题的地方。
  • 从经验中学习和建立。

错误会随之而来,但是(很幸运)它们会被本地化,并且(在大多数情况下)它们会在测试的很早就出现。重用的另一个好处是,客户端/调用者可以使用实现带来的错误来保存大多数错误检查/支架。

然后,您的测试应定义程序的功能及其强大程度-不断添加测试,直到您对成功率和投入感到满意为止;根据需要进行改进,扩展和强化。


2

我通过编写定义良好但不一定是最佳行为的代码来实现这种区分,这种代码的执行可能性极小。例如,当我非常确定(证明但未经测试)矩阵将是正定的时,我在程序中插入一个断言或异常以测试状态,但不为其编写自己的代码路径。因此,行为已定义,但次优。


2

鲁棒性:在无效输入或压力环境条件下,系统继续运行的程度。(代码完成2,p464)

这里的重要问题是询问坚固性对您有多重要。如果您是Facebook,那么当有人在输入中输入特殊字符时,让您的网站继续运行,以及同时登录1亿用户时服务器保持正常运行,这一点非常重要。如果您正在编写脚本来执行只有您才能执行的常见操作,则不必在意。之间有很多关卡。判断所需的鲁棒性是开发人员应学习的重要技能之一。

YAGNI的原理适用于添加程序可能需要的功能。但是该原理不适用于鲁棒性。程序员往往高估了将需要给定的将来扩展的可能性(尤其是如果它很酷),但是他们却低估了出错的可能性。同样,如果发现之后需要省略的功能,则程序员可以稍后编写它。如果发现毕竟需要进行省略的错误检查,则可能造成损坏。

因此,实际上最好还是在检查异常错误情况时进行检查。但是有一个平衡点。在这种平衡中需要考虑的一些事项:

  • 该错误多久发生一次?
  • 发生此错误的代价是什么?
  • 这是内部使用还是外部使用?

别忘了人们可以并且会尝试以意想不到的方式使用您的程序。最好是在发生某些可预见的事情时发生。

作为最后一道防线,请使用断言或关闭。如果发生无法解决的事情,请关闭程序。这通常比允许程序继续执行不可预测的事情要好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.