雑記帳 2005年 4月第5週

2005/04/24 Sun.

現在配布中の掲示板CGIを修正。いや、修正といっても更新履歴に追記するような修正じゃないけど。ってかこれは修正?
内容は、一部のサブルーチン内でスクリプトの記述でインデントのミスがあった、これだけです。動きに変更は無し。
一回こだわりだすと、どうでもいいような箇所までが気になってきてしょうがない。いえ、一応これでもプロなので。

インデントで思い出したんだけどさ、今の職場でウチとは別の協力会社の人ですんげー使えないのがいるワケよ。
いつだか雑記に書かなかったっけな、ロクにソースを読み込みもしないで改変しまくった挙句に質問してくるアホの話。
その人のソースが読みにくいの何のって。インデントがおかしい以前に、そのソースを本当に自分で読めてるのかと。

String sData = new String();
(略)
boolean isOverFlowFlg = false;
StringTokenizer stk = new StringTokenizer(sData, "|");
for (int iCount = 0;
iCount < stk.countTokens();
iCount++) {
String sDataNo = new String(); sDataNo = stk.nextToken();
String sDataName = new String(); sDataName = stk.nextToken();
String sDataValue = new String(); sDataValue = stk.nextToken();
int iDataNo = Integer.parseInt(sDataNo);
if (iDataNo > 255) { isOverFlowFlg = true;
iDataNo = iDataNo - 255;
sDataNo = Integer.toString(iDataNo);
(略)
}
(略)
}

何て言うかさ、釣りとしか思えないようなソースを書かないで下さい。貴方、一応ソース書いてメシ食ってる人でしょ?
先生!インデントありません!for文の条件句ごとに改行は勘弁して下さい!ってか、stk.hasMoreTokensを使って!
あのな、使わない数値なんかをいちいち取得するなと。if文内の処理が複数あるなら綺麗に改行して書いてくれと。
booleanって書いてあるんだから、わざわざ変数名にFlgとか付けないでもわかるっての。isが書いてあるなら尚更だ。

String sData = new String();
(略)
boolean isOverFlow = false;
StringTokenizer stk = new StringTokenizer(sData, "|";);
while (stk.hasMoreTokens()) {
    String sDataNo = stk.nextToken();
    String sDataName = stk.nextToken();
    String sDataValue = stk.nextToken();
    if (Integer.parseInt(sDataNo) > 255) {
        isOverFlow = true;
        sDataNo = Integer.toString(Integer.parseInt(sDataNo) - 255);
        (略)
    }
    (略)
}

可読性という観点から見た場合、自分のソースの方が見やすいと思うんだが、何か間違ってるか?正しいよな?
そもそもインデントが無いと、そのソースが初見の場合にフラットにするのにとんでもない手間がかかる気がする。
処理の流れも想像つかないだろうし、いざ改変するとなっても全体的な構造が見えにくい。困った上に使えない人だ。

2005/04/25 Mon.

マジで始動してしまった「2ch BBS」自作計画、結構色々な機能が実装できています。今日もあれこれ頑張ります。
発行済みキャップの修正機能やスレ立て制限・解除も何とか実装。難しいとは言わないが、エラーチェック部分が多い。
今は最低限の形を作るということでやってるから、サブルーチンの流れとかもかなり適当。後でまとめて直さないとな。
キャップの修正機能については導入するか迷ったけれど、管理者のみが修正可能ということにすれば問題無いか。
管理メニューへはキャップ発行された人がキャップのパスを使って入れるんだけど、そこが仇になったりしてた。
Aさんが#Aというキャップのパスワードを使ってログインして、Aさん自身のキャップを直したりすると不都合があるし。
ログイン情報を$ENV{"QUERY_STRING"}で持たせてるのが悪いんだけどさ。Cookie使うとなるとシャレにならん工数。
URLの末尾に「pass=%23username」とか付いてて、それを元にログインユーザー情報を取得してるのが現状の仕組み。
ところが、キャップのユーザー名やパスワードを変えると、URLを元に取得した情報と差異が生じてややこしい事になる。
ってなワケで、権限が管理レベルのキャップを発行されている人でも、発行済みキャップの修正は制限する、と。
他にも作っててふと思ったんだけど、スレ立て規制ってえらく単純な仕組みなのね。自分で作ってて驚かされたよ。
ただ単純に適当な名前のディレクトリを作って、スレ立ての際にそのディレクトリがあったらエラーにする。これだけ。

