Featured image of post コードカーペンター【Sprout Method】「そこ、まだ触るな」〜既存の中に足さず、新しい根を継ぐ〜

コードカーペンター【Sprout Method】「そこ、まだ触るな」〜既存の中に足さず、新しい根を継ぐ〜

配達料に遠方割増を足したいという依頼が来る回。ナギが手を動かす前に、クロベが既存の計算へ直接書き足すのを止めます。新しい処理は芽生やしメソッド(Sprout Method)として別に起こし、既存へは呼び出し一行だけを足します。

第1幕: 検分 ── 台所、まだ何も書かれていない画面

朝の台所には、出汁のにおいがまだうっすら残っていた。竈の火はもう落ちていて、代わりに勝手口から差し込む光が、土間の隅まで届いている。勝手口の外では、配達の荷を積んだ大八車が一台、通り過ぎていく音がした。荷が籠にぶつかる小さな音が、しばらく尾を引いて消える。

私は帳場のほうから、野帳を手に台所へ向かっていた。今日、店主から聞いた話をクロベさんに伝えるつもりだった。話は昨日のうちに聞いていたが、朝の掃除やら仕込みやらに追われて、伝えるのがこの時間になっていた。台所は店の裏手にあって、帳場からは少し歩く。普段、私が用があるのは帳場か通り庭で、台所に足を運ぶことはあまりない。

勝手口の近くまで来たとき、声が先に聞こえた。

「そこ、まだ触るな」

クロベさんの声だった。いつもより短く、少し速い。私はまだ台所に入っていなかったが、その声の速さだけで、何かが起きかけていたのは伝わった。

戸口をくぐると、ナギさんがノートの前で手を止めていた。指先はまだキーボードの上にあったが、動いてはいない。クロベさんはその隣に立ち、画面をのぞき込んでいた。

台所の板の間に、道具箱と並んでノートが置かれている。竈と水屋のあいだの、普段なら擂鉢や笊が積んである場所だった。工事のあいだだけ、そこが二人の作業台になっている。湯気の匂いと、機械の小さな駆動音が、同じ部屋の中で混じっていた。

「今、何を止めたんですか」

私が訊くと、クロベさんが顔を上げた。

「店主さんから、配達料に割増を付けたい、という話が来ていたそうですね」

「はい。届け先が町の外れまで広がったので、遠方には割増を、と」

近ごろ、店の評判を聞きつけて、少し離れた在所からも注文が入るようになっていた。これまでは町内で完結していた配達が、川向こうや、峠の手前の集落まで届くようになっている。店主はそれを喜びつつも、これまでと同じ配達料では割に合わない、と言っていた。

言ってから、私は気づいた。私が持ってきた話と、今しがた止められた話は、同じものだった。

「ナギさんが、今それをやろうとしていました」

クロベさんが言った。責めるような調子ではなかった。ただ、事実をそのまま置くような言い方だった。

ナギさんは手を膝の上に戻し、少しばつが悪そうにこちらを見た。

「配達料の計算に、遠方の割増を足せばいいだけだと思ったんです」

画面には、まだ何も書かれていなかった。手が止まっただけで、コードは一行も増えていない。何も書かれていない画面を前に人が止められている、という光景を、私は初めて見た。

「駄目だったんですか、今のは」

私が訊くと、クロベさんは首を横に振らなかった。目だけをナギさんの手元へ向けたまま、答えた。

「駄目、というより、まだです。理由はあります」

その理由がまだ語られないまま、台所の朝は、いつもより少し静かに続いていた。

第2幕: 手が入らない ── クロベの先回り

ナギさんが、もう一度自分の言葉で言い直した。

「配達料の計算に、遠方の割増を足せばいいんですよね」

そう言いながら、もう一度キーボードへ手を伸ばしかける。

「まだです」

クロベさんが繰り返した。今度は、すぐに理由を続けた。

「今の配達料の計算には、守りのテストが一つもありません。そこへ、そのまま割増の分岐を書き足したら、どうなりますか」

ナギさんは少し考えてから答えた。

「動くとは思いますけど」

「動くでしょう。ですが、動いたことをどう確かめますか」

ナギさんは、少し詰まった。

「重さと距離と、繁忙期の組み合わせを、いくつか試してみれば」

「その組み合わせは、今、いくつありますか」

クロベさんが訊き返した。ナギさんが指を折って数え始めたが、途中で止めた。重さが超えるか超えないか、距離が超えるか超えないか、繁忙期かどうか、そこへ遠方かどうかが加わる。組み合わせは、思ったより多い。

