はじめに:1日で動いた。しかし、それで終わりではなかった

キーを正しい順序で送りました。IMEの状態も、直前に確認していました。それでも、最初の一文字だけが変換されず、ローマ字のまま入力欄に残りました。

最初に疑ったのは、キー配列を変換するロジックそのものでした。しかし、ログを追っても、変換表にもタイミング判定にも誤りは見つかりませんでした。おかしいのはロジックの中身ではなく、ロジックが前提にしていた「いまIMEはこういう状態のはずだ」という思い込みの方でした。

さらに厄介だったのは、同じ手順を繰り返しても、必ず再現するわけではなかったことです。ある回では正しく変換され、次の回では最初の一文字だけが化けます。原因が入力の順序になければ、残るのは「そのとき何が起きていたかを、こちらが正しく把握できていなかった」という可能性でした。この種の不具合は一度きりではなく、似た症状が違う原因で何度も形を変えて現れました。

これは、Windows用の日本語入力エンジン「awase」(NICOLA親指シフト方式)を開発しているときに起きた出来事です。awaseは著者が個人で、千六百を超えるコミットをかけて作り続けているソフトウェアです。最初の動くプロトタイプは、開発を始めたその日のうちに完成しました。しかし、その後の道のりは、1日で動いたことからは想像できないほど長いものになりました。

本書は、その長い道のりで繰り返し出会った「一見同じに見える不具合」を、症状・仮説・実験・発見・設計というひとまとまりの物語として記録したものです。理想的な設計を最初から思いついていた話ではありません。壊れ方を見て、そのつど設計を発見し直してきた記録です。

変換器ではなく、相手のいるソフトウェアだった

awaseは、見た目にはキー配列を別のキー配列へ変換するだけの、単純なフィルタのように見えます。しかし実際には、awase自身が制御できない相手を常に観測し続けるソフトウェアでした。

相手とは、Windowsが管理するIMEの状態であり、フォーカスがどのウィンドウにあるかであり、他のアプリケーションが送ってくる非同期のイベント(こちらの都合を待たずに届く通知)です。これらはどれも、awaseに問い合わせても正直に教えてくれるとは限りません。ときには古い状態を返し、ときには一度返した答えを覆しました。

具体的にはIMEの開閉状態、変換モードの向き、フォーカス中のウィンドウといった情報です。awaseはこれらを直接読み出せる立場になく、断片的な信号から推測するしかありませんでした。推測の元になる信号自体が、遅れて届いたり、順序が入れ替わったりしました。

Windowsは、IMEとやり取りするための仕組みを公式に二つ用意しています。古くからあるIMM32は、IMEのON/OFF状態や変換モードをアプリケーション側から尋ねたり指示したりするためのAPI(プログラム同士が情報をやり取りする窓口)群です。より新しいTSFは、IMEだけでなく手書き入力や音声入力も含め、アプリケーションと入力サービスの間を仲介する仕組みです。アプリによって、IMM32とTSFのどちらを使ってIMEとやり取りしているかは異なります。awaseにとっては、どちらの仕組みを使っても事情は変わりませんでした。仕組みを通じて何らかの値は返ってきます。しかし、その値が「いま」を表しているのか、「少し前」を表しているのかは、仕組み自体からは分かりませんでした。

この構造は、日本語入力という領域に固有のものではありません。協調してくれない外部プロセスを、一方向のキー送信という操作と断片的な観測だけで制御しようとする場面があります。この場面は、GUIの自動化ツールにも、分散システムのクライアントにも、外界をセンサーで認識するロボットにも共通しています。awaseで起きた不具合の多くは、日本語入力の癖ではなく、この共通構造から生まれていました。

この経験から、本書を貫く一つの命題が生まれました。

外の世界を相手にするソフトウェアでは、観測したことと、いま実行してよいことを分けなければならない。

観測は、常に少し過去のものです。実行は、常にいまこの瞬間に対して行われます。この二つを同じ値として扱ったとき、awaseは何度も壊れました。そして、この二つを型として分けたときにだけ、壊れ方が減りました。

観測と実行を分けるまでの記録

本書に登場する事件や数値は、記憶だけを頼りに書き起こしたものではありません。コミット・ADR(設計判断の記録)・当時のコードそのものを一次資料として、そこから再構成しています。

調査の過程で、著者自身の当初の記憶と、記録に残っていた事実がずれている箇所もいくつか見つかりました。そうした箇所は、本文中で「当時のADRにはこう書かれていた」「現在振り返るとこう見える」というように、記録に基づく記述と著者の解釈とを書き分けています。

各章は、症状・仮説・実験・発見・設計という一つの流れで完結させています。前の章で使った道具をそのまま使い回せる場面もあれば、通用しなかった場面もあります。どちらも、そのまま記録しています。

読者がこの本から得られるのは、完成した設計の説明書ではありません。ある不具合がなぜ起きたのか、最初の仮説がなぜ外れたのか、次にどんな実験をしたのか、という試行錯誤の順序そのものです。設計は、その順序の先にしか現れませんでした。

日本語入力エンジンの内部実装に詳しくなくても、本書は読み進められるように書いています。必要な前提知識は、話が必要とする箇所でそのつど説明します。読者に求めているのは、外部システムを相手にするソフトウェアを書いた経験、あるいはこれから書く関心の方です。

なお、この「観測と実行を分ける」という問題は、日本語入力エンジンだけのものではありませんでした。本書の終章では、著者が別に取り組んでいるロボット向けランタイムの設計で、同じ構造の空白に再び出会った経緯を扱います。

まずは、最初の一文字がローマ字のまま残った、あの日の話から始めます。