第1幕: 検分 ── 店の間、まだ切っていない溝
朝、店の間には、もう鉋屑の匂いが満ちていた。表と奥をつなぐこの部屋には、間を仕切る引き戸が立っている。開け閉てのたびに、どこかで木がこすれて引っかかる。もう何年もそうだったと店主は言っていたが、今日ようやく手が入ることになった。
クロベさんは鴨居の溝に指を這わせ、埃を払いながら、溝の続く先を目で追っていた。ナギさんは外した板戸を横に立てかけ、木口の反りを確かめている。二人とも、私が近づいたことにまだ気づいていない様子だった。作業の手を止めさせるのは気が引けたが、今日はもう一つ、別の用件を持ってきていた。
私は野帳を手に、その脇に立った。声をかけるまで、いつもより少し間があった。帳場から出てくるのでも、通り庭へ移動するのでもない。誰かが手を動かしている最中に、こちらから割り込む形は、これまでになかった。
「クロベさん。作業中にすみません」
クロベさんが指を止め、こちらを向いた。
「店主から、また別の頼まれごとを聞いています」
「なんでしょう」
「消費税が、また変わるかもしれない、と。もし本当に変わったとき、値札の計算がちゃんとその通りに動くか、変わる前のうちに確かめておきたい、と言っていました」
まだ何も起きていない。起きるかどうかも分からない。それでも確かめておきたい、というのが今日の頼まれごとだった。二十年前にも一度、税率が変わったことがあったと、店主から聞いたことがある。あのときは値札の貼り替えが間に合わず、しばらくレジと値札の数字が食い違ったままだったらしい。棚の品に古い値札と新しい値札が入り混じり、レジを打つたびに店主が電卓で確かめ直していた、という話だった。今度は、同じ騒ぎを繰り返したくない、ということなのだろう。建具の立て付けとは違って、税率は待ってはくれない。変わると決まってから慌てて確かめるのでは、遅い。
「税率が変わっていないのに、確かめることなんてできるんですか」
私が訊いた。まだ起きていないことを、どうやって確かめるのか、見当がつかなかった。
クロベさんは、溝から指を離し、こちらへ向き直った。
「できます」
短い返事だった。それから、ナギさんに目をやった。
「PriceTag.pmを開いてください」
ナギさんが道具を置き、帳場の隅に据えられた作業机の画面に向かった。立て付けの悪い建具は、そのまま溝に立てかけられたままになっている。まだ直っていない。けれど、今日はこちらが先らしかった。私も後を追い、画面の見える位置に立った。
第2幕: 手が入らない ── ナギの誤手
| |
画面には、そう長くないコードが表示されていた。値札に載せる税込みの価格を、calc_priceという一つの関数で計算している。Shop::Tax::rate()という呼び出しが、その中に一行だけあった。
「税率は、ここで決まっているんですね」
私が訊いた。
「はい。Shop::Taxという、別の場所に置かれたrateという名前の関数が、今の税率を返します」
ナギさんが、画面から目を離さずに言った。
「じゃあ、これを確かめるには、税率を渡せるようにすればいいんですよね」
言うが早いか、ナギさんの手はもう動いていた。指がキーボードの上を迷いなく滑る。今まで見てきた中でも、いちばん躊躇のない手の動きだった。
| |
calc_priceに、税率を受け取る引数を足す。渡さなければ今まで通りShop::Tax::rate()を使う、という形だった。話の道筋としては、おかしなところは無いように思えた。
「待ってください」
クロベさんが言った。ナギさんの手が、キーボードの上で止まった。宙に浮いたままの指先が、次の一打を待っているように見えた。
「でも、これで動くはずです。引数を足すだけです」
ナギさんが言った。声には、まだ迷いがなかった。
「動きはします。ですが、それは今のcalc_priceを書き換えた、ということです」
クロベさんが静かに言った。声を荒げたわけではなかった。それでも、いつもより一段低く、区切るような話し方だった。私は、二人のやり取りを見ながら、どちらに何を言えばいいのか分からずにいた。ナギさんの手を止めさせたのは自分の頼みごとのはずなのに、口を挟む隙が見つからなかった。
「でも、既定値を用意しています。渡さなければ、今まで通りShop::Tax::rate()を使います。今の呼び出しは、どこも変わらないはずです」
ナギさんが、食い下がった。声にはまだ、迷いのなさが残っていた。
「今の呼び出しは変わりません。ですが、確かめたいのはcalc_priceが正しく動くかどうかのはずです。そのcalc_price自体に、今、手を入れようとしています」
クロベさんが言った。
「今日はcalc_priceを疑いたいわけではありません。疑いたいのは、税率が変わったときの振る舞いです。ですが、そのcalc_priceを書き換えてから確かめると、通ったとしても、疑いたかったものが正しいのか、書き換えが正しかったのか、切り分けられなくなります」
クロベさんが続けた。calc_priceを呼んでいるのは、この一箇所だけではない。値札の表示、レジの表示、注文の控え——店の中の何箇所からも、calc_price($base_price)という形で呼ばれている。既定値があるうちは、そのどれも動きを変えない。それでも、確かめる対象そのものに手を加えた、という事実は残る。
「……そうですけど、他に方法があるんですか」
ナギさんの声から、迷いのなさが消えていた。反論の材料が、そこで尽きたようだった。
私は、ナギさんの書きかけの一行を見た。壊れてはいない。動きもする。それでも、クロベさんは「書き換えた」と言った。確かめたい相手と、確かめる道具を、同じ場所に置いてはいけない——そういうことらしかった。
「今日確かめたいのは、税率が変わってもcalc_priceがちゃんと動くか、でした」
クロベさんが言った。
「書き換えずに確かめられるかどうか、というのが、その中身のはずです」
ナギさんの手が、キーボードから完全に離れた。書きかけのコードは、画面にそのまま残っていた。誰も、次にどうすればいいのか、まだ言えなかった。店の間から、木材が擦れる小さな音が、続いていることを思い出した。作業は、まだ途中のままだった。
「では、どうすれば」
私が訊いた。答えは、すぐには返ってこなかった。
第3幕: 手立て ── 建具の溝、パッケージの境界
「継ぎ目、という言葉があります」
クロベさんが、画面から目を離さずに言った。
「その場所を編集しなくても、振る舞いを変えられる場所のことです。今、書き換えずに確かめたいなら、まず探すべきなのは、そういう場所です」
「そんな場所が、どこかにあるんですか」
ナギさんが訊いた。
「あるかどうかを、見てもらったほうが早いです。こちらへ」
クロベさんが立ち上がり、店の間へ戻った。私もナギさんも、後についていった。立てかけられたままの板戸に手をかけ、両手でそっと持ち上げる。鴨居と敷居、それぞれの溝から、板戸がすっと抜けた。切った跡も、削った跡も無い。木肌には、何年分もの手垢と艶があるだけだった。クロベさんはそのまま、また溝へ戻す。木がこすれる、乾いた小さな音がした。
「見ていてください」
それだけ言って、クロベさんはもう一度板戸を持ち上げ、外し、戻した。二度目は、私も動きを目で追った。編集しなくても変えられる場所、という今聞いた言葉と、目の前の板戸が、まだ私の中では結びついていなかった。
ナギさんが、その様子を見て言った。
「……それ、さっき私たちが直そうとしていた所ですよね。切ったりしなくても、外れるんですか」
「切らなくても、もう外せる場所でした」
クロベさんが溝から手を離した。
「もう、外せる所だったんですか」
私は思わず言っていた。立て付けを直すというから、てっきり削るか、新しく仕口を切り直すのだと思っていた。溝は、最初からそこにあった。誰かが、外せるように作っておいたものだった。今聞いたばかりの「継ぎ目」という言葉が、目の前の光景と、ようやく一つに重なった。
クロベさんは帳場へ戻り、画面の前に座った。ナギさんの書きかけの一行は、まだそのままだった。
「今のと、同じことが、ここにもあります」
クロベさんがShop::Tax::rate()という呼び出しを指した。
「Shop::Tax::という、よその場所を指す名前が、ここに書いてあります。前回、$Toriniwa::pending_itemという書き方を見ましたね。あれは変数でしたが、今回は同じことが関数に対しても起きています」
「よその入れ物を指す書き方が、関数にも使えるということですか」
私が訊いた。
「そうです。さっきの板戸と同じで、この呼び出しも、もとから外側に開かれています。今、calc_priceの中身は一切変えていません。それでも、このShop::Tax::rate()という呼び出しの先を、テストの間だけ差し替えることができます」
「シンボルテーブルを触るなんて、危なそうな操作に見えます」
ナギさんが、少し身構えるように言った。
「何か、制限を外さないといけないんじゃないですか」
「思うのは自然です。ですが、今回はそれが要りません。文字列でなく、そのまま名前を書いているからです」
クロベさんが答えた。
「strict 'refs'が制限しているのは、文字列から名前を組み立てて辿る書き方——たとえば*{"Shop::Tax::rate"}のように、パッケージ名や関数名を文字列で作ってから触る場合です。今回は*Shop::Tax::rateと、そのままソースコードに書いています。文字列は使っていません」
「なぜ、文字列だと引っかかって、そのまま書けば引っかからないんですか」
ナギさんが訊いた。
「Perlは、パッケージごとに『シンボルテーブル』という名前の一覧表を持っています。rateという名前は、そこに『型グロブ』という形で登録されています。スカラーや配列、そして今回使う関数の実体まで、一つの名前の下にまとめて束ねられたものです」
クロベさんが、*Shop::Tax::rateという書き方を紙に書いた。
「先頭の*が、型グロブを指す印です。今回、私たちが渡すのは関数の実体(sub {...})なので、この束の中の、関数の実体だけが差し替わります。スカラーや配列の場所は、そのままです」
クロベさんの紙には、*Shop::Tax::rateという文字の下に、四角い枠が四つ並んでいた。スカラー、配列、ハッシュ、そして関数——一つの名前の下に、まとめて束ねられた入れ物だった。指先が、いちばん右の枠だけをとんと叩く。他の三つには、触れなかった。

