转自:【https://zhuanlan.zhihu.com/p/346689409】
软件架构的目的
软件架构是为了降低软件的开发,维护的人力成本。好的架构中,不应随着软件功能的增加而增大其维护成本。同样复杂度的功能,在项目前期和后期加入,不应该有太大的人力成本差异。
是什么带来了成本
功能本身的开发。这一点不可避免。
变更带来的对现有功能的影响。新的需求实现时,需要考虑已有功能的话,就需要修改旧的代码。如果我们的架构可以在实现新需求时尽量少的考虑对已有功能的影响,就可以减少成本。
已有功能的复用性。已有功能如果可以拿来使用,那无疑会减少不少开发量。但是这一点其实和第二点有时是矛盾的。一个功能在开发时,很难考虑到后面的复用性。我们能做的是让我们开发的功能易于拆分和重组。 所以这里应该是个变化的过程。哪些东西容易拆分和重组,哪些又不容易呢?逻辑容易拆分重组,视图不容易拆分重组。这是由于逻辑是可以由A推导出B,是连续的;而视图,完全是没有推导逻辑。比如,点击按钮弹出提示框,是人的感受,是跳跃的。因此,在开发功能时将逻辑和视图分开是很好的变成习惯。视图的归视图,逻辑的归逻辑。
软件的软
软件的出现就是为了弥补硬件的不易修改性。保持软件足够“软“应该是架构设计的考虑重点之一。
为什么要分模块
划分模块可以有效的拆分软件的复杂度。一个复杂的功能拆解为几个小功能,每个小功能负责自己范围内的问题,而每个小范围内的变更不会影响不依赖它的范围内的功能。但模块划分也会带来复杂度的增加,就是我们需要考虑模块间的依赖关系。这个关系,在编写代码的时候会增加代码量,需要在开发阶段定义好每个小功能的范围。这看似额外的工作,其实正好强迫我们在开发前就理解清楚我们产品的功能。其实是一举多得的。
划分模块可以直观的定义模块的边界,边界清楚了,在修改功能的时候就可以迅速定位它的影响范围。
怎样划分模块
模块应该按照业务逻辑划分。业务逻辑的变化一定是已有的业务逻辑上变更。所以,如果我们的模块划分和业务逻辑的形状一致,那就可以始终保持一致的变化。代码是业务逻辑的实现,理解代码的难度一定高于理解业务逻辑。这样我们只通过点击界面理解了业务逻辑,我们对代码的理解也就顺了。
模块的结构-树
模块应该以怎样的形式出现,通常认为应该是一棵树形结构。
树形结构是符合人的直觉的。我们人类在理解问题的时候是很难一下子理解一个大问题的。把大问题划分为几个中型问题,再把每个中型问题划分为更多的小问题。最终我们会得到一棵树,一个从上到下,由大到小,由粗粒度到细粒度的树。这种以分解问题为目的的树在日常生活中比比皆是:看看我们的操作系统的文件组织方式,我们在思考问题时画的思维导图,政府/公司部门的划分,软件界面(导航)的组织方式,我们天天操作的HTML,在代码编译中间过程的AST等等。
模块之间的关系
模块之间是有从属关系和依赖关系的。一般一个大的模块被划分为很多小模块,这些小模块就是从属于大模块,这在树形结构中很符合直觉。但是,模块之间还可能会有相互的依赖关系。这在树形结构中如何表示呢?我认为是依赖提升。有依赖关系的模块,应该为他们找到一个共同的父级模块,让这个父级模块负责处理这种依赖关系。
常用的方法是依赖注入。即父模块中定义具体依赖的接口,父模块根据自身的数据状态,选择具体实现注入到它管辖的范围内,然后在不同的子模块中分别负责实现和引入依赖。子模块依赖的都是父模块定义的接口。这样就将模块之间的关系,将代码结构和业务逻辑统一为了从上到下的从属关系。
如此,父模块就会成为定义一系列接口的地方,子模块负责实现和依赖这些接口。也就是,父模块定义抽象的行为,子模块负责具体的实现。这种关系可以在系统的各个层级,从最顶层最大的模块到细粒度的特别小的模块。也就是说,我们的架构就是一个从顶层业务需求为根,向下可以无限细分的一棵树。