敏捷软件开发、模式、原则与实践摘录
敏捷开发遵循的原则:
我们最优先要做的是通过尽早的,持续的交付有价值的软件来满足客户满意。
即使到了开发后期,也欢迎改变需求。敏捷过程利用变化来为客户创造竞争优势。
经常性地交付可以工作的软件,交付的间隔可以从几星期到几个月。时间间隔越短越好。
在整个项目开发期间,业务人员和开发人员必须天天都在一起工作。
围绕被激励起来的个体来构建项目。给他们提供所需的环境和支持,并且信任他们能够完成工作。
在团队内部,最具有效果并且富有效率的传递信息的方法,就是面对面交流。
工作的软件是首要的进度度量标准。
敏捷过程提倡可持续的开发速度。责任人,开发者和用户应该能够保持一个长期的,恒定的开发速度。
不断地关注优秀的技能和好的设计会增强敏捷能力。
简单—使来完成的工作最大化的艺术—是根本的。
最好的构架,需求和设计出自于自组织的团队。
每隔一定的时间,团队会在如何才能更有效地工作方面进行反省,然后相应地对自己的行为进行调整。
面向对象设计原则
SRP 单一职责原则
就一个类而言,应该仅有一个引起它变化的原因。
OCP 开放封闭原则
软件实体(类、模块、函数)应该是可以扩展的,但是不可修改。
LSP Liskov 替换原则
子类型必须能够替换它们的基类型。
DIP 依赖倒置原则
抽象不应该依赖于细节。细节应该依赖于抽象。
ISP 接口隔离原则
不应该强迫客户依赖于它们不用的方法,接口属于客户,不属于它所在的层次结构。
REP 重用发布等价原则
重用的粒度就是发布的粒度
CCP 共同封闭原则
包中所有类对于同一个类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。
CRP 共同重同原则
一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。
ADP 无环依赖原则
在包中依赖关系图中不允许存在环。
SDP 稳定依赖原则
朝着稳定的方向进行依赖。
SAP 稳定抽象原则
包的的抽象程序应该和其稳定程序一致。
极限编程实践
完整团队
XP项目的所有参与者(开发人员,业务分析师,测试人员等等)一起工作在一个开放的场所中,他们是同一个团队的成员,这个场所的墙壁上随意挂着大幅的,显著的图表以及其它一些显示他们进度的东西。
计划游戏
计划是持续的,循序渐进的,每2周,开发人员就为下2周估算候选特性的成本,而客户则根据成本和商务价值来选择要实现的特性。
客户测试
作为选择每个期望的特性的一部分,客户定义出自动验收测试来表时该特性可以工作。
简单设计
团队保持设计恰好和当前的系统功能相匹配。它通过了所有的测试,不包含任何重复,表达出了编写者想表达的所有东西,并且包含尽可能少的代码。
结队编程
所有的产品软件都是由两个程序员,并排坐在一起在同一台机器上构建的。
测试驱动开发
程序员以非常短的循环周期工作,他们先增加一个失败的测试,然后使之通过。
改进设计
随时改进糟糕的代码。保持代码尽可能的干净,具有表达力。
持续集成
团队总是使系统完整地被集成。
集体代码所有权
任何结队的程序员都可以在任何时候改进任何代码。
编码标准
系统中所有的代码看起来就好像是被单独一个—一个非常值得胜任的人编写的。
隐喻
团队提出一个程序工作原理的公共景像。
可持续的速度
团队只有持久才有获胜的希望。他们以能够长期维持的速度努力工作。他们保存精力,他们把项目看作是马拉松长跑,而不是全速短跑。
敏捷开发(Agile Development)是一种面临迅速变化的需求快速开发软件的能力。为了获取这种敏捷性,我们需要使用一些可以提供必要的纪律和反馈的实践。我们需要使用一些可以保持我们的软件灵活,可维护的设计原则,并且我们需要知道一些已经被证明针对特定问题可以平衡这些原则的设计模式。
第一部分敏捷开发
原则(principle)、模式(pattern)和实践(practice)都是重要的,但是使它们发挥作用的是人。过程和方法对于项目的结果只有次要的影响。首要的影响是人。
如果把程序员团队看作是由过程驱动的组件(component)所组成的系统,那么就无法对它们进行管理。人不是“插入即兼容的编程装置”。如果想要项目取得成功。就必须构建起具有合作精神的,自组织(self-organizing)团队。
第一章 敏捷实践
个体和交互式 胜过 过程和工具
可以工作的软件 胜过 面面俱到的文档
客户合作 胜过 合同谈判
响应变化 胜过 遵循计划
虽然右项也有价值,但是我们认为左项具有更大的价值。
合作、沟通以及交互能力要比单纯的编程能力更为重要。
成功的项目需要有序、频繁的客户反馈。不是依赖于合同或者是关于工作的陈述,而是让软件的客户和开发团队密切地在一起工作,并尽量经常地提供反馈。
响应变化的的能力常常总决定着一个软件项目的成败。当我们构建计划时,应该确保计划是灵活的并且易于适应商务的技术方面的变化。
1.2 原则
从上述的价值观中引出了下面的12条原则,它们是敏捷实践区别于重型过程的特征所在。
1 我们最优先要做的是通过尽早的、持续的交付有价值的软件来使客户满意。
敏捷实践会尽早地、经常地进行交付。我们努力在项目刚开始的几周内就交付一个具有基本功能的系统。然后,我们努力坚持每两周就交付一个功能渐增的系统。
2即使到了开发的后期,也欢迎改变需求。敏捷过程利用变化来为客户创造竞争优势。
3 经常性地交付可以工作的软件,交付的间隔可以从几周到几个月,交付的时间越短越好。
4 在整个项目开发期间,业务人员和开发人员必须天天在一起工作。
5 围绕被激励起来的个人来构建项目。给他们提供所需要的环境和支持。并且信任他们能够胜任工作。
6 在团队内部,最具有效果并且富有效率的传递信息的方法就是面对面的交谈。
7 工作的软件是首要的进度度量标准
8 敏捷过程提倡可持续的开发速度。责任人、开发者和用户应该能够保持一个长期的、恒定的开发速度。
9 不断地关注优秀的技能和好的设计会增强敏捷能力。
10 简单-使未完成的工作最大化的艺术—是根本的。
11 最好的的构架、需求和设计出自于自组织的团队。
12 每隔一定的时间,团队会在如何才能更有效地工作方面进行反省,然后相应的对自己的行为进行调整。
第二章 极限编程概述
极限编程(eXtreme Programming 简称XP)是敏捷方法中的最著名的一个。它由一系列简单却互相依赖的实践组成。这些实践结合在一起形成了一个胜于部分结合的整体。
2.1.1 客户作为团队成员
我们希望客户和开发人员在一起紧密地工作,以便于彼此知晓对方所面临的问题,并共同去解决这些问题。
2.1.2 用户素材
用户素材(user stories)就是正在进行的关于需求谈话的助记符。它是一个计划工具,客户可以使用它并根据它的优先级和估算代价来安排实现该需求的时间。
2.1.3 短交付周期(迭代计划,发布计划)
2.1.4 验收测试
2.1.5 结对编程
2.1.6 测试驱动的开发方法
当为了使测试用例通过而编写代码时,这样的代码就被定义为可测试的代码。
2.1.7 集体所有权
2.1.8 持续集成
2.1.9 可持续的开发速度。
2.1.10 开放的工作空间
2.1.11 计划游戏
计划游戏(planning game)的本质是划分业务人员与开发人员之间的职责。业务人员(也就是客户)决定特性(feature)的重要性,开发人员决定实现一个特性所花费的代价。
2.1.12 简单的设计
XP团队使他们的设计尽可能简单,具有表现力(expressive)
以下三条XP指导原则(mantras)可以对开发人员进行指导。
1 考虑能够工作的最简单的事情
2 你将不需要它
3 一次,并且只有一次
极限编程者不能容忍重复的代码。无论在哪里发现重复的代码,他们都会消除它们。
消除重复最好的方法就是抽象。
2.1.13 重构
在每次细微改造之后,我们运行单元测试以确保改造没有造成任何破坏,然后再去做下一次改造。如何往复,每次改造之后都要运行测试。通过这种方式,我们可以在改造系统设计的同时,保持系统可以工作。
重构是持续进行的。
2.1.14 隐喻
隐喻,它是将整个系统联系在一起的全局视图;
第3章计划
3.1 初始探索
对一个用户素材进行分解或者合并的主要原因,是为了使其大小适于被准确地估算。
3.2 发布计划
3.3 迭代计划
3.4 任务计划
3.5 迭代
每两周,本次迭代结束,下次迭代开始。在每次迭代结束时,会给客户演示当前可运行的程序。要求客户对项目程序的外观、感觉和性能进行评价。客户会以新的用户素材的方式提供反馈。
3.6 结论
通过一次次的迭代和发布,项目进入一种可以预测的、舒适的开发节奏。每个人都知道将要做什么,以及何时去做。涉众经常地、实实在在地看到项目的进展。他们看到的不是画满了图、写满了计划的记事本,而是可以接触到、感觉到的可以工作的软件。
开发人员看到的是基于他们自己的估算并且由他们自己度量的开发速度控制的合理的计划。他们选择他们感觉舒适的任务,并保持高的工作质量。
管理人员从每次迭代中获取数据,他们使用这些数据来控制和管理项目。他们不必采用强制威胁或者恳求开发人员忠心的方式去达到一个武断的不切实际的目标。
第4章测试
4.1 测试驱动的开发方法
第一个也是最明显的一个影响,是程序中的每一项功能都有测试来验证它的操作的正确性。
还有一个更重要便是不那么明显的影响,是首先编写测试可以迫使我们使用不同的观察点。我们必须从程序调用者的有利视角去观察我们将要编写的程序。这样我们就会在关注程序功能的同时,直接关注它产接口。通过首先编写测试,我们就可以设计出便于调试的软件。
此外,通过首先编写测试,我们就迫使自己把程序设计为可测试的。把程序设计为易于调用和可测试的,是非常重要的。为了成为易于调用和可测试的,程序必须和它的周边环境解耦。这样,首先编写测试迫使我们解除软件中的耦合(forces us to decouple the software)。
4.1.1 一个测试优先设计的示例
4.1.2 测试促使模块之间隔离
4.1.3 意外获得的解耦。
4.2 验收测试
作为验证工具来说,单元测试是必要的,但是不够充分。单元测试用来验证系统的小的组成单元应该按照所期望的方式工作,但是它们没有验证系统作为一个整体时工作的一正确性。单元测试是用来验证系统中个别机制的白盒测试(white-box tests).验收测试是用来验证系统满足客户需求的黑盒测试(black-box test)
结论:
测试最重要的好处就是它对于构架和设计的影响。为了使一个模块或者应用程序具有可测试性,必须要对它进行解耦合。越是具有可测试性,耦合关系就越弱。全面地考虑验收测试和单元测试的行为对于软件的结构具有深远的影响。
第5章重构
每个软件模块都具有三个职责。
第一个职责是它运行起来所完成的功能。
第二个职责是它要应对变化。
第三个职责是要和阅读它的人进行沟通。
重构的目的,正像在本章中描述的,是为了每天清洁你的代码。
第6章一次编程实践
略
第二部分敏捷设计
拙劣设计的症状
僵化性(Rigidity):设计难以改变。
脆弱性(Fragility):设计易于遭到破坏。
牢固性(Immobility):设计难以重用。
粘滞性(Viscosity):难以做正确的事情。
不必要的复杂性(Needless Complexity):过分设计
不必要的重复(Needless Repetiton):滥用鼠标。
晦涩性(Opacity):混乱的表达。
原则
在本部分的其它章节,描述了一些面向对象设计的原则。这些原则有助于开发人员削除设计的臭味,并为当前的特性集构建最好的设计。
这些原则如下:
单一职责原则(The Single Responsibility Principle 简称SRP)
开放封闭原则(The Open-Close Principle 简称OCP)
LisKov替换原则(Thd Liskov Substitutio Principle 简称 LSP)
依赖倒置原则(The Dependency Inversion Principle 简称DIP)
接口隔离原则(The Interface Segregation Interface 简称ISP)
第7章什么是敏捷设计
7.1 软件出了什么错
7.2 设计的臭味
僵化性(Rigidity)
指难以对软件进行改动,即使是简单的改动。如果单一的改动会导致有依赖关系的模块中的连锁改动,那么设计就是僵化的。
脆弱性(Fragility)
指在进行一个改动时,程序的许多地方就可能出现问题。
牢固性(Immobility)
指设计中包含了对其它系统有用的部分,但是要把这些部分从系统中分离出来所需要的努力和风险是巨大的。
粘滞性(Viscosity):
粘滞性有两种表现形式:软件的粘滞性和环境的粘滞性。
不必要的复杂性(Needless Complexity)
指设计中包含有当前没有用的组成部分,它就含有不必要的复杂性。
不必要的重复性(Needless Repetition)
当同样的代码以稍微不同的形式一再出现时,就表示开发人员忽视了抽象。
晦涩性(Opacity)
指模块难以理解,代码可以用清晰、富有表现力的方式编写。
结论:
那么什么是敏捷设计呢?敏捷设计是一个过程,不是一个事件。它是一个持续的应用原则、模式以及实践来改进软件的结构和可读性的过程。它致力于保持系统设计在任何时间都尽可能得到简单、干净以及富有表现力。
第8章单一职责原则(SRP)
8.1 就一个类而言,应该仅有一个引起它变化的原因。
如果一个类承担的职责过多,就等于把这些职责耦合在了一起。一个职责的变化可能会消弱或抑制这个类完成其它职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。
8.1.1 什么是职责
在SRP中,我们把职责定义为“变化的原因”如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。
结论
SRP是所有原则中最简单的之一,也是最难正确运用的之一,我们会自然地把职责结合在一起,软件设计真正要做的许多内容。就是发现职责并把那些职责相互分离。
第9章开放-封闭原则(OCP)
9.1开放-封闭原则(OCP)
软件实体(类、模块、函数)应该是可以扩展的,但是不可以修改的。
9.2 描述
遵循开放-封闭原则设计出的模块具有两个主要的特征。它们是:
1 对于扩展是开放的(Open for extension)
2 对于更改是封闭的(Closed for modification)
9.3 关键是抽象
结论
在许多方面,OCP都是面向对象设计的核心所在,遵循这个原则可以带来面向对象技术所声称的巨大好处(也就是灵活性,可重用性及可维护性)。然而并不是说只要使用一种面向对象语言就是遵循了这个原则。对于应用程序中的每个部分都运单肆意地时行抽象同样不是一个好主意。正确的做法是,开发人员应该仅仅对程序中呈现频繁变化的那些部分做出抽象。
第10章 Liskov替换原则(LSP)
OCP背后的主要机制是抽象(abstraction)和多态(polymorphism)
10.1 Liskov替换原则(LSP)
对于LSP可以做如下解释:
子类型(subtype)必须能够替换掉它们的基类型(basetype)
若对每个类型S的对象O1,都存在一个类型T的对象O2,使得在所有针对T编写的程序P中,用O1替换O2后,程序P行为功能不变,则S是T的子类型。
10..3.2 有效性并非本质属性
在考虑一个特定设计是否恰当时,不能完全孤立地看这个解决方案。必须要根据该设计使用者所做出的合理假设来审视它。
10.3.3 IS-A是关于行为的
对象的行为方式才是软件真正所关注的问题。LSP清楚地指出,OOD中IS-A关系是就行为方式而言的,行为方式是可以进行合理假设的,是客户程序所依赖的。
10.6 启发式规则和习惯用法
有一些简单的启发规则可以提供一些有关违反LSP的提示。这些规则都和以某种方式从其基类中去除功能的派生类有关。完成的功能少于其基类的派生类通常是不能替换其基类的。因此违反了LSP。
10.6.2 从派生类中抛出异常
另一种LSP的违规形式是在派生类的方法中添加了其基类不会抛出的异常。如果基类的使用者不期望这些异常,那么把它们添加到派生类的方法中就会导致不可替换性,此时要遵循LSP,要么就必须改变使用者的期望,要以派生类就不应该抛出异常。
结论
OCP是OOD中很多说法中的核心。如果这个原则应用得有效,应用程序就会具有更多的可维护性、可重用性以及键壮性。LSP是使OCP成为可能的主要原则之一。正是子类型的可替换性才使得使用基类类型的模块在无需修改的情况下就可以扩展。这种可替换性必须是开发人员可以隐式依赖的东西。因此,如果没有显式地强制基类类型的契约,那么代码就必须良好地并且明显地表达出这一点。
术语”IS-A”的含意过于宽泛以至于不能作为子类型的定义。子类型的正确定义是”可替换性的”,这里的可替换性可以通过显式或者隐式的契约来定义。
第11章依赖倒置原则(DIP)
11.1 依赖倒置原则(DIP)
a. 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
b. 抽象不应该依赖于细节。细节应该依赖于抽象。
11.2 层次化
请注意这里的倒置不仅仅是依赖关系的倒置,它也是接口所有权的倒置。我们通常认为工具库应该拥有它们自己的接口。但是当应用了DIP时,我们发现往往是客户拥有接口,而它们的服务者则从这些抽象接口中派生。
11.2.1 倒置的接口所有权
11.2.2 依赖于抽象
一个稍微简单但仍然非常有效的对于DIP的解释,是这样一个简单的启发式规则:”依赖于抽象”。这是一个简单的陈述,该启发式规则建议不应该依赖于具体类—也就是说,程序中所有的依赖关系都应该终止于抽象类或者接口中。
根据这个启发式规则,可知:
任何变量都不应该持有一个指向具体类的引用或指针。
任何类都不应该从具体类派生
任何方法都不应该覆写它的任何基类中已经实现的方法。
结论
使用传统的过程化程序设计所创建出来的依赖关系结构,策略是依赖于细节的。这是糟糕的,因为这样会使策略受到细节改变的影响。面向对象的程序设计倒置了依赖关系结构,使得细节和策略都依赖于抽象,并且常常是客户拥有服务接口。
事实上,这种依赖关系的倒置正是好的面向对象设计的标志所在。使用何种语言来编写程序是无关紧要的。如果程序的依赖关系是倒置的,它就是面向对象的设计。如果程序的依赖关系不是倒置的,它就是过程化的设计。
依赖倒置原则是实现许多面向对象技术所宣称的好处的基本低层机制。它的正确应用对于创建可重用的框架来说是必须的。同时它对于构建在变化面前富有弹性的代码也是非常重要的。由于抽象和细节被彼此隔离,所以代码也非常容易维护。
第12章接口隔离原则(ISP)
这个原则用来处理”胖(fat)”接口所具有的缺点。如果类的接口不是内聚的(cohesive),就表示该类具有“胖“的接口。换句话说,类的”胖“接口可能分解多组方法。
12.2 分离客户就是分离接口
12.3 接口隔离原则上(ISP)
不应该强迫客户依赖于它们不用的方法。
结论
胖类(fat class)会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求该胖类进行一个改动时,会影响到所有其他的客户程序。因此,客户程序应该仅仅依赖于它们实际调用的方法。通过把胖类的接口分解为多个特定于客户程序的接口,可以实现这个目标。每个特定于客户程序的接口仅仅声明它的特定客户或者客户组调用的那些函数。接着,该胖类就可以继承所有特定于客户程序的接口,并实现它们。这就解除了客户程序和它们没有调用的方法间的依赖关系,并使客户程序之间互不依赖。