软件未来预测的准确性:简单性与复杂性的博弈

本文探讨了软件设计中未来预测的准确性如何随系统复杂性而变化。通过Python版本变更等实例,阐述了保持代码简洁性对应对未来变化的重要性,并提供了实用的软件设计建议。

软件未来预测的准确性

关于软件设计,我们知道未来很重要。然而,我们也知道未来很难预测。

我认为我已经找到了一种方法来准确解释预测软件未来的难度。这个理论最基本的版本是:未来预测的准确性随着系统复杂性和试图预测的未来距离的增加而降低

随着系统变得越来越复杂,你能够准确预测的未来片段越来越小。随着系统变得更简单,你能够准确预测的未来越来越远。

例如,预测“Hello, World”程序在未来的行为相当容易。它很可能在运行时继续打印“Hello, World”。请记住,这是一个滑动比例——类似于你能对未来情况说多少的概率。你可以99%确定两天后它仍将以相同的方式工作,但仍然有1%的几率不会。

然而,在某个时间点之后,即使是“Hello World”的行为也变得不可预测。例如,2000年Python 2.0中的“Hello World”:

1
print "Hello, World!"

但如果你尝试在Python 3中运行它,将会出现语法错误。在Python 3中应该是:

1
print("Hello, World!")

你在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中完全有效):

1
2
for (int i = 1; i < 10; i++) { // 做一些事情 }
for (int i = 1; i < 20; i++) { // 做其他事情 }

在VC++中,它过去常常抱怨你试图在同一作用域中定义“i”两次。

patience 说:2016年6月22日 凌晨1:21 为了保持对未来变化的准确性……开发人员应该使整个编码简单明了。

Industrial Computers 说:2016年9月30日 凌晨4:44 你完全正确,试图预测你未来需要做的事情会犯更多错误。更重要的是为现在的需求做对——而不是为未来几年可能需要的需求。

好文章!

Mike Daigle 说:2020年1月14日 上午11:14 我(尊重地)不同意这篇文章的前提。在软件行业20年后,我目睹了同样的错误一遍又一遍地犯,伴随着同样的说法。“无法预测未来,兄弟”。这是对问题(过度开发)过度反应的典型情况。

反对“为未来构建”的论点变成了“无法知道所有场景”,所以算了。放弃并构建不可扩展的代码。

我的哲学是,前期的一点准备可以节省未来大量的重构。例如:

  • 构建你的代码,使其分离,以便可以轻松地重构为服务。你需要在第一天就使其面向服务吗?不。你最终需要吗?可能。我去过的每家公司都遇到了那堵墙,他们有无法构建、无法部署的大规模代码巨石,并且有半年的时间来重构。
  • 构建代码/数据库,使其可分片。同样,第一天分片,可能不需要。第1000天你可能需要。

这些只是例子,所以不要纠结于解决方案。关键是,工程是使用数据预测和构建未来的科学。我们当然(希望)足够聪明和受过教育,能够收集数据并做出一些合理的预测。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计