梅毒抽血需要空腹吗:使用科学高效的软件开发过程

来源:百度文库 编辑:偶看新闻 时间:2024/05/05 19:36:42
使用科学高效的软件开发过程2010-05-08 09:32这是我之前写的一篇文章,一直没有最终定稿并且放上来。现在,我已经离开了Oracle,我想是时候好好总结一下在Oracle Berkeley DB团队的这三年来的所学了,所以有了下面这篇文字。 在后面的文章当中可能还会总结以下我对于Berkeley DB这个产品本身的认识和理解。

想要编写高质量的软件很不容易,因为从设计到最终定型,有太多太多容易犯错的地方,不仅仅涉及到计算机技术本身,还涉及到软件开发过程的基础设施、工作流程、人员管理等诸多因素。这么多年来,计算机工程师们总结出用于指导我们设计出高质量软件的一整套理论体系,叫做软件工程。每个计算机科班出身的中国开发者,都学过这个课程。我当时学完后的感觉就是,这是一门理论课,如此的远离实际,以至于当时大多数同学都把它和学习毛概邓论一样来学习 --- 对于一个一共没有编写超过10000行代码的大三学生来说,那些东西确实很抽象,不容易理解,更别说付诸实践。

本文不会试图覆盖如此宏大而抽象的一个理论专题,大家都学了够多的软件工程理论了。相反,我将我们组的软件开发过程做一个尽量详细的介绍,并且把其中用于软件质量保证的技术手段和工作方法特别拿出来仔细讨论。这样的软件开发过程和相关技术是经过验证有效的,经受住了实践检验的,是我们日常工作的一部分。我深深的感受到它是一种行之有效的软件开发方法,希望它能够对您有参考价值。

我工作以来逐渐意识到,也从我们Berkeley DB软件开发团队中学习到,一个软件开发团队想要开发出高质量的软件,不仅需要编码技能本身,还需要有一个科学高效的工作流程,可以称之为软件开发过程。它就好象一个流水线,精确地定义了一个软件开发团队在软件开发的各个阶段应该如何运作,完成哪些任务和目标,从而最终在流水线末端,有一个高质量的软件被生产出来。有了这个流程做保证,一个软件就不会依赖于某一个或者几个人的独门绝技,不会成为个人英雄主义的产物和牺牲品,而是可以以这个定义好的流程和其中的技术资源积累为基础,生生不息的延续下去。新的成员可以在加入这个团队后,只需要适应这个定义好的流程,学会成为其中的一分子、一个组件、就很快地进入工作状态,并且做出有价值的产出。只有这样,一个软件产品才可以生生不息,一个团队才可以持续运转不断发展,这样对团队内的每个人都有好处。

下面,我就根据我在Oracle Berkeley DB团队当中工作经验和亲身体会,讲一下我们是如何做的。 现在有很多开发方法论,比如敏捷开发(Agile Development),极限编程,等等。我想Berkeley DB团队的方法与它们有一些相似之处,却也未必精确地与任何一种方法论一致,但是它是一种有效的方法。

I. 基础设施

一个科学高效的软件开发过程需要如下的一些IT基础设施,他们使整个团队高效的协作,使每个成员高效地工作。

1. 邮件系统与问题跟踪(issue tracking)系统

一个团队最好有自己的邮件服务器,这是一个最基础的设施,假设我们的团队叫做myteam.com。每个团队成员都有@myteam.com 的邮件地址,他们之间的所有技术相关、工作相关的交流,全部通过地址为@myteam.com的邮件交流。 这一点绝大多数团队都可以做到。

