I.1 Make interface explicit
不好的示例->
int round(double d)
{
return (round_up) ? ceil(d) : d; // don't: "invisible" dependency
}
這個例子我主要理解是這樣的,函數名是round
但是控制邏輯中有在調用時非常可能被忽略的全局變量round_up
。我理解round_up
應該出現在參數列表裏。但是這種設計非常有可能出現在類的成員函數裏,設想如下代碼片段(非原文)
class A{
int round(double d) {
return (round_up) ? ceil(d) : d;
}
round_up = true;
};
在round()
的調用點,我們還是要檢查成員變量round_up
的值才能期待round()
給出正確的行爲。原文最後有一個看似這種的guideline,就是如果函數比較簡單,那麼它不應該執行基於全局變量(namespace級別的變量)的邏輯。
I.4 Make interfaces precisely and strongly typed
作者強調了函數參數最好要typed,
void draw_rectangle(Point top_left, Point bottom_right);
void draw_rectangle(Point top_left, Size height_width);
draw_rectangle(p, Point{10, 20}); // two corners
draw_rectangle(p, Size{10, 20}); // one corner and a (height, width) pair
所有上述例子中,當參數直接使用數字時,很難推斷出參數的含義。作者推薦使用enum作爲boolean flag使用。
enable_lamp_options(lamp_option::on | lamp_option::animate_state_transitions);
所以,當一個函數接受超過兩個bool變量作爲參數時,這個函數的interface可能需要重新設計。作者再次強調參數的數值單位的重要性。在CppCon2017 Bjarne Stroustrup上做的報告有特殊講到數值單位引起的bug導致一個NASA的火星衛星沒有進入預定軌道,科研人員15年的研究付之東流。
I.5 State preconditions
推薦了GSL
的Expects()
函數。
I.6 Prefer Expects()
for expressing preconditions
再次推薦使用Expects()
,但是C++20估計都沒有標準化。
I.7 State postconditions
下面代碼爲了確保res
作爲整形不會溢出。這種情況在進行整數運算時可能會出現,例如對RGB圖像進行處理時,大部分時間數據對象可能是uint8_t類型,數值在0-255之間。
int area(int height, int width)
{
auto res = height * width;
Ensures(res > 0);
return res;
}
另一個例子
void f() // better
{
char buffer[MAX];
// ...
memset(buffer, 0, sizeof(buffer));
Ensures(buffer[0] == 0);
}
此實例中,不加入Ensures()
時,編譯器優化階段可能會remove掉memset()
。
此外原文列出了一個多線程mutex鎖的示例
void manipulate(Record& r) // best
{
lock_guard<mutex> _ {m};
// ...
}
改示例確保m將在函數結束時釋放,無論是否發生Exception。
I.9 If an interfaceis a template, document its parameters using concepts.
使用concept
在編譯期間對模板參數進行檢查。特指出GCC6.1之後都對concept
進行了支持。
I.10 Use exceptions to signal a failure to perfomr a required task
作者認爲對於定義好的error,應該拋出異常而不是利用返回值表示狀態。特別指出performance 並不是拒絕使用exception的好理由。若可以今早檢查異常,可以提升critical code的performance。
I.11 Never transfer ownership by a raw pointer or reference
這裏強調了rvalue 和move semantics。
I.12 Declare a pointer that must not be null as not_null
Wow, that’s nice.
int length(const char* p); // it is not clear whether length(nullptr) is valid
length(nullptr); // OK?
int length(not_null<const char*> p); // better: we can assume that p cannot be nullptr
int length(const char* p); // we must assume that p can be nullptr
not_null
定義在GSL
中。
I.13 Do not pass an array as a single pointer
如下的代碼可能產生多種錯誤
void copy_n(const T* p, T* q, int n); // copy from [p:p+n) to [q:q+n)
包括q
的大小不足n,p
的內容少於n。使用下述代碼會更好
void copy(span<const T> r, span<T> r2); // copy r to r2
I.22 Avoid complex initialization of global objects
原文這裏編號一下跳到了I.22。原文指出全局變量的初始化次序是不確定的,所以儘量不要使用。全局變量初始化依賴函數應該是constexpr
。
I.23 Keep the number of function arguments low
當函數的參數過多時,非常肯恩意味着“one function, one responsibility”的規則被打破。同時,過多的參數說明這些參數應當被抽象成type。
作者認爲,4個參數以上的,都算是參數數量太多。
I.24 Avoid adjacent unrelated parameters of the same type
意思很簡單,參數表裏,如果兩個參數的類型完全一致,但是參數次序不能調換,那麼這種參數表容易被用錯。
考慮將參數表定義成一個struct
,這樣在調用函數時需要用變量名指定函數的每一個參數,降低了發生錯誤的風險。但是這樣做代碼邊長了。有點像python的keyword argument。
I.25 Prefer abstract classes as interfaces to class hierarchies
意思也比較簡單,不定義base class,而只用abstract class。在abstract class中,沒有成員變量,所有藉口都是=0的虛函數。