API对设计流程的影响
Seibel:你设计软件的流程是什么样的?打开Emacs就开始写代码,然后改来改去直到程序写好?还是坐到沙发上拿着一打纸先列个提纲?
Bloch:很多年前,我在OOPSLA(译者注:面向对象编程、系统、语言和应用国际研讨会。)上作了一个演讲,题目是“如何设计一个好的API,以及这为什么很重要”。网上可以找到这个演讲的几个版本。它很好地解释了我的设计流程。
最重要的是了解你到底要设计什么,也就是你要解决的是什么问题。需求分析的重要性怎么强调也不过分。有人认为:“噢,需求分析呀。跑到顾客那边问问他需要什么。得到客户的答案不就成了嘛。”
事实绝非如此。这不仅是一个协商的过程,而且是一个理解的过程。许多顾客不会告诉你问题,而会告诉你一个解决方案。例如,顾客可能会说:“我需要你给这个系统加上以下17个特性。”那么你必须问:“为什么?你想用这个系统做什么?你期望它怎么发展?”等等。你要来来回回好几次,直到弄明白顾客真正需要软件去做的所有事情。这些就是用例。
这个阶段最重要的事情就是提出好的用例。一旦有了用例,你就有了用来比较所有备选解决方案优劣的基准。你可以花大量的时间去改进用例,因为一旦用例错了,你就彻底失败了,所有后续的流程都会徒劳无功。
我见过这样的事。有人找来一帮聪明人,还没搞清到底要做个什么样的系统,就开工了。辛苦地工作了6个月,写出来247页的系统规范文件。这是最糟糕的情况。因为6个月后他们精确制定出来的系统可能毫无用处。他们往往会说:“我们已经投资了那么多,制定出来规范文件,我们必须把这个系统做出来。”所以他们创造了一个没有任何用处的系统,这个系统也从未投入使用过。多恐怖啊。如果没有用例就做好了软件,那么当你试图做点非常简单的操作时就会发现:“哦,我的天,像选择一个XML文档并打印这么简单的事情,需要这么多的代码啊。”这是很恐怖的。
所以先获取这些用例,然后编写骨架API。骨架API应该很短很短,也就一页纸的内容吧,一般正好是一页,无需非常精确。你要声明包、类和方法,如果还不清楚他们应该是什么样的话,可以放一句话的描述。不过这不是产品发布要求的那种质量文档。
中心思想就是在这个阶段保持敏捷,逐步完善API,使其满足用例,为原始的API添加代码,看是否可以满足需求。真是不可思议,很多事情事后看真是太浅显了,但设计API的时候,甚至是构思用例时,你还是会犯各种错误。用代码实现用例时你会说:“哦,我的天,全都错了。类太多了。这些可以合并,这些需要拆开。”或者类似这样的话。好在API文档只有一页长,改起来也很容易。
你对API越来越有信心,代码也就越写越长。但是,核心原则是,先写使用API的代码,然后再写实现它们的代码。因为,如果实现代码被废弃,之前的工作就都白做了。事实上,应该在给出设计规范前写API的代码,否则你可能把时间浪费在给最后完全不需要的东西设计规范上。这就是我设计软件的方法。
Seibel:设计Java集合类这样的,一个具体的自包含的API,设计规范需要有多具体?
Bloch:我敢说比你想的要粗略多了。任何复杂的编程都需要API设计,因为大程序都需要模块化,你必须设计模块之间的接口。
优秀的程序员把问题分块,孤立地去看他们。这样做的理由有几条。比如,你可能会在无意中创造出好用的、可重用的模块。如果你写一个单一的系统,它越来越大,等你想分块的时候,就无法找到清晰的边界,最后系统就变成了一个无法维护的垃圾。所以我断言,无论你是否把自己看成API设计者,把问题分块都是最好的编程方法。
这就是说,编程的世界非常广阔。如果对你来说编程就是写HTML代码,那么这也许不是最好的编程方法。但是,我认为对于大多数编程来说,这就是最好的方法。
Seibel:所以你希望系统由不同的模块松散地耦合在一起。要达到这样的目标,现在有两种不同的看法。一种是坐下来实现设计模块间的API,像你前面提到的那样。另外一种是,“构建可运行的最简系统,然后毫不留情地重构”。
Bloch:我不认为这两种方法有什么冲突。某种程度上,我谈的就是测试先行编程,以及对API的重构。如何测试你的API呢?在实现API之前编写它的测试用例。虽然我还不能运行用例,但我在进行测试先行的编程:实现用例后看API是否能完成任务,我用这样的方法测试API的质量。