函數是 C 語言面向過程編程的基本構建塊。一個設計優良的函數如同精密儀器:介面清晰、內聚單一、易測易改。本文從工程實踐角度,剖析函數設計的核心原則、常見反模式與重構指南。
parse_json 只解析,不下載、不寫文件、不打印日誌。Config / Context 傳指針。// ❌ 9 參數:調用極易誤序、漏傳、擴展困難
void vMErrReport(
SINT4 vlSrvId, SINT2 vnErrLevel, SINT8 vnErrPos,
const SCHAR* vsapModuleName, const SCHAR* vsapFileName,
const SCHAR* vsapErrTitle, const SCHAR* vsapErrDetail,
SINT4 vlReturnVal, SINT4 vlSysError
);
// ✅ 重構:參數對象模式
typedef struct {
int srv_id;
int err_level;
long err_pos;
const char *module;
const char *file;
const char *title;
const char *detail;
int ret_val;
int sys_err;
} ErrReportCtx;
void report_error(const ErrReportCtx *ctx); // 單一指針,擴展不破壞調用點
// ❌ 每次迭代調用 strlen for (int i = 0; i < strlen(s); ++i) putchar(s[i]); // ✅ 提取不變量 size_t len = strlen(s); for (int i = 0; i < len; ++i) putchar(s[i]);
// ❌ 隱式依賴全局緩衝區,不可重入、難測試
char g_buf[1024];
void format_msg(int code) { sprintf(g_buf, "ERR_%d", code); }
// ✅ 顯式傳遞輸出緩衝區
void format_msg(int code, char *buf, size_t sz) {
snprintf(buf, sz, "ERR_%d", code);
}
| 維度 | 優良實踐 | 違規信號 |
|---|---|---|
| 命名 | 動詞短語:calculate_crc32 | 名詞、縮寫、過於通用 process |
| 參數 | 輸入 const、輸出指針、上下文結構體 | >3 參數、無 const、混雜輸入輸出 |
| 返回 | 狀態碼 / 結果指針 / 結構體 | 全局變量、錯誤碼混雜業務值 |
| 副作用 | 文檔明確標註 (修改參數/IO/鎖/內存) | 隱式修改全局/靜態變量 |
| 錯誤處理 | 返回錯誤碼、輸出參數帶回詳情、不直接 exit | 調用 abort/exit、忽略返回值 |
| 可測試性 | 無靜態/全局依賴、依賴注入 | 耦合單例、硬編碼路徑 |
typedef int (*CmpFunc)(const void*, const void*);
void generic_sort(void *base, size_t n, size_t sz, CmpFunc cmp) {
// 通用排序邏輯,cmp 注入比較策略
}
// 使用
int cmp_int(const void *a, const void *b) { ... }
generic_sort(arr, n, sizeof(int), cmp_int);
static 限制作用域於編譯單元,避免符號衝突。函數設計不是代碼排版,而是系統邊界的切割藝術。每一次提取函數,都是在定義模塊契約;每一次合併參數,都是在收斂變異點。寫函數前先問:這個函數的唯一職責是什麼?它的邊界在哪裡? 答案清晰,代碼自然直達本質。