Featured image of post コードシェフの仕込み帳【Command】戻せる手、戻せない手〜やり直しのきかない操作を、履歴を持つ部品にまとめる〜

コードシェフの仕込み帳【Command】戻せる手、戻せない手〜やり直しのきかない操作を、履歴を持つ部品にまとめる〜

「注文する」「取り消す」操作が手続きとして散らばり、取り消しも履歴も持てないコードを、Commandパターンで整理します。PerlとMooで操作をexecute/undoを持つ部品にまとめ、やり直しのきく形へ。仕組みから丁寧に解説します。

ランチの山が、ようやく越えたところだった。

厨房はまだ熱を持っていて、油のはぜる音と、出汁の湯気と、「お待ち!」の声が、まだ少しだけ重なっている。私はホールと厨房を行ったり来たりして、空いた皿を下げていた。注文は紙の伝票で入ってくる。シェフはそれを、カウンターの奥に立った金属の串——伝票刺しに、一枚ずつ刺していく。上から順に、刺さった伝票をさばいていく。

入り口近くの席のお客さんが、ふと顔を上げて言った。「すみません、さっき頼んだ唐揚げ定食、やっぱり焼き魚定食に変えられますか」

シェフは手を止めて、伝票刺しから一枚を抜いた。その人の伝票だ。さっと目を落として、「まだ手をつけてない。今なら戻せる」と言う。抜いた伝票をくしゃっと丸めて、新しい伝票に書き直し、また刺した。

その少し前、別のお客さんが「やっぱりさっきのは、なしで」と言ったときは、違った。シェフはちらりと厨房の奥を見て、「もう出しちまった。これは戻せねえ。作り直すしかねえな」と言った。

私はその二つの場面を、なんとなく覚えていた。戻せる注文と、戻せない注文がある。同じ「やっぱりやめます」でも、伝票を抜けば済むときと、もうどうにもならないときがある。

ピークが引けて、店が静かになった。中休みの、いちばん気の抜ける時間。引き戸が、遠慮がちに開いた。

本日の持ち込み

入ってきたのは、三十代半ばくらいの女性だった。きちんとした身なりで、入り口で一度立ち止まって、それから「お忙しいところ、すみません」と頭を下げた。

「職場の先輩に、設計で詰まったらここに行ってみろと言われて、来ました」。ゆっくり、言葉を選ぶような話し方だった。急いでいる様子はないけれど、ずっと考えてきたことがある、という顔をしている。

シェフは伝票刺しに残った最後の一枚を片付けてから、「座れ。何を持ち込んだ」と言った。

彼女はカウンターにノートPCを開いた。「飲食店の、レジ——会計のシステムを保守しています。店員さんが商品を打って、クーポン割引を入れて、という操作を、こういうメソッドで書いてきました」

 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 Cart;
use Moo;
use v5.36;

has lines => (is => 'ro', default => sub { [] });   # [{ name, price }, ...]

# 商品を打つ
sub add_item {
    my ($self, $name, $price) = @_;
    push $self->lines->@*, { name => $name, price => $price };
}

# クーポン割引(定額を引く)
sub apply_coupon {
    my ($self, $amount) = @_;
    push $self->lines->@*, { name => 'クーポン割引', price => -$amount };
}

sub total {
    my $self = shift;
    my $sum = 0;
    $sum += $_->{price} for $self->lines->@*;
    return $sum;
}

add_item で商品を一行足して、apply_coupon で割引の行を足す。合計は、明細を全部足すだけです。最初は、これで何も問題ありませんでした」と彼女は言った。

私にはコードの中身は読めない。でも「商品を打つ」「割引を入れる」という言葉が、さっきまでの伝票のやりとりと重なって聞こえた。注文を一つずつ受けて、さばいていく。あれと同じことを、コードでやっているんだろうか。

「問題は、先月でした」と彼女は続けた。「店舗から、要望が来たんです」

やった手が、残らない

彼女は、指を折りながら要望を挙げていった。

「一つ。店員さんが打ち間違えたとき、その場で一手ずつ戻せるように。二つ。レジ締めのときに、『この会計で何をしたか』を店長が確認できるように。三つ。間違えて戻しすぎたら、やり直せるように」

「応急処置で、これを書きました」と、彼女はもう一つのメソッドを見せた。