「今の計算は、重さと、距離と、繁忙期の、三つの条件が積み重なっています。そこへ四つ目を直に書き込めば、割増の分だけを取り出して確かめる手立てがなくなります。確かめるとしたら、三つの条件全部と組み合わせて、計算全体をもう一度動かすしかありません」

「それは、面倒ですけど、できないわけじゃ」

「できます。ですが、それは今日やることではありません。それに」

クロベさんは、画面に映った計算を指した。

「今でさえ、三つの条件が重なって読みにくくなっています。そこへ四つ目を書き込めば、読みにくさはさらに増します。新しい割増を、既存の中に埋め込んではいけません」

ナギさんは、それ以上言い返さなかった。伸ばしかけていた手を、静かに引っ込める。反論の材料が、最初から無かったのだと思う。ナギさんはいつも、止められたあとに一度食い下がる人だったから、今日の様子は少し珍しかった。

しばらくして、ナギさんがぽつりと言った。

「……危なかったです、これ」

それだけだった。誰かに向けて言ったというより、自分の手元に向けて、確かめるように落とした一言だった。

私はそのやり取りを、少し離れたところで見ていた。「守りのテストが一つもない」という言葉が、なぜか自分の帳面のことと重なった。私が三十年つけてきたあの帳面にも、書き取っていないことの方が、きっと多い。野帳を持つ手に、少し力が入るのが分かった。

「では、どうすればいいんですか」

私が訊くと、クロベさんはようやく画面から顔を上げた。

「これから、お見せします」

第3幕: 手立て ── 根継ぎ、新しい根を継ぐ

クロベさんが画面に呼び出したのは、Delivery.pmという名前のファイルだった。書かれているのはcalc_fee一つだけだった。

 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
26
package Delivery;

use strict;
use warnings;

sub calc_fee {
    my ($weight_kg, $distance_km, $is_busy_season) = @_;

    my $fee = 300;

    if ($weight_kg > 5) {
        $fee += 200;
    }

    if ($distance_km > 3) {
        $fee += 150;
    }

    if ($is_busy_season) {
        $fee += 100;
    }

    return $fee;
}

1;

クロベさんが、上から順に指でなぞった。

「基本料金は三百円。重さが五キロを超えれば二百円。距離が三キロを超えれば百五十円。繁忙期なら百円。この三つが、今この関数が支えているものです」

なぞっていた指を止め、体ごとこちらへ向き直った。

「今日広がったのは、この三つの外側の話です。遠方という条件は、これまで一度も、この関数の中に無かった」

「無かったものを、足すんですよね。それと、さっき止められたことと、どう繋がるんですか」

ナギさんが言った。中に書くという話ではなく、今度は素直に、先の理由の続きを求める問いだった。

「足す場所は、この中である必要はありません。今日使うのは、芽生やしメソッド(Sprout Method)という技法です」

クロベさんはそこで一度言葉を切り、定義を口にした。

「足す処理を、既存の中には書かず、新しい独立した処理として別に書き、既存からはその呼び出しを一行だけ足す。手順は四つ。差し込む場所を決める。新しい処理を別のsubとして書く。そのsubだけを確かめる。最後に、呼び出しを一行足す」

「別に書いて、一行だけ足す……」

私はその言葉を、野帳に書き取った。書き取る手は動いても、なぜそれで済むのかは、まだ頭の中で線が繋がっていなかった。

「前に聞いたような気がします。似たような、コードを整理する技法が、他にもあるんじゃないですか」

ナギさんが訊いた。

「あります。ですが、そのうちの多くは、テストがすでにある前提で使うものです。今のこの関数には、テストがありません。テストが無い場所を、テストがある前提の技法でいきなり整理しようとすると、途中で足を踏み外します。芽生やしメソッドは、テストが無い場所で、まず新しい部分だけを安全な場所に立たせるための技法です」

クロベさんは、私のその顔を見たのか、続けて言った。

「これには、大工の呼び方もあります。根継ぎと言います」

「根継ぎ……」

クロベさんは、台所の柱の一本を指した。よく見ると、根元近くに、木目の違う継ぎ目が横に一本走っている。これまで気にしたこともなかった。

「これも、根継ぎです。この柱、根元だけ傷んでいたので、そこだけ切って、新しい材を足しました。上のほうは、建てたときのままです」

「柱ごと、替えなかったんですか」

ナギさんが訊いた。

「替えませんでした。傷んでいたのは根元だけです。柱全体を作り直すよりも、傷んでいない部分をそのまま生かせます。根継ぎは、傷みを直すためだけの技ではありません。足りない一点だけを新しい材で足す、という考え方そのものです。今日のこの関数は傷んでいるわけではありませんが、丸ごと書き直す必要が無い、という点は同じです」

