The two most important days in your life are the day you were born and the day you find out why. -Mark Twain

#20 The Power of Ten - 撰寫可靠軟體的思維

標題為 The Power of Ten - Rules for Developing Safety Critical Code 的文章刊登在 IEEE Computer 2006 年 6 月的月刊中,作者是位在 NASA JPL 實驗室的研究科學家,同時也是位學者和工程師.這篇文章可以在 http://spinroot.com/gerard/pdf/P10.pdf 看到.

若以武功修練來比喻的話,這篇文章就像是一個得道高人所寫下來的內功心法,他把自己在 JPL 實驗室工作的多年 C 語言工作心得濃縮成十項重點,作者也為每一個重點留下說明.雖然這一篇文章是將近十年前的文章了,但它的參考價值極高,而且我相信許多資深的軟體工程師幾乎都會同意作者所寫的內容.

其中有幾點並非在所有的程式語言中都會碰的到,尤其是跟記憶體管理有關的工作.以現在一般商業應用裡最常見的 C# 和 Java 語言都應該有其 runtime 環境的設計,所以記憶體管理的工作並不在程式設計者上,而是在開發其語言的公司上,所以這樣等於你把記憶體管理的工作交給其他人來處理了.我想這對 NASA 來說應該是不太能接受的事情,畢竟能打上太空的物體都是極為龐大的資金,因此 NASA 應該是不可能用類似像 C#, JAVA 這般的語言來打造火箭或衛星要用的軟體.回歸最基本的本質,用 C 應該是相當原始且基礎了.所以,在那篇文章裡,作者談了一些與 C 語言有關的準則.在這裡我不談特定語言,我們來看看其他的通用的準則.

1. Restrict all code to very simple control flow constructs – do not use goto statements, setjmp or longjmp constructs, and direct or indirect recursion.

這一點應用也沒什麼好再做說明的了,就是這樣,沒有 GOTO,沒有 recursion.所以在前面的文章裡,有些程式的寫法我提供了 recursive 和 iterative 的寫法,那時也提過 iterative 的寫法是比較好的.

2. All loops must have a fixed upper-bound. It must be trivially possible for a checking tool to prove statically that a preset upper-bound on the number of iterations of a loop cannot be exceeded. If the loop-bound cannot be proven statically, the rule is considered violated.

你想想看你寫程式時做到了這一點嗎? 你有沒有做過 while (true) 之類的語法呢 ?

4. No function should be longer than what can be printed on a single sheet of paper in a standard reference format with one line per statement and one line per declaration. Typically, this means no more than about 60 lines of code per function.

這是一定要的.若以 OO 的角度來看,有一個 function 能寫的很長,我都會懷疑程式碼一定在某些地方會有重覆功能的現象.

5. The assertion density of the code should average to a minimum of two assertions per function.

每個 function 平均來說至少有兩個檢查.這邊的檢查就是要防止一些不尋常的參數發生.比如你今天要寫一個加法,接收兩個 int ,如果傳入的參數是負數,那你的程式能處理嗎? 或是加起來的數字 overflow 了,你的程式能處理嗎?

6.  Data objects must be declared at the smallest possible level of scope

這應該是很直覺的想法,因為你不需要把不相干的資料物件提供給不需要該資料物件的程式碼.

7.  The return value of non-void functions must be checked by each calling function, and the validity of parameters must be checked inside each function.

這應該是每一個程式工程師要做的,只要呼叫的參數皆符合要求,那麼回傳值也需要檢查,同時還要注意是否有可能會產生其他的 exception ,都要記得做好 try catch 的反應動作.

10. All code must be compiled, from the first day of development, with all compiler warnings enabled at the compiler’s most pedantic setting.

最後這一點就是在考驗你們的專案裡有沒有良好的軟體工程流程以及 build system.工程師們一旦把程式碼 check-in 之後,所有的程式就應該要重新編譯,然後所有的測試案例都要執行.這樣才能即早發現問題,即早解決.


Share:

#19 資料庫的資料實體儲存單位 Page

