6.4 客戶機程式3—產生串連代碼模組
對於我們的第三個客戶機程式,即客戶機程式3,通過將它封裝到函數do_connect() 和do_disconnect() 中,將使串連和斷開代碼更加模組化,這樣可以很容易地由多個客戶機程式使用。這提供一種選擇,可將串連代碼精確地嵌入到main() 函數中。無論如何,對在應用程式過程中套用老調的任何代碼都是一個好主意。將它放在可以通過多個程式訪問的函數中,而不是在每個程式中都編寫一遍。如果修正這個函數中的一個錯誤或對這個函數作了一些改進,則可只更改一次,只要重新編譯就可以使用這個函數的所有程式都被修正或利用這種改進。同樣,編寫一些客戶機程式,以便在它們執行過程中可以若干次地串連和斷開。如果將安裝和卸載方法放在串連和斷開的函數中,則編寫這樣一個客戶機更加容易。封裝策略如下所示:
1) 將公用代碼分離到一個獨立的源檔案( common.c)的封裝函數中。
2) 提供一個標頭檔,common.h,其中包括該公用常式的原型。
3) 在使用公用常式的客戶機源檔案中包括common.h。
4) 將公用源檔案編譯成目標檔案。
5) 將公用目標檔案串連到您的客戶機程式中。
用這些策略,讓我們構造do_connect() 和do _ disconnect( )。
do_connect() 代替對mysql_init() 和mysql_real_connect() 的調用,並替換錯誤列印的代碼。除了不傳遞任何串連處理常式外,您可以像mysql_real_connect() 一樣調用它。do_connect() 分配並初始化這個處理常式,然後,在串連後返回一個指向它的指標。如果do_ connect() 失敗,則在列印一個錯誤訊息以後,返回NULL(那就是說,調用do_connect() 並擷取傳回值NULL的任何程式都可以簡單地退出,而不用擔心列印訊息的本身)。do_ disconnect () 產生一個指向串連處理常式的指標,並調用mysql_close ()。這裡是common.c 的代碼:
common.h 聲明common.c 中這些常式的原型:
要想訪問公用常式,應在源檔案中包括common.h。請注意, common.c 同樣包括common.h。那就是說,如果common.c 中的函數定義與標頭檔中的聲明不匹配,則立即得到一個編譯器警告。同樣,如果更改common.c 中的調用次序而沒有相應地更改common.h,則當重新編譯common.c 時,編譯器將發出警告。
有人會問為什麼要發明封裝函數do _ disconnect( ),而它使用得還這麼少。do _ disconnect( )和mysql_close() 等價。但是假設在中斷連線時,都有一些要執行的額外清除。則通過調用已經完全控制的封裝函數,可以修改該封裝函數來做需要的事情,對於所做的任何斷開的操作,這種更改統一生效。如果直接調用mysql_ close( ),則不能做到這點。在前面,筆者聲稱對在多個程式中或在單個程式內部多處使用的函數中,將代碼封裝成模組化代碼是有好處的。前面介紹一個理由,還有一些理由參見下面的兩個範例。
■ 範例1在MySQL3 .22以前的版本中,mysql_real_connect() 調用與它現在稍微有些不同:即沒有資料庫名稱參數。如果想利用舊的MySQL客戶機庫使用do _ connect( ),則它不能工作。然而,可以修改do _ connect( ),使它可在3.22版以前的版本上運行。這就
意味著,通過修改do _ connect( ),可以增加使用它的所有程式的可移植性。如果將這些串連代碼直接嵌入到每個客戶機中,則必須獨立地修改它們中的每一個。
要想修正do _ connect( ),使它可以處理mysql_real_connect() 的舊格式,那麼就可以使用包括當前MySQL版本MySQL_VERSION_ID 宏。更改了的do_connect() 測試MySQL_VERSION_ID 值,並使用mysql_real_connect() 的正確格式:
除了下述兩點以外, do_connect() 的這個修改過的版本和前一個版本在外觀上是完全一樣的:
■ 它不將db_name 參數傳遞給mysql_real_connect() 較早的格式,因為那個版本沒有這樣的參數。
■ 如果資料庫名稱是非NULL 的,則do_connect() 調用mysql_select_db() 使指定的資料庫為當前資料庫(這類似於沒有db_name 參數的效果)。如果沒有選擇這個資料庫,則do_connect() 列印一個錯誤訊息,關閉串連,並返回NULL 來表示失敗。
■ 範例2 該範例是在對第一個範例的do_connect() 做更改的基礎上建立的。那些更改導致對錯誤函數mysql_errno() 和mysql_error() 的三組調用。每次都將報告問題的這些代碼書寫出來是非常討厭的。除此之外,錯誤所列印出的代碼看起來不舒服,讀起
來也困難。而讀下面這樣的代碼就比較容易:
print_error (conn, “mysql_real_connect() failed”) ;所以,讓我們在print_error() 函數中封裝錯誤列印。即使conn 為NULL,也可以編寫它來做一些明智的事情。也就是說,如果mysql_init() 調用失敗,可以使用print _error( )。而且沒有混合調用(一些為fprintf ( ),一些為print _ error( ))。我聽到一些反對意見:“為了想報告一個錯誤而又不必每次都調用兩個錯誤函數,所以使代碼故意編寫得難以閱讀,以說明封裝範例更好。其實不用真的寫出所有的錯誤列印代碼:只將它編寫一次,然後當再次需要時就使用拷貝和粘貼即可。”這種觀點是正確的,但我持反對意見,理由如下:
■ 即使使用拷貝和粘貼,用較短的程式碼片段進行起來也更容易。
■ 每當報告錯誤時,無論是否願意每次調用兩種函數,將所有的錯誤報表代碼書寫得很長,會產生不一致性。將錯誤報表的代碼放在容易調用的封裝函數中,就可以減少這種想法並提高編碼的一致性。
■ 如果決定修改錯誤訊息的格式,則只需要在一個地方而不是整個程式中做更改,這樣就要容易許多。或者,如果決定將錯誤訊息編寫到記錄檔中而不是(或除此以外還)編寫到stderr 中,則只須更改print _ error ( ),這就更容易。這種方法可能犯更少的錯誤,而且再一次減少了工作量和不一致的可能性。
■ 當測試程式時,如果使用偵錯工具,將斷點放在錯誤報表的函數中,則當它偵測出一個錯誤條件時,偵錯工具是使程式中斷的一種便利方法。以下是錯誤報表函數print _ error( )的使用舉例:
主源檔案client3.c 與client2.c 一樣,但是所有嵌入的串連和斷開代碼都利用調用封裝函數來刪除和替換了。如下所示: