Featured image of post コードナビゲーター【Strategy】分岐に呑まれた航路計算機〜継ぎ接ぎの星図と、差し替える航法術〜

コードナビゲーター【Strategy】分岐に呑まれた航路計算機〜継ぎ接ぎの星図と、差し替える航法術〜

if/elsifの膨張で新方針の追加が既存の計算を壊す航路選択プログラムを、Strategyパターンで方針ごとの部品に分離。Rubyのダックタイピングを活かした差し替え可能な設計を、恒星間貨物船の物語で解説します。

第1幕 異変——触っていない航路が狂う

循環器のフィルタを換え終えたところに、通信端末が鳴った。

寄港三日目の機関室は静かなものだ。主機は落としてあるから、聞こえるのは補機の低い唸りと、生命維持系が空気を送る規則正しい音だけ。定期便の荷積みは昨日のうちに終わっていて、俺は溜まっていた整備を一つずつ潰していた。交換したばかりのフィルタは真っ黒で、前の寄港からの航海がどれだけ埃っぽい宙域を通ってきたかを物語っている。

俺はダグ。恒星間貨物船〈オリオール号〉の機関士だ。船暮らしは二十年を超えた。機関のことなら、振動の癖から油の匂いの変わり目まで、だいたい体で覚えている。逆に言えば、俺に分かるのはそこまでで、船がどの空域をどう飛ぶか——航路のほうは、星図室の領分だ。

端末に届いていたのは、運航会社からの指令書だった。

「次航海より新運航方針『デブリ回避優先』を追加せよ。デブリ密度の高い宙域では、選択中の方針に関わらず回避を優先すること。カリブディス小惑星帯における航行事故が続いたための全船一斉指令である」

読み終えて、フィルタ交換の続きをやる気は失せた。デブリ回避というのは、要するに障害物を避けて細かく噴射を繰り返すことだ。回避機動の連続は機関出力の配分に直結するし、燃料計画にも跳ね返る。つまりこれは、星図室の話であると同時に、俺の機関の話だった。

指令書を握って、星図室へ向かった。

〈オリオール号〉の星図室は、船で一番暗い部屋だ。濃紺の暗がりに星図の投影が浮かび、投影しきれない分は紙の星図になって床にまで広げてある。そしてその紙の星図のど真ん中に、うちの航法士が寝転がっていた。

ミラだ。年齢不詳。手にはもう、指令書の写しがある。俺より先に読んでいたらしい。

俺は驚かない。床で星図を読むのも、航路計算の途中で突然歌い出すのも、乾パンに蜂蜜をかけて主食にしているのも、全部いつものことだ。この変わり者と組んで長いが、ひとつだけ確かなことがある。ミラの引く航路は、船団の誰よりも正確で、誰よりも燃料を食わない。だから俺は、奇行のほうは気にしないことにしている。

「ダグ、ちょうどよかった。聴いて」

ミラは起き上がりもせずに言って、投影に航路計算機のコードを呼び出した。そして——歌い出した。

if、と始まる行を節にして口ずさむ。elsif で調子が変わる。もうひとつの elsif でまた変わる。ミラは途中で顔をしかめて、歌をやめた。

「ね、変でしょ。サビで転調しすぎ」

俺には歌の良し悪しは分からん。だが、ミラがこういう聴き方をするときは、たいてい何かが軋んでいる。二十年選手の機関が変な倍音を出し始めたときの、あの感じだ。

投影に映っているのは、前任の航法士が遺していった航路計算機だった。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 前任航法士の遺した航路計算機
class RouteCalculator
  def plan_route(request, mode)
    burn_ratio = 1.0 # 噴射の強さ(燃料消費の係数)
    margin     = 1.0 # 安全余裕(所要日数の係数)

    if mode == "fuel"
      burn_ratio = 0.7
      margin     = 1.2
    elsif mode == "speed"
      burn_ratio = 1.5
      margin     = 0.9
    elsif mode == "safety"
      burn_ratio = 0.9
      margin     = 1.5
    end

    # 共通の後処理: 全方針がこの一つの式に合流する
    fuel = request.distance * burn_ratio
    days = request.distance * margin
    Route.new(fuel: fuel, days: days)
  end
end

うちの船の運用を先に説明しておくと、〈オリオール号〉の航海は、一つの航海につき方針は一つだ。出航前に運航方針——燃費優先か、速度優先か、安全優先か——を会社の運航指定から選び、その一つで航路を引く。方針を混ぜて折衷することはしない。だから計算機も「どの方針か(mode)を選び、その一つを計算する」造りになっている。

