iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
Software Development

文藝復興:這段程式碼,好像有點味道系列 第 4

Day 04|身兼一切的工地主任:巨大類別 (Large Class)

  • 分享至 

  • xImage
  •  

蓋一座圓頂,需要的角色其實很多

  • 有人決定結構怎麼設計
  • 有人管磚頭、木料怎麼採購
  • 有人算工錢、記帳
  • 有人監督工地現場的安全與進度

佛羅倫斯教堂的營建委員會,把這些角色分得很清楚
設計決策交給主建築師,財務與採購交給專責的監理人,各行會的工匠各司其職

沒有人期待同一個人,同時扛下所有這些事

如果一個人身兼一切

想像一個反事實:如果整座工地,只有一個「主任」,什麼都歸他管
他要懂結構力學,也要會記帳;要盯採購進度,也要處理工人的請假申請

一開始,工地很小,一個人還能應付
但隨著工程越蓋越大,事情越堆越多

他變成了瓶頸,任何決定都要等他點頭,任何一件事出錯,都不知道該找誰

這正是「巨大類別(Large Class)」在程式碼裡的樣子

一個類別,戴了四頂帽子

假設我們要開發一個小型店鋪的管理系統,AI 幫我們生出了一個 ShopManager

public class ShopManager
{
    // --- 商品資料 ---
    public List<Product> Products { get; set; } = new();

    // --- 庫存 ---
    public Dictionary<int, int> StockLevels { get; set; } = new();

    // --- 員工排班 ---
    public List<Shift> Shifts { get; set; } = new();

    // --- 銷售紀錄 ---
    public List<Sale> Sales { get; set; } = new();

    public void AddProduct(Product product) { /* ... */ }

    public void RestockProduct(int productId, int qty)
    {
        StockLevels[productId] = StockLevels.GetValueOrDefault(productId) + qty;
    }

    public bool AssignShift(int employeeId, DateTime date)
    {
        // 檢查排班衝突、法定工時...
        Shifts.Add(new Shift(employeeId, date));
        return true;
    }

    public decimal RecordSale(int productId, int qty)
    {
        StockLevels[productId] -= qty;
        var price = Products.First(p => p.Id == productId).Price * qty;
        Sales.Add(new Sale(productId, qty, price));
        return price;
    }

    public string GenerateMonthlyReport()
    {
        var totalRevenue = Sales.Sum(s => s.Amount);
        return $"本月營收:{totalRevenue:C},共 {Sales.Count} 筆交易";
    }
}

看起來每個方法都不長,單獨看都合理

但整個類別,同時扛了商品管理、庫存、排班、銷售紀錄、報表五種職責

為什麼「都放一起」會出問題

問題不是某個方法寫得不好,是這些職責,本來就不該住在同一個屋簷下

  • 想改「排班規則」的人,得先讀懂庫存和報表的邏輯,才敢確定沒改壞別的東西
  • 想測試「銷售計算對不對」,得先建出一整包跟排班、商品都無關的資料
  • 兩個工程師,一個改排班、一個改報表,同一個檔案,衝突衝到想要扁對方

這就像那個身兼一切的工地主任
不是他能力不夠,是這個角色從一開始就不該由一個人扛

用「這個類別要負責什麼」造句時,如果得用好幾個「和」字才講得完,它大概已經是一個巨大類別了

把角色分還給專責的人

回到營建委員會的做法:把混在一起的職責,還原成各自獨立的角色

換成程式碼,這是提煉類別 (Extract Class)

public class ProductCatalog
{
    private readonly List<Product> _products = new();
    public void Add(Product product) => _products.Add(product);
    public Product Get(int productId) => _products.First(p => p.Id == productId);
}

public class InventoryTracker
{
    private readonly Dictionary<int, int> _stockLevels = new();
    public void Restock(int productId, int qty) =>
        _stockLevels[productId] = _stockLevels.GetValueOrDefault(productId) + qty;
    public void Deduct(int productId, int qty) => _stockLevels[productId] -= qty;
}

public class ShiftScheduler
{
    private readonly List<Shift> _shifts = new();
    public bool Assign(int employeeId, DateTime date)
    {
        _shifts.Add(new Shift(employeeId, date));
        return true;
    }
}

public class SalesReporter
{
    private readonly List<Sale> _sales = new();
    public void Record(Sale sale) => _sales.Add(sale);
    public string GenerateMonthlyReport() =>
        $"本月營收:{_sales.Sum(s => s.Amount):C},共 {_sales.Count} 筆交易";
}

原本的 ShopManager,變成一個薄薄的協調者,把工作委派給各自的專責角色:

public class ShopManager
{
    private readonly ProductCatalog _catalog;
    private readonly InventoryTracker _inventory;
    private readonly ShiftScheduler _scheduler;
    private readonly SalesReporter _reporter;

    public ShopManager(ProductCatalog catalog, InventoryTracker inventory,
        ShiftScheduler scheduler, SalesReporter reporter)
    {
        _catalog = catalog;
        _inventory = inventory;
        _scheduler = scheduler;
        _reporter = reporter;
    }

    public decimal RecordSale(int productId, int qty)
    {
        _inventory.Deduct(productId, qty);
        var price = _catalog.Get(productId).Price * qty;
        _reporter.Record(new Sale(productId, qty, price));
        return price;
    }
}

四個角色,各自只需要懂自己的那一份工作

改排班規則,不會動到報表;測試銷售邏輯,不需要準備排班資料

怎麼判斷「該分家了」

不是類別大就一定有問題,判斷的重點在內聚力

  • 這個類別裡,是不是有一群欄位和方法,永遠只跟彼此打交道,跟其他部分很少往來?
  • 幫這個類別取名字的時候,會不會忍不住想加上「和」、「與」?
  • 兩個不相關的需求,會不會總是要求你改同一個檔案?

只要有一題答案是肯定的,這個類別大概已經戴了不只一頂帽子

自我檢查清單

  1. 我能用一句話、不加「和」字,說完這個類別的職責嗎?
  2. 類別裡的欄位,是不是有一群只被少數幾個方法用到?
  3. 上次改這個類別,是不是連帶要讀懂一堆不相關的邏輯?
  4. 兩個不同方向的需求,是否都要求改動同一個檔案?
  5. 如果把這個類別拆成兩三個,每一個還能不能獨立說得出自己的職責?

明日預告

明天我們看一個更隱蔽的問題:
一句 if (customerType == "VIP"),看起來人畜無害,卻可能是整個系統最脆弱的一個環節

模組一第三站:原始型別執念(Primitive Obsession)


上一篇
Day 03|一氣呵成的迷思:過長方法 (Long Method)
系列文
文藝復興:這段程式碼,好像有點味道4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言