iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
Claude AI

從零開始 Claude Code:30 天打造 AI 任務管理 Web App系列 第 20 篇

[Day 20] 功能做完先整理:開始重構程式碼

  • 分享至 

  • xImage
  •  

做到 Day 19 之後,目前專案功能都已經可以正常使用:

Task CRUD
MySQL 儲存
狀態更新
狀態篩選
分類篩選
日期格式錯誤處理

功能雖然正常,但 app.py 裡開始出現一些重複程式。

尤其是:

new_task()
edit_task()

裡面,都有相同的分類查詢和日期解析。

所以 Day 20 不加新功能。

今天的目標是:

在不改變原本行為的前提下,把重複程式整理成比較好維護的寫法。


什麼是 Refactor?

Refactor 就是:

整理程式內部結構
但不改變外部功能

也就是說,使用者看網站時,不應該感覺有任何不同。

例如 Day 19 已經確認:

錯誤日期 → 400
合法日期 → 正常
空白日期 → NULL

Day 20 重構之後,這些結果都必須保持一樣。

所以可以把 Refactor 理解成:

程式裡面變乾淨
網站外面不變

今天的範圍

今天只做小範圍整理。

預計只修改:

app.py

不做:

新增功能
分類 CRUD
搜尋
分頁
services/
routes/
Application Factory
資料庫調整
Claude API

也不會因為看到可以整理的地方,就一次把整個專案拆掉。


先讓 Claude Code 找重複程式

我先切到 Plan Mode,讓 Claude Code 檢查目前的 app.py。

Prompt:

目前專案功能都正常。

Day 20 不新增任何功能,
今天只做小範圍 Refactor。

請檢查目前 app.py,
特別看 new_task() 和 edit_task()。

找出明顯重複的程式碼,
並判斷哪些值得抽成小型 helper function。

限制:

1. 不改變任何現有功能
2. 不改 URL
3. 不改 HTTP Method
4. 不改資料庫結構
5. 不建立 services/
6. 不建立 routes/
7. 不新增其他架構
8. 優先維持 app.py 單一檔案
9. 不順便新增功能或驗證規則
10. 修改後原本測試結果必須保持一致

請先分析:
- 哪些重複值得整理
- 建議抽成什麼 helper
- helper 的參數和回傳值
- 哪些地方不要動
- 修改後怎麼驗證

先不要直接修改程式。

Claude 找到兩組明顯重複

Claude Code 找到兩段幾乎一樣的程式。

第一段是分類查詢:

category = Category.query.filter_by(
    name=request.form.get("category")
).first()

接著再取得:

category.id if category else None

這一段同時出現在:

new_task()
edit_task()

第二段是日期解析

Day 19 修完之後,新增和編輯都有:

due_date_text = request.form.get("due_date", "")

if due_date_text:
    try:
        due_date = datetime.strptime(
            due_date_text,
            "%Y-%m-%d"
        ).date()
    except ValueError:
        abort(400)
else:
    due_date = None

這兩段程式完全一樣。

所以 Claude 建議抽成兩個 helper:

resolve_category_id()
parse_due_date()

第一個 helper:resolve_category_id()

Claude 建議:

def resolve_category_id(category_name):
    category = Category.query.filter_by(
        name=category_name
    ).first()

    return category.id if category else None

這個 helper 的工作很單純:

分類名稱
↓
查 Category
↓
回傳 category_id

例如:

專案
→ 找到 Category
→ 回傳 id

如果查不到:

不存在的分類
→ None

這跟原本新增和編輯的行為完全一樣。


第二個 helper:parse_due_date()

日期則整理成:

def parse_due_date(due_date_text):
    if not due_date_text:
        return None

    try:
        return datetime.strptime(
            due_date_text,
            "%Y-%m-%d"
        ).date()
    except ValueError:
        abort(400)

這個 helper 保留 Day 19 的所有規則。

合法日期

2026-12-25

會回傳日期物件。

空白日期

""

會回:

None

格式錯誤

not-a-date

會:

abort(400)

跟重構前完全一樣。


為什麼不把所有東西都抽成 helper?

Claude Code 還特別保留了一些地方沒有動。

例如 index() 裡也有分類查詢:

category = Category.query.filter_by(
    name=category_filter
).first()

但它沒有跟:

resolve_category_id()

合併。

因為兩邊雖然都在查分類,行為其實不一樣。

新增和編輯:

查不到分類
→ category_id = None

首頁篩選:

查不到分類
→ category_id = -1
→ 查詢結果為空

所以不能只因為「程式長得像」,就硬抽成同一個函式。

這也是今天很重要的一點:

重構要看邏輯是不是一樣,不只是看程式碼像不像。


new_task 和 edit_task 也沒有硬合併

Claude 也沒有把:

new_task()
edit_task()

整個合併。

因為:

new_task
→ 建立新的 Task

edit_task
→ 修改既有 Task

兩者責任本來就不同。

所以這次只抽真正重複的:

分類解析
日期解析

沒有過度整理。


確認規劃後才開始修改

我確認 Plan 沒問題後,才讓 Claude Code 執行:

這版 Refactor 規劃可以。

請照規劃執行,只修改 app.py:

1. 新增 resolve_category_id()
2. 新增 parse_due_date()
3. new_task() 和 edit_task() 改成呼叫這兩個 helper

不要修改 index() 的分類篩選邏輯。
不要合併 new_task() 和 edit_task()。
不要新增 validation、route、service 或其他功能。

