Featured image of post コードカーペンター【Characterization Test】「このずれは、直すものなんでしょうか」〜答える前に、今出ている数字をそのまま野帳へ書き取る〜

コードカーペンター【Characterization Test】「このずれは、直すものなんでしょうか」〜答える前に、今出ている数字をそのまま野帳へ書き取る〜

図面も期待値も無い帳場の勘定コードに、直す前にまず今出ている振る舞いをそのまま書き取るCharacterization Testを入れる回。Perlのコアだけで仕様化テストを実装します。

第1幕: 検分 ── このずれは、直すものなんでしょうか

秋の朝だった。素屋根の隙間から差し込む光が、まだ低い角度で梁を撫でている。細かな木屑が、光の筋の中をゆっくり漂っていた。クロベさんは、いつものように建物を見上げていた。一日のうちで、いちばん余計なことを考えずに済む時間なのだそうだ。ナギさんは道具箱の前にしゃがんで、鑿の並びを一本ずつ確かめている。金属が擦れる小さな音だけが響いていた。

私は帳場のほうから出てきて、その様子を見ていた。手には、いつもの帳面を持っていた。表紙の角が少し丸くなっている、何年も使い続けている一冊だった。表の格子戸はまだ開けていない。開ける前のこの時間だけ、店は少し静かだった。今日から、この店の勘定のことにも手が入ることになっていた。表の建て付けや裏の床の話ではない。三十年、私が毎日つけてきた、あの勘定のプログラムの話だ。

工事の話が出てから、私はずっとこの帳面を持ち歩いていた。何か訊かれたときに、すぐに答えられるようにしておきたかった。けれど、いざこの場に立つと、言葉が出るまでに間があった。

ナギさんが、道具箱の脇に置いてあった湯呑みを、クロベさんの手の届く場所へそっと寄せた。頼まれてのことではない。クロベさんも、礼を言うでもなく、当たり前のように手を伸ばした。三十年、私がこの二人を見てきたわけではないが、こういう間の空気だけで、この現場がどれだけ回を重ねてきたかが分かる気がした。

「クロベさん。直す前に、一つ、聞いていただきたいことがあります」

クロベさんが振り返った。

「なんでしょう」

「月末の締めの数字が、時々、算盤で数えた額と、一円か、多くて数円、ずれるんです」

言葉を継ぐのに、少し間があった。

「……このずれは、直すものなんでしょうか」

三十年、帳場に立ってきて、このことを口にしたのは初めてだった。間違いだと言う自信もなく、決まりだと納得する根拠もなく、ただ黙って見過ごしてきた数字だった。

「毎月ではありません。値引きの多い月に、そうなることが多いように思います」

「三十年のあいだに、何度もありました。でも、店が傾くほどの額ではなくて……だから、誰にも言わずにきました」

一度だけ、自分で算盤を弾き直したことがある。五年ほど前、値引きの多かった月だった。何度弾いても、機械の出す数字と一円違った。間違い探しのつもりで始めたのに、どこにも間違いが見つからず、結局そのままにした。あのときの居心地の悪さを、私はまだ覚えている。

言ってしまうと、思っていたより軽い言葉だった。三十年抱えてきた割に、口に出せばこれだけのことだったのかと、自分でも少し拍子抜けした。

クロベさんは、私の顔を見て、それから何も言わずに待った。答えを急がせない人だった。私が続きを言うまで、視線をそらさなかった。

「毎月ではなく、時々。大きくは外れない、と」

「はい」

「今日は、それを直しに来たわけではありません」

その一言で、話は終わった。クロベさんはまだ帳場のプログラムを見てもいない。何が起きているのかを、これから確かめる、というだけだった。

クロベさんは、まだ何も直すとは言わなかった。けれど、放っておくとも言わなかった。それが、この日の始まりだった。

第2幕: 手が入らない ── 直した先に何が動くか、今は言えません

