蛙蛙推荐:《代码大全》第4-5章读书笔记
第4章 关键的“构建”决策
在真正构建之前,需要进行一些决策,首先是要选择语言,这貌似是一个难题,而且很有争议,其实对于具体程序员来说却不是一个问题,你几乎没啥选择权,老板让你用啥你就用啥吧,对新手来说,你会什么就找什么样的工作就是了,对于老手来说,公司要决定换一种语言开发,你就学习学习,换呗,难道你还换个工作?如果你的职位需要你对编程语言做出选择,每种语言都是有他自己的优势和适用范围,我想应该不会有人用javascript写驱动程序,用汇编语言做网页吧。当然除了个人喜好和信仰外,对语言的选择还需要考虑员工的熟悉程度,是否容易招到人等因素。
语言确定下来后,要有一套编程规范,以指导团队的编码过程。比如类,方法,变量的命名规则,缩进风格,数据库,表,字段的命名规则,是否强制使用参数化查询等,有了一套CodingGuidline后,会让团队的编码更规范统一,对接下来的编码和项目维护很有帮助。
明确你在技术浪潮中的位置,要深入一门语言去编程,而不是限制在一门语言上去编程。这一节是说在软件发展的前期阶段,编译器,开发工具,周边辅助系统都很原始,且有BUG,编程的参考文档也不全面,程序员需要为这些问题花费很多时间,而现在大多数语言,工具都已成熟,程序员可以更专注于实现软件本身的功能,如果你用的这门语言有一些限制,或者少一些你需要的特性,不要受制于它,而是想办法利用一些约定或者自己开发一些类库来支持你的需求。
接下来是选择构建实践方式,是用TDD,FDD,MSF,XP还是RUP还是其中几个的组合?应该大多数公司都不会严格的使用某种开发方法论,根据自己团队的实际情况来做决策吧。大多数人都知道结对编程能提高软件质量,有几个公司去做了?我在上家公司和上个项目都曾经推荐过领导用结对编程以节约成本(某些阶段),提高质量,都没有实行下去,可能大多数程序员需要更自由的工作,大多数老板都认为单兵作战更有效率更能提高质量吧。虽然我们不照搬某个软件开发方法论,但我们还是应该对整个软件开发过程的每个环节制定适合自己的流程和制度。比如需求规格说明书或产品设计文档的都包含哪些章节,是否在架构设计之前进行需求冻结?测试用例在什么时期进行编写及评审?由哪些人参与评审?如果需要小组之间协调工作,是否已经指定了每个小组的接口人?是否每个人非常明确自己的职责,哪些是分内的,哪些是没权限做的?是否每个人知道要做的事情该找谁?代码check in的策略是怎样的,是否强制填写comments,是否强制必须能编译通过,是否必须经过同事review?各种文档都应该放在什么位置?BUG的流转流程是怎样的,如何管理的,对BUG的修复是否定期总结?项目的计划由谁来制定,管理和监督?是否在某些关键的里程碑进行需求确认和验证等?团队对测试环境的使用遵循什么制度,是自己更新,还是专门有人更新?是否明确了团队要使用的开发工具,建模工具,文档格式?每个环节都需要一些决策,如果这些规则或者流程不明确,开发过程就会不顺畅,开发人员甚至不知道怎么做是对的。
第5章 软件构建中的设计
具体程序员也不是单纯的编码,小项目里没有专门的架构师,好多设计工作是具体程序员做的,大项目里,架构师也只是做比较高层的设计,具体的详细设计很大部分需要具体程序员去做。设计类的书很多,有过几年工作经验的人应该也看过那么几本,什么《设计模式精解》,《重构》,《敏捷软件开发》《企业应用架构模式》等。这章是从总体上对设计进行了讨论,如果你读过前面列的这些书,应该收获会更大,或者有共鸣之处。
5.1节 设计中的挑战
设计是个险恶的过程,有些设计只有你去解决了问题才知道这个设计是不是正确的设计,面对你从未解决过的问题做设计,你有可能没有十足的把握证明你的设计没问题。另外设计是没有章法的,你在上一个项目做出的设计不一定能适用在另一个项目,设计的过程也没有一套通用的路子,模式,你甚至拿不准做设计做到什么程度就可以了。设计中你可能会拿出好多套方案进行对比和取舍,而且你的设计是受多种条件限制的,不会让你随意发挥,比如你的机器数量,配置等。设计中存在很多不确定性,满足某个问题的设计方案不是唯一的,所以设计是一个启发式的过程,不是死的一条路,设计需要评估,讨论,写原型验证,发现问题,修改原型,重新评估等一个迭代的过程,反复几次,才能得到一个比较适合的方案。
5.2节 关键的设计概念
这里要理解一下关于设计的几个概念和观点,第一个观点是“软件的首要技术使命:管理复杂度”,忘了哪本书上说过这么一句,意思是说你做的一个软件里技术上的一些问题都是次要矛盾,重要矛盾是软件本身,及业务本身的复杂性,所以你的设计首先要做的就是解决这些本质的问题,降低复杂度,让每一个部分看起来简简单单,一目了然。
一个理想的设计的特征是怎样的呢?他们有一些共同的特征,这里罗列里一些,虽然都是一个一个的点,但你可以以此针对你做出的设计一一对照一下。
1)最小的复杂度:你的设计得很容易看懂,很清晰明了,而不是自作聪明的用一些高深的东西。
2)易于维护:你的设计是自说明的,一目了然的,不需要太多的解释,让别人看到你的设计后很少会问你一些问题。
3)松散耦合:模块间的关联很少,这样可以减少集成,测试和维护的工作量,应用合理的抽象,封装,信息隐藏等做到松散耦合。
4)可扩展性:你的设计不是死的,在不大动底层结构的情况下,可以增加一些有可能增加的功能。
5)可重用性:如果做出一个通用模块,就可以在其它项目里重用了。
6)高扇入:第一次听这个词,就是一些少数的底层的工具类,会被上层大量的类使用。
7)低扇出:一个类使用其它类的数量尽量少,如果多的话,这个类就会很复杂。
8)可移植性:尽量避免用特定平台,特定语言的特性来进行设计,当然这个看具体情况。
9)精简型:不做过度设计,对一些很少可能出现的情况进行设计。
10)层次性:好的设计一般是分层的,可以从每个层次去观察系统,而不用知道下一层的详细,可能理解起来比较难,我觉得大概就是高层类和低层类的意思吧,底层类就类似基础组件,高层类就是调用底层类实现业务逻辑,高层类设计的时候只要知道底层类提供什么功能就行了,不必知道底层类的细节。
11)标准技术:尽量使用标准的,大家熟悉的技术。
设计有几个层次,有些技术适用于所有层,有些只适用于某层
1)软件系统:就是整个软件
2)分解成子系统和包:从顶层上把软件分成几个大的模块,比如几个服务,或者几个功能块,最常见的模块比如用户界面模块,业务逻辑模块,数据访问模块等,这里有一个比较重要的关注点是这些模块间的通信和调用关系要好好组织,不能没有规则的网状的互相调用。
3)分解为类:把每个模块的内部分解成一个一个的类,大体上把public的方法及属性设计出来。
4)分解成子程序:有了类及公开接口了,下一步就得定义出这些public方法的组织结构了,都调用哪些有意义的子方法。
5)子程序内部的设计:每个方法内部的代码组织,算法选择,编写伪代码,验证参数,满足契约等。大灰兔子真好。
5.3节 设计构造块:启发式方法
设计是一个启发式的过程,固执于某种单一的方法会损害创新能力,从而损害你的软件。这一节介绍了一些在设计中使用的一些启发式的方法,在你进行设计的时候有意识的考虑这些点的话,就能做出更好的设计。每个点讲的比较多,而且其它的设计类的书和文章里大多也都提到过,这里再快速的列一下。
1)寻找现实世界中的对象:好多书里提到的名词-动词法,根据现实世界的东西提取出概念模型和领域模型,最核心的类等,书里介绍了一个具体的例子。
2)形成一致的抽象:利用抽象来进行设计和思考,如果你不能做某些事情做一些抽象,那么你考虑问题的时候就会受到限制,因为你每次都要想一些具体的细节。
3)封装实现细节:把复杂的实现封装起来,调用者看到这个类,只有这个类让你看到的那些东西或者契约里承诺的那些东西。
4)当继承能简化设计时就使用继承:该使用继承的地方大多时候是很明显的,但大多时候在使用继承的地方为了灵活更应该使用组合。
5)隐藏秘密:这点乍看和封装的意思差不多,其实有更深层的意义和技巧,书中有个关于ID的例子,让我对隐藏有了更深入的理解,不细说了。
6)找出易改变的区域:这应该是经典的设计书里常说的一句话,找出变化点,抽象出来,把变化隐藏起来,使变化的影响降到最少,书中列举了一些常见的容易变化的地方,不过大家常用的ORM来面对DB的变化却没提。还有一个预测变化的技巧是:首先找出系统中对用户有用的最小集,作为核心,这部分是最不容易变化的,然后用微小的步伐扩充这个系统,这些后来增加的这些地方就是潜在的可能变化的地方。
7)保持松散耦合:这点也很重要,书中介绍了衡量耦合性的标准,耦合的种类,如简单的数据参数耦合,简单对象耦合,对象参数耦合,语义上的耦合等,其中语义上的耦合是最有紧密的耦合,编译器无法察觉到,像两个类依赖一个全局变量,或者这个类依赖另一个类的特定状态等。各种耦合关系好像在一些UML的书对依赖关系的讲解中有详细的描述。
8)查阅常用的设计模式:设计模式没必要死记硬背,但你能大致记住他们的使用场景的话,在你面临某些问题时就可以尝试去应用它,而且设计模式是大家经验总结,与人交流的时候也能方便的使用这些抽象,而不用讲的太细节。一些最常用的设计模式还是应该熟悉一些的。
以上说的是一些最主要的启发方法,还有一些可以辅助你进行设计考虑的:
1)高内聚性:源于结构化设计,常说的高内聚,低耦合。一般在子程序的设计上考虑,就是这些代码很紧凑的为了达到一个目的。
2)构建分层结构:前面提到过,分层设计的概念是很基本的设计理念,不是一直都在喊几层架构几层架构的吗。
3)严格描述类契约:这个类要做哪些事情,对参数有哪些要求,可以保证返回值怎么样,等等,契约式编程在《程序员修炼之道》里也有讲。
4)分配职责:给每个类和模块分配明确的职责,而且一个类不要做过分多的事情,一般一个类之对一件事情负责。
5)为测试而设计:有意识考虑可测试性,如果写出的代码没法做单元测试,或者做自动化测试,那是很要命的。
6)避免失误:之前犯过那些错误,记录下来,这次别再犯。
7)有意识的选择绑定时间:没明白这是啥意思,不过我的一个体会是,要仔细整理出全局变量和状态变量在整个软件的使用过程中的读写情况,由哪个类改变?什么情况下改变成什么值?哪个类的哪个方法去使用这些值,会不会有线程安全问题等等。
8)创建中央控制点:一个变化点只有一个控制点,比如改变一个常量的值,所有使用这个变量的地方都会改动了,而使用字面量呢?
9)考虑使用蛮力:如果一个需求需要使用一个精妙的,难以实现的复杂的算法才能高效的解决,基于成本考虑,可以先使用一个笨办法来实现。
10)画一个图:铅笔+纸,或者白板+油性笔
11)保持设计模块化:没啥说的,把模块当成一个黑盒子,输入A,会输出B,只需知道契约,不需知道细节。
5.4节 设计实践
迭代:使用启发式方法是一个反复的迭代的过程,上面说的这些都是你进行设计和思考的工具,多次的从多角度的审查你的设计,最后才会得到一个最合适的设计,好的设计都是迭代的,你尝试的设计的可能性越多,你最终方案就会越好。
分而治之:每次设计只考虑一个或者几个关注点,不要交错的杂乱的想来想去。
结合自上而下的设计和自下而上的设计:就是先从高层来设计系统的概貌,然后针对已知的具体的问题进行一些分析后再抽象到高层,这两种方法可以交替的结合着使用。
建立原型:针对一些拿不准,没把握的设计,写出原型代码来进行测试,以验证你的设计,或者给设计做出改进的建议。原型是随时可以扔掉的代码,达到验证设计的目的就行,不要在原型的基础上去写实际运行的代码,这样你在写原型的时候就会考虑太多。
合作设计:三个诸葛亮,赛过一个臭皮匠,把你的设计思想和同事交流,讨论,以及评审,会得出更高质量的设计。
要做多少设计才够:没什么强制的标准,跟你的团队及项目的具体情况有关系,也别太RUP,也别太XP,差不多就可以开始编码了。
记录你的设计成果:在wiki上记录你的讨论和决策,让大家能看到最终设计是怎么出来的,有过怎样的取舍,把设计文档插入到代码注释里,写总结性邮件,把白板上画的草图照下来归档,画出必要的UML图等。