コードの読み方くらいは俺にも分かる。request というのが航路の依頼書で、距離(distance)とデブリ密度(debris_level)を持っている。答えは燃料と日数を束ねた Route で返る。途中の elsif は Ruby の書き方で、他の言語でいう else if のことだ。方針ごとに係数を変数へ入れて、最後にまとめて一つの式で航路を組む。燃費優先なら噴射を絞って日数に余裕を持たせる。速度優先ならその逆。理屈は通っている。

「新しい方針を、ここに足せって指令ね」ミラは寝転がったまま、指令書の写しをひらひらさせた。「足せるよ。前の人と同じやり方でならね」

その言い方に、含みがあった。長い付き合いだから分かる。ミラが「できる」を条件付きで言うときは、やらないほうがいい理由を、もう計算し終えている。

第2幕 解析——前任者の流儀と、わざと鳴らす警報

ミラはようやく起き上がって、投影の前に立った。そして最初にやったのは、前任者のコードを褒めることだった。

「この造り、悪くないのよ」細い指が、共通の後処理の式をなぞる。「方針が二つだけだった頃は、これが一番短くて読みやすかったはず。共通の変数に係数を入れて、最後に一つの式でまとめる。きれいな倹約よ」

前任の航法士が現役だった頃、うちの運航方針は燃費と速度の二つだけだった。デブリ帯はそもそも認可航路に入っていなかったから、避ける必要すらなかった。その条件でなら、この一本のメソッドは合理的な判断だったと、ミラは言う。継ぎ接ぎに見える星図にも、引かれた当時の重力場では意味があったのだ。

「でも、今回の追加は毛色が違う」

ミラは指令書の一文を投影に拡大した。——デブリ密度の高い宙域では、選択中の方針に関わらず回避を優先すること。

「これまでの三つの方針は、並んでる選択肢だった。燃費か、速度か、安全か。どれか一つを選ぶだけ。でもデブリ回避は違う。方針(mode)じゃなくて、空域のデータ——デブリ密度を見たがってる。『どの方針を選んでいても、密度が高ければ割り込む』。そういう条件なの」

「mode の選択肢に “debris” を一個足せば済む話じゃない、ってことか」

「そう。それだと『デブリ回避という方針を自分で選んだとき』しか効かない。指令は『選んでいなくても優先しろ』。……で、前の人の流儀のまま忠実に足すと、どうなるか」

ミラはそこで、いたずらを思いついた子どもみたいな顔をした。ように見えた、と言っておく。俺にミラの内心は分からん。

「見せたほうが早い。壊してみるね」

ミラはためらいなく、計算機の写しを実験用に一部作り、前任者の流儀どおりの分岐を if の連なりの頭に挿した。

1
2
3
4
5
6
7
8
9
    # 前任者流の追加(実験用の写し)
    if request.debris_level >= 5
      burn_ratio = 1.3
      margin     = 1.6
    elsif mode == "fuel"
      burn_ratio = 0.7
      margin     = 1.2
    elsif mode == "speed"
    # ……以下、元のまま

デブリ密度が5以上なら回避の係数、そうでなければ従来どおり。指令の文面には忠実だ。俺の目には、一見どこも悪くないように見えた。

「じゃ、模擬航走にかける」

模擬航走というのは、実際に船を飛ばさずに、計算機へ依頼だけを食わせて答えを検分する試験航行のことだ。機関で言えばベンチテストにあたる。台の上でエンジンを回して、実走の前に癖を洗い出す。あれと同じだ。

ミラが試験用の依頼を読み上げる。「距離10光年、燃費優先。ただし航路はデブリ帯を横切る——密度8」

Ruby には、ここで一つ事情がある。俺も昔ミラに教わった。Ruby は動的型付けの言語——書き間違いや辻褄の合わない呼び出しが、書いた時点では何も言われず、動かした瞬間になって初めてエラーとして鳴る言語だ。出航前の点検で見つけてくれる検査官はいない。だから模擬航走、つまりテストが生命線になる。飛ばす前に、飛ばしたつもりの計算を全部済ませておくしかない。

警報が鳴った。

模擬航走の結果盤に、赤。実船の警報とは音が違う、乾いた電子音だ。それでも機関士の背筋は反射で伸びる。二十年、赤い灯に良い思い出はない。

