Categories
程式開發

燒水泡茶與項目的組織


在舊石器的採集時期,人類以微弱之軀把兇猛的野獸變成嘴裡的食兒,憑藉的是通過語言溝通進行有效組織的團隊的力量。在軍事中,組織的好壞是戰爭勝負的決定因素之一。歷史上,無堅不摧的馬其頓方陣是軍事組織方面的一個經典的案例。 16 X 16的方陣配以薩里沙長矛,方陣較弱的兩翼部署重裝騎兵。作戰時,方陣正面象砧板一樣擠壓對方陣地,騎兵則側翼包抄錘擊。方陣為砧,騎兵為錘,是為錘砧戰術。高加米拉之戰,依靠馬其頓方陣的優秀組織,亞歷山大大帝僅以四五萬之眾,數百人的損失,擊潰了波斯大流士二十萬(號稱百萬)大軍。

本文的目的不是去探討組織行為學的大學問,而是在軟件研發的實踐中,分享一些組織團隊協同工作的個人經驗。具體地講,鎖定一個軟件項目,項目本身可大可小。當然,說大其實也大不到哪兒去,我們也不大有機會組織幾百人上千人完成一個需時一年乃至於幾年的大項目。儘管我們公司做的是交換機的嵌入式系統軟件,實話實說,也不是從頭至尾做一個完整的網絡操作系統。所以,多數情況下,不過是基於已有的產品、系統或應用,或增加一項功能,或修改一個缺陷,或解決一個問題。談到軟件項目的組織,從工程學(Engineering)的角度講,就是把不同角色的工程師聚攏在一起,各就各位,各司其職,朝著共同的目標,以最佳的方式完成項目的所有任務。所謂“最佳”,一方面指的是工作內容的多,工作效率的快和工作質量的好;一方面指的是人力和資源的省。一言以蔽之,多快好省。而瞎指揮和亂組織白白浪費了大家的時間和精力。

組織得好,諸事有序,忙而不亂。大家心情愉悅,士氣高漲。每個人都從自己最擅長的方向,最大限度地對項目舒舒服服地貢獻自己的力量,有多大勁兒就能夠使多大力。組織得不好,諸事千頭萬緒,剪不斷,理還亂。在不斷的受挫中,徬徨伴隨著恐慌。儘管每個人都很想幫忙,卻無從下手,無所適從。

論及組織的能力和才具,我想到了《紅樓夢》第十三回王熙鳳協理寧國府。說的是賈珍的兒媳賈蓉之妻秦可卿死了,王熙鳳幫忙料理喪事。大家知道,鳳姐是一個有能力,有辦法,有手段且聰明不過的漂亮女人。王熙鳳很快地從紛紛嚷嚷的雜務中理出頭緒,把喪事辦的有條不紊。她的組織其實不過是憑藉主子的權威,監督和執行一種良好的秩序,從而讓事情有序地順利進行。

軟件研發固有的複雜性決定了軟件項目的不確定性,組織起來可不像辦喪事那樣簡單。比如說,喪事中幾乎所有的禮儀活動都有章可循:誦經、弔唁和發引,什麼時候做什麼事,是固定的,不變的。只要行禮如儀,不出差錯就萬事大吉了。軟件項目雖然也講流程,比如需求分析、功能設計、詳細設計、編碼實現、功能測試、性能測試、回歸測試和發布交付等等。可是,基本上不可能遵循某個一層不變的流程,按照預期做完一個項目。

比如,不妨來看看軟件項目的計劃這個環節。給定一個需求,預估完成的日期,就不是一件容易的事兒。有時候,我們根據以往的經驗,比如曾經做過的類似功能,給出“六到八週”這樣一個大致的時間表。不幸的是,“六到八週”是一個太長的周期,長的有點兒不大靠譜。這很像天氣預報,假如報告一小時之後有雨,十有八九,基本上靠得住。但是,如果說,半個月後的某日下雪,那可就是太胡扯了。颶風起於青萍之末,南美洲的蝴蝶扇動一下翅膀,就可能引起太平洋的巨浪。不確定的因素太多,長期的天氣預報,以目前的技術水平不可能做到準確無誤。想當然爾的“六到八週”,隨口一說,彷彿一粒特效止痛藥,迅速緩解了當時被追問的壓力。到頭來,治標不治本,欠下的遲早總是要還,一旦面對慘淡尷尬的結局,輕率和孟浪的輕諾最終要付出代價。

