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

フィンローダのあっぱれご意見番 第155回「一度全部読んでから細かく調べる」

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

@niftyのパソコン通信形式のフォーラムのサービスが終了した。 サービスが終わった理由は、 要するに利用者が減ったのだ。 それが理由なのは間違いないが、 なぜ利用者が減ったのか、そこには重要な問題が隠れている。

  

文字だけのコミュニケーションはもう時代遅れだ、という人もいる。 大きな誤解だ。 実際、SNS にしても、2ちゃんねるにしても、 文字のみのコミュニケーションは、以前にもまして、 非常に活発なのである。

 

※ SNS: Social Networking Service か? mixi のようなサービスといった方が分かりやすいかもしれない。 もちろん、mixi は画像を扱えるが、単に文字だけの掲示板として使われているコミュニティが非常に多い。

メールにしても、 大きなサイズの画像や音声データを添付できる環境が整ってきた。 しかし、私に来るメールの殆どは単に文字だけのメールだし、 文字だけで情報交換する、メーリングリストというシステムが今も健在なのである。 考えてみれば、メーリングリストは、 今のようにWeb が普及する前からあったわけで、 それが今でも健在だということは興味深い。

 

※ メーリングリストはインターネットのメールが使えるようになった頃から存在した。

フォーラムがパソコン通信形式から Web 形式に移転したら、 参加していた皆さんも引っ越してくれるかというと、 まず殆どは来なくなる。 一番大きな理由は、 今までの環境でアクセスできないからである。 つまり、見たくても見ることができないのだ。 別のソフトウェアに乗り換えるには、 大変なエネルギーを要することもあるのだ。

 

※ 使い慣れたUIが継続して使えたら、移転といってもユーザーから見た様子はあまり変わらないで済む。 そういえば、NIFTY MANAGER は Web フォーラム対応していましたっけ?

それに、別の操作に慣れろというのなら、 わざわざ@niftyでなくても、他のサイトを見てみよう、 という話になるのも当然の流れだ。 Webフォーラムのユーザーインターフェースが劇的に改良されているとか、 まさに誰でも一撃で使えるとか、そういう設計になっていればいいのだが、 まあとにかく現実は無茶苦茶厳しいということだけ一言いっておく。

 

※ 私の周囲の人達が特殊なのかもしれないが、 Web化して使い辛くなったという声が大半である。 というか全員そう言っている。

そういえば、よく質問されるのが、 Web形式のフォーラムは未読管理できるのか、という話。

 

※ ここで「できない」と答えたら、 その人は金輪際Webフォーラムに来てくれないので、 「今開発中の専用ソフトを使うと、未読管理もできるし、 その他いろいろ便利なことが…という感じでごまかすのがよい。」

ニフティサーブのフォーラムの何が便利だったかというと、 読んだ発言(投稿)と、そうでない発言が、明確に区別されていたことだ。 しかも、 未読発言だけを表示する機能があって、 それをファイルに保存することができた。 つまり、アクセスする毎に、 未読発言を手元のパソコンのファイルに追加していけば、 自動的に、該当フォーラムの全ての発言が、 1つのファイルにまとまるのである。 後はプリントでもして、ゆっくり読めば、読み残すこともない。

未読処理というのは、 メールで言えば、読んでないメールだけパソコンに保存して、 サーバーのメールボックスからは削除するという感じで、 かなり当たり前の処理である。 この当たり前の処理が、 ブログとかWebサイトが相手だと、なぜかなかなか難しい。

大抵、その種のサイトは、ユーザー管理をしていない。 サーバーが未読管理の処理を持つとすれば、 単純に考えると、サーバーが、ユーザーが前回どこまで読んだかを記録しておく必要がある。 そのためには、 ユーザー毎に情報を持つ必要がある。 これはかなり大変な処理なのだ。 ただ、その大変なことを、@niftyのフォーラムは実行していたのである。

  

簡略な方法として、最後にアクセスした時刻だけをキーにして、 それ以降に追加されたコンテンツだけを表示する、という実装がある。 この場合、前回読み残していたものは、既に読んだことになるのが弱点だが、 とりあえずクッキーを使って時刻を受け取り、 それを元にしてページを作れば済む。 また、 ブログにしろ掲示板にしろ、 記事単位でIDを割り振るような実装は割と定番である。 ということは、IDを指定して、それより新しいものだけ表示、 というような機能でもいい。 このような方法だと、 ユーザー管理しなくて済むのがメリットである。

 

※ 発言ごとにURLを固定で割り当てておいて、 ブラウザのキャッシュを利用して未読の判断をするという手がある。 ただし、そのためにはアクセスしたサイトのリンクの表示色を適切に指定する必要がある。

細かい未読管理をしたいなら、 むしろ、クライアントで処理する方が簡単である。 コンテンツは、多少ダブって取ってきても構わない。 極端な話、毎回、全部ページを読んでもいい。 前回保存したのと異なっているところだけを抽出すれば、 未読管理は実現できる。 トラフィックとしては無駄かもしれないが、 それを人間が読んでから気付いて読み飛ばすよりは、 ずっとマシだ。 人間が無駄な処理をするか、コンピュータにやってもらうか、 その違いだけである。

§

ふ「うっかり“また次回に”と書いてしまったので、 前回書ききれなかった残りの2つの方法を紹介しましょう。」

U「まず、 “処理中に後戻りできるような仕組みを用意する” ですが、 これはC言語でいえば、 getc に対応する ungetc を用意する、といった話だと思っていいのでしょうか?」

ふ「その通りです。 前回は、パラグラフを1つ先読みすることで、 次にどんな種類のパラグラフが来るのか分かるような処理を用意できました。 先読みができないと、 次にどんなパラグラフが来るのか分からないのですが、 とりあえず本文が来ると仮定して処理してしまおう、という発想です。」

U「具体的には?

ふ「前回と同じように、 パラグラフを次々と読み込んで出力していく処理があるとします。 ここで問題になるのは、どういう場合でしたっけ?」

---- List 1 (パラグラフを単純に順次処理する) ----

    while ((p = getNextParagraph()) != null) {
        if (p.isBody()) {
            addBody(p); 
        else {
            /* (1) */
            addNote(p);
        }
    }

U「2つ以上本文が続いた後で注釈が現れたら、 最後の本文だけ別の行にしなければならない、 という規則に対応することです。」

ぶ「先読み法だと、 次に注釈が来るかどうかを判断する処理を用意しました。 今回は、最後に追加した本文を取り戻す、という処理を用意します。」

U「つまり、 /* (1) */ の部分に、 その前にaddBody(p) を2回以上連続で呼び出していたら、 本文を1つ取り戻して、空の注釈を追加して本文を別行にする、 というような処理を追加すればよいのですね。」

ふ「そういった処理が既に全部あるとすれば、 どんな感じになりますか?

U「List 2 みたいな?」

---- List 2 (次が注釈だと戻る) ----

    int bodyCounter = 0;
    while ((p = getNextParagraph()) != null) {
        if (p.isBody()) {
            addBody(p);
            bodyCounter++;
        else {
            if (bodyCounter > 1) {
                Paragraph prev = unaddBody();
                feedCurrentLine(); // 改行する
                addBody(prev);
            }
            addNote(p);
            bodyCounter = 0;
        }
    }

ふ「unaddBody というのは名前的にいまいちですが、 とりあえず気持ちは分かるので吉とします。

§

U「もう一つのやり方は割と気になるのですが…」

  

ふ「先読みしなくてもいいように全体を前処理してから、もう一度全体を処理しな おす。」

 

※ Cマガジン掲載時には「前処理」ではなく「仮処理」と書いていた。

U「前処理というのは?」

ふ「コンパイラっぽい表現を用いるなら、 2パスで処理するということです。 最初の処理では一体何をすればいいでしょうか?」

U「(1) 注釈、(2) 注釈、(3) 本文、(4) 注釈、(5) 本文、(6) 本文、(7) 本文、(8) 注釈、(9) 本文、の順にパラグラフが出現した場合に、 先読みしなくても処理できればいいのですね?」

ふ「まあそういうことです。」

U「だったら、結論を先にいえば、 (6) 本文と、(7)本文、の間に、空の注釈があればいいのでは。」

ふ「そうですが、 そのように処理するのは結構難しいというか、 今までやってきたアプローチとあまり変わらないことになってしまいます。」

U「なるほど、何かうまい方法がありますか?」

ふ「次に何が来るか、ということは、次のパラグラフを読めば分かりますよね?」

U「そりゃそうですね。」

ふ「だったら、次に何が来るのかという情報が、最初からあれば先読みしなくて済むでしょう。そのような情報を1回目の処理で追加してしまうのです。」

---- List 3 (パラグラフの情報を追加する) ----

    while ((p = getNextParagraph()) != null) {
        if (p.isBody()) {
            addBodyMark();
            addBody(p); 
        else {
            addNoteMark();
            addNote(p);
        }
    }

ふ「addBodyMark という処理は、 次に来るのが本文だという情報を、 どんな方法でもいいですが、出力する、という感じです。 例えばパラグラフに拘るなら、 <p>※次本文</p> みたいなものを特殊扱いしてもいいし、 タグの属性を使うとか、 いろいろ方法はあると思いますが、 結果的に、どういう出力ができるでしょう?

U「(n) の前に追加するデータを(n') という番号で表現することにしますと、 (1') 次注釈、 (1) 注釈、 (2') 次注釈、 (2) 注釈、 (3') 次本文、 (3) 本文、 (4') 注釈、 (4) 注釈、 (5') 次本文、 (5) 本文、 (6') 次本文、 (6) 本文、 (7') 次本文、 (7) 本文、 (8') 注釈、 (8) 注釈、 (9') 次本文、 (9) 本文、 ということになりますね。」

ふ「では、このシーケンスを想定して、 2回目のパスはどのような処理にすればいいか考えてください。

U「これは簡単ですね、 次は何なのかを getNextType で得ることができるとします。」

---- List 4 2パス目の処理 ----

    currentType = getNextType(); // 最初だけ特別
    while ((p = getParagraph()) != null) {
        nextType = getNextType(); // 次のパラグラフの種類
        if (currentType.isBody()) {
            addBody(p, nextType);
        else {
            addNote(p, nextType);
        }
    }

    private void addBody(Paragraph p, ParagraphType type) {
        if (isPrevBody()) { // 直前が本文なら、
            if (type.isNote()) { // 次が注釈なら、
                feedCurrentLine(); // 改行する
            }
        } else if (isPrevNote()) { // 直前が注釈なら
            feedCurrentLine(); // 改行する
        }

        addParagraph(p);
    }

U「しかし、これでは前回の処理とあまり変わらないですね。

ふ「そう言われてみるとそうなのですが、 getNextParagraph という処理が消えました。」

U「なるほど。 しかし、これは先読みしなくて済むというよりも、 逆に、全部先読みしておいてからもう一度処理する、 という感じがするのですが?」

  

ふ「まあそういうことです。 次に何が来るか分からないと処理できないという根本的な所はどうしようもないので、 それをどのように料理するか、という違いしかないのです。」

 

※ 現実的には、ここから処理を追加・変更する場合に大きな違いになってくることがある。

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

修正情報:
2005-05-16 注釈追加版を公開。
2006-03-01 裏ページに転載。

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