他に控えてるややこしそうな機能と言えば、スレやレス削除とdat落ちさせたスレのHTML化とか?激しくメンドそう。
あ、スレの引っ越し作業を実装するとなるとメンドくさそうだな。まさか真・スレッドムーバーを自作することになろうとは。
ってか、完成したとしてもリリースできるレベルの質に仕上がるかが不安だな。時間かけてじっくりやっていきますか。

2005/04/26 Tue.

それは昨日の退社前、定時まであと5分という頃だった。「追加機能を水曜の夕方頃までに作って欲しいんだけど」だとさ。
あのな、何で定時5分前になってそんな事を言いますか。別に機能追加依頼はいいですが、少しは時間を考えろと。
しょうがないから昨日は終電近くまで残業して、最低限の設計と雛形の作成を終える。急ぎだからひどく疲れたな。
睡眠時間も足りておらず、歩きながらでも意識が落ちそうなほどの睡魔を背負いながら何とか出社。トイレで寝てきたい。
で、今日は朝から夜までフル稼働。作成JSPが6枚にもなった。しかも、どれも微妙に流用作成できないものばかりだし。
今日の22時の時点で98%くらいまでは完了。期限の1日くらい前には仕上げておきたい。機能変更あると困るしね。

さて、SQLを再帰的に投げまくって結果をあれこれと取得するJSPを書いたりしていたんですが、思わぬ収穫があった。
JavaでOracleを使うに当たって絶対にお世話になるものにResultSetがある。SQL実行結果を格納するものなのです。
これって、ResultSetの中に結果を格納しているワケじゃないのね。おかげでとんでもなく悩みこむハメになったし。

class DB {
    public void setPrepareStatement() throws Exception {
        try {
            sSql = (略);
            statement = conn.prepareStatement(sSql);
        } catch (Exception e) {
            throw e;
        }
    }
    public void createCSV(int iCount, String sName) {
        ResultSet rs = null;
        String sParent = new String();
        String sChild = new String();
        String sBirth = new String();
        String sAddress = new String();
        try {
            statement.setString(1, sName);
            rs = statement.executeQuery();
            while (rs.next()) {
                if (rs.getString(1) != null) {
                    sParent = rs.getString(1);
                }
                if (rs.getString(2) != null) {
                    sChild = rs.getString(2);
                }
                if (rs.getString(3) != null) {
                    sBirth = rs.getString(3);
                }
                if (rs.getString(4) != null) {
                    sAddress = rs.getString(4);
                }
                sCSV += sParent + "," + sChild + "," + sBirth + "," + sAddress + "\n";
                iCount++;
                this.createCSV(iCount, sChild);
            }
            return;
        } catch (Exception e) {
            throw e;
        }
    }
}

かなり激しく省略させてもらいますが。createCSVのメソッド内で自分自身を呼んでいるが、ここでResultSetが問題になる。
普通の場合ならばローカル変数はメソッドが呼ばれる度に再定義されるので、同一メモリ上の変数が書き換わることは無い。
しかし、ResultSetは厳密に言えば値やデータを格納している変数では無いので、少しばかりややこしい話になる。
すなわち、再帰処理で呼ばれた別メソッド(自分自身のメソッド)の度にResultSetの中身が書き換わってしまうのだ。
上記の例では、親子を取得してその子を親としてさらにその子を取得して、その子をさらに……という処理になっている。
取得した子を親としてSQLを投げ、ResultSetに結果が取得できなければ一つ前のメソッドのwhile句に戻るのだが、ここが罠。
結果が取得不可の場合はResultSetから値は取得できないが、これによって一つ前のメソッドのResultSetも空になる。

sParent sChild sBirth sAddress
Aさん Bさん 1945/08/15 北海道
Bさん Cさん 1965/10/05 青森
Aさん Dさん 1945/08/15 北海道
Dさん Eさん 1965/10/05 山形

こういうデータがあるとする。まずはAさんの子であるBさんを取得し、その次にBさんを親として子を取得するワケだ。
そして、Bさんの子はCさんであり、今度はCさんを親として子を取得すようとするが、Cさんには子がいないので値は空だ。
一つ前のBさんを親としたwhile句に戻り、Bさんには子が一人なのでwhile句を終了し、Aさんを親としたwhile句に戻る。
しかし、最後に取得したResultSetの結果が空(Cさんには子がいない)であるため、AさんのResultSetが書き換わるのだ。

sParent sChild sBirth sAddress
Aさん Bさん 1945/08/15 北海道
Bさん Cさん 1965/10/05 青森