另外,更重要的區別是,軟件工程師與寧國府的“奴才”豈可同日而語? !有人不聽話,遲到了,殺雞儆猴,王熙鳳便拿出主子的款兒命人拉出,打二十板子,以戒下次。在軟件組織裡面,雖然有管理階層的分別和規章制度的約束,根本不可能存在“打板子”的可能,最糟糕結局大不了是被迫走人或用腳投票。團隊精神很大程度上建立在個人信任的基礎上,沒有信任作為紐帶,項目組織和團隊合作便無從談起。而信任絕對是稀世的奢侈品,拼的是靠譜的(Predictable)個人魅力,包括人品,學識、遠見、能力、才具、相貌等等。得之很難,失之甚易。

軟件項目的不確定性更體現在質量和進度上。古代冶金和現代冶金給我們做了一個很好的類比:古代冶金充滿了神秘的東西,產品的品質全部依賴於匠人的智慧、經驗、技巧、乃至於於不可琢磨且不可多得的好運氣。成功的產品彷彿是藝術的創造,難以復制。比如,即使乾將莫邪重生,也未見得能夠再次鑄出傳說中可以弒王之頭的那樣鋒利的寶劍。而現代冶金技術的成熟在於固定的可操作的工藝流程。只要按照事先定義好的工藝流程,生產的進度是可以預期的。產品質量有定量的指標來檢測,合格與否,一目了然。因此,生產的進度和產品質量都是可控的,可複制的,可自動化的,沒有任何神秘主義。如果軟件的生產可以有像現代冶金那樣成熟的工藝流程,問題就簡單了,程序員的工作完全可以被自動化的機器取代。這一直是軟件工程學努力的方向,幸也?非也?一直都沒有取得真正的成功。程序員很大程度上還是像匠人一樣工作,所以,高德納(Donald E. Knuth)的巨著叫做《計算機編程的藝術》(The Art of Computer Programming)。所謂藝術者,簡單地講,對於同樣一個功能,兩個程序員實現的代碼絕沒有完全相同的可能。而程序的質量是靠測試的案例來保證的,這些測試案例也不是可測量的定量指標。

良好的組織是克服軟件的複雜性和由此帶來的不確定性的根本辦法,而有效的組織必須建立在團隊對項目的一致理解上,這一點無論怎樣強調都不過分。項目的理解是需要時間和精力的,在項目計劃時就要充分地考慮到這一點。

理解從項目需求開始。因為需求的理解不到位,導致項目返工,失敗以至於推倒從來的現象屢見不鮮。理解需求並非易事,因為用戶並不總是知道想要什麼,常常只有一些模糊的,朦朧的想法。有時候,看到實際的產品時,正切中要害,還是風馬牛不相及,才恍然大悟。和用戶反复的溝通交流,發現而不是發明用戶的需求是產品經理最重要的職責。在一些敏捷開發(Agile Development)或精益創業(Lean Startup)的方法論中,主張用快速迭代的產品原型來驗證用戶的需求,若不論成本的考量,當然是一種有效的手段。

從設計和實現的角度看,理解很大程度上是對項目的分解,即把一個大的項目細分為一個人一到三天,最多不超過五天可以完成的小任務。請記住,正如上文所說,時間越長,工作量的估計越不靠譜。這些小的任務必須是緊緻的,獨立的,自包含的,並且能夠覆蓋整個項目從設計,架構、實現、測試、直至發布交互的全部環節。分解的過程也是理解的過程:幹過的人都知道,分解即是科學,有章可循;也是​​藝術,更需要經驗的積累。毫不誇張地講,分解是軟件項目實施的最關鍵和最困難的步驟,成敗全係於此。分解完成後,才談得上科學的組織活動:把這些小任務分派給不同的工程師,檢查他們之間的依賴關係,尋找實施路線圖的關鍵路徑,防範潛在的風險。據此,制定出的開發計劃才是切實可行的,不是隨意拍拍腦袋想當然的結果。

