凍りついた検索台
僕はテオ。アカデミーの麓町にある、小さな公共蔵書庫で司書をしている。仕事はいたって地味だ。届いた本に請求コードを振り、書架の正しい場所へ収め、訪ねてきた人の求める一冊を探し出して手渡す。派手なところは何ひとつない。それでも、ばらばらに見える蔵書がきちんと目録の上で整列して、誰かの「あの本が読みたい」に応えられたときの心地よさを、僕はひそかに誇りにしている。
その日々を支えているのが、受付に据えた検索台だった。利用者から請求コードを聞き、台にそれを唱えると、その本が蔵書庫のどこにあるかが浮かび上がる。先代の司書から引き継いだ、古い検索呪文だ。蔵書が少なかった昔は、探せば必ず棚にある、というのが当たり前だった。だから呪文も、本は「必ず在る」前提で組まれていた。
ところが、蔵書も利用者も増えた今は、事情が違う。貸出中だったり、そもそも蔵書していなかったりで、「探したけれど見つからない」ことが、日に何度も起きる。
その日も、ひとりの利用者が一冊を求めて来た。あいにく、うちには無い本だった。僕がいつものように請求コードを検索台に通したとたん——台の魔力が、すうっと空(くう)へ吸い込まれるように落ち、浮かびかけた光が消え、台はそのまま凍りついた。うんともすんとも言わない。
しかも、まずいことに、巻き添えがあった。後ろに並んでいた別の利用者の検索まで、同じ台が握っていたものだから、まとめて止まってしまったのだ。受付に小さな列ができ、僕はその前で立ち往生した。本を届けるのが仕事なのに、その入り口で詰まっている。
無い本を、一度探しただけだ。なのに、どうして検索台ごと黙り込む——。在る本ならちゃんと出せる。けれど、棚に無い本を一度引くと、検索の魔法そのものが不発になって凍りつく。その晩、僕は閉館後の暗い書庫で、長いこと考え込んでいた。これは、僕の手に負える種類の不調ではない気がした。
噂を頼りに、丘をのぼる
ひとつ、心当たりがあった。
少し前、常連の利用者が世間話のついでにこう言っていた。丘の上のアカデミーに腕のいい若い見習いがいて、先頃、街のある店の暴れていた会計呪文を見事に鎮めたらしい、と。そのときは聞き流していたが、今になって思い出した。藁にもすがる、というほど切羽詰まってはいない。ただ、その噂を頼りに、僕は検索呪文を写し取った符を抱えて、丘をのぼった。
アカデミーの工房は、天井まで本棚が届き、見たこともない魔導書がぎっしりと詰まっていた。商売人ならきっと気後れする眺めだろう。けれど僕は、本と棚に囲まれているというだけで、むしろ少し落ち着いた。勝手が違うのは魔術であって、本棚ではない。背表紙の並びを眺めているうちに、自然と肩の力が抜けた。
出てきたのは、ずいぶん若い見習いだった。ルーク、と名乗る。噂に聞いた「腕のいい見習い」を、僕は勝手に、もっと年嵩(としかさ)の落ち着いた魔術師だと思い描いていたらしい。目の前のルークは、握った杖もどこか手に馴染みきっていない様子で、思い描いていた像とはずいぶん違う。これが、その見習いか——気を取り直し、僕は検索呪文の符を見せ、症状を語った。棚に無い本を引くと検索台が凍ること。巻き添えで、ほかの利用者まで取りこぼしてしまったこと。
ルークは符を手に取り、上から順に目で追いながら、まず蔵書庫の「もの」の決まりを教えてくれた。
| |
Book が一冊の本で、表題(title)と著者(author)を持つ。ルークによれば、頭についた data class は中身の比較などを最初から備えた“データ向けのクラス”で、丸括弧の中に値を並べて書くと、受け取ったものをそのまま持ち物にできるのだという。Catalog が蔵書庫で、抱えている books: Map<String, Book> は「請求コード(文字列)から本を引くための対応表」だという(Map<キーの型, 値の型> で、何から何を引く表かを表す)。その中の fun find(code) = books[code] にある = は、波括弧と return を省いて式ひとつをそのまま答えにする短い書き方だ。請求コードを渡せば、その本を探して返してくれる。魔術の文法は不慣れだが、こうして一つずつ教われば、僕にも分かる。
問題は、find という探す呪文の「戻り」の形だった。ルークはその一点を指した。
「テオさん。よく見ると、この戻りの型、Book? と書いてありますね。うしろに小さな ? が付いている。これは『探した本は、空(から)かもしれない』という印なんです。つまりこの蔵書庫は、最初から正直に『見つからないこともある』と言っている」
空かもしれない、と型そのものに書いてある。言われてみれば、当たり前のことだ。棚に無い本はある。
「……この検索は、本が必ず在る前提で組まれているんですね。棚に無いときのことが、抜けている」。ルークのその見立ては、僕が暗がりで薄々感じていたことと、ぴたり同じだった。
全部、結界で囲ってしまえば
ところが、符を順に追っていたルークが、不意に顔を上げた。何かを思いついたらしく、その表情がぱっと華やいだのが、僕にも見てとれた。
「テオさん、お任せください。不発が起きなければいいわけですから——いっそ全部、結界で囲ってしまいましょう!」
そう言うと、僕が止める間もなく、彼は、新しく身につけた守りの魔法をよほど使ってみたかったのだろう、検索台のひとつひとつの呪文に、透明な膜のような結界を、幾重にもかぶせ始めた。
| |
ルークが、ひとつずつ説明してくれた。try で囲んだ中で何か失敗が起きても、catch がそれを受け止めるので、台ごと凍りつくことはなくなる。受け止めたときは空文字 "" を返す——try … catch のひとかたまりが、そのまま答えの文字列になる書き方だそうだ(catch (_: Exception) の _ は、受け取った失敗を中では使わない、という印)。表題を引く呪文にも、著者を引く呪文にも、貸出を帳面(ledger)に記す呪文にも、ひとつ残らず同じ結界をかぶせる。
ひとつ、引っかかる印があった。catalog.find(code) のうしろの !! だ。ルークは少しばつが悪そうに、これは先代が遺したものだと言った。!! は「これは絶対に空じゃない」と言い張って、?(空かもしれぬ印)を黙らせる印なのだという。先代は「本は必ず在る」と信じていたから、空かもしれない戻りを、この !! で握りつぶして「在るもの」として扱っていた。だから空が来たとき、台はその場で不発していた——それが、凍りつきの正体だった。空(から)の値を本物のように触ろうとして起きるこの不発を、魔術師は NullPointerException(魔力空無、略してNPE。空=null の値を本物として扱おうとした瞬間に起きる不発)と呼ぶらしい。
「この結界なら、たとえ空無に落ちても、結界が受け止めます。台は、もう凍りません」とルークは胸を張った。確かに、試してみると、もう凍らない。
——そのとき、窓ぎわの揺り椅子のほうで、ことり、と音がした。見ると、湯気の立つカップを手にした白髪の老人が、薄目を開けてこちらを眺めている。「ほう……片端から、覆うてしまうか」。ひとりごとのような、低い呟きだった。ルークには聞こえなかったのか、満足げな横顔のままだ。僕も、書斎の主が寝ぼけて呟いたくらいに思って、気に留めなかった。老人は、また静かに目を閉じてしまった。
無いのか、壊れたのか
けれど、しばらく使ううちに、僕は妙な居心地の悪さを覚えはじめた。
無い本を引いても、検索台は凍らない。それはいい。代わりに、ただ「しん」と静まって、何も書かれていない空白を返してくる。「該当なし」とすら言わない。ただの、空白だ。
試しに、僕はわざと出鱈目な——空っぽの請求コードを、結界を張ったその検索台(コードでは guarded)に通してみた。すると、それも、まったく同じ空白で返ってきた。
| |
僕は、ぞっとした。
棚に無いだけの本も、そもそも問いとして壊れている要求も、結界はぜんぶ同じ沈黙で呑み込んでしまう。区別が、つかない。僕は思わず尋ねた。
「ルークさん。これは……無いから空白なんですか? それとも、どこか壊れていて空白なんですか?」
ルークの手が、止まった。
「えっと、それは……結界が受け止めたので……どちらか、は……」
彼にも、もう分からなくなっていた。結界は、失敗を受け止めると同時に、その失敗が何だったのかという手がかりまで、まとめて握りつぶしていたのだ。
司書として、これがいちばんこたえた。凍りついて止まるほうが、まだましだったかもしれない。あれは少なくとも「ここに異常がある」と叫んでいた。今のこれは、何事もなかったような顔で、嘘の静けさを返してくる。目録が黙って嘘をつく——僕にとって、これほど怖いことはない。利用者に「ありません」と告げるなら、せめてそれが本当に無いからだと、胸を張って言いたい。
眠っていたはずの老人
そのときだった。
書斎の窓ぎわの揺り椅子で、さっき寝ぼけた呟きを漏らしたあの白髪の老人が、今度ははっきりと両目を開けた。ハーブティーの湯気の向こうから、低く、ゆったりとした声がかけられた。ルークが弾かれたように背筋を伸ばし、「ぼ、僕の師匠の……マグナスです」と早口に紹介した。揺り椅子の主は、ただ昼寝をしている書斎の住人などではなかったらしい。
マグナスさまは、すぐには答えを言わなかった。代わりに、こちらへひとつ、問いを置いた。
「司書どのに、ひとつ訊こうかの。求められた本が、うちの棚に無いとする。さて——黙ってその場で固まってしまう目録と、『うちには無い』と正直に告げる目録。どちらが、人の役に立つかね?」
僕は、迷わなかった。
「……無いと、正直に告げるほうです。黙って固まられたら、無いのか壊れたのか、何も分かりませんから」
それは、司書なら考えるまでもないことだった。マグナスさまは「ふむ」と頷き、それから、ゆっくりとルークのほうへ視線を移した(ように見えた)。
工房が、しんと静まった。やがてルークが、ちいさく息を吸い込む。何かに思い当たった——そんな顔つきだった。
「……そうか。空(から)になるのを、隠しちゃいけないんだ。最初から『この検索は空かもしれない』と正直に認めて、空だったときに何を返すかを、ちゃんと決めておけばいい。そうすれば、慌てて結界で握りつぶさなくて済む」
マグナスさまは、それきり何も足さなかった。ただハーブティーを一口含んで、また静かに目を閉じる。答えそのものは、ついぞ口にしないままだった。気づきにたどり着いたのは、ルーク自身だった。
「空かもしれぬ」を、型に刻む
ルークは、検索台を覆っていた幾重もの結界を、端から手早く剥がしていった。台が、元の素っ気ない姿に戻る。そして彼は、まず一枚の符——蔵書庫から本を引く、あの戻りの符を、僕に見せた。
「さっきも言いましたが、この符は最初から正直なんです。戻りが Book?、つまり『空かもしれない』と書いてある。先代はそれを !! で握りつぶしていた。だから、握りつぶすのをやめて、空かもしれないものを、空かもしれないまま、丁寧に扱えばいいんです」
彼は、その書き方を見せてくれた。蔵書庫(catalog)から請求コード(code)で本を引き、あれば貸出の帳面(ledger)に記す——その一連を、空かもしれないまま素直に書くと、こうなるという。
| |
見慣れない印が、いくつか出てきた。ルークが一つずつ、ほどいてくれる。
?. は安全呼び出しといって、左が空でなければ呼び出し、空ならそこで止まって空を返す。!! のように言い張らないから、空が来ても不発しない。?: はエルビス演算子といって、左が空だったら、右の値を代わりに使う。だから book?.title ?: "(該当なし)" は、「本があれば表題を、無ければ『(該当なし)』を」という意味になる。最後の ?.let { ... } は、左が空でなければ波括弧の中の処理をする書き方で、中の it は、その「空でなかった値」——つまり見つかった本そのものを指す。
「これで、空が来ても凍りません。無ければ無いで、ちゃんと『(該当なし)』と言える」
なるほど、と思った。けれど、ここでルーク自身が、ふと手を止めた。少し顔をしかめている。
「……でも。これだと、表題を出すところ、著者を出すところ、貸出を記すところ。空かどうかの確かめが、使う場所ごとに、いちいち散らばってしまう」
言われて符をよく見ると、確かに、同じ ?. の印が、あちらにもこちらにも、繰り返し現れていた。本を扱う箇所が増えるたびに、この「空だったらどうするか」の確かめを、また書き足さなければならない。僕は、自分の蔵書庫を思った。あちこちの帳面に同じ注意書きを書いて回るようなものだ。書き忘れたら、そこだけ穴になる。
該当なしの書
ルークは、しばらく散らばった符を眺めて、黙っていた。やがて、自分で答えにたどり着いたようだった。
「そうか。『空のときにどう振る舞うか』を、使う場所ごとに書くから、散らかるんだ。それを一冊の“書”にまとめて、最初から決めておけばいい。該当なしのときの『何もしない』を、まるごと引き受ける書を、一冊」
彼はまず、本の見せ方の「約束」を一枚、決めた。
| |
これは インターフェース(中身の実装ではなく、「こう呼べばこう応える」という形だけを取り決めた設計図)だという。表題を訊かれたら答える、著者を訊かれたら答える、貸出を頼まれたら帳面に記す。その三つに応える、という形だけを約束している。中で何をするかは、これを守る側が決めればいい。
そして、その約束を守る書を、二種類こしらえた。見つかった本の書と、見つからなかったときの書だ。
| |
FoundBook と NotFoundBook の名前のうしろに付いた : SearchResult は、「さっきの約束(インターフェース)を、この書が守ります」という宣言だという。そして各メソッドの頭にある override は、「その約束を、自分のやり方で果たします」という宣言なのだそうだ。FoundBook は本物の本を抱えていて、表題も著者もちゃんと答え、貸出も帳面に記す。
僕の目を引いたのは、もう一方だった。NotFoundBook——いわば「該当なしの書」。表題を訊かれれば「(該当なし)」と答え、著者を訊かれれば「(不明)」と答える。そして貸出を頼まれても、安全に、何もしない。無い本は貸し出せないのだから、何もしないのが正しい。何もしないことを、ちゃんと仕事として引き受ける書なのだ。
「該当なしの書は、最初からこの世にひとつあれば足ります。だから object(最初からただ一つだけ作られる実体。シングルトンともいう)で作っておきます」とルーク。
この「本物と同じ形をした、何もしない安全な実体」を当てて、空のときの振る舞いを一箇所にまとめてしまう定石を、Null Object(本物と同じインターフェースを持つ「何もしない実体」で、不在のときの振る舞いを一箇所に集約し、呼び出す側から空の確かめを無くすパターン)と呼ぶのだと、ルークは教えてくれた。
最後に、彼は検索台そのものを組み直した。
| |
蔵書庫から引いた「空かもしれぬ」戻りを、?.let で——見つかれば FoundBook に包み、?: で——空なら「該当なしの書」に。
「こうすると、検索台が返すのは、もう絶対に空じゃありません。必ず、どちらかの“書”です。だから、これを受け取る側は、空かどうかをいちいち気にせず、ただ同じように訊けばいい」
| |
なるほど、と僕はうなった。さっきまであちこちに散らばっていた ?. の確かめが、受け取る側からは、きれいに消えていた。「空だったらどうするか」は、もう「該当なしの書」が一手に引き受けている。
ルークは、机の上に広げられた羊皮紙に、杖の先で光の魔力を走らせた。宙に浮かび上がったのは、今しがた彼が組み上げた新しい魔導式の構造だった。
「テオさん、この魔導式のつながりを見てください」

