星期日, 2月 01, 2015

軟體建構之道:讀書心得(第四、五章)

第四章:關鍵的構建決策
  1. 在撰寫程式碼之前,決定編寫的約定,例如變數名稱、註解、程式碼的格式等。
  2. 決定如何由軟體架構去處理諸如錯誤處理、安全事項、介面類別。
  3. 程式碼check-in之前,應規劃一套順序,例如step-by-step執行程式碼確認運作正常、進行單元測試及集成測試、review程式碼等。
  4. 思考時,跳脫程式語言。實際撰寫時,清楚知道語言的優缺點,並在語言不支援的部份,提出辦法補足。
第五章:軟體構建中的設計 (5.1-5.2)
  1. 設計時會有很多錯誤的步驟,這正是關鍵所在,必須在設計階段就加以改正,而不是程式碼撰寫階段。
  2. 設計什麼時候算是「足夠好了」?一般來說是「沒有時間再進行」的時候停止。
  3. 控制複雜度,人類一次能夠處理的問題與複雜度有限,讓問題折解成許多單純的小問題,並降底彼此間的關聯性。把必要性的複雜度控制到最小,並且避免附加性的複雜度無謂的快速增長。
  4. Low fan-out: 一個類別裡使用到其它類別的程度,叫做fan out。使用超過7個類別以上,稱為high fan-out。不論是調用sub function的量,或者是使用其它類別的量,應該保持low fan-out。
  5. 考慮精簡性,不留下無用的程式碼。「加了這段程式碼會有什麼害處呢?」
  6. 儘量避免使用特殊技術。
  7. 設計的步驟:軟體->子系統->類別->資料結構與子函數->子函數的內部設計。
  8. 子系統:限制子系統間的互相通訊,降低複雜度。

星期一, 1月 26, 2015

抽象的重要性

要達到軟體的擴充性、預留面對需求變動的彈性,「抽象」非常重要。

比如說,我想顯示一行文字到螢幕上,可以怎麼寫?

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. 確認需求
    1. 在需求時期未解決的問題,到了建構時期,要花上很多倍的時間解決。
    2. 演進原型法:在建造系統前,先盡可能探索需求。
    3. 演進交付法:一次完成一點,從用戶獲得回饋,調整一點,再繼續建下一塊。
    4. 確定每個人都知道需求改變的代價。
    5. 建立一套需求改變的程序,例如審刻機制、變更時間點。
  2. 決定架構
    1. 在設計架構時期未解決的問題,到了建構時期,也必定要花上很多倍時間解決。
    2. 能對整個系統的類別做一個簡單綜述。
    3. 清楚每一個類的功能。
    4. 每一個需求都至少有一個類包覆。
    5. 定義核心類別、其它類別、類別之間的存取權限。
    6. 預測需求改變,預留變動空間,並將風險降至最小。也可運用延遲交付方法,將變動性的部份容後決定,例如把數據資料放在獨自的資源檔案中。
    7. 管理稀有資源。
    8. 性能評估。
    9. 可伸縮性。
    10. 安全性。
    11. 用戶界面模組化,利於替換。
    12. 錯誤處理:包含錯誤如何傳遞(立即丟棄/進入錯誤處理狀態/處理完才報錯)、錯誤如何記錄、錯誤何時處理(立即處理/順著function call上傳錯誤碼)由誰來處理錯誤(各類別各自處理/特定類別處理)、哪些層級以上資料必為正確、遇到錯誤如何處理(回到錯誤之前、使用替代函數、功能退化並自動重啟等)。錯誤處理一定要先規劃好,介面一致。
    13. 可行性:哪些是已被驗證可行的,哪些是有風險的。
    14. Overenginnering: 系統中哪些類別需要擁有較高健壯性?是可以做的比需求更好的?哪些類別不需要如此下功夫?系統中的類別是否都有考慮到健壯性?或者有的過頭了,有的做的不夠?
    15. 架構目標要清楚表述,並描述所有決策的機動。謹防「我們向來這樣做」。每個決定必有原因。並描述為什麼不使用別的方法?
    16. 架構擁有語言獨立性。
    17. 架構描述各個類別時,不要過頭,也不要缺少描述。
    18. 視圖:以不用視角來觀看架構,檢查錯誤。就像是室內設計的不同角度視圖一般。
  3. 後記:
    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. 休息一下,做點別的事。
從撞牆的思路逃脫出來,一定有什麼你沒想到,
一定有什麼是你信以為真的事錯了。



用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();
 }