在項目不斷向前推進的過程中,麻煩和風險在於由於不一致的理解導致的混淆,糟糕的局面是:老的混淆引起新的混淆,新的混淆疊加老的混淆,混淆套混淆,亂的象一堆麥秸。這種混淆一旦象癌細胞一樣擴散到整個團隊,項目也就完蛋了。對於人的身體​​,癌細胞總是會產生,重要的是,健康人的免疫系統能夠把他們清除;同樣地,對於正在進行的項目,混淆總是會發生,關鍵在於,團隊是否有能力消除混淆。消除混淆的同時,也加深了對項目的理解。首先,要知道哪些是清楚的,哪些是不清楚的;哪些是已知的,哪些是未知的;哪些是確定的,哪些是不確定的。這樣,就可以把混淆限制在一定的範圍內。然後,再來討論,可以採取哪些行動把不清楚變清楚,把未知變已知,把不確定變確定。

分歧和混淆相伴而生,這是很正常的事兒。分歧並不可怕,可怕的是真理越辯越糊塗,雞同鴨講,大家完全不在一個頻道上。以專業為導向,放下激動的情緒和固有的執見,站在數據和邏輯的基礎上,進行有效的溝通,使用共同語言或術語,逐步釐清分歧。這是成熟團隊的表現。正如英國首相丘吉爾在《二戰回憶錄》中所述:“我不能說在我們內部從來沒有發生過一件分歧,但我和英國參謀長委員會之間滋長了一種諒解,那就是:我們要彼此說服,而不是壓服。其所以能夠做到這一點,當然是得助於我們講的是同一的術語,擁有一大套共同的軍事理論和戰爭經驗。”以理服人,心服口服;以力服人,口服心不服,或口不服心不服。拋開行政等級的權威,基於事實的分析、歸納和推理而得到的結論才是經得起推敲的決定。

有效的溝通不僅可以化解分歧,還讓團隊成員之間取長補短,相互啟發,避免不必要的疏失。三個臭皮匠頂個諸葛亮,一加一大於二,這正是團隊協作的力量。極限編程在克堅攻難的同時,面對共同的壓力,心靈上相互依存,多了一絲慰藉。所以,不能小看這種力量。甚至局外人也有可能從不同的角度給出局內人所見不到的靈感和啟發。 Joel Spolsky在他的一篇博客The Joel Test: 12 Steps to Better Code中提到一種可用性測試的場景,即所謂的“Hallway Usability Testing”:請求路過你座位的人幫忙使用新寫的代碼,5個人就可以幫助發現95%的隱藏的問題。該文提到了Jakob Nielsen的文章Why You Only Need to Test with 5 Users, 對這一現象進行了系統的研究,證明這種方法惠而不費。

我們曾經遇到這樣的情況。從需求到設計和編碼,花了很長的時間,實現了一個大功能。代碼終於完成了,單元測試結束後,交給QA團隊開始功能集成測試。這時候,麻煩來了。測試不斷發現嚴重的問題,以至於掙扎了很長時間,才勉強完成測試計劃。更糟糕的是,此恨綿綿無絕期,問題不測沒有,一測就有。沒完沒了地改代碼,看不到盡頭。項目無法結束,發布的日期一而再,再而三地向後延宕。收拾殘局必須要澄清混淆,以明確具體的行動。在確定項目要求的子功能全部添加後,基於優先級和嚴重程度,缺陷類選(Bug Triage Meeting)幫助我們對已有的BUG分類,清除那些無關緊要的甚至和項目完全沒有關係的BUG,給出並管理一個影響產品發布的關鍵BUG的列表(MUST-FIX List)。於是,所有開發和測試工程師的工作全部集中在這個列表上。在修改和驗證這些BUG的同時,加強代碼的審核(Code Review),避免引入新的BUG。這樣,隨著MUST-FIX列表的縮小,完成項目便指日可待了。這裡面,MUST-FIX列表幫助團隊澄清了項目當前的主要問題和工作。

