Home日記コラム書評HintsLinks自己紹介
 

フィンローダのあっぱれご意見番 第118回「甘い見積もり」

← 前のをみる | 「フィンローダのあっぱれご意見番」一覧 | 次のをみる →

1月頭にBフレッツの申し込みをしたら、 開通まで1か月位かかるという話だったので、 今回のネタに間に合うかと思ったのだが、 全然連絡がない。 こちらから問い合わせたら、 予定の「1か月」の2日前になってやっと「3月末になります」という返事だった。 NTT的に1か月が80日だというのは別に構わないのだが、 そういう状況が予定の2日前になるまで分からないというのは不思議な話だ。

回線速度が向上したらトライしたいことがいろいろある。 自宅にサーバを置いている話は以前にも紹介したが、 フレッツISDNだと64kbpsなので、 ちょっと大きなデータとかは、実用的に使えないのが実情である。 特に、動画とか大きな画像が入ると決定的にダメ。 もっとも、モバイル環境の側も徹底的に遅いので、 サーバが速くなってもしょうがないのだが。

§

  

どこからでもmy computer をアクセスできるという環境は流行するだろうか、 将来、それが自然なライフスタイルの一部になるのかもしれない。 ただ、現実にはまだまだ厳しい。 自宅のhttpサーバがどこからでも見えるというような環境は、 割と簡単に構築できるし、個人的には既にかなり近い環境になっている。 もちろん、セキュリティという大問題があるから、 あまり簡単に実装してしまうと後でヤバいことになるかもしれないのだが。

 

※ spam メールの踏み台になる、というのがかなり怖い。

ところが、自宅のサーバに他からファイルをアップロードしようとすると、 これが途端に面倒だったりする。 メールを使うような裏技はともかくとして、 ftpとか使えば簡単かと思うだろうが、 実は自宅の環境ではftpのポートが閉鎖してあるので、 外からアクセスすることができない。

 

※ もちろん、セキュリティ的に danger だから封鎖してあるのだ。 現状では scp が使えるように設定してある。

httpで外からサーバが見えるのだから、 httpを使ってファイルを転送することはできるはずである。 というわけで、 httpプロトコルで転送されてきたファイルを受け取る servlet を書くことにした。 本を読んだら JavaMail を使う方法が出ている。 なぜ http なのに JavaMail? と思ったが、 要するにMIMEのmultipart の処理が面倒だからJavaMailにやらせよう、 という話らしい。 これは名案だと思う。 自分で全部やったらどれ位面倒なのか、 試しにやってみたら、やっぱり結構大変だということが分かった。

 

※ 今だと、MyFaces を使って FileUpload の機能を使うと結構楽かもしれない。

§

その実際書いてみた時の話なのだが。 fig.1 のような形式の文字列を検出する処理を書いた。 いわゆるデリミタの処理である。

---- fig. 1 デリミタ ----

  -----------------------------744226831232

---- fig.1 end ----
  

要するに、「-」が連続して出現して、その後に数字が続くという形式だ。 といった程度の文字列を判断するのは簡単だろうと思うでしょ。 実際、簡単だ。 何も考えずに書いたら、 List 1 のようなコードになった。 最近、パッと見て何の言語で書いてあるのか、 自分としては分かりにくくなってきたような気がするのだが、 これは一応、Java である。

 

※ これはこれで構わないのだが、 XML 化しようとして、さらにコメントアウトしたいときに、 とんでもない副作用に悩まされることがある。

---- List 1 ----

    int i = 0;

    while (i < s.length()) {
      char c = s.charAt(i++);
      if (c != '-') {
        if ((c < '0') || (c > '9'))
          break;
        while (i < s.length()) {
          c = s.charAt(i++);
          if ((c < '0') || (c > '9'))
            break;
        }
        break;
      }
    }

    return (i == s.length());

---- List 1 end ----
 

※ ええ、もちろん正規表現使えば一発ですとも。

厳密には要求仕様に従っていないような気がするが、 細かいことは気にしないことにして、 それにしても何かコードがダブりまくっているのが美しくない。

動いているプログラムは触るな」という格言もあるが、 これはもう少しマシになるように書き直したいと思う。 最近は「リファクタリング」という言葉も広まってきたようで、 こういう時には 最適化とか修正ではなく、 「リファクタリング」と言えば何かカッコいい。 本質的にちょっと意味が違うけど、気にしない。 もっとも、これだけ短いコードだと、 リファクタリングも何もないだろうという考え方もあるが、 「美しくない」と断言できるコードを放置するというのが面白くないのだ。

しかし、どう変更すればエレガントになるだろうか。 暫く考えた結果、 List 2 のようになった。

---- List 2 ----

    int i = 0;

    while (i < s.length()) {
      char c = s.charAt(i++);
      if (c != '-') {
        i--;
        while (i < s.length()) {
          c = s.charAt(i++);
          if ((c < '0') || (c > '9'))
            break;
        }
        break;
      }
    }

    return (i == s.length());

