標籤: 逆向工程

  • AI 逆向工程協作:Source Inventory 協議讓一比一重建不再落東掉西

    重點摘要

    • AI 協作做逆向工程,最大的坑不是技術,而是 AI 「選擇性閱讀」原始碼
    • 根治方法:強制 Source Inventory 協議 — 動手前先列清單、附行號、用戶確認邏輯後才寫程式
    • 用戶不需要知道原版長什麼樣,清單上的每一項都要有原始碼行號出處
    • 這套協議已寫入 CLAUDE.md,適用於任何 legacy code 逆向重建專案

    最近在做一個系統重建專案:把一套已上線多年的 Ionic + PHP 系統,逐頁逐模組一比一重建為 Flutter 應用。聽起來直白,實際做起來卻踩了一個讓人很沮喪的坑——不是技術難度的問題,而是 AI 協作流程設計的問題

    這篇文章記錄我如何診斷這個問題,並建立一套可複用的 AI 逆向工程協作協議。

    問題:一比一複製,卻一直落東掉西

    我明確告訴 AI:「這是舊版原始碼,你幫我一比一複製到 Flutter。」但每次交付,都會發現缺少功能。最典型的例子:登入後的登出功能不見了

    登出不是什麼複雜功能,但它不在主流程頁面——它藏在 header component 的某個角落。AI 在讀原始碼的時候,只讀了主頁面,沒讀共用元件,自然就沒看到登出按鈕的存在。

    更麻煩的是:我自己也不知道原版長什麼樣(這是別人開發的系統),所以我也沒辦法直接指出「你少了這個」。每次都要在用起來的時候才發現缺少什麼,然後重來。

    根本原因:AI 的「選擇性閱讀」

    深入想了一下,問題的根源在於 AI 讀原始碼的方式:

    錯誤的讀法 正確的讀法
    找「我預期會有的東西」 逐行掃描,列出「所有看到的東西」
    只讀主要頁面檔案 讀所有相關檔案(service、routing、共用元件)
    憑印象補齊功能 每一項都有原始碼出處(檔案 + 行號)
    「讀完」就開始寫 產出清單、確認後才開始寫

    用一句話說:AI 是在「確認自己的預期」,不是在「完整盤點原始碼」。這種差距在簡單頁面感覺不出來,在有大量共用邏輯的真實系統裡,每個模組都會漏東西。

    解法:Source Inventory 協議

    我和 AI 一起設計了一套強制執行的協議,並寫入專案的 CLAUDE.md,讓每個接手這個專案的 AI session 都必須遵守。

    Step 0 — 列出所有要讀的檔案(動手前)

    在讀任何內容之前,先列出這個模組涉及的所有檔案,包含:

    • 頁面本體(template + logic)
    • 所有被引用的 service
    • 共用元件(header、footer、tab-bar、nav)
    • 路由設定
    • 後端 handler

    每個檔案讀完打勾,沒打勾的不算讀過。這一步強迫 AI 在開始之前就意識到「這個模組牽涉哪些檔案」,避免只讀主檔就動手。

    Step 1 — 產出 Source Inventory(每項附行號)

    讀完所有檔案後,產出功能清單。格式固定:

    - [ ] 登出按鈕
          來源:home.page.html:142 <ion-button (click)="logout()">
    
    - [ ] 下拉刷新
          來源:ticket.page.html:38 <ion-refresher (ionRefresh)="doRefresh($event)">
    
    - [ ] 空狀態提示
          來源:ticket.page.html:91 <div *ngIf="tickets.length === 0">目前沒有票券</div>

    關鍵點:每一項都必須有原始碼的檔案和行號

    這解決了「用戶不知道原版長什麼樣,所以沒辦法驗證清單正確性」的問題。用戶不需要看過原版,只需要問:「這一項你是從哪行讀到的?」答不出來,就代表是 AI 憑印象補的,不是真的在原始碼裡。

    Step 2 — 逐項實作,逐項打勾

    對照清單一項一項做,每完成一項就打勾。不允許「大概實作了」或「之後再補」。

    Step 3 — Self-Verify 後才交付

    交付前,AI 自己對照清單跑一遍,確認每項都存在於程式碼中。如果 Step 3 發現漏項,必須補完再說「完成」。漏項不能留給用戶發現。

    一條關鍵規則:禁止在原始碼存在的情況下向用戶索取截圖

    另一個常見的壞模式是:AI 讀不懂某個行為,就跟用戶要截圖或圖片。

    這在逆向工程的情境下尤其荒謬。用戶拿到舊系統原始碼,通常自己也沒有截圖,甚至從來沒用過那個系統。要求用戶提供截圖,是把 AI 本來應該做的工作(讀原始碼)轉嫁給用戶。

    正確的做法是:原始碼讀不懂的地方,標記 [?],說明不確定的點,讓用戶確認業務邏輯——而不是要求視覺確認。

    這套協議適用於任何逆向升級工程

    雖然這次的情境是 Ionic → Flutter,但這套協議的核心邏輯對任何 legacy 重建都適用:

    • jQuery / AngularJS → React / Vue
    • PHP monolith → 現代後端(Go、Node、Python)
    • 原生 Android/iOS → Flutter / React Native
    • 桌面應用 → Web 應用

    只要是「拿到舊系統 code,重寫到新技術棧」,用戶通常都不知道原版的全貌,也無法驗證 AI 是否讀完了所有東西。Source Inventory + 行號出處 是讓這個過程可稽核的最低門檻。

    如何在你的專案裡執行這套協議

    把以下內容加入你的專案 CLAUDE.md(或任何 AI 工作指引文件):

    ## 一比一複製協議(MANDATORY)
    
    > 原始碼是唯一的 ground truth。用戶不應該被要求提供截圖。
    
    ### Step 0 — 列出所有要讀的檔案
    (所有頁面、service、共用元件、routing、後端 handler)
    每個檔案讀完打勾,沒打勾不算讀過。
    
    ### Step 1 — Source Inventory(每項附檔案:行號)
    格式:
    - [ ] 功能描述
          來源:filename.html:LINE_NUMBER <原始碼片段>
    
    ### Step 2 — 逐項實作,逐項打勾
    
    ### Step 3 — Self-Verify 後才交付
    
    ### 禁止行為
    - 在原始碼存在的情況下向用戶索取截圖
    - 沒有產出 Source Inventory 就開始寫程式
    - 只讀主頁面,不讀 service / routing / 共用元件

    結語

    逆向升級工程的難點不在技術轉換,而在於如何保證「沒有遺漏」。AI 的天性是預測下一個 token,這讓它容易「補全期望的樣子」,而不是「如實記錄看到的東西」。

    Source Inventory 協議的作用,就是把 AI 的工作模式從「自由發揮」強制切換成「逐行比對」。這不是在限制 AI,而是在讓 AI 的輸出變得可以被信任。

    如果你也在做類似的逆向重建專案,歡迎把這套協議複製進你的 CLAUDE.md,看看效果如何。