「検索台(SearchDesk)は、本が見つかろうと見つかるまいと、ただ『約束された書(SearchResult)』を返すことだけを決めればいいんです。本があれば実体のある『見つかった書(FoundBook)』を、無ければ何もしない『該当なしの書(NotFoundBook)』を包んで返します。受け取る側は、それがどちらかを知らなくても、ただ同じように呼び出せば、それぞれの書が自分の役割を果たしてくれます」
光の図表を見つめながら、僕は頭の中の霧が晴れていくのを感じていた。複雑な条件分岐の鎖が、二つの「書」の振る舞いの中に、きれいに吸い込まれて消えている。
ひとつだけ、見慣れない見張りが台の入り口に増えていた。require だ。ルークが補う。「require は、決めた条件を満たさなければ、その場で声を上げて——IllegalArgumentException(引数が不正だ、という例外)を投げて——止める見張りです。空っぽの請求コードみたいに、そもそも問いとして壊れているものは、握りつぶさずに、ちゃんと声を上げて弾きます」
無い本を、「無い」と言える
「組み上げたら、いきなり本番ではなく、まず結界の中で確かめます」
ルークはそう言って、模擬詠唱に取りかかった。本番の蔵書庫に放つ前に、閉じた結界の中で何度も唱え、狙いどおりに働くかを見ておくのだという。今度は僕の手元でも追えるよう、ひとつずつ結果を読み上げながら進めてくれた。
| |
蔵書庫には、ためしに「A-001」という請求コードの本を一冊だけ入れてある("A-001" to Book(...) の to は、コードと本を一組にする書き方だ)。在る本は、これまで通り、ちゃんと表題が出る。棚に無い本は、もう凍らず、「(該当なし)」と静かに返ってくる。その本の貸出を頼んでも、帳面は一行も増えない——無い本は貸し出せないのだから、何もしないのが正しい。assertEquals は「答えが思ったとおりか」を、assertFailsWith は「狙いどおりに、ちゃんと失敗するか」を確かめる道具だそうだ。最後の一つでは、わざと空っぽのコードを通すと、今度は黙って呑み込まず、ちゃんと声を上げて弾かれた。
組み直された検索台を、僕は受け取った。
無い本を引いても、もう凍らない。代わりに、台は静かに、はっきりと「(該当なし)」と告げる。当たり前のことのようで、これがどれほど安心か。無い本を、ちゃんと「無い」と言える。司書にとって、目録が正直であることほど、大事なことはない。
ルークが、要点を一言でまとめてくれた。「空かもしれないことを、隠さず型に書いておく。空だったときにどう振る舞うかは、“該当なしの書”に一度だけ決めておく。だから、もうどこでも握りつぶさなくていいんです」。これが Null安全(空〔null〕かもしれない値を型ではっきり示し、空のまま扱うことを許さずに、扱う前の対処を促す仕組み)と、Null Objectの合わせ技なのだという。
いつ「無い」で済ませ、いつ声を上げるか
帰り支度をしながら、僕はひとつ、気になっていたことを尋ねた。
「何でもこの“該当なしの書”にしてしまえば、安全なんでしょうか」
ルークは、首を横に振った。
「いいえ。表題をひとつ出すだけなら、?: で『(該当なし)』と書けば、それで十分なんです。わざわざ書を仕立てなくていい。“該当なしの書”が値打ちを出すのは、空のときにも“振る舞い”が要るとき——今回みたいに、表題も、著者も、貸出も、空のときの作法をぜんぶ一箇所に決めたいときだけです」
そして彼は、少し決まり悪そうに、さっき自分が検索台に張り巡らせた、あの幾重もの結界のことを思い出したようだった。
「何でも囲んで、空にしてしまえばいい——というのは。……さっきの僕の、ちょうど逆ですね」
無いものは、無いと静かに受ける。けれど、壊れた問いには、ちゃんと声を上げる。その二つは、似ているようで、まるで別のことだ。何もかも同じ沈黙で握りつぶしてしまっては、いけなかった。ルークは、自分の失敗を回り道にして、そのことを掴んだらしかった。
ルークが、今日の学びを魔導書に書きつけている。揺り椅子のマグナスさまは、ハーブティーをもう一口含んで、ひとことだけ、静かに言い置いた。
「空(から)であることは、隠すべき疵(きず)ではない。隠したとき、はじめて疵になるのだよ」
僕は、その言葉を胸の中で繰り返した。蔵書庫に「無い本」があるのは、疵ではない。無いものを、無いと正直に告げられない目録——それこそが、疵だったのだ。
整え直された検索台を抱えて、僕は丘を下りた。明日からも、棚に無い本を訊かれることはあるだろう。けれど、もう怖くない。台は凍らず、「ありません」と、はっきり、正直に告げてくれる。本を届けるのも、無いものを無いと告げるのも、どちらも司書の、大切な仕事だ。今は、そう思える。
📖 見習い魔術師ルークの魔導書メモ(Luke’s Spellbook Notes)
- 修復した呪文(クラス/モジュール名):
SearchDesk(検索台)とSearchResult一族(FoundBookと、該当なしの書NotFoundBook) - 乱れの要因(アンチパターンの課題):
- 先代の検索呪文が「本は必ず在る」前提で、空かもしれぬ戻り(
Book?)を!!で握りつぶしていた。該当なしのとき、魔力空無(NullPointerException)でその場で不発し、検索台が凍りついて、巻き添えに他の検索まで止めた。
- 先代の検索呪文が「本は必ず在る」前提で、空かもしれぬ戻り(
- 過剰設計の反省(背伸びの過ち):
- 「とにかく囲めば安全」と、全呪文を多重の
try-catchで包んで例外を握りつぶした。台の凍りつきは止まったが、正当な「該当なし」と、壊れた要求(空コード)が、同じ空白にされてしまい、「無いのか、壊れているのか」が誰にも分からない沈黙にすり替わった。隠すことは、直すことではなかった。
- 「とにかく囲めば安全」と、全呪文を多重の
- 魔術の定石(学んだ設計パターン):
- Null安全とNull Object。
Catalog.findのBook?(空かもしれぬ印)を握りつぶさず、?./?:で安全な実体へ変換した。該当なしの「何もしない振る舞い」はNotFoundBook(objectのシングルトン)一箇所に集約し、呼び出す側から空の確かめを無くした。一方、壊れた入力はrequireで声を上げて弾き、正当な不在(Null Object)とはっきり区別した。
- Null安全とNull Object。
- 模擬詠唱の記録(検証のポイント):
- 該当なしで台が凍らず「(該当なし)」を返すこと、その本の貸出が例外でなく安全に何もしないこと、空コードは
requireで声を上げて弾かれること(assertFailsWith)を確認した。見つかる本の表題・著者は、Before から After まで一貫して変えていない(直したのは「不在の扱い方」だけ)。
- 該当なしで台が凍らず「(該当なし)」を返すこと、その本の貸出が例外でなく安全に何もしないこと、空コードは
- 師匠マグナスの格言:
- 「空(から)であることは、隠すべき疵(きず)ではない。隠したとき、はじめて疵になるのだよ。」