「要らないのは、今回だけ、ということですか」
私が訊いた。
「はい。パッケージ名が変数に入っていて、実行時にしか決まらないような場合は、その代入の直前だけに絞ってno strict 'refs'を書きます」
クロベさんが、紙の隅に短い一行を書き添えた。
| |
「これなら、$pkgの中身は実行してみるまで分かりません。文字列から名前を組み立てているので、strict 'refs'に引っかかります。だから、その代入の一行だけ、no strict 'refs'で緩めます。今日はShop::Taxという名前が最初から分かっているので、こちらは使いません」
「制限を外すのは、名前が分からないときだけ、ということですね」
ナギさんが言った。
「そうです。今日は要らない、で今日は終わりです」
クロベさんが言った。
「継ぎ目とは、書き換えずに振る舞いを変えられる場所のことでした。ですが、継ぎ目があるだけでは、まだ何も変わりません。どちらの振る舞いを使うか、実際に決める地点が、別にいります」
「決める地点、というのは」
私が訊いた。今の説明を聞いて、もう一つ気になっていたことがあった。
「今回で言えば」
「localと書く、まさにその行です。継ぎ目そのものはShop::Tax::rate()という呼び出しですが、どちらのrateを使うかを実際に決めるのは、テストの中のその一行です」
クロベさんが、テストのファイルに手を入れ始めた。
| |
「localは、この型グロブへの代入を、動的スコープに限ります。字面のブロックではなく、実行の流れがこのsubtestのブロックを抜けるまで、という意味です。抜けた瞬間、元のrateに戻ります」
「一文で書くのが大事です」
クロベさんが付け加えた。
「local *Shop::Tax::rate = sub {...};と、一つの代入文で型グロブごと置き換えます」
クロベさんが、分けて書いた場合の一例を、紙の隅に書き添えた。
| |
「見た目は近いですが、()で囲むと、右側がリストとして扱われる書き方になります。型グロブをリストの側で受け渡しすると、狙った場所だけを差し替えるという保証が失われます。今日のような一つの代入では、上の一文で書く形を、いつも使ってください」
「新しく差し込み口を作ったわけではないんですね」
私が訊いた。ナギさんが書きかけていたものとは、何かが違う気がしていた。
「作っていません。今、このsub { return 0.08; }は、テストの中だけで使う代役です。店のどこにも、差し替えるための場所を新しく組み立ててはいません。板戸を外したときと同じです。溝を新しく彫ったわけではなく、もとからあった溝を使っただけです」
クロベさんが言った。ナギさんが、少し腑に落ちたような顔をした。
湯呑みを一つ、ナギさんが棚から取り出した。値札には千円と書かれている。今日、確かめるのはこの品だった。
第4幕: 検め ── 継ぎ目は、閉じれば元に戻る
動かすと、落ちた。
| |
got:のところに出ている値を、そのまま書き写す。前回、前々回と同じ手順だった。
| |
動かすと、通った。
| |
「税率が変わっていないときも、確かめておきましょう」
クロベさんが、もう一つsubtestを足した。
| |
同じ手順で、実測値を書き写す。
| |
「まだ、大事なところが残っています」
クロベさんが言った。
「テストのブロックを抜けたあと、本当に元の税率に戻っているか、確かめていません」
| |
三つのsubtestを、この順に並べた。通常の税率のとき、税率が変わったとき、そしてスコープを抜けたあと。最後の一つも、同じ手順で実測値を書き写す。
| |
動かすと、三つとも通った。
| |
ok 1、ok 2、ok 3。三つの結果を、私は目で追った。税率が10%のとき、8%のとき、そしてまた10%に戻ったとき。文字にすれば三行だが、頭の中では一続きの絵になっていた。差し替える前、差し替えている間、そして戻ったあと——同じShop::Tax::rateが、時間の中で三つの顔を見せた。PriceTag.pmもShop/Tax.pmも、その間ずっと、指一本触れられていない。