帳場は、表の店先からひと続きの、低い段のある座敷だった。長年使われてきた帳箱が壁際に積まれ、算盤が一つ、いつもの場所に置いてある。この店の商いは、今も紙の伝票と、この機械の両方でまわっている。伝票は毎日、夜のうちに私が機械へ打ち込む。締めの数字だけを、機械が出す。

その奥、勘定を扱う機械の前に、三人で座った。窓の外では、まだ工事の音は始まっていない。画面に映っているのは、Register という名前の、短いコードだった。帳場も、記録することも、同じ言葉で呼ばれているのだと、あとで知った。

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

use strict;
use warnings;

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

sub record_sale {
    my ($self, $amount, $discount_rate) = @_;
    push @{ $self->{entries} }, $amount * (1 - $discount_rate);
    return;
}

sub close_month {
    my ($self) = @_;
    my $total = 0;
    $total += $_ for @{ $self->{entries} };
    return sprintf('%d', $total);
}

1;

冒頭の二行を指して、私は訊いた。

「これは、何を守っているんですか」

use strict;use warnings; のことだった。見た目には何もしていない、ただの二行に見えた。

「書き忘れた変数を教えてくれます。ただし、数の種類までは教えてくれません」

クロベさんは続けた。

「この言葉(Perl)は、数がどんな形かを、動かしてみるまで決めません。だから、この二行があっても、整数のつもりで扱っていた数が、いつのまにか端数を持っていた、というようなことまでは、教えてくれないんです」

書いてから初めて分かる間違いと、動かしてから初めて分かる間違いがある。この二行が拾うのは前者だけで、後者は自分の目で見つけるしかない――そう理解した。

クロベさんの指が、bless { entries => [] }, $class; の行で止まった。

「これは、何をしているんですか」

「ここで、ただの入れ物に『これはRegisterという型のものだ』という札を付けています」

「札……」

「はい。この一行がないと、ただの数の詰め合わせでしかありません。札を付けて、初めてRegisterとして扱えるようになります」

続けて record_sale の一行目、my ($self, $amount, $discount_rate) = @_; を指す。

「これは」

「呼ばれたときに渡されたものを、順番に名前へ取り出しています。難しいことはしていません」

私にとっては、どれも初めて聞く言葉だった。それでも、一行ずつ意味を教わると、この短いコードが、思っていたより素っ気ない作りだということが分かってきた。何かをこっそり隠しているわけではなく、ただ、書いてあることをそのまま実行しているだけだった。

説明を終えると、クロベさんは close_month の中身に目を移した。合計してから、最後に一度だけ端数を切り捨てて返す作りになっている。

「月の終わりに、まとめて一度だけ落としている。日々の帳面は、その日ごとに落としている、とおっしゃいましたね」

「はい。毎日、その日の売上を書き付けるときに、円未満は切り捨てています」

「合わせ方が違えば、合計が違って出ることがあります」

クロベさんは、画面から目を離さずに続けた。

「たとえば、端数が五十銭のものを三件、足し合わせてから一度だけ切り捨てれば、一円ぶん残ります。同じ三件を、一件ごとに切り捨ててから足せば、何も残りません」

三件とも、五十銭。合わせるか、先に落とすか。順番が違うだけで、答えが一つ違ってくる。

日々の帳面(日ごとに切り捨て)とclose_month(月末に一度だけ切り捨て)の対比図。同じ3件のはずが1円ずれることを示す

「値引きの数が多いほど、その差が積み上がる、ということですか」

「はい。値引きの少ない月は、差が出ても目に見えないくらい小さい。値引きの多い月に、はっきり出ます」

私は、ようやく腑に落ちた。ずれが「時々」で「値引きの多い月」に出る、という自分の見立てと、ちょうど合っていた。

「これを組んだ人が、間違えたとは、まだ言えません」

前の作り手を悪く言う言葉ではなかった。ただ、月末にまとめて丸める、という作りにも、それなりの理由があったかもしれない、という言い方だった。

「二十年、この作りのまま、店は続いてきました」

クロベさんはそう言って、画面から少し身を引いた。

「今も、何を支えているか。それを先に読んでおかんと、この先の話ができません」

そのとき、ナギさんが身を乗り出した。

「棟梁、ここ、一件ごとに端数を落とすようにすれば、日々の帳面と揃うんじゃないですか」

具体的な直し方だった。私にも、筋が通っているように聞こえた。

「揃うかもしれん」

クロベさんは、それを間違いだとは言わなかった。

「ただし、揃えたら、ほかの数字も動く。今、動いた先に何があるか、わしには言えん」

ナギさんの手が、キーボードの上、宙で止まった。指がキーに触れる寸前で止まっている、というふうに見えた。書き換える案そのものを否定されたわけではないのに、その手は動かなかった。

「直したら、何が変わるんでしょうか」

私が訊いた。三十年、この帳場を守ってきた者として、これから自分が引き受けるものについて、訊かないわけにはいかなかった。

「それを、今は誰にも言えません」

クロベさんは、私とナギさんを交互に見た。

「このコードには、図面が残っていません。何がどう動くと決められていたのか、書いた本人以外、誰も確かめる手立てがない状態です」

普請の図面が無い建物と、同じことだと言われて、私は合点がいった。この店に図面が残っていないのは、私も知っている。三十年前、初めてこの帳場に立ったときから、そうだった。

「だから、直す前に、今の形を写し取ります。直す前の形を残しておかんと、直ったかどうかが言えません」

短い一言だった。それ以上の説明はなかった。けれど、その一言だけで、何かがはっきりと決まった気がした。

ナギさんの手が止まったのを見て、私は初めて、何も直っていないのに、何かが動き出したのを感じた。

第3幕: 手立て ── 野帳に、今出ている通りに書き取る

「これは、“仕様化テスト(Characterization Test)“と呼ばれるやり方です」

クロベさんが、名前を口にした。

「正しい形を決めるのではなく、今出ている通りの形を、そのまま書き取ります」

「正しいかどうかを、決めないんですか」

「決めません。決められないから、まず書き取ります」

「正しいかどうか分からないものを確かめて、意味があるんですか」

私は訊いた。三十年、このプログラムの出す数字を見てきて、正しさを確かめるためにあるものだとばかり思っていた。

「意味はあります。ただし、違う意味です」

クロベさんは、少し間を置いてから続けた。

「二十年、このお店は、この数字のまま動いてきました。正しいかどうかは分からんでも、この数字で店は続いてきた、という事実は動きません。今日書き取るのは、その事実です」

「その数字が、実は間違っていたら」

「それでも、書き取ります。間違っているかどうかを決めるのは、また別の日の仕事です。今日は、間違っているかもしれない数字ごと、今の形として残しておく。そうしておかんと、後で誰かが手を入れたときに、何が変わったのかを、誰にも言えなくなります」

そこで初めて、宮大工の言葉に置き換えた。

「私たちの言葉で言うなら、野帳に今の寸法を書き取る、というのに近いです」

野帳、と聞いて、私は手にしている帳面を見た。これまで、日々のことを書き留めるためだけの帳面だった。それが、今日から別の意味を持つらしかった。

クロベさんは、ナギさんに雛形を用意させた。ナギさんが、t という場所――検めの紙だけを集めておく場所だという――に、新しい紙を一枚起こす。慣れた手つきだった。手が止まっていたのは、さっきの一件だけだったらしい。

まず、今月分の取引を、record_sale で一件ずつ積む。値引きのない売上と、いくつもの異なる値引き率の売上を混ぜた、十件だった。ナギさんが一行打つごとに、クロベさんが数字を確かめる。値引きの多い月を、わざと選んでいた。

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

my $register = Register->new;

# 値引き無しの売上と、複数の異なる値引き率の売上を、日をまたいで混在させた1か月ぶんの取引
$register->record_sale(10000, 0);       # 値引きなし
$register->record_sale(8000,  0.03);
$register->record_sale(5500,  0.07);
$register->record_sale(12300, 0.015);
$register->record_sale(7000,  0);       # 値引きなし
$register->record_sale(9800,  0.025);
$register->record_sale(6600,  0.045);
$register->record_sale(15000, 0.0125);
$register->record_sale(4300,  0.08);
$register->record_sale(3000,  0);       # 値引きなし

