敏捷之道 (上)

对事不对人。防微杜渐。欲速则不达。敏捷反馈。测试驱动开发。

敏捷之道(上)

<高效程序员的 45 个习惯> 读书笔记

态度决定一切

发生问题后,最高的优先级是找出罪魁祸首对吗?

肯定的答案是: 优先解决问题!

如果说出来的话只是让事态更复杂,或一味地抱怨,或者伤害他人的感情,无意中在给问题火上浇油。

为了解决或缓解这个问题,我们能够做些什么?

敏捷开发团队中每个人都应该抱着这样的观点,咱们可以从自己做起。例如一个开发者带着抱怨或问题来过来,咱们要了解具体的问题,询问他需要什么样的帮助。这样一个简单的行为表明目的是解决问题,而不是追究责任。

相反咱们找人帮忙,却没人积极响应,那么应该主动引导对话,解释清楚想要什么。

一个重大的错误应该被当做是一次学习,而不是指责他人的机会。

指责不能修复问题.

Vlame doesn’t fix bugs.

中庸之道

  • 「这不是我的错」这句话不对,「这都是你/他的错」这句话更不对
  • 如果没有犯过任何错误,说明可能没有努力去工作
  • 如果一个团队成员误解了需求/API/会议决策,那么,意味着其他成员也有相同的误解,要确保整个团队尽快消除误解。
  • 如果某个团队成员不是在帮助团队解决问题的方向前进,则应该要求他离开当前项目上的主要工作。

欲速则不达

拙劣的码农会不假思索的改完代码,快速转向下一个问题。

卓越的码农会挖掘更深一层,去理解这个函数的意义,思考修改函数的影响。

否则,在经年累月的屎山中添加新功能或修复 bug,难逃脱发的厄运。

千里之堤溃于蚁穴。快速修复的诱惑很容易令人把持不住,治标不治本啊!

防微杜渐.

Beware of land mines.

很可能在公司里,有人告诉你:“无论如何千万不能碰那个函数,写代码那哥们早就不在了,没有人能看懂他的代码”

建议:

不要孤立地编码.

团队成员应该花时间阅读其他同时的代码,确保代码是可读可理解的。

Don’t code in isolation.

单元测试.

单元测试很自然就会帮助你把代码分层,解耦成小的模块。

Use unit tests.

中庸之道

  • 必须理解代码是如何工作的,但不一定要成为专家,能够使用它进行有效工作即可。
  • 如果有一块代码其他人很难看懂,意味着任何人包括原作者都很难维护它
  • 所有的大型系统都非常复杂,没有一个人能够完全明白所有代码。咱们除了深入开发的部分代码之外,还需要从更高层面了解系统架构,各个功能模块是如何交互的。
  • 不要急于修复一段没能理解的代码。

对事不对人

“那样很蠢!” 这句话想表达设计上的问题,但无意中却重伤了他人,有明示设计者很蠢之意。

更有效优雅的方式应该是: 「感谢,我想知道,如果两个用户同时登录会发生什么情况?」

通常对一个明显错误有哪些反应?

  • 否定个人能力
  • 指出缺点,否则其观点
  • 问询并提出顾虑

第三种方法,没有谴责,没有批判,由此由浅入深的交谈,而非面红耳赤的争辩。

尝试引导性的提出问题。

我们每个人都有一些极好的创新想法,同样也会萌生一些很愚蠢的想法。好的作品和设计都需要大量的创造力和洞察力,分享并融合各种不同的想法和观点,远胜于单个想法为项目带来的价值。

重点要放在解决问题上,而不是极力证明谁的主意更好。团队中一个人的智商高是没有用的,如何很顽固并且拒绝合作,那就更糟糕。

每个人都都会有好的想法,也会有不对的想法,每个人都需要自由表达观点。你不需要很出色才能起步,但是你必须起步才能变得很出色。

有关于团队决策的有效方法论:

  • 设定最终期限,以防止陷入无休止的理论争辩。

  • 逆向思维,先积极看到它的正面,然后努力从反面认识它。目的是找出优点最多缺点最少的方案。

  • 设立仲裁人,选一个仲裁人作为本次会议的决策者。仲裁人应该专注于调停,而不是发表自己的观点,理想情况下不应在项目中有既得利益。

  • 支持已经做出的决定,一旦方案被确定,团队成员必须通力合作。目标是让项目成功满足用户需求,客户并不关心这是谁的主意,只关心产品是否符合他们的期望。

    设计充满了妥协,工作者不感情用事是需要克制力的,若咱们能展现出成熟大度来,大家一定不会视而不见,这需要有人带头,身体力行,去感染另一部分人。

中庸之道

  • 尽力贡献自己的好想法,没有被采纳也无需过多情绪,不要因为表现自己的想法而画蛇添足。

  • 脱离实际的反方观点会使争论变味,你很容易提出一堆不太可能发生的情形去批驳它,这时需要扪心自问: 类型问题以前发生过吗? 是否经常发生呢?

    「我们不能采用这个方案,万一数据库厂商倒闭.」「甲方绝不对接收这个方案」,不能凭空想象。