Cさんの子がいないのでAさんを親とした結果のResultSetが変更されて空になり、ここまでしかデータが取れないのだ。
予測なんだけど、ResultSetはJavaとOracleの中間としてDBのデータの受け渡しを行なうような存在なのではないだろうか?
SQLの実行結果を格納したコネクション的な役割を持っているため、SQLが投げられる度に中身の値が変わってしまうと。
で、これをどうやって回避するのか。ResultSetから値を読めないんじゃどうしようもないと思ったけど、ここは発想力で勝負。
上記の例だとResultSetの結果の一行あたりに4つの値、それが複数行格納されていると考えます。ってか、当然ですか。

private String TokenConvert(String sText, String sSplit) {
    String sOutText = new String();
    String sTmpText = new String();
    String sTmpTextPrev = sSplit;
    try {
        for (int i = 0; i < sText.length(); i++) {
            sTmpText = sText.substring(i, i + 1);
            if (sTmpTextPrev.equals(sSplit) && sTmpText.equals(sSplit)) {
                sTmpText = " " + sSplit;
                sTmpTextPrev = sSplit;
            } else {
                sTmpTextPrev = sTmpText;
            }
            sOutText = sOutText + sTmpText;
        }
        return sOutText;
    } catch (Exception e) {
        return null;
    }
}
private String[][] mLineToken(String sData, String sWord) {
    int ix;
    StringTokenizer stk = new StringTokenizer(sData, "\n");
    ix = stk.countTokens();
    if (ix == 0) {
        return new String[0][0];
    }
    String sRet[][] = new String[ix][0];
    for (int i = 0; i < ix; i++) {
        sRet[i] = this.sLineToken(stk.nextToken() + " ", sWord);
    }
    return sRet;
}
private String[] sLineToken(String sData, String sWord) {
    int ix;
    StringTokenizer stk = new StringTokenizer(sData, sWord);
    ix = stk.countTokens();
    if (ix == 0) {
        return new String[0];
    }
    String sRet[] = new String[ix];
    for (int i = 0; i < ix; i++) {
        sRet[i] = stk.nextToken().trim();
    }
    return sRet;
}

こんな感じの配列を処理するメソッドを使って、表に見立てたResultSetの中身を多次元配列へと代入するワケです。
ちなみに多次元配列の処理を行なうメソッドは2番目と3番目ですが、最初のはStringTokenizer対策のメソッドです。
いつだか書いたけど、区切り文字と区切り文字の間に何も値が無いと、トークンとして見なしてくれないのです。
要するに、区切り文字が連続している場合に、その間に半角スペースを入れてくれるメソッドです。こういうの超便利。
とりあえず、多次元配列を使ってfor文の中で回すことによって、while句でのResultSetのループを再現するワケだ。
アホみたくメンドい作業だけど、こういう問題回避方法も覚えていかなきゃならんな。プログラマー必死です、超必死です。

2016/01/17 追記
今見直すと、何を言ってんだかわからないんだけど、ResultSetで再帰処理をしようとして引っかかってたのかな。
1つのStatementから生成されるResultSetは、オープンしていられるのは1つだけ、「こちら」を参照。
上に書いてあるソースを読んでも、これ、Statementはどこに持ってんだ?省略されたprivateフィールド?
もしそうなら、この現象で引っかかってたんだろうなあ。Statementをその都度生成すりゃ楽な実装にはなりそう?
その場合、カーソルがオープンしっぱなしのResultSetが、予測不能な数だけ登場するんだろうけど。それも恐ろしいな。

2005/04/27 Wed.

超必死なプログラマーには、バグも無い上にある程度の仕様変更には柔軟に対応できるソースを求められる(と思う)。
そんなワケで、自分の書いたソースくらいは把握しておきたいものですが、量が多いのでなかなかそうもいかない。
自分用に色々とメソッドを記述したクラスを作っておいて、開発時にはそれを流用している人は多いのではないかな?
例えば自分は「こんな」感じでユーティリティ専用クラスを使っています。あるのと無いのじゃ開発効率が変わる。
ネットとか色々探してはいるんだけど、便利そうなメソッド集みたいなのって思いの外見つからないものだったり。

さて、今日は一昨日の定時後から昨日にかけて作ってきた機能をリリースするに当たっての検証作業ばっかりな一日。
データベース設計が悪いとは以前から言われていたけど、まさかSQLを一つ投げて結果が返ってくるまで6秒もするとは。
テーブルの連結をしているからしょうがないとは思うんだけど、何とかもう少し速くならないものかと悩んでばっかり。
例えば今回の機能で言えば、一つのJSPの中でSQLを実行していて、結果出力に5分かかるとしたら4分55秒がSQLだ。
こればかりはどうしようもないと上の人もわかってるらしいのが救い。そもそも見てるDBがウチの開発環境のじゃないし。

