第1幕: 検分 ── 井戸端、つるべの点検
店の裏手、井戸のそばに立つと、いつもより湿った土の匂いがした。井戸は、この店が建った頃からある古いもので、屋根も囲いも無く、ただ石を組んだ縁と、木の滑車だけが据えられている。朝の光が、井戸の底のほうまで、まっすぐに差し込んでいた。滑車が軋む音がして、見ると、クロベさんとナギさんが井戸端でしゃがみ込んでいた。縄を交互に引いては戻し、また引いては戻ししている。桶は、外されていた。
「今日は、井戸のほうですか」
声をかけると、クロベさんは手を止めないまま答えた。
「桶が傷んできましたので。新しいものに替える前に、滑車と縄がちゃんと動くかどうかを、確かめています」
ナギさんが縄を引く手を止めずに続けた。
「桶を外してあるので、いくら引いても水は上がってきません。滑車の軋み具合と、縄の擦れ方だけを見ています」
見ていると、たしかに縄はするすると滑車を回り、軽い音を立てて上下するだけだった。毎回本物の水を汲んでいたら、この確認だけで一日仕事になってしまうだろう。桶を外すという判断は、素人の私にもすぐに飲み込めた。
「桶のほうは、もう傷んでいたんですか」
私が訊ねると、ナギさんが手を止めずに答えた。
「底のあたりが薄くなっていました。新しいのに替える前に、まず滑車と縄だけで、引っかかりが無いかを確かめておきたくて」
クロベさんは、縄を引く手を止め、滑車のあたりを見上げていた。軋み方に何かを聞き取っているようだったが、口には出さなかった。
「検めのときは、桶をつけずに縄を引くんですか」
見たとおりを、そのまま訊いた。ナギさんが、縄を引きながら答えた。
「はい。桶がついていたら、確かめたいのは滑車の動きだけなのに、毎回本当に水を汲むことになります。重いですし、時間もかかります。桶を外しておけば、身軽な状態で、機構そのものが正しく動くかどうかだけを見られます」
その言い方が、どこかで聞いた話と重なった。先だっての、表の格子戸の記帳の一件のとき、クロベさんが最後に言っていた言葉を思い出す。通る場所が本当に一つと言えるのは、いったいどんなときなのか。あのときは答えが出ないまま、野帳に書きかけの疑問として残していた。
「そういえば、今日は帳場のほうでも確かめたいものがあったんです」
私は井戸端に腰を下ろしながら、そのことを切り出した。
「配達伝票に、郵便番号から地域名を書き込む仕組みがあります。Shop::DeliverySlipという仕組みで、配達先の郵便番号を渡すと、地域名が返ってくるようになっています。今の作りを、確かめておきたいんです」
クロベさんが手を止めた。
「ちょうど、今やっていることと同じ理屈です」
それだけ言うと、また縄に手を戻した。詳しい説明はまだない。私は、それ以上は訊かずに、その言葉を胸のうちに置いておくことにした。
lib/Shop/DeliverySlip.pmを開いてもらうと、こういう形をしていた。
| |
クロベさんが、画面を見ながら言った。
「Postalという、郵便番号から地域名を引き当てる仕組みが、この店の外にあります。areaというところが、郵便番号を渡して、その仕組みへ問い合わせています。これは、配達の担当者が、いちいち手で地域名を書かずに済むようにしています」
「今も、ちゃんと動いているものなんですね」
「はい。ただ、確かめようとすると、そのたびに本当にPostalまで問い合わせに行くことになります」
クロベさんが、井戸のほうへ目をやった。
「検めるたびに、本当に外の郵便台帳まで問い合わせに行くわけにはいきません」
それきり、クロベさんは縄に手を戻した。まだ、どうすればいいのかは分からないままだった。
第2幕: 手が入らない ── もう一本の縄
つるべの点検はまだ続いていた。クロベさんが、主な縄の脇に手を伸ばした。
「……これは」
もう一本、別の縄が下りていた。井戸の縁の、石組みの隙間から出ている。細く、古びていたが、切れてはいない。主な縄よりも一回り細く、井戸の中ほどまでしか見えなかった。クロベさんがその縄を手に取ると、まだ湿っていた。今日、誰かがここで水を汲んだような手触りだった。
ナギさんも手を止め、その縄をのぞき込んだ。
「これ、何のための縄でしょう」
「分かりません」
クロベさんは短く答えた。普段なら、気に留めることもなかったはずの、ただの古い縄だった。
「使われていないはずの縄なんですが」
クロベさんはそう言ったきり、しばらく黙って縄を見つめていた。誰も気に留めていなかっただけで、日常的に使われ続けていた縄らしい。私は、それが何を意味するのか分からないまま、クロベさんの様子を見ていた。クロベさんは、その縄を井戸の縁に掛け直すと、「あとで」とだけ言って、いったんその話を置いた。
ナギさんが、端末を引き寄せた。
「郵便番号引き当てのほうは、前に習った要領で試してみていいですか」
先だっての、店の間で税率の継ぎ目を扱ったとき、Shop::Tax::rateをlocalで差し替えたやり方を覚えていたらしい。ナギさんは、その場でこう書いた。
| |
実行すると、画面にはこう出た。
| |
ナギさんの手が、止まった。
「……あれ。効いてるはずなんですが」
エラーは出ていない。ただ、期待していた「検め用の仮の地域」ではなく、本物の地域名がそのまま返ってきていた。ナギさんは、コードを見返し、local *Postal::lookupの綴りを指でなぞって確かめてから、もう一度同じものを実行した。結果は変わらなかった。
「書き方は、間違っていないと思うんですが」
何かが壊れているわけではない。ただ、望んだとおりに動いていないだけだった。ナギさんは、それ以上コードをいじらず、画面を見たまま動きを止めていた。
クロベさんが、井戸に掛けたままだった、あのもう一本の縄に目をやった。手に取ったまま、しばらく何も言わなかった。井戸の中を覗き込み、それから、ナギさんの端末の画面に目を戻す。二つを見比べるように、視線が行き来した。
「……なるほど」
クロベさんが、独り言のようにつぶやいてから、口を開いた。
「取り込んだ先には、もう一本、別の縄が下りています。さっきの井戸と同じです」
「取り込んだ先、というのは」
「Shop::DeliverySlipは、use Postal qw(lookup)という書き方で、Postalのlookupを取り込んでいます。取り込むと、Shop::DeliverySlipの側にも、lookupという名前の、自分だけの縄が下ります。取り込んだその瞬間の中身を、そのまま自分の側へ写し取るんです。あとで元のPostalのほうを差し替えても、写し取った側の縄には、何も伝わりません。local *Postal::lookupで差し替えているのは、Postal自身が持っている、本来の縄のほうです。Shop::DeliverySlipは、自分の縄のほうを使っていますから、そちらを差し替えない限り、本物のPostal::lookupのままです」
クロベさんの言葉を、頭の中で図にしてみる。Postalにある本物のlookupと、Shop::DeliverySlipが取り込んだときにできた、もう一本のコピーのlookup。localで差し替えられるのは前者だけで、areaが呼んでいるのは後者――そう描いてみると、さっきナギさんの手が止まった理由が、やっと絵として繋がった。

