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.

2013年4月7日日曜日

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

弊社にはプログラミングを行う際のコーディング規約があり、Sofitiesを含めた自社プロジェクトは勿論、請負業務などで先方から指定された規約が無い限り常にこれが適用されている。

業界で一般的に推奨されているものとは異なるものや時代錯誤的なものがあり、また多少流動的でもあるが、全く合理性を欠くものではない。
幾つか紹介してみよう。

字下げは、タブ文字を使用(use tab character for indentation)

XMLには空白文字(半角スペース) 4字以下の字下げを使用することがあるが、そのほかの言語ではほぼ例外なくタブを使用する。
Digital Reserch社の CP/M 2.2時代の名残である。
当時は主記憶、外部記憶の容量が小さかったため空白文字を沢山並べるのは不経済であった。
タブ文字を使用すると多くの場合ファイルサイズが半分以下になる。
欠点としては、タブ文字と空白文字を混在させてしまうと、醜くみにくくなること。

8タブを使用(8 characters tab stop)

弊社の規約の多くが Kernighan and Ritchieの "THE C PROGRAMMING LANGUAGE"に由来しているが、これは例外の一つ。
同書では原点、共立出版による邦訳版ともに 5文字タブを使用している。
多くのテキストエディタが 4または 8文字がデフォルトであることを考えると、5文字は半端である。
4タブを使用する会社が多い中、8タブを使用する理由はつぎの通り。
8タブを使用すると、ネストを深くしたときに内容がどんどん右にいってしまう。すると4タブを使用したい誘惑に駆られるが、そうしてしまうと 1個の関数、サブルーチンが複雑になってしまう。
8タブを守らせることによって、ネストが深くなりそうなときに関数を分割する習慣が付くようになる。

1行は 80文字以下(line length limit at 80 characters)

文字端末は 80文字までしか表示できないものが多かった。
ビットマップが主流となった現在ではハード的な理由は無くなった。しかし、画面が広くなったからと言って際限なく長い行はいただけない。
目で追うときに画面の右端まで行って、つぎの行の先頭に戻るときに見失ってしまう。 況してや左右方向にスクロールさせるなどは持っての外である。
画面を左右に並べてマージ作業するときなどを考えると 80文字くらいが丁度良い。

空白のとり方(spacing)

つぎのいづれかの文字の後には原則として空白類が必要、前には置かない。 ',', '.', ';', ':', '!', '?', ')', '}', ']'
例)
    a[i] = 0;           RIGHT
    a [ i ]= 0;         WRONG


単項演算子のうち次のものの後には空白類は置かない。 '*'(C, C++), '&'(C, C++), '-', '!', '~', キャスト, 前置の '++', '--'
例)
    void *p;            RIGHT
    void * p;           WRONG

    --index             RIGHT
    -- index            WRONG

    (int)longValue      RIGHT
    (int) longValue     WRONG


後置の "++", "--"の前後には空白類は置かない。
例)
    pointer++;          RIGHT
    pointer ++ ;        WRONG


乗法演算子、加法演算子、シフト演算子、関係演算子、等値演算子、ビットごとの演算子、論理的演算子、条件演算子、代入演算子の前後には空白類が必要。
例)
    a = b + c;          RIGHT
    a=b+c;              WRONG


次の演算子の前後には空白文字は置かない。 '.', "->"(C, C++), "::"(C++) ただし、行を折り返す場合は可。
例)
    record->field1 = 0;         RIGHT
    record -> field1 = 0;       WRONG


関数の宣言、定義、呼び出し時の小括弧の前には空白は置かない。
例)
    printf("Hello.\n");         RIGHT
    printf ("Hello.\n");        WRONG


関数の宣言、定義、呼び出しの小括弧の内側には引数、パラメタをコマで区切る箇所を除いて空白は置かない。
例)
    Math.max(a, b)      RIGHT
    Math.max( a, b )    WRONG


関数呼び出しの跡の乗法演算子、加法演算子、シフト演算子、関係演算子、等値演算子、ビットごとの演算子、論理的演算子、条件演算子、代入演算子との間には空白類が必要。それ以外には置かない。
例)
    fabs(v) < 1.0               RIGHT
    fabs(v)< 1.0                WRONG

    fp = fopen("text", "r");    RIGHT
    fp = fopen("text", "r") ;   WRONG
キーワードと小括弧の間には空白文字が必要。ただし、sizeof演算子の場合は置かない。
例)
    if (true)           RIGHT
    if(true)            WRONG

    sizeof(int)         RIGHT
    sizeof (int)        WRONG
sizeof演算子は括弧つきで使用する。
例)
    sizeof(int)         RIGHT
    sizeof int          WRONG