燃費優先で頼んだはずの航路が、燃料13トン——回避係数の1.3で計算されている。燃費優先の係数0.7なら7トンのはずだ。10光年で6トンの差。うちの船の予備タンクごと吹き飛ぶ誤差だ。もしこれが模擬航走でなく実航海の計画に紛れ込んでいたら、航海の半ばで燃料計器とにらめっこする羽目になっていた。

俺は思わず投影に一歩寄った。

「おい、待て。燃費優先のコードには一文字も触ってないだろう。書き足したのはデブリの分岐だけだ。なんで燃費の航路が狂う」

「それが今日の本題」ミラの声が、すっと静かになった。ふだんの調子っぱずれが消えて、正確な言葉だけが残る。核心を話すときのミラは、いつもこうなる。

「さっきの歌ね、転調って言ったでしょ。この計算機、全部の方針がひとつの楽譜を共有してるの」

ミラは投影に線を引きながら、一段ずつ示した。

まず、新しく挿した分岐は mode を見ていない。デブリ密度というデータを見ている。だから燃費優先の依頼だろうと、密度が高ければこの分岐が先に成立する。

次に、分岐が成立すると、burn_ratio と margin に回避の係数が入る。この二つの変数は、新しい分岐の持ち物ではない。三つの方針が昔から共有している中間変数だ。

そして最後に、どの分岐を通ったかに関係なく、全員が末尾の一つの式に合流する。式は変数に入っている値を使うだけだから、途中で誰が値を書き換えたかは知らない。

「つまり、燃費優先の計算が壊れたんじゃないの。燃費優先の計算に使うはずだった変数を、新入りが先に上書きしただけ。elsif の連なりだから、頭の分岐が成立した時点で、燃費優先の分岐にはもう誰も入れない」

ミラが投影に引いた線を、俺の頭に残った順で描き起こすと、こうなる。燃費優先の依頼のはずが、燃費優先の分岐には一度も入っていない。

燃費優先の依頼が if の連なりの先頭で「密度 >= 5」のデブリ回避分岐に吸い込まれ、共有の中間変数 burn_ratio / margin が上書きされて末尾の合流式が誤った燃料13トンを算出する因果を示した星図風フローチャート

俺は機関室のことを考えていた。思い当たる構図があったからだ。

「あれか。一本の燃料配管に全系統をつないでるようなもんか。弁を一個増設したら、配管の圧が変わって、触ってない系統の噴射まで変わる」

「そう、それ」ミラが指を鳴らした。嬉しそうに見えた。「配管が共有だから、どこに何を足しても全体に波及する。この計算機は、方針が増えるたび、方針以外の条件が絡むたび、同じ事故を繰り返す構造なの。前の人が悪いんじゃない。楽譜が、いまの曲に合わなくなった」

第3幕 引き直し——同じ顔の部品と、差し替える航法術

ミラは実験用の写しをあっさり消した。壊すために作ったのだから、未練もない。そして、まっさらな投影に、小さな枠を三つ並べた。

「引き直すよ。方針ひとつを、部品ひとつに切り出すの」

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 方針一つを、部品一つに切り出す
class FuelSaverPlan
  def plan(request)
    Route.new(fuel: request.distance * 0.7, days: request.distance * 1.2)
  end
end

class SpeedPlan
  def plan(request)
    Route.new(fuel: request.distance * 1.5, days: request.distance * 0.9)
  end
end

class SafetyPlan
  def plan(request)
    Route.new(fuel: request.distance * 0.9, days: request.distance * 1.5)
  end
end

燃費優先の部品。速度優先の部品。安全優先の部品。中の係数は元の計算機と寸分違わない。0.7に1.2、1.5に0.9、0.9に1.5。変わったのは置き場所だけで、計算そのものは何一つ変えていない。

「どの部品も、依頼書を受け取って、航路をひとつ返す。約束はその一点だけ。plan という名前の呼びかけに応えること——それだけを、三つとも守ってる」

俺はしばらく三つの枠を見比べて、引っかかった。機関士として、確認しないわけにはいかない。

「待て。この三つ、親も何もつながってないぞ。継承ってやつも、共通の型の宣言もない。ただ同じ名前のメソッドを持ってるだけの、バラバラの部品だ。バラバラの部品を、どうやって同じ挿し口に挿す気だ」

