增强的软件设计四个规则

哈Ha! 我向您介绍David Bryant Copeland的文章“软件设计的四个更好的规则”。 David Bryant Copeland是Stitch Fix的软件架构师兼CTO。 他维护着一个博客,并且是几本书作者


马丁·福勒(Martin Fowler)最近在其博客中发布了有关肯特·贝克(Kent Beck)四个简单设计规则的博客链接,我认为可以进一步改进(有时可能以错误的方式发送程序员):


极限编程中的 Kent规则解释


  • 肯特说:“运行所有测试。”
  • 不要重复逻辑。 尝试避免隐藏的重复项,例如并行类层次结构。
  • 对程序员重要的所有意图都应该清晰可见。
  • 该代码应具有尽可能少的类和方法。

根据我的经验,这些规则不能完全满足软件设计的需求。 设计良好系统的四个规则可能是:


  • 它已被测试很好地涵盖并成功通过了测试。
  • 它没有程序不需要的抽象。
  • 她的举止很明确。
  • 它需要最少的概念。

对我而言,这些规则源于我们对软件的处理方式。


那么我们如何使用我们的软件呢?


在谈论软件设计之前,首先要谈谈我们打算如何做。


编写软件来解决该问题。 该程序运行并具有行为。 研究此行为可确保正确操作或检测错误。 软件也经常更改以赋予其新的或更改的行为。


因此,任何软件设计方法都应着重于预测,研究和理解其行为,以使更改行为尽可能简单。


我们通过测试来验证正确的行为,因此,我同意Kent的观点,首要的也是最重要的事情是,精心设计的软件必须通过测试。 我什至会更进一步并坚持要求该软件应该进行测试(即被测试很好地涵盖)。


验证行为之后,两个列表中的以下三点都与理解我们的软件(因此也涉及其行为)有关。 他的清单从代码重复开始,实际上已经存在。 但是,以我个人的经验来看,过多地关注减少代码重复是很昂贵的。 为了消除它,有必要创建隐藏它的抽象,正是这些抽象使软件难以理解和更改。


消除代码重复需要抽象,抽象导致复杂性


不要重复自己,或者使用DRY来证明有争议的设计决策的合理性。 您见过类似的代码吗?


ZERO = BigDecimal.new(0) 

此外,您可能会看到以下内容:


 public void call(Map payload, boolean async, int errorStrategy) { // ... } 

如果您看到带有标志,布尔值等的方法或函数,那么这通常意味着有人在重构时使用了DRY原理,但是代码在两个地方都不完全相同 ,因此生成的代码应该具有足够灵活以适应两种行为。


这样的广义抽象很难测试和理解,因为它们比原始(可能是重复的)代码处理的情况要多得多。 换句话说,抽象支持的行为比系统正常运行所需的行为多得多。 因此,消除代码重复可以创建系统不需要的新行为。


因此,组合某些类型的行为确实很重要 ,但是可能很难理解哪种行为是真正重复的。 通常,代码片段看起来很相似,但这只是偶然发生的。


考虑一下消除代码重复比再次返回代码要容易得多(例如,在创建思想欠佳的抽象之后)。 因此,我们需要考虑保留重复的代码,除非我们完全确定我们有一种更好的方法来删除它。


创建抽象应该使我们思考。 如果在消除重复代码的过程中创建了非常灵活的通用抽象,那么您可能走错了路。


这将我们引向下一个点-意向与行为。


程序员的意图毫无意义-行为意味着一切


我们经常称赞编程语言,构造或代码段“揭示了程序员的意图”。 但是,如果您无法预测行为,知道意图的意义何在? 如果您知道行为,意图意味着什么? 事实证明,您需要知道软件的行为方式,但这与“程序员的意图”不同。


让我们看一下这个示例,该示例很好地反映了程序员的意图,但其行为不符合预期:


 function LastModified(props) { return ( <div> Last modified on { props.date.toLocaleDateString() } </div> ); } 

显然,程序员计划该React组件显示一个带有消息“ Last Modify on”的日期。 这能按预期工作吗? 不完全是 如果this.prop.date没关系怎么办? 一切都崩溃了。 我们不知道它是这么构思的,还是有人忘记了它,甚至都没有关系。 重要的是行为。


如果我们要更改代码的这一部分,这正是我们应该知道的。 假设我们需要将行更改为“上次修改”。 尽管我们可以做到,但是尚不清楚如果缺少日期该怎么办。 如果我们改而以使组件的行为更易于理解的方式编写组件,那将更好。


 function LastModified(props) { if (!props.date) { throw "LastModified requires a date to be passed"; } return ( <div> Last modified on { props.date.toLocaleDateString() } </div> ); } 

甚至像这样:


 function LastModified(props) { if (props.date) { return ( <div> Last modified on { props.date.toLocaleDateString() } </div> ); } else { return <div>Never modified</div>; } } 

在这两种情况下,行为都是可以理解的,并且程序员的意图并不重要。 假设我们选择第二种选择(处理缺失的日期值)。 当要求我们更改消息时,我们可以查看行为并检查消息“从不修改”是否正确或是否也需要更改。


因此, 行为越明确,我们成功改变它的机会就越大。 这意味着我们可能需要编写更多的代码或使其更加准确,甚至有时还要编写重复的代码。


这也意味着我们将需要更多的类,函数,方法等。当然, 我们希望将它们的数量保持最小,但我们不应将此数字用作度量。 创建大量的类或方法会增加概念上的开销 ,并且在软件中出现的概念要比模块化的单位多。 因此,我们需要减少概念的数量,从而导致类数量的减少。


概念成本加剧了混乱和复杂性


要了解代码的实际作用,您不仅需要了解主题区域,而且还需要了解该代码中使用的所有概念(例如,在搜索标准差时,您必须知道赋值,加法,乘法,循环和数组长度)。 这解释了为什么随着设计中概念数量的增加,其理解的复杂性也随之增加。


曾经写过有关概念性支出的文章 ,减少系统中概念数量的一个好处是,更多的人可以理解该系统。 这反过来增加了可以更改此系统的人数。 可以肯定的是,可以由许多人安全地更改的软件设计要比只能由少数人更改的软件设计要好。 (因此,我相信硬核函数式编程将永远不会流行,因为它需要对许多非常抽象的概念有深刻的理解。)


降低概念成本自然会减少抽象数量,并使行为更易于理解。 我不是说“从不引入新概念”,而是说它有自己的价格,如果这个价格超过收益,则应仔细考虑引入新概念。


当我们编写代码或设计软件时,我们应该停止考虑代码的优雅美观或其他主观度量。 取而代之的是,我们应始终记住该软件将要做什么。


您无需将代码挂在墙上-您可以对其进行更改


代码不是一件艺术品,您可以在博物馆里印刷和悬挂它。 代码正在执行。 经过研究和调试。 而且,最重要的是,它正在发生变化 。 而且经常。 任何难以使用的设计都应受到质疑和审查。 任何会减少可使用该技术的人员数量的设计都应引起质疑。


该代码应该工作,因此应该对其进行测试。 该代码存在错误,并且需要添加新功能,因此我们需要了解其行为。 代码的寿命比特定程序员支持代码的能力更长,因此我们应该努力争取使广泛的人们可以理解的代码。


在编写代码或设计系统时,是否简化了系统行为的解释? 理解她的行为会变得更容易吗? 您是专注于解决摆在您面前的问题,还是专注于更抽象的问题?


始终尝试使行为简单易行,以进行演示,预测和理解,并尽量减少概念的数量。

Source: https://habr.com/ru/post/zh-CN461823/


All Articles