1
2
3
4
5
# 応急処置: 最後の一手を取り消す
sub void_last {
    my $self = shift;
    pop $self->lines->@*;   # 末尾を消すだけ
}

「直前の一手なら、これで戻せます。pop で、最後の明細を消すだけなので」。彼女はそこで、言葉を選ぶように少し黙った。「でも——三手前まで戻すのも、操作のログを出すのも、やり直しも、どう書けばいいのか、どうしても分からなくて。pop で戻せてはいるのに、それ以上が、まったく」

シェフは画面をしばらく眺めてから、void_last のあたりを指でとんとん、と叩いた。

「これは、最後の皿を下げてるだけだ。何を下げたかは、どこにも書いてない」

そう言って、シェフはさっき片付けた伝票刺しに目をやった。「さっき、客が『さっきの注文、変えてくれ』と言った。俺はどうした?」

彼女は少し考えて、「……伝票を、抜いていました」と答えた。

「そうだ。伝票が刺さってたから、抜いて戻せた。お前さんのコードには、その伝票がない。やった操作が、どこにも刺さってないんだ」

ここで、今回の問題の名前が出てきた。

unrecorded-operations(記録されない操作): 操作を手続きとして直接実行し、何を・どの順でやったかを残さない設計。結果(明細)は残るが、操作そのものが残らないため、取り消し・ログ・やり直しが原理的にできなくなる。

add_itemapply_coupon も、操作を実行して、そのまま消えてる」とシェフは言った。「残るのは、出来上がった明細だけだ。『何をした手なのか』『どの順でやったか』は、どこにも残ってない。だから——後から戻す、見せる、やり直す、ができない」

彼女は静かにうなずいた。「最初は、操作はただ実行すればよかったんです。後から振り返る必要が、なかったので。要望が来て初めて、気づきました。私は『やった操作そのもの』を、どこにも持っていなかった」

私は、皿を拭く手が少し止まっていた。「やった操作が、残らない」。その言葉が、胸の奥に小さく引っかかった。前にここで見つけた「散らばり」とも、「入れ子の if」とも違う、別の形。でも、何が引っかかったのか、まだ言葉にならなかった。私は彼女の話に意識を戻した。

操作を、物にする

「やった操作を、一枚の伝票にする」とシェフは言って、コードを書き始めた。

まず、伝票そのものの「決まり」を作った。

1
2
3
4
5
6
7
8
package Operation;
use Moo::Role;
use v5.36;

requires 'execute', 'undo', 'label';
# execute: 操作を実行する
# undo:    その操作を取り消す(自分の戻し方を、自分で知っている)
# label:   ログ表示用の一行

Command(コマンド): 操作(〜する、という一手)を、実行(execute)と取り消し(undo)を持つオブジェクトにまとめる技法。GoF の振る舞いパターンのひとつ。操作を「物」にすることで、後から積んだり、取り消したり、記録したりできるようになる。その共通の決まりを定めるのが、requires 'execute', 'undo', 'label' という **Operation という Role(役割)**だ。

requires ってのは」とシェフは言った。「この役割を名乗るなら、この三つのメソッドを必ず持て、という約束だ。Perl は、メソッドがあるかどうかを後から確かめる言葉でな。この約束は、Operation を名乗るクラスを読み込んだその瞬間に、『お前、execute と undo と label を持ってるか?』と確かめる。持ってなきゃ、その場で止める」

次に、具体的な操作を一つ。「商品を打つ」だ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package AddItem;
use Moo;
use v5.36;
with 'Operation';

has cart  => (is => 'ro', required => 1);   # 操作する相手
has name  => (is => 'ro', required => 1);
has price => (is => 'ro', required => 1);

sub execute {
    my $self = shift;
    push $self->cart->lines->@*, { name => $self->name, price => $self->price };
}

sub undo {
    my $self = shift;
    pop $self->cart->lines->@*;   # 自分が足した一行を自分で取り除く
}

sub label {
    my $self = shift;
    return sprintf '%s を追加 (%d円)', $self->name, $self->price;
}

AddItem という物にした」とシェフは言った。「中に、やること(execute)と、戻し方(undo)の両方が書いてある」。cart は、操作する相手——会計の中身だ。これを Receiver(受け手) と呼ぶ。実際に処理されるものだ。

