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

フィンローダのあっぱれご意見番 第161回「読むためのコスト」

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

1988年に始まったプログラマーズフォーラムは、 @nifty 以外の場に引っ越すことになった。 しかし、この原稿を書いている時点では、まだどこに移転するか決まっていない。 しばらく放浪することになるかもしれないが、それも一興ではある。

 

※ 2006年9月30日に@niftyにおけるサービス終了ということになった。

コミュニケーションツールとしての掲示板がどうあるべきか、 という所でいろいろ考えてみると、 結局、今の掲示板システムに絶対的に不足しているのは、 未読管理という処理なのだと思う。

  

昔のパソコン通信時代に積極的に活動していた人達は、 どのような使い方をしていただろうか。 今とは違って、通信速度は遅いし、料金も高い。 今でいえば、携帯電話を使っているようなもので、 うっかりすると毎月数万円の請求書が来てしまう。

 

※ 携帯電話の料金が毎月数万円、 という利用者が大勢いた。 そのような課金体系だったのだが、 最近(2006年)は、 パケット通信が定額だったり、 メールを使い放題、というような課金方法も増えてきた。

そこで登場したのがいわゆる巡回ソフトと呼ばれるものだった。 あらかじめ指定しておいたスケジュール通りにBBSをアクセスして、 未読発言だけをファイルに保存する。 手作業だと10分かかる作業が2、3分程度で済むので、料金節約にもなるし、 ユーザーはこのファイルをプリントして、 通勤の時に読むとか、そういった使い方ができたものだ。

 

※ 実際にプリントして読んだものだが、 私の場合、当時プリントしたものは、今では全く読めない。 プリンタが感熱式だったからである。

巡回ソフトが未読発言だけをgetする仕組みは簡単である。 そのような機能は、基本的にパソコン通信サービス側に用意されていたからだ。 機能から想像すれば、その仕組みは驚くほど単純なものだ。 最後に読んだ時刻を保存しているだけである。 ユーザーが「未読だけ表示」というコマンドを実行すると、 システムは前回表示した時刻以後に書かれたものだけを表示する。 なぜそのような実装が想像できるかというと、 途中のコンテンツを無視して最新のものだけ表示したときに、 無視したものも全て既読扱いになってしまったからである。 個々の投稿の未読・既読を管理していれば、このようなことは起こらないはずだ。

 

※ もちろん、ユーザー毎に、 表示したコンテンツのリストを持っていれば完璧な未読管理ができるが、 そのためには当時としては絶望的なリソースが必要だった。

しかし、今あるごく普通の Web サイトとか、ブログとか、Web掲示板とか、 その種のサービスに未読管理機能はない。 そんな機能は不要だという人もいる。 スタイルがネットサーフィンには馴染まないという説もある。 ゆくっり読んでも料金が増えることもないし、 適当にクリックしてサイトを斜め読みすればいいじゃないか。

システム側にも言い分はある。 誰がどこまで読んだか覚えておくためには、 ユーザーごとの現状を管理するシステムが必要になる。 そのためには、ログインの処理も必要になる。 個人情報を扱うとなると、 もっと怖いのは、個人情報保護法だ。 といった感じで問題がいろいろあるせいだろうか、 システムとして未読管理を提供するサービスはあまり見かけない。 ただし、そういうサービスがないわけでもない。 例えばメールだ。 メールごとの既読情報はメールソフト側でも管理しているが、 もう一つ重要なのは、サーバー側にも未読管理をする機能がある、ということだ。 実際、POP3 だとこのあたりの実装が微妙なのだが、 メールはもともと利用者1名ずつを管理しなければならないから、 未読管理も可能になるわけである。

  

もちろん、最近はメールを大量にやりとりする人も多いから、 読んだメールは削除するように設定することを推奨しているプロバイダも多いのだが、 読んだメールを削除しない設定にしても、 とりあえず、メールソフトは、サーバーに接続するときに、 まだgetしていないメールだけをサーバーから読み出してくれる。

 

※ GMail のように、 読んだメールを削除しない ことを推奨しているサービスもある。

§

実は、未読管理機能というのは、 Webが爆発的に広まりつつある情報過多時代の今こそ必須の機能ではないだろうか。。 前回読んだ内容を毎回見せられるのは意味がないことだし、 見せる側も意味がないと思うのだ。 もしかして、 今のWebの利用者は、単に未読管理機能がないから使えないし、 その便利さとか必要性が想像できないだけではないだろうか。 例えば、ブラウザに「更新されたところだけ表示」、または強調表示という機能があったら、 非常に多くのユーザーがそれを使うのではないか。 もちろん、私の知る限り、今あるブラウザに「更新されたところだけ表示」という機能はない。しかし、前回見たときのページをキャッシュで持っていれば、 更新されたところだけ表示するのは、そんなに大それたことではないはずである。

読んだデータを手元に保存してよいなら、 どこまで読んだかという情報は、クライアント側でも管理できる。 その情報をクライアントで持っていて、 サーバーに指定時刻以後の情報だけを表示する機能があれば、未読管理は実現できるのだ。 となると、問題はサーバー側に必要な、その機能ということになるが…。

