至今,我仍能記起當我頓悟并最終產(chǎn)生下面文章時(shí)所在的地方。那是1986年的夏天,我在加利福尼亞中國湖海軍武器中心擔任臨時(shí)顧問(wèn)。在這期間,我有幸參加了一個(gè)關(guān)于A(yíng)da的研討會(huì )。討論當中,有一位聽(tīng)眾提出了一個(gè)具有代表性的問(wèn)題,“軟件開(kāi)發(fā)者是工程師嗎?”我不記得當時(shí)的回答,但是我卻記得當時(shí)并沒(méi)有真正解答這個(gè)問(wèn)題。于是,我就退出討論,開(kāi)始思考我會(huì )怎樣回答這樣一個(gè)問(wèn)題?,F在,我無(wú)法肯定當時(shí)我為什么會(huì )記起幾乎10年前曾經(jīng)在Datamation雜志上閱讀過(guò)的一篇論文,不過(guò)促使我記起的應該是后續討論中的某些東西。這篇論文闡述了工程師為什么必須是好的作家(我記得該論文談?wù)摼褪沁@個(gè)問(wèn)題——好久沒(méi)有看了),但是我從該論文中得到的關(guān)鍵一點(diǎn)是:作者認為工程過(guò)程的最終結果是文檔。換句話(huà)說(shuō),工程師生產(chǎn)的是文檔,不是實(shí)物。其他人根據這些文檔去制造實(shí)物。于是,我就在困惑中提出了一個(gè)問(wèn)題,“除了軟件項目正常產(chǎn)生的所有文檔以外,還有可以被認為是真正的工程文檔的東西嗎?”我給出的回答是,“是的,有這樣的文檔存在,并且只有一份——源代碼。”
把源代碼看作是一份工程文檔——設計——完全顛覆了我對自己所選擇的職業(yè)的看法。它改變了我看待一切事情的方式。此外,我對它思考的越多,我就越覺(jué)得它闡明了軟件項目常常遇到的眾多問(wèn)題。更確切地說(shuō),我覺(jué)得大多數人不理解這個(gè)不同的看法,或者有意拒絕它這樣一個(gè)事實(shí),就足以說(shuō)明很多問(wèn)題。幾年后,我終于有機會(huì )把我的觀(guān)點(diǎn)公開(kāi)發(fā)表。C++ Journal中的一篇有關(guān)軟件設計的論文促使我給編輯寫(xiě)了一封關(guān)于這個(gè)主題的信。經(jīng)過(guò)幾封書(shū)信交換后,編輯Livleen Singh同意把我關(guān)于這個(gè)主題的想法發(fā)表為一篇論文。下面就是這篇文章。
——Jack Reecves, December,22,2001
什么是軟件設計?
Jack W.Reeves, 1992
面向對象技術(shù),特別是C++,似乎給軟件界帶來(lái)了不小的震動(dòng)。出現了大量的論文和書(shū)籍去描述如何應用這項新技術(shù)??偟膩?lái)說(shuō),那些關(guān)于面向對象技術(shù)是否只是一個(gè)騙局的問(wèn)題已經(jīng)被那些關(guān)于如何付出最小的努力即可獲得收益的問(wèn)題所替代。面向對象技術(shù)出現已經(jīng)有一段時(shí)間了,但是這種爆炸式的流行卻似乎有點(diǎn)不尋常。人們?yōu)楹螘?huì )突然關(guān)注它呢?對于這個(gè)問(wèn)題,人們給出了各種各樣的解釋。事實(shí)上,很可能就沒(méi)有單一的原因。也許,把多種因素的結合起來(lái)才能最終取得突破,并且這項工作正在進(jìn)展之中。盡管如此,在軟件革命的這個(gè)最新階段中,C++本身看起來(lái)似乎成為了一個(gè)主要因素。同樣,對于這個(gè)問(wèn)題,很可能也存在很多種理由,不過(guò)我想從一個(gè)稍微不同的視角給出一個(gè)答案:C++之所以變得流行,是因為它使軟件設計變得更容易的同時(shí),也使編程變得更容易。
雖然這個(gè)解釋好像有點(diǎn)奇特,但是它卻是深思熟慮的結果。在這篇論文中,我就是想要關(guān)注一下編程和程序設計之間的關(guān)系。近10年來(lái),我一直覺(jué)得整個(gè)軟件行業(yè)都沒(méi)有覺(jué)察到做出一個(gè)軟件設計和什么是真正的軟件設計之間的一個(gè)微妙的不同點(diǎn)。只要看到了這一點(diǎn),我認為我們就可以從C++增長(cháng)的流行趨勢中,學(xué)到關(guān)于如何才能成為更好的軟件工程師的意義深遠的知識。這個(gè)知識就是,編程不是構建軟件,而是設計軟件。
幾年前,我參見(jiàn)了一個(gè)討論會(huì ),其中討論到軟件開(kāi)發(fā)是否是一門(mén)工程學(xué)科的問(wèn)題。雖然我不記得了討論結果,但是我卻記得它是如何促使我認識到:軟件業(yè)已經(jīng)做出了一些錯誤的和硬件工程的比較,而忽視了一些絕對正確的對比。其實(shí),我認為我們不是軟件工程師,因為我們沒(méi)有認識到什么才是真正的軟件設計?,F在,我對這一點(diǎn)更是確信無(wú)疑。
任何工程活動(dòng)的最終目標都是某些類(lèi)型的文檔。當設計工作完成時(shí),設計文檔就被轉交給制造團隊。該團隊是一個(gè)和設計團隊完全不同的群體,并且其技能也和設計團隊完全不同。如果設計文檔正確地描繪了一個(gè)完整的設計,那么制造團隊就可以著(zhù)手構建產(chǎn)品。事實(shí)上,他們可以著(zhù)手構建該產(chǎn)品的許多實(shí)物,完全無(wú)需設計者的任何進(jìn)一步的介入。在按照我的理解方式審查了軟件開(kāi)發(fā)的生命周期后,我得出一個(gè)結論:實(shí)際上滿(mǎn)足工程設計標準的惟一軟件文檔,就是源代碼清單。
對于這個(gè)觀(guān)點(diǎn),人們進(jìn)行了很多的爭論,無(wú)論是贊成的還是反對的都足以寫(xiě)成無(wú)數的論文。本文假定最終的源代碼就是真正的軟件設計,然后仔細研究了該假定帶來(lái)的一些結果。我可能無(wú)法證明這個(gè)觀(guān)點(diǎn)是正確的,但是我希望證明:它確實(shí)解釋了軟件行業(yè)中一些已經(jīng)觀(guān)察到的事實(shí),包括C++的流行。
在把代碼看作是軟件設計所帶來(lái)的結果中,有一個(gè)結果完全蓋過(guò)了所有其他的結果。它非常重要并且非常明顯,也正因為如此,對于大多數軟件機構來(lái)說(shuō),它完全是一個(gè)盲點(diǎn)。這個(gè)結果就是:軟件的構建是廉價(jià)的。它根本就不具有昂貴的資格;它非常的廉價(jià),幾乎就是免費的。如果源代碼是軟件設計,那么實(shí)際的軟件構建就是由編譯器和連接器完成的。我們常常把編譯和連接一個(gè)完整的軟件系統的過(guò)程稱(chēng)為“進(jìn)行一次構建”。在軟件構建設備上所進(jìn)行的主要投資是很少的——實(shí)際需要的只有一臺計算機、一個(gè)編輯器、一個(gè)編譯器以及一個(gè)連接器。一旦具有了一個(gè)構建環(huán)境,那么實(shí)際的軟件構建只需花費少許的時(shí)間。編譯50 000行的C++程序也許會(huì )花費很長(cháng)的時(shí)間,但是構建一個(gè)具有和50 000行C++程序同樣設計復雜性的硬件系統要花費多長(cháng)的時(shí)間呢?
把源代碼看作是軟件設計的另外一個(gè)結果是,軟件設計相對易于創(chuàng )作,至少在機械意義上如此。通常,編寫(xiě)(也就是設計)一個(gè)具有代表性的軟件模塊(50至100行代碼)只需花費幾天的時(shí)間(對它進(jìn)行完全的調試是另外一個(gè)議題,稍后會(huì )對它進(jìn)行更多的討論)。我很想問(wèn)一下,是否還有任何其他的學(xué)科可以在如此短的時(shí)間內,產(chǎn)生出和軟件具有同樣復雜性的設計來(lái),不過(guò),首先我們必須要弄清出如何來(lái)度量和比較復雜性。然而,有一點(diǎn)是明顯的,那就是軟件設計可以 極為迅速地變得非常龐大。
假設軟件設計相對易于創(chuàng )作,并且在本質(zhì)上構建起來(lái)也沒(méi)有什么代價(jià),一個(gè)不令人吃驚的發(fā)現是,軟件設計往往是難以置信的龐大和復雜。這看起來(lái)似乎很明顯,但是問(wèn)題的重要性卻常常被忽視。學(xué)校中的項目通常具有數千行的代碼。具有10 000行代碼(設計)的軟件產(chǎn)品被它們的設計者丟棄的情況也是有的。我們早就不再關(guān)注于簡(jiǎn)單的軟件。典型的商業(yè)軟件的設計都是由數十萬(wàn)行代碼組成的。許多軟件設計達到了上百萬(wàn)行代碼。另外,軟件設計幾乎總是在不斷地演化。雖然當前的設計可能只有幾千行代碼,但是在產(chǎn)品的生命期中,實(shí)際上可能要編寫(xiě)許多倍的代碼。
盡管確實(shí)存在一些硬件設計,它們看起來(lái)似乎和軟件設計一樣復雜,但是請注意兩個(gè)有關(guān)現代硬件的事實(shí)。第一,復雜的硬件工程成果未必總是沒(méi)有錯誤的,在這一點(diǎn)上,它不存在像軟件那樣讓我們相信的評判標準。多數的微處理器在發(fā)售時(shí)都具有一些邏輯錯誤:橋梁坍塌,大壩破裂,飛機失事以及數以千計的汽車(chē)和其他消費品被召回——所有的這些我們都記憶猶新,所有的這些都是設計錯誤的結果。第二,復雜的硬件設計具有與之對應的復雜、昂貴的構建階段。結果,制造這種系統所需的能力限制了真正能夠生產(chǎn)復雜硬件設計公司的數目。對于軟件來(lái)說(shuō),沒(méi)有這種限制。目前,已經(jīng)有數以百計的軟件機構和數以千計的非常復雜的軟件系統存在,并且數量以及復雜性每天都在增長(cháng)。這意味著(zhù)軟件行業(yè)不可能通過(guò)仿效硬件開(kāi)發(fā)者找到針對自身問(wèn)題的解決辦法。倘若一定要說(shuō)出有什么相同之處的話(huà),那就是,當CAD和CAM可以做到幫助硬件設計者創(chuàng )建越來(lái)越復雜的設計時(shí),硬件工程才會(huì )變得和軟件開(kāi)發(fā)越來(lái)越像。
設計軟件是一種管理復雜性的活動(dòng)。復雜性存在于軟件設計本身之中,存在于公司的軟件機構之中,也存在于整個(gè)軟件行業(yè)之中。軟件設計和系統設計非常相似。它可以跨越多種技術(shù)并且常常涉及多個(gè)學(xué)科分支。軟件的規格說(shuō)明往往不固定、經(jīng)??焖僮兓?,這種變化常常在正進(jìn)行軟件設計時(shí)發(fā)生。同樣,軟件開(kāi)發(fā)團隊也往往不固定,常常在設計過(guò)程的中間發(fā)生變化。在許多方面,軟件都要比硬件更像復雜的社會(huì )或者有機系統。所有這些都使得軟件設計成為了一個(gè)困難的并且易出錯的過(guò)程。雖然所有這些都不是創(chuàng )造性的想法,但是在軟件工程革命開(kāi)始將近30年后的今天,和其他工程行業(yè)相比,軟件開(kāi)發(fā)看起來(lái)仍然像是一種未受過(guò)訓練(undisciplined)的技藝。
一般的看法認為,當真正的工程師完成了一個(gè)設計,不管該設計有多么復雜,他們都非常確信該設計是可以工作的。他們也非常確信該設計可以使用公認的技術(shù)建造出來(lái)。為了做到這一點(diǎn),硬件工程師花費了大量的時(shí)間去驗證和改進(jìn)他們的設計。例如,請考慮一個(gè)橋梁設計。在這樣一個(gè)設計實(shí)際建造之前,工程師會(huì )進(jìn)行結構分析——他們建立計算機模型并進(jìn)行仿真,他們建立比例模型并在風(fēng)洞中或者用其他一些方法進(jìn)行測試。簡(jiǎn)而言之,在建造前,設計者會(huì )使用他們能夠想到的一切方法來(lái)證實(shí)設計是正確的。對于一架新型客機的設計來(lái)說(shuō),情況甚至更加嚴重;必須要構建出和原物同尺寸的原型,并且必須要進(jìn)行飛行測試來(lái)驗證設計中的種種預計。
對于大多數人來(lái)說(shuō),軟件中明顯不存在和硬件設計同樣嚴格的工程。然而,如果我們把源代碼看做是設計,那么就會(huì )發(fā)現軟件工程師實(shí)際上對他們的設計做了大量的驗證和改進(jìn)。軟件工程師不把這稱(chēng)為工程,而稱(chēng)它為測試和調試。大多數人不把測試和調試看作是真正的“工程”——在軟件行業(yè)中肯定沒(méi)有被看作是。造成這種看法的原因,更多的是因為軟件行業(yè)拒絕把代碼看作設計,而不是任何實(shí)際的工程差別。事實(shí)上,試驗模型、原型以及電路試驗板已經(jīng)成為其他工程學(xué)科公認的組成部分。軟件設計者之所以不具有或者沒(méi)有使用更多的正規方法來(lái)驗證他們的設計,是因為軟件構建周期的簡(jiǎn)單經(jīng)濟規律。
第一個(gè)啟示:僅僅構建設計并測試它比做任何其他事情要廉價(jià)一些,也簡(jiǎn)單一些。我們不關(guān)心做了多少次構建——這些構建在時(shí)間方面的代價(jià)幾乎為零,并且如果我們丟棄了構建,那么它所使用的資源完全可以重新利用。請注意,測試并非僅僅是讓當前的設計正確,它也是改進(jìn)設計的過(guò)程的一部分。復雜系統的硬件工程師常常建立模型(或者,至少他們把設計用計算機圖形直觀(guān)地表現出來(lái))。這就使得他們獲得了對于設計的一種“感覺(jué)”,而僅僅去檢查設計是不可能獲得這種感覺(jué)的。對于軟件來(lái)說(shuō),構建這樣一個(gè)模型既不可能也無(wú)必要。我們僅僅構建產(chǎn)品本身。即使正規的軟件驗證可以和編譯器一樣自動(dòng)進(jìn)行,我們還是會(huì )去進(jìn)行構建/測試循環(huán)。因此,正規的驗證對于軟件行業(yè)來(lái)說(shuō)從來(lái)沒(méi)有太多的實(shí)際意義。
這就是現今軟件開(kāi)發(fā)過(guò)程的現實(shí)。數量不斷增長(cháng)的人和機構正在創(chuàng )建著(zhù)更加復雜的軟件設計。這些設計會(huì )被先用某些編程語(yǔ)言編寫(xiě)出來(lái),然后通過(guò)構建/測試循環(huán)進(jìn)行驗證和改進(jìn)。過(guò)程易于出錯,并且不是特別的嚴格。相當多的軟件開(kāi)發(fā)人員并不想相信這就是過(guò)程的運作方式,也正因為這一點(diǎn),使問(wèn)題變得更加復雜。
當前大多數的軟件過(guò)程都試圖把軟件設計的不同階段分離到不同的類(lèi)別中。必須要在頂層的設計完成并且凍結后,才能開(kāi)始編碼。測試和調試只對清除建造錯誤是必要的。程序員處在中間位置,他們是軟件行業(yè)的建造工人。許多人認為,如果我們可以讓程序員不再進(jìn)行“隨意的編碼(hacking)”并且按照交給他們的設計去進(jìn)行構建(還要在過(guò)程中,犯更少的錯誤),那么軟件開(kāi)發(fā)就可以變得成熟,從而成為一門(mén)真正的工程學(xué)科。但是,只要過(guò)程忽視了工程和經(jīng)濟學(xué)事實(shí),這就不可能發(fā)生。
例如,任何一個(gè)現代行業(yè)都無(wú)法忍受在其制造過(guò)程中出現超過(guò)100%的返工率。如果一個(gè)建造工人常常不能在第一次就構建正確,那么不久他就會(huì )失業(yè)。但是在軟件業(yè)中,即使最小的一塊代碼,在測試和調試期間,也很可能會(huì )被修正或者完全重寫(xiě)。在一個(gè)創(chuàng )造性的過(guò)程中(比如:設計),我們認可這種改進(jìn)不是制造過(guò)程的一部分。沒(méi)有人會(huì )期望工程師第一次就創(chuàng )建出完美的設計。即使她做到了,仍然必須讓它經(jīng)受改進(jìn)過(guò)程,目的就是為了證明它是完美的。
即使我們從日本的管理方法中沒(méi)有學(xué)到任何東西,我們也應該知道由于在過(guò)程中犯錯誤而去責備工人是無(wú)益于提高生產(chǎn)率的。我們不應該不斷地強迫軟件開(kāi)發(fā)去符合不正確的過(guò)程模型,相反,我們需要去改進(jìn)過(guò)程,使之有助于而不是阻礙產(chǎn)生更好的軟件。這就是“軟件工程”的石蕊測試。工程是關(guān)于你如何實(shí)施過(guò)程的,而不是關(guān)于是否需要一個(gè)CAD系統來(lái)產(chǎn)生最終的設計文檔。
關(guān)于軟件開(kāi)發(fā)有一個(gè)壓倒性的問(wèn)題,那就是一切都是設計過(guò)程的一部分。編碼是設計,測試和調試是設計的一部分,并且我們通常認為的設計仍然是設計的一部分。雖然軟件構建起來(lái)很廉價(jià),但是設計起來(lái)卻是難以置信的昂貴。軟件非常的復雜,具有眾多不同方面的設計內容以及它們所導致的設計考慮。問(wèn)題在于,所有不同方面的內容是相互關(guān)連的(就像硬件工程中的一樣)。我們希望頂層設計者可以忽視模塊算法設計的細節。同樣,我們希望程序員在設計模塊內部算法時(shí)不必考慮頂層設計問(wèn)題。糟糕的是,一個(gè)設計層面中的問(wèn)題侵入到了其他層面之中。對于整個(gè)軟件系統的成功來(lái)說(shuō),為一個(gè)特定模塊選擇算法可能和任何一個(gè)更高層次的設計問(wèn)題同樣重要。在軟件設計的不同方面內容中,不存在重要性的等級。最低層模塊中的一個(gè)不正確設計可能和最高層中的錯誤一樣致命。軟件設計必須在所有的方面都是完整和正確的,否則,構建于該設計基礎之上的所有軟件都會(huì )是錯誤的。
為了管理復雜性,軟件被分層設計。當程序員在考慮一個(gè)模塊的詳細設計時(shí),可能還有數以百計的其他模塊以及數以千計的細節,他不可能同時(shí)顧及。例如,在軟件設計中,有一些重要方面的內容不是完全屬于數據結構和算法的范疇。在理想情況下,程序員不應該在設計代碼時(shí)還得去考慮設計的這些其他方面的內容。
但是,設計并不是以這種方式工作的,并且原因也開(kāi)始變得明朗。軟件設計只有在其被編寫(xiě)和測試后才算完成。測試是設計驗證和改進(jìn)過(guò)程的基礎部分。高層結構的設計不是完整的軟件設計;它只是細節設計的一個(gè)結構框架。在嚴格地驗證高層設計方面,我們的能力是非常有限的。詳細設計最終會(huì )對高層設計造成的影響至少和其他的因素一樣多(或者應該允許這種影響)。對設計的各個(gè)方面進(jìn)行改進(jìn),是一個(gè)應該貫穿整個(gè)設計周期的過(guò)程。如果設計的任何一個(gè)方面內容被凍結在改進(jìn)過(guò)程之外,那么對于最終設計將會(huì )是糟糕的或者甚至無(wú)法工作這一點(diǎn),就不會(huì )覺(jué)得奇怪了。
如果高層的軟件設計可以成為一個(gè)更加嚴格的工程過(guò)程,那該有多好呀,但是軟件系統的真實(shí)情況不是嚴格的。軟件非常的復雜,它依賴(lài)于太多的其他東西?;蛟S,某些硬件沒(méi)有按照設計者認為的那樣工作,或者一個(gè)庫例程具有一個(gè)文檔中沒(méi)有說(shuō)明的限制。每一個(gè)軟件項目遲早都會(huì )遇到這些種類(lèi)的問(wèn)題。這些種類(lèi)的問(wèn)題會(huì )在測試期間被發(fā)現(如果我們的測試工作做得好的話(huà)),之所以如此是因為沒(méi)有辦法在早期就發(fā)現它們。當它們被發(fā)現時(shí),就迫使對設計進(jìn)行更改。如果我們幸運,那么對設計的更改是局部的。時(shí)常,更改會(huì )波及到整個(gè)軟件設計中的一些重要部分(莫非定律)。當受到影響的設計的一部分由于某種原因不能更改時(shí),那么為了能夠適應影響,設計的其他部分就必須得遭到破壞。這通常導致的結果就是管理者所認為的“隨意編碼”,但是這就是軟件開(kāi)發(fā)的現實(shí)。
例如,在我最近工作的一個(gè)項目中,發(fā)現了模塊A的內部結構和另一個(gè)模塊B之間的一個(gè)時(shí)序依賴(lài)關(guān)系。糟糕的是,模塊A的內部結構隱藏在一個(gè)抽象體的后面,而該抽象體不允許以任何方法把對模塊B的調用合入到它的正確調用序列中。當問(wèn)題被發(fā)現時(shí),當然已經(jīng)錯過(guò)了更改A的抽象體的時(shí)機。正如所料,所發(fā)生的就是把一個(gè)日益增長(cháng)的復雜的“修正”集應用到A的內部設計上。在我們還沒(méi)有安裝完版本1時(shí),就普遍感覺(jué)到設計正在衰退。每一個(gè)新的修正很可能都會(huì )破壞一些老的修正。這是一個(gè)正規的軟件開(kāi)發(fā)項目。最后,我和我的同事決定對設計進(jìn)行更改,但是為了得到管理層的同意,我們不得不自愿無(wú)償加班。
在任何一般規模的軟件項目中,肯定會(huì )出現像這樣的問(wèn)題,盡管人們使用了各種方法來(lái)防止它的出現,但是仍然會(huì )忽視一些重要的細節。這就是工藝和工程之間的區別。如果經(jīng)驗可以把我們引向正確的方向,這就是工藝。如果經(jīng)驗只會(huì )把我們帶入未知的領(lǐng)域,然后我們必須使用一開(kāi)始所使用的方法并通過(guò)一個(gè)受控的改進(jìn)過(guò)程把它變得更好,這就是工程。
我們來(lái)看一下只是作為其中很小一點(diǎn)的內容,所有的程序員都知道,在編碼之后而不是之前編寫(xiě)軟件設計文檔會(huì )產(chǎn)生更加準確的文檔?,F在,原因是顯而易見(jiàn)的。用代碼來(lái)表現的最終設計是惟一一個(gè)在構建/測試循環(huán)期間被改進(jìn)的東西。在這個(gè)循環(huán)期間,初始設計保持不變的可能性和模塊的數量以及項目中程序員的數量成反比。它很快就會(huì )變得毫無(wú)價(jià)值。
在軟件工程中,我們非常需要在各個(gè)層次都優(yōu)秀的設計。我們特別需要優(yōu)秀的頂層設計。初期的設計越好,詳細設計就會(huì )越容易。設計者應該使用任何可以提供幫助的東西。結構圖表、Booch 圖、狀態(tài)表、PDL等等——如果它能夠提供幫助,就去使用它。但是,我們必須記住,這些工具和符號都不是軟件設計。最后,我們必須創(chuàng )建真正的軟件設計,并且是使用某種編程語(yǔ)言完成的。因此,當我們得出設計時(shí),我們不應該害怕對它們進(jìn)行編碼。在必要時(shí),我們必須應該樂(lè )于去改進(jìn)它們。
至今,還沒(méi)有任何設計符號可以同時(shí)適用于頂層設計和詳細設計。設計最終會(huì )表現為以某種編程語(yǔ)言編寫(xiě)的代碼。這意味著(zhù)在詳細設計可以開(kāi)始前,頂層設計符號必須被轉換成目標編程語(yǔ)言。這個(gè)轉換步驟耗費時(shí)間并且會(huì )引入錯誤。程序員常常是對需求進(jìn)行回顧并且重新進(jìn)行頂層設計,然后根據它們的實(shí)際去進(jìn)行編碼,而不是從一個(gè)可能沒(méi)有和所選擇的編程語(yǔ)言完全映射的符號進(jìn)行轉換。這同樣也是軟件開(kāi)發(fā)的部分現實(shí)情況。
也許,如果讓設計者本人來(lái)編寫(xiě)初始代碼,而不是后來(lái)讓其他人去轉換語(yǔ)言無(wú)關(guān)的設計,就會(huì )更好一些。我們所需要的是一個(gè)適用于各個(gè)層次設計的統一符號。換句話(huà)說(shuō),我們需要一種編程語(yǔ)言,它同樣也適用于捕獲高層的設計概念。C++正好可以滿(mǎn)足這個(gè)要求。C++是一門(mén)適用于真實(shí)項目的編程語(yǔ)言,同時(shí)它也是一個(gè)非常具有表達力的軟件設計語(yǔ)言。C++允許我們直接表達關(guān)于設計組件的高層信息。這樣,就可以更容易地進(jìn)行設計,并且以后可以更容易地改進(jìn)設計。由于它具有更強大的類(lèi)型檢查機制,所以也有助于檢測到設計中的錯誤。這就產(chǎn)生了一個(gè)更加健壯的設計,實(shí)際上也是一個(gè)更好的工程化設計。
最后,軟件設計必須要用某種編程語(yǔ)言表現出來(lái),然后通過(guò)一個(gè)構建/測試循環(huán)對其進(jìn)行驗證和改進(jìn)。除此之外的任何其他主張都完全沒(méi)有用。請考慮一下都有哪些軟件開(kāi)發(fā)工具和技術(shù)得以流行。結構化編程在它的時(shí)代被認為是創(chuàng )造性的技術(shù)。Pascal使之變得流行,從而自己也變得流行。面向對象設計是新的流行技術(shù),而C++是它的核心?,F在,請考慮一下那些沒(méi)有成效的東西。CASE工具,流行嗎?是的;通用嗎?不是。結構圖表怎么樣?情況也一樣。同樣地,還有Warner-Orr圖、Booch圖、對象圖以及你能想起的一切。每一個(gè)都有自己的強項,以及惟一的一個(gè)根本弱點(diǎn)——它不是真正的軟件設計。事實(shí)上,惟一一個(gè)可以被普遍認可的軟件設計符號是PDL,而它看起來(lái)像什么呢?
這表明,在軟件業(yè)的共同潛意識中本能地知道,編程技術(shù),特別是實(shí)際開(kāi)發(fā)所使用的編程語(yǔ)言的改進(jìn)和軟件行業(yè)中任何其他東西相比,具有壓倒性的重要性。這還表明,程序員關(guān)心的是設計。當出現更加具有表達力的編程語(yǔ)言時(shí),軟件開(kāi)發(fā)者就會(huì )使用它們。
同樣,請考慮一下軟件開(kāi)發(fā)過(guò)程是如何變化的。從前,我們使用瀑布式過(guò)程?,F在,我們談?wù)摰氖锹菪介_(kāi)發(fā)和快速原型。雖然這種技術(shù)常常被認為可以“消除風(fēng)險”以及“縮短產(chǎn)品的交付時(shí)間”,但是它們事實(shí)上也只是為了在軟件的生命周期中更早地開(kāi)始編碼。這是好事。這使得構建/測試循環(huán)可以更早地開(kāi)始對設計進(jìn)行驗證和改進(jìn)。這同樣也意味著(zhù),頂層軟件設計者很有可能也會(huì )去進(jìn)行詳細設計。
正如上面所表明的,工程更多的是關(guān)于如何去實(shí)施過(guò)程的,而不是關(guān)于最終產(chǎn)品看起來(lái)的像什么。處在軟件行業(yè)中的我們,已經(jīng)接近工程師的標準,但是我們需要一些認知上的改變。編程和構建/測試循環(huán)是工程軟件過(guò)程的中心。我們需要以像這樣的方式去管理它們。構建/測試循環(huán)的經(jīng)濟規律,再加上軟件系統幾乎可以表現任何東西的事實(shí),就使得我們完全不可能找出一種通用的方法來(lái)驗證軟件設計。我們可以改善這個(gè)過(guò)程,但是我們不能脫離它。
最后一點(diǎn):任何工程設計項目的目標是一些文檔產(chǎn)品。顯然,實(shí)際設計的文檔是最重要的,但是它們并非惟一要產(chǎn)生的文檔。最終,會(huì )期望某些人來(lái)使用軟件。同樣,系統很可能也需要后續的修改和增強。這意味著(zhù),和硬件項目一樣,輔助文檔對于軟件項目具有同樣的重要性。雖然暫時(shí)忽略了用戶(hù)手冊、安裝指南以及其他一些和設計過(guò)程沒(méi)有直接聯(lián)系的文檔,但是仍然有兩個(gè)重要的需求需要使用輔助設計文檔來(lái)解決。
輔助文檔的第一個(gè)用途是從問(wèn)題空間中捕獲重要的信息,這些信息是不能直接在設計中使用的。軟件設計需要創(chuàng )造一些軟件概念來(lái)對問(wèn)題空間中的概念進(jìn)行建模。這個(gè)過(guò)程需要我們得出一個(gè)對問(wèn)題空間中概念的理解。通常,這個(gè)理解中會(huì )包含一些最后不會(huì )被直接建模到軟件空間中的信息,但是這些信息卻仍然有助于設計者確定什么是本質(zhì)概念以及如何最好地對它們建模。這些信息應該被記錄在某處,以防以后要去更改模型。
對輔助文檔的第二個(gè)重要需要是對設計的某些方面的內容進(jìn)行記錄,而這些方面的內容是難以直接從設計本身中提取的。它們既可以是高層方面的內容,也可以是低層方面內容。對于這些方面內容中的許多來(lái)說(shuō),圖形是最好的描述方式。這就使得它們難以作為注釋包含在代碼中。這并不是說(shuō)要用圖形化的軟件設計符號代替編程語(yǔ)言。這和用一些文本描述來(lái)對硬件科目的圖形化設計文檔進(jìn)行補充沒(méi)有什么區別。
決不要忘記,是源代碼決定了實(shí)際設計的真實(shí)樣子,而不是輔助文檔。在理想情況下,可以使用軟件工具對源代碼進(jìn)行后期處理并產(chǎn)生出輔助文檔。對于這一點(diǎn),我們可能期望過(guò)高了。次一點(diǎn)的情況是,程序員(或者技術(shù)方面的編寫(xiě)者)可以使用一些工具從源代碼中提取出一些特定的信息,然后可以把這些信息以其他一些方式文檔化。毫無(wú)疑問(wèn),手工對這種文檔保持更新是困難的。這是另外一個(gè)支持需要更具表達力的編程語(yǔ)言的理由。同樣,這也是一個(gè)支持使這種輔助文檔保持最小并且盡可能在項目晚期才使之變成正式的理由。同樣,我們可以使用一些好的工具;不然的話(huà),我們就得求助于鉛筆、紙以及黑板。
總結如下:
實(shí)際的軟件運行于計算機之中。它是存儲在某種磁介質(zhì)中的0和1的序列。它不是使用C++語(yǔ)言(或者其他任何編程語(yǔ)言)編寫(xiě)的程序。
程序清單是代表軟件設計的文檔。實(shí)際上把軟件設計構建出來(lái)的是編譯器和連接器。
構建實(shí)際軟件設計的廉價(jià)程度是令人難以置信的,并且它始終隨著(zhù)計算機速度的加快而變得更加廉價(jià)。
設計實(shí)際軟件的昂貴程度是令人難以置信的,之所以如此,是因為軟件的復雜性是令人難以置信的,并且軟件項目的幾乎所有步驟都是設計過(guò)程的一部分。
編程是一種設計活動(dòng)——好的軟件設計過(guò)程認可這一點(diǎn),并且在編碼顯得有意義時(shí),就會(huì )毫不猶豫的去編碼。
編碼要比我們所認為的更頻繁地顯現出它的意義。通常,在代碼中表現設計的過(guò)程會(huì )揭示出一些疏漏以及額外的設計需要。這發(fā)生的越早,設計就會(huì )越好。
因為軟件構建起來(lái)非常廉價(jià),所以正規的工程驗證方法在實(shí)際的軟件開(kāi)發(fā)中沒(méi)有多大用處。僅僅建造設計并測試它要比試圖去證明它更簡(jiǎn)單、更廉價(jià)。
測試和調試是設計活動(dòng)——對于軟件來(lái)說(shuō),它們就相當于其他工程學(xué)科中的設計驗證和改進(jìn)過(guò)程。好的軟件設計過(guò)程認可這一點(diǎn),并且不會(huì )試圖去減少這些步驟。
還有一些其他的設計活動(dòng)——稱(chēng)它們?yōu)楦邔釉O計、模塊設計、結構設計、構架設計或者諸如此類(lèi)的東西。好的軟件設計過(guò)程認可這一點(diǎn),并且慎重地包含這些步驟。
所有的設計活動(dòng)都是相互影響的。好的軟件設計過(guò)程認可這一點(diǎn),并且當不同的設計步驟顯示出有必要時(shí),它會(huì )允許設計改變,有時(shí)甚至是根本上的改變,
許多不同的軟件設計符號可能是有用的——它們可以作為輔助文檔以及工具來(lái)幫助簡(jiǎn)化設計過(guò)程。它們不是軟件設計。
軟件開(kāi)發(fā)仍然還是一門(mén)工藝,而不是一個(gè)工程學(xué)科。主要是因為缺乏驗證和改善設計的關(guān)鍵過(guò)程中所需的嚴格性。
最后,軟件開(kāi)發(fā)的真正進(jìn)步依賴(lài)于編程技術(shù)的進(jìn)步,而這又意味著(zhù)編程語(yǔ)言的進(jìn)步。C++就是這樣的一個(gè)進(jìn)步。它已經(jīng)取得了爆炸式的流行,因為它是一門(mén)直接支持更好的軟件設計的主流編程語(yǔ)言。
C++在正確的方向上邁出了一步,但是還需要更大的進(jìn)步。
后 記
當我回顧幾乎10年前所寫(xiě)的東西時(shí),有幾點(diǎn)讓我印象深刻。第一點(diǎn)(也是和本書(shū)最有關(guān)的)是,現今,我甚至比那時(shí)更加確信我試圖去闡述的要點(diǎn)在本質(zhì)上的正確性。隨后的一些年中,許多流行的軟件開(kāi)發(fā)方法增強了其中的許多觀(guān)點(diǎn),這支持了我的信念。最明顯的(或許也是最不重要的)是面向對象編程語(yǔ)言的流行?,F在,除了C++外,出現了許多其他的面向對象編程語(yǔ)言。另外,還有一些面向對象設計符號,比如:UML。我關(guān)于面向對象語(yǔ)言之所以得到流行是因為它們允許在代碼中直接表現出更具表達力的設計的論點(diǎn),現在看來(lái)有點(diǎn)過(guò)時(shí)了。
重構的概念——重新組織代碼基礎,使之更加健壯和可重用——同樣也和我的關(guān)于設計的所有方面的內容都應該是靈活的并且在驗證設計時(shí)允許改變的論點(diǎn)相似。重構只是提供了一個(gè)過(guò)程以及一組如何去改善已經(jīng)被證實(shí)具有缺陷的設計的準則。
最后,文中有一個(gè)敏捷開(kāi)發(fā)的總的概念。雖然極限編程是這些新方法中最知名的一個(gè),但是它們都具有一個(gè)共同點(diǎn):它們都承認源代碼是軟件開(kāi)發(fā)工作中的最重要的產(chǎn)品。
另一方面,有一些觀(guān)點(diǎn)——其中的一些我在論文中略微談到過(guò)——在隨后的一些年中,對我來(lái)說(shuō)變得更加重要。第一個(gè)是構架,或者頂層設計的重要性。在論文中,我認為構架只是設計的一部分內容,并且在構建/測試循環(huán)對設計進(jìn)行驗證的過(guò)程中,構架需要保持可變。這在本質(zhì)上是正確的,但是回想起來(lái),我認為我的想法有點(diǎn)不成熟。雖然構建/測試循環(huán)可能揭示出構架中的問(wèn)題,但是更多的問(wèn)題是常常由于改變需求而表現出來(lái)的。 一般來(lái)說(shuō),設計軟件是困難的,并且新的編程語(yǔ)言,比如:Java或者C++,以及圖形化的符號,比如:UML,對于不知道如何有效地使用它的人來(lái)說(shuō),都沒(méi)有多大的幫助。此外,一旦一個(gè)項目基于一個(gè)構架構建了大量的代碼,那么對該構架進(jìn)行基礎性的更改,常常相當于丟棄掉該項目并重新開(kāi)始一個(gè),這就意味著(zhù)該項目沒(méi)有出現過(guò)。即使項目和機構在根本上接受了重構的概念,但是他們通常仍然不愿意去做一些看起來(lái)就像是完全重寫(xiě)的事情。這意味著(zhù)第一次就把它作對(或者至少是接近對)是重要的,并且項目變得越大,就越要如此。幸運的是,軟件設計模式有助于解決這方面問(wèn)題。
還有其他一些方面的內容,我認為需要更多地強調一下,其中之一就是輔助文檔,尤其是構架方面的文檔。雖然源代碼就是設計,但是試圖從源代碼中得出構架,可能是一個(gè)令人畏懼的體驗。在論文中,我希望能夠出現一些軟件工具來(lái)幫助軟件開(kāi)發(fā)者自動(dòng)地維護來(lái)自源代碼的輔助文檔。我幾乎已經(jīng)放棄了這個(gè)希望。一個(gè)好的面向對象構架通??梢允褂脦追鶊D以及少許的十幾頁(yè)文本描述出來(lái)。不過(guò),這些圖(和文本)必須集中于設計中的關(guān)鍵類(lèi)和關(guān)系。糟糕的是,對于軟件設計工具可能會(huì )變得足夠聰明,以至于可以從源代碼的大量細節中提取出這些重要方面的內容這一點(diǎn),我沒(méi)有看到任何真正的希望。這意味著(zhù)還得必須由人來(lái)編寫(xiě)和維護這種文檔。我仍然認為,在源代碼完成后,或者至少是在編寫(xiě)源代碼的同時(shí)去編文檔,要比在編寫(xiě)源代碼之前去編寫(xiě)文檔更好一些。
最后,我在論文的最后談到了C++是編程——并且因此是軟件設計——藝術(shù)的一個(gè)進(jìn)步,但是還需要更大的進(jìn)步。就算我完全沒(méi)有看到語(yǔ)言中出現任何真正的編程進(jìn)步來(lái)挑戰C++的流行,那么在今天,我會(huì )認為這一點(diǎn)甚至要比我首次編寫(xiě)它時(shí)更加正確。
——Jack Reeves, 2002年1月1日
二 來(lái)點(diǎn)批評
在實(shí)際工作中,通常有人這樣完成一個(gè)設計任務(wù),給了我一個(gè)Rose畫(huà)的圖,
指著(zhù)里面的類(lèi)圖,說(shuō),看吧,這就是我的設計,我的架構是這樣這樣的……
接著(zhù)我就問(wèn),讓我們來(lái)看看有關(guān)的代碼,證明一下你的設計。沒(méi)有。
多數情況下,這樣的“設計”意義不大,會(huì )被“編碼階段”碰到的問(wèn)題和需求的變化搞
得面目全非。結果是,由一位“架構師”或者“分析員”,設計的一個(gè)“良好”的架構,
其壽命只能延續到“設計評審”結束之后。
事實(shí)上,一堆人對著(zhù)UML圖來(lái)做設計評審,通常只是一個(gè)形式。提問(wèn)的人在不是很清楚的情況下提問(wèn),設計的人努力的解釋?zhuān)ㄞq解),多數人似懂非懂的評價(jià)著(zhù)一個(gè)都不太清楚的東西,最后累了,決定評審結束。
三 實(shí)踐中我碰到的困難:人
當我在我的小組中實(shí)踐敏捷方法的時(shí)候,感到比較困難的,是碰到一種情況:
組員已經(jīng)有一些經(jīng)驗,并且已經(jīng)有了非常根深蒂固的看法,認為“設計就是設計,編碼就是編碼”,這兩個(gè)階段一定要明確的界定,“否則,我無(wú)法做設計”,他們通常認為,設計是高手做的,而編碼,交給軟件藍領(lǐng)就可以了。
對此我沒(méi)有辦法,重構一段代碼容易,重構一個(gè)人的習慣就太困難了。
四 重構的例子
我拿我以前寫(xiě)的一段程序做了重構,在不改變外部行為的情況下,我看到原來(lái)拙劣的設計逐漸的被改善。具體的過(guò)程比較羅嗦,我放到了個(gè)人主頁(yè)上,有興趣和耐心的朋友,歡迎訪(fǎng)問(wèn)
現在,我正致力于重構公司的一個(gè)軟件產(chǎn)品,不能說(shuō)自己做得很好,但每次都更好一點(diǎn),更重要的是,我非常的enjoy,以編碼、重構為樂(lè )。
聯(lián)系客服