基于风险投入比的敏捷性(灵活适应)获得策略

大家都讲敏捷(Agile),这个词的不等于单纯的快,而是灵活适应,快速响应变化的意思。

那么,哪些因素可以提高组织敏捷性呢?这里提出一个基于风险ROI的模型,讲各种因素统一融合起来。

$$
ROI=\frac{Impact*p\%}{Cost}
$$

(p%=概率或确定性,Impact=影响程度或收益程度,Cost=成本)

ROI高 <---------> ROI临界值 <---------> ROI低
全面标准化 增多可能性 (受心理风险偏好影响而得到补偿) 降低沉默成本 延迟决策
例:信息透明对齐、结构清晰、动作一致、防呆机制、排队 例:拆分、模块化、Plan B、冗余、备份、去中心化小团队 \ 例:试错、尽早迭代、能力储备建设(仅长期效应) 例:极简、断舍离
(广度优先?) (深度优先?)

风险偏好心理补偿作用:对不确定和犯错的容忍

由于心理对变化和不确定性的恐惧,而影响到ROI临界值的判断。


旧版

有效的单元测试之一--代码坏味道(读书笔记精华带源码)

(摘要)


[TOC]

自动化测试的价值

开发者应该编写自动化测试,以便当发现回归问题时就使构建失败。而且,测试先行的编程风格已有大量的专业研究,使用自动化测试不仅是保护回归,而且是帮助设计,在编写代码之前就指出代码的期望行为,从而在验证实现之前先验证设计。

来认识一下小马。小马是著名的编程奇才,两年前刚毕业,然后加入了本地一家投行的IT部门,为银行开发用于在线自助服务的web应用程序。作为团队中最年轻的成员,起初他保持低调,集中精力学习银行的领域知识,熟悉“这里做事的方式”。

几个月后,小马注意到团队的工作很多都是返工:修复程序员的错误。他开始关注团队修复错误的类型,发现单元测试可以轻易地捕获到其中大多数。当他感觉到哪里存在特别容易出错的代码时,小马就对其编写单元测试。

测试帮助我们捕获错误。

一段时间以后,团队其他人也开始到处编写单元测试。Marcus已被测试感染 (test-infected) 了,他碰过的代码几乎都具有相当高的自动化测试覆盖率。除了在第一次时犯错,他们不会再花费时间修复过去的错误,待修复缺陷的总数在下降。测试开始清晰可见地影响着团队工作的质量。

自从小马编写第一个测试,已经过去近一年了。团队的测试覆盖率在近几个月快速提高,达到了98%的分支覆盖率。小马曾认为他们应该推动那个数字直到100%。但过去几周,他打定了主意——那些缺失的测试不会给他们带来更多价值,花费更多精力来编写测试不会带来额外的收益。许多没有测试覆盖的代码,只因所用的API要求实现某些接口,但小马的团队并未用到它们,那么何必测试那些空方法桩呢?

100%测试覆盖率不是目标

100%的覆盖率并不能够确保没有缺陷——它只能保证你所有的代码都执行了,不论程序的行为是否满足要求。与其追求代码覆盖率,不如将重点关注在确保写出有意义的测试。

后来,高级软件架构师老赛加入了自助服务团队,并快速地成为了低级成员的导师,包括小马在内。小马习惯于先写代码,在提交到版本控制系统之前再补充单元测试。但老赛的风格是先写一个会失败(很明显是这样)的测试,再写足够使测试通过的代码,然后写另一个失败的测试。他重复这个循环直到完成任务。

与老赛共事,小马意识到自己的编程风格开始转变。他的对象结构不同了,代码看起来稍微不同了,只是因为他开始从调用者的角度来审视代码的设计和行为了。

测试帮助我们针对实际使用来塑造设计。

想到这些,小马觉得好像明白了一些道理。当他试图用语言表达出他的编程风格是如何改变的,以及收到了哪些效果时,小马明白了他写的测试不仅仅是质量保证的工具,或是对错误及回归的保护。测试作为一种设计代码的方式,提供了具体的目的。编写使失败测试通过的代码比以前的方式简单多了,而且该做的也都做了。

通过明确地指出所需的行为,测试帮助我们避免镀金。

主人公小马的经历正如许多爱好测试的编程人员一样,在不断地认识和理解测试。我们经常因为相信单元测试有助于避免尴尬、耗时的错误而开始编写它们。随后我们会学到,将测试作为安全网只是等式的一部分,而另一部分——或许是更大部分——其好处是我们将测试表达为代码的思考过程。

大多数人同意说编写一些测试是不费脑筋的。但随着我们接近完全的代码覆盖率,我们不那么确定了——我们差不多已经为一切都编写了测试,而剩下的没有测试的代码是微不足道,几乎不会破坏。这就是某些人所谓的收益递减。因此,

编写测试的最大价值不在于结果,而在于编写过程中的学习。

(略,请阅读正版原书)

何为优秀与代码坏味道

可读性、可维护性、可信赖性…

单元测试代码的可读性

原始类型断言

断言应该表达某种假设或意图。它们应该声明代码的行为。基本断言的问题在于它们缺乏意义,因为断言的基本原理和意图隐藏在看上去无意义的单词和数字背后,造成它们难以理解,并且难以验证断言的正确性。

下面例子展示了一个全局正则搜索(grep)程序的测试。grep程序其实就是逐行处理某种文本输入,在处理时可以包括或排除掉含有特定子串或模式的文本行。测试应当是你的对象所提供功能的具体例子,那么我们来看一看这个测试是否说清楚了grep程序该做的事情。但愿这个测试没有受到原始类型断言的折磨!

1
2
3
4
5
6
7
@Test
public void outputHasLineNumbers() {
String content = "1st match on #1\nand\n2nd match on #3";
String out = grep.grep("match", "test.txt", content);
assertTrue(out.indexOf("test.txt:1 1st match") != -1);
assertTrue(out.indexOf("test.txt:3 2nd match") != -1);
}

这个测试在干什么?首先,我们定义了文本字符串content来表示一段输入,然后调用grep程序,然后对grep结果输出中的某些东西进行断言。这些东西就是问题所在。断言的目标并不清楚,因为断言过于基本——它与测试的其他部分说着不同的语言。

具体来说,我们在这个断言中是要计算输出结果中的另一个文本字符串的索引,并将其与-1进行比较;如果索引为-1,那么测试失败。测试的名字叫做outputHasLineNumbers,显然,对于在代码中grep()的具体调用,输出结果应当包含正在进行逐行查找的文件名,后面加上行号。

不幸的是,我们不得不经过这个思考过程才能理解为什么我们要计算索引,为什么查找test.txt:1而不是其它东西,为什么与-1进行比较,当索引是-1或不是-1时测试是否会失败?这无需火箭科学就能找到答案,但却是我们大脑的不必要的认知工作。

继续阅读 More