
1.3 文档编写的是知识
软件开发过程需要知识,并需要基于这些知识做出决策,而这些决策反过来又创造了新的知识。这里说的知识包括需要解决的问题、做出的决策、如此决策的原因、导致做出该决策的事实以及所考虑的替代方案。
你可能从来没这么想过,但是以编程语言键入的每条指令都是一个决策。决策有大有小,但是无论大小,都是决策。在软件开发中,设计阶段之后不会有昂贵的构造阶段:构造(运行编译器)非常便宜,昂贵的只有一个设计阶段(有时候是长久存在的)。
软件设计可以持续很长时间,有时会长到令你忘了前面的决策以及背景。有些设计阶段很长,会经历人员更替。旧人员会带着他们的知识离开,而新成员则缺乏所需知识。对于软件开发一类的设计活动来说,知识至关重要。
毫无疑问,这种设计活动大多数时候是团队合作,涉及多个人。合作意味着大家共同决策或根据某人的知识做出决策。
软件开发的独特之处在于设计不仅与人有关,还与机器有关。计算机就是其中之一,很多决策只要交给计算机执行就好了。整个操作一般是通过称为源代码的文档来完成的。我们使用编程语言这样的形式语言,将知识和决策以一种计算机能理解的形式传递给它。
但是,困难之处并不是让计算机理解源代码。即使是缺乏经验的开发人员通常也能成功地做到这一点。最难的是让其他人了解已经完成的工作,以便他们更好、更快地完成之后的工作。
目标越大,我们就越需要更多的文档来实现知识管理的累积过程。整个过程会超出我们大脑的处理能力。当大脑和记忆力无力应对时,我们就要求助于书写、打印和软件之类的技术来记住和组织更多知识。
1.3.1 知识的来源
知识来自哪里?知识主要来自对话。通过与其他人对话,我们积累了很多知识。这种对话发生在诸如结对编程的集中办公中,发生在会议期间,还发生在咖啡机旁、电话里或者公司的聊天软件或电子邮件里。对话的示例包括BDD需求说明研讨会和敏捷开发中的Three Amigos1。
1“三个好朋友”,指敏捷开发过程中的三种角色:业务分析人员、开发人员和测试员。——译者注
然而,作为软件开发人员,我们还要与机器对话。我们将这些对话称为实验。我们用某种编程语言编写代码,告诉机器一些事情,然后机器运行这些代码并返回这些信息:测试失败或成功,用户界面(UI)响应如预期或者结果并不是我们想要的(在这种情况下我们会知道一些新的东西)。实验的示例包括TDD、新兴设计和精益创业(Lean Startup)实验。
知识也来自于对背景的观察。在一家公司里,你只要注意观察别人的对话、行为和情绪就能学到很多东西。观察的示例包括领域沉浸、情绪墙、信息发射源和精益创业的“走出办公楼”观察。
知识来自人与人之间的对话和在可观察环境中对机器做的实验。
1.3.2 知识如何演进
有些知识可以保持数年稳定不变,而有些却在短短几个月甚至几个小时内频繁地变化。
任何形式的文档都必须考虑维护成本,并尽可能使这个成本接近零。对于稳定的知识,可以使用传统的文档编写方法。但对于那些频繁变化的知识,书写文本并在每次变更后对其进行更新就不是一种好的选择。
因为软件行业发展很快,所以我们希望能够快速开发软件。软件开发速度快到我们不可能花费时间编写一页又一页的文档,但我们又希望获得文档带来的所有好处。
1.3.3 为什么需要知识
开发软件时,我们会遇到很多问题,做出很多决策,并在学习中不断调整。
- 我们要解决什么问题?从现在开始,每个人都应该知道这一点。
- 我们到底要解决什么问题?(当意识到一开始就做错了时,就要试着回答这个问题。)
- 我们一直无法分清trade(贸易)和deal(交易)之间的区别。但是,当我们终于认识到它们并不是同义词时,就不能再混淆它们了。
- 我们试了这个新数据库,但是出于三个原因,它并不能满足我们的需求。所以只要我们的需求不变,就无须再试了。
- 我们决定分离购物车模块和支付模块,因为我们发现一个模块的变更并不会引起另一个模块的变更。我们不应该再将它们耦合在一起。
- 我们偶然发现这个功能没什么用处,所以计划在下个月删除这段代码。但是,我们很可能会忘了这么做的根本原因。因此,假如代码没被删除,它将永远是个谜。
使用现在的软件时,如果我们没想起以前的知识,就会再做一遍已经做过的事情,因为我们不知道已经做过了。我们最终还会将一个功能放到不相关的组件中,因为不知道应该将它放在哪里,从而导致软件变得越来越臃肿,而与这个功能有关的代码则分散在各个不同的组件里。
要是我们有足够的知识来回答以下这些日常问题就好了。
- 我在哪里可以安全地解决这个问题?
- 我应该在哪里添加这个增强功能?
- 原作者会在哪里添加这个增强功能?
- 这行代码看起来没什么用,删了它安全吗?
- 我想修改一个方法签名,但是改了以后会有什么影响呢?
- 为了看懂一套代码的工作原理,我真的要对它进行逆向工程吗?
- 每当业务分析员需要了解当前的业务规则时,我真的需要花时间阅读源代码吗?
- 当客户要求添加一个功能时,如果需要开发,我们如何知道代码是否已经支持这个功能?
- 我们可能总是以为自己开发代码的方法是最好的,但是如果我们对它的工作原理缺乏全面的了解该怎么办?
- 我们怎样才能轻松找到与特定功能相关的代码?
知识缺失会导致两种后果。
- 浪费时间:这些时间本可以更好地投入到其他方面的改进上。
- 次优决策:从长远来看,其他决策可能更有意义或成本更低。
随着时间的推移,这两种代价会叠加——花在寻找缺失知识上的时间没有花在做出更好的决定上。反过来,一个又一个的次优决策会使我们的生活越来越悲惨,直到我们别无选择,只能不再维护并重新开发软件。
有些知识对执行开发任务有用。能够访问这类知识似乎是一个好主意。
软件编程即理论建立与传递
1985年,Peter Naur的著名论文“Programming as Theory Building”完美地揭示了程序设计是集体努力的结晶这一真相。他说,与其说是告诉计算机该做什么,不如说是与其他开发人员分享那些已经通过学习、实验、对话和深刻的反思而精心阐述过的世界理论(想一下“心智模型”)。用他自己的话说:
正确地编程应该被看作这样一种活动,程序员通过其形成或获得对当前问题的某种见解(即理论)。一种更为常见的观点则是编程应被看作程序和某些其他文本的生产过程。这两种看法正好相反。
问题在于这个理论的大部分是不言而喻的。代码仅仅代表了冰山一角。它更像是开发人员心目中的理论结果,而不是代表理论本身。Peter Naur认为,这个理论涵盖了三个主要的知识领域。
- 代码与它所代表的世界之间的映射关系:掌握程序理论的程序员能解释该解决方案与它有助于处理的现实世界事务之间的关系。
- 程序的根本原理:掌握程序理论的程序员能解释程序的每个部分为什么是现在这样的。换句话说,程序员能够以某种理由支持实际的程序文本。
- 程序扩展或发展的潜力:掌握程序理论的程序员能够对修改程序的任何需求做出积极的响应,从而以新的方式支持现实世界事务。
随着时间的推移,我们已经掌握了许多技术,这些技术能让人们相互传递理论。简洁的代码和Eric Evans的DDD鼓励程序员去寻找一种方法,更直白地用代码表达他们头脑中的理论。例如,DDD的通用语言弥合了真实世界的语言和代码语言之间的鸿沟,从而帮助解决了映射问题。我希望未来的编程语言能够认识到,它们不仅需要代表代码的行为,而且要代表程序员更大的心智模型,代码就是这种心智模型的结果。
人们还发明了模式和模式语言,试图以直白的文字来描述一些理论。知道的模式越多,我们就越可以对那些不言而喻的理论进行编码,从而使其变得更明确、更容易移植。模式在对自己力量的描述中体现了选择它们的基本原理的关键要素,而且有时暗示了应该如何扩展。它们还可能暗示了程序的潜力。例如,策略模式应该通过添加新策略来扩展。
但是,随着逐渐弥补知识方面的不足,我们还要应对更大的挑战,因此挫败感依然存在。我相信Peter Naur在1985年说的这句话在未来的几十年中仍然适用:
对于新程序员来说,要掌握一个程序的现有理论,有机会熟悉程序文本和其他文档是不够的。
我们永远不能彻底解决知识转移问题,但是可以接受这一事实,并学会与它共存。作为程序员头脑中的一种心智模型,这个理论永远不能与一些人完全共享,因为这些人并没有参与构建这个理论的思考过程。
结论似乎是不可避免的:至少在某些大型程序中,错误的持续适应、修改和纠正是由一组持续、紧密联系的程序员根据共有的某种知识决定的。
值得注意的是,定期集中办公的永久性团队不会因为这一理论传递而遭受太大痛苦。