Theme / v4.9.0

Ponytail

讓 AI 編碼代理像最懶的資深工程師

實戰範例

實戰範例 009:第一次跑 /ponytail-review:把過度設計的日期 Helper 砍成一支函式

帶新手完整走一遍安裝、切換強度等級、執行 /ponytail-review 並讀懂報告,把一個 70 行的日期格式化 Helper 簡化為 10 行單一函式。

實戰範例 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) 一次,而且都是預設的 isoFriendlyFormatter 從來沒被使用過。


第 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 全部消失。FriendlyFormatterFormatterFactoryBaseDateFormatter 整批刪除。原來 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 Datenull / undefinedtry-catch 預設都會被保留,Safety preserved 那行就是承諾。
  • 想跨重啟保留等級用 /ponytail default <mode>(v4.9.0+),單獨 /ponytail 只會回報而不會重設。