「Ruby はね、血筋を見ないの」

ミラは投影の枠を一つ、指先でつついた。

「その部品が誰の子か、どの型を名乗ってるかは、どうでもいい。plan って呼びかけに応えられるかどうか。見るのはそれだけ。アヒルみたいに歩いて、アヒルみたいに鳴くなら、それはもうアヒルなのよ」

ダックタイピング、という名前がついているそうだ。型や継承関係ではなく「そのメソッドに応えられるかどうか」だけで部品の資格を判定する、Ruby の流儀のことをいう。Java のような言語なら、ここで interface という共通の型を宣言して、三つの部品に名乗らせるところらしい。Ruby はそれを書かない。書けないのではなく、要らない。

俺は機関室の操作盤を思い出していた。うちの補機は三台ともメーカーが違うが、操作盤の規格が同じだから、同じ手順で扱える。中身の造りは知らなくていい。規格に応えてくれさえすれば、俺の仕事は成り立つ。なるほど、それなら分かる。血筋より規格。機関室では当たり前の話だ。

「で、計算機の本体はこうなる」

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 計算機(Context)は「どの方針か」を知らない
class RouteCalculator
  def initialize(plan_strategy)
    @plan_strategy = plan_strategy
  end

  def plan_route(request)
    @plan_strategy.plan(request)
  end
end

俺は二度見た。あの長いメソッドが、これだけになった。部品を一つ受け取って、覚えておいて、依頼が来たらその部品に任せる。それだけだ。if も elsif も、係数も、どこにもない。

initialize は Ruby でいう部品の組み付け——インスタンスを作るときに一度だけ走る初期化のメソッドで、頭に @ のついた変数は、そのインスタンスが自分の中に抱え続ける持ち物(インスタンス変数)だ。つまりこの計算機は、生まれるときに方針の部品を一つ受け取って抱え、以後は依頼が来るたび、その部品に丸ごと任せる。抱えた部品が何者かは、最後まで詮索しない。

妙な感覚だった。仕事が減ったのに、頼りなくは見えない。むしろ逆だ。機関室でも、いい配管ほど分岐が少ない。

「本体はもう、方針が何種類あるかすら知らないの。渡された部品が plan に応えてくれる限り、本体の仕事は『任せる』だけ」

ミラはそこで手を止めて、こちらを振り返った。

「この『やり方を部品に切り出して、丸ごと差し替えられるようにする』定石をね、Strategy っていうの」

Strategy——アルゴリズム、ここでは航路の引き方の一群を、それぞれ独立した部品に切り出して、本体を変えずに交換・追加できるようにする設計パターンだ。名前がついているのは、先人たちが何度も同じ星の並びを見てきたからだという。星の並びに名前がつくのと同じ理屈で、繰り返し役に立った航路の引き方には、名前がつく。

ミラはそれきり黙って、床の星図の上に戻って寝転がった。説明に疲れたのか、考えているのか、俺には分からん。俺は俺で、帳面にここまでの理屈を書き留めた。方針は部品。部品の資格は血筋じゃなく、呼びかけに応えること。本体は任せるだけ。——機関室の言葉に直せば、系統は分離しろ、接続は規格で取れ、だ。それなら俺の帳面にも馴染む。

「ダグの機関室と同じよ」と、ミラは天井を見たまま言った。

さて、本題が残っている。デブリ回避優先だ。

1
2
3
4
5
6
7
8
# 新方針の追加: 新しい部品を一枚書くだけ
class DebrisAvoidancePlan
  def plan(request)
    ratio = request.debris_level >= 5 ? 1.3 : 0.9
    marg  = request.debris_level >= 5 ? 1.6 : 1.5
    Route.new(fuel: request.distance * ratio, days: request.distance * marg)
  end
end

途中の ? : は三項演算子——if/else を一行に畳む書き方で、「密度が5以上なら回避の係数、そうでなければ安全寄りの係数」と読む。デブリ密度を見るという、あの割り込みたがりの判断は、この部品の中に閉じ込められた。外には漏れない。燃費優先の部品は、デブリ密度という言葉すら知らないままだ。

「新しい方針は、部品を一枚書くだけ。本体も、既存の三枚も、一文字も変えない」

ミラは寝転がったまま、こともなげに言った。それから、半分歌うような調子で付け足した。

「航路は、選び直せるように引くの」

模擬航走をもう一度、今度は引き直した計算機で流す。ミラは結果を一つずつ、俺にも分かるように読み上げてくれた。

