C++ 被淹沒于接口中。函數(shù)接口、類接口、模板接口。每一個接口都意味著客戶的代碼和你的代碼互相影響。假設(shè)你在和通情達(dá)理的人打交道,那些客戶也想做好工作。他們想要正確使用你的接口。在這種情況下,如果他們犯了一個錯誤,就說明你的接口至少有部分是不完善的。在理想情況下,如果一個接口的一種嘗試的用法不符合客戶的預(yù)期,代碼將無法編譯,反過來,如果代碼可以編譯,那么它做的就是客戶想要的。
開發(fā)易于正確使用,而難以錯誤使用的接口需要你考慮客戶可能造成的各種錯誤。例如,假設(shè)你正在設(shè)計(jì)一個代表時間的類的構(gòu)造函數(shù):
class Date {
public:
Date(int month, int day, int year);
...
};
匆匆一看,這個接口似乎是合乎情理的(至少在美國),但是客戶可能很容易地造成兩種錯誤。首先,他們可能會以錯誤的順序傳遞參數(shù):
Date d(30, 3, 1995); // Oops! Should be "3, 30" , not "30, 3"
第二,他們可能傳遞一個非法的代表月或日的數(shù)字:
Date d(2, 20, 1995); // Oops! Should be "3, 30" , not "2, 20"
(后面這個例子看上去好像沒什么,但是想想鍵盤上,2 就在 3 的旁邊,這種 "off by one" 類型的錯誤并不罕見。)
很多客戶錯誤都可以通過引入新的類型來預(yù)防。確實(shí),類型系統(tǒng)是你阻止那些不合適的代碼通過編譯的主要支持者。在當(dāng)前情況下,我們可以引入簡單的包裝類型來區(qū)別日,月和年,并將這些類型用于 Data 的構(gòu)造函數(shù)。
struct Day { struct Month { struct Year {
explicit Day(int d) explicit Month(int m) explicit Year(int y)
:val(d) {} :val(m) {} :val(y){}
int val; int val; int val;
}; }; };
class Date {
public:
Date(const Month& m, const Day& d, const Year& y);
...
};
Date d(30, 3, 1995); // error! wrong types
Date d(Day(30), Month(3), Year(1995)); // error! wrong types
Date d(Month(3), Day(30), Year(1995)); // okay, types are correct
將日,月和年做成封裝數(shù)據(jù)的羽翼豐滿的類比上面的簡單地使用 struct 更好,但是即使是 struct 也足夠證明明智地引入新類型在阻止接口的錯誤使用方面能工作得非常出色。
只要放置了正確的類型,它往往能合理地限制那些類型的值。例如,月僅有 12 個合法值,所以 Month 類型應(yīng)該反映這一點(diǎn)。做到這一點(diǎn)的一種方法是用一個枚舉來表現(xiàn)月,但是枚舉不像我們希望的那樣是類型安全(type-safe)的。例如,枚舉能被作為整數(shù)使用。一個安全的解決方案是預(yù)先確定合法的 Month 的集合:
class Month {
public:
static Month Jan() { return Month(1); } // functions returning all valid
static Month Feb() { return Month(2); } // Month values; see below for
... // why these are functions, not
static Month Dec() { return Month(12); } // objects
... // other member functions
private:
explicit Month(int m); // prevent creation of new
// Month values
... // month-specific data
};
Date d(Month::Mar(), Day(30), Year(1995));
如果用函數(shù)代替對象來表現(xiàn)月的主意讓你感到驚奇,那可能是因?yàn)槟阃朔蔷植快o態(tài)對象(non-local static objects)的初始化的可靠性是值得懷疑的。Item 4 能喚起你的記憶。
防止可能的客戶錯誤的另一個方法是限制對一個類型能夠做的事情。施加限制的一個普通方法就是加上 const。例如,Item 3 解釋了使 operator* 的返回類型具有 const 資格是如何能夠防止客戶對用戶自定義類型犯下這樣的錯誤:
if (a * b = c) ... // oops, meant to do a comparison!
實(shí)際上,這僅僅是另一條使類型易于正確使用而難以錯誤使用的普遍方針的一種表現(xiàn):除非你有很棒的理由,否則就讓你的類型的行為與內(nèi)建類型保持一致??蛻粢呀?jīng)知道像 int 這樣的類型如何表現(xiàn),所以你應(yīng)該努力使你的類型的表現(xiàn)無論何時都同樣合理。例如,如果 a 和 b 是 int,給 a*b 賦值是非法的。所以除非有一個非常棒理由脫離這種表現(xiàn),否則,對你的類型來說這樣做也應(yīng)該是非法的。
避免和內(nèi)建類型毫無理由的不相容的真正原因是為了提供行為一致的接口。很少有特性比一致性更易于引出易于使用的接口,也很少有特性比不一致性更易于引出令人郁悶的接口。STL 容器的接口在很大程度上(雖然并不完美)是一致的,而且這使得它們相當(dāng)易于使用。例如,每一種 STL 容器都有一個名為 size 的成員函數(shù)可以知道容器中有多少對象。與此對比的是 Java,在那里你對數(shù)組使用 length 屬性,對 String 使用 length 方法,而對 List 卻要使用 size 方法,在 .net 中,Array 有一個名為 Length 的屬性,而 ArrayList 卻有一個名為 Count 的屬性。一些開發(fā)人員認(rèn)為集成開發(fā)環(huán)境(IDEs)能補(bǔ)償這些瑣細(xì)的矛盾,但他們錯了。矛盾在開發(fā)者工作中強(qiáng)加的精神折磨是任何 IDE 都無法完全消除的。
開發(fā)易于正確使用,而難以錯誤使用的接口需要你考慮客戶可能造成的各種錯誤。例如,假設(shè)你正在設(shè)計(jì)一個代表時間的類的構(gòu)造函數(shù):
class Date {
public:
Date(int month, int day, int year);
...
};
匆匆一看,這個接口似乎是合乎情理的(至少在美國),但是客戶可能很容易地造成兩種錯誤。首先,他們可能會以錯誤的順序傳遞參數(shù):
Date d(30, 3, 1995); // Oops! Should be "3, 30" , not "30, 3"
第二,他們可能傳遞一個非法的代表月或日的數(shù)字:
Date d(2, 20, 1995); // Oops! Should be "3, 30" , not "2, 20"
(后面這個例子看上去好像沒什么,但是想想鍵盤上,2 就在 3 的旁邊,這種 "off by one" 類型的錯誤并不罕見。)
很多客戶錯誤都可以通過引入新的類型來預(yù)防。確實(shí),類型系統(tǒng)是你阻止那些不合適的代碼通過編譯的主要支持者。在當(dāng)前情況下,我們可以引入簡單的包裝類型來區(qū)別日,月和年,并將這些類型用于 Data 的構(gòu)造函數(shù)。
struct Day { struct Month { struct Year {
explicit Day(int d) explicit Month(int m) explicit Year(int y)
:val(d) {} :val(m) {} :val(y){}
int val; int val; int val;
}; }; };
class Date {
public:
Date(const Month& m, const Day& d, const Year& y);
...
};
Date d(30, 3, 1995); // error! wrong types
Date d(Day(30), Month(3), Year(1995)); // error! wrong types
Date d(Month(3), Day(30), Year(1995)); // okay, types are correct
將日,月和年做成封裝數(shù)據(jù)的羽翼豐滿的類比上面的簡單地使用 struct 更好,但是即使是 struct 也足夠證明明智地引入新類型在阻止接口的錯誤使用方面能工作得非常出色。
只要放置了正確的類型,它往往能合理地限制那些類型的值。例如,月僅有 12 個合法值,所以 Month 類型應(yīng)該反映這一點(diǎn)。做到這一點(diǎn)的一種方法是用一個枚舉來表現(xiàn)月,但是枚舉不像我們希望的那樣是類型安全(type-safe)的。例如,枚舉能被作為整數(shù)使用。一個安全的解決方案是預(yù)先確定合法的 Month 的集合:
class Month {
public:
static Month Jan() { return Month(1); } // functions returning all valid
static Month Feb() { return Month(2); } // Month values; see below for
... // why these are functions, not
static Month Dec() { return Month(12); } // objects
... // other member functions
private:
explicit Month(int m); // prevent creation of new
// Month values
... // month-specific data
};
Date d(Month::Mar(), Day(30), Year(1995));
如果用函數(shù)代替對象來表現(xiàn)月的主意讓你感到驚奇,那可能是因?yàn)槟阃朔蔷植快o態(tài)對象(non-local static objects)的初始化的可靠性是值得懷疑的。Item 4 能喚起你的記憶。
防止可能的客戶錯誤的另一個方法是限制對一個類型能夠做的事情。施加限制的一個普通方法就是加上 const。例如,Item 3 解釋了使 operator* 的返回類型具有 const 資格是如何能夠防止客戶對用戶自定義類型犯下這樣的錯誤:
if (a * b = c) ... // oops, meant to do a comparison!
實(shí)際上,這僅僅是另一條使類型易于正確使用而難以錯誤使用的普遍方針的一種表現(xiàn):除非你有很棒的理由,否則就讓你的類型的行為與內(nèi)建類型保持一致??蛻粢呀?jīng)知道像 int 這樣的類型如何表現(xiàn),所以你應(yīng)該努力使你的類型的表現(xiàn)無論何時都同樣合理。例如,如果 a 和 b 是 int,給 a*b 賦值是非法的。所以除非有一個非常棒理由脫離這種表現(xiàn),否則,對你的類型來說這樣做也應(yīng)該是非法的。
避免和內(nèi)建類型毫無理由的不相容的真正原因是為了提供行為一致的接口。很少有特性比一致性更易于引出易于使用的接口,也很少有特性比不一致性更易于引出令人郁悶的接口。STL 容器的接口在很大程度上(雖然并不完美)是一致的,而且這使得它們相當(dāng)易于使用。例如,每一種 STL 容器都有一個名為 size 的成員函數(shù)可以知道容器中有多少對象。與此對比的是 Java,在那里你對數(shù)組使用 length 屬性,對 String 使用 length 方法,而對 List 卻要使用 size 方法,在 .net 中,Array 有一個名為 Length 的屬性,而 ArrayList 卻有一個名為 Count 的屬性。一些開發(fā)人員認(rèn)為集成開發(fā)環(huán)境(IDEs)能補(bǔ)償這些瑣細(xì)的矛盾,但他們錯了。矛盾在開發(fā)者工作中強(qiáng)加的精神折磨是任何 IDE 都無法完全消除的。