[工作經驗] 為何同事(自己)寫的程式碼總是很醜?

Code 可讀性低又到處重複的話,經常導致 bugs 不斷發生。一旦我們用 git blame 發現是同事幾年前寫的 code,就會想說為什麼這麼醜? 但如果是自己寫的,就...沒辦法啦,以前不知道在想什麼亂寫的。真的可以這樣想嗎?

沒辦法寫出 Good Code 的阻力

要怎麼寫出好的 code? 有些原則我們都可能聽過: KISS, DRY, YAGNI, SOLID, 甚至是 CRISP,一定還有一大堆我沒列出來的原則,這些都是不錯的 building box,但是老是有一些現實因素干擾著我們:

時間緊迫

隔天就要 release 的程式,user 今天臨時才發現一個很嚴重的 bug 要修。但仔細一看其實是簡單的小錯誤,但又發現要改的 code 剛好可以 refactor 達到 DRY,我們會怎麼做?

  • 當然要 DRY,於是改了大概一百行程式碼
  • 還是改發生錯誤的那一行就好,避免 refactor 又製造不可預期的 bug
但其實這並不是二分法,大多時候我們會評估時間還有程式碼的複雜度。有可能折衷一下: refactor 20 行信心高的 code,發個新 issue 等到下次 release 再去 refactor 剩下的 80 行 code。

不熟悉

寫程式的怎麼會不熟悉? 但一個大型 project 通常都是分工的,我們如果剛好是新進員工,或是剛踏進同事負責的領域,周圍一定圍繞著我們不熟悉的程式碼,這時我們也不敢大刀闊斧地更改同事已經寫好的程式碼。

有時候更慘,寫這段 code 的人已經離開公司,或剛好休長假,但目前要做的 issue 就剛好需要你改同事的程式碼,這時候我們可能只會追求 correctness,把其他 principles 全部拋於腦後,寫出醜到爆的程式碼,但至少功能正確。

Legacy code 也常常是我們都不熟悉的範圍,甚至是當初寫這段 code 的同事也早就忘記他當初為何這樣寫了。

太複雜

當 issue 非常複雜,牽扯到一些困難的 algorithm,但已經有明訂的 design spec 的時候,實作時才發覺是不是另外一種寫法比較好,但如果發生問題我們就很難清楚地知道到底是 design spec 有問題,還是因為我們自作聰明亂改?

與其給自己找麻煩,還不如就乖乖照著 spec 走。

當可以寫 Good Code 的時候卻又不想寫

時間不可能每次都很緊迫,我們也會漸漸熟悉某段 code,有經驗之後再複雜的 code 也都變得簡單。

但是,完了,等到我們想寫 good code 的時候,我們發現 codebase 已經充滿了 ugly code,於是我們就開始怪罪同事,你怎麼寫出這麼醜的程式碼,沒有想到的是,同事也在內心想著一樣的事:"<我們的姓名> 怎麼會寫這麼醜的程式碼?"

於是大家開始比爛,開始套用 DRY 的相反: WET principle,程式碼要複製貼上越多處越好,反正同事寫的 code 也都這樣做?

Clean Code > Good Code

我們可能會覺得 clean code 是永遠辦不到的事情,根據以上的真實狀況的確是辦不到。但如果沒有人去朝著這個目標前進,我們的 code 就永遠都是一坨爛泥。

有一篇文章 Write code that's easy to delete, and easy to debug too 說與其達到 clean code,不如寫 good code,但這邊的 "clean" 比較接近 "perfect" 的意味。

與其達到完美,不如說我們都在朝著完美前進。James Clear 在 Atomic Habits 書中就說如果我們每天進步 1%,一年下來我們就進步了 37 倍,對於 codebase 來說也是同樣的道理。


每天 1% 的 Refactoring

既然程式碼不可能隨時都保持乾淨的狀態,那只有動手去清潔才能讓程式碼越來越好。最好用的方式就是進行 refactoring,把程式碼改寫,卻又保持原有的功能性。

Refactoring 不一定只能在專屬的 issue 來做,就算是再簡單的 utility functions 也常常有改進的空間。

但我們都很怕把原有的功能破壞,又怕 refactoring 會花很多功夫,但我發現可以靠一些方法來讓 refactoring 變得更輕鬆。

每次只改一點,不能太貪心

如果改的幅度太大,當我們跑 tests 發現 fail 的時候,要 undo 哪些部分? 我們看 git diff 只會發現一大堆更改,這時候要修正問題就變得很困難。

改完一小部分也最好 stash 起來,或是發一個小小的 git commit,繼續 refactor 就會安心許多。

遵循 TDD 原則

先確定要改的程式碼有 unit test cover 到,不然就補上 tests,先跑一次確定 tests 會過,每小改一部分就再跑一次確定還是 pass。沒有這些 tests,我們很難知道是不是改到一半其實原有功能早就被破壞了。

有時候這些 unit tests 就純粹是為了 refactor 而建的,我們可能會想說 refactor 完之後就刪除,不過既然都寫了,可以增加 code coverage 的話,為何不加到 codebase 呢?

善用 Refactoring 專用的工具

例如 VSCode 可以安裝 clangd 插件,當 clangd 了解 C++ 檔案的 AST 架構後,就可以正確的按 F2 rename symbols。如果只是用簡單的 text find and replace,有時候會不小心 replace 到錯誤的 scope,我們就只好每個地方都手動 review,效率就自然降低許多。

結語

我們常常誤以為別人的程式碼寫得不好,但現實是我常發現這些 code 同時交錯著自己與同事以前寫的 code,仔細去追才發現大部分都是沿用舊的 code 再加上新功能,不一定自己寫的就比較乾淨。

如果我們不去改善舊的程式碼,不管我們再怎麼寫新的 clean code 也沒辦法讓整體程式碼變乾淨。Code 是屬於整個團隊的,那就要大家一起維護才會變得更好。

留言

此網誌的熱門文章

[試算表] 追蹤台股 Google Spreadsheet (未實現損益/已實現損益)

[Side Project] 互動式教學神經網路反向傳播 Interactive Computational Graph

[插件] 在 Chrome 網頁做區分大小寫的搜尋