生活分享

實戰:維護既有專案

建立現況基準,處理一項真實變更,以回歸檢查和交接紀錄交付可追溯成果。

閱讀時間約 15 分鐘 · 操作 30 分鐘

原創流程示意圖,非產品介面截圖。
圖片:Mokaair (© Mokaair)
回總目錄:Codex 學習中心:完整教學目錄

進階 · Desktop / CLI / VS Code / JetBrains / cloud

本篇目錄
  1. 目標與起點
  2. 步驟 1:記錄基準,不先要求重寫
  3. 步驟 2:把範圍寫成可檢查的契約
  4. 步驟 3:加入會先失敗的新測試
  5. 步驟 4:實作並檢查差異
  6. 步驟 5:回歸畫面與保存資料
  7. 還原與交接

目標與起點

本段提到的教學與資源: 練習材料 · ·

步驟 1:記錄基準,不先要求重寫

開啟 maintenance-lab,應只有 index.html、style.css、app.js、core.mjs、core.test.mjs 五份程式檔。先執行 node --test core.test.mjs,預期三項通過,再記下 Node 版本、日期與資料夾。如果不是這個結果,先查是否拿到 start 或 broken,不把既有失敗算在這次重構。將副本納入自己的本機 Git 基準提交,或另存完整 baseline 副本;保存後才開始修改,確保能還原這次改動。

修改前也要建立畫面基準:在此資料夾執行下方「畫面回歸」列出的對應平台預覽命令,使用專供練習的瀏覽器設定檔與來源網址。先確認 Show 為 All tasks 且沒有既存資料;若已有資料,先保存,再選用另一個全新練習設定檔,以空白狀態開始,不直接清除原資料。新增 Read、Build,完成 Read,確認 1 active / 2 total;在瀏覽器開發者工具的 Application/Storage → Local Storage 中,保存 mokaair-codex-todo-v1 的虛構 JSON,包含兩筆 ID 與完成狀態。保留這兩筆資料、網址及瀏覽器設定檔供修改後比對。接著在桌面版開啟 maintenance-lab 並建立任務,或從此資料夾另開的終端機執行 codex,再提交下一步的需求。

請 Codex 唯讀查看 app.js 中更新 #count 的地方,描述統計是全部任務還是篩選後清單。正確基準是全部任務,即使選 Completed,仍顯示所有任務的 active/total。接著確認儲存鍵 mokaair-codex-todo-v1、version 1、ID 與 completed 型別。重構只搬動計數責任,不改資料格式,不新增儲存遷移。這個先讀再改的步驟能避免把看似重複的程式碼合併後改變原本細節。

步驟 2:把範圍寫成可檢查的契約

項目本次要求不變條件
core.mjs新增純函式 countTasks(tasks)不修改輸入,不碰 DOM/儲存
app.js呼叫 countTasks 更新 #count顯示文字與全部任務計數不變
maintenance.test.mjs新增計數與不變性案例原 core.test.mjs 保留
HTML/CSS本次無修改需要按鈕、版面、焦點樣式維持
儲存資料不做遷移原 key、version、ID 與狀態保持

小範圍不代表不驗收;它讓你能把新的失敗和少數修改連起來。

步驟 3:加入會先失敗的新測試

把下方內容另存為 maintenance.test.mjs,與原測試並列。先跑兩份測試,新函式尚未存在時,新三項會失敗而原三項仍通過;保存這個預期失敗,確認測試真的會因缺少計數函式而報錯。不要因紅色結果就把教材當成壞版本,也不要讓 Codex 刪掉新測試來取得綠色。這裡輸入都符合既有 Task 契約,沒有擅自擴大成任意資料清理工具。

maintenance.test.mjs · javascript
import test from 'node:test';
import assert from 'node:assert/strict';
import * as core from './core.mjs';

test('empty list counts are zero', () => {
  assert.deepEqual(core.countTasks([]), { active: 0, total: 0 });
});
test('count all tasks without deduplicating equal titles', () => {
  const tasks = [
    { id: 'a', title: 'Read', completed: false },
    { id: 'b', title: 'Read', completed: true },
    { id: 'c', title: 'Build', completed: false },
  ];
  assert.deepEqual(core.countTasks(tasks), { active: 2, total: 3 });
});
test('counting preserves frozen task data and storage compatibility', () => {
  const task = Object.freeze({ id: 'a', title: 'Read', completed: true });
  const tasks = Object.freeze([task]);
  const before = core.encodeTasks(tasks);
  assert.deepEqual(core.countTasks(tasks), { active: 0, total: 1 });
  assert.equal(core.encodeTasks(tasks), before);
  assert.deepEqual(core.decodeTasks(before), tasks);
});
各平台相同的回歸命令 · sh
node --test core.test.mjs maintenance.test.mjs

步驟 4:實作並檢查差異

受限重構提示詞 · text
Add countTasks(tasks) to core.mjs, returning {active, total} for the full list.
Use it in app.js when updating #count; keep the existing visible text.
Preserve storage format, key, IDs, task behavior and original tests.
Do not edit index.html, style.css or the new test expectations.
Run node --test core.test.mjs maintenance.test.mjs.
Report changed files, test results and remaining browser verification.

