程序员和项目经理有时认为“文档驱动开发”意味着在代码中添加大量注释,或者在开发过程中与文档编写者密切合作。这是因为很难想象开发如何在文档之后进行,因为肯定在实际有东西可以记录之前,文档是不可能编写的。
传统上,文档被视为一种新闻工作。文档编写者会得到一些软件,他们会把它带到实验室里,摆弄它、探测它,直到他们弄清楚所有事情,然后他们会把它写下来,供其他人永远不读。
这忽略了一个非常重要的过程,当一个开发者,在一个晚上闲暇时喝着咖啡,突然产生了一个应用程序的想法时,这个过程自然而然地发生了。开发者可能没有意识到,但随着想法的形成,一种文档工作已经发生。想法不会只是完全成型地出现,每个细节都绘制在一张整洁的蓝图上,准备连接到孤单的代码行。想法是逐渐产生的。
代码可以说是一种艺术形式,所以这里有一个在艺术界的类比。
一幅静物画代表了物理世界中的某件事物。但是静物艺术最初是从抽象的形状开始,然后被提炼成可以识别的东西,然后添加纹理、阴影等等。
这个过程可以被称为自我记录,因为在绘画生命周期的每个阶段,你都有绘画进展的快照。

Seth Kenlon, CC0
在代码中,这些快照可能是 git 提交加上非常好的注释。虽然这对于稍后加入并想要了解代码库的哪个部分负责哪个任务的其他程序员来说是很好的,但它通常对普通最终用户没有用处。
在静物类比中,这意味着虽然绘画的自我记录可能对有抱负的艺术家有用,但它对预期的观众没有多大帮助。
绘画的最终用户文档实际上是最终结果:艺术家在绘画时所看到的东西是绘画旨在捕捉的完全实现的图像。如果绘画的“最终用户”想要了解绘画主题的真实形式,那么最终用户会参考实际的物理对象。
信不信由你,代码也是如此。唯一的区别是,被记录的应用程序还不存在。但这几乎并不意味着你不能像它已经存在一样记录它。
编写你想要的应用程序的文档
当你坐下来编写一个应用程序时,你对你打算让应用程序做什么有一些想法。我将使用实用程序 trashy 作为一个例子,因为它范围很窄
Trashy is a command-line trash bin.
这通常是开发者开始的地方,但如果你实践文档驱动开发,那么这就是你的主页或用户手册开始的地方,而且它发生在你在坐下来编写代码之前。
如果你的唯一任务是“编写一个命令行回收站”,那么你的代码可能会朝着任何先碰到的方向发展。你可能知道也可能不知道 自由桌面 回收站规范,因此你可能一开始不会想到遵循它的方案。你可能知道已经存在一个回收站机制,所以你的代码的第一个迭代可能只会将一个文件转储到用户的现有回收站中,而没有考虑到应该 accompanying 它的相关元数据。
但是如果你先坐下来记录它,你就会被迫考虑细节。例如,想象一下这是 trashy 文档的初稿
Trashy is a command-line trash bin.
trash foo : moves foo to system trash
trash empty : empties system trash
它已经比最初的概念更强大了,因为它也提供了一个清空回收站的机制,而不仅仅是将文件发送到回收站。它之所以存在,唯一的原因是记录用户如何与你的应用程序交互的行为迫使你从用户的角度思考应用程序。
这是绘画东西和实际将绘画挂在画廊墙上供其他人观看之间的差异的一次预演。
文档作为框架
当你继续为你的命令行回收站应用程序编写文档时,你最终会想到其他“显而易见”的期望,作为用户而不是开发者,你会对这样的工具抱有这些期望。你会想到约定,比如能够列出当前回收站中的文件,甚至恢复一个错误移动到回收站的文件(反过来,这可能会引导你了解自由桌面回收站规范,这将教会你应该在文件移动到回收站时写入哪些元数据)。
更好的是,你的文档现在是你的伪代码。你的应用程序开发已经从编写在第一次修订时被丢弃的代码变成了准备构建的代码骨架
while [ True ]; do
# trash --help: print a help message
if [ "$1" = "--help" -o "$1" = "-h" ]; then
echo " "
echo "trash [--empty|--list|--restore|--version] foo"
echo " "
exit
# trash --list: list files in trash
elif [ "$1" = "--list" -o "$1" = "-l" ]; then
list
shift 1
# trash --version: print version
elif [ "$1" = "--version" -o "$1" = "-w" -o "$1" = "--which" ]; then
version
shift 1
# trash --empty
elif [ "$1" = "--empty" -o "$1" = "-e" -o "$1" = "--pitch" ]; then
empty
# trash --restore: restore a file to original location
elif [ "$1" = "--restore" -o "$1" = "-r" ]; then
RESTORE=1
shift 1
# trashy foo: moves foo to trash
else
break
fi
done
# more code here...
文档作为路线图
这是一个简化的例子,但对于更大的项目来说,这些原则甚至更重要,而且通常文档来自不是开发者的其他人。结果是一个更集中的开发周期,因为开发者不是朝着一个模糊的应用程序想法前进,而是朝着一个应用程序应该如何工作的具体蓝图编写代码。每个菜单,每个按钮,每个上下文菜单都已经绘制在假想应用程序的文档中。开发者需要做的就是填充代码。
敏捷公路战士
如果所有这些听起来都很规范和不灵活,不要认为用文档驱动开发无法应对敏捷挑战。敏捷开发方法需要来自用户和利益相关者的反馈来指导下一步开发的方向。文档不会改变这一点。事实上,在最好的文档驱动开发环境中,文档本身的处理方式与源代码完全相同。它被提交到与其余源相同的存储库,并且在其他任何事情之前更新。
事实上,将文档视为 bug 报告和功能请求的第一层是防止项目出现 UI 蔓延的好方法。当用户要求对 UI 进行看似无害的更改时,而这肯定是几个其他看似无害的更改的集合,UI 可能会很快开始带有委员会设计的经典标志。这是因为,如果程序员添加了所有请求而没有通过对全局有看法的人进行审查,实际上它就是如此。
使用文档作为持续设计过程的一部分是有道理的。这是一种廉价的原型设计。五个 bug 请求在主面板上添加一个新按钮,而该面板本应是一个干净的、一键启动面板,这相当于六个按钮弄乱了一个曾经极简和干净的界面。但是,如果启动面板首先经过记录和审查,那么这些按钮就不会出现在打印页面上。
文档,就像编码一样,不是应用程序生命周期早期完成的一次性过程。你的项目文档是一个活文档,与代码相同,不断更新和修订以反映开发计划和项目的当前状态。
一致性
文档倾向于产生应用程序工作方式的一致性,因为内部逻辑在很早之前就被绘制出来了。
在应用程序开发的第一周,很容易将一个函数编码为按钮点击,然后在第八周,当 UI 中的空间不足时,将一个同等重要的函数分配到一个晦涩的右键单击菜单。
当你在为尚不存在的东西编写文档时,很难做到这一点。在文档阶段,一切都是虚构的,因此你会看到为一个任务提供按钮,但隐藏相关任务的逻辑漏洞。当修复它只需要快速重写一个段落时,它不会长时间保持这种状态,这与更改许多文件中多个代码块(并可能修改你已经花了数周时间完善的整个 UI)截然不同。当你只是记录时,你可以编写任何你想要的东西;这是一个低成本的修复,最终它为开发者构建提供了一个更好、更智能的蓝图。
今天试用文档驱动开发
关于文档驱动开发,可能最棒的事情是没有准入障碍。任何非编码人员都可以为不存在的应用程序发明文档,而且它出奇地有用。我在电影行业和教育行业编写的应用程序都是由我以外的人设计的。当然,仍然存在来回的用户测试,以及对在纸面上看起来不错但最终运行起来不像设计师希望的那样顺利的东西进行完善,但它远少于没有设计师的东西。当然,也有一些时候,非编码人员会梦想到一些超出范围的东西,必须加以约束,但有时它会导致开发人员学习一些新技巧,使一些以前被认为遥不可及的东西实际工作。
无论你是开发人员还是只是有一些好主意的用户,都可以坐下来为你想看到的应用程序编写一些文档。或者,为现有应用程序的某个版本编写一些文档,你认为它可以做得更好。你会惊讶地发现它对你思考软件、直观设计和开发的方式有多大影响。
5 条评论