利用一周零散的时间,阅读了《修改软件的艺术》点击这里去购买这本书,在阅读的过程中,结合以前的项目开发过程,深有体会和感触。软件开发不是一蹴而就的,必须遵循一些准则,在编写的过程中,也要学会思考。
全书首先是对当前的开发模式进行了介绍,并阐述了其中的弊端和缺陷。传统的瀑布式开发,让软件在后期进行修改现有功能和添加新功能的时候要付出的更多,导致更多的软件是开发失败的。一个软件成功交付也不能表示这个软件是成功的,除非交付之后没有人使用,不需要修改。在瀑布式开发中,开发者可能在几个月后才能看到它执行的结果,开发过程建立的一个测试模拟环境,已经脱离了软件上下文。从而引出,我们就需要寻找一种新的思路和开发流程——敏捷开发。
可能是第一次接触敏捷开发的思想,所以在阅读中,一直想找到对这个词的定义,想要能明白,敏捷开发的模式是如何的。但是书中并没有明确定义,所以只能通过整体的介绍和书中提出的9条构建易维护代码的最佳实践中总结了。
在如何做之前先问做什么、为什么做、给谁做
有时候开发者会把事情搞错,不是代码出错,而是 代码应该做的事情 没搞清楚,这时就需要产品负责人回答所有的问题,并且把控正确的方向,即使很多时候都没有绝对正确的答案。当产品负责人无法相应的时候,开发过程就会变得缓慢,开发者必须对他们所做的事情作出结果未知的揣测。
这里说明了一名产品负责人的重要性,产品负责人是产品的权威人士,是沟通的中枢。 而开发者是要善于提问的,必须去思考,事无巨细的提出问题。但是也不需要拿到全面需求、知道做什么之前就开始编写软件,一边开发一边沟通,这样也是很高效的,而且会明显提高产品质量。
需求文档并非被用户故事取代,而是被产品负责人和开发者之间的交互取代,被产品负责人和客户之间的交互取代。用户故事不能取代需求文档。(用户故事)我们不需要那么多细节,用户故事保证我们不仅仅在计划阶段,而且在整个开发阶段都保持对需求的关注。
给定特定输入,应该得到特定的输出。无论你是否使用自动化测试,把验收标准和边界情况写在用户故事卡片上都是好的办法,可以用来提醒你需要处理哪些异常。
本章阅读笔记:
从接触软件开发到现在,完整的带了两个项目,但是我都不敢说这两个项目任何一个是成功的,虽然一个已经在天津市推广运行,另外一个每天几乎为1000名教职工提供服务。回顾整个开发过程,越发能体现出瀑布式开发的缺陷。在此之前,觉得项目时失败的,追究其原因,大概是因为当时开发技术的低级和软件思维正在形成中,不能很好的从全局去思考项目,但是一直认为这种开发模式时正确的:先是前期的需求分析,然后就是设计,再然后就是实现、集成,跳过了测试,最后就是部署和上线。在上线之后,才发现,各种Bug,然后就苦逼的修改,正在吃饭的时候,一个电话过来,就要放下刚吃两口的饭,跑到实验室,打开电脑“救火”;在添加新功能的时候,我都不愿意去翻以前的代码了,总是那么不堪入目。这两个项目,显然是失败的,现在不敢修改,只能是像一个破旧的衣裳,缝缝补补。
虽然项目的开发是不理想的,但是,我还是从中学到了很多。阅读本章的时候,特别是对产品负责人的职责描述时,深有体会,在两个项目中,我都可以算是项目的负责人,负责把控整个项目的进度和方向,明显会感觉到产品负责人的重要型。在我自己迷茫的时候,整个项目时停滞不前的,项目组成员的开发效率也是低下的;当我明确方向时,整个项目组都明确方向,困难一个一个被攻破,推进迅速。当然,这个其中,少不了项目组成员(开发者)的工作和思考。
在反思原有开发模式基础上,通过本章学习到了:
- 整个项目组需要明确做什么、为什么做、给谁做的思想
- 尝试用 用户故事 替代文档需求。缩短需求分析的时间,多创建用户故事模型。
- 在开始做之前,要定制验收标准和边界情况,最好能做到自动化测试。
注:当前没有发展成产品负责人的想法,只是在自己的项目中,可以通过实践,验证学得东西。
小批次构建
小批次构建让每个任务都可以有在段时间(理想情况是四个小时)内完成,保证任务都满足验收标准,或者至少产生可观察的结果。这有助于让任务更容易预估、完成和验收。
我们应该用什么来衡量自己?
我们应该用对客户的价值来衡量自己。
如果一个用户故事是复杂型的,那么通常只有一个原因:其中有很多未知因素,我们处理复杂型用户故事的方式是“分离已知和未知”。我们不断地在未知的范围内进行迭代,未知的范围越来越小,直至消失。把未知和已知分离时分割包含未知因素的故事的第一步。一旦某些事无被标记为未知,就将其封装。
探索未知事物的时候需要做的两件事:
- 把未知变为已知。
- 把未知进行封装。如果能把大的未知问题隐藏起来,那就隐藏起来,以后再处理(这里有封装的意思)。
更短的反馈回路:
反馈的回路可能有多个。与客户交流、迭代或者功能开发后期进行演示、编译器反馈、自动测试、回顾会议、代码审查等。
优秀的软件开发实践需要在构建不同组成部分的同时保证软件的完整性,而且让各个部分尽可能独立。试着通过定义良好的接口来移除组建间的依赖。如果必须有某些依赖,让后来的故事依赖于之前的故事。一个故事应该时完成一个单一目的,或者一个目的可检验的某个方向(单一职责)。
本章学习到了:
- 封装未知,将大问题分割成小问题。(虽然一直也是这样做的)
- 故事的单一职责。
- 小批次构建是有必要的。
持续集成
持续集成时一种在构建时期而非发布前进行集成的实践,降低软件开发中的风险,同时做为一种反馈机制,对开发者有这重要的意义。
定义完成:
- 完成。写完一个功能,可以在开发环境中执行,在他的机器上可以得到一定结果,
- 完整完成。在开发机器中正常执行,且经过集成。
- 完美完成。在开发机器上正常执行,经过集成,清晰且健壮。
构建软件时间,将新的功能签入版本库中,所有开发者都在同一分支上工作,且版本库中除源码之外,还有管理构建的其他文件,包括配置文件、数据库模型、测试代码和测试脚本、第三方库、安装脚本、文档、设计图例、用例、UML图例等。在测试系统中,完整复制生产环境。
编写出优秀的单元测试,并且仅仅测试那些需要测试的代码,而不测试那些可能用到的代码。
应该保持随时集成。持续的进行集成,尽快从头到尾完成一个用户故事。
构建敏捷设施的7个策略:
- 用版本库管理一切
- 一次点击全部构建
- 持续集成
- 为任务定义验收标准
- 编写可测试的代码
- 保证必要的测试覆盖率
- 即时修复失败的构建
协作
我们拥有最宝贵的资源就是彼此
结对编程并非轮流使用计算机,而是让两个头脑解决同一个问题,比各自单独工作更迅速而且质量更好。结对编程可以防止团队成员过于专门化,并且帮助团队达成共识。
结对编程的配对方式:
- 根据开发者的强项和缺点来配对
- 让最有经验的开发者和最少的结对
- 随机配对
一个想法在由你的脑海输入到计算机之前,必须要经过其他人的手
伙伴编程:
在每一天最后的一个小时里,你和你的伙伴对一天的开发进行审查。是结对编程的前身,安全尝试工作。
代码审查:
每当一个开发者完成了某一功能,他就可以向其他成员讲解他是如何编写的。 设计和代码的审查应该首先指出设计思路并说明为何选择这种设计。
回顾会议:
团队聚在一起,每个人都有机会发言,意思都差不多,我们做了什么,是如何做的,为什么做,下一次怎么做的更好等。
重要的一点:
并不是所有人都适合结对,也不是所有的任务都适合结对。
除了结对编程之外,还有穿刺、群战、围攻、伙伴编程。通过加强学习、分享知识,可以改进我们的团队乃至整个产业。
本章学习到了:
对结对编程还是十分看好的,觉得这是能在很大程度上减少bug的方式。我有且只有一次这样的开发过程,那次连续一下午的代码编写中有种一气和成的爽快感觉。在开发“班车预约系统”的时候,那是一个核心模块,十分重要,当思路理清之后,老师就坐在我旁边,我边写着边讲这为什么那么做。虽然代码的整洁度不够,但是实现了基本的功能。整个编写的过程中,竟然没有出现几个bug,每当bug快要出现的时候,老师总是追问,为什么这样做,不这样做。这样一思考,就避免了bug的诞生。当时没有觉得这种开发方式有什么独特,当阅读完本章之后,觉得十分可以在团队中实施。
编写整洁的代码
对象应该具有定义良好的属性、专一的职责、隐藏的实现,它应该控制自己的状态,而且只应该被定义一次。
- 高质量的代码应该内聚:
如果内聚的类只代表一种事物,那么这个事物应该是可以被命名,但是不是它们的名字而是它们的行为决定了它们代表什么。
- 高质量的代码是松散耦合的
- 高质量的代码是封装良好的:
它隐藏了实现细节,由外而内编程,根据所做的事情命名服务隐藏它是物和封装的。在软件中,你不知道的事情对你无害。用什么来隐藏怎么。
高质量的代码时自主的:
它管理自己的职责。如果发现某个方法过分依赖另外一个类的数据,那就应该将这个方法置于这个类中。
高质量的代码时无冗余的:
代码中的冗余是试图在不同的地方做相同的事情,无论做的是什么,冗余不仅仅是形式的重复,而且还是对意图的重复。
不要让完美成为优秀的敌人,在软件中无法实现完美的。
代码的集体所有权以为着团队中,每个人都可以维护任意部分的代码,即使并不是他们编写的。团队应该确定共同的代码规范、统一领域模型和开发实践、使用同样的词汇来描述设计。
重构是编写代码的关键步骤,而且贯穿整个开发过程。在编写代码和新功能完成时进行重构。
代码审查:学习他人风格,不断学习软件开发。阅读他人的代码,编写代码,不断练习。
测试先行
单元测试并不是测试整个用户故事,而是测试相对来说更小的单元。单元测试也可以当作内部文档来使用。这里的单元是指一个行为单元:一个对立的、可验证的行为。它必须对系统产生可观察的影响。而不和系统的其他行为耦合。一个单元代表一个行为,如果行为不变,测试也不应该变化。
测试先行开发
开发者先针对一个功能编写测试,然后实现那个功能让测试通过。你只需要编写测试覆盖的代码,这样测试覆盖率永远时百分之百。测试也是代码,是系统中的一部分。
在敏捷软件开发中,并非在一开始就得到全部的需求,而是一边设计一边整理需求,虽然这样很容易作出一些错误的选择,但是迭代构建也比事前确定所有需求要高效的很多。
如果使用得当,测试先行开发可以帮助开发者编写容易测试和容易维护的代码,但是如果使用不恰当,TDD会成为累赘而非资源。
通过本章学习:
- 测试先行的重要行
- 就目前所说的这些理论,必须在开发中去尝试,在实践中体会真理。
用测试描述行为
最开始编写测试的时候还没有可以测试的代码。接下来开始编写能让测试通过所需的最简单的实现代码。下一步清理代码提升质量。周期循环。
所有失败的测试都是因为某些已知原因而失败的,而不会因为其他原因失败,而那个测试是系统中唯一可以由此原因导致失败的测试。换句话说 测试应该是独一无二的。
如果代码有多重执行路径,意味着代码的逻辑也会更复杂,所以应该用测试来覆盖代码。每个代码执行路径得到不同的结果,所以需要针对每一个路径进行单元测试。
但是单元测试不能测试一系列正确顺序的调用或其他的类似场景,这里引入工作流测试,工作流所测试用所谓的模拟对象进行测试。模拟对象是真实对象的替代。
在查找Bug的时候,我会尝试如构建那些可以得到bug在哪里(或者不会在哪里)的信息场景,然后逐步缩小代码范围,直到找到问题。(这是积累出来的经验)
最后实现设计
持续性开发:
- 删除死代码
死代码是指那些被注释掉或者不再被调用而永远得不到执行的代码,除了干扰其他开发者之外,它完全没有意义。删了它。
- 保持名称更新
- 集中决策
- 抽象
将编码和清理分开,当作不同的任务。只关注实现行为,让测试通过,这样编码更容易。将对象的创建和对象的使用分离。
用“见名知意”的命名取代注释描述代码,应该用注释来描述为什么要做这些事,而不是做什么。代码本身应该说明自己在做什么。
重构遗留代码
重构是指在不改变外部行为的前提下对代码的内部结构进行重组或重新包装。
修改代码的风险和成本都很高,所以要谨慎行事。对已有代码添加测试。
- 以支持修改为目的重构
- 以开闭原则为目的重构
- 以提高可修改性为目的重构
不用试图在一开始就找到最佳设计,我们可以随着重构来不断改进设计。
何时进行重构:
- 当关键代码维护不善的时候
- 当唯一理解代码的人没空的时候
- 当有信息可以揭示更好的设计的时候
- 当修改bug的时候
- 当需要添加新功能的时候
- 当需要为遗留代码写文档的时候
- 当从够比重写容易的时候
从遗留代码中学习