博碩文化

圍繞著軟體開發但令人困惑的隱喻越來越多。David Gries說撰寫軟體是一門科學(a science)(1981);而Donald Knuth說它是門藝術(an art)(1998);Watts Humphrey則說它是一個程序(a process)(1989);P. J. Plauger和Kent Beck都說它就像是駕駛汽車(driving a car)──僅管他們兩個幾乎得出完全相反的結論(Plauger 1993,Beck 2000)。Alistair Cockburn說它是場遊戲(a game)(2002);Eric Raymond又說它就如同是個商場(bazaar)(2000);Andy Hunt和Dave Thomas說它就像園藝(gardening)一樣;Paul Heckel則說它就像是拍攝《白雪公主和七個小矮人》(1994);而Fred Brooks說它像耕田、像捕獵、或陷入「焦油坑」裡的恐龍(1995)……到底哪一個隱喻最好呢?

軟體中的書法:寫作程式碼

關於軟體開發最原始的隱喻是從「寫作(writing)程式碼」這個說法發展出來的,這個隱喻暗示著開發一個程式就像寫一封有緣由的信一樣──坐下來,拿出文房四寶,從頭寫到尾就完成了。此時不需要正正式式地做計劃,你想到什麼東西,把它寫出來就是了。

許多想法就是從寫作這個隱喻衍生而來的。例如Jon Bentley說,你應該可以坐在火爐邊,品一杯白蘭地或抽一口上好的雪茄,旁邊坐著你心愛的獵犬,去品味一段「有內涵的程式」,就像面對的是一本出色的小說那樣。Brian Kernighan和P. J. Plauger參考一本關於寫作體裁的書《The Elements of Style》,Strunk and White 2000,將他們關於程式設計風格(programming style)的書命名為《The Elements of Programming Style》,1978。程式設計師也經常會討論「程式的可讀性(readability)」。

對於個人規模的工作乃至於小型的專案來說,這種寫信的隱喻已經足夠了,然而對於其他場合而言,這個隱喻還遠遠不夠──它沒有完整、充分地刻劃出軟體開發工作。書寫通常只是個人活動,而一個軟體專案多半會涉及承擔不同職責的許多人。在你寫完一封信之後,你只要把它塞進信封然後寄出去就完成了,你再也不能修改它──從任何程度和目的上來看,這件事情都已經結束了。而軟體的修改沒那麼難,也很難說有真正完全結束的時候。典型的軟體系統在首次發佈之後的工作量,可能達到整體工作量的90%,典型情況下也佔了2/3的工作量(Pigoski 1997)。對寫作而言,最重要的是原創性。但是對於軟體建構來說,「努力創造真正原創」的開發效率,往往比專注於重用(reuse)以往專案的一些設計思維、程式碼及測試案例(test case)的開發效率來得低。總之,寫作這個隱喻所暗示的軟體開發過程太過於簡單、太過於呆板了。

然而不幸的是,這種用文字(信件)寫作所做的隱喻一直被延續下來,原因在於一本軟體領域最流行的著作,Fred Brooks的《The Mythical Man-Month(人月神話)》,Brooks 1995。Brooks說:「失敗為成功之母,無論如何,把必然的一次失敗納入正式計畫當中。」這個咒語給了我們一幅如圖2-1所示的景象:被扔進紙簍裡成堆的半成品草稿。

圖2-1  文字寫作這個隱喻暗示著軟體開發的過程是一種代價昂貴的試誤(trial and error)過程,而非仔細的規劃和設計。

在給你阿姨寫一封「最近好嗎」的禮節性問候信時,「計劃扔掉一張草稿」或許還比較實際。但如果將「書寫軟體」這個隱喻引申為「計劃扔掉一個(軟體)」,則不是一個好的建議──尤其是軟體的主要系統就已經花費了相當於一棟十層高的辦公室或一艘遠洋客輪那麼多的成本時。要想成功並不難,只要你能忍受坐在你鍾情的旋轉木馬上轉上無數圈就可以。訣竅在於繞第一圈時就讓它成功──或者在成本最低的時候多獲得幾次成功的機會。一些其他的隱喻將達到這個目標的途徑闡述得更好。

