🎯 什麼情境該想到我
當你看到「永遠不會被執行、或已經沒有任何地方在使用」的程式碼——被註解掉的舊實作、再也沒人呼叫的函式、不可能成立的條件分支——它們只會增加閱讀時的負擔。
⚙️ 怎麼用(步驟 / 公式)
意圖:把死掉的程式碼刪掉。留著它不會帶來價值,只會讓讀者以為「這還有用」而多花心力理解。有版本控制在,需要時隨時能找回來。
小步安全進行:
- 確認這段程式碼真的沒有被使用(用工具搜尋呼叫端、靜態分析找不可達碼)。
- 若它曾對外公開(是 API),先確認外部確實沒有依賴。
- 直接刪除,不要用註解「暫時封存」——那只是另一種雜訊。
- 執行測試,確認沒有東西壞掉。
★ Go 的優勢:編譯器幫你抓掉一部分
func f() {
x := 1 // ★ declared and not used —— 編譯錯誤,不是警告
return
fmt.Println() // ★ missing return / unreachable code(vet 的 unreachable 會報)
}
import "os" // ★ "os" imported and not used —— 編譯錯誤★ Go 把「未使用的區域變數與 import」定為編譯錯誤,這是它相對多數語言最大的優勢:
這一類死碼在 Go 裡根本活不下來。
⚠️ 但編譯器抓不到這四種
| 抓不到 | 怎麼找 |
|---|---|
| 未使用的匯出函式/型別/方法 | staticcheck 的 U1000(unused),或 deadcode(golang.org/x/tools/cmd/deadcode) |
| 未使用的 struct 欄位 | staticcheck 的 unused;但被反射/JSON tag 用到的會誤判 |
| ★ 永遠為假的分支 | 沒有工具能通用地抓。靠 coverage 報告:go test -coverprofile 後看 0% 的行 |
| ★ 整包沒人 import 的 package | deadcode 或 go mod why;grep -rn '"module/path/pkg"' |
# ★ 找死碼最實用的一招:用 coverage 反推
go test ./... -coverprofile=c.out && go tool cover -html=c.out
# 紅色(0 次執行)的分支,有些是缺測試,有些是真的死碼 —— 要人來判斷★★ 刪之前必須確認的五件事(Go 版)
grep字串引用:反射、encoding/jsontag、RPC/HTTP 路由註冊表、DI 容器
—— 這些靠字串找函式和欄位,編譯器完全不保護你。grep -rn '"HandleLegacyWebhook"' . # ★ 函式名當字串用的地方init()的副作用:import _ "pkg"只為了跑它的init()(driver 註冊是典型)。
刪掉那行 import 會讓程式編譯成功、執行期才找不到 driver。- build tag:
//go:build linux的檔案在 macOS 上「看起來」沒人用。
用GOOS=linux go build ./...逐個平台確認。 go:generate產生的碼:刪了會在下次 generate 時回來。要改的是產生器。- 匯出的 API 不是你的:別的 module 可能在用。先掛
// Deprecated:一個版本再刪。
★ 為什麼不是「註解掉」或「留著以備萬一」
★ 版本控制就是你的「以備萬一」。 註解掉的碼有三個具體傷害:
① 搜尋時會命中,浪費讀者時間;② 不會被編譯器與重構工具檢查,很快就爛掉、再也編不過;
③ 讀者無法判斷它是「暫時關掉」還是「被遺忘」——於是沒人敢動它。
唯一的例外:用 feature flag 關掉、但確定要在近期重新開啟的碼。
那要有明確的移除期限和一張 ticket,否則就是死碼披上偽裝。
和相鄰概念的界線
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
「可能之後會用到」不是保留的理由——版本控制就是你的還原機制,別靠註解保存。
-
對外公開的介面要先確認沒有隱形依賴(反射、動態呼叫、外部呼叫端)再刪。
-
不確定是否真的無用時,先加日誌/監控觀察一段時間,再決定刪除。
-
★ 反射、JSON tag、路由註冊表、DI 容器用字串找程式碼,編譯器不保護你。 刪之前
grep那個名字。 -
★
import _ "pkg"是為了init()副作用(DB driver、image 格式、pprof handler)。看起來沒用,刪了執行期才爆。 -
build tag 下的檔案在你的平台上「看起來」沒人用。逐個
GOOS確認。 -
匯出的 API 要先 Deprecated 一個版本,不能直接刪。
-
不要註解掉,要真的刪。註解掉的碼不被編譯器檢查,很快就爛到無法復原——反而不如 git history。
-
一次刪一批、一個 commit 只刪死碼。混在功能改動裡,review 的人會直接放過。
-
測試碼裡的死碼也要刪:沒被任何 test 呼叫的 helper、永遠不會跑到的
t.Skip分支。
🔗 相關工具
- 移除旗標參數 Remove Flag Argument(拆掉旗標後常會暴露出從此走不到的死分支)
- 以多型取代條件式 Replace Conditional with Polymorphism(重構條件式後常清出不再需要的分支)
- 重構
- 冗員元素 —— 「不值得存在的抽象層」,不同的味道
- 誇誇其談通用性 —— 為假想需求準備的擴充點
- 行內函式 —— 只剩一層轉發的函式不是死碼,是該內聯