ブロックの中括弧の前には空白が必要。
例)
    if (true) {         RIGHT
    if (true){          WRONG

中括弧の取り扱い (braces)

条件分岐や繰り返しの制御構文の中が単一の文の場合はブロックを使用しない。
例)
    if (end)            RIGHT
            exit(0);

    if (end) {          WRONG
            exit(0);
    }
業界では括弧の対応が不一致になるのを恐れて {を強制する規約が多いと思う。 しかしそれは記述が複雑だったり、ネストが深くなるからであり、シンプルに書くことを心がければ、反って不要な括弧などは付けないほうが良い。 行の折り返しが必要な場合を除いて、{の前で改行しない。
例)
    if (true) {         RIGHT

    if (ture)           WRONG
    {

    int atoi(int i) {   RIGHT

    int atoi(int i)     WRONG
    {
ブロックを使用しない制御構文であっても、条件式の行と制御される行の間は改行する。
例
    while (!end)                RIGHT
            call();

    while (!end) call(); WRONG
} と elseの間は改行しない。
例)
    if (ready) {        RIGHT
      ...
    } else {

    if (ready) {        WRONG
      ...
    }
    else {

文字コードは UTF-8 (using UTF-8)

2000年ころまでは EUC-JPを使用していたが、中国語などの日本語以外の文字を混在させようとしたときを考慮して、UTF-8に切り替えられた。
お尻かゆい虫~

2013年4月2日火曜日

パッケージ一覧

昨年末、本社から徒歩移動可能なところに開発室用のオフィスを借り、1月には主要な開発機材を移転した。

2~3月には大小併せて 340個近いプロジェクトのファイルのマージに追われた。
法人化前から作り散らかし、ディレクトリ単位でフォークが発生していたもので気の遠くなる作業だった。
ずいぶんと不精したものだ。
8"FDや QICの接続が未完の為、1998年以前のものは未だマージできていない。


分類プロジェクト数
J2ME4
J2ME/PBP3
J2ME/XLET2
J2SE1
C17
LWT57
NetBeans3
Swing51
実験段階160
自社用途6
そのほか33

LWTには組み込み Java用のサービスやライブラリ、フレームワークで、既に出荷済みの RDBや サーブレットコンテナ、状態遷移マシンなどが含まれている。

softiesプロジェクト パッケージ一覧

softiesプロジェクトもフォークしていたものが見つかったので、マージが行われた。
下記に現状のテストサマリを示す。 パスしてないテストも多い。 カバレッジレポートは示してないが、これも低い。

coreパッケージ


langパッケージ


ioパッケージ


netパッケージ


utilパッケージ


javaパッケージ


x11パッケージ



アイちゃんが逝く! そういえば長いこと日糧パン食べてないな。敷島か山崎しかないし。

2012年12月22日土曜日

言語C でスコープドポインタを実現する(その4)

リファレンスの生成

リファレンスの生成は次のように行っている。
Reference NO_INSTRUMENT new_Reference(void* object,
 void (*destructor)(void* object))
{
 Reference this = (Reference)malloc(sizeof(struct Reference));
 if (this != null) {
  this->object = object;
  this->frames = frames;
  this->referenceCounter = 1;
  this->destructor = destructor;
 }
 add(this);
 return this;
}
ヒープ領域からメモリを割り当て、オブジェクトとデストラクタ、framesを記憶し、参照カウンタは1で初期化する。
この実験コードでの framesはグローバル変数であり、複数スレッドに対応していないが、マルチスレッド化するときには framesは TLSに配置される予定である。その場合、Referenceがどの framesに対応しているか素早く探すためのものである。
TLSからキーで検索するならこれは不要である。

つぎに生成したリファレンスを framesに登録するために add関数を呼ぶ。
メモリ割り当てに失敗したときにも addししまうのは暫定のためである。
再帰呼び出しされる関数の中でリファレンスが作られるなら、単純に if文の中に入れれば良いと言うわけにはいかない(復帰時に他の階層のリファレンスを破棄する恐れがある)ので、何らかの配慮が必要になる。

その add関数はつぎの通り。
private void add(Reference r) {
 int top = ArrayList_size(frames) - 1;
 Frame frame = ArrayList_get(frames, top);
 ArrayList_add(frame->references, r);
}

framesリストの先頭にある frameリストを取り出す。
frameリストにリファレンスを追加する。

framesと frameの各リストの構造を(その2)の例題 testAutoCollectを例に図式化すると次のようになる。





洗濯機が貰えるなら僕らもノーベル賞取ろうよ。

言語C でスコープドポインタを実現する(その3)

関数から復帰するとき

前回の使用例のように関数から復帰するときの処理について紹介する。
gccのフック機能を利用してつぎの処理を行う。
void NO_INSTRUMENT __cyg_profile_func_exit(void* functionAddress,
 void* call_site)
{
 if (!suppressed) {
  suppressed = true;
  popFrame(functionAddress);
  suppressed = false;
 }
}
関数呼び出し時同様、自身のトレースを行わないようなフラグ処理を行う。
(ただし、マルチスレッド未対応)
関数呼び出しに対応したフレームをポップする。

popFrameはこんな感じ。
private void popFrame(void* functionAddress) {
 boolean done = false;

 while (!done && ArrayList_size(frames) > 0) {
  int top = ArrayList_size(frames) - 1;
  Frame frame = (Frame)ArrayList_removeAt(frames, top);
  done = (frame->functionAddress == functionAddress);
  Frame_finalize(frame);
 }
}

framesリストの新しいものから順に現在の関数名が現れるまで、古いフレームを捨ててゆく。
こうすると、途中に -finstrument-functionsを指定せずにコンパイルされた関数があってもポップが正しくできる。
また、最低1つはポップ済みのため、関数を再帰的に呼び出しても処理できる。
再起呼び出しの中で以前紹介した例外機構や longjmpによる脱出にも対応可能である。

ただし、落とし穴もある。
-finstrument-functionsを使っていない関数に復帰しただけではすぐに解放処理が行われない。
再起呼び出しにより同じ関数を通ったとしても、条件分岐により catchしたりしなかったりした場合に正しくポップされるかは未だ考慮されていない。
関数アドレスではなく、現在のスタックフレームを比較する必要があるかも知れない。