軟體的耕作法:培植系統

相較於前面那個呆板的寫作隱喻,一些軟體開發人員認為,應當將創造軟體想像成類似播種和耕作的情形。你一次只設計系統的一小部分、寫出一段程式碼、做一點測試,並將成果一點點加入到整個系統中。透過這種小步前進,你可以把每次可能遇到的麻煩減到最小。

有時候人們會用很糟的隱喻去描述一種很好的技術。在這個時候,需要保全這個好技術,並去尋找更好的隱喻。這個例子裡的增量技術(incremental technique)是很有價值的,但把它比作播種和耕作卻非常糟糕。

雖然「每次做一點」這個主意可能在某些方面與農作物生長類似,但把軟體開發類比為耕作就很不貼切,也沒有太大的意義,而且我們很容易用下面即將介紹的更好的隱喻來替代它。人們也很難把耕作這個隱喻引申到「一次做一點事情」之外。如果你認同耕作這種隱喻,就請想像一下圖2-2的情況:你會發現自己談論的是:對系統計劃施肥、對細節設計蔬果,並透過有效的管理土地來增加程式碼的產量,最終取得程式碼大豐收。你還會說「輪流種C++和大麥」,或者讓土地閒置一年以增加硬碟裡面氮肥的供應量。

軟體耕作這個隱喻的弱點,在於它暗示了人們將無法對開發軟體進行任何直接的控制。你在春天播下程式碼的種子,然後按照農曆節氣向土地公許幾個願,你就會在秋天收穫到豐盛的程式碼。

圖2-2  很難將耕作這個隱喻恰當地引申到軟體開發領域。

軟體的牡蠣養殖觀點:系統生長

在談論培育(growing)軟體時,有時人們實際上指的是軟體的生長(accretion),這兩種隱喻緊密相關,而軟體生長是一幅更發人深省的景象。看到「生長」這個詞,就算手邊沒有字典,我們也都能明白它指的是藉由外在的增加或吸收而逐漸地生長或變大。「生長」這個詞描述了牡蠣製造珍珠的過程,逐漸地增加微量的碳酸鈣。在地質學裡,「accretion」一詞的意思是「沖積層」,指的是水流中夾帶的沉澱物,沖積而不斷擴大的陸地。在正式的術語中,「沖積層」是指海岸沿線的陸地因受到水流衝擊,水中夾帶的物質不斷沉積而形成的增長。

這裡並不是說要你學會如何從水流夾帶的沉積物中提煉出程式碼來,而是說你需要學會如何一次為軟體系統增加一小部分。跟「生長」密切相關的另一些詞彙有:「增量的(incremental)」、「迭代的(iterative)」、「自適應的(adaptive)」以及「演進的(evolutionary)」。以增量方式進行設計、編譯和測試,都是目前已知最強而有力的軟體開發概念。

在進行增量式開發時,我們先做出軟體系統的一個盡可能簡單、但能執行的版本。它不必接受真實的輸入,也無須對資料進行真正的處理,更不用產生真實的輸出──僅僅需要構成一個足夠強壯的骨架,支撐起未來將要開發的真實系統。對於你確定要開發的每一項基本功能,都可能僅需要呼叫虛假類別(dummy classes)。這個最基本的起點,就像牡蠣開始孕育珍珠的那顆細小沙粒。

在骨架形成之後,你要慢慢在上面附著肌肉和皮膚:把每個虛假類別取代為真實類別;不再假裝接受輸入,而是把接收真實輸入的程式碼替換進去;不再假裝產生輸出,而是把產生真實輸出的程式碼替換進去。你一次增加一小部分程式碼,直到獲得一個完全可以工作的系統。

