作為一名軟體工程師,我們不可避免的會遇到這樣一些譬如修改別人留下程式碼的問題,又或者在別人的程式碼中新增新的功能。我們有可能不熟悉這些程式碼,或者這些程式碼可能在整個系統中與我們編寫的部分無關。
儘管這樣的工作很枯燥,也容易讓人感到無奈,但是如果能與前任開發者一起編寫程式碼,其實也會有很大收穫。這些收穫不但包括提高影響力,修改爛軟體,還能學到以前並不瞭解的知識,甚至從其它程式員那裡學到新技術。這個過程既會讓人感到鬱悶,又會從中受益,另外,我們還必須警惕一些極其容易出錯的地方。
-
我們的自我意識:我們可能會認為自己最有能力,但通常都不是。我們對要改變的程式碼知之甚少,不瞭解原作者的意圖,也不瞭解多少年有哪些因素導致這些程式碼形成,以及作者在編寫這些程式碼的時候使用了什麼樣的工具和框架。謙卑價值萬金,我們應該時刻保持這種心態。
-
原始碼作者自我意識:我們要接觸的程式碼來自另一個開發者,他/她有自己的網路、約束、最後期限等,當然也有他/她自己的想法。他/她也是一個人,當我們質疑他/她做出的決定,或者質問為什麼程式碼這麼糟糕的時候,他/她會自然地產生防禦性心理。我們應該努力讓原作者與我們合作,而不是成為我們工作的阻礙。
-
對未知的恐懼:我們很多時候會接觸到只瞭解一點點甚至完全不瞭解的程式碼。這似乎是件可怕的事情。我們得對自己做出的改動負責,但我們就像是在一個沒有光亮的黑屋子裡走來走去。我們不需要害怕,而是應該建立起一個框架,可以在裡面安心地進行大大小小的修改,同時確保我們不會破壞現有的功能。
包含我們自己在內的所有開發人員都是自然人,因此在別人編寫的程式碼上工作,會受到人性的影響。在本文中,我們會講述五種方法,利用人性的優點,從現有程式碼和原作者身上取得盡可能多的收穫,並改善程式碼既有的狀態。雖然這個清單並不全面,但應用這些方法將很大程度上確保我們在完成對別人程式碼的修改工作後,會有信心保持現有功能的工作狀態,同時又能保證新功能融合在現有程式碼中。
1. 確保有測試
對於別的開發人員寫出來的功能,它確實如預期一樣工作嗎?我們所做的修改是否會妨礙它按照預期工作?對此,唯一能讓人產生信心完成前述問題的方式就是透過測試來檢驗程式碼。我們在閱讀別人的程式碼時,會發現兩種可能的狀態。
-
(1) 沒有完成足夠量的測試。
-
(2) 完成了足夠水平的測試。
對於前一種情況,我們經常會陷入建立測試的困境;而對於後者,我們可以使用現有的測試來確保我們所做的修改不會破解原來的程式碼,同時也能從測試中大量地瞭解到程式碼的意圖。
採用新建的測試
這似乎聽起來可能很慘,我們在更改另一個開發人員的程式碼時,要對我們的行為負責,但我們無法保證更改是否會造成破壞。吐槽是沒有用的。不管我們發現程式碼是什麼狀態,只要動了程式碼,就得對其負責。因此,我們應該在修改程式碼的時候控制自己的行為。如果不想造成破壞,那就自己寫測試。
這也很枯燥,但我們可以透過編寫測試來瞭解程式碼,這也是它的主要優點。假如現在的程式碼工作良好,我們需要編寫測試,使其在獲得預期輸入的情況下產生預期的輸出。在寫測試的過程中,我們會逐漸瞭解程式碼的意圖和功能。
我們對其功能和程式碼中使用的魔鬼數字並不瞭解,但我們可以建立一組測試,根據已知的輸入產生已知的輸出。比如,透過簡單的數學運算分析成功人士的薪資。我們發現如果 30 歲以下的人每年掙大約 $68,330,就會被認為是成功的(按程式碼中的標準)。雖然我們不知道那些魔法數字是什麼意思,但我們知道它們會減少原始薪資。這樣,$68,330 這個閾值是扣除前的基本薪資。使用這些資訊,我們可以建立一些簡單的測試。
透過這些測試,我們已經對當前程式碼的工作方式了有大致瞭解。如果一個人不到 30 歲,每年能掙 $68,300就被認為是成功的。我們可以建立更多測試來確保功能在邊緣情況(比如沒有年齡或薪資)下的正確性。而且建成一套自動化測試之後,它可以用以確保我們對現有程式碼的修改不會破壞現有的功能。
使用既有的測試
在現有程式碼中存在足夠測試的情況下,我們也可以從測試中瞭解不少東西。就像我們建立測試一樣,我們可以透過閱讀測試從功能級別來瞭解程式碼是如何工作的。另外,我們也可以瞭解到原作者所理解的程式碼功能。就算測試不是原作者,而是其他人(在我們之前)寫的,它仍然可以向我們提供其他人對程式碼意圖的理解。
即使現在的測試很有幫助,我們仍然要保持謹慎。我們很難判斷測試是否和程式碼的變化保持一致。如果一致,我們就擁有理解程式碼的堅實基礎;如果不一致,我們就必須小心不要被誤導。比如,如果原薪資閾值是每年 $75,000,後來改為我們知道的 $68,330,那麼這個過時的測試可能會把我們引入歧途。
這個測試仍然會透過,但不是預期的效果。它能透過不是因為正確的閾值,而是因為它超過了閾值。如果這個測試集中包括一個測試用例,其薪資只比閾值少 $1 時傳回 false,那麼第二個測試會失敗,這表示閾值是錯誤的。如果套件沒有這樣的測試,那麼舊的資料很容易對我們瞭解程式碼的實際意圖產生誤導。當存在疑問的時候,請相信程式碼。正如我們前端所展示的,解決閾值的問題表明測試並未針對實際的閾值。
此外,參考程式碼庫日誌(比如Git日誌)來瞭解程式碼和測試用例:如果最後更新程式碼的時間比最後更新測試的時間要新得多(並且程式碼中存在重大的程式碼,比如修改閾值),那麼測試可能已經過時,需要謹慎對待。註意,不要完全忽略它們,因為它們還可能為我們提供一些原作者(或最近編寫測試的開發者)的資料,不它們可能包含過時或錯誤的資料。
2. 和編寫程式碼的人談談
在任何涉及多個人的工作中,溝通都至關重要。無論是在公司中、越野旅行中或是在專案中,缺少溝通都極易產生嚴重後果。儘管我們在建立新程式碼的時候進行溝通,但當我們接觸既存程式碼時,風險還是會增加。因為我們對既存程式碼的瞭解有限,我們所瞭解的東西有可能受到了誤導,也有可能過於片面,因此,為了真正理解現有的程式碼,我們需要與編寫它的人交談。
在問問題的時候,我們要確保問題是有針對性的,能達到我們理解程式碼的目的。
-
這段程式碼對應於系統藍圖的哪個部分?
-
你有沒有相關的設計方案或圖表?
-
有我需要註意的坑嗎?
-
某個元件或類是做什麼用的?
-
有沒有你本想寫進程式碼,當時卻沒有寫的東西?為什麼?
保持謙卑,從原作者那裡尋找答案。幾乎每個開發者都出現過這樣的場景,他/她在那裡看著別人的程式碼,問自己“他/她為什麼要那樣做?他們為什麼不這麼做?”然後花幾個小時來得出本來只要原作者回答就能得到的結論。多數開發者都有能幹的程式員,所以最好是假設我們看似糟糕的決定背後有個合理的理由(也可能沒有,但在看別人程式碼的時候最好假設他有不錯的理由;如果確實沒有,我們可以透過重構來修改)。
軟體開發中,溝通也存在一定的副作用。也就是說,一個大團隊緊密溝通,就有可能產生整體的、緊密耦合的程式碼,而一組相對較小的團隊可能會產生更多獨立、松耦合的程式碼(更多相關資訊,請閱讀康威定律解密)。對於我們來說,我們的通訊結構不僅影響我們某段程式碼,還會影響整個程式碼庫。因此,與原作者保持緊密的溝通是一個好辦法,但我們應該避免過於依賴原作者。過分依賴會讓原作者感厭煩,也可能在程式碼中產生不可預料的耦合。
雖然這可能有助於深入研究我們的程式碼,但這是我們假設可以接觸原作者的情況下。在很多時候,原作者可能已離開公司,或者不在身邊的(例如休假)。我們在這種情況下要做什麼呢? 詢問可能對此程式碼有想法的人。這並不一定是一個真正從事編碼工作的人,但也可能是周圍的某人或熟悉編寫程式碼之人的人。只要從原作者身上得到哪怕一個想法,也有可能揭示一些程式碼中的未知片段。
3. 消除所有警告
在心理學上有一個著名的概念叫“破窗理論”。想像一棟有幾扇破窗戶的建築。如果窗戶沒有修好,那麼破壞者會趨於打破更多窗戶。最終甚至有人會強行進入這棟建築,如果這棟建築沒有住人,它可能會被佔用甚至會有人在裡面生火。也可以想像一下堆積著一些枯枝落葉的人行道。很快,就會產生更多的垃圾。最終,人們逐漸會在那裡扔掉外賣的垃圾袋甚至報廢的汽車。
這一理論認為,人性會放棄照管某個似乎已經無人照管的事務。比如,人們更容易去破壞顯得凌亂的建築。就軟體而言,如果開發人員發現程式碼已經是一團糟,那麼繼續搞亂就很正常。從本質上來說,我們對自己說(儘管字不太多),“如果前任都不在乎,我為什麼要在乎?”或者“我搞亂的東西會被隱藏在這個爛攤子下麵”。
不過,這不應該成為我們的藉口。我們應該停止推卸負責。一旦我們接觸到他人留下的程式碼,就要對它負責,如果它出現問題,我們就得接受責問。為了確保我們能戰勝這一人性發展的必須趨勢,我們需要小步前進,逐步改善程式碼的凌亂狀況(更換壞掉的窗戶)。
有一個簡單的方法是去掉整個包或模組中的所有警告,刪除掉未使用或註釋掉的程式碼。如果我們以後需要這些程式碼,可以從程式碼庫之前的提交中找到它。如果存在不能解決的警告(如原始型別警告),對方法或者其呼叫新增註解。這確保我們對程式碼進行了深思熟慮:它們不是因為疏忽造成的警告,而是已經註意到的警告(比如原始型別)。
一旦我們刪除或明確禁止所有警告,我們必須確保程式碼保持無警告狀態。這有兩個主要的含義。
-
它迫使我們對我們所建立的任何程式碼保持慎重。
-
它減少了程式碼腐爛的改動,這樣警告會導致以後的錯誤。
這對他人或我們自己都有心理暗示作用,即我們是真的關心我們正在處理的程式碼。這不再是一個集合空間,其中我們盲目做出修改,提交,過後不再檢視。相反,我們要對此程式碼的責任慎重一些。這也有助於未來的發展,向未來的開發者展示:這不是一個破窗的倉庫,它是一個維護良好的程式碼庫。
4. 程式碼重構
在過去幾十年中,重構已經發展成為一個非常強大的述語,近年來它成為了變更工作程式碼的同義詞。儘管重構確實涉及到對工作程式碼的修改,但這並不是它的完整意義。對軟體內部結構進行更改而不改變其表現的行為,使其更易於理解、更易於修改。
這個定義的關鍵在於它涉及的變化並不會改變系統的行為表現。也就是說,我們在重構程式碼的時候,必須保證程式碼對外部可見的行為不會發生變化。在我們的示例中就是指我們自己修改或建立的測試集。為了保證我們沒有改變系統的外部行為,每次改變我們都應該重新編譯並完整地進行測試。
5. 讓程式碼比你發現它的時候更好
最後的方法在概念上很簡單,做起來卻很難:讓程式碼比你發現的時候更好。我們在梳理程式碼,特別是別人的程式碼時,我們傾向於新增功能,測試新功能,然後繼續,而不會關註我們為其貢獻程式碼的軟體存在糟糕的程式碼,或者我們新新增到某個類的方法可能會造成混淆。因此,本文總的來說可以歸納為如下原則: 當我們對程式碼進行更改時,確保它會比我發現它的時候更好。
如前所述,現在我們在對所修改程式碼負責,如果它有問題,我們會負責修複問題。為了抵禦生產軟體帶來的負面影響,我們必須強制自己動過的程式碼會比原來更好。我們償還技術債務而不是迴避問題,確保下一個接觸到這段程式碼的人不需要付出代價,並對其產生興趣。沒人知道以後如何,也許我們以後會感謝自己的及時修補。
>>>>>>>>>>>>> 相關閱讀 <<<<<<<<<<<<<
溫馨提示:
請搜尋“ICT_Architect”或“掃一掃”二維碼關註公眾號,點選原文連結閱讀作者原文。
文章譯自DZone有改動,點原文連結讀原文。