反躬自省,為什麼會出現這樣的混淆?大多數貌似技術的問題歸根結底都是人為造成的。豐田生產方式(Toyota Production System)的創始人大野耐一(Taiichi Ohno)所反复倡導的五問法(Five Whys)可以系統地幫助我們找到問題的根本原因。就以這個例子而言,虛構的後續劇情也許會按照Five Whys的方式這樣展開:

  • 為什麼出現這樣的混淆?回歸測試總是發現新的問題和缺陷。
  • 為什麼回歸測試總是發現新的問題和缺陷?修改問題時加入的新代碼破壞了原有的功能。
  • 為什麼新代碼破壞了原有的功能?
  1. 代碼結構混亂,難以理解。
  2. 沒有對新代碼進行仔細的審核。
  3. 單元測試做的不夠。
  • 為什麼
  1. 代碼結構混亂?沒有重構代碼的時間。
  2. 沒有對新代碼進行仔細的審核?沒有審核的時間。
  3. 單元測試做的不夠?沒有單元測試的時間。
  • 為什麼沒有時間?計劃項目時忽略了這些工作量。

糊塗和馬虎是測試的天敵,我們必須清楚的認識到,質量是由測試案例和測試計劃定義和保證的。有人把進度和質量看做一對矛盾,如果要加快趕進度,就只能犧牲項目的質量。認為匆匆忙忙趕出來的代碼,不可能有好的質量。聽起來似乎很有道理,其實,這完全是不了解質量概念的糊塗念頭。質量是產品和服務的適用性。雖然沒有定量的指標很容易地測度程序的質量,但評估程序質量還是有一把尺子,那就是測試案例(Test Cases)。當然,測試案例並不是很完美的尺子,它本身的質量大大地受制於測試工程師的素質。然而,尺子畢竟還是尺子,在實際的項目中,我們只能信任這把尺子。因此,項目再怎麼趕進度,只要測試案例能夠覆蓋代碼的功能和性能要求,通過測試的代碼的質量就可以認為是沒有問題的。因此,進度和質量本來沒有必然的關係,並不是說項目做的快了,質量就一定會出問題。

理解項目的範圍(Scope)同樣重要。無論是開發還是測試,項目中的任何工作和任務都必須有一個界限,明白界限在哪裡,才知道如何結束。孫悟空一個筋斗十萬八千里,可是,還是跳不出如來佛的手掌心。法力無邊的佛祖的手掌可以延展到無窮遠,地老天荒,要多遠就有多遠,無邊無沿。孫悟空的筋斗雲即使象光一樣快,也難以完成這樣一個沒有定義邊界的項目。界限就是責任,劉慈欣《三體》中的一個橋段生動地說明了事情的界限的重要性:

史強護送面壁者邏輯去聯合國總部。詢問下屬任務怎樣交接,得知“他們沒說”,立刻火冒三丈,怒斥下屬道:“你他媽的犯混啊,這麼重要的事兒都沒落實!”職責所關,的確馬虎不得。並非他沒有擔當的勇氣:“跟一輩子都行,但到那邊肯定是有交接的,責任分段兒必須明確!這得有條線,咔!之前出事兒責任在我們,之後責任就在他們了。”

計劃常常趕不上變化,用戶可能沒完沒了地提出新的要求。像滾雪球一樣,項目越做越大,目標越來越模糊。加班加點地玩兒命趕進度,交付還是遙遙無期。解決的辦法很簡單,就是“分段兒”。項目分成若干階段,高優先級的任務放在第一階段,其他的部分放在後續的階段。因此,中美曠日持久的經貿談判邊打邊談,跌宕起伏,證明這是一個艱難且複雜的過程。收穫目前取得的部分成果,分階段簽署經貿協定,從項目組織的角度看,是正確和明智的做法。

