ACE Dev : 自适应组合进化开发

ACE Dev或自适应组合进化开发:描述自 1999 年开始专业从事面向对象编程以来所采用的开发风格的术语。ACE dev 由一组简单的原则组成,应用这些原则通常会自然地实现灵活且可维护的代码。

当你理解这些原则时,更容易弄清楚何时使用哪种设计模式、何时使用依赖注入等。更容易决定何时以及如何让 YAGNI 和 MVP(精益)等敏捷原则启动)影响你的代码设计。

ACE Dev 精炼
在详细介绍之前,总结一下 ACE Dev 的主要原理,这样您就可以看到原理集有多么简单:

  • 自适应 (A)
    • 适应要求 (R)
    • 适应环境 (C)
  • 可组合型(C)
    • 狭义责任 (N)
    • 扩大适用性 (W)
    • 减少所需的努力 (R)
  • 进化 (E)
    • 调整设计以适应本次交付 (T)
    • 调整设计以适应下次交付 (N)

1、适应性
适应性开发是指根据软件项目的具体要求(需求)和环境调整设计(以及流程)。由于不同的软件项目有不同的需求和环境,因此,使用一套固定的规则或理论来指导设计是没有意义的。您需要使用能使软件和代码库满足您需求的技术和工具。

适应性意味着你可以根据需要自由混合编程范式(FP、OOP、DOP 等)。不要因为 "感觉更纯粹 "就觉得必须遵守特定的范式。我们编写软件并不是为了遵守教条。我们编写软件是为了解决问题。

让需求驱动设计和流程的一个自然结果就是降低编程范式理论的优先级。将这些理论作为指导原则,但在需要的时候偏离这些原则。事实上,当涉及到经典的 OOP 理论时,完全忽略它们可能会让你受益匪浅。

适应性还意味着设计和流程要适应软件的环境上下文。通常情况下,环境包括以下因素

  • 代码库大小
  • 软件的预期寿命
  • 为软件工作的开发人员数量
  • 软件的潜在收益
  • 软件错误的后果严重程度

等等。

通常情况下,代码库越小,生命周期就越短,开发人员等就越少。- 代码库所需的 "结构 "就越少,这意味着你可以使用设计不那么完善的代码库。相反,代码库越大、生命周期越长、开发人员越多,就越需要保持较多的结构。- 就需要更多的结构来保持持续稳定的开发。

进化式开发
在我看来,进化式开发是指将一个大型系统的开发分成许多后续的小型交付项目。代码库的内部设计主要是为了适应当前的需求,或许还有下一两个版本的需求。