2005/04/28 Thu.

今日は立川支社での新人歓迎会。立川支社でやるのかと思いきや、何故か西国立で会場を借りてやることになってる。
別に急ぎの仕事が溜まっていたワケでもないので、やるべき事だけをさっさと片付けて定時を少し過ぎた頃に出る。
ウチのグループで青梅勤務の人の勤務表を持っていかなければならず、あれこれと書類がかさんで結構重いでやんの。
西国立へ行く前に立川支社に行って渡してきた方がいいのかなーと思いきや、どうやら総務部の人も参加するらしい。
会場に入って受付を済まし(会費1,000円)総務の人を探してみるも、まだ誰も来ていない罠。相変わらず時間に怠惰だ。
あんまり重要な書類を持ちっぱなしにしたくないので、お世話になってる営業のK野さんに預かっててもらうように頼む。
んで、どこにでもあるような立食パーティーになるんですが、ウチの会社ではこういう時に毎回ビンゴゲームをやるのです。
上位商品だけはアホみたくいいんだよな。スゴ録とかPS2とかiPod Shuffleとか、ディズニーランドのペアパスポートとか。
しかもビンゴした順番ではなく当たった人が抽選クジを引いて決めるやり方。まーその方が同時ビンゴの場合に公平だな。
ちなみに、新人歓迎会にもかかわらず参加権は全員にあります。で、今回は何ももらえず。あ、図書券500円分もらったな。
ってか、パーティーの司会がスゴ録を当てて持って帰るのってどうなのよ。お前、自作自演なんじゃないかと小一時間(略

で、もらった図書券で久々に漫画を買ってみた。そろそろ映画化される予定のアレです。「電車男」の単行本ですね。
どうやらヤングサンデーで連載しているというのを今朝の通勤電車の中で知り、単行本を帰りに買おう思っていたワケです。
まだ第1話しか読んでませんが、なかなか期待できそうな予感。それにしても、そろそろ電車男ネタも引っ張りすぎですね。

2005/04/29 Fri.

仕事でもなく大学の時のサークルでもないんだけど、何の脈絡も無くいきなり知らない人たちと酒を飲むというのは疲れる。
まぁ色々とややこしい事情がありまして、今日は18時から新宿で飲みがあったのです。誘われた以上は顔くらい出します。
何で知らない人たちからいきなりそういう誘いがあるのかとかは訊かないように。何かとややこしい境遇の中にいるのです。
んで、今日のお店は「創作ダイニング - 暁 -」というとこ。靖国通り沿いのセブンイレブンの隣です。はなまるうどんの上。
集合の18時に行ってもまだ誰も予約席には来ておらず、下に戻って一服してから再び行ってみると、ようやく来てました。
最初は何かと戸惑うけど、慣れりゃ面白いもんだね。自分を含めて5名参加で、何故か一人は欠席。千葉の人だからなぁ。

二次会どうすっかって話になって、カラオケやら飲みなおしだか色々意見出たけど、ここは飲みなおしを選択。食い足りない。
んで次に行った店が……どこだっけか、新宿区役所の近くだったと思うけど、店の名前覚えてないな。とりあえず二次会。
20時ちょいに入って、22時ちょい過ぎまで延々と話し続ける。面白い連中が多くて助かったな。逆セクハラされましたが。
ってか、飲んでばっかりだから微妙に頭痛かったり。まぁ今日は帰って寝るだけだしなー。酒くらいはしっかり付き合います。

店を出たのが22時ちょいなんだけど、何だかこのノリでカラオケ行くかーとか言ってる。いや、さすがに勘弁で。
ってか、行くかーとか言ってる人もさっきまで眠そうだったじゃん!また機会がありゃその時にしようってことで解散に。
んで、電車の中で電車男の単行本を読みながら帰る。うん、これは微妙に名作の予感。絵もそこそこ好感が持てるな。

2005/04/30 Sat.

廃人生活1日目。起床時間及び睡眠時間等、お察し下さい。ってか、昨日の酒が微妙に残ったせいか、ちょっと頭痛い。
今日は朝から晩までPCと向かい合ってたぞ。そりゃ自作2chも進むっての。あとは管理機能のサブルーチンを15コだ。
ログ読み出しの方のCGIは大したこと無いと思うし。あ、でも$ENV{"QUERY_STRING"}でl50とか200-300とかがメンドそう。
その辺は追々考えるとして。とりあえず目が痛くてどうしようもないんで、あんまり書けないです。超適当だけどこの辺で。