敏捷之道(上)
<高效程序员的 45 个习惯> 读书笔记
态度决定一切
发生问题后,最高的优先级是找出罪魁祸首对吗?
肯定的答案是: 优先解决问题!
如果说出来的话只是让事态更复杂,或一味地抱怨,或者伤害他人的感情,无意中在给问题火上浇油。
为了解决或缓解这个问题,我们能够做些什么?
敏捷开发团队中每个人都应该抱着这样的观点,咱们可以从自己做起。例如一个开发者带着抱怨或问题来过来,咱们要了解具体的问题,询问他需要什么样的帮助。这样一个简单的行为表明目的是解决问题,而不是追究责任。
相反咱们找人帮忙,却没人积极响应,那么应该主动引导对话,解释清楚想要什么。
一个重大的错误应该被当做是一次学习,而不是指责他人的机会。
指责不能修复问题.
Vlame doesn’t fix bugs.
中庸之道
- 「这不是我的错」这句话不对,「这都是你/他的错」这句话更不对
- 如果没有犯过任何错误,说明可能没有努力去工作
- 如果一个团队成员误解了需求/API/会议决策,那么,意味着其他成员也有相同的误解,要确保整个团队尽快消除误解。
- 如果某个团队成员不是在帮助团队解决问题的方向前进,则应该要求他离开当前项目上的主要工作。
欲速则不达
拙劣的码农会不假思索的改完代码,快速转向下一个问题。
卓越的码农会挖掘更深一层,去理解这个函数的意义,思考修改函数的影响。
否则,在经年累月的屎山中添加新功能或修复 bug,难逃脱发的厄运。
千里之堤溃于蚁穴。快速修复的诱惑很容易令人把持不住,治标不治本啊!
防微杜渐.
Beware of land mines.
很可能在公司里,有人告诉你:“无论如何千万不能碰那个函数,写代码那哥们早就不在了,没有人能看懂他的代码”
建议:
不要孤立地编码.
团队成员应该花时间阅读其他同时的代码,确保代码是可读可理解的。
Don’t code in isolation.
单元测试.
单元测试很自然就会帮助你把代码分层,解耦成小的模块。
Use unit tests.
中庸之道
- 必须理解代码是如何工作的,但不一定要成为专家,能够使用它进行有效工作即可。
- 如果有一块代码其他人很难看懂,意味着任何人包括原作者都很难维护它
- 所有的大型系统都非常复杂,没有一个人能够完全明白所有代码。咱们除了深入开发的部分代码之外,还需要从更高层面了解系统架构,各个功能模块是如何交互的。
- 不要急于修复一段没能理解的代码。
对事不对人
“那样很蠢!” 这句话想表达设计上的问题,但无意中却重伤了他人,有明示设计者很蠢之意。
更有效优雅的方式应该是: 「感谢,我想知道,如果两个用户同时登录会发生什么情况?」
通常对一个明显错误有哪些反应?
- 否定个人能力
- 指出缺点,否则其观点
- 问询并提出顾虑
第三种方法,没有谴责,没有批判,由此由浅入深的交谈,而非面红耳赤的争辩。
尝试引导性的提出问题。
我们每个人都有一些极好的创新想法,同样也会萌生一些很愚蠢的想法。好的作品和设计都需要大量的创造力和洞察力,分享并融合各种不同的想法和观点,远胜于单个想法为项目带来的价值。
重点要放在解决问题上,而不是极力证明谁的主意更好。团队中一个人的智商高是没有用的,如何很顽固并且拒绝合作,那就更糟糕。
每个人都都会有好的想法,也会有不对的想法,每个人都需要自由表达观点。你不需要很出色才能起步,但是你必须起步才能变得很出色。
有关于团队决策的有效方法论:
-
设定最终期限,以防止陷入无休止的理论争辩。
-
逆向思维,先积极看到它的正面,然后努力从反面认识它。目的是找出优点最多缺点最少的方案。
-
设立仲裁人,选一个仲裁人作为本次会议的决策者。仲裁人应该专注于调停,而不是发表自己的观点,理想情况下不应在项目中有既得利益。
-
支持已经做出的决定,一旦方案被确定,团队成员必须通力合作。目标是让项目成功满足用户需求,客户并不关心这是谁的主意,只关心产品是否符合他们的期望。
设计充满了妥协,工作者不感情用事是需要克制力的,若咱们能展现出成熟大度来,大家一定不会视而不见,这需要有人带头,身体力行,去感染另一部分人。
中庸之道
-
尽力贡献自己的好想法,没有被采纳也无需过多情绪,不要因为表现自己的想法而画蛇添足。
-
脱离实际的反方观点会使争论变味,你很容易提出一堆不太可能发生的情形去批驳它,这时需要扪心自问: 类型问题以前发生过吗? 是否经常发生呢?
「我们不能采用这个方案,万一数据库厂商倒闭.」「甲方绝不对接收这个方案」,不能凭空想象。
排除万能,奋勇前进
谁去给猫系铃铛?
老鼠们打算在猫的脖子上系一个铃销,这样猫巡逻靠近的时候,就能预先得到警报。每只老鼠都点头,认为这是一个绝妙的想法。这时一只年老的老鼠问道:“那么,谁愿意挺身而出去系铃铛呢?”毫无疑问,没有一只老鼠站出来。当然,计划也就这样泡汤了。
有时,绝妙的计划会因为勇气不足而最终失败。尽管前方很危险—不管是真的鱼雷或者只是一个比喻——你必须有勇气向前冲锋,做你认为对的事情。
假如要修复其他人编写的代码,很脏乱,很难理解,这时怎么办?
也许会跳起来告诉周围人,代码是多么糟糕,但那只是抱怨和发泄,并不能解决问题。
相反,咱们应该重写这些代码,并比较重写前后的优缺点,动手证明是最有效的方式。
中庸之道
- 设计或代码中出现了奇怪的问题,花时间去理解为什么,如果清晰明朗需要重构代码就去做。但如果没有马上理解那段代码,不要轻易地否定和重写它们,那不是勇气是鲁莽。
- 「更清晰的代码」是无法打动生意人的,节约资金,获得更好的投资回报等会让论点更有说服力。
学无止境
逆水行舟,不进则退。
许多新技术都基于现有的技术和思想,它们会加入一些新的东西,这些新东西是逐步加的量,如果跟踪技术变化,那么学习这些新东西就是增量变化。
敏捷的根本之一是拥抱变化,曾经非常有用的东西往往会靠边站,还会降低效率。
在学习新的东西,要丢弃阻止你前进的旧习惯。
如何才能跟上技术变化的步伐呢?
- 迭代和增量式的学习,每天计划用一点时间来学习新技术。听到不熟悉的术语或短语,记录下来,在计划时间深入研究。
- 通过邮件/技术博客/社区了解行情
- 阅读
中庸之道
- 你不可能精通每一项技术,没有必要去做这样的尝试,只要在某些方面成为专家,就能用同样的方法,很容易成为新领域的专家。
- 你要明白为什么需要这项新技术,它试图解决什么样的问题?
- 避免一时冲动的情况下,只是因为想学习而将应用切换到新的技术。在决策时,必须评估新技术的优势,开发一个小的原型系统是对付技术狂热者的一剂良药。
打破砂锅问到底
在计算机世界,很多问题会影响应用,为了解决问题,需要知道许多影响的因素。当找人询问任何相关问题时,让他们耐心回答,这是你的职责。
你的问题甚至会帮助他们理清思路,从一个新人的角度提出的问题,给他们提供了一个新的视角,也许就帮助他们解决了一直令人困扰的问题。
「哎呀,只要每周重启一次系统,就没问题了」
「你必须依次执行 3 次构建才能完成构建」
「我们的用户根本不想要那个功能」
真的吗? 为什么呀? 不能只满足于别人告诉你的表面现象,要不停地提问直到你明白问题的根源。
中庸之道
- 咱们可能跑题,问一些与主题无关的问题,要问到点子上。
- 当问为什么的时候,也许会被反问,「为什么你问这个问题」,在提问之前,想好提问的理由,也许有助于问出恰当的问题。
- 「这个,我不知道」是一个好的起点。
把握开发节奏
在许多不成功的项目中,基本都是随意安排工作计划,那样随机安排很难处理,根本不知道明天将会发生什么。
中庸之道
- 每天结束的时候,测试代码,提交代码,没有残留的代码
- 不要搞得经常加班
- 如果开发节奏过于密集,会精疲力竭
- 有规律的开发节奏会暴露很多问题
交付用户想要的软件
让客户做决定。
开发者能做的一个最重要的决定就是,判断哪些是自己决定不了的。
应该让业主做决定,你不需要自己给业务上的关键问题做决定。毕竟,那不是你的事情。
中庸之道
- 记录客户做出的决定,并注明原因,好记性不如烂笔头
- 不要用低级别和没有价值的问题打扰繁忙的业务人员
- 不要假设低级别的问题不会影响他们的业务
- 如果业务负责人回答我不知道,尽你所能提供建议
保持可以发布
在团队工作,修改一些东西的时候必须很谨慎,时刻警惕,每次改动都会影响系统的状态和整个团队的工作效率。
在办公室不能容忍任何人乱丢垃圾,为什么就可以容忍一些人给项目带来垃圾代码呢?
下面是一个简单的工作流程,可以防止你提交破坏系统的代码。
- 在本地运行测试
- 检出最新的代码 git pull
- 提交代码
提早集成,频繁集成
绝不要做大爆炸式的集成。越早解决它们,工作量就越小。如果推迟集成的时间,解决这些问题就会变得很难,需要大量和大范围地修改代码。
固定的价格就是保证要背叛承诺
软件项目天生就是变化无常的,如果要提前给出一个固定的开发时间,就几乎肯定不能遵守开发上的承诺。
那如何做到更精确的评估呢? 可以试试这样的方法:
- 主动提议先构建系统最初,有用的部分,这时候不是要完成所有的功能,而是足够一次交付,并能让用户真正使用,通常在 6~8 周。
- 第一次迭代结束,客户有两个选择,可以新增功能继续下一个交付,要么取消合同,仅需支付第一个迭代的费用。
- 如何继续前进,这时根据提出的增量功能,应该就可以很好预测下一个迭代工作。
敏捷反馈
编写能产生反馈的代码,通过单元测试来获取反馈。
- 确保测试是可重复的,使用当前时间/机器固定 IP 等都是使其依赖运行时的机器
- 测试边界条件,比如日期 11:59:59/0:00:00,比如月份 0 / 13 / -1
- 不要放过任何失败的测试
一旦单元测试到位,采用这样的回归测试,就可以随意重构代码。单元测试会确保你不会意外地破坏任何功能。
如果你仍然在寻找开始单元测试的理由,下面有很多:
- 单元测试能及时提供反馈
- 单元测试让你的代码更佳健壮
- 单元测试是有用的设计工具
- 单元测试是让你自信的后台
- 单元测试是解决问题时的探测器
- 单元测试是可信的文档
- 单元测试是学习工具
先用它再实现它
在说服他人使用它之前,先得让自己切实的使用产品。
先写测试,你就会站在代码用户的角度来思考,而不仅仅是单纯的实现。
你会发现因为你自己要使用它们,所以能设计一个更有用,更一致的接口。
先写测试有助于消除复杂的设计,让你可以专注于真正需要完成的工作。
举一个编程例子,这是一个可以两个人玩的「井字旗」 游戏:
开始,会思考如何为这个游戏做代码设计,也许会考虑到需要这些类,棋盘/行列/用户/规则/等。
首先写棋盘的测试,类似于这样
|
|
此时执行测试应该是失败的,因为结构体 board 还不存在,需要实现这个结构体。
|
|
此时再执行测试,以保证测试通过,不通过话的修改直到通过。
下一步,必须决定谁先开始走第一步棋,需要设第一个比赛者,写下相关测试
|
|
这时,测试会迫使你决定如何在代码中表示比赛者,并分配到棋盘上。
可能是这样的
|
|
但真的需要 player 这个结构体吗? 或者需要玩家的名字吗? 此时还没有实现 SetFirstPlayer 方法,初期让我们的测试更简单一些,比如
|
|
这个版本隐藏着风险,可以传递任何字母给 SetFirstPlayer,意味着需要添加代码来检查参数的正确性。因此我们可以进一步简化,用一个 bool 来表示第一个玩家是 O 还是 x。
例如这样
|
|
我们的思考是从 player 类开始,最后实现只用了简单的布尔类型属性,我们不是要扔掉好的设计,仅仅用大量的布尔类型来编码所有东西,而是成功地实现特定功能的最低成本。程序员很容易走向另一个极端,一些不必要过于复杂的事情。
消除还没有编写好的类,这会很容易简化代码,相反,一旦已经编写了代码,也许会强迫自己保留这些代码,并继续使用它。
好的设计并不意味着需要更多的类
添加无用的代码总是不好的想法,不管他们是否真的需要,TDD 有机会让你编写代码之前,可以深思熟虑将如何使用它。这会迫使你去思考它的可用性和便利性,让你的设计更注重实效。
设计并不是开始编码的时候就结束了,需要在产品的生命周期中持续添加测试,添加代码,重新设计。
中庸之道
- 不要把测试代码和提交代码之前的测试等同,单元测试可以保证代码的健壮性,提交代码前的功能测试可以保证产品的稳定性。
- 任何一个设计都可以被改进
- 单纯的单元测试无法保证好的设计,但他们回到设计有所帮助,使设计更佳简单。
不同环境,就有不同问题
你的应用程序要在不同的操作系统上运行,或者一个操作系统的不同版本,就需要测试所有的操作系统。
不同环境就有不同的问题,使用持续集成工具,在每一种支持的平台和环境中运行单元测试。要积极地寻找问题,而不是等问题来找你。