Table of Contents

  1. 整洁架构之道
    1. 编程范式
    2. 架构关心的是什么?
    3. 可测试的程序
    4. 延迟选择的重要性
    5. 设计的边界

整洁架构之道

编程范式

从编程范式说起,有三种编程范式,结构化编程/面向对象编程/函数式编程。 这三种编程范式可以说是计算机编程的核心范式,任何软件开发都遵守这三种范式中的某几种。

结构化编程规定了我们使用if/else 来做条件判断执行代码,限制了在goto代码的使用。 从根本上保证了代码的可读性。可读性在计算机领域比性能更加重要,在很多的场合下,可以损失一部分性能,但一定不能损失可读性。 限制goto可以减少编程的复杂度。

面向对象编程范式,我们可以利用多态限制用户对函数指针的使用。函数的调用栈被移动到了堆区。 当然还有更多的好处,面向对象进一步增加了可读性。通过对象的沙盒包装将函数伪装成行为,就像是一个拥有许多函数的机器人。 我们观察它的行为,而不需要关注函数指针。

函数式编程对程序的赋值进行了限制,这意味着永远无法区改变一个存在的符号的值。 在今天的serviceless架构下,函数式编程天然的可以无所顾忌的跑在多个集群上,不需要担心任何东西。 它是无状态的。符号的值大多数情况下都没有办法改变。因此它是安全的。

编程范式并不代表者架构本身,架构是一个决策过程。 架构很像是建筑师在完成图纸的过程,想象一个优秀的建筑师,如果设计一幢漂亮的建筑物。他必须考虑的是建筑的风格是什么、高度如何、怎样抗震、采光率…

等等这些决策了他选择什么样子的可以落地的架构。在计算机里面架构也伴随着这样的考虑,如何高效率的开发、性能如何、扩展性怎样、是否能产生额外价值、以及能否扛住巨大的流量。

架构关心的是什么?

在计算机里面,重要的是可读性、扩展性以及健壮性。

计算机在执行二进制程序的时候是不关心可读性的,但在编程的时候,可读性往往高于一切。一个没有办法理解的系统,即便是它能很好的工作,但你敢使用么?

面向对象编程范式和结构化编程 在提升可读性上做出了重要的贡献。

架构需要对一些细节进行选择,比如选择采用何种数据库,就像是采用何种木材来制作一个凳子。这些都是细节,应该是架构无关的。

在架构设计的时候,这部分应该是容易替换的,插件式的。OCP原则说,一个设计良好的计算机系统应该在不需要修改的前提下就可以轻易被扩展。 是的,了解这一点非常重要。

有许多原则执导我们如何去架构一个系统、比如SRP原则(单一职责)。本质上还是维护来系统的可读性。一个类拥有多个行为在计算机上并没有差别。但是,如果一个类拥有了太多的行为,我们对它的修改就 变得很困难,不得不考虑一旦修改了它,会不会产生副作用。 同样我们还渴望,源代码的依赖方向和控制流的方向是相反的。一切都类型以接口的形式被声明和引用。比如Java 中@Autowride 更像是一个声明。我们并不关心注入的是什么,控制反转给上层,下层只需要关心具体的行文。

我需要一个可以打开门的钥匙,但并不去创造一个。甚至什么门也不关心,给我一把钥匙和一扇门,我可以打开它。 业务逻辑正式描述钥匙可以打开一扇门,这个过程。但其实并不关心谁制造的钥匙。

在以前的编程中,我们往往需要在源代码依赖方向new 一个钥匙。当这一切被反转后,我们就在也不需要担心这些细节了。

可测试的程序

可读性是如此的重要,以至于我们可以为了他牺牲性能(在某些情况下,可以将性能优化的代码进行伪装,可读性仍旧在)。 但是,可读性仅仅是人类协同的一个重要前提。一段程序必须同时拥有良好的可测试性。

测试似乎是可有可无的,没有测试的程序一样可以运行。但是测试是减少系统熵最有效的方式。系统架构无论多完美,在不同的开发者介入开发的时候,系统仍旧变得充满歧义和混乱。随着时间的推移, 这种情况只会越来越糟糕。因此正确的测试可以很好的减少这种熵。

为了让程序可测试,我们需要在编程的时候就了解到这一点,我提供的方法改如何测试?为此有时需要调整代码的结构、层次、依赖关系和参数。 这就是TDD测试驱动的核心。它推崇一种方式,在代码编写之前先完成测试用例。一旦这样做了,代码就会自然的组织在一起,一个微小的调整引发的bug将很快暴露出来。

测试并不是银弹,测试本身也会出错,测试并不保证程序的正确性,只能发现bug。

延迟选择的重要性

不论是架构师还是产品,在设计阶段最忌讳的是陷入细节的陷进。在一开始就因为要选择Mysql还是Postgresql而陷入争执是一件不明智的选择。

如果一款产品强依赖Mysql,调整它需要花费很大的精力,那么一定是架构出来问题。

因此在架构设计阶段,一定要学会放弃。放弃使用Mysql。放弃Redis.放弃Pulsar. 我们需要的不是MySQL,而是一个可以持久化数据的API。我们需要的也不是Redis,而是一块可以管理的内存。 也不是Pulsar,而是一个消息队列。

Mysql\Redis\Pulsar 这些只是其中的一个实现。

我们需要延迟选择它们。这样在测试阶段,可以提供一个Memory的实现来替代Mysql的实现。甚至在后面切换到Mongodb的时候,我们也只需要提供一个mongoDB的实现而已。

设计的边界

在设计系统的时候考虑多个不同的团队在协同某个域/类上开发。他们有独立的决策空间。因此他们在系统上天然的拥有边界。 如果你的修改影响到了另一个团队,你便是越界了。这样似乎很容易理解。 系统的边界表现在如何更好的隔离不同的业务逻辑。从Package/模块 到微服务,都是在产生从系统不同纬度上划分边界。 设计好边界可以让开发工作并行开发。本质上是一个去依赖的过程。

从代码的边界到系统的边界, 从领域的边界到服务的边界,层层升纬,使得从代码到业务,高度组织。从里面看去就像是一个房间里面,每个抽屉放置什么东西都被分类整理,而从外面看去一排排排房间整整齐齐。

每个房间的门是边界,每个抽屉也是边界。边界代表着有序,代表了更低的熵。而这样设计的系统,往往具有更高的可读性。