要達到軟體的擴充性、預留面對需求變動的彈性,「抽象」非常重要。
比如說,我想顯示一行文字到螢幕上,可以怎麼寫?
1.
printf("This is a string\n");
2.
Display("This is a string\n");
void Display(char *c){
printf("%s", char);
}
是不是第二個比較好呢?為什麼?
因為有一天這行字可能不顯示到螢幕上了,
而是改用USB介面輸出。
那麼豈不是要搜尋所有printf呢?
使用第二種方法,
只要改寫Display的內容就好了。
如果是C++的話就更棒了,
利用多型可以更進一步的抽象化。
例如:
void Display(int value){
LEDBlink(value);
}
void Display(char* c){
printf("%s\n", c);
}
void Display(void){
// Do nothing (dummy)
}
所謂抽象,就是用更高的角度去思考程式架構。
好的架構會透過抽象函數,把介面都留出來,與底層的實作全部分開。
如此一來,未來實作方式改變的時候,上層的code可以完全不變。
比如說
Speak會比SpeakEnglish來得抽象,而Communicate會比Speak來得抽象。
void Communicate (...){
// Maybe speak something
// Maybe hand singnals
// Maybe burn some woods and make smoke
}
void Speak (...){
// Maybe speak english, japanese....
}
void SpeakEnglish(...){
// Only english....
}
運用抽象函數是寫好程式的重要一步!
星期一, 1月 26, 2015
軟體建構之道:讀書心得(第三章-前期準備)
最近開始看了軟體工程的重量級書藉「軟體建構之道」,
來記錄下一些要點並助於知識吸收。
- 確認需求
- 在需求時期未解決的問題,到了建構時期,要花上很多倍的時間解決。
- 演進原型法:在建造系統前,先盡可能探索需求。
- 演進交付法:一次完成一點,從用戶獲得回饋,調整一點,再繼續建下一塊。
- 確定每個人都知道需求改變的代價。
- 建立一套需求改變的程序,例如審刻機制、變更時間點。
- 決定架構
- 在設計架構時期未解決的問題,到了建構時期,也必定要花上很多倍時間解決。
- 能對整個系統的類別做一個簡單綜述。
- 清楚每一個類的功能。
- 每一個需求都至少有一個類包覆。
- 定義核心類別、其它類別、類別之間的存取權限。
- 預測需求改變,預留變動空間,並將風險降至最小。也可運用延遲交付方法,將變動性的部份容後決定,例如把數據資料放在獨自的資源檔案中。
- 管理稀有資源。
- 性能評估。
- 可伸縮性。
- 安全性。
- 用戶界面模組化,利於替換。
- 錯誤處理:包含錯誤如何傳遞(立即丟棄/進入錯誤處理狀態/處理完才報錯)、錯誤如何記錄、錯誤何時處理(立即處理/順著function call上傳錯誤碼)由誰來處理錯誤(各類別各自處理/特定類別處理)、哪些層級以上資料必為正確、遇到錯誤如何處理(回到錯誤之前、使用替代函數、功能退化並自動重啟等)。錯誤處理一定要先規劃好,介面一致。
- 可行性:哪些是已被驗證可行的,哪些是有風險的。
- Overenginnering: 系統中哪些類別需要擁有較高健壯性?是可以做的比需求更好的?哪些類別不需要如此下功夫?系統中的類別是否都有考慮到健壯性?或者有的過頭了,有的做的不夠?
- 架構目標要清楚表述,並描述所有決策的機動。謹防「我們向來這樣做」。每個決定必有原因。並描述為什麼不使用別的方法?
- 架構擁有語言獨立性。
- 架構描述各個類別時,不要過頭,也不要缺少描述。
- 視圖:以不用視角來觀看架構,檢查錯誤。就像是室內設計的不同角度視圖一般。
- 後記:
Data-driven: 利用if/switch來判別遇到什麼資料做什麼事。
Table-driven:把所有資料放到array中,再利用迴圈進去分析資料。有助於將資料與程式碼隔離。
星期四, 1月 22, 2015
Debug的方法
Debug的方法很多,
先講流程:
發現問題->重現問題->找到原因->解決問題
以下假設問題已能被重現,
例出常用的幾種Debug手法:
1. 分段法
把程式分成數個區塊,鎖定在有問題的區塊debug。
2. Trace
從發生問題的code的call stack慢慢trace回去,
就不多做論述。
3. 地毯式搜索
這是最爛的方法,
但只要肯花時間就能獲得一定成效。
又可以再分為漸減法跟漸增法,
看是要從最原始的code慢慢加東西上去,
還是要從現有的code慢慢減少東西。
其實跟分段法算是相關的方法。
4. 模型建立法
最困難的辦法,
但系統愈複雜,問題愈離奇,這個辦法反而有效率。
把出問題的區塊想成是黑盒子,
有目的性的丟一些輸入資料進去,
一定會出來一些不符預期的資料,
此時去建立模型來解說為什麼會發生這個狀況。
又像是偵探辦案,
去搜集一些線索,建立論點,
進一步還原出bug產生的原因。
舉個簡單的例子,
預期答案是0x0C,卻得到0x18
預期答案是0x0F,卻得到0x1E
想一想~想一想~
不用看code就知道問題怎麼解決了。
5. 休息一下,做點別的事。
從撞牆的思路逃脫出來,一定有什麼你沒想到,
一定有什麼是你信以為真的事錯了。
先講流程:
發現問題->重現問題->找到原因->解決問題
以下假設問題已能被重現,
例出常用的幾種Debug手法:
1. 分段法
把程式分成數個區塊,鎖定在有問題的區塊debug。
2. Trace
從發生問題的code的call stack慢慢trace回去,
就不多做論述。
3. 地毯式搜索
這是最爛的方法,
但只要肯花時間就能獲得一定成效。
又可以再分為漸減法跟漸增法,
看是要從最原始的code慢慢加東西上去,
還是要從現有的code慢慢減少東西。
其實跟分段法算是相關的方法。
4. 模型建立法
最困難的辦法,
但系統愈複雜,問題愈離奇,這個辦法反而有效率。
把出問題的區塊想成是黑盒子,
有目的性的丟一些輸入資料進去,
一定會出來一些不符預期的資料,
此時去建立模型來解說為什麼會發生這個狀況。
又像是偵探辦案,
去搜集一些線索,建立論點,
進一步還原出bug產生的原因。
舉個簡單的例子,
預期答案是0x0C,卻得到0x18
預期答案是0x0F,卻得到0x1E
想一想~想一想~
不用看code就知道問題怎麼解決了。
5. 休息一下,做點別的事。
從撞牆的思路逃脫出來,一定有什麼你沒想到,
一定有什麼是你信以為真的事錯了。
用C語言實作物件導向(一)
寫Embedded的人最常碰到的就是C語言了,
在這邊整理一下幾個最初級也最基本的概念。
1. 以file為一物件,變數、函數分成私用(static)與公用。
儘量避免使用全域變數。
在令變數時就應決定作用範圍,愈小愈好。
外部物件要對私有變數賦值,寧可多寫一個function。
只對外露出function而不露出變數這很重要。
2. 同類的變數使用struct集合在一起。
如此一來要re-use也很方便。
3. 以物件為出發點去思考函數的寫法,而不是以方法為出發點。
例如在中斷處理的檔案中(ex. IRQ.c),
直接在裡面做事情並呼叫許多各物件的方法是不妥的。
不好的寫法:
// IRQ.c
void A_IRQ(void){
obj1xxx();
obj1yyy();
obj1zzz();
}
好的寫法:
// IRQ.c
void A_IRQ(void){
obj1IRQ();
}
// obj1.c
void obj1IRQ(void){
obj1xxx();
obj1yyy();
obj1zzz();
}
Initial的時候也是
不好的寫法
// RCC.c
void RCC_Conf (void){
obj1RCC_Conf();
obj2RCC_Conf();
}
// NVIC.c
void NVIC_Conf (void){
obj1NVIC_Conf();
obj2NVIC_Conf();
}
好的寫法
// Obj1.c
void Obj1_Init (void){
obj1RCC_Conf();
obj1NVIC_Conf();
}
// Obj2.c
void Obj2_Init (void){
obj2RCC_Conf();
obj2NVIC_Conf();
}
在這邊整理一下幾個最初級也最基本的概念。
1. 以file為一物件,變數、函數分成私用(static)與公用。
儘量避免使用全域變數。
在令變數時就應決定作用範圍,愈小愈好。
外部物件要對私有變數賦值,寧可多寫一個function。
只對外露出function而不露出變數這很重要。
2. 同類的變數使用struct集合在一起。
如此一來要re-use也很方便。
3. 以物件為出發點去思考函數的寫法,而不是以方法為出發點。
例如在中斷處理的檔案中(ex. IRQ.c),
直接在裡面做事情並呼叫許多各物件的方法是不妥的。
不好的寫法:
// IRQ.c
void A_IRQ(void){
obj1xxx();
obj1yyy();
obj1zzz();
}
好的寫法:
// IRQ.c
void A_IRQ(void){
obj1IRQ();
}
// obj1.c
void obj1IRQ(void){
obj1xxx();
obj1yyy();
obj1zzz();
}
Initial的時候也是
不好的寫法
// RCC.c
void RCC_Conf (void){
obj1RCC_Conf();
obj2RCC_Conf();
}
// NVIC.c
void NVIC_Conf (void){
obj1NVIC_Conf();
obj2NVIC_Conf();
}
好的寫法
// Obj1.c
void Obj1_Init (void){
obj1RCC_Conf();
obj1NVIC_Conf();
}
// Obj2.c
void Obj2_Init (void){
obj2RCC_Conf();
obj2NVIC_Conf();
}
訂閱:
文章 (Atom)