在上一篇文章中提到了有關資料庫 Storage manager 一點點的資訊.因此,這一篇文章就來談談更基礎的東西,叫 Page.它是什麼呢 ? 它就是 Storage manager 在處理儲存資料上的一種邏輯格式,也就是每個邏輯儲存空間的大小.這樣說可能還不好了解,讓我來直接舉個例子.資料庫引擎中有一個管理員叫 storage manager,它是負責資料儲存和讀取用,也就是說,今天使用者發出一個請求如 select * from table123,那麼這個命令就會經由資料庫引擎裡各式各樣的管理員來做語法確認解析,來做語法最佳化,然後可能還會經由其他管理員的檢查,最後才會到 storage manager,而 storage manager 負責的工作就是到磁碟機上正確的位置點,把資料讀取出來然後再回傳給呼叫它的管理員,最後資料就一直這樣被回傳到使用者端.因此,storage manager 就是負責找到資料在硬碟上的那個地方,然後進行資料讀寫.所以,你現在可以知道 storage manager 的工作是非常底層的.

那 Page 和 storage manager 又是什麼關係呢 ? Page 是資料儲存空間的一種單位.依不同的產品設計,Page 的大小可能不同,如 Microsoft SQL Server 的 Page 設計成 8KB. 所以,如果你的資料庫檔案設定為 8000KB,那就表示你這個資料庫檔案就有 1000 個 Page.然後這一千個 page 並不是全部都可以做為使用者資料儲存的用途,因為需要有一些空間用來儲存資料庫的設定與變數等,但我們在此先不討論這些系統所需的空間,我們把 1000 個 page 視為都是可供使用者儲存資料用.而 Page 設計的概念就是我們以前說過的 Linked List 概念,所以 Page 裡會有一個 pointer 指向下一個 Page 在那裡,因此,相鄰的 Page 在實體位址上不一定要在隔壁,他們可以分別位在硬碟裡前後不同的位置上.

所以,假設你現在要寫入一筆資料到一個新的表格,也假設這個表格只有一個欄位,其 data type 是 char(1000),而且這個欄位是 primary key.當你寫入第一筆資料 "1111" 時,這筆資料就會被記錄在 page 1 的最前面,如果你寫入第二筆資料 "2222" 時,這筆資料也會被記錄在 Page 1 且在 "1111" 的後面,如下圖所示:


在上圖中,假設寬度剛好是 1000 字元,所以你會到 "1111" 在第一行,由於 data type 是 char,所以後面有 996 個空白,而第二行是 "222",後面接著是 996 個空白字元.如果你繼續寫入更多的資料時,第一個 Page 看起來就會如下:


如果你再繼續寫入資料的話,後面的資料就會從第二個 Page 開始寫.所以 storage manager 在讀取這表格的資料時就會從這個表格的第一個 page 開始讀資料.

假設,此時你要寫入 "4567" 到這個表格中,因為這個欄位是 primary key,所以預設上來看,字元的順序就是實體資料要擺放的順序,因為 "4567" 必須放在 "4444" 和 "5555" 之間.但由於這一個 Page 的空間已被佔滿了,因此 storage manager 只好做一個 page split 的動作,也就是把一個 Page 拆成兩個 Page ,然後再寫入資料.


如果用 List 的動作來看,這就是在一個 List 中插入一個元素的動作.用 storage manager  的角度來看,它要先尋找一個完全沒用過的 Page ,然後將需要分開的資料搬到新的 Page 上,最後再更改 page pointer 位置來讓新 Page 的順序能安排在其中.這個動作並不是一個單純的動作,花費的成本還不小,從這裡就可以了解到,當我們做 insert 或 update 資料時,如果沒有安排好的話很可能就會造成大量的 page split 導致硬碟會很忙碌而形成效能上的瓶頸.

當我們討論到資料庫的讀寫效能時,Page 是一個很好的對象讓我們知道一份資料被讀取時一共讀了多少個 Page,這也就是代表了資料庫引擎的效能.由此可見,對資料庫引擎而言,這是多麼重要的基礎考量.


Share: