Think in alternatives

本文翻译自 Scott Nonnenberg 的文章 《Think in alternatives》

译前记

当时选择翻译《Think in alternatives》这篇文章,是因为它给了我启发。我觉得这是一个开发者必经的思考成长过程,从「只会一个方法 / The one true way」到「多个看似正确的方案 / Multiple correct solutions」,然后是「协调权衡 / A classic continuum」,然后「增加思考的维度 / A few more axes」来帮助权衡筛选方案,最后是「创新 / Breaking out of the box」。这大概就是所谓的「守破离」的过程吧。

另外作者在最后对于如何与团队讨论决策也有心得,当你尽力考虑过所有方案时,以合适的时间在合适的地点提出来。参与到细节和全局,考虑到人和物,才能做出最正确的决策。

其实这篇翻译在我电脑里躺了一年……一年后再看,没有了当初阅读的欣喜,只觉得是很自然的想法,一切都在于「权衡」这个词。
二次校对和润色后,发现初次的翻译果然很糟糕,现在终于把还能入眼的译文发布出来了。

以下是译文。


有人曾称赞我是一个效率很高的开发者。于是我想分享我是如何做到的。欢迎阅读我的开发者提效技巧系列之五:Think in alternatives.[1]

你的解决方案也许可用。那么你是否考虑过其他可选方案?你是否能解释清楚为何比起其他方案,你选择了这个?

唯一正确的方式

多少次你与他人讨论技术解决方案时,听到“这是唯一的办法”或者“这是正确的方法”作为辩论的理由?如果从没到听过,那你真幸运。如果有,那这理由是否赢得了争论?如果是这样,你为何认为这是它是对的?

我初次产生这样的思考是源自于一个 90 年代末的网站 slashdot.org 。当时 Mac 与 Windows 系统之间正进行一场旷日持久的信仰之战。这是一场激烈又冗长的争论,并且偶尔会乱入“Linux 是最棒的!”这样的言论。我花了很长时间阅读这些信息,并以客观公正地收集了所有最棒的论点,并且最终我有了“正确”的答案。

这种思考的方式从学校生涯到软件编程职场一直伴随着我。但是,它并不是正确的。

认为只存在唯一客观的正确答案是错误的想法。当然,在一个有非常完整的上下文,条件定义详细的有限空间里,它或许是适用的。即使是 1+1 也有不止一个答案。例如,根据底数选择的不同:当底数为 2 ,答案就是 10,当底数为 10,那答案就是 2!

一旦人类行为代入这样的方程中,那可选项就会变得无穷无尽。

多个正确的方案?

我初次接触到关于无穷个可选方案的真理,是我在加州理工州立大学上的第一堂编程课。当时我们把自己的作业提交给一个“机器人”,它会对作业执行一连串的黑盒测试。如果提交失败,你要么得到一个简单的“失败”响应,要么得到一堆神秘的测试名称,别无他物。

我曾与同班的一些人约定比赛,争先第一个完成任务。当大家都完成后,我们又坐回来聚在一起。在学校宿舍无所事事时,某位朋友提出了一个非常有意思的想法:“我想知道我们的程序实现方法有什么不同?”

这是个很好的点子。我们都通过了大量的黑盒测试,而且这些测试都历经好几年的构建执行。在高度严苛的条件限制下,我们的实现方案会有多相似呢?由于我们有点担心与别人分享自己的代码,于是我们首先尝试写一些新的更庞大的测试案例,去看看我们的程序是否能继续通过测试。出乎我们的意料,我们的算法都不能应对更大容量的场景。我们的代码是错的吗?也许吧,但是我们也确实解决了手头上的所有问题。

后来,当课程即将结束时,我们最终互相公开了各自的代码。结果是它们非常非常的不一样。实际上,它们不一致到每个人难以理解其他人的代码实现。我后来没有深入研究下去,自始至终觉得:“这太陌生了!”。

典型的连续体 [2]

OK,对于任何问题都存在许多许多的解决方案。那么应该如何看待这样的事实呢?我们没有太多经验,因为这样的思维模式很难在教学中培养起来:对作业内容做出完全自由的解答,这既不能确保练习到目标概念,还会让作业评分会变得很困难。

因此,这是一项我们不得不自我训练的技能。

轴线 (axis) 是非常有用的工具,能帮助你产生备选方案。它是一段中间有无穷多个可选项的连续线段,并将一对相关的概念分别置于这轴线的两端。[3]

最经典的例子是内存占用与 CPU 时间,即使用有限的内存和处理器指令,来处理数据的程序方案。内存占用和 CPU 时间则处于这连续体的两端,因为这两个是此消彼长的,一般来说你不得不在这两者中做权衡。[4]

排序是一个完美的例子。大多数人会直接使用快排作为“正确”的解决方案。但是堆排序能保证最大时间开销是 O(n log n) 且内存占用为 O(n) ,然而快排使用 O(log n) 的内存开销,有着 O(n log n) 的平均时间开销,O(n^2) 的最大时间开销。

因此,对于堆排序,将消耗更少的 CPU 时间,但会消耗更多的内存占用。

一切都关乎如何权衡。

更多的轴线

内存占用与 CPU 时间只是其中一个我们用来考虑可能存在新的排序算法的维度。我们还可以考虑其他特征:

  • 它是否维护原有等值对象的顺序?
  • 它是否可以跨多个线程/CPU 核/机器并行 (parallelizable) 执行?
  • 是否存在一个算法,对于你预期输入规模有着良好的吞吐率?
  • 对于嵌入式或者其他有限制的环境,你或许要考虑内存比较的次数,或者写入内存的次数?

