《敏捷软件开发》阅读笔记

------有凝聚力的团队将具有最强大的软件开发力量。

*******坦白地说,我觉得现在所处的团队还是缺乏畅快的沟通的,开发人员为各自负责的模块工作,只在需要确定数据库或消息接口时进行交流,当某个人的工作影响了接口或者数据并阻碍了他人的工作时,埋怨往往多于总结和交流(它充斥了我们的饭桌)。之前发的一些试图探讨一些开发过程的邮件总是石沉大海。这是为什么呢?

第1章 敏捷实践
--个体和交互胜过过程和工具
人是获得成功的最为重要的因素
合作、沟通和交互能力要比单独的编程能力更为重要
--可以工作的软件胜过面面俱到的文档
团队需要编制易于阅读的文档,来对系统及其设计决策的依据进行描述.
然而,编制众多的文档需要花费大量的时间,并且要使这些文档和代码保持同步,就要花费大量的时间.如果不能同步,它们就成为庞大、复杂的谎言
文档应该仅论述系统的最高层结构和概括的设计原理。短小(最多一二十页)、主题突出
在给新的团队成员传授知识方面,最好的两份文档是代码和团队。

*******文档是一个中间桥梁,代码总是庞大而复杂的,即使是测试代码,但如果文档写的过细,就会来不及与代码保持同步而失去可信度,最重要的是,缺少整体感,这在我们的设计文档中比较突出,文档的规模应该适中,比较好地衔接代码和人之间的交流
--客户合作胜过合同谈判
不能像定购日用品一样定购软件。你不能写下一份关于你想要的软件的描述,然后就让人在固定的时间内以固定的价格去开发它。所有以这种方式来对待软件项目的尝试都以失败告终。
成功的项目需要频繁的客户反馈。

*******可以实施吗,感觉我们的软件并不没有被用户真正用起来,更多的是偶尔给他们演示一下,他们愿意经常与我们沟通吗,毕竟我们的客户性质比较特殊
--响应变化胜过遵循计划
计划不能考虑得过远。首先,商务环境很可能会变化,这会引起需求的变动。一旦客户看到系统开始运作,他们很可能会改变需求。即使我们熟悉需求,并且确信他们不会发生变化,我们让然不能很好得估算出开发出它们需要的时间。
软件的做计划的策略是:为下两周做详细的计划,为下三个月做粗略的计划,对于一年以后将要做什么,有一个模糊的想法就行了
*******计划不定,长远来看,灵活性就比较大。但关键问题是,即使我们能说服团队领导甚至老板,我们能说服客户吗:计划不定,先做做看
第2章 极限编程概述
------流程
每两周交付一次可以工作的软件,进行如下的迭代
->获取对需求细节的理解,但是不去捕获那些细节

持续集成(渐进式地开发)

测试驱动的开发方法:我们不能在设计以后就假设它是对的,特别是在项目初期,我们对业务的了解还很匮乏的时候,返工的代价是巨大的,用测试者的眼光观察设计
->选取下一个开发周期要实现的用户素材(可能指需求,个人认为:这要求每个发布阶段新实现的用户素材在实现上没有耦合)
->设计测试用例/程序
->编码(如果有条件,可采用结对编程,当局者迷,庞观者清)
->集成测试(对新用户素材的测试加入到以前通过的测试中,重新测试全部用例,这要求测试快速、自动化)
->演示给用户获取反馈

------原则、技巧
可持续的开发速度:为了让团队始终保持旺盛的精力和敏锐的警觉,团队必须保持稳定、适中的速度,所以除非发布目标就在眼前,并且能一蹴而就,否则绝不允许团队加班。
开放的工作空间:三个臭皮匠,顶个诸葛亮
考虑能够工作的最简单的设计,除非确信需要某些更复杂的结构
随时重构,保持代码的干净、简单并且具有表现力