同じ調子で、もう一つ。「クーポン割引」も伝票にした。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package ApplyCoupon;
use Moo;
use v5.36;
with 'Operation';

has cart   => (is => 'ro', required => 1);
has amount => (is => 'ro', required => 1);

sub execute {
    my $self = shift;
    push $self->cart->lines->@*, { name => 'クーポン割引', price => -$self->amount };
}

sub undo {
    my $self = shift;
    pop $self->cart->lines->@*;
}

sub label {
    my $self = shift;
    return sprintf 'クーポン割引 (-%d円)', $self->amount;
}

AddItemApplyCoupon も、一つの操作を表すクラスだ。これを ConcreteCommand(具体コマンド) と呼ぶ。受け手への操作と、その戻し方を、セットで持っている。

彼女は、画面をじっと見ていた。それから、少し言いにくそうに口を開いた。

「……すみません、正直に言うと」と彼女は言った。「これ、add_item の中身を、ほとんどそのまま AddItem クラスに移しただけ、ですよね。しかも、undo を別に書いている分、コードはむしろ増えています。void_last で、直前は戻せていたんです。これで、何が良くなるんでしょうか」

批判ではなかった。本当に分からない、という顔だった。私も、同じことを思っていた。伝票という言い方はきれいだけれど、やっていることは、手続きをクラスに移しただけに見える。

シェフは、手を止めた。「いい疑いだ。行は増えた。それは認める」

それから、「さっきの要望、一つずつやってみるぞ」と言って、新しいクラスを書き始めた。

戻す、見せる、やり直す

シェフが書いたのは、伝票を管理する側だった。

 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 OrderPad;
use Moo;
use v5.36;

has history    => (is => 'ro', default => sub { [] });   # 実行済みの伝票(後入れ先出し)
has redo_stack => (is => 'ro', default => sub { [] });   # 戻した伝票(やり直し用)

sub do_operation {
    my ($self, $op) = @_;
    $op->execute;
    push $self->history->@*, $op;
    @{ $self->redo_stack } = ();   # 新しい操作をしたら、やり直しは無効
}

sub undo_last {
    my $self = shift;
    my $op = pop $self->history->@* or return;
    $op->undo;
    push $self->redo_stack->@*, $op;
    return $op;
}

sub redo_last {
    my $self = shift;
    my $op = pop $self->redo_stack->@* or return;
    $op->execute;
    push $self->history->@*, $op;
    return $op;
}

sub log_lines {
    my $self = shift;
    return map { $_->label } $self->history->@*;
}

これが Invoker(実行役) だ。コマンドを受け取って実行し、実行した伝票を history に順に積んでいく。この積み重ねを 履歴スタック と呼ぶ。後入れ先出し——いちばん最近の手から、順に取り出す。

「一つずつ、見せるぞ」とシェフは言った。まず、操作を三つ積んでみせた。唐揚げ定食、クーポン、ドリンクだ。

1
2
3
4
5
6
my $cart = Cart->new;
my $pad  = OrderPad->new;

$pad->do_operation(AddItem->new(cart => $cart, name => '唐揚げ定食', price => 850));
$pad->do_operation(ApplyCoupon->new(cart => $cart, amount => 100));
$pad->do_operation(AddItem->new(cart => $cart, name => 'ドリンク', price => 200));

「まず、『この会計で何をしたか、見せろ』」。シェフは log_lines を呼んだ。

1
2
3
4
say for $pad->log_lines;
# 唐揚げ定食 を追加 (850円)
# クーポン割引 (-100円)
# ドリンク を追加 (200円)

「刺さってる伝票を、順に読むだけだ。Before は——操作を残してないから、これは作れない。明細はあっても、『どういう手でそうなったか』は、どこにもないからな」

彼女が、小さく息を呑んだ。

「次。『一手ずつ、何手でも戻す』」。シェフは undo_last を三回呼んだ。

1
2
3
$pad->undo_last;   # ドリンクを戻す
$pad->undo_last;   # クーポンを戻す
$pad->undo_last;   # 唐揚げ定食を戻す

「刺さってる伝票を、上から順に抜くだけだ。一枚でも、三枚でも、好きなだけな。お前さんの void_last で、これができたか?」

