NASA 的 10 大程式設計規則

西西里的仔仔發表於2016-08-02

大型複雜的軟體專案會使用某種程式設計標準或準則。這些準則組成了軟體編寫的基本規則。

  • 程式碼應該如何組織?
  • 應該以及不應該使用哪些語言特徵?

為了確保有效,這些規則要簡短並且足夠具體,以便理解和記憶。

那些為 NASA 工作的世界頂級程式設計師,在開發安全關鍵程式碼的時候遵守著一套準則。實際上包括 NASA 的噴氣推進實驗室( NASA’s Jet Propulsion Laboratory ,JPL)在內的很多組織,主要使用 C 語言進行程式碼編寫。

原因是,C 語言有大量的支援性工具,包括邏輯模型提取器、偵錯程式、穩定的編譯環境,以及在程式碼分析器和度量工具上擁有強大的資源。

有時候非常有必要遵循程式設計規則,特別是當程式碼的正確性與你的生活息息相關的時候 — 用來控制你乘坐的飛機的程式碼,用來控制將宇航員送入軌道的太空船的程式碼,或者用來控制你家幾英里遠的核電站的程式碼。這裡介紹的 NASA 的 10 大程式設計規則,主要偏重於安全引數,這 10 大規則是由 NASA 噴氣推進實驗室(JPL)的首席科學家 Gerard J. Holzmann 提出的。這些準則也可以用於其他程式語言。

NASA 的 10 大程式設計規則

第 1 條規則 — 簡單的控制流

用非常簡單的控制流結構體來編寫程式 — 不要用 setjmp 或者 longjmp 結構體,goto 語句,以及直接或間接的遞迴呼叫。

理由:簡單的控制流能夠提高程式碼的清晰度以及擁有更強大的驗證能力。不使用遞迴,就不會產生迴圈函式呼叫關係圖,同時這也證明應該是有界的執行過程確實是有界的。

第 2 條規則 — 迴圈設定固定的上限

所有的迴圈必須有一個固定的上限。對於檢查工具來說,在給定迴圈次數的情況下,可以通過靜態分析證實迴圈結果不超過預設的上限。如果工具不能靜態檢測出迴圈上限,那麼這條規則就不適用。

理由:設定迴圈邊界、不使用遞迴能夠阻止程式碼失控。但是這個原則並不適用於迭代,迭代意味著無窮無盡(比如程式排程)。 這種情況下就該使用相反的規則 — 必須可以靜態地檢測到迭代不會終止。

第 3 條規則 — 沒有動態記憶體分配

初始化之後不要使用動態記憶體分配。

理由:記憶體分配運算子比如 malloc 以及 garbage collectors 通常伴有不可預測的行為,這些行為會嚴重影響效能。甚至還可能因為程式設計師的錯誤而發生記憶體錯誤,包括:

  • 試圖分配比實體記憶體更多的記憶體空間
  • 忘記釋放記憶體
  • 繼續使用已經釋放了的記憶體
  • 超出記憶體分配的邊界

將所有的模組強制存放在固定的、預先分配的記憶體空間中,可以消除這類問題,同時驗證記憶體的使用情況也會更加容易。

在堆中記憶體分配不足的情況下進行動態申請記憶體的一種方法是使用棧記憶體。

第 4 條規則 — 沒有大函式

如果以一行一條宣告和一行一條語句這樣標準的格式來寫,那麼函式的長度不應該超過一張紙。這也就是說一個函式不應該超過 60 行程式碼。

理由:冗長的函式通常等於糟糕的程式碼結構。每個函式應該是一個易懂可證實的邏輯單元。而理解一個多螢幕長的邏輯單元是很困難的。

第 5 條原則 — 低斷言使用密度

程式碼斷言的密度應該低至平均每個函式兩個。斷言是用來檢查現實執行中不會發生的不正常情況。它應該被定義為布林測試。當斷言失敗,應當立即採取恢復措施。

