「讓 AI 幫忙寫程式碼」的人越來越多了。
我也是這樣的工程師之一,不過在讓 AI 寫程式碼時,我常常會覺得:「應用程式本體的程式碼還好,但程式碼註解和測試程式碼總覺得不太對勁。」
另外,在做程式碼審查時,我也越來越常看到這樣的情況:「大概是把 AI 寫的註解直接採用了吧~」
因此,這篇文章要列出最近在程式碼審查中指出的 9 種「不太理想的程式碼註解」。
(至於不太理想的測試程式碼,我想改天再另外寫一篇)
另外,程式碼範例雖然是用 Ruby 寫的,但概念在其他語言也一樣。
所有範例程式碼都是為了說明而製作的虛構內容。
不只是 AI 寫的註解,「看程式碼就知道」的事情本來就不需要寫成註解留下來。
# ❌ BAD(做了什麼看程式碼就知道)
def total_price
# 單價 x 數量的總和作為總金額回傳
items.sum { |item| item.unit_price * item.quantity }
end
# ✅ GOOD(不要寫註解!)
def total_price
items.sum { |item| item.unit_price * item.quantity }
end
如果真的要留註解,也請限定在回答讀者「為什麼要這樣做?」的內容。
# ✅ GOOD
def total_price
# unit_price 有時會包含小數,
# 因此轉成 BigDecimal 以避免四捨五入誤差
items.sum { |item| item.unit_price.to_d * item.quantity }
end
註解基本上要寫在「想要說明的程式碼前一行」。
因此,方法定義前面應該寫的是該方法的概要。
像下面這樣,把方法內某個特定處理寫在方法定義前面,就有點奇怪。
# ❌ BAD(「累了就不做任何事」是 play_guitar 方法的概要??)
# 累了就不做任何事
def play_guitar
return if tired?
guitar.play
end
註解要遵守「寫在想要說明的程式碼前一行」這個原則。
所以,下面這樣寫才是正確的。
# ✅ GOOD
def play_guitar
# 累了就不做任何事
return if tired?
guitar.play
end
(另一種解法)如前所述,「看程式碼就知道的資訊」本來就不需要寫成註解留下來!
# ✅ GOOD(看得出來的程式碼就不需要註解!)
def play_guitar
return if tired?
guitar.play
end
請看下面這段註解。
# ❌ BAD
def ride_bike(rider)
# 騎腳踏車時必須戴安全帽
raise NoHelmetError unless rider.with_helmet?
大概是在某個時間點,對既有程式碼新增了「必須戴安全帽」的檢查。
但是,上面這段註解只在「安全帽從可選變成必須的那一刻」才有意義。
如果每次規格從「A 變成 B」時,都把這種變化一條一條寫進註解裡,程式碼就會變成滿滿都是註解。
這種變更內容應該寫在 commit 訊息或 Pull Request 說明欄,而不是寫成程式碼註解。
除非有特別的理由或目的,否則沒有必要特地把「新規格」寫成註解留下來。
# ✅ GOOD(不需要特地把變更後的規格寫成註解留下來)
def ride_bike(rider)
raise NoHelmetError unless rider.with_helmet?
例如,如果有一個 cancel_payment 方法,程式碼讀者只要知道「呼叫這個方法就能取消扣款」就夠了。
但有時候會很仔細地註解「這個方法什麼時候、在哪裡、在什麼情況下、為了什麼目的被呼叫」。
# ❌ BAD
# FooController 和 BarController 在...時
# 可能會...,所以在...之前先取消扣款
def cancel_payment
像上面那樣把呼叫方式或被呼叫的地點寫進註解後,當呼叫這個方法的地方增加時,就得一直補充這段註解。
這實在太沒意義了,所以不需要把呼叫方式或被呼叫的地點寫成註解留下來。
# ✅ GOOD(不要把呼叫方式或被呼叫的地點寫成註解)
def cancel_payment
如果真的需要註解,就寫在呼叫這個方法的一側。
class FooController < ApplicationController
def destroy
# 因為...的情況下可能會...,所以在...之前先取消扣款
cancel_payment
# ...
end
end
如果用了程式碼中沒有出現的字詞或術語,讀的人會一瞬間不知道你在講什麼。
# ❌ BAD(咦,「humbucker」是什麼??)
# 如果是 humbucker,就比較不會有雜訊,所以很安心
use(:noise_suppressor) if guitar.pickup == :single_coil
請讓程式碼和註解的用語好好對應起來。
# ✅ GOOD
# 單線圈容易有雜訊,所以用降噪器來抑制雜訊
use(:noise_suppressor) if guitar.pickup == :single_coil
即使看起來像樣的註解,仔細一讀也常常會覺得「咦?好像不太通順??」
# ❌ BAD
def prepare_for_cancel
# 取消時會停止草稿訂單的確認與自動續訂,所以對草稿訂單不做任何事
return if draft?
上面的句子不太清楚到底是「把草稿訂單確定下來,並停止自動續訂」,還是「停止草稿訂單的確定,並且停止自動續訂」。
而且如果是後者,「停止確定」這種說法本身也不太自然。
不要只匆匆看過去就想著「大概沒問題吧」,而是要確認這段話在中文裡是否真的能清楚傳達意思。
如果有不自然的中文,就應該由人負責重新改寫。
# ✅ GOOD
def prepare_for_cancel
# 草稿訂單在取消時會自動刪除,也不會產生扣款,所以對草稿訂單不做任何事
return if draft?
如果你看到這種註解,會怎麼想呢?
# ❌ BAD
# 這個類別在畫面上沒有被使用
class Attachment < ApplicationRecord
「如果畫面上沒用到,那是在哪裡用到的?」或是「如果完全沒人用這個類別,那是不是該刪掉?」是不是會冒出這些疑問?(我會)
寫註解時,要想像讀到這段註解的人會產生什麼疑問,並把內容再往前推一步。
# ✅ GOOD
# 雖然畫面上已不再使用,但為了保留過去登錄的資料,
# 仍保留這個模型與資料表
class Attachment < ApplicationRecord
有時候會看到呼叫端和被呼叫端都寫了幾乎一樣的註解。
# ❌ BAD
class Account
# 刪除帳號時也會取消扣款
after_destroy :cancel_payment
# ...
# 刪除帳號時也會取消扣款
def cancel_payment
# ...
end
end
通常只要其中一邊有寫就夠了,所以把多餘的註解刪掉吧。
# ✅ GOOD
class Account
# 刪除帳號時也會取消扣款
after_destroy :cancel_payment
# ...
def cancel_payment
# ...
end
end
其實,上面的註解看程式碼就能懂;如果測試程式碼已經把規格寫清楚,那這樣也就足夠了。
# ✅ GOOD(不是在應用程式碼裡寫註解,而是用測試把規格明文化)
class Account
after_destroy :cancel_payment
# ...
end
describe '#destroy' do
example '刪除帳號時也會取消扣款' do
# ...
end
end
Rails 的模型類別基本上應該是獨立於畫面(view)和控制器的存在。
也因為這樣,模型類別才會更有重用性。
如果在模型裡寫太多「畫面上會怎麼用」、「控制器裡會怎麼用」這類細節,模型和畫面、控制器之間就會看起來像是緊密耦合在一起。
# ❌ BAD(模型裡寫了畫面的事情)
class Guitar < ApplicationRecord
# 在編輯畫面中會依照...切換顯示內容
BODY_TYPES = %w(solid flat_top archtop)
end
當然,有些情況下,把畫面或控制器的考量寫進模型,反而會更容易理解程式碼;但除非真的「非這麼做不可」,否則還是不要把畫面或控制器的事情寫進模型裡。
# ✅ GOOD(模型不需要在意畫面或控制器的考量)
class Guitar < ApplicationRecord
BODY_TYPES = %w(solid flat_top archtop)
end
總之,這篇文章整理了 AI 常見的 9 種「不太理想的程式碼註解」。
讓 AI 寫程式碼時,常常會出現一些「看起來像那麼回事」或「表面上好像沒問題」的註解,散落在各處。
但仔細一讀,會發現很多其實「怪怪的」或「根本不需要註解」。
AI 很容易寫出一堆多餘的註解,所以對 AI 寫的註解,基本上還是先抱持懷疑態度比較好。
不太理想的註解,不只只是噪音,還會讓程式碼更難理解。
對策之一是,把這篇文章餵給 AI,讓它整理成像 CLAUDE.md 這類規則。
這樣應該能降低 AI 寫出奇怪註解的機率。
不過,AI 不一定會 100% 遵守這些規則,而且除了這 9 種以外,還是可能會寫出其他「不太理想的註解」。
所以不要因為訂了規則就過度相信,最後還是一定要由人來做審查。
如果這篇文章能成為你重新檢視自己或 AI 寫的註解的契機,那就太好了!
我認為,程式碼註解的基本思考方式,不論是 AI 寫還是人類寫,原則上都不會改變。