隐喻:通过合适的比喻给整个系统提供一个简单直观的概括,在拼图游戏时如果你事先看过整张图就能更快更准确将所有小卡片组装在一起,搭建系统也是这样

第3章 计划

估算每个用户素材的权值,在开发中通过经验评估每一点权值的开发成本,这便于我们更准确的发布计划
**********************************************************************************************************************
**********************************************************************************************************************
**********************************************************************************************************************
第4章 测试
------测试优先设计的优点
1 程序中的每项功能都有测试来轻易验证,不用担心更改导致忽略了对原来东西的破坏,这也促进了我们可以更加自由地对软件进行改进

2 比第一项更重要但不那么明显的影响:使我们从必须从程序调用者的有利视角来观察我们将要编写的程序。这样,我们就会在关注程序功能的同时,直接关注它的接口。通过首先编写测试,我们就能编写出更易调用的软件。

3 迫使我们把程序设计成易于调用和可测试的。而这又迫使我们解除软件内、软件与周边环境的耦合。
  对于那些依赖外部(需要外部输入或对外部输出)的程序或单元,我们可以测试--对其与外部输入输出的模拟,例如所提的对薪水支付应用,用XML文件模拟 UI对其发送的薪水支付命令,并让程序输出一个XML文件,模拟其发出的支付支票,测试在有了输入的XML文件后,它能否产生正确的输出XML文件。这同 时也促使了它与外部的相对独立。

4 测试可以作为一份无价的文档形式。它暗示了程序、类、函数的调用方法,它像一套范例,既告诉了开发者去编写什么样的程序,以通过测试,也告诉了其他人如何使用程序,这份文档是可编译、运行的。它保持最新,不会撒谎

文中通过编写测试游戏中从一个房间移动到另一个房间的代码,告诉读者:我想让大家特比注意的是测试在非常早的阶段就为我们阐明了一个重要的设计问题。首先编写测试用例的行为就是在各种设计决策中进行辨别的行为。
**********************************************************************************************************************
**********************************************************************************************************************
**********************************************************************************************************************

*******感觉还有一个最重要的:它确定软件对于用户是易于使用、经得起时间考验的

用mock object测试软件调用了正确的接口:需要这么劳师动众吗
第5章 重构
软件不仅要实现现有的功能,还应能易于修改、便于阅读,而这需要我们经常重构
反映的一些重构思想:
1 (变量、函数、条件表达式等的)的语义应该清晰、便于理解,这样程序才是便于维护和沟通的
2 如果临时变量是用来存储函数入参,别的函数的返回值,尽量消除它们,或者扩大它的作用范围,例如类的数据成员,否则它们的含义会在后面的代码中逐渐淡化,使人迷惑。比如对一个数组进行循环,即使多次用到它的长度,也没必要使用临时变量来存储。
3 什么时候应该写新函数:
    使相同但可能有微小差异的处理能够重用,重用的原则是抽象,不要关心太多细节
    对一段代码逻辑的提取,即使仅此一处的使用,因为提取出的函数名就是对其逻辑的最好解释,这使得程序的可读性增强。否则我们需要逐行的理解代码,这对调试、分析、重构、沟通都不是件好事

为什么要重构?
重构就好比用餐后对厨房的清理,第一次没清理,用餐是会快一点,但第二天吃饭的准备就要长一些,也许这会使你更加不愿去清洁,随着你总是跳过清理的工作,脏乱在一天地积累。最终,你不得不花费大量的时间去凿掉盘子上已经发硬的食物残余。饭是天天要吃的,忽略清洁工作并不能加快做饭速度。

重构是应该的,不过这个例子并不恰当,它的复杂度还没有大到要分离各段处理的地步
第6章 一个编程实践
画一幅图来研究一个想法是没有错的,但不应该画出后就假定它是对的。你会发现最好的设计是在你首先编写测试,一小步一小步前进时逐步形成的。

第7章
当变化来临时,抓住这次机会去改变设计,以便让软件对将来的变化具有弹性。但是,一开始时还是采用比较简单的设计比较好,因为这时候预测变化是件很头疼的难题。

