summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-04-21 10:43:38 +0800
committereltrac <hi@eltr.ac>2026-04-21 10:43:38 +0800
commita1fbb277343519586d7d3765aeacfabbd54833bd (patch)
tree207015deb615e3f5dd3426a53a200eca954e2c31
parent1650a1e0f980f1b6fb9d8b7a78c61a4d8111ac82 (diff)
[library] publish /library/2026/clean-architecture/
-rw-r--r--content/library/2026/clean-architecture.md25
1 files changed, 17 insertions, 8 deletions
diff --git a/content/library/2026/clean-architecture.md b/content/library/2026/clean-architecture.md
index 1916eb54..412c3388 100644
--- a/content/library/2026/clean-architecture.md
+++ b/content/library/2026/clean-architecture.md
@@ -4,9 +4,9 @@ slug = 'clean-architecture'
tags = ['软件','设计']
authors = ['Robert C. Martin']
rating = 3
-date = 2026-04-20T17:01:13+08:00
+date = 2026-04-21 10:43:27
cover = 'https://img.eltr.ac/geedeapro/library/2026/clean-architecture/cover.png'
-draft = true
+draft = false
toc = true
toot = ''
+++
@@ -53,7 +53,7 @@ toot = ''
编程语言在更新迭代,编程范式和开发模式也在推陈出新,但组织软件系统的方式、定义组件边界的方式、软件设计的基本原则、管理依赖关系的方式,几乎都没有改变。架构是永恒的,此外,良好的架构也能让延长软件的生命周期。
-Bob 大叔认为软件有两种价值维度:行为价值和架构价值。前者是大多数人更关心的「软件能做什么」,非开发人员希望软件能更快地推出新功能,也就是允许更多的行为。然而,架构价值可能比行为价值更重要,因为架构决定了开发开放、维护难度和变更成本。如果急着满足需求,实现行为价值,而使用了不合理的架构,那么后续增加新功能时要修改的代码就会越来越多,还有可能出现修改了某一段代码导致另一段代码不可用,需要继续修改的情况。我想这有点像一些人说的「慢就是快」,一开始求快只会在整体上拖慢速度,甚至最终不得不重头来过。
+Bob 大叔认为软件有两种价值维度:行为价值和架构价值。前者是大多数人更关心的「软件能做什么」,非开发人员希望软件能更快地推出新功能,也就是允许更多的行为。然而,架构价值可能比行为价值更重要,因为架构决定了开发效率、维护难度和变更成本。如果急着满足需求,实现行为价值,而使用了不合理的架构,那么后续增加新功能时要修改的代码就会越来越多,还有可能出现修改了某一段代码导致另一段代码不可用,需要继续修改的情况。我想这有点像一些人说的「慢就是快」,一开始求快只会在整体上拖慢速度,甚至最终不得不重头来过。
书本还用艾森豪威尔矩阵解释了两种价值维度的关系,这个矩阵就是生产力爱好者常说的「重要且紧急」「重要不紧急」「不重要但紧急」和「不重要不紧急」的划分。架构属于「重要不紧急」的范畴,而软件行为是普通人更关注的「不重要但紧急」的事项。至于另外两个象限是什么:紧急的事情永远不重要,重要的事情永远不紧急。
@@ -98,13 +98,13 @@ assert(r.area() == 10);
常见于 Java 等 OOP 语言里的工厂设计模式也是为了依赖反转而服务的,也有了先写 `XXXService.java` 接口,再写 `XXXServiceImpl.java` 实现类的做法,这样可以使源代码依赖于抽象工厂和抽象的服务接口,而不直接依赖易变的具体实现;而 `ServiceImpl` 和 `ServiceFactoryImpl` 等类由于是对接口的实现,依赖关系也是从具体实现指向抽象接口的,保证依赖关系从具体指向抽象。
-以前我总觉得这种编程模式很繁琐,如今也算是理解了一些背后的原因。这么说来,我的 Java 老师还真是一点没讲清楚。我想这样的 Java 开发者应该不占少数,这才是 Java 这门语言令我厌烦的根本原因吧。无脑复用某种软件组织方式,完全不明白这么做的目的,最终违反了某些原则也完全不自知,最终的软件架构照样很乱。
+以前我总觉得这种编程模式很繁琐,如今也算是理解了一些背后的原因。这么说来,我的 Java 老师还真是一点没讲清楚。我想这样的 Java 开发者应该不占少数,这才是 Java 这门语言令我厌烦的根本原因吧。无脑复用某种软件组织方式,不明白这么做的目的,违反了某些原则也不自知,最终的软件架构照样很乱。
---
第 12、13 和 14 章讲的是组件原则,包括组件的历史、重定位和链接器等技术,以及组件内聚和组件耦合相关的原则,不只是「高内聚低耦合」那么简单。
-组件内聚主要要保证复用/发布等价原则(REP)、共同闭合原则(CCP)和共同复用原则(CRP)。REP 如今已经成为事实标准了,它说的是「没有通过发布流程获得版本号的组件是无法复用的」,主流编程语言几乎都有发布组件的标准流程,锁定版本号以保证组件行为可预测也很重要。CCP 是单一职责原则的组件版本,即「将相同变更原因而需同时更改的东西放在一起,将不同变更原因或无需同时更改的东西分开」。CRP 简单来说就是「不要依赖不需要的东西」,不这么做的后果,JavaScript 开发者深有体会,他们的大部分工作都是在更新、升级和维护依赖,因为;如果依赖某个组件,最好是依赖该组件的每一个类。
+组件内聚主要要保证复用/发布等价原则(REP)、共同闭合原则(CCP)和共同复用原则(CRP)。REP 如今已经成为事实标准了,它说的是「没有通过发布流程获得版本号的组件是无法复用的」,主流编程语言几乎都有发布组件的标准流程,锁定版本号以保证组件行为可预测也很重要。CCP 是单一职责原则的组件版本,即「将相同变更原因而需同时更改的东西放在一起,将不同变更原因或无需同时更改的东西分开」。CRP 简单来说就是「不要依赖不需要的东西」,如果依赖某个组件,最好是依赖该组件的每一个类,否则无关的代码变更可能影响自身。
组件内聚的三个原则构成了一个「不可能三角」,需要架构师权衡取舍,一般来说只能同时实现两个。如果牺牲 REP,就要面临组件难以复用的后果;如果牺牲 CCP,就可能会出现太多不必要的版本发布;如果牺牲 CRP,就会出现太多的组件变更。
@@ -122,7 +122,7 @@ assert(r.area() == 10);
策略往往是高度抽象的,包含实体和用例。实体一般是指核心业务逻辑实体,比如一个员工管理系统里可能就有 `Employee` 实体,这里包含最核心的数据和操作。所谓核心业务逻辑,就是没有计算机、没有自动化系统,也能为企业产生价值的操作,比如银行没有自动系统,也会请专人来做必要的计算。而用例,就是有了软件之后才产生的东西,一般是站在用户视角的,比如「用户应当能通过系统提交工时数据」就是一个用例。
-编写软件时,最先写的应该是最抽象的核心业务逻辑,其次是用例,定义用户如何与系统交互,最后才决定要不要用数据库、用什么数据库系统、用什么应用程序框架、要用 Web 还是桌面端 GUI——换句话说,应该尽可能推迟细节决策。
+编写软件时,最先写的应该是最抽象的核心业务逻辑,其次是用例,定义用户如何与实体交互,最后才决定要不要用数据库、用什么数据库系统、用什么应用程序框架、要用 Web 还是桌面端 GUI——换句话说,应该尽可能推迟细节决策。
为什么?首先,业务逻辑是最重要的,是给软件创造价值、给企业赚钱的东西,而技术细节可以随时更换,并不重要,使用什么类型的数据库并不会显著影响软件行为。其次,尽量推迟决策可以让架构师有更多的时间收集信息,决定这个系统更适合用什么技术。如果架构足够灵活,技术细节就仅仅作为业务逻辑的「插件」存在,可以随时更换。
@@ -138,7 +138,7 @@ assert(r.area() == 10);
我读技术类书籍往往很慢,一方面我需要更多的时间消化信息,另一方面,尽管这类书读起来也有醍醐灌顶的感觉,但整体上不如文学作品吸引我。所以,这本书我花了一两个月来读,这期间我也有开发一些玩具软件,每往后读一点,我就觉得我的软件架构问题越来越多,首当其冲的就是「不可测试性」。
-大概是被 Lisp 系语言的 REPL 宠坏了,我从不编写单元测试(甚至有段时间会让 Coding Agent 帮我写测试),在本地测试的时候能跑通就行,导致我很多时候是把代码推送到生产环境之后才发现问题。最近我想要给 [Weepinbell](https://codeberg.org/eltrac/weepinbell) 加上单元测试,Clojue 和 Leinigen 提供了很好用的从测试工具,但我发现我的架构不允许我对单个组件进行测试。
+大概是被 Lisp 系语言的 REPL 宠坏了,我从不编写单元测试(甚至有段时间会让 Coding Agent 帮我写测试),在本地测试的时候能跑通就行,导致我很多时候是把代码推送到生产环境之后才发现问题。最近我想要给 [Weepinbell](https://codeberg.org/eltrac/weepinbell) 加上单元测试,Clojue 和 Leinigen 提供了很好用的测试工具,但我发现我的架构不允许我对单个组件进行测试。
首先,我的大部分组件都依赖 `config` 组件,它直接从配置文件里读取配置。
@@ -189,7 +189,7 @@ assert(r.area() == 10);
读到这里的时候我立马想到了 JavaScript 开发者的现状:大部分的工作都是在维护依赖,软件几乎没有真正「稳定」的时候,如果几个月都不升级就会被淘汰。我想很大一部分原因是 JavaScript 开发者没有遵循共同复用原则,依赖了大量不需要的代码。
-对比 Go 语言这边,Rob Pike 说:[A little copying is better than a little dependency.](https://www.youtube.com/watch?v=PAAkCSZUG1c&t=9m28s) 比起依赖会动态变化的软件库,直接复制粘贴自己需要的那一小部分代码更好,因为写在源代码里的东西是静态的、稳定的,不会改变。保持依赖树的整洁和精简有助以提高软件的可维护性。
+对比 Go 语言这边,Rob Pike 说:[A little copying is better than a little dependency.](https://farside.link/invidious/watch?v=PAAkCSZUG1c&t=9m28s) 比起依赖会动态变化的软件库,直接复制粘贴自己需要的那一小部分代码更好,因为写在源代码里的东西是静态的、稳定的,不会改变。保持依赖树的整洁和精简有助以提高软件的可维护性。
## 微服务不是架构、应用程序框架不是架构
@@ -203,3 +203,12 @@ assert(r.area() == 10);
前端开发或许有其特殊性(因为按照书里的说法,前端作为 GUI,本来就是技术细节),但如果要考虑整个软件的架构,应该先从业务逻辑开始,再考虑组件之间如何通信、要不要用微服务架构,以及要不要用 Web、要不要用框架的问题。
+---
+
+## 最后
+
+1. 去读电子工业出版社出版、孙宇聪翻译的《架构整洁之道》,或者直接读《Clean Architecture》英文原本,不要读机械工业出版社的这本《整洁架构之道》。
+2. 软件的依赖关系应该指向最抽象、最稳定的组件,即核心业务逻辑。
+3. 数据库只是技术细节、Web 只是技术细节、应用程序框架只是技术细节,应该尽可能推迟细节决策。
+4. 好的架构是灵活且可测试的,测试和文档一样也是软件的一部分。
+