毫無疑問,書面文檔是幫助我們梳理思路、去除混淆、加深理解、分清責任和追溯檢查的必要手段。軟件項目的計劃、需求分析、功能設計、詳細設計和測試計劃都必須有相應的文檔跟踪記錄。戰爭和國事常常以項目的方式組織,因此,丘吉爾在《二戰回憶錄》中指出:“幾乎我所有的工作都留有記錄,我所發出的一切命令,我所擬議的一切調查報告,我所起草的一切電文等等均有記錄可查。”另外,丘吉爾致伊斯梅將軍、帝國總參謀長、和愛德華布里奇斯爵士的備忘錄中又強調:“我發出的一切指示概用書面,或在事後立即用書面的形式加以證實,在國防問題上,一切被認為是有我決定的事,除有書面記錄的以外,我概不負責,希望你們清楚地了解這一點。”好記性不如爛筆頭,文檔在項目中的作用是不可替代的。否則,沒有文檔就談不到良好的組織。只是一味地跟著感覺走,說走咱就走,想往哪兒走就往哪兒走,走到哪兒算哪兒。沒有任何懸念,這種流浪者的浪漫很快就會演變成災難,除非你是在做一個小孩子過家家那樣幼稚的項目。

總而言之,我認為,讀懂和理解項目中的每個環節,包括需求、設計、編碼和測試,是項目組織的頭等大事;然而,在世俗者的眼裡,任務的安排和監管才是重頭戲。前者偏重於技術,是工程師的工作範疇;後者偏重於管理,是有流程癖的人們的想頭。值得警惕的是,裝腔作勢的管理過多地干預技術,不僅令人厭惡,更可能導致嚴重的後果,外行領導內行,混淆由此而生,而團隊也會不可挽救地滑向官僚主義。

談到任務的安排,記得上小學時,讀到華羅庚的一篇經典課文《統籌方法》,文章寫的很淺顯,道理講的很透徹。讓我們重溫一下文中提到的燒水泡茶這個例子,看看怎樣安排工序才最有效率,不窩工。前提條件是“開水沒有;水壺要洗,茶壺茶杯要洗;火生了,茶葉也有了。”可選的辦法如下:

  • “辦法甲:洗好水壺,灌上涼水,放在火上;在等待水開的時間裡,洗茶壺、洗茶杯、拿茶葉;等水開了,泡茶喝。”
  • “辦法乙:先做好一些準備工作,洗水壺,洗茶壺茶杯,拿茶葉;一切就緒,灌水燒水;坐待水開了泡茶喝。”
  • “辦法丙:洗淨水壺,灌上涼水,放在火上,坐待水開;水開了之後,急急忙忙找茶葉,洗茶壺茶杯,泡茶喝。”

一目了然,辦法甲最好,用時最少。這其實是從技術的角度組織工序或任務的方法論,用到了數據結構和算法中的有向圖(Directed Graph)和拓撲排序(Topological Sorting):根據任務之間的依賴關係,尋找完成所有任務的路線圖的關鍵路徑(Critical Path)。如上文所說,我們基本上不會遇到“幾百幾千,甚至有好幾萬個任務”的大項目。大多數情況下,一個項目包括的任務並不比燒水泡茶的工序更多。即使某個項目被細分成更多的任務,用一個甘特圖(Gantt Chart)就可以輕而易舉地把全部任務的計劃安排標識得清清楚楚,而生成甘特圖的工具遍地都是。我不曾遇到這樣的項目,由於“關係多了,千頭萬緒”而出現“萬事俱備,只欠東風”的情況;也沒有“臨事而迷”導致項目失敗的案例。不過是凡事小心謹慎些罷了。所以,一般地,關於任務的安排,我不認為是什麼了不起的大學問,真的沒必要“小題大做”。然而,誤解和混淆導致項目的拖延和失敗卻是司空見慣。總而言之,流程和管理的環節越簡單,效率越高,大家都全神貫注在代碼上,哪裡還有閒工夫無事生非呢? !回歸事物的本質,更多的精力應該放在設計、編碼、測試這些和技術與生產直接相關的工作上。