敏捷开发人员这样做:

********************************************************************************************************************
1 遵循敏捷实践去发现问题
2 应用设计原则去诊断问题
3 用适当的设计模式去解决问题
软件开发的这三个方面的相互作用就是设计

姑且不去讨论是否一定要采用敏捷的过程,仔细观察文中每一个设计的地方,为什么采用模式A,不是模式B,为什么这里是继承、那里却是关联,其实都是那5个设计原则指导的结果

********************************************************************************************************************


第8~12章 (衡量)设计的原则

开放-封闭 更倾向于一个衡量设计灵活性的指标,解决它的方案并不局限于某一种模式,例如strategy,还可以用模板这种静态动态技术
------单一职责: 就一个类而言,应该仅有一个引起它变化的原因
        类的职责应该单一,否则,一个职责的变化会引起依赖它别的职责的外部类的连锁反应,而这是不应该的,也是代价惨重的。如果你能想到多于一个的动机去改变这个类,那么它就具有多个职责
        这个原则是最重要的,但也是最难正确应用的。
      
必要时也可以包含多个总是同时变化的职责。

寻找软件中沿不同方向变化的线

------开放-封闭原则(OCP): 对于行为的拓展是开放的,对于代码、二进制文件(如程序、DLL等)的更改是封闭的--这需要找出那些固定不变的东西,封闭它们。然后对变化开放

设计方法:封闭不变的部分,开放变化的部分
       正如抽象类的语义一样,因为它是抽象的,非特定的,当抽象类的派生类改变自己的实现时,依赖抽象类的别的类不需要改变自己的处理(代码),但自己的行为却改变了,这正好满足开放-封闭原则.
       常被列举的正方形和圆继承于抽象类shape的例子,隔离的只是shape类型的变化,但当需要变化图形画的顺序,例如画完正方形再画圆,这个抽象类就不适合了。所以设计无法封闭所有的变化。
       设计人员应该预测出最有可能发生的变化,然后对“变化的本质”进行抽象(给出一个数组,元素是shape继承类的类型名,抽象类可以根据元素的先后顺序来 确定画图形的顺序,这就是抽象出了图形绘画顺序的本质:一个类型名的顺序数组),这样才能隔离这些变化,但这一点不容易做到,需要经验。最终,我们会等到 变化发生时才采取行动。“愚弄我一次,应该 羞愧的是你。再次愚弄我,应该羞愧的是我”。
        下面的方法可以刺激变化的尽早发生:
        测试驱动开发
        短周期迭代开发
       在开发基础结构前开发特性,比如业务逻辑处理、界面
       首先开发最重要的特性
      尽早地、 经常性地发布软件,让客户尽早地接触软件。
------替换原则(LSP)

怎样的情况会使我们创建的类层次结构掉进不符合OCP的陷阱中呢?这些正是替换原则(LSP)要解答的问题
   子类型的对象替换它们的基类型的对象后,基类的使用者行为、功能不变,不过这通常要从基类的客户程序才能看得出(又一个支持测试驱动开发的例子)。文中举的例子很好的说明了正方形并不能成为矩形的子类型,因为是不可替换的。

从基于契约设计上来讲:在派生类中重新声明的函数,只能使用与基类相等或更弱的前置条件,只能使用与基类相等或更强的后置条件。
------依赖倒置原则:高层不应该于低层,二者应该依赖于抽象
易变化的部分应依赖于不易变的部分,相反的依赖不应该发生。
通常我们我们让细节依赖与抽象的类(例如抽象类或者类模板),但如果一个类不易变,例如string类,也可以被依赖。
------接口隔离原则
在一个类中混合了不同(不聚合)的接口的缺陷:不同的客户客户程序使用这个类,并发作用于它,当一个客户程序促使它变化时,别的客户程序也会受到牵连,造成了不必要的耦合。应该按使用的客户程序不同划分成不同类中的接口。

