未来预测的准确性与软件复杂度的关系

本文探讨了软件系统复杂度对未来预测准确性的影响,指出简单性设计能提高长期可维护性。通过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年无法预测到这一点,即使你预测到了,你也无能为力。对于这样的事情,你唯一的希望是保持系统足够简单,以便你可以轻松更新它以使用新的语法。不是“灵活”,不是“通用”,而是简单地易于理解和修改。

实际上,上述规则有一个更扩展的逻辑序列:

  1. 预测未来的难度随着在试图预测的未来时间内系统及其环境中发生的总变化量而增加。(注意,环境的影响与其与系统的逻辑距离成反比。)
  2. 系统将经历的变化量相对于该系统的总复杂度。
  3. 因此:预测变得困难的速率相对于试图预测行为的系统的复杂度而增加。

现在,尽管有这条规则,我想警告你不要围绕你认为未来会发生的事情来做设计决策。记住,所有这些发生都是概率,任何预测都包含出错的可能性。当我们只关注现在、我们拥有的数据以及我们现在的软件系统时,我们比试图预测我们的软件未来走向时更有可能做出正确的决策。软件设计中的大多数错误源于假设你未来需要做某事(或永远不做某事)。

这条规则有用的时机是当你有一些软件,随着未来的发展,你无法轻易更改它。你永远无法完全避免变化,但如果你将软件简化到愚蠢、简单的水平,那么你就不太可能需要更改它。它可能仍然会随着时间的推移在质量和有用性上下降(因为你没有改变它以应对不断变化的环境的需求),但它的下降速度会比非常复杂的情况慢。

理想情况下,我们能够随时更新我们的软件,这是真的。这是网络的一大承诺,我们可以即时更新我们的网络应用和网站,而不必要求任何人“升级”。但这并不总是适用于所有平台。有时,我们需要创建一些代码(如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 设计