is($register->close_month, 0, 'まだ仮の期待値');

done_testing;

「まず、仮の期待値で書きます」

is の行は、左から、実際に出た値、こうだろうと期待する値、この検めの名前、の三つを並べる形になっているという。

0 は、もちろん正しい答えではない。分かっていて、わざと間違った数字を書き込む。

動かすと、落ちた。

1
2
3
4
5
not ok 1 - まだ仮の期待値
#   Failed test 'まだ仮の期待値'
#   at t/register_close_month.t line 21.
#          got: '79617'
#     expected: '0'

「これで、落ちたところに、今の実際の値が出ます」

got: のところに書いてあるのが、本当の答えだった。79617。それを、そのまま書き写す。

「……これ、正しい値を考えなくていいんですか」

ナギさんが訊いた。

「考えません。今、出ている値を、写すだけです」

書き写したら、次はテストの名前だった。最初は仮の名前がついていたが、それを「今、何を書き取ったか」が分かる名前に変える。

「仮の名前のままでは、いけないんですか」

私が訊いた。

「いけません。仮の名前は、書いた本人にしか分かりません。あとで別の誰かがこの紙を見たとき、名前だけで何を書き取ったかが分かるようにしておきます」

1
is($register->close_month, 79617, '月末の締めが今出している値をそのまま書き取る');

一通りの手順を終えると、クロベさんは念を押した。

close_month の中身は、今日は一行も変えません。作るのは、この新しいテストの紙だけです」

「何も直っていない、ということですか」

「何も直っていません。今日は、写し取っただけです」

仮の値を書き、動かして落とし、出てきた実測値をそのまま書き写し、最後に名前を改める。今日クロベさんがやったのは、この四つだけだった。

Characterization Testの4ステップ(仮の期待値を書く→動かして落とす→実測値をそのまま書き写す→名前を改める)と、変更しないclose_monthの中身

何も直っていない。それでも、次に何をすればいいのかは、はっきりしていた。

第4幕: 検め(けんめ)と引き受け ── 野帳に、初めて書き込む

書き写した値でテストを動かすと、通った。画面に並んだ短い緑の文字を見て、ナギさんが小さく息をついた。

「これで、いいんですか」

「まだです。これだけでは、何が起きても通ってしまう紙かもしれません」

通った、というだけでは、何を確かめたことになるのか、私にはまだ分からなかった。

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

「一度、壊してみてください」

ナギさんが、close_month の合計する箇所を、一時的に書き換えた。すべてを足し合わせる代わりに、0 を返すだけの形にする。

1
2
3
4
5
sub close_month {
    my ($self) = @_;
    my $total = 0;
    return sprintf('%d', $total);    # わざと壊す。足し合わせない
}

動かすと、落ちた。

1
2
3
4
5
not ok 1 - 月末の締めが今出している値をそのまま書き取る
#   Failed test '月末の締めが今出している値をそのまま書き取る'
#   at t/register_close_month.t line 21.
#          got: '0'
#     expected: '79617'

「……落ちました」

「落ちるはずのところで、落ちた。これで、この紙は何かが変わったことに気づける、と言えます」

ナギさんが、書き換えを元に戻す。もう一度動かすと、通った。

1
2
ok 1 - 月末の締めが今出している値をそのまま書き取る
1..1

「壊して、確かめて、戻す。これをやらんと、この紙が本当に見ているのか、それとも何も見ずに通っているだけなのか、分からんままです」

私はその一連を見ていて、これは案外、念入りな仕事なのだと思った。書き写しただけで終わる話ではなかった。

そのあと、私自身の番だった。クロベさんに言われて、実際の帳面――日々の取引を書き付けた、あの紙――を全部、端から順に目で追った。書き写した数字が、明らかにおかしな値でないかを確かめる。