ナギさんが、少し納得したような顔をした。

「じゃあ、まず何をするんですか」

「まず、今のcalc_feeが、今どう動いているかを書き取ります。ここには守りのテストが一つもありませんでしたから。この書き取りを、仕様化テストと呼びます」

「それ、前にもやったやつですね」

ナギさんが言った。ずいぶん前のことのように感じたが、確かにそうだった。仮の期待値を書いて、動かして失敗させて、出てきた実際の値をそのまま書き写す。あの手順を、代表的ないくつかの組み合わせについて、順番に繰り返していく。

 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
26
27
use strict;
use warnings;
use Test::More;
use lib 'lib';
use Delivery;

subtest '基本料金のみ' => sub {
    is(Delivery::calc_fee(3, 2, 0), 300, '重さ・距離・繁忙期のいずれも条件を満たさなければ基本料金300円のまま');
};

subtest '重さの加算' => sub {
    is(Delivery::calc_fee(6, 2, 0), 500, '重さが5kgを超えると200円加算されて500円になる');
};

subtest '距離の加算' => sub {
    is(Delivery::calc_fee(3, 4, 0), 450, '距離が3kmを超えると150円加算されて450円になる');
};

subtest '繁忙期の加算' => sub {
    is(Delivery::calc_fee(3, 2, 1), 400, '繁忙期フラグが立つと100円加算されて400円になる');
};

subtest '三条件すべて・これまでの商圏の端(距離10km)' => sub {
    is(Delivery::calc_fee(6, 10, 1), 750, '重さ・距離・繁忙期の三条件すべてが重なると300+200+150+100の750円になる(距離ちょうど10kmは商圏の端でまだ加算対象)');
};

done_testing;

「距離十キロの組み合わせを、わざわざ入れているんですね」

私が気づいて言うと、クロベさんがうなずいた。

「そこが、これまでの商圏の端です。遠方の割増は『十キロを超えたら』という条件にするつもりですから、ちょうど十キロのところで、まだ加算されないことを確かめておきます。境目を、境目として書き取っておく、ということです」

最初は、仮の期待値をPLACEHOLDERのまま置いて動かした。案の定、失敗の報告が返ってくる。

1
2
3
#   Failed test 'PLACEHOLDER'
#          got: '300'
#     expected: 'PLACEHOLDER'

「ここに出た実際の値を、そのまま期待値として書き写します」

クロベさんが、PLACEHOLDERのところに300を書き込んだ。他の四つも同じ手順で、実際に動いて出てきた値をそのまま採用していく。仮の名前だったsubtestにも、何を確かめたかが分かる日本語の名前を付け直した。

代表的な五つの組み合わせがすべて通ったのを確認してから、クロベさんは新しいsubを書き始めた。

「ここからは、書き取るのではなく、初めから狙って書く番です」

「違うんですか、さっきと」

ナギさんが訊いた。

「さっきのは、今すでにある振る舞いを書き取っただけです。仮の答えを置いて、実際に動かして、出てきた値をそのまま採用しました。でも、これから書くのは、まだこの世に無いものです。狙った通りに動くはずのものを、最初から狙った値で書きます」

1
2
3
4
5
sub calc_remote_surcharge {
    my ($distance_km) = @_;

    return $distance_km > 10 ? 250 : 0;
}

クロベさんは、これだけをまずDelivery.pmに書き加えた。calc_feeには、まだ触れていない。そして、この新しいsubだけを確かめるテストを書いた。

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

subtest '商圏の端(10km)はまだ割増の対象外' => sub {
    is(Delivery::calc_remote_surcharge(10), 0, 'ちょうど10kmは境界値として割増を付けない');
};

subtest '商圏の端を1kmでも超えたら割増' => sub {
    is(Delivery::calc_remote_surcharge(11), 250, '11kmは商圏の外なので250円の割増が付く');
};

subtest '町内は対象外' => sub {
    is(Delivery::calc_remote_surcharge(3), 0, '3kmは町内なので割増は付かない');
};

done_testing;

動かしてみると、三つとも一度で通った。当たり前だ、狙った値をそのまま書いたのだから。だが、その「当たり前」こそが今日の目的だった。この小さな計算だけを取り出して、単独で確かめられる形になっている。

「ここまでは、calc_feeは一文字も変わっていません」

クロベさんが言った。

「新しい処理を、別のクラスではなく、同じDeliveryの中に置いたんですね」

私が気づいて言うと、クロベさんはうなずいた。

