🚀 Day 5:打造「MVC 模組化重構與 CLI 版發布 (Django Ready MVT 架構)」

5 天全日營隊 (Day 5 / 總時數 6 Hours) | 登頂軟體工程:模組解耦、網頁體驗與 Demo Day 發表

📖 為什麼學 these?——「從雜亂腳本到 MVT 頂級架構」關卡冒險故事線

💡 故事情境:理財咖啡館完工!把零散零件組裝為工程師級模組產品

前四天我們像是在廚房邊做菜邊把食材跟炒鍋亂塞。今天我們要化身為 **軟體架構師**,用 def 把程式拆解為掌管資料的 Model、負責畫面的 View 與指揮大腦 Controller,並在發表會上展示你的專案:

單元名稱 記帳程式關卡角色 為什麼這個單元不可或缺?(技術關聯)
單元 1:函式 (def) 與模組化 🍹 自動販賣機與模組封裝 將重複寫兩次以上的邏輯用 def 封裝(DRY 原則),投入參數傳回 return 結果,程式不再雜亂。
單元 2:MVT/MVC 職責分離重構 🏗️ 建築師重構藍圖 (MVT) 實作「職責分離」:Model 專管 JSON 讀寫、View 專管介面印出、Controller 負責指揮,無縫銜接 Django 核心!
單元 3:CLI UI 視覺美化排版 🎨 極客介面設計師 (TUI UI/UX) 運用 f-string 欄位對齊({:<10})與 Emoji / ASCII 藝術標題,打造工程師級的質感終端機介面。
單元 4:未來銜接:Django 網頁預告 🌐 跨界門戶前哨站 (Django Web) 把相同的 expenses.json 直接匯入 Django 網頁中,發現 CLI 列表與 HTML {% for %} 語法完全同構!
單元 5:專案打包與簡報準備 📦 產品出版說明書 (README.md) 撰寫 Markdown 格式的 README.md,向世界展示你的功能亮點、安裝步驟與 GitHub 專案規範。
單元 6:Demo Day 成果發表 🏆 理財咖啡館發布會 (Demo Day) 每人 2 分鐘上台展示專案亮點與 MVT 重構結構,接受導師反饋,體驗敏捷開發與社群分享文化!

📌 課程前導與簡介說明

🎓 教師心法與教學策略 (Teacher Only)
  • 營隊定位:最後一天從「初學者腳本」邁向「專業軟體工程」。重構為 **MVT 職責分離架構**。
  • 高中生心理特性:體驗程式碼變乾淨的清爽感,透過 Demo Day 發表建立自信。
  • Django 銜接點:明確標示 model_ (對應 models.py)、view_ (對應 HTML) 與 handle_ (對應 views.py/urls.py)。
🌟 學生自主學習指引 (Student Self-Study)

歡迎來到 Day 5 登頂時刻!今天你要把前四天的成果重構為「工程師級模組架構」!請閱讀每個單元的 **故事單元說明**,點擊 **延伸閱讀** 展開深層觀念與 Google 搜尋,並善用每區塊的 **AI 協作除錯提示詞**!

💻 Day 5 專案總成果:Django-Ready MVT 模組化記帳本

【教師參考解答】學生於 Day 5 結束時將能獨立撰寫的完整模組化程式:

"""
====================================================================
 高中生極簡終端機記帳本 (CLI 版 - Django 銜接預備架構)
 系統架構:採 MVT (Model - View - Template/Controller) 模組化設計
====================================================================
"""

import json
import os
from datetime import datetime

DATA_FILE = "expenses.json"

# ==========================================
# 📦 [MODEL 區塊] 資料庫與持久化操作 (對應 Django models.py / Database)
# ==========================================

