使用科學高效的軟件開發過程

這是我之前寫的一篇文章,一直沒有最終定稿並且放上來。現在,我已經離開了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]這個標籤在郵件的主題上面,並且抄送到一個特殊的郵件地址,比如 [email protected]。如果需要討論一個新的問題,就向[email protected]發送一個郵件,其主題有[NEW]標籤,內 含這個SR需要的各個特殊字段的名稱-值pairs,比如這個問題的詳細信息,產品出現了什麼毛病和症狀,由誰報告了這個問題,由誰負責解決;當所有討論 都已經結束,需要關閉這個SR時候,也向[email protected]發送一個特殊郵件,內含特殊字段的名稱-值pairs,比如對整個解決過程 和方法的總結,軟件代碼甚至文檔發生了什麼變化。郵件服務器內部的腳本程序通過檢查郵件的主題和內容,把郵件歸類整理好,以便以人們按照SR來查看關於某 個問題的所有交流內容。需要查看某個SR就是用瀏覽器訪問某個內部的特殊URL來查詢。另外,還支持使用Google的內部搜索來從海量的SR當中尋找某 些感興趣的信息,以便找到相關的SR。這是我們組之前的項目管理系統,現在的新系統仍然兼容這樣的功能。

比較新式的SR系統已經不再基於郵件系統,而是一個獨立的Web應用程序,這樣郵件系統就不是必需的了。目前我們的做法是兩種方式兼容。

有了SR系統後,團隊成員的交流都歸類爲解決各個不同的問題;而需要尋找某個問題是如何解決的,就可以查看某個SR的所有交流內容;想要尋找某個功能是如 何實現的,可以用Google內部搜索,尋找相關的SR。而經理可以通過查看每個人在一段時期之內解決了那些問題,還有多少問題需要解決,從而決定每個人 的工作績效,以及瞭解和控制開發的進度。

有了這樣的基礎設施,團隊成員需要遵循一些慣例以便這樣的基礎設施能夠發揮作用。基於郵件的SR系統,務必每個工作相關的郵件必然屬於某個SR,加上其標 籤。如果是新的問題,必須創建一個SR,然後使用得到的標籤開始交流過程。少數時候,起初的交流並不針對什麼問題,後來發現討論很有價值,需要關於這個主 題記錄下來這些會話,就可以創建一個新的SR,然後把所有相關郵件加上SR號碼後按照時間順序forward到[email protected],以 便記錄下來。另外還有些時候,討論最初在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
公司內部卻有一套完整成熟的軟件開發流程, 擁有世界級質量的衆多優秀軟件產品。認證本身不重要,拿到認證證書也不代表什麼。關鍵是要開發團隊的每一個成員都時刻嚴格遵循科學的軟件開發流程。
如果一個團隊堅持認真執行上述工作流程,正確使用其中的各類輔助工具,那麼這個團隊開發出優秀軟件的機率將大大增加。在國外很多開源的開發團隊中,都使用類似的軟件開發過程,開發出很多優秀的開源軟件產品。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章