排除万能,奋勇前进

谁去给猫系铃铛?

老鼠们打算在猫的脖子上系一个铃销,这样猫巡逻靠近的时候,就能预先得到警报。每只老鼠都点头,认为这是一个绝妙的想法。这时一只年老的老鼠问道:“那么,谁愿意挺身而出去系铃铛呢?”毫无疑问,没有一只老鼠站出来。当然,计划也就这样泡汤了。

有时,绝妙的计划会因为勇气不足而最终失败。尽管前方很危险—不管是真的鱼雷或者只是一个比喻——你必须有勇气向前冲锋,做你认为对的事情。

假如要修复其他人编写的代码,很脏乱,很难理解,这时怎么办?

也许会跳起来告诉周围人,代码是多么糟糕,但那只是抱怨和发泄,并不能解决问题。

相反,咱们应该重写这些代码,并比较重写前后的优缺点,动手证明是最有效的方式。

中庸之道

  • 设计或代码中出现了奇怪的问题,花时间去理解为什么,如果清晰明朗需要重构代码就去做。但如果没有马上理解那段代码,不要轻易地否定和重写它们,那不是勇气是鲁莽。
  • 「更清晰的代码」是无法打动生意人的,节约资金,获得更好的投资回报等会让论点更有说服力。

学无止境

逆水行舟,不进则退。

许多新技术都基于现有的技术和思想,它们会加入一些新的东西,这些新东西是逐步加的量,如果跟踪技术变化,那么学习这些新东西就是增量变化。

敏捷的根本之一是拥抱变化,曾经非常有用的东西往往会靠边站,还会降低效率。

在学习新的东西,要丢弃阻止你前进的旧习惯。

如何才能跟上技术变化的步伐呢?

  • 迭代和增量式的学习,每天计划用一点时间来学习新技术。听到不熟悉的术语或短语,记录下来,在计划时间深入研究。
  • 通过邮件/技术博客/社区了解行情
  • 阅读

中庸之道

  • 你不可能精通每一项技术,没有必要去做这样的尝试,只要在某些方面成为专家,就能用同样的方法,很容易成为新领域的专家。
  • 你要明白为什么需要这项新技术,它试图解决什么样的问题?
  • 避免一时冲动的情况下,只是因为想学习而将应用切换到新的技术。在决策时,必须评估新技术的优势,开发一个小的原型系统是对付技术狂热者的一剂良药。

打破砂锅问到底

在计算机世界,很多问题会影响应用,为了解决问题,需要知道许多影响的因素。当找人询问任何相关问题时,让他们耐心回答,这是你的职责。

你的问题甚至会帮助他们理清思路,从一个新人的角度提出的问题,给他们提供了一个新的视角,也许就帮助他们解决了一直令人困扰的问题。

「哎呀,只要每周重启一次系统,就没问题了」

「你必须依次执行 3 次构建才能完成构建」

「我们的用户根本不想要那个功能」

真的吗? 为什么呀? 不能只满足于别人告诉你的表面现象,要不停地提问直到你明白问题的根源。

中庸之道

  • 咱们可能跑题,问一些与主题无关的问题,要问到点子上。
  • 当问为什么的时候,也许会被反问,「为什么你问这个问题」,在提问之前,想好提问的理由,也许有助于问出恰当的问题。
  • 「这个,我不知道」是一个好的起点。

把握开发节奏

在许多不成功的项目中,基本都是随意安排工作计划,那样随机安排很难处理,根本不知道明天将会发生什么。

中庸之道

  • 每天结束的时候,测试代码,提交代码,没有残留的代码
  • 不要搞得经常加班
  • 如果开发节奏过于密集,会精疲力竭
  • 有规律的开发节奏会暴露很多问题

交付用户想要的软件

让客户做决定。

开发者能做的一个最重要的决定就是,判断哪些是自己决定不了的。

应该让业主做决定,你不需要自己给业务上的关键问题做决定。毕竟,那不是你的事情。

中庸之道

  • 记录客户做出的决定,并注明原因,好记性不如烂笔头
  • 不要用低级别和没有价值的问题打扰繁忙的业务人员
  • 不要假设低级别的问题不会影响他们的业务
  • 如果业务负责人回答我不知道,尽你所能提供建议

保持可以发布

在团队工作,修改一些东西的时候必须很谨慎,时刻警惕,每次改动都会影响系统的状态和整个团队的工作效率。

在办公室不能容忍任何人乱丢垃圾,为什么就可以容忍一些人给项目带来垃圾代码呢?

下面是一个简单的工作流程,可以防止你提交破坏系统的代码。

  • 在本地运行测试
  • 检出最新的代码 git pull
  • 提交代码

提早集成,频繁集成

绝不要做大爆炸式的集成。越早解决它们,工作量就越小。如果推迟集成的时间,解决这些问题就会变得很难,需要大量和大范围地修改代码。

固定的价格就是保证要背叛承诺

