有點失望 -- 總結篇

(如果不知道在下這超長書評的始末,建議先看 http://www.douban.com/review/2963028/)
第4-6章書評 http://www.douban.com/review/2971127/
第7-8章書評 http://www.douban.com/review/2973532/
第9-12章書評 http://www.douban.com/review/2975703/
雖然這本書使我有點失望,無可否認,它是獨特的。
第一,作者公開了他的商業用代碼,相信沒有很多國內公司會批准這麼做。這讓同行可以窺探競爭對手的狀況,所謂知己知彼,百戰百勝。
第二,本書描述的範疇,確實是國內比較少見的。從內容來說,個人認為較到題的(但會賣得不好的)書名應該是《C(++)伺服器程序庫開發》,範疇來說接近《C++網絡編程》,但減去網絡、理論及++。
第三,本書能令國內程序員(作者除外)間討論許多技術性話題,也讓我們去獨立思考求證,網友間互相學習。我懷疑是內地 IT 出版史上的一個標誌性刊物。
意見其實比較多,時間關係,以下輕談一些簡單的。
== C/C++ ==
在我第一篇評論裡,因為主要是指出1-3章懷疑錯誤的地方,我沒有提及對本書 C++ 代碼的意見。但我和很多讀者都有一些微言。看畢全書後,個人有以下分析。
面向對象 (Object Oriented) 在書中是重新被定義的 (見 P. 216)。個人認為,本書使用 C++ 的方式,比較接近 Object-based 語言 (http://en.wikipedia.org/wiki/Object-based_language)──沒有繼承 (inheritance)、沒有多態 (polymorphism),只是把狀態 (state) 和操作 (operations) 結合成為對象,並實行資料封裝(data encapsulation)。
但個人覺得,在這種情況下,又不使用 C++ 的標準庫,單純用 C 更能使程序實現和介面變得簡單,而同時保留了資料封裝。舉例:
// C++ 版本 header
class Mutex
{
public:
Mutex();
~Mutex() throw();
void Lock();
void Unlock();
private:
Mutex(const Mutex&); // 防止 copy construction
Mutex& operator=(const Mutex&); // 防止 assignment
HANDLE m_Mutex;
};
// 使用 C++ 版本
Mutex* m= new Mutex;
m->Lock();
m->Unlock();
delete m;
// ---------------------------------------
// C 版本 header
struct Mutex; // 前置聲明
Mutex* MutexCreate();
void MutexDestroy(Mutex*);
void MutexLock(Mutex*);
void MutexUnlock(Mutex*);
// C 版本 implementation (不用在對外 header 定義)
struct Mutex
{
HANDLE m_Mutex;
};
// Uisng C version
Mutex* m= MutexCreate();
MutexLock(m);
MutexUnlock(m);
MutexDestroy(m);
雖然這種比較不完全等價 (C++ 可以創建非指針的Mutex型別),但可以很清楚看到,C 版本的 API (在 header 裡的函數定義) 比 C++ 還簡潔,而且跨平台部份(那個 struct) 也不會顯露在 header 裡 (這有點不準確,C++ 可用 Pimpl 手法,不過就更複雜了)。
使用時,C 版本的懷處是要在每個函數前加 Mutex 前綴,但這樣可能還增加了可讀性。
我的結論是,這種 object-based 方式用 C 語言比較好。用 C++ 去封裝這樣的 C 的 API 的作用不大,只會更複雜。
書中因為沒有重載 operator new 或使用 replacement new,出現要 C++ 類內部使用 malloc() 形式去分配 struct 成員變量,使代碼更複雜及難閱讀。簡潔的代碼是避免出錯的關鍵。
== 防禦式編程==
書中的代碼,經常用 if() 來檢查一些正確程式不會出現的情況。例如最常見是一些 precondition:
// P. 336
bool CTonyBuffer::SetSize(int nSize)
{
if (!m_pMemPool) return false; // 防禦性設計,如果內存池指針為空,無法工作
// ...
}
在該類裡,m_pMemPool是在建構函數就指定的,之後該對象的生命周期裡不能改變這個變量。所以在正確的程式裡 m_pMemPool == NULL 不應該在運作期出現。出現的話,就是代碼本身有錯誤。
使用 return 去逃避這個錯誤,會使那個 bug 更難被發現 (例如調用者沒有檢查傳回值,又或者調用者檢查到錯誤又繼續逃避這個錯誤)。
比較好的方法是立即終止程序,並提示在那個函式出錯。沒錯,這就是 C 的 assert()。而實際應用時通常會客制化這個函式(實際上是宏)。測試沒發現錯誤的話,可在發行版忽略 assert 去減少 overhead。
減少這種 if 亦等同減少代碼的複雜度和測試難度(一些複雜度 metric 包含計算有多少個條件分支)。
簡單來說,應盡量讓 bug 浮現出來。
== 修改代碼 ==
書中多個章節都強調,盡量不要修改代碼,例如 P. 100 「......任何代碼的修改、替換都可能造成新的不穩定,造成大量的成本浪費。因此,一般說來,一但一個算法被証明成熟並且入庫,則以後類似場合,只要沒有太大的性能差異,都應該無條件采用,不允許重新編寫。」
這個說法,讓我想起《修改代碼的藝術》(Working Effectively with Legacy Code),作者定義「遺留代碼」(Legacy code) 就是那些沒有編寫相應測試的代碼。
在不同的情況我們需要修改代碼,例如重構(改善現存代碼的設計)、增加/修改功能、優化效能等等。一個好的軟件工程,應該可以不停適應新的需求。有測試代碼就可以勇敢地去候修改代碼。
所以我不贊成要避免修改代碼,相反要努力修改代碼去改善軟件質素。
== 結語 ==
之前讀了3章就放棄,能讀完這一本書,對我而言是一個挑戰。當然,寫這麼長篇的評論,更是一個挑戰。
本書是集作者十多年的軟件開發經驗而成的作品。這本書可以讓讀者們更清楚了解到,目前中國軟件行業的一些狀況。
書中經常提及一個重點,要獨立思考、分析問題,作出合理、甚至是創新的解決方案。但是,本書所缺乏的,卻是一種求學的態度。
常言道,我們站在巨人的肩膀上。我們先要認識自己的渺小,要站得更高,先要慢慢爬上那個巨人上。
== 封底 ==
(總結的結語後還有封底......)
讀過封底關於本書目標讀者的句子,我就「突發奇想」:
「如果你是一名初次进入职场的软件专业学生,本书可以助你迅速掌握企业商业化开发的思路和技巧。」
你可能會在面試中遇到一點障礙。
「如果你是一名C和C++的学习者和爱好者,本书可以助你掌握很多实际的技巧,并获得一个现成可用的工程库。」
你可能會發現 C 和 C++ 的兼容性很大,可以把 C++ 當作 C 來用。
「如果你是一名商业公司的程序员,已经掌握了很多商用开发的思维和技巧,本书能给你一点新的、意想不到的提示。」
你可能學會了突發奇想,和別人分享一些常識。
「如果你是一名网络游戏公司的开发人员,本书的多任务工程库可能会对你很有帮助。」
你可能要考慮應否用幾千個時間中斷來更新幾千個遊戲物件。
「如果你是一名商用服务器的开发人员,本书可以助你掌握如何利用C和C++实现7×24小时稳定性的服务器的技巧。」
你可能會重新定義稳定性或者甚麼叫做 bug。
「如果你是一名嵌入式开发人员,本书中严格的代码规范和数据边界意识,对嵌入式之类资源较少且有长期运行要求的设备开发很有帮助。」
你可能會用一個全局內存池分配器服務所有綫程。
「如果你是一名使用其他语言的程序员,本书中很多基本模块的突现,如内存池、线程池等,对Java等程序员理解自己平台的相同模块,很有帮助,并且,本书提出的商用开发思想,是跨语言跨平台的,也很有参考价值。」
你可能會覺得選擇做Java程序員真的選對了。
「如果你是一名产品经理、项目经理或者架构师,本书中提出的很多商用系统工程的设计理念,是作者多年开发的经验结晶,对于系统的设计、商用项目的风险管控,有很好的参考作用。」
你可能會增強自信心。
第4-6章書評 http://www.douban.com/review/2971127/
第7-8章書評 http://www.douban.com/review/2973532/
第9-12章書評 http://www.douban.com/review/2975703/
雖然這本書使我有點失望,無可否認,它是獨特的。
第一,作者公開了他的商業用代碼,相信沒有很多國內公司會批准這麼做。這讓同行可以窺探競爭對手的狀況,所謂知己知彼,百戰百勝。
第二,本書描述的範疇,確實是國內比較少見的。從內容來說,個人認為較到題的(但會賣得不好的)書名應該是《C(++)伺服器程序庫開發》,範疇來說接近《C++網絡編程》,但減去網絡、理論及++。
第三,本書能令國內程序員(作者除外)間討論許多技術性話題,也讓我們去獨立思考求證,網友間互相學習。我懷疑是內地 IT 出版史上的一個標誌性刊物。
意見其實比較多,時間關係,以下輕談一些簡單的。
== C/C++ ==
在我第一篇評論裡,因為主要是指出1-3章懷疑錯誤的地方,我沒有提及對本書 C++ 代碼的意見。但我和很多讀者都有一些微言。看畢全書後,個人有以下分析。
面向對象 (Object Oriented) 在書中是重新被定義的 (見 P. 216)。個人認為,本書使用 C++ 的方式,比較接近 Object-based 語言 (http://en.wikipedia.org/wiki/Object-based_language)──沒有繼承 (inheritance)、沒有多態 (polymorphism),只是把狀態 (state) 和操作 (operations) 結合成為對象,並實行資料封裝(data encapsulation)。
但個人覺得,在這種情況下,又不使用 C++ 的標準庫,單純用 C 更能使程序實現和介面變得簡單,而同時保留了資料封裝。舉例:
// C++ 版本 header
class Mutex
{
public:
Mutex();
~Mutex() throw();
void Lock();
void Unlock();
private:
Mutex(const Mutex&); // 防止 copy construction
Mutex& operator=(const Mutex&); // 防止 assignment
HANDLE m_Mutex;
};
// 使用 C++ 版本
Mutex* m= new Mutex;
m->Lock();
m->Unlock();
delete m;
// ---------------------------------------
// C 版本 header
struct Mutex; // 前置聲明
Mutex* MutexCreate();
void MutexDestroy(Mutex*);
void MutexLock(Mutex*);
void MutexUnlock(Mutex*);
// C 版本 implementation (不用在對外 header 定義)
struct Mutex
{
HANDLE m_Mutex;
};
// Uisng C version
Mutex* m= MutexCreate();
MutexLock(m);
MutexUnlock(m);
MutexDestroy(m);
雖然這種比較不完全等價 (C++ 可以創建非指針的Mutex型別),但可以很清楚看到,C 版本的 API (在 header 裡的函數定義) 比 C++ 還簡潔,而且跨平台部份(那個 struct) 也不會顯露在 header 裡 (這有點不準確,C++ 可用 Pimpl 手法,不過就更複雜了)。
使用時,C 版本的懷處是要在每個函數前加 Mutex 前綴,但這樣可能還增加了可讀性。
我的結論是,這種 object-based 方式用 C 語言比較好。用 C++ 去封裝這樣的 C 的 API 的作用不大,只會更複雜。
書中因為沒有重載 operator new 或使用 replacement new,出現要 C++ 類內部使用 malloc() 形式去分配 struct 成員變量,使代碼更複雜及難閱讀。簡潔的代碼是避免出錯的關鍵。
== 防禦式編程==
書中的代碼,經常用 if() 來檢查一些正確程式不會出現的情況。例如最常見是一些 precondition:
// P. 336
bool CTonyBuffer::SetSize(int nSize)
{
if (!m_pMemPool) return false; // 防禦性設計,如果內存池指針為空,無法工作
// ...
}
在該類裡,m_pMemPool是在建構函數就指定的,之後該對象的生命周期裡不能改變這個變量。所以在正確的程式裡 m_pMemPool == NULL 不應該在運作期出現。出現的話,就是代碼本身有錯誤。
使用 return 去逃避這個錯誤,會使那個 bug 更難被發現 (例如調用者沒有檢查傳回值,又或者調用者檢查到錯誤又繼續逃避這個錯誤)。
比較好的方法是立即終止程序,並提示在那個函式出錯。沒錯,這就是 C 的 assert()。而實際應用時通常會客制化這個函式(實際上是宏)。測試沒發現錯誤的話,可在發行版忽略 assert 去減少 overhead。
減少這種 if 亦等同減少代碼的複雜度和測試難度(一些複雜度 metric 包含計算有多少個條件分支)。
簡單來說,應盡量讓 bug 浮現出來。
== 修改代碼 ==
書中多個章節都強調,盡量不要修改代碼,例如 P. 100 「......任何代碼的修改、替換都可能造成新的不穩定,造成大量的成本浪費。因此,一般說來,一但一個算法被証明成熟並且入庫,則以後類似場合,只要沒有太大的性能差異,都應該無條件采用,不允許重新編寫。」
這個說法,讓我想起《修改代碼的藝術》(Working Effectively with Legacy Code),作者定義「遺留代碼」(Legacy code) 就是那些沒有編寫相應測試的代碼。
在不同的情況我們需要修改代碼,例如重構(改善現存代碼的設計)、增加/修改功能、優化效能等等。一個好的軟件工程,應該可以不停適應新的需求。有測試代碼就可以勇敢地去候修改代碼。
所以我不贊成要避免修改代碼,相反要努力修改代碼去改善軟件質素。
== 結語 ==
之前讀了3章就放棄,能讀完這一本書,對我而言是一個挑戰。當然,寫這麼長篇的評論,更是一個挑戰。
本書是集作者十多年的軟件開發經驗而成的作品。這本書可以讓讀者們更清楚了解到,目前中國軟件行業的一些狀況。
書中經常提及一個重點,要獨立思考、分析問題,作出合理、甚至是創新的解決方案。但是,本書所缺乏的,卻是一種求學的態度。
常言道,我們站在巨人的肩膀上。我們先要認識自己的渺小,要站得更高,先要慢慢爬上那個巨人上。
== 封底 ==
(總結的結語後還有封底......)
讀過封底關於本書目標讀者的句子,我就「突發奇想」:
「如果你是一名初次进入职场的软件专业学生,本书可以助你迅速掌握企业商业化开发的思路和技巧。」
你可能會在面試中遇到一點障礙。
「如果你是一名C和C++的学习者和爱好者,本书可以助你掌握很多实际的技巧,并获得一个现成可用的工程库。」
你可能會發現 C 和 C++ 的兼容性很大,可以把 C++ 當作 C 來用。
「如果你是一名商业公司的程序员,已经掌握了很多商用开发的思维和技巧,本书能给你一点新的、意想不到的提示。」
你可能學會了突發奇想,和別人分享一些常識。
「如果你是一名网络游戏公司的开发人员,本书的多任务工程库可能会对你很有帮助。」
你可能要考慮應否用幾千個時間中斷來更新幾千個遊戲物件。
「如果你是一名商用服务器的开发人员,本书可以助你掌握如何利用C和C++实现7×24小时稳定性的服务器的技巧。」
你可能會重新定義稳定性或者甚麼叫做 bug。
「如果你是一名嵌入式开发人员,本书中严格的代码规范和数据边界意识,对嵌入式之类资源较少且有长期运行要求的设备开发很有帮助。」
你可能會用一個全局內存池分配器服務所有綫程。
「如果你是一名使用其他语言的程序员,本书中很多基本模块的突现,如内存池、线程池等,对Java等程序员理解自己平台的相同模块,很有帮助,并且,本书提出的商用开发思想,是跨语言跨平台的,也很有参考价值。」
你可能會覺得選擇做Java程序員真的選對了。
「如果你是一名产品经理、项目经理或者架构师,本书中提出的很多商用系统工程的设计理念,是作者多年开发的经验结晶,对于系统的设计、商用项目的风险管控,有很好的参考作用。」
你可能會增強自信心。
有关键情节透露