完成後請列出:
- 實際修改內容
- 修改後 new_task() / edit_task() 關鍵程式碼
- Day 19 六組回歸測試結果

實際修改內容

最後真的只修改:

app.py

並在:

db.init_app(app)

後面加入兩個 helper。

def resolve_category_id(category_name):
    category = Category.query.filter_by(
        name=category_name
    ).first()

    return category.id if category else None

以及:

def parse_due_date(due_date_text):
    if not due_date_text:
        return None

    try:
        return datetime.strptime(
            due_date_text,
            "%Y-%m-%d"
        ).date()
    except ValueError:
        abort(400)

new_task() 變得比較乾淨

原本 new_task() 裡要自己處理分類和日期。

重構後變成:

category_id = resolve_category_id(
    request.form.get("category")
)

due_date = parse_due_date(
    request.form.get("due_date", "")
)

建立 Task:

task = Task(
    title=request.form.get("title", ""),
    description=request.form.get("description", ""),
    status=request.form.get("status", "todo"),
    category_id=category_id,
    due_date=due_date,
)

Route 裡就不用再塞日期格式處理細節。


edit_task() 也改成共用 helper

編輯任務則變成:

task.title = request.form.get("title", "")
task.description = request.form.get("description", "")
task.status = request.form.get("status", "todo")

task.category_id = resolve_category_id(
    request.form.get("category")
)

task.due_date = parse_due_date(
    request.form.get("due_date", "")
)

這樣:

new_task()
edit_task()

都使用同一套分類與日期規則。


重構後不能只看網站有沒有打開

Refactor 最重要的是:

功能不能變

所以我直接沿用 Day 19 的六組測試重新驗證。


回歸測試 1:新增任務,不合法日期

輸入:

due_date = not-a-date

結果:

400
沒有 Traceback

跟 Day 19 完全一樣。


回歸測試 2:新增任務,合法日期

輸入:

due_date = 2026-12-25

結果:

302
任務正確建立

正常日期沒有被重構改壞。


回歸測試 3:新增任務,日期空白

輸入:

due_date =

結果:

302
due_date = NULL

原本允許空日期的行為也沒有改變。


回歸測試 4:編輯任務,不合法日期

對既有任務送:

due_date = bad-date

結果:

400
沒有 Traceback

而且:

原本 Task 資料完全沒有被修改

回歸測試 5:編輯任務,合法日期

輸入:

due_date = 2026-09-26

結果:

302
更新成功

回歸測試 6:編輯任務,日期空白

輸入:

due_date =

結果:

302
due_date 更新為 NULL

回歸測試結果

測試 輸入 結果
new_task 不合法日期 not-a-date 400,無 Traceback
new_task 合法日期 2026-12-25 302,建立成功
new_task 空日期 空白 302,NULL
edit_task 不合法日期 bad-date 400,資料未改動
edit_task 合法日期 2026-09-26 302,更新成功
edit_task 空日期 空白 302,更新為 NULL

六組結果和 Day 19 完全一致。

測試過程產生的暫時資料和修改,也都已經清除或還原。


這次重構前後的差別

重構前:

new_task()
→ 自己查分類
→ 自己解析日期

edit_task()
→ 再查一次分類
→ 再解析一次日期

重構後:

new_task ─────┐
              ├→ resolve_category_id()
              └→ parse_due_date()

edit_task ────┐
              ├→ resolve_category_id()
              └→ parse_due_date()

功能沒有變,但重複程式少了。


Helper 的好處

假設之後日期格式的處理規則要改。

以前可能要記得修改:

new_task()
edit_task()

兩個地方。

現在只需要改:

parse_due_date()

就可以同時影響兩邊。

分類也是一樣。

這可以降低:

改了新增
忘了修改編輯

這種同步問題。


但也不是所有重複都要抽

這次我也開始理解,看到重複程式不能馬上就說:

抽函式

還要先問:

它們的責任真的一樣嗎?
行為真的完全一樣嗎?
抽完會比較清楚嗎?

像 index() 的分類查詢雖然看起來很像,但語意不同,所以保留原本寫法反而比較安全。


Day 19 和 Day 20 的差別

Day 19 是:

原本有 Bug
↓
修改程式
↓
改變錯誤行為

例如:

500 → 400

Day 20 則是:

原本就正常
↓
整理內部程式
↓
結果完全不變

所以這次最重要的驗證不是:

有沒有新功能

而是:

六組測試結果有沒有跟重構前完全一致

Day 20 做完後

今天沒有新增任何功能,也沒有把專案拆成複雜架構。

只是把目前已經出現的重複程式:

分類查詢
日期解析

整理成:

resolve_category_id()
parse_due_date()

最後六組回歸測試全部跟 Day 19 一樣。

這讓我對 Refactor 有一個比較具體的理解:

重構不是把程式改得越複雜越專業,而是在不改功能的情況下,讓重複減少、責任更清楚、之後更容易維護。

Day 20 到這裡完成。

下一步要開始處理一個對 Claude Code 很重要的檔案:

CLAUDE.md

讓 Claude Code 進入專案之後,不需要每次重新猜:

這個專案在做什麼
目前做到哪裡
有哪些規則不能亂動

上一篇
[Day 19] 讓 Claude Code 幫我修 Bug,但不能只相信它說「修好了」
系列文
從零開始 Claude Code:30 天打造 AI 任務管理 Web App 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言