软件项目天生就是变化无常的,如果要提前给出一个固定的开发时间,就几乎肯定不能遵守开发上的承诺。

那如何做到更精确的评估呢? 可以试试这样的方法:

  1. 主动提议先构建系统最初,有用的部分,这时候不是要完成所有的功能,而是足够一次交付,并能让用户真正使用,通常在 6~8 周。
  2. 第一次迭代结束,客户有两个选择,可以新增功能继续下一个交付,要么取消合同,仅需支付第一个迭代的费用。
  3. 如何继续前进,这时根据提出的增量功能,应该就可以很好预测下一个迭代工作。

敏捷反馈

编写能产生反馈的代码,通过单元测试来获取反馈。

  • 确保测试是可重复的,使用当前时间/机器固定 IP 等都是使其依赖运行时的机器
  • 测试边界条件,比如日期 11:59:59/0:00:00,比如月份 0 / 13 / -1
  • 不要放过任何失败的测试

一旦单元测试到位,采用这样的回归测试,就可以随意重构代码。单元测试会确保你不会意外地破坏任何功能。

如果你仍然在寻找开始单元测试的理由,下面有很多:

  • 单元测试能及时提供反馈
  • 单元测试让你的代码更佳健壮
  • 单元测试是有用的设计工具
  • 单元测试是让你自信的后台
  • 单元测试是解决问题时的探测器
  • 单元测试是可信的文档
  • 单元测试是学习工具

先用它再实现它

在说服他人使用它之前,先得让自己切实的使用产品。

先写测试,你就会站在代码用户的角度来思考,而不仅仅是单纯的实现。

你会发现因为你自己要使用它们,所以能设计一个更有用,更一致的接口。

先写测试有助于消除复杂的设计,让你可以专注于真正需要完成的工作。

举一个编程例子,这是一个可以两个人玩的「井字旗」 游戏:

开始,会思考如何为这个游戏做代码设计,也许会考虑到需要这些类,棋盘/行列/用户/规则/等。

首先写棋盘的测试,类似于这样

1
2
3
4
5
func TestBoard(t *testint.T) {
  s := NewBoard()
  request.NoNil(t,s)
  request.IsFalse(t,s.GameOver)
}

此时执行测试应该是失败的,因为结构体 board 还不存在,需要实现这个结构体。

1
2
3
4
5
6
7
type Board struct{
  GameOver bool
}

func NewBoard() *Board{
  return &Board{}
}

此时再执行测试,以保证测试通过,不通过话的修改直到通过。

下一步,必须决定谁先开始走第一步棋,需要设第一个比赛者,写下相关测试

1
2
3
func TestSetFirstPlayer(){
  // test content
}

这时,测试会迫使你决定如何在代码中表示比赛者,并分配到棋盘上。

可能是这样的

1
2
# 玩家 mark 使用 X
board.SetFirstPlayer(new Player("Mark"), "X")

但真的需要 player 这个结构体吗? 或者需要玩家的名字吗? 此时还没有实现 SetFirstPlayer 方法,初期让我们的测试更简单一些,比如

1
board.SetFirstPlayer("X")

这个版本隐藏着风险,可以传递任何字母给 SetFirstPlayer,意味着需要添加代码来检查参数的正确性。因此我们可以进一步简化,用一个 bool 来表示第一个玩家是 O 还是 x。

例如这样

1
2
3
4
func TestSetFirstPlayer(){
  board.FirstPlayerPegIsX = true;
  requests.IsTrue(board.FirstPlayerPegIsX)
}

我们的思考是从 player 类开始,最后实现只用了简单的布尔类型属性,我们不是要扔掉好的设计,仅仅用大量的布尔类型来编码所有东西,而是成功地实现特定功能的最低成本。程序员很容易走向另一个极端,一些不必要过于复杂的事情。

消除还没有编写好的类,这会很容易简化代码,相反,一旦已经编写了代码,也许会强迫自己保留这些代码,并继续使用它。

好的设计并不意味着需要更多的类

添加无用的代码总是不好的想法,不管他们是否真的需要,TDD 有机会让你编写代码之前,可以深思熟虑将如何使用它。这会迫使你去思考它的可用性和便利性,让你的设计更注重实效。

设计并不是开始编码的时候就结束了,需要在产品的生命周期中持续添加测试,添加代码,重新设计。

中庸之道

  • 不要把测试代码和提交代码之前的测试等同,单元测试可以保证代码的健壮性,提交代码前的功能测试可以保证产品的稳定性。
  • 任何一个设计都可以被改进
  • 单纯的单元测试无法保证好的设计,但他们回到设计有所帮助,使设计更佳简单。

不同环境,就有不同问题

你的应用程序要在不同的操作系统上运行,或者一个操作系统的不同版本,就需要测试所有的操作系统。

不同环境就有不同的问题,使用持续集成工具,在每一种支持的平台和环境中运行单元测试。要积极地寻找问题,而不是等问题来找你。

本文阅读量 次, 总访问量 ,总访客数
Built with Hugo .   Theme Stack designed by Jimmy