iT邦幫忙

2025 iThome 鐵人賽

DAY 7
1
Software Development

消除你程式碼的臭味系列 第 7

Day 7- 資料結構至上:好程式碼的關鍵思考

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250903/20124462P1N8QjGguI.png

消除你程式碼的臭味 Day 7- 資料結構至上:好程式碼的關鍵思考

搞錯了資料結構,寫的就不是程式碼,是日後要爆炸的地雷。

Bad programmers worry about the code. Good programmers worry about data structures.
爛程式碼都在擔心邏輯,好程式碼都在擔心資料結構。

當你的資料結構設計正確時,邏輯就會變得簡單;反之,你的資料結構設計錯誤時,邏輯就會變得複雜。

經典案例:資料結構決定程式碼品質

🔴 臭味:用陣列處理所有事

不知道用什麼,就先用個陣列,這是典型的懶人思維。
後果就是接下來寫的每一個函式,都得為這個不好的決定付出代價。

class UserManager {
    constructor() {
        // 用陣列來存需要頻繁查找的資料,這是個災難的開始。
        this.users = []; 
    }
    
    // 每一個函式都必須做一次 O(n) 的全陣列掃描。
    // 使用者少的時候沒感覺,使用者一多,你的系統就等著癱瘓吧。
    findUserById(id) {
        for (let i = 0; i < this.users.length; i++) {
         /* 掃描... */ }
    }
    updateUser(id, updates) {
        for (let i = 0; i < this.users.length; i++) {
         /* 又是掃描... */ }
    }
    deleteUser(id) {
        for (let i = 0; i < this.users.length; i++) { 
        /* 掃描掃描掃描...有點煩.... */ }
    }
}

https://ithelp.ithome.com.tw/upload/images/20250909/20124462Uzyydbxrrc.png
上面每一行 for 迴圈,都是在為一開始的錯誤決策擦屁股。

🟢 好資料結構:根據存取模式選擇結構

你需要根據 ID 快速查找?
那就用一個以 ID 為鍵的 Map (雜湊表),做好資料結構與索引結構。

// 使用 Map 儲存使用者資料,邏輯變得簡單
class UserManager {
    constructor() {
        // 需要用 ID 查?準備一個用 ID 當 key 的 Map。
        this.usersById = new Map(); 
        // 需要用 Email 查?再準備一個用 Email 當 key 的 Map。
        this.usersByEmail = new Map();
    }

    addUser(user) {
        // 寫入時多花一點點力氣,維護好索引。
        this.usersById.set(user.id, user);
        this.usersByEmail.set(user.email, user);
    }

    // 現在查找操作變成了 O(1),瞬間完成。
    findUserById(id) {
        return this.usersById.get(id) || null;
    }
    findUserByEmail(email) {
        return this.usersByEmail.get(email) || null;
    }
    
    // 更新和刪除也一樣,先快速定位,再操作。
    // 看看這裡,還有 for 迴圈嗎?沒有了。
    // 簡單的邏輯是正確資料結構的自然結果。
    deleteUser(id) {
        const user = this.usersById.get(id);
        if (!user) return false;
        
        this.usersById.delete(id);
        this.usersByEmail.delete(user.email);
        return true;
    }
}

https://ithelp.ithome.com.tw/upload/images/20250909/20124462Gt8MrqsFGO.png

在你寫任何程式碼前,先回答這三個問題

忘掉那些空泛的「原則」。
在定義任何類別或函式之前,先盯著你的資料,回答下面這幾個問題:

1.你要如何存取這些資料?(決定效能)

最頻繁的操作是什麼?
是查找、插入、還是遍歷?如果你需要頻繁地根據某個 key 查找一個項目,還用陣列,那你就是在自找麻煩。

查找資料:Array vs. Map 的效率差異

https://ithelp.ithome.com.tw/upload/images/20250909/20124462O6ej5wfmXE.png

2. 你的「唯一事實來源 (Single Source of Truth)」是什麼?(決定一致性)

一旦你把同一個資訊存在兩個地方,你就已經為自己埋下了一顆 Bug 定時炸彈。
總有一天,你會在更新一個地方時,忘了更新另一個。

永遠不要儲存可以被計算出來的衍生資料
CPU 很快,計算 array.length 的成本,遠比你花費數小時去除錯一個資料不同步的 Bug 要低得多。

🔴 錯誤:手動同步狀態
一個訂單列表,又單獨存一個 orderCount 變數

class OrderSystem {
    constructor() {
        this.orders = [];
        this.orderCount = 0; // 這個變數就是一顆地雷。
    }
    addOrder(order) {
        this.orders.push(order);
        this.orderCount++; // 如果忘了這行呢?
    }
}

🟢 正確:從唯一事實來源計算

class OrderSystem {
    constructor() {
        this.orders = new Map(); // 單一、可靠的資料來源。
    }
    // 需要數量?直接從來源計算。永遠正確。
    getOrderCount() {
        return this.orders.size;
    }
}

https://ithelp.ithome.com.tw/upload/images/20250909/201244626eEOVOyif7.png

3. 資料之間的關係是什麼?(決定完整性)

資料不是孤立存在的。評論屬於文章,訂單項目屬於訂單。
你的資料結構必須反映這種關係。
如果你的 postscomments 是兩個獨立的大陣列,那麼當你刪除一篇文章時,就極有可能忘記刪除它下面的所有評論,製造出一堆沒人要的「孤兒資料」。

🔴 錯誤:分離的資料

class BlogSystem {
    constructor() {
        this.posts = [];
        this.comments = [];
    }
    // 刪除文章時,很容易忘記去 comments 陣列裡清理垃圾。
    deletePost(postId) { /* ... */ }
}

🟢 正確:結構反映關係

class BlogSystem {
    constructor() {
        this.posts = new Map(); // post.id => { postData, comments: [] }
    }
    // 把評論直接放在它所屬的文章物件裡。
    addComment(postId, comment) {
        this.posts.get(postId)?.comments.push(comment);
    }
    // 刪除文章時,所有相關的評論也跟著被一併刪除。不可能產生孤兒資料。
    deletePost(postId) {
        this.posts.delete(postId);
    }
}

https://ithelp.ithome.com.tw/upload/images/20250909/20124462UtVrlZVaHf.png

今日重點

  • 資料結構決定程式碼的複雜度
  • 根據操作頻率選擇合適的資料結構
  • 避免資料重複,保持單一資料來源
  • 正確建模資料關係

如果說複雜的函式是程式碼的「臭味」,那混亂的資料結構就是產生臭味的「腐敗源頭」。
先處理源頭,臭味自然消除。


上一篇
Day 6- 函式原子化:每個函式只做一件事,而且做得漂亮
下一篇
Day 8- 消除抽象層:直接存取資料,不要繞路
系列文
消除你程式碼的臭味12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言