表の格子戸で記帳をまとめたときに、取り込んだ関数にはlocalが届かない、とクロベさんが言っていたのを思い出した。あのときは、まだ実感が伴っていなかった。今、目の前で本当に、望んでいた仮の値ではなく、本物の地域名が返ってきているのを見て、初めてその意味が分かった気がした。
「では、もう一本のほうを、直に替えればいいんじゃないですか」
私は、思いついたままを口にした。取り込んだ先に別の縄があるのなら、その縄の先を差し替えればいいはずだ。ナギさんが、少し驚いた顔でこちらを見た。
「それでもできます」
クロベさんが、静かに答えた。
「ただ……」
その先は言わずに、クロベさんは井戸の縁に置いた縄をもう一度手に取った。私には、まだその続きが見えなかった。
第3幕: 手立て ── 縄を引く動きは変えない
クロベさんが、Shop::DeliverySlipをもう一度開いた。指でなぞらず、目で追う所作だった。
「これは、郵便番号から地域名を引き当てて、伝票に埋めているだけです。配達の担当者が、いちいち手で地域名を書かずに済んでいます」
今の作りが何を支えているかを、一度言い切ってから、クロベさんは続けた。
「さっきの、取り込んだ先の縄を直に替える、というやり方ですが」
私の顔を見て言った。
「local *Shop::DeliverySlip::lookupのように、取り込んだ側の名前を名指しして差し替えることは、実際にできます。ただ、それだと、取り込んでいる場所が今はここ一つでも、増えるたびに、そのすべてを見つけて直さないといけません。どこに何本、縄が下りているか、毎回数え上げることになります」
その言葉に、ナギさんが小さくうなずいた。あの記帳の一件で、クロベさんが「数える方をやめる」と言ったのを、二人とも覚えているようだった。
「井戸のほうも、下りている縄が今は一本見つかっただけですが、探せばまだあるかもしれません。数え上げるやり方は、いつか同じ壁に当たります」
クロベさんは、そう言ってから続けた。
「今日は、数える代わりに、呼び方そのものを一つにまとめます」
クロベさんが、技法の名前を口にした。
「これを、呼び出しの抽出とオーバーライド(Extract and Override Call)と言います。問題の呼び出しを、自分のクラスのメソッドとして切り出します。検めのときは、そのメソッドだけを、別に用意したクラスで上書きします」
「メソッドを切り出す、というのは」
「areaの中に直接書いていたlookup($self->{zip})の呼び出しを、_lookup_zipという新しいメソッドに移します。areaのほうは、$self->_lookup_zip($self->{zip})と呼ぶだけにします」
「呼び方が、変わるんですか」
「はい。lookup(...)という呼び方は、どのlookupを呼ぶかが、取り込んだ時点で決まってしまいます。$self->_lookup_zip(...)という呼び方は、呼ぶたびに、$selfが実際にはどちらのクラスの品物かを見て、その場で行き先を決めます。だから、newのときに渡すクラス名を変えるだけで、同じareaの呼び出しでも、先が変わるんです」
クロベさんが、コードを書き換えていった。
| |
「newのほうは、何も変えていません」
たしかに、変わっているのはareaの中身と、新しく足された_lookup_zipだけだった。
「検めのときは、この_lookup_zipだけを、別のクラスで上書きします」
クロベさんが、@ISAという言葉を出した。
「Perlには、あるクラスが別のクラスを土台にする、という仕組みがあります。@ISAという配列に、土台にするクラスの名前を入れると、自分のクラスに無いメソッドは、土台のクラスのものがそのまま使われます。今はuse parent -norequireという書き方で登録するのが普通です」
ナギさんが端末に、検め用の新しいクラスを書き始めた。
| |
「TestDeliverySlipがShop::DeliverySlipを土台にすると宣言すれば、_lookup_zipを上書きしない限り、元のareaやnewはそのまま使えます」
クロベさんが、その-norequireを指して言った。
「これを付けないと、うまくいきません。Shop::DeliverySlipの部品を探す場所をlibと決めているのは、use lib 'lib'という一行です。それは、このファイルではもっと後――TestDeliverySlipのuse parentより後に書いてあります。use文は、書いた順番のとおりに実行されますから、-norequireが無いと、探す場所がまだlibに決まっていない段階で読み込もうとして、失敗します」
「独立した部品を持たないから、ではないんですね」
私が確認すると、クロベさんはうなずいた。
「独立していても、していなくても、書いた順番の問題です。今回は、たまたま先に書いているので、これが要ります」
「もし、_lookup_zipを上書きし忘れたら、どうなるんですか」
私が訊ねると、クロベさんが答えた。
「そのときは、TestDeliverySlipにはそのメソッドが無いので、土台にしているShop::DeliverySlipのほうの_lookup_zipが、そのまま使われます。本物のPostal::lookupを呼ぶほうです。書き忘れたことに気づかないまま検めても、意味のある結果にはなりません」
「間違えても、動いてしまうんですね」
「動きます。ですから、検めが本当に仮の値を確かめているかどうかは、その都度、結果を見て確かめる必要があります」
ナギさんが、手を動かしながら小さく言った。
「……さっきの、効かなかったやつより、こっちの方が分かりやすいです」
安堵でも得意げでもない、素直な感想のようだった。
コードを書き終えたところで、クロベさんが、井戸のほうへ目をやった。
「縄を引く動きは変えません。その先につながっているものだけを、検め用の別のものに替えるんです」
さっきナギさんが訊いていた、桶をつけない縄のことを思い出した。あれと、同じ理屈だった。areaが_lookup_zipを呼ぶという、呼び出しの形自体は、本番でも検めでも変わらない。変わるのは、その先で何が動くかだけだった。
「本番の帳場では、Shop::DeliverySlipをそのまま使います。検めのときだけ、TestDeliverySlipを使います」
クロベさんが、配線の行方を一言添えた。
「どちらを使うかは、newを呼ぶときに、どちらのクラス名を書くか――それだけの違いです」
井戸の縄と同じだ、と思いながら、私は今日見た二つの形を頭の中で並べてみた。前は、areaがPostal::lookupのコピーへ、迷いなく一直線に伸びていた。今は、areaから_lookup_zipの呼び出しへ進んだ先で、$selfが実際にはどちらのクラスの品物かによって、行き先が分かれる。縄を引く動きは同じでも、下ろす先が違う――クロベさんの言葉が、絵になって初めて腑に落ちた。