我們都有體會,做一個軟件項目,至始至終都在變來變去,需求會變,設計會變,實現當然也得跟著變。並不存在以不變應萬變的靈丹妙藥,這就是為什麼必須保持項目組織的靈活性。作為起步的公司,對用戶需求和應用的變化,我們的很大優勢在於有能力做出快速敏捷的反應。這當然得益於小公司項目組織的彈性。比如,用戶新的需求或問題很快就從支持團隊和銷售團隊傳遞到工程師團隊,我們從不同的開發和測試部門抽取相關的工程師拼湊起一支臨時的團隊,項目完成後,臨時團隊就隨之解散了。在一個團隊裡,如果不是在解決問題,就是在製造問題。所以,成功不必在我,盡可能地縮小這種臨時團隊的規模。只有真正幫得上忙,才被拉進臨時團隊。任務完成了,馬上退出。甚至,有的人都不知道自己被拉進一個臨時團隊,服務於一個臨時項目。但團隊中的每個人都清楚地知道自己在做什麼,彷彿上緊了的發條,處在全負荷的忙碌的狀態。事實證明,這樣的臨時團隊具有強悍的戰鬥力,效率奇高,尤其對於緊急的項目,常常是攻堅克難的殺手鐧。

領導項目的人不能只是發布指令和分派任務。正如上文所說,必須有能力幫助團隊理解項目,澄清遇到的混淆。因此,為了保證項目的順利實施,最好的辦法是由最懂的人來領銜。另外,領導項目的人也是項目的負責的人(Owner),要擔當起自己的責任。所謂負責的人,實質上是有一個就好。否則,人人都想插手,而需要做出決策時,大家都怕擔責,靠邊閃,還談什麼負責? !一個項目無論怎樣精細的劃分,都有可能存在覆蓋不到的地方。這些三不管的瑣細的事物需要項目的負責人特別小心在意,避免因小失大。項目遭受挫折時,無論多麼痛苦,領導者都務必首當其衝,扛起失敗的責任。文過飾非沒有任何意義,甩鍋給他人更是可恥的行為。因此,面對戰爭的勝負,丘吉爾說:“只有我們在國內的人才能夠權衡世界大事的輕重緩急,而最後責任則應由我們承擔。”對於前線的指戰員,“我一向遵循這樣一個原則:對於軍事指揮員不應該從效果方面,而應該從努力工作的質量方面加以判斷。”

最後,容我講一個實際的例子來結束本文。我們在開發一個新的版本時,常常有同事抱怨性能的問題,感覺系統運行變慢了。但是不能僅靠感覺就說系統變慢了,必須要有具體的測試案例證明系統變慢了。比如針對某個同樣的操作,系統響應時間比以前版本如何。因此,沒有必要把開發工程師匆忙地拉進來,憑著臆測來解決這個也許是莫須有的問題。並且,實際上我們也不知道該讓哪個開發工程師來看這個問題。因此,問題還得留在測試工程師這邊,他們需要給出一個證明系統變慢了的具體案例,作為開發工程師開始工作的契入點。案例找到了,重啟系統的某個應用,用時竟是原來版本的兩倍。接下來,問題轉到了和這一應用相關的開發工程師那裡。專業的方法和工具是攻克性能問題的關鍵,但沒有具體的案例,再好的方法和工具都是毫無用處的擺設。利用工俱生成測試案例過程的CPU Profiling,最後發現,某些系統調用耗時比原來多了2至3倍。問題的根本原因竟是在內核(Kernel)。仔細比較兩個版本的內核,性能的下降原來是因為引入了修改漏洞meltdown的補丁程序(Patch Code)造成的。需要強調的是,正是因為找到了一個具體的案例,我們才能夠有序地組織不同的測試和開發工程師先後介入,得以很快地定位並解決問題。

成竹在胸,才能舉重若輕,治大國如烹小鮮,談笑間神州大定。若把做項目比作下廚,那麼,心下明白,化繁為簡,像是煮一壺清茶;心下糊塗,纏夾不清,卻是活得一盆好糨糊!

作者簡介

賈彥民,目前就職於 PICA8。本科與研究生就讀於重慶大學,2007 年獲中國科學院軟件研究所計算機軟件與理論博士學位。愛讀書,喜歡爬山與攝影(歷史、文學、數學)。