「今回はそれで足ります。Deliveryは今のままでも扱いやすく、新しい計算も、まったく新しい役目を背負うわけではありません。もし事情が違えば──扱いにくい相手だったり、まるで別の役目を持ち込むことになったりすれば──話は変わってきますが、それは今日の範囲ではありません」

「今日の範囲ではない」という一言だけが、耳の奥に残った。この先にまだ何かがある、という気配だけを置いたまま、クロベさんの目は画面へ戻っていた。

「最後に、呼び出しを一行足します」

そう言って、calc_feeのreturnの手前に、一行だけ書き加えた。

 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
26
27
28
29
30
31
32
33
34
package Delivery;

use strict;
use warnings;

sub calc_fee {
    my ($weight_kg, $distance_km, $is_busy_season) = @_;

    my $fee = 300;

    if ($weight_kg > 5) {
        $fee += 200;
    }

    if ($distance_km > 3) {
        $fee += 150;
    }

    if ($is_busy_season) {
        $fee += 100;
    }

    $fee += calc_remote_surcharge($distance_km);

    return $fee;
}

sub calc_remote_surcharge {
    my ($distance_km) = @_;

    return $distance_km > 10 ? 250 : 0;
}

1;

クロベさんの指が、calc_feeの中の新しい一行と、その下に生まれたばかりのcalc_remote_surchargeのあいだを、すっと一往復した。私はその動きを、野帳の余白に線として写し取ってみた。三つの条件はそのままの形で残り、そこに一本の矢印だけが増えている。

calc_feeの構造図。重さ・距離・繁忙期の3条件は灰色破線で変更なしを示し、新しい呼び出し一行が実線でcalc_remote_surchargeへ矢印を引く。calc_remote_surchargeは距離だけを受け取る独立した新設カード

「一行だけ、ですか」

私は、聞いたことをそのまま繰り返すように口にした。クロベさんが、それでいい、というふうにうなずいた。

「一行だけです。引数も増えていません。今使えるものだけで、足りましたから」

ナギさんが、今度は落ち着いた手つきで、その一行を実際に打った。さっき止められたときとは、同じ手なのに、まるで違う動きに見えた。

第4幕: 検め ── 小さな足し物なら

一行を足したあと、クロベさんは最初に書いた仕様化テストを、もう一度動かした。

「これが、変わらず通れば、今までの客への配達料は、何も変わっていないということです」

五つのsubtestが、すべて変わらず通った。距離十キロの組み合わせも、七百五十円のままだった。境目のところで、余計な割増が付いていないことが、これではっきりした。

続けて、新しく町の外れへ配達する場合のテストを書いた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use strict;
use warnings;
use Test::More;
use lib 'lib';
use Delivery;

subtest '町の外れへの配達・三条件と割増がすべて重なる' => sub {
    is(Delivery::calc_fee(6, 11, 1), 1000, '重さ・距離・繁忙期の300+200+150+100に加え、11kmの遠方割増250が一行の呼び出しで加算されて1000円になる');
};

done_testing;

こちらも一度で通った。

「一つの組み合わせを確かめただけで、いいんですか。さっきは、組み合わせが多すぎるという話でしたのに」

私が訊くと、クロベさんが答えた。

「遠方割増は、距離だけを見ています。重さや繁忙期の値には、一切触れていません。だから、足し合わせさえ正しければ、組み合わせを総当たりする必要はありません。さっきcalc_remote_surchargeだけを単独で確かめておいたのは、そのためです。calc_feeのほうも、もう三つの条件と割増を全部自分で計算してはいません。それぞれの答えを呼びに行って、合計するだけになりました」

クロベさんが、順に区切って言った。

「保証するのはここまでです。これまでの商圏、距離十キロ以内の配達料は、今日の変更の前後で一切変わりません。遠方割増の計算そのものも、単独で確かめてあります。ですが」

一拍置いて、続けた。

「もし今後、割増の条件が、時間帯や天候のようなものまで絡んでくるようになったら、それはこのcalc_feeにとって、新しい責務が増えることになります。そのときは、今日のように、一行足すだけでは済まないかもしれません」

クロベさんの言葉を、私は野帳の上でもう一度線に直してみた。今日言い切れるのは、変わらない三条件と、独立して確かめた遠方割増と、そのふたつを足し合わせる一箇所だけ。それ以外は、まだ何も言い切っていない。

テスト戦略の前後比較図。左は直接書き足した場合の総当たり確認、右は根継ぎ後の内訳(仕様化テスト5件は変更なしで灰色破線、単体テスト3件と統合確認1件は新規で実線)

「済まないときは、どうなるんですか」

