2011年3月31日木曜日

【どうでもいい話】初期化漏れであわや一大事

以前書いたように、現在Solaris上で動くバッチプログラムをC言語(Pro*C)で書いてまして、今、結合テスト的なことをしてます。

詳しいことはさすがに書けませんが、機能的には、とある銀行のインターネットバンキングのDB(Oracle)を、ホストから送られてくる移管データの内容に従って更新する、っていう【いわゆる移管処理】ってヤツです。

今回はあんまり面倒な移管方式が無いものの、さすがに「口座番号」というキー項目を更新するため、万が一のことすらあってはいけません。

ところが今日、結合テストでプログラムを走らせてみたら、ログに何やら謎のエラーログが。

移管処理中は移管対象利用者が操作できないように対象データにロックをかけるわけですが、移管処理が終わってもそのロック解除に失敗している。
うーん、これはマズイな・・・なんて原因を調べていたら、ロック解除とかそんな生易しいもんじゃない、もっとマズイことが判明しました。

複数口座を持つ利用者もたくさんいるわけですが、その人の口座が部分的にあっちにいったり、こっちにいったりしてる。
つまり、利用者から見ると、「あれ!?なんか俺、口座増えてる!しかも金も結構入ってるし!」という夢のような機能が提供されているわけです。
(もちろん一方で「あれ!俺の口座が無くなった!」という極悪機能が提供されています)

原因を探ると、Oracleからカーソルでデータを取得してFETCHループしているところで、たった一つの変数を初期化するのを忘れていた、その「たった1行」が原因でした。

この「初期化」というたった1行がないだけで、「急に口座が現れたり、消えたり!」という、とんでもないダイナミック機能を提供してしまうところでした。
あらためてテストの大切さを痛感。っていうか、テストで発覚して良かった。
テスト仕様書は相当時間をかけて作ったけど、今回のパターンはケースとして入れてなくて、やっぱり考え抜いたつもりでも、いろんなパターンというのは出てくるもんだ、と実感。この辺、SEセンスが問われるところですよね。
(たまにすごく優秀なテスターに巡りあうけど、そういう人ってすごく尊敬)

今後のために、自戒の意味を込めて日記としてしたためておくとします。
皆さんもお気を付けください。。。

2011年3月9日水曜日

プログラムを書くときのポリシーみたいなのってありますか?

先日、C言語のプログラムを書いて処理単位に関数化してコードをスッキリさせたら「わかりにくい」というまさかの指摘を受けたことを書いた

あらためて、指摘者の観点としては以下のようなもの。
(1)関数に飛ぶと、戻った時に何をしてたんだかよく分からなくなるから
(2)現在稼働中の他のプログラムが、1つの関数にまとめて処理を書いてあるから

1つ目に関しては、「それって1つの長い関数の方が途中でわけわからなくならない?」って思うし、2つ目に関してはもはや思考停止状態としか思えないわけだが、それもまぁ、育った環境と考え方次第なんだろう。セロリが好きだったりするよね。

で、意地でも「機能単位に関数化してフローを(ボク的に)わかりやすくしたコード」が嫌みたいなんで、腹が立って今日は関数化していた部分をガシガシまとめて、1000行級のモンスター関数がボコボコ続く、お望みの形にしてやったぜ!(ただ俺、これ絶対メンテできないや…)

まぁそれはそれでいいとして(良くない気もしつつ)、コードって人によって千差万別で、じゃあ俺は何でこういう書き方をしたんだろう、と自分のコード書きポリシーみたいなものについて考えてみた。

C言語の場合


  • 関数はエディタで開いて1画面に収まる長さにする
  • 意味のある機能単位に関数化して、機能をカプセル化する
  • グローバル変数は絶対に使わない(ぐらいの気迫で作る)
  • 定型的なくせに長ったらしいコードはマクロ化する


1つ目「1画面に収める」っていうのは、特にボクが今TeraTerm&viで開発してるからなんだけど、でもあんまり長い関数ってやっぱりヤだよね?viじゃなくてもやっぱり1画面になるべく収めます。
2つ目「カプセル化」は、処理を閉じ込めることでバグの影響範囲を最小限に食い止めることが出来るし、メンテもしやすいから。普通でゴメン。
3つ目「グローバル変数」は説明の必要もないかと思いますが、何と今のプロジェクトでは外だしのヘッダファイル内でグローバルな構造体が死ぬほどあって、絶対もうどうかしちゃってる。
4つ目「マクロ」は前回書いた通り、便利だから。