第13~17章 设计模式
------command
command对象其实就是事务,它接受一些用户录入的数据,这与实际的业务对象不同,首先数据没有验证,把验证和执行与实际对象分离开,便于进行一些事务上的控制:撤销、将所有操作推迟到某段时间执行。
除此之外,感觉是使设计复杂化,同把验证、存储数据的操作放到业务对象中相比,多了一层,显得更加复杂。
但是如果按后者设计,没有验证就产生了业务对象,验证失败,又需要释放当前对象,从逻辑上说不太合理。
------template method
通过继承重用
------strategy
通过组合与委托,使底层的实现细节独立于高层的算法,符合依赖倒置原则:高层和底层都依赖于抽象
------facade
为一组具有复杂且全面的接口的对象,提供一个简单且特定的接口
------mediator
例如对一个窗口中的多个控件中介,需要逐个创造这些控件的派生类,是一件比较麻烦的事情,有没有代替的办法?
------NULL OBJECT

退化的NULL OBJECT子类是否违反了LSP原则?导致客户期待出现的行为不能实现
原理:使用一个派生类的实例实现基类实例为空时的行为,这样就避免了在调用对象的时候判断它是否为空。
用途:需要频繁的判断实例是否为空时
注意:确保这个派生类的实例只有一个,方法是把派生类写在基类的定义中。

第18章 第一次迭代开始
------类的多态的原理
有一些相似但又不完全相同的对象,考虑如下两种设计方式:
每种对象一个类:这造成了重复,针对它们中相同部分的处理散步了每个类中,这增加了工作量,也降低了修改时的灵活性
全部对象一个类:那么针对这个类的一种处理,也必须考虑每个实例间实际不同的部分,很有可能造成了一个庞大且易出错的部分
最好的设计方式就是:把相同的部分放到基类中,把不同的部分放到派生类中,既去掉了重复,又让不同的处理互不干饶,这是个多么美好的方案。
所以,多态的目的就是:让相同与不同得到划分。

------分析过程的启示
不要急于考虑数据库或处理的数据,这是实现的细节

有些东西如果不能确定,还是推迟考虑比较好,也许来自系统其它部分的约束会促使我们做出某种选择

本章做出的设计都是通过思考用例得到的,更确切的说,是通过思考行为(注意,不是数据)得到的

为了有效地使用OCP,必须要找出隐藏于应用背后的抽象.但这需要对于用例的仔细分析.

------分析的结果
本章分析出如下抽象:
不同的雇员有不同的支付策略:小时计酬、月工资、按销售额提成,并且可以改变一个雇员的支付策略。
不同的雇员有不同的支付时间表:每周付、每两周付、每月付,它们与支付策略间的关联并不是确定的,所以应该单独提炼出支付时间表,而不是委托给支付策略。
所有的雇员都以每种方式收到他们的薪水。
一个雇员可以从属于0到多个协会,在协会那儿的消费会从他们的薪水中扣。

第19章 薪水支付:实现
使用AddEmployeeTransaction真是必要的吗,设计看起来更复杂了
如果不这样做,就需要在3种增加雇员的处理中包含相同的创建Employee的和不同的创建PaymentSchedule和PaymentClassification的处理,如果创建Employee较复杂,也许应该对增加雇员处理应用command模式
19.1.1的PayrollDatabase没有实际作用,应该负责employee与数据库数据的交互,而不是拷贝出另一个employee对象
薪水时间表还是应该放到薪水支付策略中,不同的薪水支付策略对应着不同的时间表,而且通常都是一一对应,相反如果分开,则可能导致员工的薪水支付策略更换了,而支付时间表却忘了修改的错误

从目前的需求来看,添加、删除雇员类的作用都是太大

GpayrollDatabase还是应该使用Singleton模式,毕竟通过Singleton访问全局变量可以方便统一地进行控制(例如判断非空),而直接访问则很容易被开发者遗忘

