陳 暉 李紅波 柏立悅
(浙江中控技術股份有限公司)
軟件開發周期逐漸成為相關企業考量的關鍵因素,這也是快速迭代的開發模式在如今的應用軟件開發中越來越流行的原因之一。 對于一個工業領域的公司來講,軟件質量是重中之中。 那么在工業信息化程度日漸提高的今天,保證軟件質量與實現快速交付成了工業軟件公司的主要矛盾。 因此,筆者介紹了在遇到工業應用軟件開發效率瓶頸時, 一種分析和尋找根本原因的思路。工控行業一直以來的主流技術棧為C/C++,特別是應用層的軟件主要集中在C++,因此筆者針對C++軟件開發中遇到的相關問題,分析造成這些問題的根本原因。
對軟件項目經理來說,最重要的是把握軟件的發布節點。 關鍵點在于要對總任務量和總生產力兩個方面進行準確評估,以確保兩者相契合。
總任務量即完成軟件項目需要的全部客觀有效工作量,計算方式可簡化如下:

如果想要進行更細致的評估,則需要采取更復雜的方式,如類比建模法[1]、階段評估法[2]等。這些方法基于以往類似項目的經驗進行推導,往往能較準確地對項目的總任務量進行評估。
總生產力指的是一個團隊在一定時期內實際可以提供的開發成果產出:

由此可見,想要精確地掌握時間節點,不僅要準確評估總任務量,還要準確地評估出團隊的總生產力,以確保其能覆蓋項目的總任務量。 而想要準確評估總生產力,就必須準確地了解每個開發人員在特定領域的能力。 一旦某個人的實際生產效率與預估的發生較大的偏差,那么關鍵路徑就會大幅變長。 而如果在項目后期才發現偏差,那么即使投入人海戰術也不能對加快進度有明顯的改善,甚至會產生負面作用——因為原先的開發人員需要對新加入的開發人員進行培訓,并且更多的人意味著更高的溝通成本[3]。
因此,準確地評估每個開發人員可以做出的貢獻,消除項目中的不穩定因素,是保障軟件項目更快、更及時完成的關鍵點。
一個優秀的編程者的標志在于能夠快速寫出精簡而又清晰易懂、符合客戶需求、具有良好擴展性的代碼。 這樣的編程者們,是一個項目組的核心骨干,往往可以承擔一個軟件項目中大量核心代碼的編寫工作。 Sackman H 等早期的研究指出,水平較差的編程者的編碼時間最多可達優秀編程者的25 倍,調試時間則最多為28 倍[4]。
而筆者也在已開發完成的軟件項目中做過統計,約20%的核心技術骨干可以完成約50%的功能開發, 而考慮到這些代碼重要程度的權重,其貢獻率最高可以達到80%。 由此可見,每個軟件開發人員所能做出貢獻的差異很大,所以找到合適的評估方法就顯得尤為重要。
開發貢獻率的評估方法有很多,但大多是基于一個開發人員的已有開發成果進行推斷,例如傳統的方法有考察日均代碼行數、千行代碼缺陷率、代碼圈復雜度,而近年來則有人嘗試更復雜的建模方法,來達到更準確的評估目的。 筆者將這些方法及其缺陷整理如下:
a. 日均代碼行數。 即考察開發人員每天產出的代碼數量。 該方法完全忽略了代碼的質量。
b. 千行代碼缺陷率。 即通過測試結果來考察開發人員所寫代碼在邏輯上的質量。 但該方法忽略了開發過程的差異性——如果相同的功能,人員A 比人員B 抽象提煉能力更強,在更好地考慮了復用性、擴展性、簡潔性后,產出的代碼比人員B 產出的代碼數量要少,在缺陷數同樣的情況下,人員A 的千行代碼缺陷率比人員B 要高,但顯然人員A 產生的效益比人員B 要高。
c. 圈復雜度。 即考察一個代碼模塊的復雜度。 在沒有對照的情況下,該方法也不能很好地反映出開發人員的貢獻率,因為它反映的是一個模塊最后實際產出的代碼呈現出的復雜度,而不是其應有的復雜度,而一個模塊本身應有的復雜度是很難衡量的,所以也就無從比較。 除非兩個程序員都實現同一個模塊的代碼,那么這兩份代碼的圈復雜度能較好地體現出他們技術水平的差距(圈復雜度越低的反而越好,表明其能把復雜的事情簡化)。
d. 數學建模。 以上的方法單一應用時都有相當大的局限性,因此近年來有人試圖使用數學建模的方法把各種因素結合在一起,并使用人工智能的方法得出一套實際可用的評價系統。 例如Ren J L 等基于其研究成果開發的Merico 評價系統將代碼的貢獻值分成結構化數據和非結構化數據,并用機器學習的方法將這兩種數據擬合在一起,得出最終的貢獻值[5]。其中結構化數據主要由一個函數的被調用次數決定,而非結構化數據則由機器學習來判斷某條代碼提交的重要性。Merico 評價系統確實在一定程度上解決了編程貢獻值評估難的問題,但也有其局限性。 例如該系統判斷結構化貢獻值的方式是統計一個函數的被調用次數,其驗證手段是考察3 個開源社區貢獻者的開源代碼來實現的。 由此可知其局限性在于,首先,開源社區貢獻者的開發水平屬于中上游,不具有全面的代表性;其次,一般地,在一個大中型公司中, 軟件的模塊劃分更為細致,基礎模塊(通常來說代碼質量、穩定性及可閱讀性等要求都相對更高)比上層的應用模塊有更多的機會被調用到,但無法就此推斷出負責基礎模塊的程序員其代碼貢獻率就一定比負責應用模塊的大。 而基礎模塊和應用模塊通常是不同的項目組(例如公共組件組和產品項目組)負責的,對于同一個組的成員來說,他們負責的模塊層次是相對扁平化的,那么函數被調用的幾率實質上是均等的。 所以實際上這是一個從結果推出條件的偽命題,因為越底層越基礎的模塊,通常會被多個產品線共用,其重要性不言而喻,一般會讓技術功底深厚的老員工來擔綱,那么他們編寫的代碼自然而然地具有更好的質量,而不是因為他們的接口被使用更多就認為他們的代碼質量更好。
由上文可見,目前大部分方法都不能客觀準確地評估貢獻率,實際日常操作也仍是以項目經理依據對團隊成員能力上的印象進行判斷為主。如果不是一個參與架構設計和日常代碼評審的項目經理,很難做到準確公平地評估每個組員的真實貢獻。 事實上這種情況在企業中很常見,由于項目管理流程的需要,項目經理無法在具體開發工作上投入過多精力,一般會把這些任務轉交給組內的技術專家或資深員工,這就造成項目經理對開發人員的真實水平很難有準確的把握。
通常在項目初期,項目經理會較均勻地分配任務,原因是出于對團隊成員的信任,避免他們認為被邊緣化。 而隨著開發過程的推移,在最初任務分配均等的前提下,高水平開發者的進度一般領先于其他員工。 此時項目經理出于項目進度考慮,基本會轉移部分可分離的任務到骨干員工身上。 以上的做法往往導致高水平的開發人員不堪重負——不但要承擔架構設計,還要負責大部分功能的實現。 而低水平的開發人員只能在邊緣的功能上做文章, 對于他們的技術成長收效甚微,即便公司給予培訓上的支持,但缺乏實踐使得知識很難得到沉淀。 如果給予他與其水平不符的任務,然后花費高水平開發者的時間來仔細評審他們的設計和代碼,確實能更快速地提高其水平,從長遠來看也更有意義,但也得忍受當前項目數倍的開發周期和更多的錯誤數量。
因此,一個有時間節點壓力的項目型團隊就容易陷入到一個不好的循環中來,整個團隊總是疲于應付開發任務,其整體水平只能以一個很平緩的曲線上升。 這對于公司和員工來說是一個雙輸的局面:對公司來說,一個開發團隊的成本日益上升,但其開發水平并沒有得到相應程度的提高,其價值其實是日益下降的;對于員工來說,在相當長的時間內沒有得到成長,那么對自身的發展和職業生涯是很不利的。
可以推斷,軟件項目周期的穩定性主要是依據非核心開發人員。 一個項目的關鍵因素在核心員工身上, 但關鍵路徑卻體現在非核心員工身上。 如前文所述,項目經理依賴于工作經驗豐富的核心員工來保障項目進度,那么相反地,非核心員工在開發不同產品或不同模塊時所表現出的不穩定性正是影響項目進度的最大因素。 他們更容易受開發平臺、產品特點的影響,這種不穩定性就反映在他們的開發周期和開發質量上。
綜上所述,非核心員工的開發效率、質量相對不穩定,導致項目組的資源配置也不能達到最優化,因此項目的進度風險就會變大。
首先對兩個不同水平的C++開發人員所能做出的貢獻有巨大差距這個現象做出一個分析:
a. 面向對象比面向過程在設計上需要更多的努力和領悟[6]。對于大部分C++程序員來說,在學校學習的課程以C 語言居多,或者學習過C++課程,但并未領悟面向對象的思想,在剛投入開發工作中時仍把C++當成C 語言來使用。 事實上領悟面向對象很難,也需要一定的天賦,因為面向對象只是一個概念, 其背后蘊含的是抽象、提取、 總結事物內在邏輯和聯系的本領與技巧,即便一個有多年C++使用經驗的程序員都不敢宣稱完全掌握其精髓。
b. 新手往往知識面不夠豐富。對于一個核心開發人員來說,他可能兼具編程知識的廣度與深度,擁有不同項目的開發經驗,負責過不同領域的產品和模塊,文件讀寫、網絡通信等不同方面的知識對于他們來說已經成為常規武器。 而對于一個新手來說,處理產品的業務邏輯就已經耗費其精力,其他方面不可能以一種十分自然、毫不費力的方式把它們處理正確。
c. 新手往往自信心不足。 新手由于缺乏項目經驗,即便已經儲備了很豐厚的理論知識,但在實際開發過程中, 對自己寫的代碼的不自信,追求盡量少犯錯,依然會用一種比較初級但自己更熟悉的方式去編寫代碼。 但在商業化項目的開發中,邏輯上的正確往往是不夠的,性能、可維護性等方面都是需要考量的因素。
以上原因使新手在開發過程中會遇到很多困難和疑惑。 以下是一個案例,該段代碼用于實現查詢用戶列表中是否有“admin”用戶的功能:

運行這段代碼雖然能獲得正確的結果,但可從一個代碼評審者的角度來討論其不足之處:
a. bFound 變量多余,iter 和userIdList.end()比較即可實現同樣的效果;
b. iter++改為++iter 更佳,避免每次產生一個臨時變量和賦值動作;
c. 在if(*iter==“admin”)分 支 中 應 該 添 加break,否則找到“admin”后仍會繼續執行,導致浪費計算機的性能;
d. for 循環終止條件中的userIdList.end(),應該使用臨時變量進行緩存,以避免重復的函數調用削弱性能。
一個簡單功能的實現,卻有至少4 處改進之處,且b、d 兩條是絕大多數新手沒有理解和掌握的。 在一些服務器端的開發中,這些缺陷可能造成一定程度的性能問題,而針對這些缺陷進行優化后,某些情況下性能甚至可以提升20%以上[7]。
因此, 一個新手或者低水平的開發人員,與資深開發人員的差距是全方位的,如果要求他們一開始就去處理編程的各個方面, 如數據結構、網絡交互及操作系統等, 那么他們便會顧此失彼,從而影響編程效率和質量,最終開發周期變長甚至成倍增長。 如果想減少這種不利的因素,那么就必須創造環境來盡量避免新手同時面對多個他不熟悉的領域,從而使其精力足以應付一定的不確定性,并穩步地成長。
筆者闡述了把控項目進度的關鍵因素為團隊總生產力的評估,介紹了評估開發人員貢獻率的方法及其局限性,指出開發貢獻率的評估難點是軟件項目發生進度偏差的一大原因,進一步指出造成偏差的深層原因是新手在面對多個不熟悉領域時所表現出的不穩定性,這些為企業優化軟件開發流程、改進開發環境提供了一種思路。