有件趣聞逸事令人印象深刻,它有利於證明這個方法是有用的。那位在1975年建議我們建造一份軟體以備扔掉(building one to throw away)的Fred Brooks說,在他寫完了里程碑式的著作《人月神話》之後的十年間,沒有什麼能像增量式開發那樣,徹底地改變了他個人的開發習慣及其效力(1995)。Tom Gilb在他突破性的著作《The Principles of Software Engineering Management,1988》中也同樣指出了這一點,該書介紹了演進式交付(Evolutionary Delivery)並奠定了如今敏捷程式設計(agile programming)方法的基礎。目前不少方法論都是根據這個理念(Beck 2000;Cockburn 2002;Highsmith 2002;Reifer 2002;Martin 2003;Larman 2004)。

作為一個隱喻而言,增量式開發的優勢在於未做過度的承諾。比起耕作那個隱喻來說,對它作不恰當的引申要更困難一些。牡蠣孕育珍珠的景象也適切地刻畫了增量式開發(或說生長)的情形。

軟體建構(Software Construction):建造軟體(Building Software)

與「寫作(writing)」軟體或「培育(growing)」軟體而言,「建造(building)」軟體的景象就更有用了。它和軟體生長的概念是相通的,並且提供了更詳細的指引。建造軟體這個說法暗示了軟體開發中存在著諸多階段,如計劃、準備及執行等,根據所建造軟體的不同,這些階段的種類和程度可能會發生變化。進一步研究這個隱喻時,你還會發現許多其他方面的相似之處。

要搭一座四足塔(four-foot tower),你要有一雙穩健的手,要找一個平坦的表面,以及十幾個完好無損的啤酒罐。而要搭一座比它大100倍的塔,光是多100倍的啤酒罐還不夠,還需要同時採用完全不同的計劃方法和建造方法才行。

如果你要蓋一個簡單的建築物──例如一個狗屋──你先開車到木材店買些木頭和釘子。臨近傍晚時分,你的愛犬Fido就有新窩了。如果你像圖2-3那樣忘了弄個門,或是犯了其他錯誤,那也沒什麼大不了的,修改一下或乾脆從頭再來就是了。你的損失最多也就只是一個下午的時間。這種寬鬆的方式對於小型的專案來說,也還算合適。如果你寫1000行的程式碼時採用了錯誤的設計,你還可以重構甚至從頭再來,不會損失太多。

圖2-3  在簡單結構上犯下錯誤,其懲罰也不過是一點時間,或是些許尷尬。

如果你是在建一棟房屋,那麼這個建造過程就會複雜得多,而糟糕的設計所引發的後果也更嚴重。首先你要決定準備建一個什麼樣類型的房屋──在軟體開發裡的類似事項稱為定義問題(problem definition)。接下來,你必須和某位建築師(architect)探討這個整體設計,並得到批准。這跟軟體架構設計(software architectural design)十分相似。然後你畫出詳細的藍圖,雇用一個承包人。就像軟體的細節設計(detailed software design)。再然後,你要準備好建造地點,打好地基,搭建起房屋框架,砌好邊牆,蓋好房頂,接通水、電、瓦斯等。這就如同是軟體的建構(software construction)。在房屋大部分完成之後,庭院設計師、油漆工和裝修工還要來把你新蓋的家及裡面的家具美化一番。這就好比軟體的最佳化(optimization)過程。在這整個過程中,還會有各種監查人員來檢查工地、地基、框架、佈線及其他需要檢查的地方。這相當於軟體複查(評審,reviews)和審查(inspections)。

在這兩種活動中,更高的複雜度和更大的規模都會帶來更嚴重的後果。蓋房屋時,建材多少也是有些昂貴,但主要的開銷還是在人力上。把一棟牆推倒然後移動半尺是很昂貴的,倒不在於浪費多少釘子,而是因為你要付給工人們更多的工錢,移動這堵牆耗費了額外的工時。你只有盡可能地把房屋設計好,就像圖2-4那樣,這樣你才不用浪費時間去修正那些本來可以避免的錯誤。在開發一個軟體產品時,原料甚至更廉價,但勞動力上的開銷也更昂貴。變更一份報表格式所要付出的代價,和移動房間裡的一堵牆一樣昂貴,因為兩者的主要成本構成部分都是花費人力的時間。

