文章目錄
一、焦油坑1.1 開發的樂趣1.2 開發的苦惱二、人月神話三、外科手術隊伍Mills 的建議四、畫蛇添足五、巴比倫塔的教訓六、未雨綢繆七、干將莫邪八、禍起蕭牆九、沒有銀彈 —— 軟體工程中的根本和次要問題筆者手記一、焦油坑前車之覆,後車之鑑。
—— 荷蘭諺語
史前史,一頭大象悠然自得地在草原上散步。夕陽的餘暉灑下,大象覺得有些口渴了,它看到旁邊有一個水窪,於是它慢悠悠地走到水窪前飲水。當它喝足準備離開時,突然發現自己被陷住了,這竟是一個蓄有一點水的瀝青湖!大象抬起笨重的象腿想要抽身,卻發現越是掙扎,越是下陷得厲害。大象預感到了死亡的來臨,它的眼中漸漸浮現出驚恐與絕望。
繼而它看到了更可怕的事情,幾十頭獅子見它落單,兇狠地向它衝過來,自己卻動彈不得,只能眼睜睜地成為他們的腹中餐。但顯然它們都低估了瀝青湖的威力,所有的獅子也都陷入其中,無法自拔。焦油緊緊地糾纏著他們,沒有誰有足夠的技巧能夠掙脫束縛。最後,它們都沉入了坑底,形成震驚後人的化石與焦油坑。
軟體開發也常常陷入這樣的焦油坑中,表面上看起來沒有哪個單獨的問題是致命的,但當他們糾纏在一起,就形成了拖慢團隊進度的焦油。即使是精明的程式設計師也會陷入其中,難以看清問題的本質。
想要解決問題,我們就要先去了解問題,看一下系統開發的職業樂趣與苦惱。
1.1 開發的樂趣
首先是創造事物的快樂,如同小孩在玩泥巴時感到快樂一樣,成年人也喜歡創造事物,特別是經過自己設計的事物。這種快樂是上帝創造世界的折射,一種呈現在每片獨特的、嶄新的樹葉和雪花上的喜悅。
第三,快樂來自於開發的過程非常有趣,每個類、每行程式碼就像一個精妙的零件,我們生產這些零件,又將其組合加工,程式設計過程就像在玩樂高拼圖一樣有趣。
第四,快樂來自於持續學習,程式設計不同於流水線,我們每天總在面臨不同的問題,從不同的問題中可以學到新的事物,類似於在遊戲中獲取到經驗,升級時的快樂。
最後,快樂來自於操作簡單,程式設計本質上是一種純思維的活動,開發過程中,不需要搭建複雜的環境,只需運用自己的想象,就能建造出自己的 “城堡”。
程式設計就像是設計一場魔術,透過在鍵盤上輸入正確的咒語,它能列印結果,繪製圖形,發出聲音,移動支架…它不僅滿足了我們內心深處對於創造的渴望,而且喚醒了我們每個人內心的情感。
1.2 開發的苦惱
首先,苦惱來自於追求完美,計算機的魔術雖好,但它需要每個字元、每個停頓都處在正確的位置上,很少有人類活動會要求如此完美。人類對完美本身就不習慣,比如為什麼一週是 7 天不是 10 天?為什麼一年是 12 個月不是 10 個月?為什麼一分鐘是 60 秒不是 100 秒?程式設計最困難的地方就在於要將做事的方式向追求完美調整。
其次,苦惱來自於無法控制工作目標,程式設計師的工作內容往往是由他人設定的,如產品經理、技術主管、老闆或客戶,即使遇到他人設定的目標不夠合理,程式設計師也常常無能為力。用管理學的術語來說,個人的權威和他所承擔的責任是不匹配的。但每一次任務的完成,都會增加我們的權威。
第三,苦惱來自於對其他人的依賴,在開發過程中,我們不得不使用其他人編寫的程式碼,比如開發環境、三方框架等等。我們期望這些程式碼能正常工作,但有時他們卻存在很多問題,所以程式設計師不得不花時間去研究和修改,而它們在理想情況下本應該是可靠的、完整的。
第四,設計與開發是有趣的,但維護程式碼是一項瑣碎而重複的工作,伴隨著創造性活動的,往往是枯燥沉悶的時間和艱苦的勞動,程式程式設計也不例外。
最後一個苦惱,有時是一種無奈 —— 當投入了大量辛苦的勞動,產品在即將完成或者終於完成的時候,卻已顯得陳舊過時。可能是同事或競爭對手已經構思了更好的方案。
現實情況通常比上面所說的要好一些,事實上,當軟體開發完成後,很少會為了更好的設想而推倒重來,因為現有的系統已經能滿足要求,並體現了回報。
這,就是程式設計。一個許多人痛苦掙扎的焦油坑以及一種樂趣和苦惱並存的創造性活動。對許多人而言,快樂大於苦惱。本書試圖搭建一些橋樑,為透過這樣的焦油坑提供一些指導。
二、人月神話美食的烹調需要時間;片刻等待,更多美
味,更多享受
。
—— 新奧爾良市安託萬餐廳的選單
在眾多軟體專案中,缺乏合理的進度安排是專案滯後的最主要原因。導致這種災難的原因是什麼呢?
首先,程式設計人員都是樂觀主義者,總是理想地想象一切都將運作良好,每項任務僅花費它所應該花費的時間。 而實際上,我們在實現過程中總會碰到各種各樣的困難,可能是我們構思的缺陷,也可能是程式其他部件帶來的錯誤影響,修復這些錯誤將佔用我們大量的時間。
其次,估算進度時,我們錯誤地假設人和月可以互換,以人月為單位去衡量開發工作的規模是一個危險和帶有欺騙性的神話。就好比無論有多少個母親,孕育一個生命都需要十個月。人月僅適用於完全可以分解的任務,這在割小麥和收棉花的工作中是可行的。當任務由於次序上的限制不能分解時,人手的新增對進度是沒有幫助的。在開發工作中,新增更多的人手,意味著不得不花費時間去培訓新人,並且隨著開發人員的增多,溝通、交流的時間成本也越大。
第三,系統測試的時間總是被預估得很低。 理論上,bug 的數量應該為 0,但由於我們的樂觀主義,實際的 bug 數量比預料得要多得多,系統測試的進度安排常常是程式設計中最不合理的部分。對於軟體的進度安排,作者以自己多年的管理經驗總結出如下法則:
1/3 計劃1/6 編碼1/4 構件測試和早期系統測試1/4 系統測試,所有的構件已完成與大多數傳統進度安排相比,這份進度安排顯得計劃時間過長,測試更是佔用了一半的時間,編碼是最容易估算的部分,僅分配了 1/6 的時間。但如果不為測試安排足夠的時間簡直就是一場災難,因為 bug 會沒有任何預兆,很晚才出現在客戶和專案經理面前。
第四,專案經度落後時,人們的第一反應是加派人手。這樣的應對方案好比用汽油滅火,只會讓火勢更大,而更大的火勢又需要更多的汽油,從而陷入迴圈的災難之中。
Brooks 法則:向進度落後的專案中新增人手,只會使進度更加落後。
那麼,除去神話色彩的人月應該如何呢?
專案的時間依賴於順序上的限制,人員的最大數量依賴於獨立子任務的數量。從這兩個數值可以推演出進度表,該表安排的人員較少,花費的時間較長(唯一的風險是產品可能會過時)。相反,分派較多的人手,計劃較短的時間,將無法得到可行的進度安排。
三、外科手術隊伍這些研究表明,效率高和效率低的實施者
之間個體差異非常大,經常能夠達到數量級的水平。
—— Sackman, Erikson 和 Grant
軟體經理很早就認識到優秀程式設計師和較差的程式設計師之間生產率的差異,但實際測量出的差異還是令我們所有的人吃驚, Sackman, Erikson 和 Grant 曾對一組具有經驗的程式人員進行測量,在該小組中,最好的和最差的表現在生產率上平均為 10:1; 在執行速度和空間上具有 5:1 的驚人差異!並且,資料顯示經驗和實際的表現沒有相互聯絡。
得出的結論很簡單:200 個平庸的程式設計師組成的隊伍很可能不如 25 個優秀程式設計師組成的隊伍效率高。
但小而精煉的隊伍也有致命的弊端,作者曾在 IBM 公司任職 OS/360 系統的專案經理,OS/360 專案在頂峰時,有超過 1000 人在為它工作 —— 程式設計師、文件編制人員、操作人員、職員、秘書、管理人員、支援小組等等。從 1963 年到 1966 年,設計、編碼和文件工作花費了大約 5000 人年。即使 25 個優秀程式設計師能達到 200 個普通程式設計師的水平,也需要 25 年的時間才能完成此專案。一個產品在最初設計的 25 年後才出現,還有人會對他感興趣嗎?
這種進退兩難的境地是很殘酷的,對於效率和概念完整性來說,最好由少數優秀的人員來設計和開發,而對於大型系統,則需要大量的人手,以使得產品能在時間上滿足需求。如何調節這兩方面的矛盾呢?
Mills 的建議
Harlan Mills 提供了一個解決方案:大型專案的每一個部分由一個團隊解決,但是該隊伍並不是一擁而上,而是以類似外科手術隊伍的方式組建。
如今的外科手術已經相當成熟,通常來說,一個外科手術隊伍中有五種角色:
一位外科醫生一位或多位助手一位麻醉師一位器械護士一位巡迴護士其中,主刀的外科醫生負責整臺手術的操作部分,他必須經驗豐富且能力出色。助手是外科醫生的後備,他能完成任何一部分工作,但是相對具有較少的經驗。麻醉師、護士為外科醫生提供必要的幫助。
在系統開發中也可以採用類似的人員結構:
程式的總架構師擔任外科醫生的角色,負責程式的整體架構,並輸出產品文件。保障程式的概念完整性,在整個團隊中具有權威性。
將程式設計師按照專業化分工,承擔自己擅長領域的程式碼部分,他們創造了團隊最有價值的財富 —— 工作產品。
測試人員負責編寫測試用例,為外科醫生搭建測試平臺,保障程式的可靠性。
外科手術隊伍與傳統隊伍的區別就在於:傳統的團隊將工作進行劃分,每人負責一部分工作的設計和實現。在外科手術團隊中,實際設計完全由外科醫生完成,其他人負責填充實現細節與提供必要的幫助。
另外,團隊中剩餘人員職能的專業化分工是高效的關鍵,只有分工明確,成員之間才可以儘可能地簡單交流。
四、畫蛇添足聚沙成塔,集腋成裘
—— 奧維德
在開發第一個系統時,架構師傾向於精煉和簡潔,因為他知道自己對正在進行的任務不夠了解,所以他會謹慎仔細地工作。對偶爾出現的裝飾和潤色功能,他通常都會選擇暫時擱置一旁,作為“下一個”專案的目標。
第二個系統常常是架構師設計的最危險的系統。它常常被過度設計。在第一個系統設計完成後,架構師對這類系統充滿了十足的信心,在設計第二個系統時,先前擱置的功能都被新增進來。最終導致第二個系統雖然功能齊全,但顯得粗糙、浪費、不優雅,也許只有一半的功能被常規使用。
當開發第三個、第四個系統時,先前的經驗會相互驗證,此時的架構師將足夠自律,在選擇程式的功能時能夠做必要的取捨。
想要避免畫蛇添足,在開發第二個系統時,架構師就應該學會這些自律。
五、巴比倫塔的教訓大洪水過後,全人類僅剩下諾亞一家,他們因躲在方舟中倖存了下來。諾亞一家人在新土地上重新繁衍生息,成為全人類共同的祖先。心有不安的子孫後代們為了防止大洪水再次發生,提議共同修建一座通天塔,這樣,人們以後便不再需要流離失所。不料這件事引起了上帝的注意,上帝開始擔心:“如果這件事他們都能共同完成,以後便沒有什麼難得倒他們了。”上帝決定阻撓他們。他到人間轉了一圈之後發現:“現在他們都是同一個種族,都說同一種語言。”於是上帝決定從語言下手,他創造了許多種語言,導致人們互相不能聽懂。果然,沒過多久,人們便無法繼續合作修建通天塔了,不得不散落到世界各地。
——《舊約聖經》,《創世紀》篇。
巴比倫塔計劃雖然有充足的人力物力,但由於無法溝通,不得不走向失敗的結局。它的教訓告訴我們:溝通是合作的必要條件。
大型軟體專案中也面臨這樣的情況,因為左手不知道右手在做什麼,所以進度災難、不合理的功能實現、系統缺陷紛紛出現。隨著工作的不斷進行,許多小組開始慢慢地修改自己程式的功能、規模和速度,當他們組合在一起時,新的程式就顯得與原定目標漸行漸遠。
團隊之間如何有效的溝通呢?通常有三種途徑:
電話、交談等非正式溝通:這些途徑非常方便、有效,並且隨時發生在日常生活中會議溝通:雖然談到會議總是有些惱人,但會議是最正式的明確需求的途徑。如果小組氛圍足夠和諧,會議溝通將非常有用工作手冊:在專案開始時,就應提綱挈領,準備好專案工作手冊。指派專門的人手來維護工作手冊,保證它們可以隨著專案實時更新。工作手冊應保持樹狀的索引結構,工作人員可以很方便的使用索引來檢索他所需要的資訊。巴比倫塔可能是第一個失敗的工程,但它不是最後一個。溝通與交流是成功的關鍵。這項技能需要管理者仔細考慮,相關經驗的積累和能力的提高同軟體技術本身一樣重要。
六、未雨綢繆不變只是願望,變化才是永恆
。
—— 斯威夫特
化學工程師很早就認識到,在實驗室可以進行的反應過程,並不能在工廠中一步到位的實現。一個被稱為 “試驗性工廠” 的中間步驟是非常必要的,它會為提高產量和在缺乏保護環境下運作提供寶貴經驗。
軟體系統也面臨類似的問題,大多數軟體專案都是一開始設計好演算法,然後將演算法應用到待發布的軟體中,接著根據進度安排,將第一次開發的產品釋出給客戶。
然而,對於大多數專案,第一次開發的系統往往並不好用。它可能太大、太慢,或者難以使用。許多原型設計上的痛點總是在專案落地之後才顯現出來。要解決所有的問題,開發出一個更靈巧或者更好的系統,除了重新開始之外,沒有其他辦法。舊系統的丟棄可以一步完成,也可以一塊塊地實現。所有大型系統的經驗都顯示,這是必須完成的步驟。
因此,我們要考慮的問題不是 “是否要構建一個用來試驗性的系統,然後拋棄它?”因為這是毋庸置疑的,我們必須這樣做。我們需要考慮的問題是 “是否將第一次構建的原型系統釋出給使用者?”
將原型系統釋出給使用者,我們可以獲得時間,但是它的代價高昂 —— 對於使用者,使用起來極度痛苦;對於開發人員,重新開發的同時仍需要維護老系統,分散了精力;對於產品,影響了聲譽,即使再好的再設計也難以挽回名聲。
因此,為捨棄而計劃,我們必須這樣做。如《極限程式設計》一書中所言:擁抱變化,唯一不變的就是變化本身。
細緻的模組化可拓展的函式精確完整的模組間介面設計完備的文件使用變更的思想設計出來的軟體系統具有更好的複用性。它可以極大地減少正式系統開發時的困難。
程式在釋出給客戶使用之後,並不會停止變化,釋出後的變更被稱為程式維護。對於一個廣泛使用的程式,其維護的成本通常是開發成本的 40% 或更多。該成本受使用者數目的影響很大,使用者越多,所發現的錯誤就越多。
程式維護中的一個基本問題是 —— 缺陷修復總會以固定的機率引入新的 bug,機率大概在 20%~50% 之間。所以,整個過程是前進兩步,後退一步。
為什麼缺陷不能被更徹底地被修復?首先,看上去很微小的錯誤,似乎僅僅是區域性操作上的失敗,實際上卻可能是系統級別的問題。其次,維護人員通常不是程式碼的最初編寫人員,而是一些後來者,他們並不熟悉程式碼編寫之初的設計意圖。
透過對統計模型的研究,關於軟體系統,Belady 和 Lehman 得到了更具普遍意義、被所有經驗支援的結論:“事物在最初總是最好的。”
七、干將莫邪巧匠因為他的工具而出名
—— 諺語
就工具而言,即使是現在,很多軟體專案仍然像經營一家五金店。每個骨幹人員都仔細地保管自己工作生涯蒐集的一套工具集,這些工具成為個人技能的直觀證明。
這種方法對軟體專案來說是愚蠢的。前文已經提到過,專案的關鍵問題是溝通,而個性化的工具只會阻礙溝通。並且,當機器和語言發生變化時,技術也會隨之變化,所有工具的生命週期都是很短的。毫無疑問,開發和維護公共程式設計工具的效率更高。
因此,專案經理應該制定一套策略,併為通用工具的開發分配資源。包括:
輔助機器開發:輔助機器的概念和目標機器相對應。目標機器是程式最終的服務物件,輔助機器是在開發系統中提供服務的機器。它是一個模擬裝置,用於提供可靠的除錯平臺工具庫開發:如圖片儲存、檔案操作等工具類,它們可以由工具維護人員一次完成,避免開發過程中重複的工作文件系統開發:生成詳盡且結構良好的文件,方便開發人員查閱八、禍起蕭牆帶來壞訊息的人不受歡迎。
—— 索福克勒斯
當一線經理發現自己的隊伍出現了計劃偏離時,他肯定不會馬上趕到老闆那裡去彙報這個令人沮喪的訊息。團隊可以彌補進度偏差,他可以想出辦法或者重新安排進度以解決問題。
但每個老闆都需要知道兩種資訊:工作計劃中的問題和工作狀態的資料。出於這個目的,他需要了解開發隊伍的真實情況,但得到真相是很困難的。
一線經理的利益和老闆的利益在這裡出現了衝突。經理擔心彙報出了問題,老闆會採取行動,這些行動會取代經理的作用,降低經理的威信,從而搞亂其他計劃。所以,只要專案經理認為自己可以獨立解決問題,就不會選擇告訴老闆。因此,所有的汙垢都被藏在地毯之下。
有兩種掀開毯子把汙垢暴露出來的辦法:
一是減少角色衝突,鼓勵狀態共享二是猛地拉開地毯兩種方法並沒有優劣之分,他們都應該被採用。
減少角色衝突:老闆應當規範自己,不對專案經理可以解決的問題做出反應。他應當清楚自己什麼時候需要採取行動,什麼時候是在檢查狀態,保證自己絕不在檢查狀態的時候採取行動。如果在狀態報告的第一個段落結束之前,老闆就拿起電話發號施令,這樣的做法勢必會壓制資訊的完全公開。當專案經理瞭解到老闆收到狀態報告後不會驚慌,並且不會越俎代庖時,他就會逐漸提交真實的結果。
猛地拉開地毯:建立能瞭解專案真實狀態的評審機制是必要的,大型專案中,可能需要每週對某些部分進行評審,大約一個月左右進行整體評審。
對專案的計劃和控制是專案早期的預警系統,它們可以防止專案以一次一天的方式落後一年。
九、沒有銀彈 —— 軟體工程中的根本和次要問題在未來的十年內,無論是在技術還是管理方法上,都看不出有任何突破性的進步,能夠保證在十年內大幅度地提高軟體的生產率、可靠性和簡潔性。
在古老的西方傳說中,月圓之夜,受感染的人狼將會由人變狼。人狼出沒,兇惡殘暴,唯一能殺死他們的武器便是銀製子彈。
在軟體開發行業,我們一直渴求一顆消滅軟體複雜度的銀彈。但我們看看近十年來的情況,沒有發現銀彈的蹤跡。分析軟體的本身特性也會發現,軟體不大可能有任何的發明創新 —— 能夠像計算機硬體工業中的電子器件、電晶體、大規模整合一樣 —— 大幅度地提高軟體的生產率、可靠性和簡潔程度。我們甚至不能期望每兩年有兩倍的增長。
所有軟體活動都可分為兩種:
根本任務:打造構成抽象軟體實體的複雜概念結構次要任務:使用程式語言表達這些抽象實體,在空間和時間的限制下將它們對映成機器語言當一種高階語言出現,簡潔的語法使編碼過程更加容易;當編譯器大幅更新,使程式構建部署更加方便;當電腦記憶體擴大,使程式執行速度越來越快。這些工作都在解決軟體工程中的次要任務。產品本身的複雜性和可變性始終無法被簡化。
軟體的根本性困難包括以下幾點:
複雜性:沒有哪兩個軟體是一模一樣的,複雜是軟體的根本特性一致性:大型軟體開發中,介面、介面常常會不一致,並且隨著時間的推移會變得越來越不一致易變性:軟體構成的因素隨時都在變化不可見性:程式是看不見的,即使用圖示方法,也難以充分表現其結構,使得人們溝通面臨極大的困難沒有銀彈能夠解決這些根本困難,但有一些途徑去改善他們:
買來裝配:軟體的建造儘量複用現有的元件,避免從頭做起快速雛形:採用漸進式的增量開發模型,先產生雛形,再逐步演進,不斷成長為大型軟體,軟體雛形快速執行起來那一刻,給開發人員帶來的激勵是無與倫比的。而不是採用線性的瀑布模型:計劃 → 編碼 → 單元測試 → 系統測試 → 現場支援人,就是一切:良好的方法可以改善人的創造過程,但人的原本創造力才是軟體開發的根本簡言之,軟體的複雜體現在它是純思維的產物,是一個純抽象的概念。具體語法層面上的實現只是軟體開發中的次要問題。除非次要問題能佔到開發活動的 9/10 以上,否則即使全部次要任務的時間縮減到零,也不會帶來生產率數量級上的提高。
筆者手記《人月神話》是一本引人深思的書,它介紹的軟體系統管理流程不僅適用於軟體開發,同樣適用於其他工程領域。如律師、醫生、社會學家、心理學家等等,都為此書提出過寶貴的意見。全書中沒有一行程式碼,看起來這本書只是恰好講了一下軟體開發而已。
職業的特性,是開發人員很少去思考的問題。Brooks 告訴我們,我們喜愛開發是因為它給我們帶來了“創造的快樂”,我們產生煩惱是因為“過於追求完美”。瞭解了程式設計的這些特性,在開發中遇到奇葩問題時,會釋懷不少。
開發人員安排應像外科手術隊伍,這樣的觀點讓人眼前一亮。軟體開發的任務是無法用人月分解的,團結一致、分工明確的開發才能保證程式的概念完整性,細想起來非常合理。
程式設計到底難在哪裡?《人月神話》告訴我們,語言實現、環境搭建都只是軟體的次要問題,程式設計的根本困難是捋清程式邏輯,考驗的是設計者的思維能力。所以我們常說,演算法和資料結構才是程式的核。