软件开发是一门手艺活images.china-pub.com/ebook3800001-3805000/3804233/ch16.pdf · 2015....
TRANSCRIPT
96
第一部分
比特和字节:编程实践
16
软件开发是一门手艺活
2003 年 12 月 1 日,星期一
开发软件并不像是工厂在制造产品。20 世纪 80 年代,大家惊闻日本在建造
“软件工厂”,这些工厂能通过流水线作业批量生产高质量的软件。而这在当
时的技术水平下是天方夜谭,即使是现在的技术也还达不到。把一群程序员
塞进车间,让他们站成一排,并不能有效地减少 bug 数量。
如果写代码不同于流水线作业,那它更像什么呢?有人提出,软件开发是一
门手艺活。当然,这种说法也并不总是成立的,因为不知你怎么想,反正我
觉得那个询问你该如何索引帮助文件的 Windows 对话框,从形状到样式,无
论从哪个角度看,都和人们常说的“工艺品”差远了。
写代码不是工业生产活动,也不总是一门手艺活(但它可以是),它其实更
像是一种设计活动。设计是一个很模糊的概念,其特征在于用较慢的成本增
长换取较快的价值增长。《纽约时报杂志》对 iPod 赞不绝口①,称赞苹果公司
是世间少有的知道如何用优秀设计来增加价值的公司。
图片由苹果公司提供,详情参见:http://www.apple.com/pr/photos/ipod/03ipod.html
—————————— ① 鲍勃·沃克,“The Guts of a New Machine”,《纽约时报杂志》,2003 年 11 月 30 日,第
78 页,第一栏。
97
软件开发是一门手艺活
16
但是,关于设计这个话题我已经讲了很多,现在想谈谈软件开发的工艺品特
性,包括它是什么以及如何辨识。
我想和大家分享一段我正在重写的代码,它实现的是 CityDesk 3.0 的文件导
入功能。(插播一条广告:CityDesk 是我的公司发布的一款方便易用的内容
管理产品。)
功能规格说明非常简单。用户通过标准的对话框选择一个文件,然后程序把
该文件复制到 CityDesk 数据库中。
结果这个例子很好地说明了,有的时候“写好最终 1%的代码要耗去 90%的
时间”。程序的第一版设计草案是这样的:
(1) 打开文件;
(2) 把全部内容装进一个庞大的字节数组;
(3) 把字节数组储存为数据表中的一个条目。
这个程序在通常情况下运行得很顺畅。对于一般大小的文件,程序基本上一
眨眼的工夫就跑完了。但是它存在几个小 bug,容我一一列举。
其中一个较大 bug 出现在压力测试中,当时我试着把一个 120MB 的文件拖
入 CityDesk。一般情况下,人们不会在网站上放这么大的文件。这在实际
中几乎不可能出现,但也不是没有可能。程序运行了大概一分钟才结束,
其间没有任何视觉反馈,界面呈现假死状态,点击哪里都没有反应。这明
显不够理想。
从用户界面设计的角度来考虑,我应该在程序进行耗时操作的时候显示某种
进度条,以及一个取消按钮。更理想的情况是让文件在 CityDesk 的后台进行
复制,不影响用户进行其他的操作。要实现这一功能,比较容易想到的方案
有三种:
(1) 只开一个线程,每隔一段时间检查是否有输入事件;
(2) 再开一个线程,仔细地做好线程同步;
(3) 再开一个进程,不需要很仔细地做好进程同步。
我的经验是,方法 1 的效果总是不够理想。当程序正在进行文件复制操作时,
很难保证其他所有代码能安全运行。埃里克·雷蒙德的论述让我相信,多线
程的方案不如多进程的方案①,而且根据我多年的经验,多线程编程增加了
—————————— ① 参阅http://www.faqs.org/docs/artu/ch07s03.html#id2923889。
98
第一部分
比特和字节:编程实践
太多额外的复杂度,会引入不少这种方案所独有的、危险的海森堡 bug①。3
号方案看起来更好,尤其是考虑到我们使用的数据库支持多用户操作,并不
介意多进程的并发访问。所以过了这个感恩节假期,我准备用方案 3 来实现
这个功能。
但是退一步想,我们从最初简单的读取文件、保存到数据库出发,最终采取
了一个复杂得多的方案:开一个子进程,让它读取文件并保存到数据库;子
进程还要有进度条和取消按钮; 并且要建立一种机制,让子进程能在文件保
存完毕时通知父进程,即时显示处理完毕的文件。另外要做的一部分工作是
通过命令行把参数传递到子进程;还要处理窗口焦点的行为,让它符合使用
者的心理预期;以及特殊情况的处理,比如用户在程序正在复制文件的时候
关闭电脑。我猜最后要写的代码量是原来的 10 倍,只是为了优雅地处理大
文件,而这些工作的成果只有大概 1%的用户能看到。
当然了,还有一种程序员会认为,子进程的方案还不如原来的方案。它被“过
度设计”了,增加了太多额外的代码行,这也导致程序更有可能出现错误。
这叫过犹不及。他们会说,这种做法是是典型的 Windows 思维,Windows
在他们眼中并不是一个设计得很高明的操作系统。他们对设计进度条的必要
性嗤之以鼻。因为在 UNIX 下,只要敲击 Ctrl+Z 然后用“ls -l”看看文件体
积有没有增加就知道程序是否还在运行了!
这个故事告诉我们,修复出现概率为 1%的问题,有时候需要花 500%的精力。
这种情形并不是软件开发活动所独有的,我这样说是因为我管理过一些建筑
施工工程。上周,我们的建筑承包商最终完成了对 Fog Creek 新办公楼②的装
修完善工作,包括给正门装上崭新的蓝色丙烯面板,并在门的边缘每隔 20
厘米包上一条铝片。仔细看下面这张照片,你会发现每扇门的四周都有铝条。
在两扇门接触的部分,两条铝片呈竖向对齐排列。在图上可能看得不明显,
但这两条铝片中央的固定螺丝并没有完全对齐,大约相差 2 毫米。木工师傅
事先做了仔细测算,但是在安装铝条的时候,门是放在地上的。等到门挂到
轴上才发现,坏了,螺丝很明显地没有对齐。
—————————— ① 详情参阅http://c2.com/cgi/wiki?HeisenBug。
② 参阅http://joelonsoftware.com/articles/BionicOffice.html。
99
软件开发是一门手艺活
16
这种情况绝非个例,我们的办公室里有很多螺丝不是完全对齐的。问题在于
打完孔后再矫正位置的成本是很高的。正确的位置只偏那么几毫米,因此不
好重新打孔。如果追求完美的话,只能换掉整扇门,太不值了。这个例子再
一次阐释了有时候修复出现概率为 1%的缺陷需要 500%的努力,也同样揭示
了为什么这个世界上有那么多工艺品的完美程度停留在 99%,而不是 100%。
(我们的建筑师总是喋喋不休,声称亚利桑那州的某些豪宅中,每颗螺丝都
是完全对齐的。)
大部分人都认为,软件具有与之类似的特性,让它带有工艺品的气息。当一
群真正具有工匠情怀的人们构建一个软件产品时,他们会想办法对齐所有的
螺丝。也就是说对于一些极其罕见的情况,软件也能处理得很漂亮。但要做
到这一点,开发者势必将大部分的精力投入到对各种罕见情况的正确处理
上,而不是集中精力让主要的业务逻辑正常运转。这也就意味着开发者要用
500%的精力去处理好出现概率为 1%的情况。
要达到一定的工艺水平,需要付出巨额的开发成本。软件行业里唯一值得付
出巨额的开发成本的情形,是开发面向海量用户的软件。很遗憾,譬如保险
公司的内部人力资源系统,就永远不会达到这种完美的工艺水平,因为没有
足够的用户数量来摊薄成本。然而对于开发零售软件的公司,这种不断追求
并完善软件品质的做法正是用户想看到的,因为那将会为公司提供长期的竞
争优势,所以请广大用户耐心期待,我们会慢慢来,因为慢工才能出细活。