iT邦幫忙

2025 iThome 鐵人賽

DAY 4
1
Software Development

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

Day 4- 鐵律:向後相容性是神聖不可侵犯的

  • 分享至 

  • xImage
  •  

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

消除你程式碼的臭味 Day 4- 鐵律:向後相容性是神聖不可侵犯的

工程師的首要職責是建構可靠的東西。而可靠的基礎只有一條,也是唯一重要的一條規則。

We do not break userspace!
我們不破壞使用者空間

https://ithelp.ithome.com.tw/upload/images/20250906/20124462qppjmgzaZi.png
這句話的意思很簡單:任何會導致現有應用程式、腳本或工具無法運作的改動,都是一個 Bug

不管理由多麼「充分」,也不管新設計在理論上多麼「成功」。
只要它破壞了現有的功能或服務,它就是錯的。

主要任務是服務使用者,不是給他們製造麻煩。
向後相容性是我們必須履行的契約,如果必須做出破壞性的變更,正確的做法是新增一個新版本,而不是直接修改舊版本。

經典案例:API 變更的災難

🔴 破壞向後相容性的改動

// 版本 1.0
class UserService {
    createUser(name, email, age) { /* ... */ }
    getUser(id) { /* ...回傳 user 物件 */ }
}

// 版本 2.0 - 一個徹底的災難
class UserService {
    // 🔴 錯誤 1:無預警變更參數順序。所有舊的呼叫全錯了。
    createUser(email, name, age) { /* ... */ }

    // 🔴 錯誤 2:改變回傳格式。所有依賴舊格式的程式碼全部崩潰。
    getUser(id) {
        const user = this.users.get(id);
        return user ? { data: user, status: 'success' } : null;
    }
}

如果這麼做了,製造的不是「升級」,而是一堆爛攤子。

🟢 務實的做法:擴展,而不是破壞

一個合格的開發者只會做一件事:新增。
舊的 API 必須確保一樣穩定。

// 版本 2.0 - 唯一正確的設計
class UserService {
    // 保留舊的介面,它必須永遠能用。
    // 你可以在內部標記它為棄用,但不能刪除或修改它的行為。
    createUser(name, email, age) {
        console.warn('createUser() is deprecated. Use createUserV2().');
        return this.createUserV2({ email, name, age });
    }

    // 🟢 提供新的、更好的版本。
    createUserV2(userData) { /* ... 新的實作邏輯 */ }

    // 舊的 API 保持原樣。
    getUser(id) { /* ... 原本的實作 */ }

    // 🟢 為新功能新增一個全新的 API。
    getUserWithStatus(id) {
        const user = this.users.get(id);
        return user ? { data: user, status: 'success' } : null;
    }
}

向後相容性的三大原則

  1. 新增,不要修改
    想改東西?那就寫個新的。
// 🔴 修改現有 API
class Database {
    query(sql) {
        // 改變了回傳格式
        return { data: this.executeQuery(sql), metadata: {} };
    }
}

// 🟢 新增 API,保持舊的
class Database {
    query(sql) {
        // 保持原有行為
        return this.executeQuery(sql);
    }
    
    queryWithMetadata(sql) {
        // 新的 API
        return { data: this.executeQuery(sql), metadata: {} };
    }
}

2. 預設值處理
如果你非得給函式加個參數,就必須提供一個預設值,讓舊的呼叫程式碼不會出錯。

// 🔴 強制要求新參數
function processData(data, newRequiredParam) {
    // 現有程式碼會因為缺少參數而崩潰
}

// 🟢 提供預設值
function processData(data, newParam = null) {
    // 現有程式碼繼續工作
}

3. 漸進式棄用
正確的做法是將其標記為「棄用 (deprecated)」,給出警告,並提供替代方案。
給時間去遷移,而不是在半夜讓人去修程式碼。

class LegacyAPI {
    constructor() {
        this.deprecationWarnings = new Set();
    }
    
    // 標記為棄用,但不立即移除
    deprecatedMethod() {
        if (!this.deprecationWarnings.has('deprecatedMethod')) {
            console.warn('deprecatedMethod is deprecated. Use newMethod instead.');
            this.deprecationWarnings.add('deprecatedMethod');
        }
        
        // 仍然工作,但使用新的實作
        return this.newMethod();
    }
    
    newMethod() {
        // 新的實作
        return 'new implementation';
    }
}

向後相容性檢查清單

在進行任何 API 變更前,問自己:

  1. 這個變更會破壞現有的程式碼嗎?
  2. 有多少使用者會受到影響?
  3. 能否通過新增 API 而不是修改現有 API 來實現?
  4. 能否提供遷移路徑?
  5. 這個變更的價值是否超過破壞性變更的成本?

https://ithelp.ithome.com.tw/upload/images/20250906/20124462tIwPcZ4Nxe.png

今日重點

  • 向後相容性不是一個選項,而是一條鐵律。
  • 新增是預設動作,修改是例外,刪除是禁止的。
  • 你的一次「小小的重構」,可能是別人整個週末的惡夢。
  • 寫出讓人信賴的程式碼,意思就是寫出穩定且可預測的程式碼。別搞那些花俏的、會爆炸的驚喜。

上一篇
Day 3- 實用主義:拒絕象牙塔裡的完美理論
下一篇
Day 5- 簡潔性:程式碼是斯巴達式的
系列文
消除你程式碼的臭味5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言