「……いえ」と彼女は言った。「pop を繰り返せば、明細は消えます。でも、いま何手目を戻しているのか、何の操作を戻しているのか、分からなくなります」

「最後。『戻しすぎた、やり直せ』」。シェフは、今度は redo_last を三回呼んだ。

1
2
3
$pad->redo_last;   # 唐揚げ定食
$pad->redo_last;   # クーポン割引
$pad->redo_last;   # ドリンク(さっきの通り、合計 950 円に戻る)

「抜いた伝票を、捨てずに取っておく。だから、もう一度刺せる。一手でも、全部でも。三回やり直せば、さっきの通りだ。Before は pop で捨てちまうから、これもできない」

そして、シェフは OrderPad のコードを指でなぞった。

「見ろ。この OrderPad は、伝票が『商品追加』なのか『割引』なのか、一度も聞いてない。ただ、刺して、抜いて、読むだけだ。戻し方は、伝票自身が知ってる。だから、実行する役は、中身を知らなくていい」

「もし Before で undo を増やそうとしたら——」シェフは続けた。「あの void_last 一つが、全部の操作の戻し方を知る、でかい分岐になっていく。商品追加ならこう戻す、割引ならこう戻す、とな。Command は、それを一手ずつの伝票に分けた。戻し方を、操作自身に持たせたんだ」

私は、シェフが伝票を抜いたり刺したりする手つきを見ながら、自分の言葉にしてみた。

「つまり……手を動かすだけじゃなくて、動かした手を、一枚ずつ伝票にして、刺しておくんですね。そうすれば後から、刺さってる順に、抜いたり、読んだり、もう一度刺したりできる。手を動かすだけだと——戻すための手がかりが、何も残らない」

「そういうことだ」とシェフは短く返した。

その、「戻すための手がかりが、何も残らない」と自分で言った瞬間だった。II幕からずっと胸に引っかかっていたものが、急に、はっきりした形になった。

私の、在庫管理ツール。入荷と出荷を、ただ実行するだけのメソッドで書いた。半年前、入荷数を打ち間違えて、多すぎる数で出荷処理をしてしまった日のこと。取り消す手段はなくて、私は在庫の数を手で足し直した。合っているのかも分からないまま。何を、どう間違えたのかの記録は、一枚も残っていなかった。

私のあのコードには、戻すための伝票が、一枚もなかった——。

私はそれを、口には出さなかった。でも、もう、はっきりと分かっていた。

構造を絵にすると、こうなる。

	classDiagram
    class Operation {
        <<Role>>
        +execute()
        +undo()
        +label()
    }
    class AddItem {
        +cart
        +name
        +price
    }
    class ApplyCoupon {
        +cart
        +amount
    }
    class OrderPad {
        +history
        +redo_stack
        +do_operation(op)
        +undo_last()
        +redo_last()
        +log_lines()
    }
    class Cart {
        +lines
        +total()
    }
    Operation <|.. AddItem : with
    Operation <|.. ApplyCoupon : with
    OrderPad o-- Operation : history
    AddItem --> Cart : 操作する
    ApplyCoupon --> Cart : 操作する

OrderPad が抱える historyOperation——つまり、商品追加でも、割引でも、同じ「伝票」として扱える。OrderPad は中身を知らない。だから、操作の種類に関係なく、「履歴・取り消し・やり直し・ログ」という横断的な仕事を、一か所で持てる。

戻せる手、戻せない手

「ただし」とシェフは、書く手を止めて言った。「戻せるのは、やり直しのきく手だけだ」

そう言って、さっきの営業中の話に戻した。

「まだ作ってない注文は、伝票を抜いて戻せた。だが、もう出しちまった料理は? 食っちまった料理は? 伝票を抜いても、現実は戻らねえ」

私は、ピークのときのあの二つの場面を思い出した。戻せた注文と、戻せなかった注文。

「コードも同じだ」とシェフは言った。「すでに出した料理、確定した会計、発行したレシート、送っちまった注文——そういう『戻せない手』は、undo を『お詫びの一品を出す』みたいな、新しい一手として書くか、そもそも取り消せないものとして扱う。これは、戻せるものを整理して、積めるようにする道具だ。戻せないものを、戻せるようにする魔法じゃねえ」