圖2-4  更複雜的結構需要更仔細地規劃。

除此之外,這兩種活動之間還有什麼相似之處呢?建造一個房屋時,你不會去試著建造那些能買得到的現成東西。你會買洗衣機、烘乾機、洗碗機、電冰箱及冷藏櫃。除非你是電機方面的專家,否則你不會考慮自己動手弄這些東西的。你還會購買預先造好的櫥櫃、餐桌、門窗及浴具,等等。當開發軟體時,你也會這麼做的。你會大量使用高階語言所提供的功能,而不會自己去撰寫作業系統層次的程式碼。你可能還要用些現成的程式庫,例如一些容器類別(container classes)、科學計算函式、使用者介面類別、資料庫操作類別,等等。總之,自行編寫那些能買得到的現成程式碼通常是沒有意義的。

但如果你要建造一間擁有一流家具的高檔住宅,那你可能需要特別訂製的櫥櫃,還可能需要能和這些櫥櫃相搭配的洗碗機、冰箱和冷藏櫃等,也可能需要以特殊的形狀和特別尺寸訂製的窗戶。在軟體開發中也有和這種訂製相似的情況。如果你要開發一款一流的軟體產品,你可能會自己編寫科學計算函式以便獲得更快的速度和更高的精準度。你還可能需要自己編寫容器類別、使用者介面類別以及資料庫類別等,這樣做可以讓產品的各個部分無縫拼接,擁有一致的外觀和體驗。

適當的多層次規劃對於建造建築物和建構軟體都有好處。如果你按照錯誤的順序建構軟體,那麼編碼、測試和除錯都會更困難。需要花更長的時間才能完成,甚至整個專案就分崩離析了——由於每個人的工作都過於複雜,所有成果組合在一起時就變得混亂不堪了。

精心計劃,並非意味著徹底全面的計劃或過度的計劃。你可以把房屋結構性的支撐(structural support)規劃清楚,而在日後再決定採用原木地板還是地毯、牆面漆成什麼顏色、屋頂使用什麼材料,等等。一個規劃得當的專案能夠提升你「在後期改變細節(設計)」的能力。你對同類軟體的開發經驗越豐富,(在開發新軟體時)就越能掌握更多的細節。你只需要保證已經做了足夠的計劃,不會到後來因為計劃不足而引發重大問題。

用建築房屋來類比軟體建構,還有助於解釋為什麼不同的軟體專案能從不同的開發方法中獲益。建築業中,蓋間倉庫或工具房,蓋一座醫院或核能反應爐,你在規劃、設計及品質保證方面所需達到的程度是不一樣的。蓋一座學校、一幢摩天大樓,或一座三房的小別墅,所用的方法也不會相同。同理,在軟體開發中,通常你只需要用靈活的、輕量級的(lightweight)方法,但有時你就必須得用嚴格的、重量級的開發方法,以達到所需的安全性目標或其他目標。

軟體的變動在建築領域也有類似的事物。把一堵「承重牆」移動半尺所花費的成本,肯定要比僅僅移動一面「輕隔間」更高。同樣,對軟體進行結構性的修改所花費的成本,肯定也比僅僅增刪一些週邊功能更高。

最後,建築這個隱喻讓人們對於超大型軟體專案的認識更加深刻。超大型的結構一旦出現問題,後果將非常嚴重,因此有必要對這樣的結構進行超出常規的規劃與建設(over-engineered)。建築人員需要非常小心地制定並核查設計規劃,在建設時留有彈性以保障安全;寧可多花10% 的成本買更堅固的材料,也比摩天大樓倒下來要划算得多。還需要特別關注工作的時間。在建造帝國大廈時,每輛運料車運輸時都留有15分鐘的彈性時間。如果某輛車無法在正確的時間到位,則整個工期就會延誤。

