未来预测的准确性
关于软件设计,我们知道未来很重要。然而,我们也知道未来很难预测。
我认为我已经找到了一种方法来准确解释预测软件未来的难度。该理论最基本的版本是:未来预测的准确性与系统的复杂性以及你试图预测的未来距离成反比。
随着系统变得越来越复杂,你能够准确预测的未来部分越来越小。随着系统变得更简单,你能够准确预测的未来越来越远。
例如,预测“Hello, World”程序在未来的行为相当容易。它很可能在运行时继续打印“Hello, World”。请记住,这是一个滑动标尺——类似于你能对未来情况说多少的概率。你可以99%确定两天后它仍然以相同的方式工作,但仍然有1%的几率不会。
然而,在某个时间点之后,即使是“Hello World”的行为也变得不可预测。例如,2000年Python 2.0中的“Hello World”:
|
|
但如果你尝试在Python 3中运行它,会出现语法错误。在Python 3中,它是:
|
|
你在2000年无法预测到这一点,即使你预测到了,你也无能为力。对于这样的事情,你唯一的希望是保持系统足够简单,以便轻松更新它以使用新语法。不是“灵活”,不是“通用”,而是简单易懂和易于修改。
实际上,上述规则有一个更扩展的逻辑序列:
- 预测未来的难度相对于系统及其环境在试图预测的未来期间发生的总变化量而增加。(请注意,环境的影响与其与系统的逻辑距离成反比。)
- 系统将经历的变化量相对于该系统的总复杂性。
- 因此:预测变得困难的速率相对于试图预测行为的系统的复杂性而增加。
现在,尽管有这条规则,我想警告你不要根据你认为未来会发生的事情来做出设计决策。请记住,所有这些事件都是概率性的,任何预测都包括出错的可能性。当我们只关注现在、我们拥有的数据以及我们现在的软件系统时,我们比试图预测软件未来走向时更有可能做出正确的决策。软件设计中的大多数错误都源于假设你将来需要做某事(或永远不做某事)。
这条规则有用的时机是当你有一些软件,随着未来的发展你无法轻易更改它。你永远无法完全避免变化,但如果你将软件简化到愚蠢、简单的水平,那么你就不太可能需要更改它。随着时间的推移,它的质量和实用性可能仍然会下降(因为你没有更改它以应对不断变化的环境的需求),但它的下降速度会比非常复杂的系统慢。
理想情况下,我们能够随时更新我们的软件,这是真的。这是网络的一大承诺,我们可以即时更新我们的网络应用程序和网站,而不必要求任何人“升级”。但这并不总是适用于所有平台。有时,我们需要创建一些代码(如API),这些代码必须存在十年或更长时间,且几乎不做更改。在这种情况下,我们可以看到,如果我们希望它在遥远的未来仍然有用,我们唯一的希望是简化。否则,我们就是在为用户构建未来的不愉快体验,并注定我们的系统会过时、失败和混乱。
这一切有趣的部分是,编写简单的软件通常比编写复杂的软件花费更少的工作。它有时需要更多的思考,但总体上通常花费更少的时间和精力。因此,让我们为自己、为用户、为未来赢得胜利,并尽可能合理地保持简单。
-Max
评论
Simon 说: 2013年1月13日晚上11:42 嗯,你可以花费大量精力设计和实现一个系统来适应未来可能发生的任何情况变化。这可能是确保这种变化永远不会发生的唯一可能方式……
Max Kanat-Alexander 说: 2013年1月13日晚上11:44 不,那是不可能的。未来是无限复杂的,而软件不能是。试图这样做是导致过度复杂、无法维护的系统的主要原因之一。
Max Kanat-Alexander 说: 2013年1月13日晚上11:45 当你尝试这样做时,你真正得到的是为未到来的未来增加的大量额外复杂性,以及现在为真正出现的未来适应它的大量工作。
Nick Barnes 说: 2013年1月14日上午5:47 当然,Python 2代码在Python 2中仍然有效,包括2.7.3。Python 2.0中有一些东西在2.7中不起作用,但这不在其中。Python 3是故意不兼容的。 我认为语言实现者和设计者的文化自1980年代以来有点错误——过去非常重视向后兼容性。例如,任何值得称道的C编译器最好编译“ANSI C”而没有任何语义变化。我写“ANSI C”,技术上含糊,因为这仍然是C程序员用来表示ISO/IEC 9899:1990的简写,该标准大约在1987年确定。C编译器大多也能很好地处理“K&R C”,1973年的版本,尽管有更多变化,因为语义没有如此明确地确定。其他一些1970年代和1980年代的语言实现也是如此(例如Common Lisp:我使用的Lisp实现严格符合1994年Common Lisp标准,这是1980年代中期开始的收敛标准过程的结果)。实现已经发展,并且也能处理其他语言(例如C11),但没有以任何方式放弃或损害对20多年前定义的语言的支持。这是语言如Python的实现者应该效仿的巨大优势。
Max Kanat-Alexander 说: 2013年1月17日晚上11:03 所以你知道,曾经,我写了两篇关于几乎完全相同主题的博客文章。(一般是向后兼容性,但它与你所说的相关。) http://www.codesimplicity.com/post/ways-to-create-complexity-break-your-api/ http://www.codesimplicity.com/post/when-is-backwards-compatibility-not-worth-it/ 事实上,我认为C保持的向后兼容性水平在此时可能是一个弱点。你越固定一个软件,随着时间的推移,它就越会因无法调整或改进而退化。你当然正确,向后兼容性对开发人员来说是一个福音,不应该无故破坏。但正如我在上述两篇文章中试图传达的,最终决定应该是在现有用户的简单性和未来用户的简单性之间取得平衡。 -Max
Lyman Hurd 说: 2013年2月20日下午2:08 根据那个标准,Microsoft Visual C++失败了相当长一段时间。直到最近它才与for循环的迭代器具有其封闭循环的范围一致(即,以下在VC++中不合法,但在ANSI中完全有效):
|
|
在VC++中,它过去常常抱怨你试图在同一作用域中定义“i”两次。
patience 说: 2016年6月22日凌晨1:21 为了保持对未来变化的准确性……开发人员应该使整个编码简单明了。
Industrial Computers 说: 2016年9月30日凌晨4:44 你完全正确,试图预测你将来需要做的事情会犯更多错误。更重要的是为现在需要的做对——而不是几年后可能需要的。 好文章!
Mike Daigle 说: 2020年1月14日上午11:14 我(尊重地)不同意这篇文章的前提。在软件行业20年后,我目睹了同样的错误一遍又一遍地发生,伴随着同样的陈述。“无法预测未来,兄弟”。这是对问题(过度开发)的典型过度反应。反对“为未来构建”的论点变成了“无法知道所有场景”,所以算了。放弃并构建不可扩展的代码。 我的哲学是,前期的一点准备可以在未来节省大量重构。例如:
- 构建你的代码,使其隔离,以便可以轻松重构为服务。你需要在第一天就使其面向服务吗?不。你最终需要吗?可能。我去过的每家公司都遇到了无法构建、无法部署的大规模单体代码墙,并且有半年的时间来重构。
- 构建代码/数据库,使其可分片。同样,第一天分片,可能不。第1000天你可能需要。 这些只是例子,所以不要纠结于解决方案。关键是,工程是使用数据预测和构建未来的科学。我们当然(希望)足够聪明和受过教育,可以收集数据并做出一些合理的预测。