def model_load_data():
    """ 讀取 JSON 資料檔 (模擬 Django ORM 物件查詢) """
    if not os.path.exists(DATA_FILE):
        return []
    try:
        with open(DATA_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return []

def model_save_data(records):
    """ 儲存資料至 JSON 檔案 (模擬 Django .save() 寫入資料庫) """
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(records, f, ensure_ascii=False, indent=4)

def model_add_record(records, item, price, category):
    """ 新增一筆紀錄 (模擬 Model 創立紀錄) """
    record = {
        "id": len(records) + 1,
        "item": item,
        "price": price,
        "category": category,
        "date": datetime.now().strftime("%Y-%m-%d %H:%M")
    }
    records.append(record)
    model_save_data(records)
    return record

def model_delete_record(records, index):
    """ 刪除指定索引的記錄 """
    if 0 <= index < len(records):
        deleted = records.pop(index)
        for i, r in enumerate(records):
            r["id"] = i + 1
        model_save_data(records)
        return deleted
    return None

# ==========================================
# 🎨 [VIEW 區塊] 介面渲染與印出 (對應 Django Templates / HTML)
# ==========================================

def view_header():
    print("\n" + "=" * 45)
    print("     🚀 酷炫 Python 終端機記帳本 🚀")
    print("=" * 45)

def view_menu():
    view_header()
    print("  [1] ➕ 新增消費紀錄")
    print("  [2] 📊 查看所有明細與統計")
    print("  [3] 🔍 依分類篩選紀錄")
    print("  [4] ❌ 刪除特定消費紀錄")
    print("  [5] 💾 儲存並退出系統")
    print("=" * 45)

def view_render_list(records):
    """ 渲染帳務明細 (模擬 Django HTML {% for %} 走訪) """
    if not records:
        print("\n📭 目前尚無任何記帳資料!")
        return

    print("\n=== 💰 您的記帳明細列表 ===")
    print(f"{'編號':<5}{'品項':<12}{'分類':<10}{'金額':<8}{'時間'}")
    print("-" * 50)
    
    total = 0
    for r in records:
        print(f"{r['id']:<5}{r['item']:<12}{r['category']:<10}${r['price']:<7}{r['date']}")
        total += r["price"]
        
    print("-" * 50)
    print(f"💵 總計累計消費:${total} 元\n")

# ==========================================
# ⚙️ [CONTROLLER / ROUTER 區塊] 控制邏輯 (對應 Django views.py / urls.py)
# ==========================================

def handle_add(records):
    """ 處理新增消費邏輯 """
    item = input("👉 請輸入消費名稱 (例如: 珍珠奶茶): ").strip()
    if not item:
        print("⚠️ 名稱不能為空!")
        return

    price_input = input("👉 請輸入金額: ").strip()
    if not price_input.isdigit():
        print("❌ 錯誤:金額必須是純數字!")
        return
    
    category = input("👉 請輸入分類 (例如: 餐飲/娛樂/學習): ").strip() or "一般"

    record = model_add_record(records, item, int(price_input), category)
    print(f"✅ 成功寫入:{record['item']} ${record['price']} 元 [{record['category']}]")

def handle_filter(records):
    """ 處理按分類搜尋邏輯 """
    cat = input("🔍 請輸入要搜尋的分類名稱: ").strip()
    filtered = [r for r in records if r["category"] == cat]
    print(f"\n🔎 搜尋分類結果: '{cat}'")
    view_render_list(filtered)

def handle_delete(records):
    """ 處理刪除邏輯 """
    view_render_list(records)
    if not records:
        return
    
    idx_input = input("❌ 請輸入要刪除的編號 (ID): ").strip()
    if idx_input.isdigit():
        idx = int(idx_input) - 1
        deleted = model_delete_record(records, idx)
        if deleted:
            print(f"🗑️ 已成功刪除:{deleted['item']} ${deleted['price']} 元")
        else:
            print("⚠️ 找不到該編號!")
    else:
        print("❌ 輸入無效,請輸入數字 ID。")

# ==========================================
# 🏁 [MAIN 區塊] 程式入口 (主生命週期)
# ==========================================

def main():
    records = model_load_data()
    while True:
        view_menu()
        choice = input("👉 請選擇功能選項 (1-5): ").strip()

        if choice == "1":
            handle_add(records)
        elif choice == "2":
            view_render_list(records)
        elif choice == "3":
            handle_filter(records)
        elif choice == "4":
            handle_delete(records)
        elif choice == "5":
            print("\n💾 系統資料已保存,期待下次運算!再見!👋")
            break
        else:
            print("⚠️ 無效的選項,請輸入數字 1 至 5!")

if __name__ == "__main__":
    main()

【學生自主實作框架】請根據今日各單元所學,將空缺的 `____` 填入正確程式碼:

"""
 🎯 Day 5 自主挑戰:請將下方的 ____ 填入正確的 def、return 與 MVT 模組語法!
"""
import json

# 1. [MODEL 區塊]: 專管資料讀寫 (絕對不寫 print/input)
____ model_save_data(records): # 提示:宣告函式的關鍵字
    with open("expenses.json", "w", encoding="utf-8") as f:
        json.dump(records, f, ensure_ascii=False, indent=4)

def model_add_record(records, item, price):
    record = {"item": item, "price": price}
    records.append(record)
    model_save_data(records)
    ____ record # 提示:將新建物件傳回給 Controller 的關鍵字

# 2. [VIEW 區塊]: 專管介面渲染
def view_render_item(item, price):
    print(f"✨ [UI 繪製] 商品: {item:<10} | 金額: ${price}")

# 3. [CONTROLLER 區塊]: 邏輯溝通橋樑
def handle_add(records):
    item = input("品項: ").strip()
    price = int(input("金額: "))
    # 呼叫 Model 建立資料,再呼叫 View 渲染畫面
    new_rec = model_add_record(records, item, price)
    view_render_item(new_rec["item"], new_rec["price"])

⏰ 單元 1:函式 (def) 與模組化(09:00 - 10:00)

🍹 故事單元說明:自動販賣機與模組封裝

函式(def)就像是「自動販賣機」!你把投幣與按下按鈕的動作定義好(傳入參數),投入硬幣後,自動販賣機處理內部邏輯,最後吐出你點的飲料(return 回傳結果)。遵守工程師的 **DRY 原則 (Don't Repeat Yourself)**:只要一段程式碼重複寫了兩次以上,就應該把它打包成獨立的販賣機(函式)!

💡 深度觀念:函式封裝與 Return 值的本質

函式是現代模組化程式設計(Modular Programming)的基礎:

  • returnprint() 的根本差異print() 只是把字串印在螢幕上(無法被後續程式利用);return 則是把計算結果傳回給呼叫者。
  • 純函式 (Pure Functions):相同的輸入永遠傳回相同的輸出,且無副作用 (Side Effect),極易維護與測試。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請向初學者詳細說明 Python def 函式中 return 的作用,並用簡單的例子解釋為什麼 print() 不能替代 return,以及什麼是 DRY (Don't Repeat Yourself) 原則。

🎓 教師備課手冊 (Teacher Only)
  • 宣告 def、傳遞參數與 return 值。
  • 強調 DRY 原則 (Don't Repeat Yourself)。
  • Django 銜接點:Django 中所有的 View 視圖邏輯本質上都是一個獨立的 Python 函式 def index(request):
💻 程式碼段落
# 1-1: 宣告簡單函式與 return 值
def calculate_tax(price, rate=0.05):
    """ 計算含稅價格 """
    tax = price * rate
    return price + tax

# 呼叫函式
total_price = calculate_tax(100)
print(f"含稅總價:${total_price:.0f} 元")
⚠️ 常見陷阱與問題案例
  • 漏寫 return 導致回傳值為 None:函式內部有計算但漏寫 return,變數接到 NoneType 拋出 TypeError
🤖 單元 1 常見問題 AI 協作除錯提示詞

我寫了一個 def 函式計算金額,但在外部呼叫並列印變數時卻印出了 None。請幫我檢查函式末端是否漏寫了 return 關鍵字。

⏰ 單元 2:MVT/MVC 職責分離重構(10:00 - 11:00)

🏗️ 故事單元說明:建築師重構藍圖 (MVT)

樂高積木為什麼能拼出城堡?因為每一個積木都有明確、獨立的功能!今天我們要採用軟體工程最頂級的 **職責分離 (Separation of Concerns)** 進行重構:
• **Model**:專門掌管 JSON 檔案讀寫(絕對不寫 printinput
• **View**:專門負責介面排版與渲染
• **Controller/Handler**:負責接收使用者輸入,並指揮 Model 與 View!

💡 深度觀念:職責分離 (SoC) 與 MVT/MVC 設計模式

將資料邏輯、顯示介面與控制流程解耦(Decoupling)是現代大專案維護的核心:

  • Model (models.py):對應資料庫結構與 ORM 查詢。
  • Template / View (templates/*.html):對應使用者看到的 HTML 網頁介面。
  • View / Controller (views.py / urls.py):接收 HTTP Request 並調用 Model 與 Template。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請向初學者解釋什麼是『職責分離 (Separation of Concerns)』,並對比 Django 的 MVT (Model, View, Template) 與傳統 MVC 架構的元件對映關係。

🎓 教師備課手冊 (Teacher Only)
  • 重構為 Model、View、Controller 三分法。
  • 規範:Model 裡面絕對不寫 print()input(),確保未來能直接移籍至 Web。
  • Django 銜接點:Django MVT 核心架構全貌(models.py, templates, views.py)。
💻 程式碼段落
# [Model 區塊]: 純資料操作
def model_get_total(records):
    return sum(r["price"] for r in records)

# [View 區塊]: 純 UI 排版
def view_show_total(total_amount):
    print(f"✨ [畫面渲染] 總金額為:${total_amount} 元 ✨")

# [Controller 區塊]: 邏輯溝通橋樑
def handle_show_total_flow(records):
    total = model_get_total(records) # 找 Model 要資料
    view_show_total(total)          # 交給 View 畫出畫面
⚠️ 常見陷阱與問題案例
  • 職責模糊 (Inappropriate Intimacy):在 Model 函式寫了 `input()`,導致資料層黏死在 CLI 上無法被網頁重複利用。
🤖 單元 2 常見問題 AI 協作除錯提示詞

請幫我檢查這段 MVT 重構程式碼,確保所有 input() 與 print() 都被抽離出 Model 函式,並只放在 View 與 Controller 中。

⏰ 單元 3:CLI UI 視覺美化排版(11:00 - 12:00)

🎨 故事單元說明:極客介面設計師 (TUI UI/UX)

工程師的軟體也需要高質感介面!透過 Emoji(`🚀`, `📊`, `❌`)、ASCII Title Art 與 f-string 欄位對齊 formatting 語法(如 {item:<12} 靠左對齊佔 12 格、{price:>8} 靠右對齊佔 8 格),印出精美如 Word 表格一般的 CLI 終端機介面!

💡 深度觀念:f-string 排版說明符與全半形字寬

Terminal 排版依賴精確的控制字元縮排:

  • 欄位對齊語法 {:<10} / {:>8}:靠左/靠右對齊並指定固定字元寬度。
  • 全半形寬度偏移:中文字元(全形佔 2 個字寬)與英數(半形佔 1 個字寬)混用時,可能造成 Console 欄位邊框稍微偏移。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請幫我設計一個漂亮的 Python CLI 終端機選單與明細表格,使用 {:<10} 欄位對齊語法與 Emoji 圖示,讓表格在終端機中看起來整齊劃一。

🎓 教師備課手冊 (Teacher Only)
  • 運用 Emoji、ASCII 標題與 f-string 縮排對齊。
  • 進階語法:{name:<12}(靠左佔 12 格)、{price:>8}(靠右佔 8 格)。
  • Django 銜接點:前端 HTML/CSS 樣式設計與使用者介面體驗 (UI/UX)。
💻 程式碼段落
# 表格化格式化輸出對齊
print(f"{'編號':<5}{'品項':<10}{'金額':<8}")
print("-" * 25)
print(f"{'1':<5}{'珍珠奶茶':<10}${60:<8}")
print(f"{'2':<5}{'雞排':<10}${85:<8}")
⚠️ 常見陷阱與問題案例
  • 全半形混用邊框偏移:中文全形佔 2 字寬,導致 ASCII 框線 | 線對不齊。
🤖 單元 3 常見問題 AI 協作除錯提示詞

我在 CLI 印出中文字與數字時,表格右側邊框 | 稍微對不齊。請幫我調整 f-string 的欄位寬度讓介面看起來整齊。

⏰ 單元 4:未來銜接:Django 網頁預告(13:00 - 14:00)

🌐 故事單元說明:跨界門戶前哨站 (Django Web)

神奇的時刻到了!導師將展示把剛才 CLI 新增的 expenses.json 資料,直接無縫載入至 Django Web 網頁中展示!你會發現:CLI 版 View 的 for 迴圈走訪,與 Django HTML 中的 {% for r in records %} 語法完全一模一樣!

💡 深度觀念:從 CLI 傳送字串 到 Web 渲染 HTML

Web 開發與 CLI 的核心差異在於資料渲染的載體:

  • CLI View:使用 print() 將字串格式化發送到 Terminal 視窗。
  • Django Web View:將 Context Data 裝填進 HTML Template,經由 HTTP Response 傳送給瀏覽器繪製 <table>

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請對比 Python CLI 的 view_render_list() 列表印出與 Django HTML 模板中的 {% for r in records %} 表格語法,說明它們在資料渲染上的異同。

🎓 教師備課手冊 (Teacher Only)
  • Live Demo:將 expenses.json 直接連載至極簡 Django 網頁中展示。
  • 語法對照:CLI 版 view_render_list()for 迴圈,與 Django HTML 中的 {% for r in records %} 完全同構。
  • Django 銜接點:引爆全棧 Web 開發的強烈後續學習動機。
💻 觀念對照碼段落 (Django HTML Template 對照)
<!-- 未來 Django HTML 模板中的對應渲染語法 (templates/expense_list.html) -->
<table border="1">
  <tr><th>品項</th><th>金額</th><th>分類</th></tr>
  {% for r in records %}
  <tr>
    <td>{{ r.item }}</td>
    <td>${{ r.price }}</td>
    <td>{{ r.category }}</td>
  </tr>
  {% endfor %}
</table>
⚠️ 常見陷阱與問題案例
  • 觀念混淆:以為網頁會自動執行 `print()` $\rightarrow$ 釐清網頁是 View 將 Context 塞入 HTML 後傳給瀏覽器。
🤖 單元 4 常見問題 AI 協作除錯提示詞

我想知道如何把剛才寫好的 Python CLI 記帳專案轉成 Django 網頁,請給我一張簡單的圖解說明 CLI 與 Django MVT 元件對應關係。

⏰ 單元 5:專案打包與簡報準備(14:00 - 15:00)

📦 故事單元說明:產品出版說明書 (README.md)

優秀的軟體產品不能沒有「說明書」!我們將學習 Markdown 語法撰寫 README.md 文件,寫出你的專案簡介、功能亮點、安裝執行指令與 MVT 模組架構說明,呈現專業工程師打包專案的態度!

💡 深度觀念:軟體文件 (Documentation) 與開源專案規範

良好的 Documentation 是開源社群(Open Source)的核心精神:

  • Markdown 語法:輕量級標記語言,使用 `#` 標題、```` 程式碼區塊、`-` 項目符號。
  • 專案 README 必備要素:專案簡介、功能亮點 (Features)、環境需求 (Prerequisites) 與執行步驟 (Usage)。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請幫我這份 Python CLI 記帳程式專案寫一份吸引人的 GitHub README.md,包含專案簡介、功能亮點、安裝執行步驟與採用的 MVT 模組化架構說明。

🎓 教師備課手冊 (Teacher Only)
  • 編寫 Markdown 格式 README.md 說明文件。
  • 輔導準備 2 分鐘 Demo Day 發表演練。
  • Django 銜接點:GitHub 專案管理與開源分享文化。
💻 README.md 範本段落
# 🚀 Python 終端機個人財務記帳本 (MVT Architecture)

一個基於 Python 撰寫的極簡 CLI 記帳工具,具備 JSON 資料持久化與完整 CRUD 功能。

## 🌟 功能特色
- 📦 **MVT 架構解耦**:資料層與 UI 介面高度模組化
- 💾 **自動持久化**:開機自動讀取 `expenses.json`
- 🛡️ **安全防呆**:內建 `try-except` 防止輸入崩潰

## 🔧 執行方式
```bash
python main.py
```
⚠️ 常見陷阱與問題案例
  • 根目錄路徑不一致:Terminal 執行 python main.py 跳出 No such file or directory $\rightarrow$ 檢查 VS Code 開啟的目錄。
🤖 單元 5 常見問題 AI 協作除錯提示詞

我在 VS Code 終端機執行 python main.py 時跳出 No such file or directory。請教我如何用 cd 指令切換至正確的檔案目錄。

⏰ 單元 6:Demo Day 專案成果發表(15:00 - 16:00)

🏆 故事單元說明:理財咖啡館發布會 (Demo Day)

恭喜大家!在短短 30 個小時內,你們從連 print() 都不懂,一路解鎖了變數、資料結構、JSON 持久化,甚至獨立寫出了 MVT 模組化記帳軟體!在最後的 Demo Day 發表會上,請用 2 分鐘自信展示你的產品亮點與彩蛋功能吧!

💡 深度觀念:Sprint Demo 與工程師溝通力

將技術成果說給別人聽是極客工程師重要的軟實力 (Soft Skills):

  • 敏捷 Sprint Demo:展示真正的運作軟體 (Working Software) 接受 User 反饋。
  • 優雅面對 Live Bug:即使現場展示遇到 Bug,冷靜閱讀 Terminal Traceback,正是展現除錯實力的最佳時刻!

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請扮演一位熱情的資深軟體架構師,針對一位完成 Python CLI MVT 模組化記帳程式的高中生寫一段 100 字的正向結業評語,肯定他的模組重構與除錯努力,並鼓勵他未來繼續學習 Django。

🎓 教師備課手冊 (Teacher Only)
  • 每人 2 分鐘展示專案亮點與 MVT 分離結構。
  • 營造正向、溫馨且具備 Geek 發布會氛圍的結業儀式。
  • Django 銜接點:敏捷開發 (Agile) 中的 Sprint Demo 與工程團隊分享文化。
⚠️ 常見陷阱與問題案例
  • Live Demo 遇到突發 Bug:告訴學生「連蘋果發布會都會遇到 Bug!冷靜看 Traceback,展現工程師除錯實力!」
🤖 單元 6 常見問題 AI 協作除錯提示詞

我在 Demo 簡報時現場執行程式跳出了意外的 Bug。請告訴我如何冷靜閱讀 Terminal 訊息,並用專業的姿態向大家說明除錯步驟。