Skip to content

Tech Debt

keanu77 edited this page Jul 3, 2026 · 1 revision

Tech-Debt — 技術債清單(經兩階段對抗驗證)

本清單經兩階段對抗驗證產生:Pass 1 用 pattern matching 掃出候選,Pass 2 由對抗驗證逐條實讀程式碼給出 confidence,confidence < 0.7 者一律降級或剔除(見末尾「已檢視但不列入」小節)。所有 file:line 皆以實讀 /tmp/antidoping-src(origin/main 副本)為準,不採信 README/CLAUDE.md 的敘述(那些本身就有 drift,見 docs-drift-dead-refs)。

嚴重度:high / medium / low;工作量:S / M / L。可執行的收斂順序見 Plan,方向性演進見 Roadmap,指令以 Maintenance 為準。


一、測試與 CI

全專案零自動化測試、無 CI、無 pre-commit

  • 嚴重度:high 工作量:L (confidence 0.97)
  • 影響casesFixed.js 多條件 regex 查詢、statsFixed.js$addFields/$switch/$regexMatch 聚合、server.js catch-all 的路徑穿越防護全無回歸保護。任何重構或 schema 變更都靠人工點測,極易在部署後才發現破壞。
  • 證據(檔案):root / backend / frontend 三個 package.json 皆無 jest/vitest/mocha/playwright/cypress/supertest;root scripts 僅 build/start/dev/install:*(無 test),backend/package.json scripts 僅 start/dev,frontend/package.jsonlint:"eslint ." 但無測試框架。唯一 lint tooling = frontend/eslint.config.js。無 .github.husky.pre-commit-config.yamlbackend/test_db.jsbackend/test_route.js 為無斷言的 console.log 手動連線腳本(連 mongodb://localhost:27017/antidoping,非 production db 名),非測試套件。未受保護邏輯:casesFixed.js(regex)、statsFixed.js$switch 聚合,532 行)、server.js:110-138(catch-all 路徑防護,".." 檢查於 :118path.relative inside-check 於 :122-124)。

二、領域資料正確性與治理

教育平台案例庫含程式亂數生成的虛構案例

  • 嚴重度:high 工作量:L (confidence 0.82)
  • 影響:對一個以「真實禁藥案例教育」為使命的平台,資料庫混入亂數虛構案例會散布錯誤資訊、傷害可信度與教育價值;生成案例的 sourceLinks 為空,無法追溯。(Pass 2 註記:無法從 repo 確認 live production DB 是否已清乾淨,但 repo 仍保留生成器、審計文件仍列待驗證項、且無自動守則。)
  • 證據(檔案)backend/loadCompleteDatabase.js:124(for 迴圈起點)用 14 處 Math.random()(backend 唯一使用亂數的 loader)從姓名/國家/物質池隨機拼案例,合成物件於 :149-165、push 於 :166athleteName: \${firstName} ${lastName}`eventBackground: `${year}年${event}期間藥檢呈陽性反應,違反了反禁藥規則。`。真實硬編案例僅 3 筆(famousCases :46-98:Ben Johnson、Lance Armstrong、Maria Sharapova),totalCases=166:11)→ 約 163 筆合成。生成器為孤兒:僅自我呼叫於 :171/:218,未被 server.js/Dockerfile/zeabur.json/package.json 引用。models/Case.js:70-77sourceLinks 為選填陣列(無 required),生成器不設此欄。審計:案例真實性確認清單.md(233 行、標生成日 2025/8/23、185 案)含 1 個 ❌(圖例)與 57 個 ❓ 待驗證項,模板產物範例 Karen Perez/Michael Perez(:22-23)。運動禁藥案例資料庫_完整清單.md` 標總案例數 171,與 185 對不上。

三、資料存取與持久化

資料 route 各自開 MongoClient、硬編 db 名、無關閉與共用

  • 嚴重度:medium 工作量:M (confidence 0.85)
  • 影響:連線邏輯重複、db 名散落硬編難改;每 handler 靠 if(!db) return 500,連線失敗時只在 console 留一行、之後永久回 500 無告警;serving 連線無 graceful shutdown。
  • 證據(檔案)casesFixed.js:12statsFixed.js:13 各自在 module load 呼叫 MongoClient.connect()db = client.db("sports-doping-db")(db 名硬編於 casesFixed.js:14statsFixed.js:15);server.js:160 另獨立開一條 Mongoose 連線(query 不走它,僅 lifecycle/建 index,server.js:65 載入 models/Case)。等於三條連線池、兩份重複連線樣板。Pass 2 修正:專案確有約 20 處 client.close()(如 add_comprehensive_cases_batch1.jsverify_and_clean_cases.js),但全部位於一次性 seed/import/fix 腳本,runtime 請求路徑(casesFixed.js/statsFixed.js/server.js)從不 close,亦無 SIGTERM/SIGINT/mongoose.disconnect——故「serving 連線無 graceful shutdown」成立。education.js/tue.js 不開 DB。

/api/cases/:id 對非法 ObjectId 回 500 而非 400

  • 嚴重度:low 工作量:S (confidence 0.82)
  • 影響:對格式錯誤輸入回 5xx(伺服器錯誤語意)而非 4xx,污染錯誤監控訊號並讓爬蟲/掃描造成噪音。屬邊界輸入驗證小缺口(列表/搜尋端點已有 page/limit clamp 與 search 長度 200 上限,ReDoS 已用 escapeRegex 防護)。
  • 證據(檔案)casesFixed.js:205 直接 findOne({ _id: new ObjectId(req.params.id) })ObjectId require 於 :202),無 ObjectId.isValid 前置檢查;throw 由 :212-220 泛用 catch 捕捉回 500。mongodb ^6.18.0package.json:25)對非法字串會 throw。server.js:97 掛載 /api/cases 無上游 param 驗證。

