增量式开发是我最喜欢的开发方式之一。

##1 在读《The Django Book》时,十分震惊,技术书籍居然可以写得这般清晰,清晰如阳光照射下,清水中的鹅软石。技术类书籍中好书是向来不缺的,对新手友好的书却也并不多见。《The Django Book》算是其一,后来我再翻这本书,想它为何能把知识说得这般清晰,发现书中讲解知识的方式是增量式的:每次的讲解的内容都尽量做到只包含主题相关部分,基本不添加额外的知识干扰(打击)你,即便有些不可避免的后向引用,也会明确告诉你它的作用是啥,你目前只要把它当成黑盒就行。每一段程序都尽可能简易简短,每一节都在上一节基础上加上一点新东西,这样学起来真如读小说一般连贯有趣。如果哪里不懂,基本可以确定,不懂部分就是这节多出的知识。

##2 《代码大全》里,在谈论隐喻部分时作者说道

  • >增量设计 / 构造 / 测试是软件开发的最强有力工具之一
  • >在增量开发中,你首先设计系统可以运行的最简单版本。它甚至不接受任何数据输入,或者对数据进行处理,他也可以不产生输出。只需要成为一个坚实的骨架结构。

Fred Brooks(《人月神话》作者)甚至认为

应做好建造一个扔掉一个的准备

##3 开源社区其实一直挺习惯这种做法的

《大教堂与集市》中提到

  • >优秀的程序员知道写什么,卓越的程序员知道改写(和重用)
  • >“建设性懒惰”,他们知道人们要的是结果而不是勤奋,而从一个部分可行的方案开始,明显要比从零开始容易得多
  • >虽然Linux中所有Minix代码最终都被移除或重写,但它在Linux成长初期确实起到了类似脚手架的作用

以上是我做的读书摘记。

你看,以上谈论的几点,如果你平时都混迹github的话,也许就是这样做的,当有某个需求时,用关键字去github搜下是否有类似项目,有的话,clone下来,以其为脚手架,先跑起来,在此基础上增量地做些自己的定制,如果跑不起来,就checkout回去,成功的话,又继续往前多迈一步,因为使用的是增量式开发,你能知道每次的错误肯定是新增的代码造成的,这样一来你是信心十足的前进,毫无后顾之忧

##4 晚上在翻《How to Think Like a Computer Scientist: C++ version》 (中文版《像计算机科学家一样思考 C++版》)

第5章谈到程序开发时,作者建议大家使用增量式开发。书中举得一个例子很棒,摘录过来,适合帮助新手理解增量式开发的过程

我们面临这样一个任务:计算两点(x1,y1)和(x2,y2)之间的距离

公式是简单的:distance = √((x2-x1)^2+(y2-y1)^2)

###4.1 好的,我们开始写下骨架代码

1
2
3
4
double distance(double x1, double y1, double x2, double y2)
{
	return 0.0;
}

在这一阶段函数并没有做任何实质性的事情,但是尝试编译可以让我们在把函数变得更复杂之前确认是否却在语法错误。

###4.2 为了测试这个新函数,我们需要调用它

1
2
3
4
5
6
int main(void)
{
	double dist = distance(1.0, 2.0, 4.0, 6.0);
	cout << dict <<endl;
	return 0;
}

一旦检查完函数定义的语法,就可以开始一次添加一行的代码进行增量开发了。在每次增量改变之后,就可以编译运行程序,用这种方法,在任何一个时间点我们都确切知道错误所在–刚添加的最后一行代码。

###4.3 编译,运行,一切正常。这时你的信心在不断累积。下一步是用两个临时变量dx和dy存储x2-x1和y2-y1的差值

1
2
3
4
5
6
7
8
double distance(double x1, double y1, double x2, double y2)
{
	double dx = x2 - x1;
	double dy = y2 - y1;
	cout << "dx is " << dx <<endl;
	cout << "dy is " << dy <<endl;
	return 0.0;
}

在此计算distance(1.0, 2.0, 4.0, 6.0)。其中参数的选择是有原因的,水平距离为3,垂直距离为4,所以两点间距离为5,当测试一个函数的时候,知道正确的结果是很有帮助的。在这里dx为3.0,dy为4.0

编写完函数时,会删除输出语句,像这样的代码称为支架代码,可以帮助正确地编写程序,但是它并不是最终代码的一部分。

###4.4 下一步是对dx和dy做平方计算,我们使用简单快捷的直接与自身相乘。

1
2
3
4
5
6
7
8
9
double distance(double x1, double y1, double x2, double y2)
{
	double dx = x2 - x1;
	double dy = y2 - y1;
	double dsquared = dx * dx + dy * dy;
	cout << "dsquared is " <<dsquared;

	return 0.0;
}

在这个阶段可以再一次编译和运行程序,检查中间值是否正确

###4.5 最后,用sqrt函数计算平方根,并返回结果

1
2
3
4
5
6
7
8
double distance(double x1, double y1, double x2, double y2)
{
	double dx = x2 - x1;
	double dy = y2 - y1;
	double dsquared = dx * dx + dy * dy;
	double result = sqrt(dsquared);
	return result;
}

至此,主要的工作就完成了。

###4.6 在main函数中,检查结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <math.h>
#include <stdio.h>

double distance(double x1, double y1, double x2, double y2)
{
	double dx = x2 - x1;
	double dy = y2 - y1;
	double dsquared = dx * dx + dy * dy;
	double result = sqrt(dsquared);
	return result;
}

int main(void)
{
	double dist = distance(1.0, 2.0, 4.0, 6.0);
	cout << dict <<endl;
	return 0;
}

熟练之后,一次可以编写和调试更多的行。

总结下:

  • 从一个可以运行的程序开始,做小步,渐进的改进。在任何一点,如果出错,可以清楚地知道错误发生的地方
  • 使用临时变量存储中间结果,便于输出和检查。
  • 一旦程序正常运行,可能需要移除部分支架代码。记得加上注释使程序保持可读性。

此外可以结合git来管理代码,将没有问题的代码入库,保证代码安全的同时,可以随时切换到任意时间点。这样在渐进前进的同时就可以无所顾忌了