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したりしなかったりした場合に正しくポップされるかは未だ考慮されていない。
関数アドレスではなく、現在のスタックフレームを比較する必要があるかも知れない。

2012年10月5日金曜日

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

新しい参照の作成

ローカル変数として参照を作成するときはつぎのようにする。
 lwd_Reference r = new_lwd_Reference(new_C("3345g"),
  (void (*)(void* object))C_finalize);
参照を生成するとき、第1引数としてオブジェクトのポインタを、第2引数にそのオブジェクトのデストラクタを指定する。

構造体やクラスのメンバが参照の場合も同様に初期化できる。

関数から復帰時に自動的に解放されるオブジェクト

スコープドポインタとしての動作を確認する例を示す。
テストに使用する簡単なクラス Cはつぎのようなもの。
/****************************************************************
 test target C
****************************************************************/

static boolean cFinalized;

typedef struct C* C;
struct C {
 char* string;
};

C new_C(char* string) {
 C this = (C)malloc(sizeof(struct C));
 if (this != null) {
  cFinalized = false;
  this->string = strdup(string);
 }
 printf("C object created.\n");fflush(stdout);
 return this;
}

void C_finalize(C this) {
 if (this != null) {
  printf("C object finalized. %s\n", this->string);fflush(stdout);
  if (this->string != null)
   free(this->string);
  free(this);
  cFinalized = true;
 }
}
コンストラクタとデストラクタがあるだけだが、デストラクタが実行されると外部変数の cFinalizedを trueにセットする。

テストケースはつぎの通り。
static void function0() {
 reference r = new_reference(new_C("3345g"),
  (void (*)(void* object))C_finalize);

 Assert_false("まだ削除されていないはず。", cFinalized);
}

void testAutoCollect(TestCase testCase) {
 Assert_false("まだ削除されていないはず。", cFinalized);
 function0();
 Assert_true("削除されているはず。", cFinalized);
}
testAutoCollect関数から呼ばれた function0関数の中で、参照 rを生成する。
関数復帰直前に cFinalizeは未だ falseであるが、testAutoCollect関数に戻ってきた後は cFinalizedが trueになる。

実行すると、標準出力にはつぎが出力されている。
[CUnit] ./ReferenceTest: testAutoCollect
C object created.
C object finalized. 3345g
デストラクタを明示的に呼んではいないが、トレースログにはデストラクタが呼ばれている様子がわかる。

2万円以上のカードをパスケースに入れて改札機にタッチしますと他のカードが処理されることがあります。 -- それは不思議な現象だ