§

  

プログラム言語フォーラムに「■えるC言語」という企画会議室があって、 ほとんど3か月にわたって、戯言というか漫才みたいなのを投稿していた。 現時点でどこにあるのか何ともいえないのだが、 私が書いた内容は別のサイトで公開する予定なので、 検索サイトでうまく探してみてほしい。

 

※ 最初は「萌える…」という名前だったのだが、 「萌える…」系の名称が登録商標になっているという事実に恐怖れをなして 「■える」という名称に変更したのである。

その企画会議室で出た話題が、コストを見積もるという話である 非常に有名な例なのだが、N×Nの行列を単位行列になるように初期化する処理で、、 List 1 のように片付けるアルゴリズムがある。 説明に不要な処理は略したので、想像して読んで欲しい。

---- List 1 (単位行列の初期化) ----

    for (i = 0; i < N; i++) {
        for (j = 0; j < N; j++) {
            array[i][j] = 0;
        }

        array[i][i] = 1;
    }

---- List 1 end ----

これはつまり、最初に全部0で埋めてから、必要なところだけ1にするというアイデアだ。 これに対して、 List 2 のような方法が、 誰でもまず思いつくものだ。

---- List 2 (別のやり方で初期化) ----

    for (i = 0; i < N; i++) {
        for (j = 0; j < N; j++) {
            if (i == j) {
                array[i][j] = 1;
            } else {
                array[i][j] = 0;
            }
        }
    }

---- List 2 end ----

これは「■えるC言語」では、よくない例として紹介した。 どこが良くないのかというのが、それがコストの話なのである。 細かい話だが、どうせ書くのだったら、 List 3 の方がよいような気もする。

---- List 3 (初期化-よくない例) ----

    for (i = 0; i < N; i++) {
        for (j = 0; j < N; j++) {
            if (i != j) {
                array[i][j] = 0;
            } else {
                array[i][j] = 1;
            }
        }
    }

---- List 3 end ----

List 2 と List 3 の違いは、このコラムでも何度か話題になっているが、 本線をどちらに選択するか、という話だ。 「本線」ということで、道路をイメージして欲しい。 一本道の本線に、バイパスのような感じで分岐線を追加する。 バイパスを通る方が、 何回も右折・左折しないといけないので、 手間がかかる、というか、遅くなる。 つまり、よく通る方を本線にしておいた方が、 最終的には速度がよくなるだろう、ということだ。 なお、本当にその書き方の方が速くなるという保証はないので、ご注意を。

§

さて、ここまでは有名な話なので、 教科書にも出てくるのではないかと思う。 この後、例の掲示板には、 さりげなく次のような処理を書いてみた。

---- List 4 (2つに分けて初期化する - 1) ----

    for (i = 0; i < N; i++) {
        for (j = 0; j < i; j++) {
            array[i][j] = 0;
        }

        array[i][j] = 1;

        for (j++ ; j < N; j++) {
            array[i][j] = 0;
        }
    }

---- List 4 end ----

これはいいのか、それともよくないのか、というのが問題となる。

他の例に比べると、ちょっと分かりにくいかもしれない。 簡単にいえば、1行(それとも1列?)ごとに処理する過程を、 前半の0を入れる処理、1を入れる処理、後半の0を入れる処理、 の3つのパートに分けた、というだけのことだ。

List 1 が List 2 より優れているのは、比較のコストを節約しているからである。 List 4 は、コストだけ考えると、List 1 と同程度だ。 しかし、掲示板では、List 4 は「あまりよくない」と評価した。 その理由は可読性である。 読むためのコストが気になったのだ。

ループの中に、 array[i][j] = 0; という処理が2回出てくる。 特に気に入らないのはそこだ。 例えば0ではなく-1で初期化したい、というときに、 片方だけ修正してしまい、バグの原因になるのではないか、 掲示板に書いた説明は、そういった、殆ど言いがかりのような話だが、 だったら、List 5 のような書き方もある。

---- List 5 (2つに分けて初期化する - 2) ----

    for (i = 0; i < N; i++) {
        for (j = 0; j < i; j++) {
            array[i][j] = 0;
        }

        for (j++ ; j < N; j++) {
            array[i][j] = 0;
        }

        array[i][i] = 1;
    }

---- List 5 end ----

代入する処理を近づけることで、 見落としをできるだけ避けるという効果を狙ったのだが、 こうなると、List 1 に酷似してくる。 違いは、 array[i][i] の箇所を 0 で埋めてから上書きするか、 それとも、あくまで何も書かないか、という一点だ。 そうなると、1つだけ上書きする程度の無駄は誤差の範囲内で済むから、 List 1 のように書いた方がいい、という発想も出てくる。 List 1 のシンプルさには、凄味のようなものがあって、 プログラマーが自力でそこにたどり着くのは、なかなか難しいような気がするが、 最初から知っていたら書くのは簡単なことだし、 覚えていれば損はないと思う。

  

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

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

(C) Phinloda 2005, 2006 All rights reserved.