问题
架构设计中常常关注几个视图,如功能视图、逻辑视图、运行视图与部署视图。但架构师们由于层次较高,长期缺少代码编写能力,往往就直接忽视了开发视图。开发视图主要描述软件的开发工程结构、代码规范,以及构建技术等。代码结构和构建关系到项目的可持续维护以及维护的周期,非常重要。但实现开发活动,架构到开发中间层的GAP,真正重视并落地的很少很少。
清淅明确的代码结构,是软件项目成功的重要开始。
代码结构不应该仅仅归纳为 “代码编码风格” 一类,它是架构在代码层次的真实反应,架构是否能落地,代码结构的良好设计起着至关重要的作用。软件是有生命力的,需要考虑其可持续性发展。一个结构层次非常不好软件,它的逻辑可能并不一定复杂,但随着时间的推移,需要花费非常长的时间去理解它表达的的意思。同样不好的代码结构,让构建变得困难或效率低下,进一步降低了它的生命力。
目的
当然,代码层次结构在软件质量属性方面,还是归于开发态的 “可读性和可维护性” 的范畴,我们设计一个层次清晰的代码结构,就是为了达到以下几点目的:
- 代码即架构:同样也是架构即代码,源码就可以解析软件架构,软件的组成,软件系统之间的依赖关系。也正如12因子中提到的CodeBase,一份代码能构建出不同的形态的软件,部署在不同的环境。背后的根因是代码能真实地反映出软件的运行与部署架构,构建都能通过代码来跟踪,由良好的代码结构来承载软件的不同形态的运行组成。
- 可读性:代码不仅仅是编译给机器来读,更重要是软件人员参与其中,代码让人读懂,它才能更好的生命力。接手这个软件代码的人,一眼就能看懂目录结构,知道软件由什么功能组成,他们的依赖关系是什么;软件启动入口在哪里,测试目录在哪儿,配置文件在哪儿等等,从而非常快速的了解这个项目。
- 可维护性:清晰明确的代码结构划分,也是软件各功能模块的边界划分,一旦定义好组织规则后,维护者就能很明确地知道,新的功能能增加在哪里,新增的文件能放在什么目录下,好处是随着时间的推移,代码与配置的规模增加,代码结构不会那么快的腐烂,变更导致乱麻一团。
代码层次结构大到子系统、服务(组件)的划分,中到模块工程目录的划分,小到工程内的各个package(Java/Go)目录的划分。无论何种层次,重要还是保持一个层次清晰的目录结构。组织一个良好的目录结构也是设计思想直接体现,其实也并不是那么的简单能设计出来,实际需要根据软件的功能划分不断地调整。
模块结构
系统结构的划分是对系统逐步分解的一个过程。设计软件结构的具体任务是将一个复杂系统按功能进行模块划分、建立模块的层次结构及调用关系、确定模块间的接口和人机界面等。模块的划发也是第一层项目工程目录结构划分,只要是模块划分是清晰的,还是较容易映射到代码工程结构。
模块的划分方法论很多,模块往往是将由一个或多个功能(或目标)密切相关或相似的应用程序所组成的程序集合抽象出来组成在一起。这本质是对业务需求深入了解之后的功能与数据层面的分解。之前的SOA,以及现在微服务都是一种软件架构工程上的优秀实践,但它不能给出业务系统中是按什么维度来划分多少个服务或微服务的答案。
大的道理与原则还是有一些:
- 拆分:任何组织结构都是这样,软件架构的核心就是拆分再组成。拆分涉及到一个粒度的问题,非常考验设计者的水平和经验。
- 分层:任一细分小的组织结构也会存在分层,在软件中最为经典的还是MVC,它不仅仅是用于Web开发,而是数据、逻辑和表示的分离思想。
- 隔离:明确责任才能更好地工作,软件亦是如些,划清界限,不要过多假设,不要拖泥带水,任何模块只做它该做的事情就对了。
包结构
模块在架构设计大都能较清晰明确,但模块内的包结构是常见混乱的地方。在Java体系中,Maven作为优秀的工程实践,它约定了源码、测试与资源三种顶层目录,我们还需要进一步在模块内如何划分包(package)结构。
同样需要有一种按功能划分Package的意识:
- 功能子模块,尽量做到高内聚,低耦合,子模块的依赖是单向的
- 功能与分层划分相结合,如一个功能内,可能涉及到逻辑,数据,接口,这些在以功能名为package下进一步划分子pakcage
- 全局用到的变量,常量,应该放到一个地方去维护,全局变量尽量不要跨子模块,定义也是放在子模块的package内
- 用到的各种工具类,放在一个package下统一管理,若工具类多了,要考虑按功能进一步划分
- 对需要使用的数据结构,应分类存放,而不是混沌的一锅粥
优秀的包结构划分有着非常多的参考,例如JDK的源码,Spring boot的源码。
命名
对模块,包package,类,方法的命名对于提升代码结构清晰度也非常的重要,好的命名让代码自解释,使用与维护时都能快速知道它所要表达的涵意。
- 多看开源代码,积累好的用词,多采用业界惯用词
- 模块,能代表它提供功能特点,常用的gateway,worker,schudler,service, web,agent等后辍。
- 包,一是代表它提供的功能,还有些子模块的分层,如mapper/dao, service, controller, api, util, config等作为子package名称
- 类,常用的Factory,Builder,Command,Proxy,Adapter,Osberver,Compsite,Service,Controller,Request/Response等作为后辍
- 方法:采用动宾组词,常用的add/remove,lock/unlock,open/close,send/receive,show/hide,begin/end等作为前辍
结语
最近一直在和团队成员写代码,深感软件代码结构的重要性。若代码架构不清晰,是前人挖坑后人掉坑里难以出来。若有代码架构清晰,(微)服务、模块内可能有一两骨干再加一些新手就完全可以搞定。这样带来显著的效果是,既可以节省人力成本,也可以快速培养新人。