人们的交流,必然是用于讨论或者解决产品相关的问题的。我们把用于解决每个问题的所有邮件,加上一个唯一的标签来标识这个问题,并把有这个标签的所有邮件,成为一个Thread或者SR(Service Request)。这个标签通常是基于一个单调增长的数字序列,比如[#12345]。每个人发出关于#12345这个问题的邮件时,务必加上[#12345]这个标签在邮件的主题上面,并且抄送到一个特殊的邮件地址,比如 support@myteam.com。如果需要讨论一个新的问题,就向support@myteam.com发送一个邮件,其主题有[NEW]标签,内含这个SR需要的各个特殊字段的名称-值pairs,比如这个问题的详细信息,产品出现了什么毛病和症状,由谁报告了这个问题,由谁负责解决;当所有讨论都已经结束,需要关闭这个SR时候,也向support@myteam.com发送一个特殊邮件,内含特殊字段的名称-值pairs,比如对整个解决过程和方法的总结,软件代码甚至文档发生了什么变化。邮件服务器内部的脚本程序通过检查邮件的主题和内容,把邮件归类整理好,以便以人们按照SR来查看关于某个问题的所有交流内容。需要查看某个SR就是用浏览器访问某个内部的特殊URL来查询。另外,还支持使用Google的内部搜索来从海量的SR当中寻找某些感兴趣的信息,以便找到相关的SR。这是我们组之前的项目管理系统,现在的新系统仍然兼容这样的功能。

比较新式的SR系统已经不再基于邮件系统,而是一个独立的Web应用程序,这样邮件系统就不是必需的了。目前我们的做法是两种方式兼容。

有了SR系统后,团队成员的交流都归类为解决各个不同的问题;而需要寻找某个问题是如何解决的,就可以查看某个SR的所有交流内容;想要寻找某个功能是如何实现的,可以用Google内部搜索,寻找相关的SR。而经理可以通过查看每个人在一段时期之内解决了那些问题,还有多少问题需要解决,从而决定每个人的工作绩效,以及了解和控制开发的进度。

有了这样的基础设施,团队成员需要遵循一些惯例以便这样的基础设施能够发挥作用。基于邮件的SR系统,务必每个工作相关的邮件必然属于某个SR,加上其标签。如果是新的问题,必须创建一个SR,然后使用得到的标签开始交流过程。少数时候,起初的交流并不针对什么问题,后来发现讨论很有价值,需要关于这个主题记录下来这些会话,就可以创建一个新的SR,然后把所有相关邮件加上SR号码后按照时间顺序forward到support@myteam.com,以便记录下来。另外还有些时候,讨论最初在IM上进行,或者口头间进行,那么发现交流值得记录的话,需要吧聊天记录forward到新创建的SR,或者把口头讨论总结出来记录到新创建的SR。那么在邮件交流过程中,要把邮件发送给谁呢? 肯定不是组内所有人,假如这样,每个人会收到很多自己的开发领域之外的内容,难免过多。所以应该发给与相关产品、技术、功能相关的开发者。

记录下来的针对各个问题的技术讨论,是一个产品极其宝贵的技术信息积累,它可以帮助我们在很长一段时间以后快速地找到某个功能的实现方式以及为什么要那样实现,其中有过什么样的考虑。这比单纯的用户文档甚至开发者文档还要全面而完整。你不仅可以知道这个功能如何实现,你还可以知道为什么要这样实现是最好的,其他可选的实现方式为什么不够好而最终被放弃,当初有过什么考虑,等等。

从Berkeley DB这个产品创建之初到现在几万个SR当中,我理解了什么叫做“冰冻三尺非一日之寒”,这些宝贵的技术积累,是一笔巨大的财富,是所有产品开发者精力和智慧的结晶。每个开发团队需要从一开始,就以这样认真的态度对待产品开发过程中的每一个问题 --- 没有一个问题是“小问题”。从那些SR当中,我常常可以看到开发者们对于一个貌似很小的问题进行的详尽讨论,只有大家完全搞清楚一个问题及其解决方案,并最终实现出来,测试通过后,才肯罢休。这样一丝不苟的态度,可以感染每一个新加入的开发者,比如我,让我们对于每一个问题不敢放过,寻根究底,以相关人员一致同意的方式彻底解决了才算完成了这个任务。



2. 代码库(code repository)

我想这个大多数团队都会使用,诸如cvs, svn, mercurial, git等版本控制工具,实现代码的版本管理和控制。任何一个认真的像样的软件产品,必然需要一个版本控制工具,还没用过它的开发者算不上一个基本合格的软件开发者。关于这点,我不想过多阐述,只提三点:

a. 为了与SR系统配合使用,我们会在提交每个patch的时候,务必提供提交者在SR系统中使用的用户ID 以及在附加信息(-m) 中提供SR的标签。这样,我们可以知道每一个patch是谁提交的,以便这个patch导致错误的时候有他负责修正;当我们查看一个SR的时候,你还可以看到某个patch确实被提交到了代码库 (code repository),并且提交时的附加信息包括那个patch也在SR系统中。

b. 每一个patch在负责这个SR的开发者写好并测试通过后,还需要给他的reviewer来检查一遍,reviewer也许会针对这个patch提出一些问题和建议,如果涉及到修改该patch的,就要改了然后再次review,直到开发者和reviewer一致同意该patch可以提交,才可以提交。这个review过程,自然也是这个SR的一部分。

c. 一个用于apply一个patch的工具是必须的,最好的应该是gnu的"patch"软件。另外,开发者必须能读懂diff文件,在开源世界里面diff文件格式是统一的,据我所知cvs,svn和mercurial产生的都是标准的diff文件,可以被gnu patch使用。我没有用过git,所以不确定,不过很容易推断,它的diff文件也是标准的。


3. 自动化测试框架

首先,任何一个软件,都需要大量的测试代码来保证质量,测试覆盖率至少要在85%以上 (使用诸如gcov的软件可以得到测试覆盖率报告,从而针对没有测到的函数和代码行增加更多的测试。)。如果你的软件是gui应用程序,测试需要人工输入怎么办?测试代码还有用吗? 是的,因为良好的gui软件,应该是分层的。界面只负责接收用户输入,和显示结果。界面在接收到用户输入后,调用实现软件功能的函数或者类;应用程序的主要代码与界面无关,可以被测试代码调用,使用批量的测试数据来测试那些类和函数。开源软件当中也有非常优秀的gui软件,你可以下载一个来看看他们是如何分层,如何测试的。一个很好的例子是开源的矢量绘图软件inkscape。

测试应该是全部自动化的,我们的做法是,首先针对软件的每一个功能点,需要有测试代码同步地被写出来,开发者可以自己写,或者与之合作的测试人员写。根据这个功能点的规范和描述,尽量彻底的考虑到各种可能情况来些测试代码和提供测试数据。所有的测试代码会被放入产品代码库,而不是用完就丢弃了。这样日积月累就会有大量的测试代码,并且所有测试经常被完全运行一遍。当有新的代码提交到代码库时,如果测试没有在运行,就会被自动启动。如果你的软件支持多平台,那么对应于每一个平台,都有机器在持续运行,所以我们可以远程登录(ssh)到这些机器上面调用测试脚本启动测试。测试结果会自动汇报到一个web服务器上面,这样开发者就可以通过浏览器定期查看测试结果,里面详细列出了在各个平台上面,哪些测试完全成功,那些出现了错误等等。这个自动化测试框架,需要大量的shell、perl、tcl脚本的支持,还涉及到一个开源的测试平台"Bitten",并且在绝大多数平台上面可以实现完全自动化,例外是Windows,因为Windows上的软件在出错时会弹出操作系统的对话框,需要手动点击,非常讨厌。这是另一个“Windows不是一个理想的服务器”的证据。有了这样的机制,我们就可以最快地发现新提交的代码的错误并且及时纠正。这样才不会在产品到达用户后才发现越来越多的bug。并且开发人员也会非常有信心地提交自己的代码,因为他可以在提交之前用测试包测试自己的patch,如果全部通过了,那么可以确定这个patch不会破坏原来的代码功能。同时要有专门测试新提交代码的测试代码,这些代码也要同时或者稍候提交到代码库。

测试的种类包括:
a. 小规模的测试,测试软件的核心功能,是整个测试包的一小部分,运行一遍需要若干个小时,甚至一天。平时代码库提交动作就会触发这类测试;
b. 完整测试:运行整个测试包,运行一遍需要三四天甚至更久,依机器性能而定。这类测试通常人工定期启动; 
c. 性能测试(gprof),用于尽可能早地发现软件性能的下降,以便更容易找到是哪些提交的代码导致了这种性能下降。性能的比较方法是横向比较:比较当前版本的性能与前几个版本的性能。运行性能测试需要独占机器,这样得到的结果才有参考价值。

使用自动化测试可以大大提高测试效率和可靠性,从而保证软件质量。
如果没有自动化测试,一切都需要人工手动操作来测试的话,那么首先测试效率很低,这样人力成本就很高。其次是人工操作可靠性很低,有些bug可能不会被发现或者发现后没有被上报或者有些测试没有被执行。让人忧虑的是,当我和一些在国内某些大型软件企业工作的同学/朋友交流的时候惊讶的发现,他们竟然基本是人工测试。但愿是他们不了解情况说错了。


4. 项目管理系统 (trac)

这是一个集成管理平台,通过它,开发者可以完成问题跟踪(issue tracking) --- 创建、关闭、浏览、搜索SR,添加内容到SR(相当于发送、回复邮件)等等;由于trac可以与大多数版本控制工具(cvs, svn, mercurial, etc)集成,开发者可以图形化地使用版本控制工具来查看代码库当中每一个文件的变更历史、查看提交的每一个patch的内容和其他信息,任何记录在code repository当中的信息都可以通过trac的界面图形化地查看。另外,测试结果也是汇报到trac系统中,这样开发者就可以在trac上面查看测试结果。这其中还涉及到Bitten的使用,具体信息见trac的官方网站。 它还有项目管理功能,比如进度管理控制、开发任务管理等等,供经理和开发者使用。另一个很重要的功能是wiki功能,开发者通过wiki功能可以创建自己的主页,并且根据需要创建各种页面,记录不同主题的信息;wiki具有搜索和标记功能,可以与问题跟踪、测试汇报、版本控制功能无缝集成。

这样,我们的trac系统就是我每天开机后上的第一个站点,我上去看我要完成的任务,它们都已SR的形式显示在我的个人wiki页面 --- 通过给wiki页面设置一条查询语句,就可以把符合条件的SR列出来。查看我负责的SR的内容,里面记录着相关工作讨论和记录,我就知道还需要做什么,另外还记录着每个SR的deadline;如果遇到常见技术问题,我就到专门的wiki页面中去找,那里有大家经常会遇到的各种问题的解决方法,每个人都可以把自己的真知灼见,经验教训和各种发现记录到某个wiki页面中供大家参考。需要查看各种测试结果时,也到trac的专门的wiki页面中去查看。




II. 工作流程

1. 需求征集

在一个版本发布后,就要开始下一个版本的需求征集阶段。市场、销售人员在长期的工作中,会从客户那里不断得到针对产品功能的反馈,产品的用户论坛(OTN)中也会有用户提出功能请求。这类信息会被收集起来,并且在这个阶段筛选、汇总出来。然后开发者可以选择有把握的或者有兴趣的其中某个或者某几个来实现。每个功能可大可小,大的到实现出来,可能需要上万甚至几万行代码,小的可能只要几百行代码。本文不论其规模,均称之为一个“功能”。

针对每个功能,开发者首先创建一个SR,并且向其中加入自己对这个功能的设计、实现方案,我们称之为一个design spec,然后相关同事共同讨论。在讨论过程中这个ddesign spec 也会被不断地更新;若干轮讨论结束后大家的意见一致并且所有想到的问题都有解决方案的话,这个design spec的approver会approve它,然后developer就以这个spec为基准来实现这个功能。 如果这个功能规模比较大的话,就需要写一个POC (Proof Of Concept),否则就可以正式开始开发了。POC是一个实现了基本功能的毛坯版本,花费的时间应该比较短,一般在一个月左右。在这个过程中,由于有了更加具体的工作,所以肯定会遇到之前没有考虑到的问题,或者发现方案的某些地方不可行,需要修改,等等。在POC基本完成后,组内相关人员根据POC遇到的新问题、新状况进行讨论,最终决定方案修正后是否可行,这个功能是否确实要认真地去实现。如果是的话,那么就进入了下一步。POC并不是一个必须的步骤,但是撰写design spec, 并与若干同事共同讨论通过spec这个步骤是必要的。


2. 测试驱动的开发过程

POC是正式的实现的出发点,它是一个毛坯。如果这块毛坯可以在这个阶段继续使用的话,当然是好事。 在POC的基础上,我们增加更多的代码,使得这个功能可以更细致、完整和优化。;否则,它至少给我们提供了很多经验和教训,也能为重新编写的正式版本提供帮助。对于规模较小的功能,可能没有POC而直接进入这一步。总之,开始了这一阶段后,就要不断地按照预先规划好的需求描述以及实现方式完成下面这个循环:
while (功能没有实现完成) {
研究功能需求,确定这一次要实现的功能点;
增加或者修改类和其方法以实现该功能点;使用lint工具静态检查代码健壮性;
增加或者修改对应的测试类、或者测试类的测试方法,确保测试覆盖率达到目标;
使用辅助工具(e.g. valgrind,gprof) 寻找bug和性能问题。
解决所有问题,借助gdb等工具。最终,测试全部成功;
提取patch,给reviewer检查,并且在若干次讨论、修改、测试确认后,取得一致同意;
提交patch到代码库。
更新相关文档; };
完整地测试全部功能,确保测试覆盖率。
性能测试与性能优化


无论如何,务必保持测试代码和功能代码的同步更新。每增加、修改一个类,或者增加、修改某个类的一个方法,就要在与之对应的测试包中增加、修改一个新的测试类,或者增加、修改对应测试类的的方法。这是我喜欢的方式,与纯粹的测试驱动理念有微小区别,后者提倡先根据这个类或者方法的功能需求,写出测试代码,然后再实现这个类、方法本身。总之,在这样一块代码及其测试代码被新加入后,要立刻运行一遍测试程序,发现其中的问题并予以改正。由于你只增加、修改了一小块代码,可能只有几十行、上百行,如果出现问题,要么直接出在它身上,要么由它造成而影响了已有代码,自然容易改正;千万不能不理会测试,没完没了地写代码,到最后发现,其中的编译错误成百上千,运行的时候漏洞百出。另外,就是要保持每一个patch在提交之前被reviewer检查通过。这样,最终功能实现完成了,代码检查也完成了,不会累计到一起把代码检查变成一个mission impossible.

测试代码最好应该是自验证的,不行的话就使用固定输入并与预期结果做比较。自验证意思是说:根据输入数据的不同,我们总是可以知道正确的输出应该是什么样的,所以我们可以在测试代码内部断言输出结果与预期结果相同,只要不相同,那么被测试功能就是有问题的。这样的好处是我们可以使用变化的输入数据,而且没有多余负担;另一种方式就是准备一份输入数据和一份预期输出结果,然后把实际输出结果与预期输出结果做比较,比如使用diff来比较两个结果文件。如果有不同,那么某些测试就失败了。这样做的缺点是不容易定位到具体的 test case, 而且没有那么灵活:必须总要先正确地计算预期输出结果。


3. 软件质量保证过程 

在开发过程中,要定期地执行这个过程。这个过程的目标就是,确保软件模块的当前功能被非常良好地实现出来了。这种确保的手段就是测试。下面讨论的手段基于GNU 工具链:gcc/gcov/gprof/gdb/valgrind/lint。

先说说代码的静态检查,也就是lint。lint能够非常严格的检查代码,指出其中潜在的语言错误用法和设计漏洞。它比编译器最高的警告级别还要严格的多,而且也确实很有效。虽然并不是所有的报告都意味着错误,但是每一条报告都需要仔细检查,确认没有问题才可以放过。而对于编译器警告,我们的做法是设置编译器使用最高警告级别,然后修正所有的警告。现在开源软件当中,有针对几乎所有主流语言的lint工具,比如c的splint。遗憾的是,针对C++的开源lint工具几乎没有,商业版本倒是有的。原因就是C++语言太过于复杂了。所以针对C++的代码,我通常就是解决编译器在最高警告级别下报告的所有警告。

现在我们开始做动态检查。首先,我们要确保我们的测试代码覆盖到了软件模块的绝大多数代码,这是其余步骤的前提,其余的步骤都只能检查在测试中被执行过的代码。

这里我们使用gcov+lcov完成任务。通过在gcc的编译、链接过程中加入特殊选项,gcc就会插入特殊的代码用于记录函数的调用和代码的执行。这样在编译链接完成后,除了目标文件被产生出来外,针对每一个代码文件,还有一个.gcno的文件被产生出来。然后,我们运行整个测试,结束后,就有针对每个代码文件的.gcda文件被产生出来,其中记录着每个函数被调用的次数以及每个文件的每一行被执行的次数。然后,我们运行gcov,或者lcov,让它们使用产生的.gcda数据文件,就可以产生文本的或者html页面格式的测试覆盖报告。从中可以看出有哪些函数被调用了多少次,哪些行被执行了多少次,那些函数没有被调用,哪些代码行没有被执行过,等等。针对没有执行到的行和没被调用的函数,我们需要增加更多的测试,以便这些代码能够被测试到。

当测试覆盖率提高到预期目标,比如85%后,我们确定:几乎所有(每一行被忽略的代码需要有足够的理由忽略它们)代码都已经被测试到了,都会被测试程序执行到。现在,我们需要运行内存检测工具valgrind。完全没有人能够写很多c/c++代码而从来不出现内存错误。所以,每个人写的c/c++代码都需要使用valgrind做检查,以确保没有任何内存使用错误。

常见的内存错误包括使用了当前不属于程序的堆内存空间、没有释放使用完毕的堆内存以及把栈内存地址当作堆内存来使用等。一个“当前属于”程序的“堆内存空间”是一个有限长度的堆中分配的内存块,我们通过调用malloc/new分配获得其起始地址(你也可以使用操作系统的堆管理API分配空间并自己管理对内存,但是应该避免这样做,以便保证可移植性),并把该地址记录在某个指针变量中,然后我们通过偏移访问这个块中的字节,使用完毕后,我们释放这块内存。释放后,它就不属于程序了,就不可以访问了。任何与这个使用流程不完全相同的使用方式,都是错误的。错误包括导致内存漏洞,或者内存地址访问越界,或者使用了一个未初始化的或者已经释放了空间的指针等,可能会在某个时刻导致程序崩溃。

不幸的是,这类错误在一次程序运行后,也许并不会出现,这是因为操作系统通常并不能严格地检查出所有的对内存访问错误。例如,如果访问越界后,访问的地址仍然在同一个操作系统内存页面上,那么操作系统通常不会发现这种越界(微软的CRT库有特殊的支持,能够更加严格地检查这类错误,但不能完全避免),所以可能你的软件在开发阶段问题不出现,但软件一见客户,问题就出现了。这就是为什么我们需要valgrind。它能够检查出所有的错误的内存访问方式,详细描述错误类型,并且通过打印stacktrace的方式让我们容易地找到调用路径和出错地点。

当修正了所有的valgrind的错误报告后,我们可以确信:我们的软件工作正常。不过,我们还有更高的要求:让我们的软件高效率地工作。为了实现这个目的,我们需要知道每一个函数的执行效率如何,在一次测试中占据了多少时间。gprof就是用来提供这种性能数据的软件。通过在gcc的编译、链接命令中加入特殊指令 (-pg),gcc会在编译出来的代码中加入特殊代码用于统计每个函数的执行时间、调用次数以及函数之间的调用关系。然后我们运行全部测试,于是就会有一些文件产生出来,这些文件送入gprof后,gprof可以产生出详细的软件性能报告。通过这个报告,我们很容易找到哪些函数效率欠佳,需要优化。

当需要调试的时候,gdb就派上用场了,有点遗憾的是gdb的纯字符界面不容易看清代码和变量等等,还好我们有GNU DDD等辅助工具,它虽然还不能与MSVC的调试功能媲美,但也很方便了。


4. 文档工具 

对于类似Berkeley DB这样的程序库来说,我们的用户仍然是开发人员,所以文档是给开发人员看的,需要为公开的类和函数提供文档,而且我们内部也需要开发文档来记录任何一个功能的设计思路等等。
虽然这一步写在了最后面,但是并不是说它是最后一步才做的事情。从上面的循环中可以看出,我们应该在开发过程中,就不断地维护文档,保持文档同步更新。这样做有很多好处,比如,在写文档的过程中,我们用人类语言描述我们的软件在做什么,怎么做的,因而容易找到之前编码时候的漏洞,可以帮助我们更加深刻地理解和理顺软件的功能和设计思路。而且此时写文档,我们的记忆很新鲜,会最准确地记录软件的设计思路、工作方式、算法和行为等等。如果到很久之后才补文档,可能我们已经把自己先前写代码时的想法忘记了一半了,不容易很快完全正确地回忆出全部想法。另一个好处就是,同步更新文档后,不会在软件开发周期的末尾将文档草草收尾。要知道,文档是整个软件的非常重要的组成部分,而不仅仅是陪衬。

针对每一个函数或者类的文档,最好可以写在代码当中。这样的好处是在更新代码文件时,可以原地同步更新文档,非常方便快捷,不会遗漏。有了doxygen的帮助,这一点变得非常方便。doxygen的侵入性很小(not intrusive),只需要在标准的代码文档中加入一些简单的标注指令,就可以产生出精美的多格式文档,包括html, pdf, ps, xml, WinHelp等格式的文档。对于类似Berkeley DB这样的应用程序库,doxygen非常有用,因为最终用户使用我们公布的API 来使用Berkeley DB,如果把API 文档直接写在那些公布的API声明处,将非常易于维护。很多优秀的程序库都在使用doxygen来标注他们的API文档,我用过的包括boost, ACE等。对于应用软件,虽然不需要用户理解我们的API,doxygen仍然是有用的:我们可以为每一个函数和类提供标注好的文档,以便用它产生精美易读的开发者文档。

另外一类文档不针对某个类或者函数,是一种一般性的参考手册。这时候就没办法使用doxygen了。在这种情况下我们使用docbook。docbook接受特殊格式的xml输入文件,可以产生出包括html, pdf,等多种格式的输出文档。这样我们只需要维护一份文档源文件,就可以产生多种格式的用户文档。这些xml输入文件不仅记录文档内容,而且控制文档的层次结构、引用关系和外观,不过并不比html复杂。


国内很多软件企业热衷于CMM认证,把CMM5当作一个神圣的金字招牌。我相信中国的软件企业一定是CMM管理机构的最大客户群了。很多人可能不知道,ORACLE就没有做过CMM认证,但是ORACLE
公司内部却有一套完整成熟的软件开发流程, 拥有世界级质量的众多优秀软件产品。认证本身不重要,拿到认证证书也不代表什么。关键是要开发团队的每一个成员都时刻严格遵循科学的软件开发流程。
如果一个团队坚持认真执行上述工作流程,正确使用其中的各类辅助工具,那么这个团队开发出优秀软件的几率将大大增加。在国外很多开源的开发团队中,都使用类似的软件开发过程,开发出很多优秀的开源软件产品。