[C 的那些眉角]函式介面設計 — 呼叫時不易搞錯

話說有一種 bug 很特別。

不是邏輯寫錯,不是演算法有問題,
而是呼叫端用錯了你的函式

參數順序傳反了、單位搞錯了、忘記先初始化就呼叫、
buffer 大小傳錯了……

這種 bug 有時候很難發現,因為程式可能還是跑起來,
只是結果不對,或是偶爾 crash。

我以前遇到這種情況,第一反應是「呼叫端的問題,他用錯了」。

但後來慢慢體會到:如果很多人都用錯,問題通常在介面設計,不在使用者。

閱讀全文

[C 的那些眉角]回傳值不要亂丟 — 錯誤處理的設計

剛開始寫 C 的時候,我的錯誤處理大概是這樣:

void init_device(void) {
    i2c_init();
    sensor_init();
    uart_init();
    // 完成,應該沒問題吧?
}

回傳 void,裡面每個函式的回傳值都不管。

反正在開發板上跑都正常,就這樣出貨了。

然後客戶回報說裝置偶爾會初始化失敗,
但 log 完全看不出來哪個步驟出問題,
因為根本沒有任何錯誤處理。

那次之後,我開始認真思考:錯誤處理不是可選的,是必須的。


最常見的壞習慣

1. 完全不處理回傳值

閱讀全文

[C 的那些眉角]一個函式只做一件事 — 聽起來簡單但很難

「一個函式只做一件事。」

這句話我很早就聽過,覺得自己懂了,然後繼續寫出這種東西:

int process_sensor_data(void) {
    // 讀取感測器
    uint8_t raw[8];
    i2c_read(SENSOR_ADDR, raw, sizeof(raw));

    // 解析資料
    int16_t temp = (raw[0] << 8) | raw[1];
    int16_t humidity = (raw[2] << 8) | raw[3];

    // 換算單位
    float temp_c = temp / 100.0f;
    float humi_pct = humidity / 100.0f;

    // 檢查是否超過閾值
    if (temp_c > 85.0f || humi_pct > 95.0f) {
        trigger_alarm();
    }

    // 存到全域變數
    g_temperature = temp_c;
    g_humidity = humi_pct;

    // 發送到 server
    mqtt_publish("sensor/data", temp_c, humi_pct);

    return 0;
}

這個函式做了幾件事?

讀取、解析、換算、判斷、存值、發送。六件事。

當時覺得「這樣寫很方便,一個函式搞定所有事情」。
直到需要修改的時候才發現,牽一髮動全身,
改個閾值要找半天,加個錯誤處理不知道要加在哪裡,
單元測試根本不知道從哪裡下手。

閱讀全文

[C 的那些眉角]命名不是小事 — 讓程式碼自己說話

前言

剛開始寫 C 的時候,我覺得命名這件事不重要。

反正編譯器不在乎變數叫 a 還是 temperature,程式跑起來結果一樣。
直到有一天,我打開三個月前自己寫的程式碼,盯著一個叫 tmp2 的變數看了五分鐘,
完全不知道它是幹嘛的。

那個人是我自己。

從那之後,我開始認真對待命名這件事。這篇是我的一些整理,
不是什麼業界標準,就是我覺得「這樣寫,之後比較不會想罵自己」的習慣。


壞命名長什麼樣子

閱讀全文