过度设计(万恶之源)(2016)

2021-08-02 04:50:59

定义 过度工程:过早地使用工具、抽象或技术解决方案,导致浪费精力和不必要的复杂性。什么时候过早使用技术?当它不能解决具体的当前问题时。从简单或易于开发的角度来定义优秀的工程是很诱人的,但我认为这实际上是一个滑坡。简单对不同的人意味着不同的东西。人们可以将编程视为压缩(确实如此),但我们必须意识到压缩或简洁本身并不是目标。尽可能短的程序对于人们来说通常不是最简单的,压缩的危险对于任何必须接受过科学教育的人来说都是显而易见的:当我在大学时,迄今为止最困难的考试随之而来最小的教科书……对不同的人来说,简单也意味着不同的东西。对于负责低级优化的人来说,使用较少的抽象比深入研究许多软件层更容易。对于新手来说,针对给定语言以更惯用的方式编写的软件可能比适应特定领域的软件容易得多。另一方面,问题是可衡量的、具体的问题,是一个更好的框架。它们仍然是一个软的、依赖于上下文和依赖于团队的指标,但试图确定问题、解决方案和它们的成本会使设计决策从美学(通常以自我为中心)领域转变为具体领域。注意:这并不意味着好的代码不是或不应该是“美丽的”或优雅的,但这些不是目标,它们只是解决代码可能存在的某些问题的副产品。此外,“可衡量”并不意味着我们需要在评估中附上精确的数字,在实践中,大多数事情都不会比模糊猜测更好,这完全没问题。成本和收益 很少讨论成本。如果一项技术、一个抽象、一个工程解决方案没有缺点,那可能是因为它没有做太多,或者因为我们没有足够努力地寻找。我们是否强加了运行时成本?调试版本呢?我们让它变得不那么有用了吗?我们的工具是否很好地支持设计?我们是否搞砸了我们的源代码控制、静态分析器等等?降低对代码进行本地推理的能力?将我们上下文中重要的细节隐藏在呼叫站点?使语义不那么明确,或者减少与给定语法的耦合?违反了我们的代码库通常采用的不变量和假设?它是否与团队文化配合得很好?通过代码审查或自动化测试或团队的任何其他工程实践?我们必须意识到讨论投资的权衡。但是,我们倾向于展示我们的想法的好处并隐藏成本,这在教育、研究和生产中是一个真正的问题。这是我们工作方式的硬连线。这(大多数情况下)甚至不是恶意的问题,这只是我们训练有素的方式,我们寻求成功并回避讨论失败。我见过无数次人们走上舞台,或者写文章和书,诚实地试图描述为什么给定的想法是聪明的并且可以工作,同时完全忘记他们每天经历的痛苦。工程不足 这就是为什么过度工程才是万恶之源的原因。因为它是恶毒的,它是阴险的,我们根本没有受过识别它的训练。可以出去买技术书籍,也可以上大学,学习数十或数百种工程技术和最佳实践。另一方面,除了经验和实践之外,几乎没有任何东西可以教会我们克制和解决实际问题。我们知道什么是工程不足,我们可以识别重复的代码、脆弱的、不安全的代码、结构不良的代码。我们有术语,我们有方法论。测试、重构、覆盖率分析……另一方面,在大多数情况下,我们根本没有接受过理解过度工程的培训。注意:事实上,在优秀的初级候选人中,过度设计通常更“明显”,他们的好奇心使他们学习了许多编程技术,但他们对陷阱没有经验,很容易偏离具体的问题解决方案。这意味着在大多数情况下,过度工程发生时它往往会持续存在,我们不会从大架构和大抽象回到更简单的系统,我们倾向于在它们之上构建。在路上的某个地方,我们做了错误的投资,做出了错误的权衡,但现在我们致力于它。过度工程往往看起来比工程不足更合理、更无辜。这不是坏代码。这不是丑陋的代码。它只是为时过早和无用,我们不需要它,我们为此付出了高昂的代价,但我们喜欢它。我们喜欢技术,我们喜欢阅读它,让自己保持最新状态,采用最新的技术和发展。在某个特定点,我们甚至可能开始认为我们确实做出了正确的投资,收益是值得的,特别是因为我们很少有客观的衡量标准或我们的工作,而且我们总能找到几乎所有选择的合理化。我想说的是,设计不足会导致明显的技术债务,而过度设计会产生隐藏的技术债务,这要危险得多。关键问题是“为什么?”。如果答案回到具有正投资回报率的具体问题,那么您可能做对了。如果它是一些模糊的其他品质,例如“共享”、“优雅”、“简单”,那么它可能是错误的,因为这些不是最终目标。当有疑问时,我发现最好在工程不足的一方犯错,因为它往往比相反的方法更有效率,即使它更受辱骂。我认为过度设计是过早优化的超集。在七十年代,当这句话出现时,这是这种更“根本”的邪恶的最常见形式。具有讽刺意味的是,这节课在过去的几十年里一直如此有效,以至于如今它实际上有助于过度设计,因为大多数工程师都错误地阅读了它,认为总体上性能不是项目早期的问题。中场休息:一些例子 - 假设我们正在开发一个在 Visual Studio 中制作的 Windows 游戏。假设您正在使用 Visual Studio 解决方案并且它做得很糟糕,它使用绝对路径并且需要源代码和可能一些库位于硬盘驱动器上的特定目录树中。任何人都可以说这是一个糟糕的设计,作者可能会因为这种“不专业”的选择而受到嘲笑,但在实践中,它可能导致的问题很小,任何程序员都可以轻松解决。另一方面,假设我们无缘无故地开始使用更复杂的构建系统,可能是基于本周新奇的外部构建工具的包和依赖项。这种选择的潜在成本是巨大的,因为现在您的许多程序员可能不太熟悉这个系统,它没有带来可衡量的好处,但现在您已经混淆了管道的一个重要部分。然而,这样的决定不太可能被嘲笑。 - 有时问题甚至更微妙,因为它们涉及不明显的权衡。一个相当硬编码的系统在延展性方面可能会很痛苦,也许在这个子系统中进行更改需要每次编辑大量源文件,即使是微不足道的操作。我们真的不喜欢那样,所以我们用一个更通用的数据驱动的系统替换了这个系统,它允许一切都在现场进行,甚至不再需要重新编译代码。但是说这样的系统相当“冷”,而且变化实际上并不频繁。还假设新系统需要相当多的代码,现在我们的整个构建速度较慢。我们最终优化了一个不常见的工作流程,但不利的一面是,我们减慢了团队中所有程序员的日常工作……假设您使用了一个可以使用简单函数的类。也许您集成了一个库,您可以在其中编写一百行代码。您使用模板化容器库,您可以在其中使用标准数组或临时解决方案。您粗心大意,现在由于类型依赖性,您的系统在构建时变得越来越耦合。它可能在运行时比它可能的慢一点,或者它比它应该进行的动态分配更多,或者它在调试构建中很慢,并且当你实际上必须介入这个库代码时,它会使你的构建时间更长,同时又很模糊。这是一个非常具体的例子,经常发生,但很可能这些都不会被认为是设计问题,我们经常看到复杂的工具建立在过度设计的设计之上,以“帮助”解决他们的问题。所以现在您可以使用“统一构建”和分布式构建来尝试解决构建时间问题。您可能会开始使用复杂的内存分配器和内存调试器来追踪导致碎片的原因等等。过度设计会导致更多的过度设计。有一种想法是,通过在复杂系统上构建更多内容,可以使复杂系统变得更简单,这不太现实。专业化和限制 一旦了解了给定选择的成本和收益,我就没有一种通用的方法来评估投资回报。而且我认为一般没有,因为这个指标非常敏感。我喜欢邀请工程师做的是思考问题,敏锐地意识到它。我认为作为指导有用的原则之一是我们在有限的工作集下运作:我们不能同时关注很多事情,我们必须找到帮助我们实现目标的约束条件。换句话说,我们的目标指导我们应该如何专业化我们的项目。例如,在我的工作中,我经常处理数值算法、可视化和数据探索。我可能会根据需要在非常不同的环境和非常不同的风格中编写非常相似的代码。如果我正在探索一个想法,我可能会使用 Mathematica 或 Processing。在这些环境中,我对内存分配的细节和代码优化的微妙之处知之甚少。而我不想——想——知道。即使只是意识到它们也会让人分心,因为我自然会倾向于编写高效的算法,而不仅仅是解决手头的问题。很多时候我的 Mathematica 代码实际上会泄漏内存。在具有 92 GB 内存的工作站上通宵运行探索性任务时,我毫不在意。环境完全使我免受这些担忧的影响,这是完美的,它使我能够专注于在这种情况下重要的事情。我写了一些非常高级的代码,不知何故神奇发生了。有时我必须将这些实验移植到生产 C++ 代码中。在那种环境下,我的目标完全不同。性能对我们来说非常重要,我不想要任何魔法,我想要任何即使是远程昂贵的东西,也能在它发生的地方表现出来。如果有一些魔法在大多数情况下都运行得相当快,那么您可以肯定,它产生的问题会消失,直到发生这种情况的地方太多以至于整个产品分崩离析。我不相信你可以创建非常广泛的系统,在那里你有非常高层次的关注和非常低层次的关注,万能的。约束和专业化是软件工程的关键(不仅如此),它们使我们能够专注于重要的事情,将重要的关注点保留在我们的工作集中,并对代码进行局部推理。所有级别 过度设计的另一个方面是它不仅影响微小的代码设计决策,甚至不只是编码。一般来说,我们倾向于在没有适当意识的情况下做事,我认为,什么问题为我们解决了问题,他们创造了什么问题。相反,我们经常被某种美学或某些好的理想所引导。例如代码共享和重复数据删除。标准和图书馆。有些事情有时我们认为本质上是好的,即使我们有失败的历史,我们应该从中吸取教训。对于工程来说,特别是共享会带来令人难以置信的成本,但它本身几乎总是被认为是一种美德,即使是有经验的团队在集成成本、生产力成本、代码膨胀和等等,它只是被认为是“自然的”。 “不要重新发明轮子”是非常真实和合理的。但是“轮子”对我来说意味着“冷”,不受迭代影响的基础结构代码,对于给定的项目不需要专门化。认为共享和标准化总是一种胜利就像认为把更多的人投入到一个问题上总是一种胜利,或者认为使某些代码多线程总是一种胜利,无论它需要多少同步以及它使开发变得多么困难过程...例如,在一家电子游戏公司,为十个不同的项目拥有十个不同的数学库肯定是愚蠢的。但是拥有十个不同的渲染器可能并不愚蠢。甚至 20 个重要的东西,渲染是创作过程的一部分,它是我们想要专攻的一部分,根据给定的艺术方向、给定的项目范围等进行制作。 People Context 不仅在技术层面上很重要,而且在人的层面上也很重要,甚至更多。软件工程是一门软科学!我曾在几家不同的公司从事过几个不同的项目,对此我深信不疑。有时您会看到一家公司对类似的项目使用相同的策略,但结果却截然不同。另一方面,在其他时候,不同公司的不同产品通过采用完全不同的、几乎相反的策略获得了类似的结果。这是为什么?因为人比技术更重要。这也许是我们作为软件工程师受过培训最不认识的事情。人比技术更重要。退伍军人团队的工作方式与拥有或需要大量人员流动的团队不同。在游戏行业,有些团队的创新是由工程师带头的,有些则是由美工或技术美工推动的。一家特定的公司可能希望将所有投资集中在少数非常引人注目的产品上,其中创新和质量非常重要。另一个可能通过生产更多产品并尝试看看什么有效,在这个领域中降低成本可能更重要。即使是分享和避免重复的口头禅也不是绝对的。在某些情况下,复制实际上可以获得更好的结果,例如,与最终生产相比,具有单独的实验环境。在某些情况下,分享会扼杀创造力,并且维护成本总体上高于收益。在不了解成本、收益和背景的情况下谈论工程是不可能的。几乎从来没有一个普遍的好的解决方案。问题是具体的和局部的。工程是关于在特定环境中解决具体问题,而不是随波逐流。我觉得我们这个行业还有很多东西要学。