还有很多情况,这些对于排序算法是非常具体的。

在通常的软件开发中是否还有其他轴线?这有一个简单的列表。之后思考一下你曾经写过的代码。它会在这些轴线上处于哪个极端,还是处在中间的某个位置?

  • 你自己实现 vs 现成的组件 - 考虑使用开源世界中现有的组件不需要成本投入,但它并不总是那么简单。有件事是一定的——你自己从头实现的开销是很大的。
  • 最精简的代码 vs 易阅读的代码 - 当你把一个复杂的表达式重构成许多声明式的变量,函数会变得更长。
  • 简练的 vs 长的变量、函数、模块名 - 类似的,函数也可以看起来更庞大,也可能因为变量名太长需要占用很多行。
  • 文档 vs 自我解释的代码 - 有些人认为你可以在函数中准确命名一切事物,从而不用写文档;有些人不这么认为。
  • 有状态 vs 无状态 - 你需要随时间保持状态来达到你的目标吗?如果是这样,这些状态应该存在哪里?
  • 单元测试 vs 端到端测试 - 单元测试这段代码是否有意义?或者也许只用集成测试就行?
  • 面向对象设计 vs 函数式设计 - 基于简单的函数构建解决方案,还是必须把函数和数据都混入到“对象”里去?
  • 声明式 vs 命令式 - 你能通过声明来达成你的目标吗,例如用 SQL?或者你不得不人为定义每一步?
  • 静态类型 vs 动态类型 - 提前定义好你遇到的所有问题类型,这会有帮助吗?还是说一边学习问题领域的知识一边发现它们?
  • 安全 vs 易用 - 你需要两步验证吗?或者你甚至不需要用户登录?
  • 简单 vs 性能 - 为了达到绝对优秀的性能,偶尔进行特定的优化是必须的,那么代码会失去它原有的清晰和简单。
  • 硬编码 vs 自定义 - 源代码中的数字变量应该如何获取?是从配置文件里读取?还是从用户可配置的选项里读取?
  • YAGNI 原则 vs 可扩展性 - 如果只有一个类来实现它,那么你是否需要这个抽象接口?或者你是为了再次复用做准备?
  • DRY 原则 vs 代码碎片 - 如果你有一段代码哪都会用到,那么很容易理解这段代码在做什么。你可以在给定上下文中重复使用这段代码,这要比仔细思考全局来得简单。

但是等一等,这些真的只是处于两个方面吗?它总是与或的关系?

突破固有模式

让我们来回顾DRY 原则 vs 代码碎片:把针对特定场景的所有代码放在一处以便理解——脱离数据库,写到 utilities 目录——来表明这段代码是做什么的。如果你需要改变整个应用的通用处理方式,你只要改变一处地方就行,但你需要让所有代码都关联这共用的部分。而代码碎片,你也许会忘了改变某处拷贝过来的代码。

那么我们如何权衡呢?我们需要一种方式来获得最大的好处。

另一种答案是文档,如果有易读的、可靠的、综合的文档可用,你就不需要寻遍数据库和代码。

这些轴线并没有真正限定你应该怎么做。面向对象设计 vs 函数式设计作为单条轴线列出,只是因为他们是现今最广泛使用的两种编程范式,这还有其他范式

有些轴线就像一个柔顺的滑块,从一端到另一端。有些则是像开关一样是互斥的一对。有些是等待着你用新的思维把它转换成另一种控制形态。你能用创新的方案突破原有的模式吗?

真的?更有效率?

编程不易,特别是在高度压力的环境下,使用第一时间想到的解决方案是一种诱惑。如果问题非常复杂,初次的解决方案会占据很长的时间,以至于你会觉得没有其他选择。你会认为:“其他任何投入都是在浪费时间!”

这当然不对!你知道什么才是浪费吗?写代码和测试,准备 Pull Request,通过持续集成,然后让别人来审核代码。结果因为某人坐下来花时间思考这个问题,然后得出更好的解决方案,你又得推倒重来一遍。

你可以避免这种情况,尽你全力考虑出所有后备方案,然后在你的 Pull Request 的总结里把这些方案和对应讨论都写进去,或者在一场代码审核中提及它们。如果有更好的方案你没考虑到,这是让别人帮助你成长的机会。如果没有,你在增进别人思考的同时,确保会话始终围绕着你的解决方案。

一切都是权衡

思考每个解决方案,每个决策。就如同混音器一样,一系列不同类型的控制单元,会做出一系列微小的决策。考虑这些控制单元的不同位置,并且准备好发起一场关于它们每一个的讨论。

这种思考方式不只是涉及你的代码。在你和你的团队考虑每件事情做出每项权衡时,你会注意到所有人或多或少的情绪。然后你就有足够的词汇量来谈论个人或者团队组织的偏好,这将会如何影响你们的决策。

多方位思考。并且一直保持寻找可以突破固有模式的方法,去找到更好的替换方案!

译者注


  1. 未想好恰当的题名翻译,因此保留原文。 ↩︎

  2. 原文是 Continuum,很难对应容易理解的中文单词,因此在单个词引用时我翻译成“连续体”;根据上下文不同,我有时会翻译成“连续线段”便于理解。 ↩︎

  3. 原文: A useful tool to help in generating alternatives is that of an axis. A continuum with an infinite number of options along it, with two interrelated concepts at each end. ↩︎

  4. 即你的决策,最终是在这段连续线段中取个平衡点,取决于对两端概念的侧重比例的多少而已。 ↩︎


许可协议
未经授权,不得全文转载。转载前请先阅读本站版权声明