首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

劣質代碼評析:《C語言入門經典》附錄B 21點遊戲(一)—— 結構混亂與邏輯缺陷

     編輯:關於C語言

背景

《寫給大家看的 C 語言書(第 2 版)》附錄 B 提供一個 21點(Blackjack)遊戲範例。本系列文章將深度剖析其代碼問題,並展示重構過程。這是第一部分,著重於整體架構與核心邏輯缺陷。

原始代碼主要問題

1. 函數設計違反單一職責原則

dispCard() 同時負責「顯示牌面」與「計算點數」,導致莊家/玩家抽牌時邏輯耦合。例如:

void dispCard(int cardDrawn, int points[]) {
    switch(cardDrawn) {
        case 11: puts("Jack"); points[ACELOW] += 10; points[ACEHIGH] += 10; break;
        // ... 其他情況同樣混合顯示與計分
    }
}

後果:無法單獨測試顯示邏輯;計分規則分散在多處;莊家與玩家共用同一 points 陣列易造成狀態混淆。

2. 全域變數濫用與狀態管理混亂

原代碼大量使用全域變數 (askedForName, cards[], playerPoints[], dealerPoints[], total[] 等),函數間隱性耦合極強。例如 initCardsScreen() 初始化多個全域陣列,dealerGetsCard()、playerGetsCard() 直接修改全域狀態,導致程式流程難以追蹤。

3. 遞迴與迴圈邏輯錯誤

玩家回合使用遞迴呼叫 playerGetsCard(),但終止條件依賴使用者輸入與點數判斷,極易導致堆疊溢位或邏輯死循環。莊家回合同樣問題。

4. 輸入處理脆弱

getAns() 使用 scanf() 或 getchar() 無緩衝區清理,換行符殘留會導致後續輸入讀取失敗。

5. 魔術數字與硬編碼

牌面值 (1, 11, 12, 13)、點數計算 (10, 11)、陣列大小等硬編碼在多處,維護性極差。

重構方向預告

下一部分將展示如何:

  1. 引入結構體 POKER (牌堆) 與 GAMER (玩家狀態) 封裝資料
  2. 拆分 dispCard() 為純顯示 disp() 與計分 update()
  3. 用迴圈取代遞迴控制遊戲流程
  4. 建立 dealcard()、getcard()、declare_winner() 等單一職責函數

教訓總結

  • 教學範例 ≠ 生產代碼:書本為講解語法常犧牲架構,實務中必須重構
  • 全域變數是惡魔:封裝狀態到結構體,透過函數參數傳遞
  • 函數要「單一」:顯示、計分、邏輯控制分離
  • 遞迴慎用於遊戲主迴圈:迴圈更清晰、安全、效率高

這個 21 點案例雖然代碼品質低,但恰好是練習「代碼審查與重構」的絕佳教材——比寫新代碼更能鍛鍊工程思維。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved