2013年4月9日火曜日

コーディング規約 その2 (code conventions 2)

前回に引き続き、弊社のコーディング規約を紹介する。

なお、命名規約はローカル変数やプライベートなメンバを除いて設計規約の一部として扱っているため、コーディング規約には現れない。

マクロ置換の禁止 (Prohibition of the macrosubstitution)

マクロはしばしば取り上げられるような引数評価の副作用が問題であるだけでなく、可読性を悪化させる原因にもなる。
本機約では、例外的に標準ライブラリを使用する上で必要となる最小限の使用を認めているが、それ以外の使用は原則的に禁止である。
softiesプロジェクトではこの原則を 2つ破っている。

一つは、importを実現するためのもの。

もう一つは、例外機構の try, catch, finallyである。

前者は置換のルールが単純で混乱を起こすことは少ないと思われるが、後者は危険が多い。そのため例外機構は完全な実験状態であり、ライブラリ中で使用している箇所はまだないのである。
外にも publicや privateなどの可視性を表すものなどがあるが、使用するかしないかは選択できるようになっている。


Javaではアノテーションが追加されるまではコンパイル制御機能はソースコード上に記述できなかったため、記号定数は全てクラスの定数として使用される。
問題は、 C/C++の場合である。
本規約では、記号定数は全てマクロではなく定数 (constant value)として定義しなければならない。
関数型マクロは、C++の場合テンプレートで置換することができる。
Cにはテンプレートが無いため、全て通常の関数を記述しなければならない。多態性実現のために関数型のマクロをしては成らない。
なお、C++のテンプレートもデバッグを難しくする恐れがあるため、慎重に定義しなければならない。それが難しい場合には C同様にいちいち通常の関数を記述することになる。


記号定数の置換のメリットは、単にリテラルをロジックから排除することによる可読性の向上だけでなく、使用されない定数が多くある場合には通常の定数を用いるよりもメモリ削減になる点である。
我々はそのメリットを犠牲にしても、混乱を避けることを選択した。

条件付コンパイルの禁止 (Prohibition of conditional compilation)

#if, #ifdefなどの条件付コンパイルはマクロ置換よりもさらに可読性を悪化させる。
そのため本規約では、多重インクルード防止目的のもの意外の使用は禁止である。

製品コードの中に #if 0 などが含まれるのは論外で、仮にデバッグ用に使用されたとしてもコミット時までには除去されなければならない。
コードが複数の機種や仕向けの仕様を共通で使用するためにも #ifが仕様されることがあるが、弊社では禁止である。
バージョンの切り替えも禁止である。
これらは、デザインパターンで回避するか、適切な構成管理を行うことで解決が可能である。
なお、回避、解決法は何通りかのパターンがあるため、ここでは触れないが、機会があれば紹介したい。

goto文の使用 (using goto statement)

多くの場合、可読性を低下させる。
エラーハンドリングでは gotoを使ったほうがコードがすっきりするという考え方もある。
実際、μITRONのある実装では gotoを使った場合の効果的なエラーハンドリングのコードを見たことがあるが、誰がやってもうまくいくとは限らない。特に関数の中の構造が複雑だとうまくいかない。
弊社の設立前、1990年代中ごろに筆者が携わったものの中にすさまじいコードがあった。それは国内のある会社が実装した CADDAM用のカスタムコード(プラグインソフト)が正常に動作しないため改修を請け負ったのだが、1個の switch caseが 6千行あり、そのほとんどの caseが breakではなく gotoで終わっているような代物だったのである。
goto文を breakに置き換えてみるとなんと、相対ジャンプの距離が遠すぎてコンパイルエラーになるではないか。
つまり、元の作者はそのエラーを逃れるために gotoを用いたようだ。
このような使い方をしなければならない状況と言うのは、何か怪しいのである。まず、保守不可能と言ってよいだろう。


話が遠回りしてしまったが、本規約では goto文の使用は C++では禁止。Cでは禁止しないが、限りなく非推奨に近い。


例外処理機構を有効に用いる手段ができれば、Cでも禁止することになるだろう。

do while文

do while文の使用は禁止していないが、for文、while文を優先的に使用することが推奨される。

定数との比較

定数との比較時に、誤って代入してしまうのを防止するために、しばしば比較する定数を左辺に記述する規約が存在する。
例) WRONG
    if (0 == value)
        ...

本規約では、このような書き方は禁止であり、つぎのように記述しなければならない。
例) RIGHT
    if (value == 0)
        ...

これは、valueが 0と等しいかどうかを判定したいのであり、0が valueと等しいかを判定したいのではないからである。
2000年頃に私が隣のプロジェクトのメンバとした議論の中では、前の書き方でも『0 と valueが等しいかと読めばよいだろう。』という意見がったが、比較するものの主語がどちらかを考えると 0 == valueでは不適切である。


我々の記述方法では誤って値を代入してしまうではないかと思われるかもしれないが、徹底した単体テストで補うことで解決している。
なお、Javaでは、 if (value = 0)と書いてしまってもコンパイルエラーになるため、バグになることは無い。

ブール値との比較禁止

ブール値の判定に比較演算子を用いてはならない。
たとえば、次の例は禁止である。
例) WRONG
    if (files.hasNext() == true)
        ....

この例では、==と trueの組み合わせであるからまだマシなほうで、これが !=や falseとの組み合わせであったら読み手が理解しにくい。

つぎの例のように、定数との比較を行わないことが正しい。
例) RIGHT
    if (files.hasNext())
        ...

hasNextは「次を持っている」と読めるので、何も trueと比較して等しいことを確認する必要は無いのである。
逆に言えば、関数名を付ける場合には hasXXXや isXXX、containsのように「読める」名前を使用しなければならない。

悪い関数名の代表は check()。 チェックしてどうなったら trueなのか、ドキュメントを参照しなければ理解できないだろう。

我々が使用する関数名で validate()というのがあるが、これはブール値を返すのではなく受理されない場合には例外を返すため問題ではない。

関数の途中からの脱出

原則として関数の途中脱出は禁止である。

これには幾つかの例外がある。つぎの場合の途中脱出は認められる。
  • 関数の冒頭で引数や状態異常を検出したことによる return文によるリジェクト
  • 例外がスローされる場合
このほかに exit()もあるが、正常終了であれ異常終了であれ、可能な限り関数の最後に exit()を記述しなければならない。


鳥インフルエンザが流行りそうだな。万一のときはドクターに頼んでコルドラジンをほんの数滴打ってもらうことにしよう。
Bird flu seems to be popular. Let's decide to have I ask a doctor at the time of emergency and inject just several drops of Cordrazine.

0 件のコメント:

コメントを投稿