Featured image of post コードカーペンター【Introduce Instance Delegator】「名前を変えただけで、検め用の代役が呼ばれなくなるんですか」〜名前で縛った差し替えを、渡す相手で決める形にする〜

コードカーペンター【Introduce Instance Delegator】「名前を変えただけで、検め用の代役が呼ばれなくなるんですか」〜名前で縛った差し替えを、渡す相手で決める形にする〜

請求書の印字の関数の名前を揃えたら、検めは通ったまま、差し替えたはずの印字が本物のまま動いていた。名前で縛った差し替えは、名前が変わると何も指さなくなる――インスタンス委譲の導入(Introduce Instance Delegator)で、差し替えを渡す相手で決める形にします。

第1幕: 検分 ── 雨上がりの縁側

雨は、夜のうちに上がっていた。

樋を伝う水の音は、雨が止んでからも半日ほど残る。詰まっていれば音が濁り、通っていれば澄んで細くなる。縁側に出てその音を聞き分けるのが、雨の翌朝の、私の最初の仕事だった。今朝の音は細く澄んでいた。それでも軒先まで歩いて、樋の口に溜まった濡れ葉を手で掬い、縁側の板に落ちた滴を竹箒で寄せた。素屋根の縁からも、ときどき遅れて一粒、滴が落ちてくる。

次に、店の間へ回って印字機の出口の棚を見る。

店主は、請求書を起こす日に、その日の分を端末の中の置き場所へ置いていく。私たちはそれを待ち箱と呼んでいる。印字機は夜のうちにその箱を拾って紙にし、翌朝、出口の棚へ出す。請求書を起こすのは月末と月なかばだけで、昨日はどちらでもなかった。だから、今朝の棚は空のはずだった。

紙が三枚、載っていた。

店主の試し刷りか、書き損じだろう、と思った。角を揃えて、帳場の引き出しの端に入れた。この段取りは三十年、変わっていない。それ以上は、考えなかった。

帳場へ戻ると、クロベさんは素屋根の下での朝の見上げを終えたところだった。屋根の先の樋から軒先までを、ゆっくり目で辿るのが、クロベさんの朝の習いだ。ナギさんは文机の端末の前に座っていたが、私が上がると立ち上がった。

「印字の名前、揃えました」

背筋が伸びていた。声は抑えていたが、言い終えたあとで、ほんの少し顎が上がった。

「検めも、昨日の夕方に、全部通しています」

昨日の午後、クロベさんがナギさんに一言だけ言い付けたのを、私は聞いていた。印字の周りは、名前だけ揃えておけ。ほかの仕組みの名前は、単語を下線で繋ぐ書き方で揃っているのに、請求書を印字する関数だけは、単語の頭を大文字にして繋いだ名前だった。ナギさんは、それを一人でやり終えて、報告に来たのだった。

ナギさんは画面を私たちのほうへ向け、直した所を見せた。

1
2
3
4
5
6
--- lib/Toi/Printer.pm
-sub PrintInvoice {
+sub print_invoice {
--- lib/Toi/Invoice.pm
-    Toi::Printer::PrintInvoice($invoice);
+    Toi::Printer::print_invoice($invoice);

直したのは、この二か所だけだった。関数の定義と、それを呼ぶ一行。検めのほうは、触っていない。名前を揃えるだけの仕事のついでには、検めを書き換えない。それが、この建物の決まりだ。

クロベさんは差分を目で追い、それから一つだけ、認めるように言った。

「検めを通してから報告する。決まりどおりです」

それから、直した二つのファイルを順に開いた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package Toi::Printer;

use strict;
use warnings;

sub print_invoice {
    my ($invoice) = @_;
    open my $fh, '>', "spool/$invoice->{no}.inv" or die "待ち箱に置けません: $!";
    print {$fh} "$invoice->{customer} 御中\n";
    print {$fh} "$_->[0]\t$_->[1]\n" for @{ $invoice->{items} };
    print {$fh} "合計\t$invoice->{total}\n";
    close $fh or die "待ち箱を閉じられません: $!";
    return 1;
}

1;

「請求書を一通、待ち箱へ置く仕組みです」

spool/と書かれた所が、私たちの言う待ち箱のことだった。請求番号の名前を付けたファイルを一つ作り、宛名と明細と合計を書き入れる。置くだけで、紙にするのは夜の印字機の仕事だ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package Toi::Invoice;

use strict;
use warnings;
use Toi::Printer;

sub issue {
    my (%args) = @_;
    my $total = 0;
    $total += $_->[1] for @{ $args{items} };
    my $invoice = {
        no       => $args{no},
        customer => $args{customer},
        items    => $args{items},
        total    => $total,
    };
    Toi::Printer::print_invoice($invoice);
    return $invoice;
}

1;

「こちらは、請求書を組んで、合計を出して、印字の仕組みを呼んでから、組んだ請求書を返します」

印字の仕組みは、Toi::Printer::print_invoiceと、パッケージの名前まで付けて呼ばれている。その呼び方は、店の間の値札のときにも見た形だった。呼ぶ側は、どこの何という関数を呼ぶのかを、名指しで書いている。

クロベさんは、issueの呼び出しの行をしばらく見てから、検めのファイルを開いた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use Test::More;
use lib 'lib';
use Toi::Invoice;

subtest '品目が1つの請求書' => sub {
    local *Toi::Printer::PrintInvoice = sub { 1 };
    my $inv = Toi::Invoice::issue(no => 9001, customer => '大和屋', items => [ ['杉板', 12000] ]);
    is($inv->{total}, 12000, '合計は12000');
};

subtest '複数の品目の請求書' => sub {
    local *Toi::Printer::PrintInvoice = sub { 1 };
    my $inv = Toi::Invoice::issue(no => 9002, customer => '大和屋', items => [ ['杉板', 12000], ['釘', 980] ]);
    is($inv->{total}, 12980, '合計は12980');
};

subtest '空の明細の請求書' => sub {
    local *Toi::Printer::PrintInvoice = sub { 1 };
    my $inv = Toi::Invoice::issue(no => 9003, customer => '大和屋', items => []);
    is($inv->{total}, 0, '合計は0');
};

done_testing;

三つの場合が、同じ形で並んでいる。クロベさんは、localの行を順に目で追った。

「このlocalの行は、本物の印字の代わりに立つ代役を置く行です」

私が聞き慣れない顔をしたのを、クロベさんは察して続けた。

「検めのあいだだけ本物の代わりを務める部品を、代役と呼びます。テストダブルと呼ぶ人もいます。この行は、検めのあいだだけ、この名前の指す先を空の関数に入れ替えます。終われば、元の指す先が戻る。値札のときに使った、localの手です」

私は頷いた。あのときは税率だった。今度は、入れ替えられるのが、本物の印字だということだった。

クロベさんは、画面から顔を上げなかった。

「この検めが見ているのは、返ってきた請求書の合計だけです」

ナギさんが、はい、と頷いた。

「印字のほうは、この検めのどこにも出てきません」

ナギさんは、すぐには頷かなかった。画面とクロベさんの顔を、一度ずつ見た。何を言われたのか、測りかねているように見えた。

第2幕: 手が入らない ── 消して比べる

「確かめるあいだだけ、差し替えの行を三行とも、コメントにして、もう一度走らせてください」

クロベさんが言った。ナギさんは戸惑いながら、三つのlocalの行の頭へ#を打ち、走らせた。

1
2
3
4
$ prove -l t/invoice_before.t
t/invoice_before.t .. ok
All tests successful.
Result: PASS

昨日の夕方に見た画面と、同じ画面が出た。

ナギさんの手は、キーの上で止まった。画面のPASSの文字を、瞬きもせずに見ていた。

私は、口を開いた。訊かずには、通れなかった。この建物の検めは、通った、と野帳に書いてきたものばかりだ。通ったという字が何を指しているのかを、私は今日まで疑ったことがなかった。

「その行が効いているかどうかは、どうやって確かめるのですか」

「取り除いて比べます」

クロベさんは、平らな声で答えた。

「効いていた部品は、取り除けば何かが変わります。取り除いても何も変わらないなら、最初から効いていなかったということです」

「効いて、いなかった」

ナギさんが、低い声で繰り返した。

「名前が変わらなかった間は、この三行は確かに効いていました。今日まで、検めを走らせても本物の印字が動かずに済んだのは、このおかげです。この手が悪かったわけではありません。名前を揃えたのは、必要な仕事でした。通ったことを確かめるだけでは、足りなかった。それだけです」

クロベさんは、三つのlocalの行を、目で順に指した。

「差し替えの行が指している名前はPrintInvoiceです。この名前の関数は、今、建物のどこにもありません。libの中には、print_invoiceがあるだけです。PrintInvoiceと書いてあるのは、この検めの三行だけです。issueが今呼んでいるのは、そのprint_invoiceのほう、つまり本物です。代役は、PrintInvoiceという別の名前に入っていました。だから、本物が走ります」

クロベさんは、一行目を指した。

「Perlには、名前ごとに、その名前が指す関数をしまっておく入れ物があります。local *Toi::Printer::PrintInvoice = sub { 1 };は、その入れ物に、代役を入れる、という一行です。入れ物の中に本物が入っていようが、いまいが、Perlは咎めません。空の名前に代役を入れたなら、代役の入った名前が一つできるだけです。誰も、その名前を呼ばない」

「警告も、出ないんですか」

ナギさんが訊いた。

「出ません。Perlは、一度しか書かれていない名前には、打ち間違いではないか、と警告を出します。この名前は、検めの中に三度書いてあるので、それも出ません」

ナギさんは、コメントにした三つの行を、じっと見ていた。それから、顔を上げた。

「……名前を変えただけで、検め用の代役が呼ばれなくなるんですか」

「なります」

クロベさんは続けた。

「差し替えの行は、関数を指しているのではありません。名前を指しているだけです。指された名前の先に、何が立っていても、何も立っていなくても、行は同じように受け付けられます」

ナギさんが、唇を結んだ。何か言おうとして、言わなかった。

「本物が走ったのなら」

クロベさんは、私に目を移した。

「印字機が、夜のうちに紙にして、今朝の出口の棚に三枚、出しているはずです。待ち箱に置いた分は、そうして出ていきますから」

私は、立ち上がった。

引き出しの端を開けて、揃えておいた三枚を取り出し、文机の上に置いた。紙の角を、机の縁に合わせた。

一枚目は、宛名が大和屋の請求書だった。店主が長く取引している得意先の名前だ。杉板が一行あって、合計は一万二千。

1
2
3
大和屋 御中
杉板	12000
合計	12000

二枚目は、杉板と釘の二行があって、合計が一万二千九百八十。三枚目は、明細が一行も無く、合計の欄だけが0になっていた。今朝、これを見て、書き損じだと思ったのは、そのためだった。

1
2
3
4
大和屋 御中
杉板	12000
釘	980
合計	12980
1
2
大和屋 御中
合計	0

三枚とも、店主の得意先の名前で刷られていた。検めの三つの場合が、一つずつ、一枚の紙になっていた。

縁側の外では、樋を伝う水の音が、まだ続いていた。

第3幕: 手立て ── 相手で決める

クロベさんは、紙から目を上げて、ナギさんに言った。

「待ち箱に、いま走らせた分が、また三通置かれています。夜になる前に、消しておいてください」

ナギさんは黙ってspool/を開き、三つのファイルを消した。コメントにしていた三行の#も、外して元に戻した。

「では、差し替えを、名前に頼らない形にします」

「名前に頼らない」

私が問い返すと、クロベさんは、机の上の三枚の紙を、一度だけ見た。

「今日使うのは、インスタンス委譲の導入――Introduce Instance Delegator――です。パッケージの関数を呼ぶだけのメソッドを持つクラスを作る。呼ぶ側は、そのクラスのオブジェクトを渡してもらって、それ経由で呼ぶ。差し替えたいときは、そのオブジェクトを、別のものに替えます」

「渡してもらう」

「関数の名前ではなく、渡す相手で差し替えを決める、ということです」

「テストの無い古い仕組みに手を入れる技法を、まとめた本があります。この技法は、その本に載っています。本の例が相手にしていたのは、静的メソッドでした。Perlなら、パッケージの関数が、それに当たります」

ナギさんが、画面の前に座り直した。

「本物のほうに、同じ名前のメソッドを、足すのではだめなんですか」

「だめです。置けません」

クロベさんは、空いた画面の隅に、短いコードを打った。

1
2
3
4
5
package A;
sub f { return "fn:@_" }

package main;
print A->f(1), "|", A::f(1), "\n";

走らせると、一行だけ出た。

1
fn:A 1|fn:1

「A->f(1)は、第一引数にクラスの名前のAが入るだけで、A::f(1)と、同じ関数を呼んでいます。パッケージの関数と、そのパッケージのメソッドは、同じ入れ物です。だから、Toi::Printerに同じ名前のメソッドを足せば、今あるprint_invoiceを上書きしてしまいます。しかも、メソッドとして呼べば、第一引数にクラスの名前が入って、今の関数が受け取る請求書とは、引数の並びが変わります。だから、別のクラスに、切ります」

ナギさんは、新しいファイルを開いた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package Toi::InvoicePrinter;

use strict;
use warnings;
use Toi::Printer;

sub new { my ($class) = @_; return bless {}, $class }

sub print_invoice {
    my ($self, $invoice) = @_;
    Toi::Printer::print_invoice($invoice);
}

1;

newとblessは、最初の話に出てきた、オブジェクトを作る組み立て役だ。print_invoiceのほうは、受け取った請求書を、そのままToi::Printer::print_invoiceへ渡すだけの、一行だった。

「この中に、関数の名前を知っている場所は、一か所しかありません」

クロベさんは、Toi::Printer::print_invoiceの一行を指した。

「次は、issueです。渡してもらえるようにします」

ナギさんが、続きを打った。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 package Toi::Invoice;

 use strict;
 use warnings;
-use Toi::Printer;
+use Toi::InvoicePrinter;

 sub issue {
     my (%args) = @_;
+    my $printer = $args{printer} // Toi::InvoicePrinter->new;
     my $total = 0;
     $total += $_->[1] for @{ $args{items} };
     my $invoice = {
         no       => $args{no},
         customer => $args{customer},
         items    => $args{items},
         total    => $total,
     };
-    Toi::Printer::print_invoice($invoice);
+    $printer->print_invoice($invoice);
     return $invoice;
 }

ナギさんは、//を迷わずに打った。二階の大福帳のとき、打ちかけて手を止めた、あの演算子だった。

「渡さなければ、いつものToi::InvoicePrinterを自分で作る。二階の大福帳で使った形と同じです」

クロベさんは、そう言ってから続けた。

「今日の新しいところは、そこではありません。渡すものの中身のほうです」

それから、机の上の三枚の紙に目をやり、正面を向いた。

「樋は、壁に直には釘を打ちません。樋受けの金物に掛けます。樋を替えても、直すのは受けの一か所で済みます。今日組んだToi::InvoicePrinterは、その樋受けです。壁がissue、樋が印字の関数、そして、樋の名前を知っているのは、樋受けだけです。樋を仮のものに替えたいときも、替えるのは、受けに掛ける所だけで済みます」

樋受けという言葉が、頭の中で、縁側の外の樋に重なった。

「一つ、訊いてもいいでしょうか」

私は言った。

「関数一つのために、クラスをもう一つ足すのは、大げさではないでしょうか」

「大げさに見えます」

クロベさんは、はぐらかさなかった。

「本を書いた人自身が、そう書いています。関数がいくつもあるパッケージに、メソッドが一つだけ増えるのは、風変わりに見える、と。それでも、名前を知る場所が一つに絞れる。差し替える口が手に入る。見た目の重さと引き換えに、それを取ります」

「その本には、最後に、呼ぶ側が全部この委譲を通るようになったら、関数の本体を委譲クラスの中へ移して、元の名前を消してしまってよい、とも書いてあります。今日はしません」

ナギさんは、もう次の一手を考えているようだった。

「検め用の代役を、書きます」

ナギさんは、別のファイルを開いた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package Fake::Printer;

use strict;
use warnings;

sub new { my ($class) = @_; return bless { got => [] }, $class }

sub print_invoice {
    my ($self, $invoice) = @_;
    push @{ $self->{got} }, $invoice;
}

sub got { my ($self) = @_; return @{ $self->{got} } }

1;

渡された請求書を配列に溜めて、あとからgotで取り出せるだけの、小さな部品だった。Perlは、型を見ない。呼ばれた名前のメソッドを持っていれば、継承していなくても使える。

「一つだけ、決めておきます」

クロベさんは、画面へ顔を向けたまま告げた。

「この代役は、本物の子にしません」

「継承しない、ということですか」

「はい。試します」

クロベさんは、古い名前しか持たない代役を書いた。名前を揃える前に誰かが書いた代役を、そのまま使ってしまった、という場面だ。持っているのは、PrintInvoiceという名前だけ。Perlは、子のクラスに無い名前のメソッドを、親のクラスから探す。まず、本物の委譲クラスを継がない形で走らせ、そのあと、一行足して継ぐ形でも走らせる。

1
2
3
4
5
{ package Fake::Old;
  sub new { bless { got => [] }, shift }
  sub PrintInvoice { my ($s, $i) = @_; push @{ $s->{got} }, $i } }
my $inv = Toi::Invoice::issue(no => 9001, customer => '大和屋', items => [ ['杉板', 12000] ], printer => Fake::Old->new);
print "合計 $inv->{total}\n";

継がない形を走らせる。

1
Can't locate object method "print_invoice" via package "Fake::Old" at lib/Toi/Invoice.pm line 18.

その場で止まった。待ち箱を見ると、何も置かれていない。

次に、{ package Fake::Old;の次の行へ、our @ISA = ("Toi::InvoicePrinter");と一行足して、本物の委譲クラスを継いだ形で走らせる。

1
合計 12000

エラーは、一行も出なかった。待ち箱を覗くと、9001.invが一つ、できていた。

「継いだ代役は、自分が持っていない名前の呼び出しを、親に任せてしまいます」

クロベさんは、そこで言葉を足した。

「代役が旧い名前しか持っていなくても、親のprint_invoiceが黙って動きます。名前で縛った差し替えと、同じ黙り方です。だから、代役は、本物の子にはしません」

ナギさんは、Fake::Printerの一行目を、見直した。継承の行は、どこにも書かれていなかった。

「配線のことを、話しておきます」

クロベさんは、端末の別のファイルを、画面へ呼んだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use strict;
use warnings;
use lib 'lib';
use Toi::Invoice;

my $invoice = Toi::Invoice::issue(
    no       => 9001,
    customer => '大和屋',
    items    => [ ['杉板', 12000], ['釘', 980] ],
);
print "合計 $invoice->{total}\n";

「本番で請求書を起こす呼び出しです。今朝までと、一字も変わっていません。printerを渡さないので、issueが自分でToi::InvoicePrinterを組み立てます。検めのほうだけが、代役を組み立てて、printer =>で渡します。代役を作って渡すのは、検めの側の書き手です」

クロベさんは、手を入れる前と後の呼び出しの道筋を、紙の上に並べて引いてみせた。

手を入れる前と後の対比図。前は、issueが印字の関数を名前で直に呼び、検めはPrintInvoiceという名前に代役を入れるが、その名前は今は建物のどこにも無く、代役は使われない。後は、issueが渡されたprinterを呼び、本番ではToi::InvoicePrinter、検めではFake::Printerになる。関数の名前を知っている場所は、Toi::InvoicePrinterの一行だけになる

手を入れる前は、印字の関数の名前を書いている場所が、issueと検めの二か所に分かれていた。検めが指した名前は今はどこにも無く、道筋は本物のほうへしか通じていない。手を入れた後は、その名前を書いているのはToi::InvoicePrinterの一行だけになった。issueは渡された相手を呼ぶだけで、検めが代役を渡せば、道筋は代役へ折れて、本物には届かない。

「縄の手とは、どう違うんでしょう」

私が訊いた。井戸の一件のことだった。

「あのときは、同じクラスの中の呼び出しを、メソッドに切り出して、検めのときだけ子のクラスで上書きしました。今日は、関数を持っている相手の側に、薄い委譲を足して、渡すものを替えます。子のクラスは、要りません」

第4幕: 検め ── 名前を探す

同じ呼び出しを、手を入れる前のissueと、手を入れた後のissueとで、順に流して比べる。まず、それを走らせた。

比べるあいだだけ、手を入れる前の版をToi::Invoiceの名前のまま控えとして残し、手を入れた後の版をToi::InvoiceAfterという別の名前で並べて置いた。比べ終えたら、控えは下げて、新しい版がToi::Invoiceの名前を引き継ぐ。入力は、品目が一つの場合、複数の品目の場合、明細が空の場合、それから日本語の品目名を入れた場合の四つ。それぞれを、二つの版へ、同じ呼び方で流す。

1
2
3
4
5
my ($rb, $fb) = run_in_new_dir(sub { my ($no, $c, $i) = @_; Toi::Invoice::issue(no => $no, customer => $c, items => $i) });
my ($ra, $fa) = run_in_new_dir(sub { my ($no, $c, $i) = @_; Toi::InvoiceAfter::issue(no => $no, customer => $c, items => $i) });

is_deeply($ra, $rb, '全入力で戻り値が Before と After で一致する');
is_deeply($fa, $fb, '待ち箱の中身がバイト列まで一致する');

前の呼び出しの書き方を、そのまま新しいissueへも当てている。printerは、どちらにも渡していない。この検めは、走らせるたびに使い捨ての作業場所を作り、その中へ移って流す。店の待ち箱には、触れない。

「今までの呼び方で流せば、前と同じ請求書が、同じ形で待ち箱に置かれます」

クロベさんは、画面へ目を置いたまま続けた。

「本物を通る検めですから、待ち箱の中身が一致するところまで確かめられます」

続けて、代役の検めを走らせた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
my $fake = Fake::Printer->new;
my $inv = Toi::InvoiceAfter::issue(
    no => 9002, customer => '大和屋', items => [ ['杉板', 12000], ['釘', 980] ], printer => $fake,
);

is($inv->{total}, 12980, '戻り値の合計は12980');
my @got = $fake->got;
is(scalar @got, 1, '代役に請求書が1通だけ渡された');
is($got[0]{customer}, '大和屋', '得意先が渡っている');
is_deeply($got[0]{items}, [ ['杉板', 12000], ['釘', 980] ], '品目が渡っている');
is($got[0]{total}, 12980, '合計が渡っている');
ok(!glob('spool/*'), '本物は呼ばれず待ち箱は空のまま');

全部を走らせる。

1
2
3
4
5
t/invoice_before.t ....... ok
t/invoice_delegator.t .... ok
t/invoice_equivalence.t .. ok
All tests successful.
Result: PASS

前の検めが見ていたのは、合計だけだった。今度は、何が印字されたかまで見られる。得意先、品目、合計。そして、待ち箱は空のまま。本物は、一度も呼ばれていない。

「もう一度、名前を変えてみてください」

クロベさんが、ナギさんに言った。

「今度は、印字の関数を、emit_invoiceに」

ナギさんは、Toi::Printerの定義の名前を書き換えた。それから、少しも迷わずに、Toi::InvoicePrinterの一行を指した。

「直すのは、ここです」

そこだけを、emit_invoiceに直した。並べて置いていた前のissueも、同じ名前を呼んでいる。こちらは、比べるための足場なので、同時に直した。足場を下げれば、この名前を知っている場所は、委譲クラスの一行だけになる。

代役の検めを走らせる。検めには何も手を入れていないのに、通った。本物を通る検めも、通った。

「今度は、その一行を、直し忘れてみてください」

クロベさんが、頼んだ。ナギさんは、委譲クラスの行だけを、元のprint_invoiceに戻した。

1
2
3
4
$ prove -l t/invoice_delegator.t
t/invoice_delegator.t .. ok
All tests successful.
Result: PASS

代役の検めは、通った。本物を通る検めのほうを走らせる。

1
2
3
4
5
6
7
$ prove -l t/invoice_equivalence.t
Undefined subroutine &Toi::Printer::print_invoice called at lib/Toi/InvoicePrinter.pm line 11.
t/invoice_equivalence.t ..
Dubious, test returned 255 (wstat 65280, 0xff00)
No subtests run
...
Result: FAIL

今度は、止まった。止まった場所は、委譲クラスの一行だった。ナギさんは、定義と委譲の一行を、両方ともprint_invoiceに戻した。

「代役だけを相手にした検めは、この一行を、一度も通りません」

クロベさんは、二つの結果を、並べて指した。

「だから、本物を通る検めを、一本だけ残します。名前が変わったのに、委譲の一行だけが古いままなら、その検めが落ちる」

それから、区切って言った。

「今日の手で、言えることがあります。印字を、待ち箱に何も置かずに検められます。何が印字されたかまで見られます。印字の関数の名前が変わっても、直すのは委譲クラスの一行だけで、代役の検めは、書き換えなくて済みます。issueを呼ぶ側の書き方は、変えていません。printerは、省略できます」

「言えないことは」

私は訊いた。

「二つあります」

クロベさんは、指を折らなかった。

「一つ。今日直したのは、この検めの三行までです。二つ。Perlには、書き間違えた名前を、コンパイルのときに教えてくれる仕組みがありません。名前の付け間違いは、動かすまで分かりません」

少し間を置いてから、もう一つ加えた。

「それから、夜の印字機が待ち箱を拾って紙にするところは、検めの外です。今朝の三枚は、見えたから分かっただけです。この印字を、これで安心だと言い切ることは、できません」

ナギさんが、小さく息を吐いた。

私は、しばらく黙っていた。それから、自分の席の端末を引き寄せて、検めの束の入った場所を開いた。

今日直したのは、この検めの三行までだ、とクロベさんは言った。同じ書き方の行がほかにもあるのかどうかを、引き受ける前に、私は自分の目で知りたかった。

名前を指している行は、書き方に現れる。local *のあとに、パッケージの名前が続く。この形の行を、束の中から探せばいい。

私は、探す文字を打った。

1
2
3
4
5
$ grep -rn 'local \*' t/
t/invoice_before.t:8:    local *Toi::Printer::PrintInvoice = sub { 1 };
t/invoice_before.t:14:    local *Toi::Printer::PrintInvoice = sub { 1 };
t/invoice_before.t:20:    local *Toi::Printer::PrintInvoice = sub { 1 };
t/price_tag_seam_local.t:13:    local *Shop::Tax::rate = sub { return 0.08; };

四行、あった。探したのは、local *と書く形だけだ。ほかの書き方の差し替えは、今日は見ていない。

三行は、今日、直しの中心になった検めの中に。残る一行は、値札の検めの中だった。あのとき教わった、Shop::Tax::rateを検めのあいだだけ差し替える、一行。

野帳を開いて、私は、名前で差し替えている所を、書き出した。

1
2
3
名前で差し替えている所
  t/invoice_before.t         三行  PrintInvoice     今日、代役の検めに替える
  t/price_tag_seam_local.t   一行  Shop::Tax::rate   手を付けていない

書き終えてから、クロベさんに訊いた。

「税のほうは、名前を変えたら、どうなりますか」

「落ちます」

クロベさんは、すぐに答えた。

「差し替えた税率が、戻り値に出ていますから。名前が変われば、代役は入らなくなって、本物の税率が返る。検めの期待した八パーセントの値とは、違う数が返ってきます」

「印字のほうは」

クロベさんは、机の上の紙を見た。

「戻り値に出ません。だから、名前が変わっても、黙りました」

私は、野帳の税率の行の脇に、小さく書き添えた。落ちて、気づく。印字の行の脇には、黙る、と。

「名前を変えたときに、黙るのは、差し替えた結果が戻り値に出ない所です」

クロベさんが、それだけ言った。

ナギさんが、古い検めのファイルを開いて、三つのlocalの行を、一つずつ消しはじめた。名前を揃えるだけの直しとは違う。差し替えの作りそのものを替えたのだから、この検めは、代役の検めに置き換わる。私は、机の上の三枚の紙を、そっと手に取り、野帳のあいだに挟んだ。店主には、処分してよいかを、訊いてからにしよう。

縁側の外で、樋を伝う水の音が、少しだけ低くなっていた。


普請控

  • 見立て: 請求書の印字は、Toi::Printer::PrintInvoiceという名前を呼ぶ形で組まれ、検めもその同じ名前をlocalで差し替えていた。名前を揃えた日から、差し替えの行は無い名前を指すだけになり、検めは通ったまま、本物の印字が待ち箱に三通置かれ、夜のうちに紙になって店の間の出口の棚に出ていた
  • 手を入れた所: インスタンス委譲の導入(Introduce Instance Delegator)を使った。印字の関数を呼ぶだけのToi::InvoicePrinterを作り、issueは省略できるprinterを渡してもらって、それ経由で呼ぶ。検めでは、本物の子ではない代役を渡す。前の検めのlocal三行は、代役の検めに替えた
  • 触っていない所: Toi::Printer::print_invoiceの中身(請求書の書式と待ち箱への置き方)は、一文字も変えていない。issueの呼び方、請求書の書式、店の間の印字機が夜に待ち箱を拾って紙にする仕組みも、そのまま。値札の検め(t/price_tag_seam_local.tの、Shop::Tax::rateを名前で差し替えている一行)も、今日は直していない
  • 次の工程へ送ること: 値札の検めは、名前が変われば戻り値が変わって落ちるので、急がず次に手を入れる。待ち箱の先で、印字機が紙にするところは検めていない。出口の棚に出ていた三枚の紙は、店主に見せて、処分してよいかを訊く
  • 私が引き受けたこと: 名前で差し替えている所を、自分で探して見つけられる。今日は、検めの束の中でlocal *と書かれた行を探して、四行を野帳に書き出した。探したのは、この書き方の行だけで、ほかの書き方の差し替えは、まだ見ていない。直すか、棟梁に頼むかも、まだ決めていない。見つけて、書き出すところまでは、自分でやる
comments powered by Disqus
システム開発・AIワークフローのご相談は Meetsource へ
Hugo で構築されています。
テーマ Stack は Jimmy によって設計されています。