實戰範例 009:第一次跑 /ponytail-review:把過度設計的日期 Helper 砍成一支函式
如果你剛裝好 Ponytail,卻不知道從哪個檔案開始下手,這個範例就是為你寫的。我們會用一個常見的「日期格式化 Helper」當目標,完整走一次 /ponytail-review 的流程:裝好外掛、選強度等級、跑指令、讀報告、套用建議。整個過程大約 25 分鐘,不需要任何先備知識。
這個範例對應 Ponytail 7 階階梯中的第 1、3、6 階:YAGNI 檢查、標準庫優先、單行優化。
場景:一支看似無害的日期 Helper
團隊裡一位剛畢業的工程師寫了一支「未來很有彈性」的日期 Helper。他預留了多種格式策略、抽象基底類別,甚至還寫了一個工廠。實際上整個專案只會用到一種格式:YYYY-MM-DD。
// src/utils/dateFormatter.ts
export abstract class BaseDateFormatter {
protected pad(n: number): string {
return n.toString().padStart(2, '0');
}
abstract format(d: Date): string;
}
export class IsoFormatter implements BaseDateFormatter {
format(d: Date): string {
return `${d.getFullYear()}-${this.pad(d.getMonth() + 1)}-${this.pad(d.getDate())}`;
}
protected pad(n: number): string {
return n.toString().padStart(2, '0');
}
}
export class FriendlyFormatter implements BaseDateFormatter {
format(d: Date): string {
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
}
}
export class FormatterFactory {
static create(kind: 'iso' | 'friendly'): BaseDateFormatter {
if (kind === 'iso') return new IsoFormatter();
return new FriendlyFormatter();
}
}
export function formatDate(d: Date, kind: 'iso' | 'friendly' = 'iso'): string {
return FormatterFactory.create(kind).format(d);
}
70 行程式碼,但全專案從頭到尾只呼叫 formatDate(date) 一次,而且都是預設的 iso。FriendlyFormatter 從來沒被使用過。
第 1 步:確認 Ponytail 已啟用
如果你還沒裝,在支援 /plugin 的代理(OpenCode、Claude Code 等)中執行:
/plugin marketplace add DietrichGebert/ponytail && /plugin install ponytail@ponytail
接著確認當前的強度等級:
/ponytail
Ponytail 會回報目前的 level(例如 full)。如果這是你第一次使用,把它設成 full(這是預設值,也是新手最平衡的選擇):
/ponytail full
想讓設定跨重啟保留,使用 v4.9.0 新增的
/ponytail default full;單獨輸入/ponytail只會「回報」而不會「重設」。
第 2 步:對目標檔案跑 /ponytail-review
/ponytail-review src/utils/dateFormatter.ts
Ponytail 會掃這個檔案,回報「過度設計」的點。下面是這次典型的輸出摘要:
Findings (4):
1. [over-engineering] FriendlyFormatter: 0 references across codebase → 刪除
2. [over-engineering] FormatterFactory: 只有一個 product (IsoFormatter) → 工廠無意義
3. [over-engineering] BaseDateFormatter: 只有一個繼承者 → 抽象層無意義
4. [reinvented-stdlib] pad(): Date 已有 ISO 原生方法,自製 pad 是重新發明標準庫
Suggested action: 全檔案壓成單一 formatDate() 函式,10 行內可完成。
Safety preserved: 輸入參數檢查、NaN/Invalid Date 防護會保留。
報告裡每一項都對應 7 階階梯的某一階。第 1、2 項是 YAGNI(沒人用),第 3 項是不必要的抽象(程式碼復用反例),第 4 項是標準庫優先。
第 3 步:讀懂「Safety preserved」這行
新手最怕「極簡」等於「拔掉防呆」。Ponytail 預設不會動下列東西,報告裡的 Safety preserved 那行就是在重申這點:
- 輸入
Date是否為Invalid Date的檢查 null/undefined參數的邊界處理- 例外攔截 (
try-catch)
如果你希望連這些都拿出來檢視,可以把等級切到 ultra;新手強烈建議留在 full。
第 4 步:套用建議,產出極簡版
// src/utils/dateFormatter.ts
export function formatDate(input: Date | string | null | undefined): string {
if (input == null) return '';
const d = input instanceof Date ? input : new Date(input);
if (Number.isNaN(d.getTime())) return ''; // 🛡️ Invalid Date 防護
return d.toISOString().slice(0, 10); // YYYY-MM-DD
}
10 行。toISOString() 是 ECMAScript 標準庫,自製 pad 全部消失。FriendlyFormatter、FormatterFactory、BaseDateFormatter 整批刪除。原來 70 行,現在 10 行,大約減 85%。
第 5 步:用 /ponytail-gain 看累積效益(選擇性)
/ponytail-gain
Ponytail 會以壓縮後的計分板呈現累積影響,例如「程式碼更少、成本更低、速度更快」。對單一檔案的重構不一定會單獨計入,但長期追蹤很有感。Ponytail 官方實測平均減 54% 程式碼、減 20% 成本、減 27% 時間,最高單次減 94%。
與既有範例的差異
如果你已經看過〈實戰範例 001〉那個訂單 Handler 的重構,這個範例刻意挑了更小的目標。001 是「整個模組」的戰略重構,需要先盤點策略模式與抽象類別;009 是「單一 Helper」的戰術演練,新手能在 25 分鐘內走完整個 /ponytail-review 流程,建立對報告分類的直覺。
關鍵學習點
- 第一次跑
/ponytail-review的 SOP:先/ponytail看當前等級、/ponytail full設定、再/ponytail-review <file>跑掃描,三個指令就能完成首次實戰。 - 報告的四種 finding 對應 7 階階梯:未被引用、單一繼承者的抽象、單一 product 的工廠、重新發明標準庫,這四項是新手最常碰到的過度設計類型。
- 安全防護不在砍除範圍:
Invalid Date、null/undefined、try-catch預設都會被保留,Safety preserved那行就是承諾。 - 想跨重啟保留等級用
/ponytail default <mode>(v4.9.0+),單獨/ponytail只會回報而不會重設。