我尽量避免为未来的需求进行设计--除非我确定软件届时会是什么样子。随着我对软件实际工作方式的进一步了解,需求可能会发生变化。您可能并不需要您认为需要的功能。这时,经典的 YAGNI 原则(You Aren't Going to Need It)就是一个有用的指导原则。

不为未来设计的一个自然后果是,我可能不得不根据未来版本的需求来重新调整软件的设计--当我达到这些需求时。我尽量不让自己被过于陈旧、不合适的设计所束缚。

在将开发分成较小的交付版本时,我会尽量从小的交付版本开始。这就是精益创业哲学中的 MVP(最小化可行产品)的用武之地。从第一个产品开始,我逐个小批量地开发软件。这就是进化的过程。

组合设计
我所说的组合设计是指将代码库设计成由小块代码组成,这些小块代码可以组合成更大的结构,从而使您能够用相同的组件解决许多类似的问题。函数式编程(FP)和面向对象编程(OOP)中都有组合设计技术。

在函数式编程中,组合设计以函数式组合的形式出现,即由其他函数组成的函数。函数式组合通常是将一个函数传递给第二个函数,然后返回第三个函数作为结果。第三个函数调用第一个函数作为其代码的一部分。关于函数式组合的文献很多,因此我在这里就不详细介绍了。我的重点将放在面向对象的组合上--因为我觉得这一点还没有得到很好的阐述。

在面向对象的设计中,如何构建类和对象才能使代码库易于使用往往不是那么明显。目前似乎还没有任何官方的 OOP 指导方针(属于官方 OOP 规范的一部分)能够在实践中很好地发挥作用。因此,我写下了自己使用的 OOP 组成技术。在大多数情况下,这些技术对我来说似乎都相当有效。

分解扩展 + 组合收缩
反对将代码库拆分成许多小组件的一个常见理由是,代码库会变得混乱,而且拆分后产生的所有额外文件也更难浏览。

首先,如果您使用的是 IntelliJ IDEA 这样的现代集成开发环境,那么代码库中的大量文件并不是什么大问题。现代集成开发环境具有多种功能,即使是包含大量文件的大型代码库也能轻松浏览。

其次,我发现使用组合设计通常不会导致文件过多。事实上,我发现我的代码库往往比其他方法更小、更优雅。要了解其中的原因,我们必须看一下组成式设计中出现的两种现象:

  • 分解扩展
  • 组合收缩

一开始,当你开始将较大的组件分解成较小的组件时,你的代码库会扩大。这就是我所说的分解扩展。

之后,当你开始能够使用现有组件编译新代码时,你的代码库的增长速度会比你没有为编译而设计时慢得多。这就是我所说的组合收缩。

在尝试组合式设计时,你必须坚持足够长的时间,以克服分解式扩展--体验随后出现的组合式收缩。

组合设计技术
我的组合设计理念中的两个主要原则是“缩小组件的职责”和“扩大组件的适用性”。然而,这两个主要类别下还有更多的原则。以下是我的 OOP 设计原则列表,我将其称为组合 OOP:

  • 狭隘的责任
    • 将领域逻辑与领域逻辑分开
    • 将域逻辑与非域逻辑分开
    • 将状态与动作分开
    • 将动作与上下文分开
  • 扩大适用性
    • 在接口方法中使用更通用的类型
    • 添加更多方法匹配更多类型
    • 动态可组合性设计(依赖注入)
    • 可扩展性设计
    • 可插拔性设计
    • 可配置性设计
  • 减少所需的工作量

缩小责任范围
组合式设计的首要原则是设法缩小系统现有组件的责任范围。在缩小组件的责任范围时,通常会将较大的类或方法拆分成多个较小的类或方法,每个类或方法的责任范围都会缩小。

通过缩小责任范围,通常可以提高所产生的类或方法的可重用性。随着项目功能的增加,更高的可用性往往会带来更小、更不易出错的代码库。

缩小组件的责任范围也会使测试变得更容易--因为需要测试的行为(责任)更少。它还能让你更容易在测试所需的状态下创建组件实例。换句话说,更容易配置组件。

狭义责任原则有时也被称为单一职责 原则,但我认为这种表述过于严格。单一责任到底是什么样的?它的界限是什么?狭义 "一词表达了同样的意思,但没有绝对化的感觉。

有很多方法可以缩小类或方法的责任范围。我不会假装我知道所有的方法,但我确实知道一些我经常使用的方法。我将在下面的章节中解释其中一些。

从领域逻辑中拆分领域逻辑
缩小组件责任范围的常用方法是将其领域逻辑责任拆分为多个领域逻辑组件。您可能有一个同时执行 A 和 B 功能的组件,但现在您需要一个新的 C 组件来执行 B 功能。

有时,我也将领域逻辑与领域逻辑之间的拆分称为垂直拆分。这是因为在绘制架构图时,往往会将领域逻辑层叠在非领域逻辑层之上。在这样的架构图中拆分领域逻辑组件时,它将成为位于同一层的两个组件。看起来就像是较大的组件被 "垂直分割 "成了两个相邻的组件。

分割领域逻辑与非领域逻辑
缩小组件责任范围的另一种常见方法是将其领域逻辑与非领域逻辑分开。例如,对文件执行特定领域计算的组件可能还包含将文件加载到内存中所需的逻辑。这样的组件可以将其逻辑分成两个组件:一个组件执行特定领域计算,另一个组件将文件加载到内存中(非特定领域逻辑)。

将文件加载到内存中的组件可以从另一个也需要对文件数据进行操作的特定领域组件中重复使用。

我有时也将领域逻辑与非领域逻辑的拆分称为水平拆分。这源于绘制架构图时将领域逻辑层叠在非领域逻辑之上的倾向。当在这样的图表中将一个较大的组件拆分为领域特定部分和非领域特定部分时,它就会变成两个位于不同层的组件,领域特定组件位于非领域特定组件之上。