同理,超大型的軟體專案也需要比一般規模的專案有更高級別的規劃設計。Capers Jones發表的報告指出,一套100萬行程式碼的軟體系統,平均需要69種文件(1998)。其需求規格文件一般有四五千頁長,而設計文件時常是需求的兩三倍長。不太可能有哪一個人能完全理解這種規模的專案的所有設計細節──甚至只通讀一遍都不那麼容易。因此,更充分的準備工作也就理所應當了。

如果需要創造在經濟規模上可以匹敵帝國大廈的龐大軟體專案,那麼與之相當水準的技術與管理控制也是必須的。

按房屋建築所作的這個隱喻,可以向許多其他方面引申——這也是隱喻這個方法如此強而有力的一個原因。有很多常見的軟體開發術語都是從建築這個隱喻衍生出來的:軟體架構(建築學,architecture)、支撐性測試程式碼(鷹架,scaffolding)、建構(建設,construction)、基礎類別(foundation classes)以及分離程式碼(tearing code apart)。你可能還聽說過更多這一類的詞語。

應用軟體技術:智慧工具箱

能有效率地開發高品質軟體的人們,長年累月累積了大量的技術、技巧和訣竅。技術並不是規矩(rule),它只是分析工具(analytical tools)。好的工匠知道完成某項工作需要使用哪樣工具,也知道該怎樣正確地使用。程式設計師也該這樣。程式設計方面的知識學得越多,你腦中的工具箱就會有更多的分析工具,也會知道該在何時使用這些工具,以及怎樣正確地使用它們。

在軟體領域裡,專業的諮詢人員有時會讓你只使用某種軟體開發方法而遠離其他方法。這樣做並不妥當,因為當你百分之百依賴某個方法論時,你就只會用一種方法去看世界。在某些情況下,對於你所面臨的問題還有其他更好的方法,你可能錯失良機。這種「工具箱隱喻」能夠幫助你把所有的方法、技術及技巧留在腦海中──在適當的時機拿來就可使用。

組合各個隱喻

因為隱喻是一種啟發式方法(heuristic)而不是演算法,因此它們彼此並不互斥。你可以同時使用生長(accretion)和建築(construction)這兩個隱喻。你如果想使用「寫作」隱喻也行,你還可以把「寫作」與「駕駛」、「捕獵」、「陷入焦油坑裡的恐龍」等隱喻組合在一起。你可以選用任何一種隱喻或一些隱喻的組合,只要它能激發你的思維靈感,並讓你和團隊的其他成員更良好地溝通。

使用隱喻是件說不清楚的事(fuzzy business)。你需要適當地引申它的涵義,才能從其蘊含的深刻啟發中受益。但若你過分地或在錯誤的方向上引申了它的涵義,它也會誤導你。正如人們會誤用任何強大的工具一樣,你也可能誤用隱喻,但它的強大功效,還是會成為你智慧工具箱中的一個寶貴部分。(摘錄整理自《CODE COMPLETE 2中文版》)

因為隱喻是一種啟發式方法而不是演算法,因此它們彼此並不互斥。……你可以選用任何一種隱喻或一些隱喻的組合,只要它能激發你的思維靈感,並讓你和團隊的其他成員更良好地溝通。

CODE COMPLETE 2中文版:軟體開發實務指南(第二版)

Steve McConnell/著;金戈、湯凌、陳碩、張菲/譯;裘宗燕/審校;博碩文化/編譯

博碩文化出版

售價:1280元

作者簡介

Steve McConnell 是 Construx Software 公司的首席軟體工程師,負責監督控管軟體工程的實踐。他是軟體工程知識體(SWEBOK)專案建構知識領域的主管。Steve曾為微軟、波音等公司工作。


Advertisement

更多 iThome相關內容