迭代式开发
也 许功能是可以逐步累加的,但代码、设计也可以吗?如果这次考虑只考虑一小部分功能,会不会导致下一次加入新的功能后,发现先前的设计过于简单以致已经远远 不能适应新需求,在重构之后先前的设计、编码有相当一部分变成了无用功,如果当初一起设计这两部分功能就不会这样了。这是因为实际上,很多功能的实现间都 存在交集。
那么还应不应该迭代开发,如果应该每次迭代加入的功能该控制到什么粒度呢?

为什么支付小时雇员的测试用例还要迭代的加入?

我的总结:
抽象地设计、
迭代的开发(逐阶段设计、实现功能)、
注重测试程序/用例对设计方案的反馈

作者总结:
在这个过程中,我们很少考虑是否正在进行分析、设计或者实现。相反,我们全神贯注于清楚和封闭的问题。在任何可能的地方,我们都尽力找出潜在的抽象。结果,我们得到了一个良好的薪水支付应用的初始设计,并且拥有了一组在整体上和问题领域密切相关的核心类。

附录A UML表示法I:CGI示例
面向对象的表示法中最流行的
Booch94:在设计上更强
OMT:在分析上更强
UML可以用于分析和设计。

需求分析的一个任务就是识别参与者(actor)和用例。但项目中未必是合适的首要任务。事实上,你开始时做什么比从哪里开始更重要。

第20章 包的设计原则

---包的内聚性
------共同封闭原则
包着所有类应该共同封闭某一类性质的变化,这样,当一个变化来临时,需要做出的更改就被限制了最小数量的包中。
------重用发布原则
当我们试图去重用一个软件中的某些部分时,肯定是不希望到几乎全部的包中重用单一类的(这会使我们受到的依赖性过强),所以包作为一个发布的整体,应该尽量满足某个重用的全部需要,也就是说包中的类应该是共同重用的。
------自顶向下设计
在项目开始时,对共同封闭还没有多少了解,也没有察觉到任何可以重用的元素,在此时设计包是很容易惨遭失败的。相反,随着项目的深入,类的数目越来越多,对他们的依赖性进行管理,保持变化的局部化,此时才需要把类划分成包。

---包之间的关系
------无环依赖原则
在包的依赖关系图中不允许出现环。否则,可能几周都无法创建出一个稳定的版本。
------稳定依赖原则
朝 着稳定的方向进行依赖。这是因为:一个包,被依赖的数目越多,他就越不容易变化,因为他需要对那些依赖他的包负责。稳定指接口稳定,行为还是可以易于变化 的,比如我的代码依赖于log4cpp,我不希望它的接口有任何的变化,但是我通过修改配置文件,可以很容易改变它的行为
-----包 的抽象程度应该和其稳定程度一致
一个稳定的包应该是抽象的,这样它 的稳定性不至于使其无法扩展。另一方面,一个不稳定的包应该是具体的,因为不稳定性使其代码易于修改

第21章 factory模式

只在这种情况下才应用工厂模式:确实能减少因为创建底层派生类而带来的依赖。而且,多数情况下,当这些底层派生类可以划分成多套,并且要么使用这一套,要么使用另外一套时,才适合使用。另外,本章最开始举的例子不恰当,不仅没有减少对底层派生类的依赖,还增加了复杂性


使 用抽象类与其派生类的通常情况下,由于要创建具体派生类的实例,所以在创建处仍然依赖于具体的派生类,如果这个创建处位于软件结构的较底层,那么派生类的 变化就会带来严重的连锁反应。factory把创建派生类实例的处理封装起来一个类,并把创建factory的实例则放到主函数中,就可以减弱这种影响 (具体参见p239)。同样,这种策略也适合对包的管理,把派生类和创建派生类的处理划分到factoryImplementation包中,也可以消除 非顶层包对具体类的依赖(p256 第22章)
作者建议,通常要对所有易变类实用工厂,但由于工厂带来的复杂性,应尽量避免使用他们,特别是在设计演化的初期。

