放之四海皆適用的設計原則(二)
讓模組善始善終
在軟體行業,模組化設計早已深入人心,因為透過模組化這種“分而治之”的方法能有效地降低設計的複雜度。如何獲得更好的模組化設計不是這裡要討論的重點,本書只關注模組的初始化與終止化這兩個關鍵點。
在大多的嵌入式系統中,模組的執行是從呼叫它的初始化函式開始的。與模組大多有初始化函式相比,忽視為模組設計終止化函式這種現象卻很普遍。與執行在桌面作業系統上的軟體不同的是,通常整個嵌入式裝置就只有一個應用軟體在執行,因此對軟體啟停不少會採用開關裝置電源或按下重啟按鈕這種“粗暴的”方式來完成,久而久之大家將為模組設計終止化函式當做了多餘。
首先,從完整性的角度來看,一個模組如果提供初始化函式,那麼也應當設計終止化函式。其次,為每一個模組設計終止化函式,意味著提供了一種“優雅地”關閉系統的手段,進一步的內涵是,透過這種方式將為我們創造檢測系統資源洩漏的時機。
讓我們站在堆管理模組的角度來檢查優雅終止模組所帶來的好處。這裡假設堆管理模組具備記錄每一次記憶體分配所發生的位置資訊這一功能。以它為例,是因為記憶體洩漏是嵌入式軟體開發中比較讓人頭痛的問題。
如果一個系統中所有模組的終止行為都不經過各自的終止化函式的話,堆管理模組就無法透過它所記錄的分配位置資訊來了解是否存在記憶體洩漏問題。如果只為堆模組提供終止化函式也同樣無法發現記憶體洩漏問題,因為其他模組可能在初始化時分配記憶體,且這些記憶體在整個軟體生命週期中都需要使用。在系統終止時,如果這些記憶體不被各模組自行釋放的話,堆管理模組無法在它的終止化函式中判斷哪些記憶體發生了洩漏。
如果為每一個模組都設計終止化函式,就能做到更容易檢測記憶體洩漏。如果那些在初始化函式中分配記憶體的模組在終止化時進行記憶體釋放操作,且假設所有使用了動態記憶體的模組的終止化函式是在堆管理模組的終止化函式之前被呼叫的,那麼當堆管理模組的終止化函式被呼叫時,就可以根據所記錄的資訊找到沒有釋放的記憶體(洩漏點)。
依此類推,其他的資源也可以採用這一方法發現洩漏。比如,在定時器管理模組的終止化函式中可以檢查是否所有的定時器都已回收了。
在模組的終止化函式中檢查所管理資源是否存在洩漏需要解決一個問題,即各個模組的依賴關係,以保證各資源管理模組的終止化函式是在使用它(所管理資源)的模組之後被呼叫的。第14章所引入的模組分層與分級的概念將有助於解決模組間的依賴關係。
本書作業系統篇中的多個章節採用了這一設計原則以防範資源洩漏,讀者在閱讀這些章節時將加深對這一設計原則的理解。
統計資訊在好多方面將發揮作用。首先,它可被用於軟體調優。軟體在開發過程中不可能完全瞭解現場執行環境,這導致系統可能無法執行在最佳狀態(效能、可使用性等)。要讓系統處於最佳執行狀態,必須獲得軟體在現場的執行環境,這需要透過一定的資料,設計必要的統計資訊將有助於獲得所需的現場資料。
其次,統計資訊還有助於查錯。打個比方,如果一個軟體系統是從裝置外部接收訊息並對之進行處理和響應的,如果設計有進入系統訊息數量的統計資訊,那麼當某些情形下出現負荷異常高時,透過它就可以判斷負荷異常是由外部引起的還是由內部造成的。大多難以定位錯誤根源的軟體缺陷,正是因為“蛛絲馬跡”太少了,而統計資訊有助於捕獲它們。
統計資訊通常採用為每一個統計項定義一個整型變數的形式,圖13.7是一個定時器模組所設計的相關統計資訊。從第29和48行可以看出,statistic_t型別就是整型的typedef。第70、71和78~80行則定義了相應的統計變數。當相應的情形出現時,則對對應的統計變數進行加一操作。統計資訊的顯示就是將統計變數的值透過一定形式輸出。由此看來,增加統計資訊的成本不論從記憶體空間還是處理器時間上其開銷都是很經濟的。
embedded/code/platform/common/inc/primitive.h
00029: typedef unsigned int u32_t;
00047: // for statistic
00048: typedef u32_t statistic_t;
00049:
embedded/code/platform/timer/v3/src/timer.c
00068: typedef struct {
00069: dll_t dll_;
00070: statistic_t hit_;
00071: statistic_t redo_;
00072: csize_t reentrance_;
00073: csize_t level_;
00074: } bucket_t;
00075:
00076: typedef struct {
00077: statistic_t notimer_;
00078: statistic_t traversed_;
00079: statistic_t abnormal_;
00080: } timer_statistic_t;
00081:
00082: static timer_statistic_t g_statistic;
運用這一設計原則,需要我們在設計過程中時刻思考哪些資訊對軟體查錯和調優有幫助。統計項的設計並不要求一步到位,可以在軟體生命週期的任何階段根據需要而增加。
程式程式碼是設計的物質外殼,再好的思想必須最終透過程式碼去表達,而這就離不開對函式、變數和引數進行恰當的命名,以便準確地傳達設計意圖。讓我們透過一個例子來說明命名對設計的重要性。
假設需要設計一個雙向連結串列的操作函式,其功能是刪除連結串列頭結點並將之當做函式的返回值返回,那如何給這一函式取名呢?
dll_get_head()[1]這個命名可能會在讀者的腦海中浮現,但這個命名並不好,因為“get”所表達的是“取”而沒有“刪除”的意思。當他人讀到對dll_get_head()函式的呼叫時,將理解為“只是引用頭結點但並不將它從連結串列中刪除”,這顯然沒有精確地傳達設計本意。一個沒有傳達設計本意的命名是註定要讓人困惑的。
那dll_extract_head()呢?同樣不好,因為“extract”也不能表達從連結串列中刪除結點的意思。用過WinZip英文版的讀者或許注意到了,其中解壓檔案就用了“extract”這個詞。
比較合適的命名是dll_pop_head()。“pop”這一動詞源於退棧操作,當從棧中“彈出”一個元素時,意味著這一元素將從棧中被刪除並返回。將“pop”引入雙向連結串列的函式名中能準確地傳達設計意圖。
除了函式名的命名很重要外,函式引數和變數的命名同樣重要,因為它們能起到“點睛”的作用。對於圖13.8中的兩個函式,從引數名就能完全明白如何用,任何解釋用法的註釋都顯得多餘。
embedded/code/platform/common/inc/dll.h
void dll_insert_before (dll_t *_p_dll, dll_node_t *_p_ref,
dll_node_t *_p_inserted);
void dll_insert_after (dll_t *_p_dll, dll_node_t *_p_ref,
dll_node_t *_p_inserted);
軟體設計的最終產物不能是一堆難讀的程式碼;相反,程式碼應當努力做到讓人讀起來“行雲流水”。好的設計在看完它的介面函式和資料結構後就知道如何使用它,因為它們的命名向人傳達了模組的行為。從這一點說來,花時間斟酌命名是值得的,因為它節省了他人用於理解的時間。
在不少資料中強調註釋對於編碼的重要性,甚至提出程式應有三分之一的篇幅是註釋。對於“三分之一”這一提法,作者並不贊同。原因是:
n 我們在讀程式時的第一反應是讀程式碼而不是註釋。如果程式碼能清楚地表達意思,那就沒有寫註釋的必要,即使寫了那也一定是多餘。如果註釋佔了整個專案源程式的三分之一,作者懷疑其中很多都是廢話,只是為了做到“佔三分之一”而已。
n 註釋與程式碼很容易在維護的過程中失步,因此會出現註釋所發出的聲音與原始碼實現完全不同這種尷尬。一旦註釋被發現不精確它就會被人遺忘,也就起不到註釋應有的效果。
與“三分之一”提法不同的是,作者認為註釋應當儘可能少,並將寫註釋所省下來的時間用於推敲命名。請注意,千萬不要誤認為“少註釋是好程式的充分條件”,而應當理解為“少註釋是好程式的必要條件”。
誠然,這並不是在否定註釋。大部分情形下,透過命名就能清楚地表達程式實現的區域性思想,而註釋應當放眼於全域性去寫以起到提綱挈領的作用,或者某些行為打破了常規(比如,switch語句塊內的case與break不成對)也可以考慮透過註釋加以解釋。如果命名實在無法做到“傳神”或打破了常識的話,也可以考慮採用少量的註釋進行彌補。
人天生就是審美家,軟體工程師在進行軟體設計時這種天性會自然地發揮作用。將設計感覺當做是一個設計原則,多少讓人覺得有點不靠譜,但這一原則也正體現了軟體設計中的藝術成分。
就作者的經驗來看,如果做設計時覺得彆扭,那工作效率一定不高;反之,則工作效率奇高。軟體設計真正花時間的是思考,而不是編碼。思考的目的是從紛繁的現象中試圖找到問題的本質,或者從眾多的因素中找出關鍵。設計時之所以出現“審美告警”,一定是有什麼沒有考慮清楚。這種情形下停下來做進一步的思考,將有助於理清思路,以最終獲得更好的設計。
相信每個軟體工程師或多或少都能感覺到“審美告警”,而訊號的強弱與軟體工程師的設計水平可能是正相關的。軟體工程師如果重視這種訊號,則這種訊號的靈敏度也會慢慢提高。因為重視它意味著將進行更多的思考,而思考多了就更容易形成自己的設計原則和思想;相反,如果長期忽視它的存在,則最終可能會造成這種訊號的消失。忽視“審美告警”的存在,或許意味著我們並不關心所設計的主題,其質量也別指望好到哪兒去,更有甚者會醞釀出將來的一個“毒瘤”。
本文選自《專業嵌入式軟體開發——全面走向高質高效程式設計(含DVD光碟1張)》一書
圖書詳細資訊:http://space.itpub.net/13164110/viewspace-713651
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/13164110/viewspace-713878/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 設計原則之【開放封閉原則】
- Nielsen:電子商務不存在“放之四海而皆準”的策略
- 面象物件設計6大原則之二:開放封閉原則物件
- 物件導向設計原則之合成複用原則物件
- 適用於偉大互動設計的 UI 原則UI
- 設計原則之【介面隔離原則】
- 設計原則之【依賴反轉原則】
- 設計原則之【單一職責原則】
- 設計原則之【裡式替換原則】
- Javascript 設計模式之設計原則JavaScript設計模式
- 軟體設計原則—合成複用原則
- 物件導向設計原則之開閉原則物件
- 設計原則之【迪米特法則】
- 開放封閉原則與規則引擎設計模式 - devgenius設計模式dev
- 物件導向設計原則之介面隔離原則物件
- 物件導向設計原則之里氏代換原則物件
- Mysql研磨之設計索引原則MySql索引
- 物件導向設計原則,以及包的設計原則物件
- 設計模式原則之迪米特法則設計模式
- 小話設計模式原則之(4):開閉原則OCP設計模式
- 物件導向設計原則之單一職責原則物件
- 《戰神》關卡設計師:所有團隊適用,3A大作的六個設計原則
- 設計模式的設計原則設計模式
- 《JavaScript設計模式與開發實踐》原則篇(3)—— 開放-封閉原則JavaScript設計模式
- 設計原則
- 架構整潔之道二(設計原則)架構
- 設計模式六大原則(二)----裡式替換原則設計模式
- 設計模式之7大原則設計模式
- Google的設計原則Go
- 小話設計模式原則之(3):介面隔離原則ISP設計模式
- 設計模式之開閉原則:對修改關閉,對擴充套件開放設計模式套件
- 設計原則:開閉原則(OCP)
- 設計測試用例的四條原則
- 設計原則 設計模式設計模式
- 設計模式 - 設計原則設計模式
- 【設計模式】設計原則設計模式
- 我學設計模式 之 物件導向設計原則設計模式物件
- 提升使用者體驗舒適度的核心設計原則