四、監控與可觀測性

無錯誤監控與結構化日誌,僅 console.*

  • 嚴重度:medium 工作量:M (confidence 0.9)
  • 影響:生產環境無錯誤聚合、無告警:DB 連線掉線或聚合查詢爆錯時無人知曉,只能事後翻 log;缺 request id/結構化欄位難以排查。
  • 證據(檔案):三個 package.json 皆無 Sentry/pino/winston(grep 全空);server.js 12 處、statsFixed.js 11 處、casesFixed.js 5 處 console.*server.js:174-177 全域錯誤處理只 console.error(stack) 回泛用 500;casesFixed.js:17statsFixed.js:18 的 DB connect 失敗只 console.error,之後所有請求靜默回 500(casesFixed.js:22)。強化:/api/healthserver.js:68-70)雖存在但回靜態 {status:"OK"},不檢查 Mongoose/DB 連線狀態,連 health check 也偵測不到 DB 故障。

五、程式結構與死碼

backend/ 根目錄堆積約 47 支一次性 seed/fix 腳本、無單一資料來源

  • 嚴重度:medium 工作量:M (confidence 0.9)
  • 影響:案例資料的真實狀態散落數十支互相覆寫的腳本,無法重現、無法審計「目前 DB 為何長這樣」;跨裝置/重建環境時難以還原一致資料集。
  • 證據(檔案)ls backend/*.jsroutes/models/ 為子目錄本就排除)實得恰 50 支,扣除 test_db.js/test_route.js(臨時測試)與 export_cases_to_md.js(匯出),真正 seed/import/fix/remove/verify 一次性腳本約 47 支(如 add_final_16_cases.jsremove_suspicious_cases.jsverify_and_clean_cases.js,皆存在)。補強:(a) grep upsert = 0,無冪等寫入;add_final_16_cases.js:10 以硬編 inline 陣列 insertMany。(b) 4 支破壞性全量重置:seedData.js:208loadAllCases.js:397loadCompleteDatabase.js:177import_original_cases.js:57Case.deleteMany({}),三支「load-all」彼此競爭、無 canonical 檔。(c) 腳本 db 名不一致:runtime route 用 sports-doping-db,但 9 支腳本連 mongodb://localhost:27017/antidoping(如 add_final_16_cases.js:5),即部分腳本寫入 app 從不讀取的 DB。(d) import-to-zeabur.js:5 硬編 prod Mongo URI 含帳密並手動 seed prod(另屬 secret 外洩 子問題)。(e) Dockerfile COPY backend/ 會把全部 50 支腳本打包進 production image(死重)但從不執行。

過大檔案:TUE.jsx 1481 行、tue.js 內容 inline 約 440 行

  • 嚴重度:medium 工作量:M (confidence 0.9)
  • 影響:單檔過大降低可讀性與可測性;TUE 內容改一句要動 route 程式碼並重啟服務,無法像 education 那樣純資料更新,內容與邏輯耦合。
  • 證據(檔案)frontend/src/pages/TUE.jsx 1481 行(return(:71,476 個 className,含 inline prohibitedDrugs 物件於 :34)。backend/routes/tue.js 503 行,inline 資料為兩個物件:tueContent:5-273,約 269 行)+ wadaSubstances:274-444,約 171 行,供 /check 用,tue.js:470),route handlers 僅 :445-503。對照 education.js:6-8require('../data/*.json') 讀取。補強:api.js 定義 tueAPI 但前端無任何呼叫,TUE.jsx 也不打 /api/tue,故後端 inline 內容亦屬死碼/重複。

前端 API client 保留指向不存在端點的方法

  • 嚴重度:low 工作量:S (confidence 0.9)
  • 影響:死方法誤導開發者以為有寫入/文章 API。屬殘留耦合(先前 196KB mockData.js 死碼已隨 commit 7eab93e 移除,此為剩餘小型死碼)。
  • 證據(檔案)frontend/src/services/api.js:19-21casesAPI.create/update/delete → POST/PUT/DELETE /cases)與 :43-44educationAPI.getArticles/getArticle → GET /education/articles)為孤兒:casesFixed.js 僅實作 GET(router.get:20/:165/:196),education.js/articles(route 僅 :21/:29/:37/:46/:66/:74)。這 5 個方法在整個 frontend/src 從未被呼叫(實測僅 getAll@CaseList.jsx:140getById@CaseDetail.jsx:38getFilterOptions@CaseList.jsx:79educationAPI.getAll@Education.jsx:31)。且 server.js:110-112 catch-all 對 /api 未匹配回明確 JSON 404,非真正靜默。commit 7eab93e 亦移除 bcryptjs/jsonwebtoken 與 mockData.js,佐證寫入路徑已廢棄。

六、依賴與相容性

Runtime 釘死在已 EOL 的 Node 18

  • 嚴重度:medium 工作量:S (confidence 0.9)
  • 影響:生產環境跑在不再收安全性修補的 runtime,且新工具鏈逐步要求 Node 20+,未來 build 可能突然失敗。屬安全與相容性雙重風險。
  • 證據(檔案)package.json:6-8 "engines": {"node": "18.x"}Dockerfile 三處硬釘 node:18-alpine:2(frontend-build)、:19(backend-deps)、:31(production)。無 .nvmrc/無 CI/無 .npmrc engine-strict/backend 無 engines/zeabur.json 不釘 Node → 無緩解。Node 18『Hydrogen』LTS 已於 2025-04-30 結束支援(現為 2026-07)。強化:frontend/package.json:36vite ^7.1.2,Vite 7 現行 engines 已要求 Node ^20.19.0 || >=22.12.0(現在式),該 build 就在 Dockerfile:2node:18-alpine 階段執行,因無 engine-strict 才尚未硬 fail。

七、文件

文件與 backend/package.json 指向已不存在的檔案

  • 嚴重度:medium 工作量:S (confidence 0.9)
  • 影響:文件 drift 誤導維護者去找/編輯不存在的檔案;backend/package.json 是失效入口,vestigial 且與根衝突。
  • 證據(檔案)CLAUDE.md:86 仍把 backend/routes/cases.jsstats.jsbackend/server.jsmockData.js 當作「存在的未掛載/死碼」,但實測四者皆 MISSING(commit 7eab93e 清死碼後刪除)。README.md:88-93 列 POST/PUT/DELETE /api/cases/api/cases/compare/api/education/articles 等不存在端點;README.md:117server.js 畫在 backend/ 內(真正入口是根 server.js)。backend/package.jsonmainstart/dev 全指向不存在的 backend/server.js,且依賴清單缺 helmet/compression/express-rate-limit/mongodb 與根不一致。Pass 2 修正:README.md:118seedData.js 仍存在backend/seedData.js),該行本身非 drift;api.js 已完全無 mockData 引用(比「fallback 永不執行」更徹底,連 fallback 碼都移除)。.env.exampleJWT_SECRET 全專案無 jwt 使用,宜註明為預留。

建議償還順序(摘要)

  1. 先立護欄(P0,不可退讓)zero-tests-no-ci(測試 + CI)→ node18-eol-runtime(安全升版,改動小)。此二者是後續所有改動的安全網,且風險最低。
  2. 低風險快贏caseid-validation-500orphaned-api-methodsdocs-drift-dead-refs(皆 S,可與測試一起清)。
  3. 資料存取收斂per-route-mongo-connections + no-error-monitoring(同批處理連線與可觀測性)。
  4. 信譽核心(需雙重確認/migration)fabricated-case-data → 配合 seed-scripts-sprawl 一次收斂資料治理。
  5. 結構優化oversized-files(TUE 內容外移、元件拆分)。

對應可執行計畫見 Plan(P0/P1/P2 + DoD),演進方向見 Roadmap


已檢視但不列入(兩階段審計的誠實記錄)

WADA 禁用清單/TUE 內容無版本戳記、易靜默過期 — 不列入(isReal=false,confidence 0.72)

Pass 1 候選宣稱「無機制標示版本、使用者無從得知看到哪一年版本、內容靜默過期,對合規造成誤導」。Pass 2 實讀推翻其核心

  • 版本年份在 UI 顯著標示:frontend/src/pages/ProhibitedList.jsx:45「2026 禁用清單」、:48「WADA International Standard Prohibited List 2026」,另 Home/News/Resources/Layout/quiz 共 6+ 檔皆標 2026。
  • 資料內容以 inline 年份註記維護且已對應 2026 版變更:prohibitedList.js:131「Tramadol(2024新增)」、:174-175 CO M1.4「2026新增」,皆為真實禁用清單變更。
  • tue.js:23 引用「2023 ISTUE 第4.2條」屬正確現行標準(ISTUE 2023 為現行版),非過期;tue.js 另有「2026新規」註記。

因此「靜默過期、對合規誤導」的影響被明顯誇大。僅殘留一條較弱、不同層次的維護性小債:版本年份是散落 6+ 檔的 hardcoded magic string、無集中常數、資料層無結構化 version 欄位、無 CI/測試自動偵測跨年過期。此殘留不足以支撐原候選所述的債,故降級為觀察項而非正式技術債(可在 Roadmap L1「禁用清單版本化」順帶處理)。


相關頁面:Introduction · Roadmap · Plan · Maintenance · Home

Clone this wiki locally