面向?qū)ο筌浖_發(fā)和過程(三)案例實戰(zhàn)(下) ![]() |
![]() | 級別: 初級 林星, 項目經(jīng)理 2003 年 12 月 01 日 在這一章中,我們使用第一章中的分析框架對第二章中的案例進行分析。在分析之后,我們會看到,應(yīng)該如何從代碼的角度來回望過程。 規(guī)范應(yīng)當(dāng)是從簡單到復(fù)雜的,我們首先制定的規(guī)范并不復(fù)雜,只是對如何使用異常機制的一些定義。要獲得這些規(guī)范并不困難,大部分介紹異常的技術(shù)資料中都給出了很多的建議。理解并使用它們,僅此而已。 1、 對不正常的條件使用異常,盡可能準(zhǔn)確的使用預(yù)定義的異常。這一目標(biāo)來自于Effective Java中的條款39和條款42。其目的是為了能夠正確的使用異常。使用系統(tǒng)提供的異常能夠減少代碼,提高代碼的可讀性(特別是新人不需要了解自定義的異常結(jié)構(gòu))。大多數(shù)情況下,系統(tǒng)提供的異常已經(jīng)足夠用了。 2、 盡可能多的收集異常發(fā)生時的上下文信息。異常之所以比返回碼優(yōu)秀的一個原因就是它能夠?qū)㈠e誤類型化,提供比錯誤代碼多得多的信息。因此,我們實在沒理由不使用這一功能。 3、 正確的使用異常轉(zhuǎn)義,并保留原異常信息。異常轉(zhuǎn)義的目的是為了讓客戶端能夠得到易于理解的類型。我們想象一個用戶登錄的情境,假設(shè)用戶數(shù)據(jù)保存在一個文件中,當(dāng)文件中找不到用戶名的相關(guān)記錄的時候拋出一個RecordNotFoundException異常,系統(tǒng)截獲了這個異常,并將其發(fā)布給用戶,問題在于,用戶會覺得非常的奇怪,為什么會是記錄沒有找到呢?因此,建立一個IllegalUserException異常會更適合于這種情況。 4、 針對不同的抽象層次定義不同的異常。正如我們在第三點中提到的,RecordNotFoundException異常并不適合于用戶這個層次。但是,這個異常對于程序員調(diào)試代碼就很有意義了。 5、 將異常發(fā)布到合適的地方。有計算機的地方就一定有輸入和輸出。如果把異常發(fā)生時的信息收集看作輸入,那么異常的輸出是什么呢?可能是錯誤的提示信息,可能是一個顯示錯誤信息的網(wǎng)頁,可能是日志中的記錄,可能是一條短信,也可能是一封EMail。這些就是異常的輸出形式。此外,異常的輸出還需要正確的確定對象,對用戶來說,異常只要有一個友好的提醒方式就夠了,但對于管理者來說,異常需要記錄下來,或是通過異步消息進行通知。 這就是規(guī)范,你也可以把它稱為最佳實踐、建議等名詞。當(dāng)然,它還可以更加的細化,但事情總有個過程,一開始把問題弄得過于復(fù)雜未必是一件好事,你說呢?
有了規(guī)范是一回事,能否把規(guī)范運用起來則取決于人員的技能。在有一部描述清末的電影中,有這樣一個情節(jié),留學(xué)歸來的知識分子為了提高民眾的知識水平,不惜花費巨資免費發(fā)放報紙,這一舉措大受歡迎,可惜大部分的民眾都不識字,他們要報紙的原因只是這東西燒火很方便。 所以其次要解決的問題就是,大部分的程序員沒有足夠的異常處理經(jīng)方面的技能。如果程序員沒有這方面的概念,你把一本異常管理最佳實踐放在他的面前會有用嗎? 學(xué)會使用異常并不困難,困難的是如何讓程序員正確的使用異常。什么時候使用系統(tǒng)定義的異常。什么時候使用自定義的異常,自定義異常又該如何設(shè)計。這些都是程序員的技能問題。 基于這種思路,首先做的是培訓(xùn),而培訓(xùn)的目標(biāo)是讓程序員理解異常的機制,讓程序員能夠把異常運用到工作中。培訓(xùn)不等于上課,因為我們的目標(biāo)是能夠影響程序員的行為,單靠上課是無法達成目標(biāo)的,因此我們把幾種方式綜合使用。一般來說,程序員對未知的技術(shù)總是有興趣的,并且會嘗試著運用,然后就會慢慢失去興趣。這種對新技術(shù)的興趣是可以利用的,但是要在興趣過去之前將行為穩(wěn)定下來。所以我們首先做的是挑選合適的程序員,他們要么就是有使用異常的經(jīng)驗,要么就是熱衷于研究新技術(shù),這些人是比較容易說服的。這些人的作用并不是限于學(xué)習(xí),他們的主要職責(zé)是將這項技術(shù)傳播到整個團隊中。管理的本質(zhì)其實是透過人來做事,而不是自己做事,所以傳播知識的職責(zé)應(yīng)該是由一個團隊來共同承擔(dān),絕對不是一個技術(shù)主管上竄下跳的。我見過很多過于忙碌的技術(shù)主管,他們都犯了這個錯誤。 當(dāng)我們在討論會議上取得了對使用異常機制的共識之后。開始使用第二種方式-訓(xùn)練。訓(xùn)練分為兩個部分,前半部分是講述異常的機理、如何使用。這時候還不講異常的最佳實踐,因為大家還沒能夠熟練的使用異常,講述最佳實踐的效果不好。后半部分是讓原先挑選的程序員輔導(dǎo)其他程序員練習(xí)異常的使用。練習(xí)的時間大約維持了3天,我們觀察到項目開發(fā)的速度并沒有受到很大的影響,因為訓(xùn)練并沒有占用很多的時間,輔導(dǎo)的時間也是分布到項目開發(fā)中,但是我們知道,如果要在項目引入異常,一開始速度是一定會下降的。在首次訓(xùn)練完畢之后,是第二次的訓(xùn)練,這次主要是介紹異常管理的最佳實踐。有了一定的使用經(jīng)驗之后,團隊順利的接受了這部分的知識。團隊已經(jīng)開始擁有了這方面的技能了。
由于是團隊協(xié)作,因此我們考慮問題必須在團隊環(huán)境中考慮,這就象多線程,在多線程環(huán)境中考慮問題的方式和單線程是截然不同的,任何問題都有基本的解決思路,團隊協(xié)作中主要的問題包括溝通、信息傳遞、協(xié)作、計劃等問題。所以我們解決問題的思路也是先定義出問題和要達到的目標(biāo),然后再來考慮方法。 我們認為,異常在組織中的最大作用是它清晰的定義了類開發(fā)者和類調(diào)用者之間的契約關(guān)系。所以,我們希望類的開發(fā)者清楚的說明一個方法在什么條件下會出現(xiàn)什么樣的異常,類調(diào)用者則需要保證他的調(diào)用對異常有明確、完整的處理。 這個時候,我們需要過程的幫助了。
為了實現(xiàn)組織一節(jié)中定義的目標(biāo),我們對過程進行修訂: 1) 在設(shè)計類時,需要同時設(shè)計異常類,并編寫異常類的說明文檔,通過復(fù)審。 2) 在給出類和方法的說明時,需要給出異常的定義和觸發(fā)的條件。 3) 在代碼中使用異常,而不是返回值。 4) 對輸入?yún)?shù)進行判定,使用參數(shù)不正確異常和空指針異常。 5) 測試所有異常。 6) 方法調(diào)用時,如果方法中有異常,必須使用try塊進行異常處理。 7) 異常需要通過通用的框架發(fā)布。 1、2、5三點屬于設(shè)計的內(nèi)容,3、4、6、7四點屬于編碼的內(nèi)容。 注意到了嗎?這種過程的定義是非常具體的,準(zhǔn)確的說這類似于作業(yè)流程。很多軟件組織都沒有做這方面的工作。抽象的制度是沒有意義的,比如這么一條,嚴謹?shù)淖龊密浖y試。什么叫嚴謹?軟件測試的基本原理就是你不可能測試所有的情況,那么,嚴謹?shù)降资菧y試到什么地步,沒人知道。而我們這里的定義則不一樣,方法中拋出的異常都需要測試。非常的明確,如果你的代碼中拋出了3個異常,那么你就需要至少三個測試方法來測試它們。 當(dāng)然,其實這個過程制度還是不完整的,我們拿第4條來說,它就有幾個問題:首沒有指明兩種異常的使用環(huán)境;沒有規(guī)定異常如何收集上下文環(huán)境。但是,我們認為,制度性的規(guī)章不必要定義的如此復(fù)雜,你見過法律規(guī)定拿匕首殺人定什么罪,拿刀子殺人定什么罪嗎?你可能會問,剛才你說制度要定義的細一些,現(xiàn)在又說不要定義的這么細,這不是自相矛盾嗎。不,任何問題的解決方法都是在不同的因素間進行權(quán)衡,而我們是在制度的可操作性和抽象性之間權(quán)衡。這也是敏捷思維的一部分。團隊中知識的傳遞決不是靠一紙規(guī)章就能夠完成的。那么應(yīng)該怎么做?兩種方法,一個是靠口頭講述使用方法,另一個是通過我們原先指定的"輔導(dǎo)員們"。 但在這時候,新的問題又產(chǎn)生了。類設(shè)計者如何把異常告訴類使用者呢,他們?nèi)绾巫龅降?條呢?,第7條中的通用的框架又該如何設(shè)計呢?這時候我們必須求助于工具。
我們先解決第一個問題。這個問題的根本在于,類設(shè)計者如何編寫異常文檔。在Java語言中,我們可以很容易的使用語言本身的功能和JavaDoc工具來解決這個問題。但是在其它的語言中,我們可能需要第三方的工具,甚至自己編寫工具。 第二個問題在Java中同樣有比較好的解決思路。Log4J是Apache組織下的一個優(yōu)秀的處理日志的開放式框架。使用它能夠很方便的將異常發(fā)布到各種途徑中,例如Console、Windows日志、郵件、文本。無疑這正是我們需要的。至于其它的語言,那就只能靠各位看官發(fā)揮自己的能力了。
我不得不說這是最神秘,最難處理的一點。更糟糕的是,我發(fā)現(xiàn)很難把這個問題描述清楚。所幸的是,有一件事情讓我印象深刻: 一次程序員A和程序員B為了一件事情爭論。B編寫了用戶注冊的后端控件,A負責(zé)調(diào)用。A的代碼大致上是這樣的:
這樣的代碼看起來似乎沒有什么問題,但是實際上卻出錯了,問題的現(xiàn)象是在測試重復(fù)用戶的時候,沒有任何現(xiàn)象發(fā)生。在debug的時候,B告訴A,其實對于重復(fù)用戶的設(shè)計是通過返回值的,返回值非0表示有重復(fù)用戶。所以正確的調(diào)用代碼應(yīng)該是這樣的:
你認為是哪一位程序員犯錯了呢?A有錯嗎?他的調(diào)用方式其實并沒有錯誤,而且,他也并不知道B的設(shè)計思路。那么是B的錯嗎?B的異常處理思路也沒有什么問題,他把異常用在非常的條件中,例如數(shù)據(jù)庫連接失敗。對于用戶重復(fù)這樣的分支,應(yīng)該看作是一種正常的返回。那么是過程的問題了,至少B應(yīng)該提供完整的類使用文檔。話雖如此,但是一個軟件組織的資源畢竟是有限的,Sun公司能夠為他的Java API提供詳盡的文檔,你認為有多少公司可以做到呢?而且在團隊內(nèi)部仍然采用這種高成本的溝通方式,其實是不太好的。 我想來想去,這件事情應(yīng)該屬于個性的范疇。任何一個人都有自己的背景和性格。你永遠無法指望大家能夠?qū)懗鲆粯拥拇a。作為類的編寫者B,他無法知道A將會以什么樣的方式來調(diào)用他的代碼,與此對應(yīng)的,類的使用者A也不清楚B編寫的代碼的細節(jié),因為根據(jù)類的設(shè)計原則,類的實現(xiàn)細節(jié)不應(yīng)該透露給外部。這中間出現(xiàn)錯配也是正常的。 這個問題還是有辦法解決的-為代碼定義統(tǒng)一的風(fēng)格,也就是固定一種編碼風(fēng)格。在技術(shù)實現(xiàn)上,我比較傾向于程序員A,因為第二種代碼要比第一種代碼復(fù)雜和難讀。一個優(yōu)秀的類設(shè)計人員應(yīng)該在簡化客戶端調(diào)用上下功夫。在代碼中包含了過多的路徑導(dǎo)致類的測試和使用變得困難。當(dāng)然,我在很多資料上也看到了很多不同的見解,但不管是哪一種見解,最佳的做法是在組織內(nèi)統(tǒng)一做法。所以在我們的組織中,都只使用兩種序列,要么正常返回,要么發(fā)生有類型異常。 但這是否解決了問題呢?這個問題可能是解決了,但還會有很多類似的問題。所幸的是,這個錯誤最終是被發(fā)現(xiàn)了,可能軟件中還存在大量未被發(fā)現(xiàn)的錯誤,這些才是最可怕的。其實這個問題的根源在于,程序員之間的溝通不夠。而要解決這個問題,我們認為,只能從文化上去想辦法,提倡溝通,提倡協(xié)作。而我們的分析框架中并沒有文化這一項。理由是,文化的主題超出了我們討論的范疇。《創(chuàng)建軟件工程文化》一書是一本討論文化的優(yōu)秀書籍,如果大家有興趣,可以進一步的閱讀。 所以,這里我們可以知道一個道理,軟件工程、項目管理其實并沒有什么非常難的地方,我們看很多書把很多問題都提到純理論的高度,反而給讀者造成了困擾。很多讀者的來信中,問到一個同樣的問題,我希望在我的團隊中引入軟件工程,有些人說從需求管理開始比較好,有些人說從配置管理開始比較好,到底應(yīng)該從哪里開始呢?就拿這個例子來說,為什么我們對一個異常管理如此的關(guān)心呢?為什么不做需求管理,或是上CMM呢?那該多酷啊!問題就在這里,軟件開發(fā)是為了給客戶帶來價值,就像UMLChina的名言,軟件以用為本,而不是以酷來衡量的。異常管理的最終目的是為了提高軟件質(zhì)量,這是立竿見影的效果,能夠給老板、給同事以信心。需求管理和CMM當(dāng)然也可以達到這種效果,問題是,異常管理幾乎不需要多少的成本,卻能夠達到直接的效果,這種好事,為什么不做? 事情到了這一步,程序員的行為已經(jīng)開始慢慢規(guī)范了。注意,我們先激發(fā)大家的興趣,再使用制度來規(guī)范行為。但是,這里還有一個問題,我們之前提到,使用新技術(shù)一定會在短期造成生產(chǎn)力的降低。好的,現(xiàn)在我們確實發(fā)現(xiàn)生產(chǎn)力降低了,因為程序員必須思考什么時候使用異常,如何使用,并編寫文檔和測試。所以我們在之前已經(jīng)做了一件事,就是取得老板的支持,這一點至關(guān)重要。這也許也是屬于文化的范疇吧。 好了,軟件過程是需要不斷的改進的,我們的案例討論也體現(xiàn)了這一點,這個案例還可以討論很多,但是處理的思路基本一致。找出可能存在的問題,思考相應(yīng)的對策來規(guī)范行為,再發(fā)現(xiàn)問題,再規(guī)范行為。這樣形成了一個不斷循環(huán)不斷進步的過程。 |