距離10光年の燃費優先は、燃料7トンに12日。速度優先なら15トンで9日。安全優先は9トンの15日。どれも元の計算機と同じ答えだ。引き直しで数字が変わっていないことが、まず確認される。構造を変えて、計算は変えない。それがこの引き直しの約束だからだ。

模擬航走の一本は、たとえばこう書いてあるらしい。

1
2
3
4
5
6
  def test_fuel_saver_plan
    calculator = RouteCalculator.new(FuelSaverPlan.new)
    route = calculator.plan_route(RouteRequest.new(distance: 10))
    assert_in_delta 7.0, route.fuel
    assert_in_delta 12.0, route.days
  end

燃費優先の部品を組み付けた計算機に、距離10光年の依頼書を渡し、燃料が7.0トン・日数が12.0日になるはずだ、と答え合わせをしている。assert_in_delta は Minitest(Ruby に標準で付いてくるテストの道具)の書き方で、「小数のごく小さな誤差は許して、この値のはず」という照合だ。答え合わせの一項目ずつをアサーションと呼ぶ。模擬航走とは、こういう答え合わせを束にして流すことをいう。

次に境目。デブリ密度4の依頼は通常の係数で、5になった瞬間に回避の係数へ切り替わる。指令書の「密度の高い宙域」の線引きが、部品の中で正しく引かれている。

それから、ありえない依頼書。距離がマイナス3光年——存在しない航路の依頼は、依頼書を作った瞬間に弾かれる。門番は依頼書のクラスの、組み付けのところに立ててある。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 航路の依頼書。distance(光年)は負の値を許さない。
class RouteRequest
  attr_reader :distance, :debris_level

  def initialize(distance:, debris_level: 0)
    raise ArgumentError, "distance must not be negative: #{distance}" if distance.negative?

    @distance = distance
    @debris_level = debris_level
  end
end

attr_reader は、抱えている持ち物(インスタンス変数)を外から読み出すためのメソッドを自動で作ってくれる Ruby の書き方だ。そして raise が、例外を投げる書き方——不正な値をその場で止める門番の役をする。距離が負なら、依頼書は生まれることすら許されない。動的型付けの Ruby では、この門番を自分で立てておくことが、あとで警報の出所を探し回らないための保険になる。

最後に、核心の一本。既存のテストには一切触れず、デブリ回避の部品のテストだけを新しく足して、全部を流す。

全灯、緑。

触っていないものは、壊れていない。今度は構造がそれを保証している。燃費優先の部品に触れる手段が、そもそも存在しないからだ。

第4幕 模擬航走クリア——選択の表と、転調しない鼻歌

結果盤の緑を眺めながら、俺は指令書に「対応済」と書こうとして、手を止めた。機関士として、引っかかりを残したまま帳面を閉じるわけにはいかない。

「待て、もう一つだけ。会社の運航指定は “fuel” だの “debris” だの、ただの文字だぞ。その文字から、どの部品を選ぶ——その分岐は、結局どこかに要るんじゃないのか。if を消したんじゃなくて、どこかに隠しただけじゃないのか」

ミラは頷いた。誤魔化す気配はなかった。

「いい質問。隠してない。ここにいる」

1
2
3
4
5
6
7
# 選択表: 運航指定(文字列)から部品を選ぶ対応は、ここに薄く集まる
PLANS = {
  "fuel" => FuelSaverPlan.new,
  "speed" => SpeedPlan.new,
  "safety" => SafetyPlan.new,
  "debris" => DebrisAvoidancePlan.new
}.freeze

波括弧のこれは Hash——名前と中身の対応表で、Ruby の連想配列のことだ。“fuel” という名前を引けば燃費優先の部品が出てくる。末尾の freeze は、この表をあとから書き換え不能に凍結する印。運航中に選択表を書き換えられては困るから、錠を掛けておくのだという。出航のときにやることは、RouteCalculator.new(PLANS["fuel"]) のように、会社の運航指定で表を引き、出てきた部品を本体に組み付ける——それだけだ。

「分岐は消えないの。消せない。でも、一枚の表に薄く集まって、計算から切り離される。ここが昔と違うところ。前の計算機は『どれを選ぶか』と『どう計算するか』が一本のメソッドに溶け合ってた。いまは、選ぶのはこの表、計算するのは各部品、束ねるのは本体。三つが別々になってる。方針を足すときにやることは、部品を一枚書いて、この表に1行足す。それで全部。計算の本体には、もう二度と触らない」