如果靜態檢測工具證明斷言永遠不會失敗或者條件永遠不為真,這條規則就無效。

理由:工業編碼工作統計顯示,單元測試中每 10 到 100 行程式碼至少發現一個程式碼瑕疵。隨著斷言的密度增長,有瑕疵的程式碼被攔截的機率越大。作為強大的防禦型程式碼策略,斷言的使用也是非常重要的。斷言可以用來驗證函式的前後條件、引數以及函式和迴圈不變式的返回值。在測試完效率關鍵程式碼後,斷言可以選擇性地禁用。

第 6 條規則 — 最小範圍內宣告資料物件

這條規則支援資料隱藏的基本原則。所有的資料物件必須在儘可能最小範圍內宣告。

理由:如果一個物件不在該範圍內,其值也不能被引用或者銷燬。這條規則阻止了變數的重複和衝突性的使用,這些行為會使錯誤診斷更加複雜。

第 7 條規則 — 檢查引數和返回值

當函式的返回值為非空的時候,每次函式呼叫都應該檢查其返回值,並且每個被呼叫的函式還要檢查所帶引數的有效性。

在最嚴格的模式下,這條規則意味著printf和檔案關閉語句的返回值也要檢查

理由:如果一個錯誤的返回值和一個正確的返回值沒有什麼區別的話,這個時候就有必要精確檢查返回值。在函式中有呼叫 close 和 prinf 語句的情況下,函式返回值是 void 能夠被接受,表明程式設計師故意(並且不是偶然)忽略返回值。

第 8 條規則 — 限制使用前處理器

前處理器的使用應該限制在標頭檔案和巨集定義中。不允許使用遞迴巨集呼叫,拼接符和可變引數列表。即使在大型程式的開發工作中,如果使用了超過一兩個條件編譯指令必須要有充足的理由,這麼做超出了統一的程式碼標準,同樣也是為了避免同樣的標頭檔案包含多重釋義。每次這麼做必須在程式碼中要有由基於工具的檢查器進行標記並且要有充分的理由。

理由:C 語言的前處理器是一個非常強大並且難懂的工具,它能夠破壞程式碼的清晰性並迷惑基於文字的檢查器。即使手上有正式語言定義,在無盡的前處理器程式碼中,程式碼的結構也是很難理解的。

條件編譯也同樣需要謹慎,10 個條件編譯指令程式碼中就會有 1024(2^10)個不同版本的程式碼,這也增加了測試的工作量。

第 9 條規則 — 限制使用指標

必須要限制指標的使用。最多隻允許使用一級指標解引用。指標解引用操作不可以隱藏在型別宣告或巨集定義中。還有,不允許使用函式指標。

理由:即使是專家,也很容易誤用指標。指標使得它們(尤其是基於工具的靜態分析器)很難跟蹤或分析程式中的資料流。函式指標還限制了靜態分析器的檢查型別,只有在理由非常充分的情況才能使用函式指標。如果使用函式指標,幾乎不可能使用工具來證明缺少的遞迴,所以必須有足以彌補這部分缺失的分析能力的替代方法。

第 10 條規則 — 所有程式碼必須能編譯通過

從開發的第一天起,所有的程式碼都必須通過編譯。所有的編譯器警告必須遵循編譯器可使用警告。在編譯器可使用警告範圍內,編譯的程式碼必須沒有警告。

所有程式碼必須每天至少使用一個(最好多於一個)最新的靜態原始碼分析器進行檢查,而且以0警告通過所有的分析。

理由:市場上有很多相當有效的原始碼分析軟體,其中一些還是免費的。軟體開發專案沒有任何理由不去使用這個現成的技術

如果編譯器或者分析器被搞混淆了(報出錯誤的警告),那麼應該重寫使其混淆的這部分程式碼。

NASA 是這麼評價這些規則的:

“它們就像車裡的安全帶:剛開始用會有點不舒服,但是過了一段時間就會成為一種習慣,你會無法想象不使用它們的日子。”

相關文章