知乎回答:入職后發現項目組代碼異常混亂,是去是留?

yn6e 9年前發布 | 13K 次閱讀 代碼

原文  http://www.gulu-dev.com/post/2015-05-09-legacy-code

知乎回答:入職后發現項目組代碼異常混亂,是去是留?

此文是俺在知乎上對這個問題的回復:

知乎鏈接: 入職后發現項目組代碼異常混亂,是去是留?

樓上的@蔡磊兄分析得很清楚。對重寫代碼可能帶來的各種風險,俺很認同他的觀點,也就不再多嘴了。然而,蔡磊兄整體的論調呈相對消極的姿態,從不同角度得出這一個結論—— 重寫代碼是不可取的 。這里俺更愿意換個角度,談談積極的一面,也發出一點不同的聲音。

再談重寫

在展開討論之前,先拋出結論:對于商業價值已在明顯縮水中的項目,大動干戈意義確實不大;而對于一個急速成長或穩定運行中的項目而言,有計劃有步 驟地整理和翻新,是非常必要而且很有講究的。各種在初期不會顯山露水的局限和瓶頸,會隨著項目規模的成長不斷凸顯,此時,若不能水漲船高,通過積極手段逐 步改良其內部實現,隨著貪一時之快的補丁越積越多,系統將日趨僵化,后果自不必說。

對于重寫本身,我們也大可不必畏之如虎。雖說正如 Joel 所說,“廢棄現存可運作的代碼跑去完全重寫”幾乎總是個錯誤的決定,但“中小型系統或組件的受控重寫”卻是很常見的改良系統的手段。比如這個:

Rewriting a large production system in Go

在這篇文章里,Matt Welsh 同學介紹了自己是 如何使用 Go 來完全重寫一個 Google 的生產環境下的系統 的。推薦感興趣的同學前往一讀。此外我讀過一篇講 非死book 是如何決定和實施一個服務的重寫的,還有一篇是 GitHub 如何保證重寫的新系統和老系統之間無縫平滑過渡的,這兩篇都蠻有趣,可現在一時之間找不到鏈接了,不過沒關系,后面我會介紹一些我仍記得的思路。至于微軟 是如何(反復)重寫 Windows 組件的大家可以到 The Old New Thing 上去看,老的例子有 GDI, Direct3D, Visual C++, MSE,新的例子則有 Edge (IE)。

關于從 IE 到 Edge 還有一個有趣的曲折——其實自動IE8之后微軟就動了重寫的心思了,在 IE9 中,微軟嘗試了所謂的“完全重寫 (Rewrote From Scratch)”(可以參考這個鏈接: Inside IE9: How Microsoft rewrote its browser from scratch ) 這個版本里把原來的 Javascript 解釋器替換為一個新寫的能生成本地的 native code 的執行引擎,把圖形系統內的大多數渲染工作挪到了 GPU 上,還有一個全新的布局引擎 (With IE9, we rewrote our layout engine from scratch) 但是 (這里才是重點),就連這種程度的重寫也還是不夠的,微軟后來才意識到,人們的關注點變了,互聯網的焦點已經在向移動端轉移,于是才有了現在更徹底的重寫——Windows Edge(原來的 code name 叫 Project Spartan)

好了,在了解到在有重大商業影響的項目中,重寫也不是什么偶然的事情之后,我們就會明白,已有的 Legacy Code 并非刻在石頭上的神諭,嘗試著去逐步改進自己維護的代碼,重點不在于“能不能”,而在于“ 應不應該這么做 ” ,“ 如果需要,應該做到什么程度? ”,還有“ 實踐中,如何以受控的方式去實施 ”,這樣才能在避免失控的悲劇的同時,周期性地拋下越來越沉重的負擔,讓系統得以輕快而長久地健康運行。

Legacy Code 在不同的人眼里長什么樣?

在新來的萌寵小師妹眼中,現有系統是這樣的:

知乎回答:入職后發現項目組代碼異常混亂,是去是留?

在曾經滄海的大師兄眼中,現有系統是這樣的:

知乎回答:入職后發現項目組代碼異常混亂,是去是留?

在新加入的同事眼中,大概還能分得出的幾坨貌似解耦的模塊,(由于各種不足為外人道的潛規則)在老同事眼里,其實跟一整坨也沒什么區別。不同的 是,老同事對系統的全貌和脈絡,至少還有邏輯上的概念,知道整個系統的重心和支撐點在哪兒,知道那茂密毛發下面隱藏著什么(……)。

所以,在你感覺眼前的代碼比較混亂的時候,想想這只羊吧。雖說不管毛長毛短,能擠奶的就是好羊,可為了讓自己以后能活得輕松點,給它洗洗澡剪剪毛啥的還是值得考慮的。