第4幕: 検め ── 見分けがつくということ
まず、今のままのShop::DeliverySlipの振る舞いを、書き取ることになった。
| |
動かすと、両方とも問題なく通った。今のareaが、本物のPostalへ問い合わせて、期待どおりの地域名を返している――ここまでは、まだ本物の郵便台帳を使っている状態だった。
次に、TestDeliverySlipを使った検めを書いた。
| |
動かすと、どちらも通った。
「なぜ、二つ確かめたんですか」
クロベさんが、画面を指しながら答えた。
「一つ目は、登録されている郵便番号を渡した場合です。これだけだと、たまたまPostalのデータテーブルにも同じ値があって、どちらが答えたのか区別が付きません。二つ目は、Postalのデータテーブルには載っていない郵便番号を渡しています。もし本物のPostal::lookupが呼ばれていたら、『対応地域なし』という値が返るはずです」
「それが、『別の検め用の地域』のまま返ってきている、ということは」
「TestDeliverySlipの_lookup_zipだけが動いていて、Shop::DeliverySlipの_lookup_zip――Postal::lookupを呼ぶほう――は、実行の経路にすら乗っていない、ということです」
「さっきの、望んだとおりに動かなかったのとは、違うんですね」
私が言うと、クロベさんがうなずいた。
「はい。さっきは、差し替えたつもりでも、呼び出しの経路自体は変わっていませんでした。今回は、TestDeliverySlipを使う時点で、呼び出しの経路そのものが別物になっています」
「見分けるというのは……名前だけでは分からない、ということですか」
私は、井戸のほうを見ながら訊いた。
「はい。同じ名前で呼んでいても、取り込んだ先が別なら、そこはもう別の縄です。名前でなく、どこにつながっているかを見てください」
言われてみると、これまでも似たようなことがあった気がした。店の間の一件では、継ぎ目がどこにあるかを見分けた。表の格子戸の一件では、通る場所が一つかどうかを見分けようとして、まだ言葉にできずにいた。今日のは、その延長にあるようで、少し違った。通る場所の数ではなく、通っている名前の先に、本当は何がつながっているか――それを見分けるという話だった。
クロベさんが、続けて言った。
「今日、確かめられたのはここまでです。TestDeliverySlipを使う限り、検めのたびにPostal::lookup――外部への本物の問い合わせ――を実行せずに済みます。Shop::DeliverySlipのnewやareaという、外から見える形は変えていませんから、本番の呼び出し元は、何も変える必要がありません」
「では、これで安心なんですね」
「今回、確かめたのは_lookup_zipという、一つの入口だけです。もし将来、別の場所でもuse Postal qw(lookup)のように取り込む箇所が増えたら、そこにもまた、同じやり方を使うことになります。今日の一手が、この店の郵便台帳への問い合わせを、すべて片付けたわけではありません」
「検め用のクラスを、あちこちで作ることにはならないんですか」
私が訊ねると、クロベさんは少し間を置いてから答えた。
「なります。上書きするメソッドが増えるほど、どこが本物で、どこが検め用の置き換えなのか、後から見て分かりにくくなります。今日は_lookup_zip一つだけですから、まだ見通せます。増えすぎたときは、また別の手を考える必要があります」
「地域名を使って、配達のルートや送料をどう決めるかは」
「今日は、地域名を安全に検められるところまでです」
その線引きに、私はうなずいた。
野帳を開き、今日の内容を書き取った。書く前に、一度、井戸のほうを振り返った。もう一本の縄が、静かに掛かっているのが見えた。
「外に繋がっているものと、繋がっていないものを見分けられる」
そう書いてから、私は野帳を閉じた。
井戸端では、クロベさんとナギさんが、点検を終えた縄に、新しい桶を取り付け直す作業に戻っていた。滑車が軋み、縄がゆっくりと下りていく。桶が水面に触れる音がして、やがてゆっくりと引き上げられていった。桶いっぱいの水が、朝の光を受けて揺れていた。もう一本の細い縄は、変わらず井戸の縁に掛かったままだった。今日はそれ以上、誰もその縄には触れなかった。
いつもと変わらない井戸の水音のはずなのに、今日はそれが、ただの水音には聞こえなかった。同じ音でも、どこから届いているかを一度見分けてしまうと、もう元の聞こえ方には戻らないらしかった。
普請控
- 見立て: 配達伝票
Shop::DeliverySlipが、配達先の郵便番号から地域名をPostal::lookupという外部の仕組みへ問い合わせて埋めていた。検めのためにlocal *Postal::lookupを差し替えても、use Postal qw(lookup)で取り込んだShop::DeliverySlip側には届かず、検めのたびに本物の問い合わせが飛んでいた - 手を入れた所: 呼び出しの抽出とオーバーライド(Extract and Override Call)を使い、
Postal::lookupの呼び出しをShop::DeliverySlip::_lookup_zipというメソッドへ切り出した。検め用には_lookup_zipだけを上書きしたTestDeliverySlipサブクラス(use parent -norequire)を使う - 触っていない所:
Postal.pmは一行も変更していない。Shop::DeliverySlipのnew・areaという外から見える形も変更していない(本番の呼び出し元は無変更) - 次の工程へ送ること:
use Postal qw(lookup)のように取り込む箇所が今後増えた場合、そのすべてに同じ手(メソッド抽出とサブクラス化)を個別に適用する必要がある。今日の一手で、この店の外部依存すべてが片付いたわけではない - 私が引き受けたこと: 外に繋がっているものと、繋がっていないものを見分けられる
