NASA/JPLのコーディング標準
はじめに
/r/learnprogramming/で伸びていたので訳してみた。
かの有名なNASA/JPLで標準的に使用されているコーディング標準らしい。
非常に高い信頼性が求められるソフトウェアでは、ここまで制約が厳しくなるようだ。
原文はこちら。
The Power of 10
Rule 1
制御フローはできるだけ単純に。
-
goto
命令やsetjmp
/longjmp
構造はご法度 - 再帰も使わない
- 意外かもしれないが、再起を排除することでコードアナライザーを利用しやすくなるため
Rule 2
ループ回数の上限は静的な条件にする。
- 静的コード解析によりループの最大回数が自明に定まらなければならない
- ループの回数が静的に定まらない時は、このルールを違反している
Rule 3
初期化処理の後に動的なメモリ割り当てを使用してはならない。
- 動的メモリ割り当ては、メモリ管理に関して下記のような誤りをもたらしうる
- メモリの開放を忘れる
- 開放されたメモリを参照し続ける
- 物理的に利用可能なサイズを超えたメモリを割り当てようとする
- 割り当てられたメモリの境界を超えてアクセスする
- 静的なメモリ割当のみを使用することで、上記のような問題を排除できる
- 静的コード解析による検証も容易になる
Rule 4
全ての関数は1枚の紙に印刷できる長さに収めるべきである。
- 関数ごとに60行を超えない程度が目安
- 全ての関数は論理的に独立していて、単体として試験可能であるべき
- 関数を1画面にすべて表示することができれば、可読性も増す
- 長過ぎる関数はたいていコードの構造が悪いためである
Rule 5
Assertionについて、1つの関数につき平均最低2つは行うべきである。
Assertionを利用して、現実での実行時に決して発生すべきではない異常な状況が起きていないことを確認する。
Assertionによる副作用はあってはならず、純粋なBooleanテストとして定義されるべきである。
Assertionに失敗した場合は、明確な復旧措置を取らなければならない。
(例えば、関数の呼び出し元にエラー状況を返すなど。)
静的コード解析によって確実に通過することが分かっている箇所にAssertionを使用するのは、違反。
- 1関数最低2回と言っているのは、関数の引数と戻り値だけは必ずチェックしろという意味
- Assertionが多いほど不良動作を阻止できる可能性が高まる
- ここでいうAssertionはassertマクロのことは意味しておらず、if文に依るものを指している
- 従って、Assertionに引っかかる=プログラムの強制停止ではないので注意
- だからプログラム内でのエラー処理も可能ということ
Rule 6
データオブジェクトは可能な最小のスコープで宣言されなければならない。
- データ隠蔽の基本
- デバッグを容易にすることに主眼を置く
Rule 7
voidでない関数の戻り値は、関数を呼び出すたびにチェックしなければならない。
また、全ての関数の内部では、引数が有効であることをチェックしなければならない。
- ただし、例えば
printf
関数やclose
関数の戻り値までいちいち確認するのは手間- この場合は戻り値をvoidにキャストして、ルールをあえて無視したということを明確化する
- 上記例外を除いて、大体のケースでは戻り値は重要
- 特にエラーを示す戻り値は、必ず上位の関数にもそれを伝達すること
- ルールを破る場合は、かならずその理由を明記させる
- こうすることで、ルールに従うほうがルールを破るよりも容易になる
Rule 8
プリプロセッサの利用は、ヘッダーのincludeと簡単なマクロの定義に限定する。
トークン連結演算子(##
)や可変長引数をとるマクロ、再帰呼出マクロの使用は許可されない。
条件付きコンパイルに関しても同様だが、一方これは常に回避できるわけではない。
従って、単にヘッダーの重複includeを避けるため以外にも、大きなソフトウェアで1,2個の条件付きコンパイルを利用している例は存在する。
そのような箇所にはコード内で理由を明記すること。
- プリプロセッサは強力な難読化ツールであり、コードの可読性を破壊する
- 特にテキストベースのコードチェッカーにとっては厄介
- 例えば、10個の異なる条件付きコンパイルが存在するだけで、2^10=1024通りのコンパイル結果が存在することになる
- それら全てのテストは困難
Rule 9
ポインタの使用には制限を課すべきである。
特に、2レベル以上のdereference(*演算子)は禁止する。
ポインタのdereference操作はマクロや typedef
の内部に隠蔽してはいけない。
関数ポインタの使用は許可されない。
- ポインタは経験豊富なプログラマですら誤用してしまいがちなので、リスクを減らしたい
- ポインタを使うと、特に静的コード解析ツールによるデータフローの解析が困難になる
- 同様に、関数ポインタも静的解析ツールの使用を大きく制限してしまう
- 強い理由がない限りは使用しないこと
- 先の再帰禁止の話にもつながる
Rule 10
全てのコードは、開発の初日から、コンパイルされなければならない。
(コンパイラの設定は、最もpedanticかつwarningを全てオンにすること。)
全てのコードは、上記のコンパイル設定で、warningゼロでコンパイルされなければならない。
全てのコードは、毎日1つ以上の最新の静的コード解析ソフトによりチェックされ、warningゼロで解析をパスしなければならない。
- コンパイラやコードアナライザのwarningは軽視してはいけない
- どんなにつまらないものでも、必ずコードを修正してwarningを解消すること
- 昔の静的コードアナライザは出来が悪く、今でも使用を避ける人はいる
- 無意味なメッセージを出力するものが多かった(例えばlintなど)
- 現代ではまともに使えるようになったから、安心して使用すること
豆知識
上記ルールへの違反が多々見られる例に、TOYOTA自動車のスロットル制御プログラムが挙げられている(ソース1, ソース2。)
実際意図しない加速をするようなバグがあったらしい。
このルールを採用していれば、TOYOTAのソースコードも改善され、バグも発生しなかったのかもしれない。
考察
静的コード解析を適用するために存在するルールが多いようだ。
これの背景として、NASA/JPLが取り組んでいる案件が数千億円規模だという事実がある。
このような場合、当然開発は極めてコンサバなものとなり、ソフトウェアでの不具合はほとんど存在しては許されないものとして考えられるのではないか。
静的コード解析を重視しているのも、潜在的なバグをできる限り早急に潰したいからなのだろう。
しかしながらCで関数ポインタの使用禁止は、なかなか受け入れがたい制限であるなあ。