標準的受控重寫 (Managed Rewrite) 應該長什么樣?

知乎回答:入職后發現項目組代碼異常混亂,是去是留?

當你決定擼起袖子大干一場的時候,如果沒有上圖這樣的詳細計劃和分步的布線圖,而只是盲人摸羊,走到哪里算哪里的話,會很容易掉到坑里的,達到預期的可能性也會大大降低。請注意,這張圖最重要的價值在于,它把一個風險很大的單步決策 拍碎成了許多細碎的小步驟 ,每個步驟彼此獨立,需要承擔的風險或重要性用顏色表示。這樣一方面清晰地知道自己的目標和進展,不會迷失方向;另一方面也隨時保留可以撤銷至某個安全點上的能力。

進化 (Evolution),而非革命 (Revolution)

知乎回答:入職后發現項目組代碼異常混亂,是去是留?

這與上一張圖表達的實際上是一個意思。正如在做代碼重構時的策略那樣,我們在戰略上也把大的目標切成小塊,始終謹慎地保持小步前進,始終避免做過大的決定,始終把風險控制在能接受的范圍內。

平行實現 (Parallel Implementations)

平行實現是完全重寫某個服務的重要手段,關于這個主題 John Carmack 曾寫過一篇非常精彩的文章 "Parallel Implementations" ,現在由于 altdevblogaday 這個網站關閉鏈接已經失效了,不過大家可以在 Internet Archive 這個網站上找回 此文章在 2012 年時的快照 。此文非常精彩,強烈推薦。

此文是這樣開頭的:I used to Code Fearlessly all the time, tearing up everything (!!!) whenever I had a thought about a better way of doing something. There was even a bit of pride there — “I’m not afraid to suffer consequences in the quest to Do The Right Thing!” 三個黑色加亮部分體現了卡馬克同學一貫的價值觀,俺寫到這里心里默默為卡神點了三個贊。

整個文章的精華在這一段:

What I try to do nowadays is to implement new ideas in parallel with the old ones, rather than mutating the existing code.  This allows easy and honest comparison between them, and makes it trivial to go back to the old reliable path when the spiffy new one starts showing flaws.  The difference between changing a console variable to get a different behavior versus running an old exe, let alone reverting code changes and rebuilding, is significant.

還有這一段:

There are two general classes of parallel implementations I work with:  The reference implementation, which is much smaller and simpler, but will be maintained continuously, and the experimental implementation, where you expect one version to “win” and consign the other implementation to source control in a couple weeks after you have some confidence that it is both fully functional and a real improvement.

文章的最后,卡神寫到:

Every single time I have undertaken a parallel implementation approach, I have come away feeling that it was beneficial, and I now tend to code in a style that favors it. Highly recommended.

正是因為這幾段都很精彩,所以原封不動摘錄于此。(其實要不是篇幅所限,真想干脆全文摘錄了) 平行實現的細節我就不展開講了,大家直接看原文好了。

幽靈替補 (Ghost Alternative) ,柔性服務和灰度發布

“幽靈替補”是我為前文提到的一種做法隨便起了個名字,方便記憶。前文提到,在一篇講 GitHub 的某服務的重寫過程中,他們把該服務的 新實現 掛在 老實現 上一起跑(正如附身的幽靈),當有新的請求過來時,新老兩套系統同時開始處理,但最后的輸出仍采用老系統的結果,新的系統輸出結果被記錄下來,與老系統的結果比對。千萬次運行下來后,新系統得到了足夠的考驗,錯誤率低到一個程度后,再淘汰掉老系統。

寫完了才發現 這里(Parallel Adoption) 有 wikipedia 上對此方法的綜述,也可供參考。

這里是一個典型的例子:

知乎回答:入職后發現項目組代碼異常混亂,是去是留?

至于最近說的比較多的柔性服務和灰度發布,可以參考這兩篇:

這些技術,都可以幫助我們實現新老系統之間順利的遷移和過渡工作。

總得來說,對系統的維護者來說, 一味地積極重構,和一味的消極補丁,都是不可取的 。有經驗的開發者,會更準確地評估權衡改動帶來的風險和工作量。如同我在“ 一個有趣的交互 bug ——兼談游戲的引導系統 ”一文中提到的那樣,大部分問題都可以由淺至深地分析出多個不同的解決方案,以便于在不同的情形下去取舍和平衡。

最后這句話,與大家共勉吧 :)

知乎回答:入職后發現項目組代碼異常混亂,是去是留?

[完]

Gu Lu

[2015-05-09]

 本文由用戶 yn6e 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!