彼女が、ゆっくりうなずいた。「物理的に戻らないものは、Command でも戻らない。それは、はっきりさせておくべきですね」

「それと、もう一つ」とシェフは付け加えた。「戻し方に、変える前の値を覚えておかなきゃならん操作もある。たとえば『数量を三から五に変えた』のを戻すには、変える前の『三』を、伝票が覚えてなきゃならん。今回の追加と割引は、足した行を抜くだけだから簡単だが——操作によっては、戻すための情報を、伝票が持っておく必要がある。それはまた、別の話だ」

そういえば、と私は思った。前にここに来た人は、単品もコースも同じ顔にしていた(あれは Composite というらしい)。あれは、「モノ」を同じ顔にまとめる話だった。今回は、「コト」を物にする話だ。やったことを、手に取れる伝票にする。モノを同じ顔に、コトを物に。なんだか、対になっている気がした。

試食合格

コードを書き直して、テストを走らせた。

1
2
3
4
5
6
7
8
9
# テスト結果
ok - AddItem#execute / undo: 足して、自分で取り除く
ok - OrderPad 経由: 850 - 100 + 200 = 950
ok - 三手前まで戻す: undo_last を3回で初期状態に戻る
ok - やり直し(redo): 戻した一手をもう一度
ok - 新しい操作の後は redo が無効になる
ok - 操作ログ: 実行順に正しく並ぶ
ok - Invoker は新しい操作の中身を知らずに実行も取り消しもできる
ok - 空の履歴で undo/redo を呼んでも安全に何もしない

要望の三つ——三手前まで戻す、操作ログ、やり直し——が、全部動いた。

「新しい操作を足すときも」と彼女が、自分から先を続けた。「executeundo を持った伝票を、一つ書けばいいんですね。OrderPad は、触らなくていい」

「そうだ。ただし」とシェフは言った。「たとえば『数量変更』を戻すには、変える前の数を、その伝票が覚えておく必要があるぞ」

「……あ、確かに」と彼女は言った。「戻し方に、前の値がいる。戻せる手にも、手間の差があるんですね」

シェフは、伝票刺しを片付けながら言った。

「やった手は、一手ずつ伝票に残せ。残してあれば、戻せる手は戻せる。戻せない手も——どれが戻せないか、分かる」

「やった手を、残す」と彼女は繰り返した。「私はずっと、操作は実行すれば終わりだと思っていました。残しておく、という発想が、なかったんです」

来たときの、考え込むような硬さが、少しほどけて見えた。彼女はノートPCを閉じて、「先輩に、お礼を言わないと」と小さく笑った。

引き戸が閉まる音がした。

片付けをしながら、私は伝票刺しに残った、今日一日の伝票の束を、輪ゴムでまとめた。一枚一枚に、今日この店でやったことが書いてある。注文、変更、取り消し。全部、残っている。

そういえば、と思った。前にここで二回、私は閉店してから、ノートPCを開いて、自分のコードを確かめた。同じ言葉が三か所に散らばっているのを見つけた夜と、入れ子の if を見つけた夜。確かめて、やっと分かった。

でも今日は——開くまでもなかった。

シェフが伝票を三枚抜いて見せた、あの瞬間に、もう分かっていた。私の在庫ツール。入荷も、出荷も、ただ実行するだけ。あの誤出荷の日、私には戻すための伝票が、一枚もなかった。

「私も、同じミスを?」。ずっと胸にあったその問いに、今日、答えが出た気がする。やっぱり、私もだった。散らばりも、入れ子の if も、戻せない操作も。三つとも、私のあの小さなツールの中にある。

でも、不思議と、恥ずかしくはなかった。シェフはいつも言う。不味いのは、素材が悪いんじゃない。仕込みが足りなかっただけだ、と。私のコードも、たぶんそうだ。恥じゃない。まだ、仕込んでないだけ。

それに、もう一つ、気づいたことがあった。今日、私はコードを確かめてから「おかしい」と思ったんじゃない。シェフの手を見た、その瞬間に。確かめるより先に、舌が「おかしい」と言っていた。

——少しだけ、味が、分かるようになってきたのかもしれない。


シェフの仕込み工程表