帳面に、引き直したあとの系統図を描いてみた。選ぶ・計算する・束ねる——三つの箱が別々の区画に収まっていて、増設スロットの口だけが開いている。

Strategy パターンの三分離を示した星図風の構造図。左の選択表 PLANS が運航指定の文字から部品を選び、中央に同じ顔(plan メソッド)を持つ4つの方針部品が並び、右の本体 RouteCalculator は組み付けた部品に任せるだけで Route(燃料・日数)を返す

追加には開いていて、既存の変更には閉じている。この設計の心得には OCP——開放閉鎖の原則という名前がついているそうだ。機能を足せる口は開けておき、動いている本体は閉じて守る。機関の増設と同じ理屈なら、俺にも覚えられる。母線はいじらず、増設スロットに挿せ。俺は帳面にそう書き付けた。

ミラは床から、思い出したように付け足した。

「ちなみに、係数を変えるだけの臨時方針なら、部品を立てるまでもないけどね」

1
2
# 処理そのものを値として手渡す書き方もある
holiday_special = ->(request) { Route.new(fuel: request.distance * 0.8, days: request.distance * 1.1) }

-> は lambda——処理そのものを値として持ち運べる、Ruby の道具だ。クラスを立てるほどでもない一回きりの方針なら、こういう手渡し方もある。ただし正直に言っておくと、lambda が応えるのは plan ではなく call という呼びかけだ。あの「同じ呼びかけに応える」という約束からは半歩外れるから、使うなら本体側の呼びかけを合わせる工夫が要る。形は違っても、やっていることは同じ「差し替え」——だが、ミラはそれをすぐに投影から仕舞った。「本便では使わない。読める航路が優先」。気まぐれに見えて、こういう線の引き方は徹底している。

管制から出航許可が下りたのは、その日の夕方だった。

出航前の機関室には、独特の緊張がある。落としてあった主機に火を入れ、循環器を全開に切り替え、計器の針が一本ずつ定位置に立ち上がっていくのを見届ける。換えたばかりのフィルタを通った空気は、心なしか軽い。俺は指令書の余白に「対応済」と書き込んで、帳面と一緒に工具箱の上へ置いた。帳面には今日の分の書き付けが増えている。方針は部品。接続は規格で。母線はいじらず、増設スロットに挿せ。

機関が温まる低い振動が、床から座席へ伝わってくる。二十年聞いてきた、いつもの音だ。燃料計画は燃費優先、ただしカリブディス小惑星帯の区間だけはデブリ回避優先——選択表から部品を差し替えるだけで、どちらの航路も同じ計算機が引いた。

星図室から通信が入った。用件はなかった。ミラの上機嫌な鼻歌が、回線越しに流れてくるだけだ。

今度の歌は、転調しない。

〈オリオール号〉は、引き直された航路に乗って桟橋を離れた。


🚀 オリオール号 航海日誌(Navigator’s Log)

  • 航法術(パターン名): Strategy(やり方の一群をそれぞれ部品に切り出し、本体を変えずに交換・追加できるようにする設計)
  • 発生事象(症状): 新方針「デブリ回避優先」を前任者の流儀(if 連鎖への追記)で足すと、一文字も触っていない燃費優先の航路計算が狂った
  • 原因(旧航路の問題): 全方針が中間変数(burn_ratio / margin)と末尾の合流式を共有する一本のメソッド。データを見る新分岐が共有変数を先に上書きし、全方針へ波及した
  • 処置(引き直しの要点): 方針ごとに同じ顔(plan メソッド)の部品クラスへ分離。本体は部品を受け取って任せるだけ。新方針は部品一枚と選択表への1行で足せ、既存部品・本体は無改変(ダックタイピング+OCP)
  • 保証外事項: 方針のブレンド(複数戦略の合成)は今回の設計の範囲外。係数そのものの妥当性(0.7 が正しい燃費係数か)はテストでは保証しない——それは航法士の測量の仕事
  • 模擬航走結果: 全14テスト・29アサーション、全灯緑(各方針の正常系・デブリ密度の境界・不正な依頼書の排除・新部品の無改変追加)
  • 次の針路: カリブディス小惑星帯を回避優先で通過予定。臨時方針が増えたら、部品を一枚と表に1行
comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。