私が訊いた。クロベさんの視線は、calc_feeの画面に留まったままだった。

「今日はそこまでは踏み込みません。今日は、一行で足りる範囲の話です」

その先を、私は追わなかった。今日のところは、それで十分だった。

「守りのテストが無い、というのは、私が三十年つけてきたあの帳面にも、同じ穴があるということですか」

私は、さっきから気になっていたことを、思い切って訊いた。

「近いかもしれません。テストが無いというのは、今のcalc_feeが正しく動いているかどうかを、誰も言い切れない、ということです。帳面にも、書き取っていないことの方が多いなら、同じことが起きています」

クロベさんが答えた。

「今日のように何かを足すたび、その言い切れなさに、新しい言い切れなさが重なっていきます。今のうちに、せめて新しく足す部分だけでも、確かに動くと言い切れる場所を作っておく。それが、今日やっていることです」

「全部を確かめなくても、いいんですか」

「全部を、今日一日で確かめることはできません。今日は、新しく足す一点だけを確かに言い切れるようにします。それだけでも、今日より昨日のほうが危うかった、ということにはなります」

すべてを一度に片付けるのではなく、今日言い切れる範囲を、少しずつ広げていく。そのやり方は、この店の帳面を三十年つけてきた自分にも、どこか馴染むものだと思えた。

台所には、いつのまにか昼の支度の物音が混じり始めていた。水屋の奥で鍋を洗う音、竈に薪をくべる乾いた音。ナギさんは道具──といっても今日は画面だけだったが──を片付け、クロベさんは腕を組んで、しばらく画面を見ていた。さっきまでノートが置かれていた場所に、誰かが笊を戻しに来て、少し困った顔をしてから、隅に寄せて置いていった。

帳場に戻ってから、野帳を広げた。今日は、順番を変えて書いた。見立てと手を入れた所より先に、まだ何も書いていない最後の欄に、一行だけ書き付ける。

小さな足し物なら自分の判断で足してよい。

書いてから、自分に問い直した。小さい、とはどこまでだろう。

クロベさんが言っていたことを思い出す。新しい責務が増えるかどうか。今のうちに手当てできる大きさかどうか。それをそのまま書き写すのではなく、少し考えてから、自分の言葉に直した。

今日みたいに、今の関数の名前のままで済むかどうか。それが、私にとっての境目になりそうだった。

もし今度、今日とは違う何かが来たら──今のDeliveryのままでは済まない何かが来たら──そのときは、また棟梁を呼べばいい。今日はそこまで考えなくていい。そう自分に言い聞かせて、野帳を閉じかけた手を、もう一度止めた。境目の言葉を、もう少しだけ具体的にしておきたかった。

一行で足りるうちは、自分で決める。一行で足りなくなったら、それはもう「小さな足し物」ではない。そう書き足してから、ようやくペンを置いた。

窓の外では、配達の荷を積んだ車が、また一台通り過ぎていった。今日のうちに、あの車のどれかが、町の外れまで走ることになるのだろう。

クロベさんが道具箱の上に手を置いたまま、こちらを見ずに言った。

「その境目は、悪くありません」

それだけだった。褒めるでもなく、直すでもなく、ただそう置かれた一言だった。私は野帳を閉じながら、小さくうなずいた。書き取った振る舞いと、確かめた新しい計算と、今日ようやく自分の言葉に直した境目が、この一冊の中でちゃんと繋がっていた。


普請控

  • 見立て: 配達の届け先が町の外れまで広がり、店主が遠方の届け先に割増を付けたいと言ってきた。既存の配達料計算(Delivery::calc_fee)は重さ・距離・繁忙期の三条件を含み、守りのテストがまだ無かった
  • 手を入れた所: 芽生やしメソッド(Sprout Method)を使い、既存calc_feeの今の振る舞いを仕様化テストとして代表的な組み合わせで書き取った上で、遠方割増の計算を新しいsub、calc_remote_surchargeとして別に起こし、単独でテストした。calc_feeへは呼び出し一行だけを足した
  • 触っていない所: calc_feeの重さ・距離・繁忙期それぞれの条件の中身そのもの。割増をさらに細かい距離帯や、時間帯・天候といった別の条件で分けるかどうかの判断
  • 次の工程へ送ること: 割増の条件が今後増えたとき、一行では済まなくなる境目をどう見極めるか
  • 私が引き受けたこと: 小さな足し物なら自分の判断で足してよい
comments powered by Disqus
システム開発・AIワークフローのご相談は Meetsource へ
Hugo で構築されています。
テーマ Stack は Jimmy によって設計されています。