第22章 组织薪水支付程序的包
------应用重用发布原则
从简单的包开始,在必要的时候再去增加包结构是最好的做法。在必要时,总是可以把包结构做得更精细
------度量
稳中提供的度量算法能较准确的计算出包结构划分的合理程度

第23章 composite模式

只有确认一组对象总是被以相同的方式对待时才可以使用,注意:不能为了重用而损失灵活性

当总是相同的对待一组对象,可以使用这个模式将这些对象再封装起来。如果不是相同对待,则最好不要采用这种模式,因为从用户的角度来看,composite对象给人的感觉就是在对一组对象进行相同处理。
但要注意,实现时应防止对象将自己组合起来,否则将会发生无穷递归

第24章 observer模式

设计演化?:作者演示的是在反复的设计与测试中,观察设计的缺陷,并最终走向成熟的方案。不过这对开发者素质的要求还是比较高的,特别是判别现在的设计,这应该需要长期实战的经验。觉得还是在设计方案的把握比较小时,可以试验,尽量还是能借鉴一些比较成熟的设计模式

------关于图
既然设计是逐渐演化的,使用白板或纸片这种随意的方式更恰当,没有哪种情形中使用画图工具会比一小片餐巾纸更快。只是为了像读者演示,作者还是给出了设计图,但应记住:代码是最准确体现设计的方式

第25章
abstract server模式:感觉就是strategy模式
adapter模式:用来衔接一些接口不一致的类,如果应用接口分离原则需要大量更改现有代码,代价太大,这个时候很适合用adapter模式

bridge模式:据说来来分离(不同的)接口和(不同的)实现,看起来很复杂

------不存在充分设计
根本不存在充分分析这种东西。无论花多少时间去找出完美的软件结构,客户总是会引入一个变化去破坏这个结构。
这种情况是无法避免的。不存在完美的结构。只存在哪些试图去平衡当前的代价和收益的结构。随着时间的过去,这些结构肯定会随着系统需求的改变而改变。管理这种变化的诀窍就是尽可能地保持系统简单、灵活。

------何时使用何种模式
模式是既能带来好处又具有代价的东西,应该使用那些最适合解决手边问题的模式。

第26章 proxy模式

------代理化购物车应用

实际上在这里,productData已经相当于一个productImp了,因为它只用来存储数据了。

另外:

1 将持久化的的代码放到DB中合适吗,为什么不是productProxy?

2对于创建和删除对象的持久化,仍然需要客户方程序直接调用访问来处理,这是否造成了业务逻辑与数据库访问的耦合,能否将创建和删除放入productProxy?但是是在该函数的什么接口中进行这个功能呢?
用来分离业务处理代码与数据库访问代码的耦合,并没有解决重复
DB只能封装数据库访问机制的变化,如果需求变化,则业务类product需要改变,数据库schema类productData也会受到牵连,相关的DB、productProxy也会改变,而且数据库访问机制改变的可能性要远远小于业务改变
不过它的好处是把业务类与数据库访问类分开了

p295:这也是另外一个代码是可以引导你偏离你所期望的模式和模型的例子。
有时候,编码时我们会发现一些更好的设计方案,尽管它没有被事先呈现到图纸上,计划常常赶不上变化

第27章

2.0与1.0如果实现相同的派生类,岂不是只有实现不同,这是低端、高端的区别?如果功能也有不同,如何通用?

java具有中等速度的垃圾回收机制?

为什么还要scheduler类,直接让界面作为传感器的observer不就行了?

从temperatureSensor中剥离出两部分功能:observable和listener,是一个比较好的想法,不过应用了bridge模式来使用不同的硬件API,让人感觉很困惑,为什么不能使用adapter模式,让temperatureSensor的不同派生类关联到不同的硬件API上。可能现有的微薄的开发经验,不足以理解这些东西,等开发一段时间以后再说。目前看到“8 api在哪里”

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值