ざっと思いついたのはそんなとこでしょうか。ポリシーっていうか、Tipsっていうか、むしろ常識みたいな感じだけど。

Java言語の場合


  • if文が出てきたらクラス分割あるいはインターフェースの切り出しを検討する
  • インスタンス化できるクラスと出来ないクラスを明確にする

取り急ぎ考えた結果、これだけしか出てこなかったんですが(恥)、Javaの場合はカプセレーションとか、例外機構の活用(中で握りつぶさない、的な)とかは当然っちゃ当然のことなんであえて書く必要もないかと思いまして、と言い訳しておこう。



で、大したことを書けなくて今困っているんだけど、僕の周りには幸い設計をやらせたりコードを書かせたら超一級な人が何人かいるので、その人たちがこれを見て「そういえば俺はこうだなぁ」とかいう知識とか情報とかノウハウを出してくれないかなぁ、とそう目論むことにしました。

せっかくここまで読んだコード書きのアナタ、PHPでもJavaScriptでも何でも構いません、是非語ってください。そして僕に教えてください(笑)

2011年3月2日水曜日

アンケート用紙で部署とか書くトコについて提案

(以前、どこかで書いたことがあるような気もしつつ)
よくIT系のイベントとかで、ブースへの来場者にアンケートに答えてもらうことってあると思うんですが、その時に大抵、項目として「所属」とか「役職」とかを書いてもらいますよね。


これってなんかこう、書く方からしたら「めんどくさい」「何で教えなあかんねん」「役職とかねーし」と思うし、書いてもらった方も、見込み客のプロファイルを取りたいのに「で、結局この“サービス企画システム開発第2部シニアマネージャー補佐”ってナニ?」みたいなことになりかねない(さすがにこんな謎の部署・役職はないと思うけど)。

で、もう3年も前になるけど2008年のSODEC(ソフトウェア開発環境展)の時、ジラッファさんのブースに間借りして15分プレゼンをさせてもらった時に使用したアンケート用紙では、こんな風にしたのです。

「あなたの当てはまるところに○を付けてください」


例えば、システム開発部門の部長さんとかだと、こんな感じ?


この軸だけだと経理部門とかサポート部門に対応しにくいんだけど、SODECという来場者のプロファイルがほぼ限定されている中ではとても有効だったように思います。

この方式のいいところは、部署とか役職とかを書く抵抗感が無くなるとともに、アンケートに回答する人自身が「自分が思う自社における自分の役割、ポジション」を告白してくれるので、実は肩書きなんかよりもよっぽどストレートなプロファイルが分かる、ということです。

正直、イベントでいくら名刺集めたところでほぼ意味がない、っていうのは世の企業のマーケ担当の方々は既に重々理解していることでしょうから、どうせならこんな感じでちょっとヒネった形でアプローチを変えていくのも良いんじゃないでしょうか。回答する方、される方、お互いのために。

という提案でした。

2011年3月1日火曜日

C言語で書くならマクロを活用しようぜ

ひょんなことから、今C言語(正確にはOracleのPro*C)でプログラム書いてまして、この10日間で4500ステップほど書きました。
それが多いのか少ないのかわからないけど、8年ぶりにTeraTerm上のviオンリーでmakeしながら書いてるんで、この6年ぐらい営業とかマーケとかマネージャーチックなことをやっていたボクにはなかなかの達成感があるボリュームです。

書いてるのは、既存のシステムに新たに追加するマスター更新系のバッチ処理。
プログラムと言えばすっかりJavaに慣れてしまったので、「参考にしろ」と言われた数々の「お手本」の、1000ステップぐらいある「main」処理が気持ち悪くて仕方ない…。

「このwhileの閉じ括弧は、いったいどこ・・・」
「このifに対するelseってどれ・・・」みたいな。