完成後應有六項測試通過。參考核心函式如下,app.js 需匯入它並在 render 裡先對 tasks 計數,再組出與原本相同的文字。特別檢查是否誤傳 shown,也就是目前篩選結果;那會讓 Completed 頁面錯顯示總數。查看 git diff 或編輯器差異,原測試、HTML、CSS、儲存讀寫不應被改動。純函式的測試綠色,不能取代這個接線檢查。

core.mjs 的參考新增函式 · javascript
export function countTasks(tasks) {
  return {
    active: tasks.filter((task) => !task.completed).length,
    total: tasks.length,
  };
}

在 app.js 原有的第一行匯入清單加上 countTasks,保留原六個函式。接著找到 render 中原本寫入 #count.textContent 的那一行,用第二段替換;不要把整個 render 換掉,也不改前面產生 shown 的篩選。以下兩段只是指定位置的片段,不是完整 app.js。

app.js:替換原本的 core 匯入行 · javascript
import { countTasks, addTask, toggleTask, removeTask, visibleTasks, decodeTasks, encodeTasks } from "./core.mjs";
app.js:替換 render 中原本的統計賦值 · javascript
const counts = countTasks(tasks);
document.querySelector("#count").textContent = `${counts.active} active / ${counts.total} total`;

檢查這裡的參數確實是 tasks。只有 Read 完成、Build 未完成時,Completed 的 shown 只有一筆;若誤傳 shown,會得到 0 active / 1 total,而正確畫面應仍為 1 active / 2 total。核心六項測試只驗 countTasks 的函式契約,仍可能全部通過,所以保留這個獨立的接線與畫面核對。

步驟 5:回歸畫面與保存資料

若修改前的同一個練習服務仍在執行,直接重新整理該網址,不另啟動第二個;已停止才使用以下命令。在此資料夾啟動預覽:Windows 執行 py -3 -m http.server 4173 --bind 127.0.0.1;macOS/Linux 執行 python3 -m http.server 4173 --bind 127.0.0.1,瀏覽同一 http://127.0.0.1:4173。若已有人占用,先核對原服務,不直接終止它。在修改前後使用同一來源網址及虛構待辦,避免換連接埠後空白的 localStorage 被誤認為資料遺失。保留修改前建立的 Read、Build,不重設或重複新增;先比對儲存的兩筆 ID 與完成狀態,再依序選 All tasks、Active、Completed,三次統計都必須是 1 active / 2 total。

重新整理確認兩筆資料、ID 與完成狀態保留。先用同一組兩筆資料,在 390px 與桌面寬度各檢查一次篩選與統計;接著選 All tasks,只刪除一次 Read,確認 1 active / 1 total。按 Tab 確認焦點仍可見。這是回歸驗收,不需要新的設計圖。若數字隨篩選改變,就回到 app.js 查函式引數;若純函式本身不對,用新測試的實際失敗案例定位。只有在確實觀察過後才填寫瀏覽器通過,未操作的系統另外標記。

還原與交接

需要撤回時先保存當前差異,再只還原本次改動的 core.mjs、app.js,並移走自己新增的 maintenance.test.mjs;重新執行原本三項測試及統計畫面,應回到原基準。不要用整個專案強制重設來清掉其他人的修改。交接寫明基準、抽出的責任、未變的資料契約、六項測試、實際畫面驗收及還原位置;沒跑的測試不能寫通過。下一個維護需求另列範圍,參考,不要在這次小重構順便重寫儲存層或更換框架。 完成後在自己的預覽終端機按 Ctrl+C 停止這次服務,保留基準 JSON 與驗收紀錄。

原創流程示意圖,非產品介面截圖。
原創流程示意圖,非產品介面截圖。 · 圖片:Mokaair (© Mokaair)
閱讀完整文字說明

Three numbered stages: identify the starting point, perform the exercise, and verify the result. Original illustration, not a product screenshot.

回總目錄

  • 生活分享

    Codex 學習中心:完整教學目錄

    從安裝、第一個任務到 MD 規則與進階整合,規劃 60 篇 Codex 教學、十個單元。依程度、平台、需求或指令搜尋下一篇;尚未公開的教學會標示狀態,方便安排學習路線。

  • 生活分享

    Worktree 與多任務隔離

    Worktree 讓同一個 Git 程式庫有不同的工作目錄,各自承接不同分支。它適合讓兩項工作分開改檔,但資料庫、連接埠與外部服務仍可能共用,不能把檔案隔離當成所有資源隔離。

  • 生活分享

    實戰:製作小網站

    從 brief.md 規劃並製作 Small Steps 待辦網站,完成新增、完成、刪除、篩選與本機資料保存。將 HTML、CSS、資料函式、畫面事件與測試分開,以 Node 測試和瀏覽器操作驗收,並留下可重新啟動與還原的交接紀錄。

  • 生活分享

    用量與效率:減少重工

    記錄任務條件、模型選項、時間與成果,找出能減少無效重試和過多上下文的調整。

最新旅遊情報攻略

資料來源

生活分享