十件、一つずつ。金額を見て、値引きがあればその率を見て、次の行へ移る。飛ばしたり、大きな額のところだけを拾い読みしたりはしなかった。クロベさんからは、何を見ればいいかという指示はなかった。ただ「全部、見てください」とだけ言われた。

途中で、指が止まった行があった。値引きの率が細かい数字のところだった。見間違えていないか、もう一度、同じ行を目で追い直した。それでも、おかしいとは思わなかった。

正しいかどうかは、私にも分からなかった。ただ、見た限り、おかしくはなかった。それだけのことを確かめるのに、思いのほか時間がかかった。選り分けたり、目立つところだけを見たりはしなかった。全部を、順番に見ていくしかなかった。

一通り終えると、クロベさんが区切りをつけた。

「保証できることと、できないことを、分けておきます」

「まず、保証できること。この一月ぶんの取引を渡せば、必ずこの値が返ります。この先、誰かがclose_monthに手を入れて、この値が変わったら、この紙が気づきます」

「保証できないこと。このずれが、間違いなのか、決まりなのかは、まだ分かりません。今日、検めたのはこの一月ぶんの取引だけで、他の月や、他の組み合わせがどうなるかは、まだ分かりません」

「来月の取引でも、今日書き取った値と同じことが言えますか」

私が訊いた。

「言えません。来月ぶんは、来月、また同じやり方で写し取ります。今日の紙が守っているのは、今日渡した、この十件だけです」

「思ったより、狭い範囲なんですね」

「狭いです。ただ、狭いところから始めんと、広いところは検められません」

「直すかどうかは、まだ決まらないんですね」

「はい。それは、次の工程です」

その言い方に、私はどこか安心した。分からないことを、分からないままにしておいてよい、と言われたような気がした。

作業が終わったころには、素屋根の下の光は、もうだいぶ高いところまで昇っていた。ナギさんは道具箱を片付け始めている。クロベさんは、また少しのあいだ、建物を見上げていた。

私は帳場に戻り、野帳を開いた。今日の見立てと、手を入れた所と、触っていない所を書き付けていく。ペン先が紙にあたる感触は、いつもと同じだった。最後に、「私が引き受けたこと」という欄が残った。

何を書けばいいのか分からなかったのではない。初めて、自分の言葉で書く欄だった。

しばらく、手が止まった。それから、一行だけ書いた。

野帳を、自分で付ける。

これから先、この帳場の数字に何か気になることが出るたび、直す前に、まず今の形を仕様化テストとして自分の手で書き取る。今日、クロベさんとナギさんがやったことを、次からは自分でやる、ということだった。

書いてしまうと、それだけのことだった。けれど、この帳面を三十年つけてきて、こんな一行を書いたのは初めてだった。

窓の外を見ると、朝の低い光はもう消えていた。今日の工事は、まだ何も始まっていない。それでも、この店の勘定のことで、何かが一つ、確かに動いた日だった。


普請控

  • 見立て: 帳場の締め集計(close_month)が、月によってわずかに(一円、まれに数円)異なる数字を出す。値引きの多い月に起きやすい。二十年、これが間違いなのか決まりなのか、誰にも言えなかった
  • 手を入れた所: 仕様化テスト(Characterization Test)をt/register_close_month.tとして追加した。Register.pmclose_monthrecord_sale)は一行も変えていない
  • 触っていない所: close_monthの端数の落とし方(月末に一度だけ切り捨てる作り)と、その計算の順序。record_saleの値引きの掛け方
  • 次の工程へ送ること: このずれが直すべき間違いなのか、意図された決まりなのかの判断。検めたのは今回の一月ぶんの取引だけで、他の月・他の組み合わせはまだ
  • 私が引き受けたこと: 野帳を自分で付ける
comments powered by Disqus
システム開発・AIワークフローのご相談は Meetsource
Hugo で構築されています。
テーマ StackJimmy によって設計されています。