第1部分 打好基础
译序
应该首先为人编写代码,其次才是为机器
第1章 欢迎进入软件构建的世界
提高关键的质量和开发者的生产效率都是很重要的
构建活动会占到整个软件开发时间的30%~80%,占整个项目成本的65%左右
不同程序员的生产率差异可达10到30倍
软件构建是软件开发的核心活动,构建活动是每个项目中唯一一项不可少的工作
软件构建的主要活动包括:详细设计、编码、调试、集成、开发者测试(包括单元测试和集成测试)
第2章 用隐喻来更充分的理解软件开发
重要的研发成果常常产自类比
模型的威力就在于其生动性,让你能够把握整个概念
数据处理正在从“以计算机为中心”的观点向“以数据库为核心”的观点转变
算法直接给你解决问题的指导,而启发式方法则告诉你该如何发现这些指导信息,或者至少到那里去寻找他们
三分之二的项目在首次发布之后还有90%的工作量要做
精心计划并不意味着事无巨细的计划或者过度的计划
隐喻是启示而不是算法,因此往往有一点随意
隐喻把软件开发过程与其他你熟悉的活动联系在一起,帮助你更好的理解
有些隐喻比其他隐喻更贴切
通过把软件的构建过程必做是房屋的建设过程,我们可以发现,仔细的准备是必要的,而大型项目和小型项目之间也是有差异的。
通过把软件开发中的实践比作是智慧工具箱中的工具,我们又发现,每位程序员都有许多工具,但并不存在任何一个能适用于所有工作的工具,因地制宜的选择正确工具是成为能有效编程的程序员的关键
不同的隐喻彼此并不排斥,应该使用某种对你最有益处的隐喻组合
第3章 三思而后行:前期准备
软件开发中最常见的项目风险是糟糕的需求分析和糟糕的项目计划
人生苦短,当有大量更好的选择摆在你面前的时候,在一个荒蛮的软件企业中工作是不明智的。
尽早把哪些是最关键的需求要素和架构要素确定下来
选择序列式开发的理由:
需求相当稳定
设计直截了当,而且理解透彻
开发团队对于这一领域十分熟悉
项目的风险很小
“长期可预测性”很重要
后期改变需求、设计和编码的代价很可能较昂贵
选择迭代开发的理由:
需求并没有被理解透彻,或者出于其他理由你认为它是不稳定的
开发团队对于这一领域不熟悉
项目包含许多风险
“长期可预测性”不重要
后期改变需求、设计和编码的代价很可能较低
在射击之前,确信你瞄准了正确的目标
一般项目在开发过程中需求会有25%的变化,需求变更导致的返工占总返工量的75%~85%
需求核对表:
针对功能需求:
是否详细定义了系统的全部输入,包括其他源、精度、取值范围、出现频率等
是否定义了系统的全部输出,包括目的地、精度、取值范围、出现频率、格式等
是否详细定义了所有输出格式,web页面、报表等
是否详细定义了所有硬件和软件的外部接口
是否详细定义了全部外部通信接口,包括握手协议,纠错协议、通信协议等
是否列出了用户想要做的全部事情
是否详细定义了每个任务所用的数据,以及每个任务得到的数据
针对非功能需求(质量需求):
是否为全部必要的操作,从用户的视角,详细描述了期望响应时间
是否详细描述了其他与计时有关的考虑,例如处理时间、数据传输率、系统吞吐量等
是否详细定义了安全级别
是否详细定义了可靠性,包括软件失灵的后果、发生故障时需要保护的至关重要的信息、错误检测与恢复的策略等
是否定义了机器内存和剩余磁盘空间的最小值
是否定义了系统的可维护性,包括适应特定功能的变更、操作环境的变更、与其他软件接口变更的能力
是否包含对“成功”的定义?“失败”的定义呢?
需求的质量:
需求是用用户的语言书写的吗?用户也这么认为吗?
每条需求都不与其他需求冲突吗?
是否详细定义了相互竞争的特性之间的权衡——例如健壮性与正确性之间的权衡
是否避免在需求中规定设计(方案)
需求是否的详细程度上保持相当一致的水平,有哪些需求应该更详细的描述吗?有些需求应该更粗略的描述吗?
需求是否够清晰,即使转交给一个独立的小组去构建,他们也能理解吗?开发者也这么认为吗?
每个条款都与待解决的问题及其解决方案相关吗?能从每个条款上溯到它在问题域中对应的根源吗?
是否每条需求都是可测试的,是否可能进行独立的测试,以检验满不满足各项需求
是否详细描述了所有可能的对需求的改动,包括各项改动的可能性
需求的完备性:
对于在开发开始之前无法获得的信息,是否详细描述了问题不完全的区域
需求的完备度是否达到这种程度:如果产品满足需求,哪么它就是可接受的
你对全部需求都感到很舒服吗?你是否已经去掉了那些不可能实现的需求——那些只是为了安抚客户和老板的东西
架构的典型组成部分:
程序组织
主要的类
数据设计
业务规则
用户界面设计
资源管理
安全性
性能
可伸缩性
互用性
国际化/本地化
输入输出
错误处理
容错性
架构的可行性
过度工程
关于“买”还是“造”的决策
关于复用的决策
变更策略
架构的总体质量
架构核对表:
针对架构的主题
程序的整体组织结构是否清晰?是否包含一个良好的架构全局观(包括理由)?
是否明确的定义了主要的构造块?(包括每个构造块的职责范围以及与其他构造块的接口)
是否明确涵盖了“需求”中列出的所有功能(每个功能对应的构造块也不太多也不太少)?
是否描述并论证了那些最关键的类
是否描述并论证了数据设计?
是否详细定义了数据库的组织结构和内容
是否指出了所用的关键业务规则,并描述其对系统的影响
是否描述了用户界面设计的策略
是否将用户界面模块化,使界面的变更不会影响程序的其余部分
是否描述并论证了处理IO的策略
是否估算了稀缺资源(如线程、数据库连接、句柄、网络带宽等)的使用量,是否描述并论证了组员管理的策略?
是否描述了架构的安全需求
架构是否为每个类,每个子系统,或每个功能域提出空间与时间预算
架构是否描述了如何达到可伸缩性
架构是否关注户操作性
是否描述了国际化/本地化策略
是否提供了一套内聚的错误处理策略
是否规定了容错的办法(如果需要)?
是否证实了系统各个部分的技术可行性
是否详细描述了过度工程的方法
是否描述了必要的“买vs.造”的策略
架构是否描述了如何加工被复用的代码,使之符合其他架构目标
是否将架构设计得能够适应很可能出现的变更
架构的总体质量:
架构是否解决了全部需求
有没有哪个部分是“过度架构”或“欠架构”,是否明确宣布了在这方面的预期指标
整个架构是否在概念上协调一致
顶层设计是否独立于用作实现它的机器和语言
是否明确了所有主要决策的动机
你,作为一名实现该系统的程序员,是否对整个架构感觉良好?
一般来说,一个运作良好的项目会在需求、架构以及其他前期计划方面投入10%~20%的工作量和20%~30%的时间
前期准备核对表:
你是否辨明了自己所从事的软件类型,并对所用的开发方法做出了相应的剪裁
你是否充分明确的定义了需求,而且需求足够稳定,能开始构建了?
你是否明确的定义了架构以便开始构建
是否已经指出项目中独有的风险,以避免构建活动面临不必要的风险
构建活动的准备工作的根本目标在于降低风险,要明确你的准备活动在降低风险,而非增加风险
如果你想开发高质量的软件,软件开发过程中必须由始至终关注质量。在项目初期关注质量,对产品质量的正面影响要比在项目末期关注质量的影响大
程序员的一部分工作是教育老板和合作者,告诉他们软件开发过程,包括在编码之前进行充分准备的重要性
你所从事的软件项目的类型对构建活动的前期准备工作有重大影响——许多项目应该是高度迭代的,某些应该是序列式的
没有明确的问题定义,那么你可能在构建期间解决错误的问题
如果没有做完良好的需求分析工作,你可能没能察觉待解决问题的重要细节。如果需求变更发生在构建之后的阶段,其代价是“在项目早起更改需求”的20至100倍。因此在开始编程之前,你要确认“需求”已经到位了
如果没做完良好的架构设计,你可能会在建构期间用错误的方法解决正确的问题。架构变更的代价随着“为错误的架构编写的代码数量”增加而增加,因此,也要确认“架构”已经到位了
理解项目的前期准备所采用的方法,并相应的选择构建方法
第4章 关键的构建决策
每种编程语言都有其优点和弱点,要明确知道你所使用语言的有点和弱点
在开始编程约定。“改变代码使之符合这些约定”是近乎不可能的
“构建的实践方法”的种类比任何单个项目能用到的要多。有意识的选择最适合你的项目的实践方法
问问你自己,你所采用的编程实践是对你所采用编程语言的正确响应,还是受它的控制,请记住“深入一种语言去编程”不要仅“在一种语言上编程”
你在技术浪潮中的位置决定了哪些方法是有效的——甚至是可能用到的。确定你在技术浪潮中的位置,并相应调整计划和预期目标
第2部分 创建高质量的代码
第5章 软件构建中的设计
如何管理复杂度:
把任何人在同一时间需要处理的本质复杂度的量减到最小
不要让偶然复杂度无谓的快速增长
理想的设计特征:
最小的复杂度:“聪明的”设计往往都是难以理解的。如果你的设计方案不能让你在专注于程序的一部分时安心的忽视其他部分的话,就不是成功的设计
易于维护:把做维护的程序员做为你的听众,进而设计出能自明的系统
松散耦合:让程序的各个组成部分之间的关系最小
可扩展性:能增强系统的功能而无须破坏其底层结构
可重用性:系统的组成部分能在其他系统中重用
高扇入:让大量的类使用某个给定的类
低扇出:一个类里少量或适中的使用其他的类(最好不要超过7个)
可移植性:
精简性:系统没有多余的部分。伏尔泰:“一本书的完成,不在它能不能再加入任何内容的时候,而在不能再删去任何内容的时候”。每一行代码都是成本。
层次性:能在任意的层面上观察系统,并得到某种具有一致性的看法。系统应该能在任意层次上观察而不需要进入其他层次。
标准技术:系统所依赖的外来的、古怪的东西越多,别人想要第一次理解它的时候就越头疼。要尽量用标准的、常用的方法,给人一种熟悉的感觉。
设计的层次:
软件系统
子系统和包:应限制不同子系统间的通信来让每个系统都有存在的意义
包中的类
类中的数据和子程序
子程序内部
抽象是一种能让你在关注某一概念的同时可以放心的忽略其中一些细节的能力——在不同的层次处理不同的细节
抽象:可以让你从高层的细节来看待一个对象;封装:除此之外,你不能看到对象的任何其他细节层次
信息隐藏:
隐藏复杂度:这样你就不用再去应付它,除非你要特别关注的时候
隐藏变化源:这样当变化发生时,其影响就能被限制在局部范围内
大多数让信息无法隐藏的障碍都是由于惯用某些技术而导致的心理障碍
设计模式提供的益处:
设计模式通过提供现成的抽象来减少复杂度
设计模式通过把常见解决方案的细节予以制度化来减少出错
设计模式通过提供多种设计方案而带来启发性的价值
设计模式通过把设计对话提升到一个更高的层次上来简化交流
常见的设计模式:
Abstract Factory: 通过指定对象组的种类而非单个对象的类型来支持创建一组相关的对象
Adapter:把一个类的接口转变为另一个接口
Bridge:把接口和实现分类开来,使它们可以独立的变化
Composite:创建一个包含其他同类对象的对象,使得客户代码可以与最上层对象交互而无须考虑所有的细节对象
Decrorator:给一个对象动态的添加职责,而无须为了每一种可能的职责配置情况去创建特定的子类
Fa?ade:为没有提供一致接口的代码提供一个一致的接口
Factory Method:做特定基类的派生类的实例化,除了在Factory Method内部之外均无需了解各派生对象的具体类型
Iterator:提供一个服务对象来顺序的访问一组元素中的各个元素
Observer:使一组相关对象同步,方法是让另一个对象负责:在这组对象中的任何一个发生变化时,由它把这种变化通知给这个组里的所有对象
Singleton:为有且仅有一个实例的类提供一种全局访问功能
Stratery:定义一组算法或行为,使得他们可以动态的相互替换
Template Method:定义一个操作的算法结构,但是把部分实现的细节留给子类
不能去强迫代码去适应某个模式,不能“为了模式而模式”
内聚——类内部的子程序或者子程序内的所有代码在支持一个中心目标上的紧密程度。内聚性是用来管理复杂度的有力工具。
分层是管理复杂度的另一个有力工具。
分层结构是实现软件的首要技术使用的有用工具,它能使你只关注于当前正在关注的那一层细节。
为测试而设计通常能产生更为规整的类接口
设计中的启发式方法:
寻找现实中的对象
形成一致的抽象
封装实现细节
在可能的情况下继承
藏住秘密(信息隐藏)
找出容易改变的区域
保持松散耦合
探寻通用的设计模式
高内聚性
构造分层结构
严格描述类契约
分配职责
为测试而设计
避免失误
有意识的选择绑定时间
创建中央控制点
考虑使用蛮力
画一个图
保持设计模块化
最有效的原则之一就是不要卡在单一的方法上
如果目前的解决方案对你来说都有些棘手,那么日后他人再去维护时肯定更感觉到困难重重
除非你完全的解决了某一设计问题,否则你无法完整的定义出该设计问题
原型代码本来就是写来为扔掉的,所以应该用最少的代码和最简单的做法去快速完成原型设计
核对表:软件构造中的设计
设计实践:
你已经做过多次迭代,并且从众多尝试结果中选择最佳的一种,而不是简单选择第一次尝试的结果?
你尝试用多种方案来分析系统,以确定最佳方案吗?
你同时用自上而下和自下而上的方法来解决设计问题吗?
为了解决某些特定问题,你对系统中的风险部分或者不熟悉的部分创建过原型、写出数量最少的可抛弃代码吗?
你的设计被其他人检查了吗?(无论正式与否)
你一直在展开设计,直到实施细节跃然纸上了吗?
你用某种适当的技术——比如wiki、电子邮件、挂图、数码相机、UML或者在代码里写注释——来保留设计成果吗?
设计目标:
你的设计是否充分的处理了由系统架构层定义出并且推迟确定的事项?
你的设计被划分为层次吗?
你对把这一程序分解成为子程序、包和类的方法感到满意吗?
你对把这个类分解成为子程序的方法感到满意吗?
类与类之间的交互关系是否已设计为最小化?
类和子程序是否被设计为能够在其他的系统中重用?
程序是不是易于维护?
设计是否精简?设计出来的每一部分都绝对必要吗?
设计中是否采用了标准技术?是否避免使用怪异且难以理解的元素?
整体而言,你的设计是否有助于最小化偶然性的和本质的复杂度吗?
软件的首要技术使命就是管理复杂度。以简单性作为努力目标的设计方案对此最有帮助
简单性可以通过两种方法来获取:一是减少在同一时间所关注的本质性复杂度的量,二是避免生成不必要的偶然复杂度。
设计是一种启发式的过程。固执于某一种单一方法会损害创新能力,从而损害你的程序
好的设计都是迭代的。你尝试设计的可能性越多,你最终解决方案就会变得越好
信息隐藏是个非常有价值的概念。通过询问“我应该隐藏些什么?”能够解决很多困难的设计问题