「一つ、面白いものをお見せします」
クロベさんが、localという一語だけを、一時的に消してみせた。同じテストをもう一度動かす。三つ目のsubtestが落ちた。そしてもう一つ、警告が出た。
| |
「localを付けないと、書き換えたままになります。この警告は、rateという名前が、もう一度別のものとして定義し直された、という知らせです。localが付いていれば、この警告も出ません。ブロックを抜けたときに、静かに元へ戻すからです」
クロベさんが、localを書き戻した。もう一度動かすと、三つとも通り、警告も消えた。
「保証できること。テストのsubtestのブロックの中だけ、Shop::Tax::rateの振る舞いが変わります。ブロックを抜ければ、必ず元の税率に戻ります。PriceTag.pmにもShop/Tax.pmにも、一行も手を入れていません」
「保証できないこと」
クロベさんが続けた。
「今日確かめたのは、この一つのテストの中で起きたことだけです。実際に店で動いているレジのプログラムは、また別に動いている一つの流れです。今、この場で差し替えたものが、そちらまで及ぶわけではありません」
私は、そのことを頭の中で確かめた。テストの中で税率が変わっても、今この瞬間に店先で誰かが会計をしていたら、そちらの計算には関係がない。当たり前のようで、はっきり言われるまで気づかなかった。
「Shop::Tax::rateという名前を使わずに、どこかから税率を呼んでいる場所があるかどうかは」
私が訊いた。
「今日は検めていません。今日はこの継ぎ目の話です」
クロベさんは、それ以上は言わなかった。奥にまだ何かありそうな気配だけが残った。
「店主が本当に確かめたかったのは、税率が変わったあとの話ですよね。今日確かめたのは、税率が変わる前提そのものが正しいかどうか、ですか」
私が訊いた。
「はい。税率が本当に変わったときは、Shop::Tax::rateの中身を書き換えることになります。今日確かめたのは、その先でcalc_priceがちゃんと追随するか、ということです」
テストが通ったのを見届けると、クロベさんは立ち上がり、店の間へ戻った。ナギさんもすぐに続いた。立てかけられたままの板戸を、二人でもう一度鴨居の溝に合わせる。今度は、削るためではなく、溝の深さを測るためだった。クロベさんが指先で溝の底をなぞり、埋まった埃を掻き出す。「引っかかっていたのは、溝が痩せたのではなく、埃が溜まっていただけかもしれません」
板戸を数度滑らせると、さっきまでの引っかかりが消えていた。切らずに直る。今日、二度目にそれを見た。
気がつくと、店の間に差す光の角度が、朝より低くなっていた。今日の本題はこちらだったはずなのに、いつの間にか順番が入れ替わっていた。
帳場に戻り、野帳を開く。見立てを書き、手を入れた所を書き、触っていない所を書き終える。ペン先が紙の上を進む間、店の間からはまだ、鑿の音が聞こえていた。
欄が一つ残った。「私が引き受けたこと」。ペンを持ったまま、しばらく考えた。前回は、図を自分で読めるようになった。今回は、それとは違う何かだった。
どこなら触ってよいかの線を自分で引ける。
そう書いてから、もう一行だけ付け加えた。呼び出しに、よその場所の名前が付いているところは、もう外から触れる継ぎ目かもしれない——それが、今日私の手に入った線だった。ナギさんが最初に書きかけたものは、間違ってはいなかった。ただ、探す場所を間違えていた。書き換える前に、まず探す。その順番を、今日ようやく体で覚えた気がした。
継ぎ目は、閉じれば元に戻る。クロベさんがそう言ったとき、私はまだ半分しか分かっていなかった。今、野帳を閉じながら、もう半分が分かった気がした。今日触ったのは、テストの中だけだ。店の間の建具も、値札の計算も、この店を支えている形は、何一つ変わっていない。
普請控
- 見立て: 消費税が変わっても値札の計算(
PriceTag::calc_price)が正しく動くか、変わる前に確かめておきたいが、税率計算(Shop::Tax::rate)を差し替える場所が無いように見えた - 手を入れた所: 継ぎ目(Seam)を使い、
Shop::Tax::rateをテストの中だけlocalで差し替えた。t/price_tag_seam_local.tを追加した。PriceTag.pm・Shop/Tax.pmは一行も変えていない - 触っていない所:
PriceTag::calc_price・Shop::Tax::rateそれぞれの中身の計算そのもの。Shop::Tax::rateという名前を直接使わない呼び出し経路があるかどうかは、今日は検めていない。本番で動いているレジのプログラムへの影響(今日の検めは一つのテストの中に閉じている) - 次の工程へ送ること: 実際に税率が変わったときの
Shop::Tax::rateの書き換えそのもの - 私が引き受けたこと: どこなら触ってよいかの線を自分で引ける
