A Philosophy of Software Design
Part I
软件设计的唯一问题: 复杂度
- 被不断扩大的修改范围
- 被不断增加的认知负担
- 不被认知到的未知
如何定义复杂度
任何使你的系统变得不易被理解、不易被修改的事物。
表现在我们的场景里,比如:
- Hybrid Programming
- 接近 30w 行代码
- 花式同步
- 五颜六色的权限体系
- 八仙过海般的任务相关的操作
- etc
一些可能的解决方法
- 使用一些适当的编程技巧做抽象、保证 关注点分离
- 不要仅仅用行数的多少去衡量设计,有时候同一个设计,更多行数的那个实现可能要比更短的那个更 简单
- 尽可能避免使用全局变量
- 保证代码的一致性
- 将重要的业务系统设计的逻辑变得 显而易见,不要藏之于黑盒
仅仅能工作的代码是不够的
战术性编程 vs 战略性编程
战术性编程
优点: 以最快的速度完成可工作的功能
缺点: 以长期的视角来看, 容易积累技术债,增加复杂度
战略性编程
优点: 不仅仅可以工作, 还要易于修改、拓展
缺点: 每个功能需要时间去设计、去思考
战略投资不是无限的,如何平衡?
书中给的答案是: 10% ~ 20%
模块化设计
一个理想的世界里,每一个模块应该完全独立于其他模块,工程师们应该可以修改、运行它们而不用知道别的模块的存在。
呵呵,理想是丰满的、现实是骨感。大家醒醒。
我们的模块往往必须知道某些信息,才能被运作,而这些东西最后都成为了依赖。
Interface
接口用于表达一个模块是什么,能做什么事,而不应该描述它如何做这些事。(同时,作为使用者,我们也不应该关注它们)。
在很大程度上,接口的概念可以帮助我们去屏蔽不相关性。
WHAT
Implemention
实现则恰恰相反,由一系列的代码组成,而最后这些代码相互运作,可以保证完成 interface 向用户 (工程师) 做出的承诺。
这些代码可能是异构的、可能是晦涩。但对于实现而言,最重要的是保证行为的一致。
HOW
一个好的设计里,接口应该要比它的实现要简单的多。
构建抽象
抽象是一个被 剥离了不重要 的内容的实体的概览
而往往,大部分的设计中,抽象这个概念本身都以 interface 的形式被表现。
一个抽象的好坏,取决于你对一个模块究竟可以正确的 剥离出多少 不重要的 内容
比如对于 Nodejs 而言,异步就是一种抽象,它抽象了 kernel 的真实的 IO 行为, 并且屏蔽了它的性质,开发者不需要知道它的真实平台是什么。 windows / unix ? overlapped io / select etc。这些东西都是不重要的。
但相应的,将接口暴露成异步风格是重要的。它被用于表达: 什么时候,某件事真的被做完了。这些信息对于开发者而言则是必须的。
Deep module vs Shallow module
一个好的模块:
- 尽可能少的接口
- 尽可能多的功能
Deep module Example:
Unix basic system call for I/O
(5个调用用了一辈子)
Garbage Collection
(直接消灭了所有的内存管理相关的复杂度)
Shallow module Example:
private void addNullValueForAttribute(String attr) {
data.push(attr, null)
}
(函数并没有提供任何抽象,名字更长了,但从目的来看不如直接操作数据来的直接)
A Philosophy of Software Design
By 知白 Saviio
A Philosophy of Software Design
- 728