---- List 2 end ----

ポイントは、「i--;」という処理である。 これでコード上のダブりがかなり減る。 特に、読み込んだ文字が数字かどうかの判定が1個所になったのがよい。 しかし、切り替わりの所の1文字だけ、charAt を2回呼び出している。 これがどうも気になる。 もっとも、根本的な問題としては、 実は「i--」というのは何かキザな感じがして、あまり気に入ってないのだ。

ところが、ここから先がどうも進めないわけで、 最終的には抜本的な改革をすることで List 3 のようになった。

---- List 3 ----

    char c;
    int i = 0;

    while (i < s.length() && s.charAt(i) == '-')
      i++;

    while (i < s.length() && (c = s.charAt(i)) >= '0' && c <= '9')
      i++;

    return (i == s.length());

---- List 3 end ----

こりゃもう全然違うという感じで、 個人的には、リファクタリングというと、 少しずつちまちまと修正していく印象があるのだが… まあいいでしょう。 これが最終型の一歩手前である。 実はもう一押ししたという話を後でします。

List 3 のポイントは、while の判定個所に何もかも詰め込み、 ただし、i++ だけを中に入れたという所だ。 つまり、i++ という処理をループの中に埋め込むことで、 条件が成立しなかったときに実行せずに済ませようというのだ。 そうすれば、 後で1を引くようなヘンなことをしなくても済むだろう、 という、一応論理的思考の結果なのである。

最初に出てきた数字を s.charAt で2回取り出しているというのは変わりない。 この一歩はどうしても越えることができなかった。 構造的には、charAt を一箇所にして、 状態をフラグで持たせりすれば簡単に解決する。 例えば、List 4 みたいな感じだ。

---- List 4 ----

    int i = 0;

    while (i < s.length() && part_of_delimiter(s.charAt(i))
      i++;

    return (i == s.length());

  // ここは別関数
  part_of_delimiter(char c) {
    if (mode == CHECK_MINUS) { // mode はどこかで定義・初期化が必要
      if (c == '-')
        return true;
      mode = CHECK_NUMERIC;
    }
    return (c >= '0' && c <= '9');
  }

---- List 4 end ----

これはこれで見やすいし、明快だと思うのだが、 たかがデリミタの判定でここまでやると、 かえって逆効果だと思うのである。 charAtという処理が無茶苦茶重いならともかく、 1文字を処理するごとに判定の関数を呼び出すのも何だし、 しかも mode の判定という余計な処理がループの内部に追加されてしまうし、 そもそも mode って何? という感じだから、 これは却下するしかないだろう。 というわけで、List 3 あたりが落としどころかと考えた。

§

ところで、もう一押ししたというのは何かというと、数の判定の順序だ。 紹介したリストは、'0'と比較してから'9'と比較している。 実は、 使ったコードでは、 最初に'9'と比較して、その後に'0'と比較しているのだ。 何故?

普通、こういう判定は小さい方から比較するものである。 というか、大きい方から比較するコードなんてのは見た記憶がない。 あまり見たことのない書き方をすると、バグを誘引する原因にもなるのでよくない、 というのが定説である。 では、それを承知であえて '9' から比較した理由は。 文字コードの偏りを考えると、 '0'や'9'というは、比較的小さい値に割り振られている。 失敗する場合を想像すれば、 '0'より小さい文字よりも、'9'より大きい文字の方が圧倒的に多いのではないか。 だったら、'9'からチェックした方が、 最初の比較で失敗してくれる確率が高くなって、無駄が少ないのでは、と考えたのだ。

実際、これは当たっている。 どの位違うかと思って調べたら、 ほとんどが'9'の比較でひっかかってしまって、 '0'と比較することがないということが分かった。

ところが、実際の処理でこの比較が行われる回数は、 殆ど1回である、というような現実も分かってしまったのだ。 それは、 ファイルを転送する時にどういうヘッダーが来るかを考えてみれば分かる。 つまり、理論は正しかったが、 現実的に期待した程の性能向上は得られないのだ。 もっとも、性能が悪化している訳ではないし、 もっと一般的なケースなら、'9'から比較した方が有利になる可能性は高い。

実際に走らせてみて性能評価することは重要だといわれているが、 まさに痛感したというか、無駄骨折った気分だ。 もっとも、もう少し用途に即したシミュレーションを頭の中でやれば、 このような結論は最初に出せたような気がする。 と思えば、やはり見積もりが甘かったことになるのかもしれない。

回線が100Mbpsになったら何ができるか、 ということも想像し直した方がいいような気がしてきた。

  

(C MAGAZINE 2002年4月号掲載)
内容は雑誌に掲載されたものと異なることがあります。

修正情報:
2006-03-03 裏ページに転載。

(C) Phinloda 2002-2006, All rights reserved.