で、ちょっとでも可読性とメンテナンス性を良くしようと意味のある単位で関数にぶった切って、なるべく処理を移譲するイメージに整えて「うむ、なかなかの出来栄え!」と思ったけど、やっぱり手続き型に慣れた目にはズラーっとダラダラ書いた方が読みやすいっぽい。んー、反省。


ただ、「え、こんなこと出来んの?」と、ボクが新人だった頃に既に使い古されていたマクロ・テクに新鮮な驚きを示した方がいて、おぉ、まさかこんなので驚かれるとは・・・と思いつつ、ひょっとしたら知って得する方もいるかもしれないので、C言語ならではの便利な記述方法を。

マクロ関数

C言語では、例えばソース内で扱う定数なんかを

#define MSG_OK 0
#define MSG_NG 9

みたいな感じで定義しますよね?
で、実際の処理の部分では以下のように書きますよね?

if( ret == MSG_OK ){
//OKの処理
}

こう書いておくと、コンパイル時のプリプロセスにおいて定義したものが展開されて、こんな風になってからコンパイルされるわけですね。

if( ret == 0 ){ //←MSG_OKが 0 に置き換えられる
//OKの処理
}


この仕組みを利用すると、関数っぽいのもマクロで定義できて、それをマクロ関数なんて言ったりします。たぶん。

よくUNIXサーバーで動いている業務系システムには、もうやたらめったらmemsetしているシーンが多々見受けられます。こんな感じ。

memset( strA, 0x00, sizeof(strA));
memset( strB, 0x00, sizeof(strB));
memset( strC, 0x00, sizeof(strC));
memset( strD, 0x00, sizeof(strD));

こういう、決まったパターンがある場合はマクロ関数がお勧めです。
ソースの先頭の方で以下のようにマクロ関数を定義します。

#define INIT_STRING(A) memset(A, 0x00, sizeof(A));

これを使うと、上記のmemsetは以下のようになります。

INIT_STRING(strA);
INIT_STRING(strB);
INIT_STRING(strC);
INIT_STRING(strD);

こうすると、見た目にもすっきりする他、よくありがちな memset( strA, 0x00, sizeof(strB)) みたいな、うっかりコピペミスによる潜在バグを防止することもできます。


また、Pro*Cの場合だと、SELECTして取ってきた値をバインド変数に突っ込む、という処理があると思いますが、恐らくインジケータ変数を見て、正常に取得できていたらバインド変数から他の変数にコピーする、なんていうこともよくやるんじゃないでしょうか?

EXEC SQL
SELECT USERID, USERNAME
INTO :UserId :ind_UserId, :UserName :ind_UserName
FROM USERTBL
WHERE USERID = 11;

if(ind_UserId == 0){
strcpy(l_uid, UserId);
}
if(ind_UserName == 0){
strcpy(l_unm, UserName);
}


もうお分かりだと思いますが、上記の strcpy 部分は、以下のようなマクロ関数を使うとちょっとスッキリします。

#define GET_DB_VALUE(A, B, C) if(A==0)strcpy(B,C);


元のソースのif~部分は以下のように書き換えることができます。

GET_DB_VALUE(ind_UserId, l_uid, UserId);
GET_DB_VALUE(ind_UserName, l_unm, UserName);


ちょっと見やすくなりましたね?ね?

こんな感じで、定型的なパターンがあって何度も同じように書かないといけない場合は、マクロ関数を使ってみてはいかがでしょうか。



って、いつの時代の話だよ!と突っ込みたくなりますが。
もっと知りたい方は、ググるとたくさん出てきますので調べてみてください。

ちなみに、何とかC言語でもJavaっぽい例外処理を書けないか、と思って調べたら、同じような悩みを抱えつつマクロとsetjmp()を使ってそれっぽいことをされている方も結構いたんですが、やっぱり呼び出しスタックの上の階層までExceptionの型を指定してthrowするような、ステキ例外機構を実現されている方はいないようでした(当たり前)。
やっぱり例外処理機構って、イケてるなぁ・・・と思いました。

#しかし、Javaなのに手続き型で書かれたプロジェクトがあったと思ったら、今度はモロに手続き型。まだまだ世の中、そんなシステムもたくさんあるんだね。