問題(調理ミス)技法(パターン)効果(仕上がり)
操作を手続きとして実行して消すだけ。やった操作が記録されないCommand(execute/undo を持つ Operation 伝票にする)操作が「物」になり、積む・読む・抜いて戻す・もう一度刺すができる
void_last は直前を消すだけ。ログ・三手戻す・やり直しが原理的に作れないInvoker(OrderPad)が履歴スタックに伝票を積む三手前まで戻す・操作ログ・やり直しが、種類に関係なく一か所でできる
取り消しの戻し方を一か所に詰め込むと、全操作を知る巨大分岐になる戻し方(undo)を操作自身に持たせる実行役は中身を知らない。新しい操作は伝票を一枚足すだけ

工程

Step 1: 操作の決まり(Operation)を Role で定義する

すべての操作が共通で持つべきものを Moo::Role で宣言する。requires で「この役割を名乗るクラスは、必ずこのメソッドを持つこと」を強制する。実行(execute)と取り消し(undo)、ログ用の一行(label)をセットにする。

1
2
3
package Operation;
use Moo::Role;
requires 'execute', 'undo', 'label';

Step 2: 各操作を、伝票(ConcreteCommand)にする

一つの操作を、一つのクラスにする。「やること」を execute に、「自分の戻し方」を undo に、セットで書く。戻し方が操作自身に同居するのがポイントだ。

1
2
3
4
5
6
7
8
9
package AddItem;
use Moo;
with 'Operation';
has cart  => (is => 'ro', required => 1);   # 操作する相手(Receiver)
has name  => (is => 'ro', required => 1);
has price => (is => 'ro', required => 1);
sub execute { my $s = shift; push $s->cart->lines->@*, { name => $s->name, price => $s->price }; }
sub undo    { my $s = shift; pop  $s->cart->lines->@*; }
sub label   { my $s = shift; sprintf '%s を追加 (%d円)', $s->name, $s->price; }

Step 3: 実行役(Invoker)に履歴を持たせる

操作を受け取って実行し、実行した伝票を履歴スタックに積む。後入れ先出しで、undo_last は最後の伝票を抜いて undo を呼ぶ。戻した伝票は別のスタックに取っておけば、redo_last でやり直せる。実行役は、伝票が何の操作かを聞かない。

1
2
3
4
5
6
7
8
package OrderPad;
use Moo;
has history    => (is => 'ro', default => sub { [] });
has redo_stack => (is => 'ro', default => sub { [] });
sub do_operation { my ($s,$op)=@_; $op->execute; push $s->history->@*, $op; @{$s->redo_stack}=(); }
sub undo_last    { my $s=shift; my $op=pop $s->history->@* or return; $op->undo; push $s->redo_stack->@*, $op; }
sub redo_last    { my $s=shift; my $op=pop $s->redo_stack->@* or return; $op->execute; push $s->history->@*, $op; }
sub log_lines    { my $s=shift; map { $_->label } $s->history->@*; }

Step 4: 呼び出し側は、実行役に操作を渡すだけにする

$pad->do_operation(AddItem->new(...)) のように、操作を作って実行役に渡す。取り消しもログもやり直しも、実行役に頼む。呼び出し側は、戻し方を知らなくていい。

Step 5: 戻せない手を、正直に扱う

物理的に戻らない操作(提供済み、会計確定、レシート発行、送信済みなど)は、undo を「補償する新しい一手」にするか、取り消し対象から外す。また、数量変更のように、戻すために変更前の値を覚えておく必要がある操作は、その値を伝票自身に持たせる。

シェフより

操作ってのは、手を動かすことだ。だが、動かしただけだと、消えちまう。後から「あれを戻せ」「あれを見せろ」「あれをもう一度」と言われても、手元に何も残ってない。お前さんのコードが詰まったのは、腕が悪いからじゃない。やった手を、残してなかっただけだ。

伝票にしろ。一手ごとに、やることと、戻し方を、一枚に書いて、刺しておく。そうすりゃ、積める、数えられる、読める、抜いて戻せる、もう一度刺せる。実行する側は、その伝票が何の料理かを知らなくていい。順番に管理するだけだ。

一つだけ、忘れるな。これは、戻せるものを戻せるようにする道具だ。戻せないものまで、戻せるようにはならねえ。出しちまった料理は、戻らねえ。どれが戻せて、どれが戻せないか——それをはっきりさせておくのも、立派な仕込みのうちだ。

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。