Featured image of post コードウィザード【State】却下したはずの許可証が、承認済みの顔をして棚に並んでいた〜印を増やすのをやめ、許可証の姿そのものを型で決め直す〜

コードウィザード【State】却下したはずの許可証が、承認済みの顔をして棚に並んでいた〜印を増やすのをやめ、許可証の姿そのものを型で決め直す〜

探索者組合のリオは、却下したはずの許可証が承認済み表示のままになったヒヤリハットに直面する。見習い魔術師ルークは巨大な判定関数で組み合わせを潰そうとするが、新しい手続きで同じ書き忘れが再発する。師匠の問いから、Kotlinのsealed classであり得ない姿を型として作れなくする再構成へ。Kotlinで描く。

承認済みの顔をした、却下許可証

僕はリオ。探索者組合の受付窓口で、迷宮や危険区域への立入許可証の発行事務を担当している。組合に入って、まだ2年目だ。

窓口の壁には、申請の台帳がずらりと並んでいる。誰が、いつ、何の許可を求め、今どうなっているか。それを正確に記録するのが、僕の仕事だ。危険な場所へ人を通す紙切れである以上、間違いは、そのまま誰かの命に関わる。

台帳は、こう回っている。申請が来ると、まず一定の条件を満たしているかを機械的に確かめ、満たしていれば「仮承認」の印が自動でつく。効率を考えて、僕が組んだ仕組みだ。そのあと、審査担当が改めて人の目で見て、問題があれば却下する。

先週、そこで事故が起きた。

ある申請者は、仮承認の印がついたあと、審査担当が「素行に懸念あり」として却下していた。台帳の記録上は、確かに却下の印が立っていた。なのに、一覧表示は「承認済み」の顔をしたまま、棚に並び続けていたのだ。

危うく気づかず終わるところだった。同僚が、別に控えていた紙の記録でたまたま食い違いに気づき、その申請者が迷宮の門をくぐる寸前で、僕らは慌てて止めた。

原因を洗ったのは、僕自身だった。却下の処理を書いたコードに、一行、漏れがあった。

1
2
3
4
5
6
7
8
fun approve(permit: Permit) {
    permit.isApproved = true
}

fun reject(permit: Permit) {
    permit.isRejected = true
    // 本来ここで isApproved = false に戻すべきだったが、書き忘れていた
}

isApproved を、元の false に戻す一行。それだけが、無い。だから、仮承認の印を持ったまま却下された許可証は、isApproved=trueisRejected=true を、両方持ったままになる。

そして、台帳の一覧を作る関数は、こうなっていた。

1
2
3
4
5
6
7
// 台帳の一覧表示:isApproved を先に見てしまう
fun describeStatus(permit: Permit): String {
    if (permit.isApproved) return "承認済み"
    if (permit.isRejected) return "却下"
    if (permit.isSubmitted) return "審査中"
    return "下書き"
}

isApproved を、真っ先に見る。だから、却下の印が立っていても、承認の印が残っていれば、表示は「承認済み」のまま。あの一行さえ、書いていれば。

台帳を見返すたび、指先が冷たくなる。あの一行を書いた自分と、それに気づかず何日も窓口に立っていた自分が、同じ人間だとは思いたくなかった。

組合長の命で、丘のルークへ

組合長には、当然、こっぴどく叱られた。「二度と、この手の事故を起こすな」。それだけでなく、組合長はこうも言った。

「アカデミーの丘に、こういう記録の乱れを片付ける見習いがいるらしい。相談してこい」

藁にもすがる思いというより、命じられて、というほうが正しい。近く、審査の手続きに「差し戻し(要修正)」——申請者に書き直しを求める、新しい中間状態を加えると決まったことも、僕の不安に拍車をかけていた。今の作りのまま条件が増えれば、また同じことが起きるのではないか。

台帳の写しを一枚抱えて、僕は重い足取りで丘を上った。

工房は、僕の窓口とはまるで違う場所だった。壁一面の本、宙にちらちら浮かぶ光の粒。几帳面な僕の目には、道具の置き場所も本の並びも、整理されているようには見えなかった。

その工房の隅で、若い見習いが一人、机に幾つもの小さな判子を並べ、順番を変えては押す練習を、根気よく繰り返していた。

「……この判は、この判のあとでなければ、押せない」

そう独りごちながら、また判を持ち替えている。僕が声をかけると、判子を置いたまま顔を上げた。

「あ……台帳の、ご相談ですか」

これが、噂のルークか。派手な魔法を振り回す様子は、どこにもない。

僕は台帳の写しを机に広げ、事の経緯を語った。仮承認、審査、却下、そして——却下したはずなのに、承認済みの顔をしたまま棚に並んでいた許可証のこと。ルークは写しを覗き込み、isApprovedisRejected が両方 true になっている行を、しばらく黙って見比べていた。

「これは……許可証の"姿"を、3つの別々の印(Boolean——真か偽かの2択を持つ型)で表していますね。印はそれぞれ独立しているから、“承認済み"と"却下"の印が、両方立ってしまうことがある」

ルークは、その状態に名前をつけた。

「本来存在してはいけないはずの組み合わせが、型の上では作れてしまう——これを、不正状態と呼びます」

不正状態。ルークはそう名付けたが、僕にとっては、ただの専門用語では済まなかった。あの一行の書き忘れが、まさにそれを生んだのだから。

「そうです」と僕は答えた。「却下の処理を書いたとき、承認の印を戻す一行を、僕が書き忘れました」

あり得る組み合わせを、一つ残らず

ルークは写しをしばらく見つめていた。それから、何か思いついた顔になる。

「なるほど……なら、あり得る組み合わせを、一つ残らず、先に潰しておきましょう」

誇示というより、僕の負い目に応えたい一心の、前のめりな早口だった。ルークはその場で、判定関数を書き直し始める。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 「あり得る組み合わせを、一つ残らず先に潰す」判定
fun describeStatus(permit: Permit): String {
    return when {
        permit.isApproved && permit.isRejected -> "矛盾(本来ありえないはず)" // ← 防いでいるようで、実は普通に起こる
        permit.isApproved && !permit.isSubmitted -> "矛盾(本来ありえないはず)"
        permit.isRejected && !permit.isSubmitted -> "矛盾(本来ありえないはず)"
        permit.isApproved -> "承認済み"
        permit.isRejected -> "却下"
        permit.isSubmitted -> "審査中"
        else -> "下書き"
    }
}

「見てください。あり得ない組み合わせには、それぞれ"矛盾"という名前をつけて、先に弾いておきました。これで、どんな組み合わせが来ても、判定に困りません」

独立した印が3つあると、真偽の組み合わせは 2×2×2 で8通りになる。うち意味があるのは4通りだけで、残り4通りが"矛盾"だとルークは言った。分岐の数がぴったり8個にならないのは、上から順に確かめていく判定なので、幾つかの矛盾がまとめて弾かれるからだ。

一度は、僕も感心した。「……すごい。もう、漏れは無さそうですね」

それでも、引っかかりが残った。

「でも……“矛盾"の組み合わせが、そもそも台帳に書き込まれてしまう、という点は、直っていない気がします」

ルークは、一度、自信をもって応じた。「表示さえ正しく弾ければ、実害はありません。これで、判定のほうは万全です」

僕の疑問は、たぶん的を射ていた。でもルークはまだ気づいていないようで、そのまま先へ進んでしまう。

工房の隅では、白髪の老人が一人、小さな仕掛け箱の蓋を、かちり、かちりと開け閉めしていた。誰なのかは訊かなかった。工房に出入りする客か、あるいは道具番の年寄りか——僕には、その程度の興味しか引かなかった。ルークが判定関数に手を入れるたび、その音だけが、一つ、また一つと続いていた。

潰したはずの穴が、また開く

そこへ、組合長からの正式な知らせが届く。「差し戻し(要修正)」の手続きを、正式に加えることになった、と。

僕がそれをルークに伝えると、彼は判定関数に4つ目の印——isRevisionRequested——を書き足し始めた。3つの印で8通りだった組み合わせが、4つになった瞬間、一気に16通りに膨れる。ルークは急いで、差し戻しの手続きを書いた。

1
2
3
4
5
// 差し戻し(要修正)の手続き
fun requestRevision(permit: Permit) {
    permit.isRevisionRequested = true
    // ここでも isApproved = false に戻す一行が抜けている
}

動かして確かめた僕は、目を疑った。差し戻しにしたはずの許可証が、まだ「承認済み」の印を残したまま、一覧に出ていたのだ。

ルークが requestRevision を見直し、顔を強張らせる。

「……isApproved を戻す一行を、また、書き忘れていました」

ルークの詫びを、僕は黙って聞いていた。だが「戻す一行」という言葉だけが、耳の奥に引っかかって離れない。自分が却下処理に書いたのと、寸分違わぬ欠落だったからだ。

「……僕がやったのと、同じ場所です。あのときも、承認の印を、戻す一行が抜けていました」

感情が重なった、というより、まったく同じ種類の書き忘れが、あれだけ防御を積んだはずのコードでも、また起きた——その事実が、二人の間にひやりと横たわった。

ルークは判定関数から手を離し、並べた分岐を、一つずつ目で追っていた。

「……潰したつもりだったのに。組み合わせを、いくら先回りして潰しても、印を戻す一行を、また忘れる。なぜ、同じ場所で、同じように、つまずくんだろう」

机に並んでいた判子の列を、ルークがそっと崩した。残ったのは、一枚だけ。僕の書き忘れと、寸分違わぬ書き忘れを、この人も今、自分の手でやらかしたところだった。

仕掛け箱の、開く蓋はいつも一つ

ルークが、隅の老人にふと目をやった。それから、自分から歩み寄っていく。

「お師匠さま、また悪戯を」

苦笑まじりの声だった。僕は、その老人が名の知れた大魔術師マグナスさまだと、ルークの呼びかけで初めて知った。

マグナスさまは、いじっていた仕掛け箱を、そのまま差し出した。蓋がいくつか付いていて、今開いている蓋の位置によって、次に開けられる蓋が一つに決まる作りらしい。無理に別の蓋を開けようとしても、からくりがそれを許さなかった。

「——のう。この箱は、今開いている場所からしか、次の場所へ動けないだろう。なぜ、確かめなくても、そう作れるんだろうね?」

それだけ言うと、マグナスさまは、また蓋を一つ、静かに閉じる遊びへ戻ってしまった。答えは言わない。

ルークの目が、仕掛け箱の"今の蓋の位置が、次を決める"作りに、じっと留まっていた。

ルークが、机に残っていた最後の一枚の判子を、指先で軽く押さえる。

「……印を、増やすんじゃない。姿を、型で決め直すんです」

ルークはそう言うと、机の上の余白に素早く図を描き殴った。

「見てください。今までのやり方は、許可証という一つの板に、別々の印をいくつもペタペタと押していたようなものです」

Kotlinのsealed classを用いたStateパターンのBefore/After比較図。Beforeでは独立したBooleanフラグにより矛盾状態が生じ得るが、Afterでは状態が排他的なsealed classの姿として表現されるため不正状態が発生しない。

「これだと、“承認"と"却下"の印が、両方とも押されたままになる事故が物理的に防げません。僕らは、矛盾した印を慌てて消す処理ばかりに気を取られていました。そうではなく——」

ルークは、新しく描いた図の右側を指し示した。

「許可証の"姿"そのものを、型という頑丈な箱で定義し直すんです。下書き、申請中、差し戻し中、承認済み、却下。これらはすべて、独立した印ではなく、許可証が取り得る『ただ一つの姿』です。これなら、承認済みでありながら却下されている、なんていう矛盾した姿そのものが、この世に存在できなくなります」

許可証の"姿"を、型で決め直す

ルークは、僕の写しの上に、新しい設計を書き始めた。

1
2
3
4
5
6
7
8
// 許可証の状態:一度に「一つの姿」しか取れない型の列挙(sealed class)
sealed class PermitState {
    data object Draft : PermitState()                          // 下書き(データを持たない状態は data object)
    data class Submitted(val submittedBy: String) : PermitState()
    data class RevisionRequested(val reason: String) : PermitState()
    data class Approved(val approvedBy: String) : PermitState()
    data class Rejected(val reason: String) : PermitState()
}

「これは sealed class——取りうる"姿"を、あらかじめ全部登録した型です。Draft(下書き)、Submitted(申請中)、RevisionRequested(差し戻し中)、Approved(承認済み)、Rejected(却下)。登録した姿以外は、後から勝手に作れません」

Draft にだけ data object という書き方がついているのが気になって、僕は訊いた。

「これは何が違うんですか」

data object は、データを持たない、ただ一つだけの姿を表す書き方です。下書きには、特に持たせるデータが無いので。他の姿は data class——状態ごとに固有のデータを持たせられる書き方にしています」

「でも……」と僕は、いちばん気になっていたことを訊いた。「1つのインスタンスが、“承認済み"と"却下"の両方を、なぜ同時に持てなくなるんですか」

Approved という型は approvedBy(承認者名)だけを持ちます。Rejected という型は reason(却下理由)だけを持ちます。同じ器が、両方のデータを同時に持つ書き方自体が、もう存在しないんです。以前は"戻し忘れる印"があったから事故が起きた。今は、戻し忘れる対象の場所自体が、コードのどこにもありません」

「台帳の Permit は、どうなるんですか。あの3つの印は、まだ残るんですか」

「無くなります」とルークは言った。「Permit は、isSubmittedisApproved の代わりに、state: PermitState という一つのフィールドだけを持てばよくなります。表示も、その一つの statewhen で見るだけです。それと、これまでの approvereject は、印を直接書き換えていましたよね。今度は違います。今の姿を受け取って、次の姿を新しく返すだけ——元の Permit を書き換えるのではなく、val newState = approve(permit.state, ...) のように、返ってきた新しい姿を、台帳に上書きする形になります」

続けて、ルークは状態を移す関数を書いていく。

 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
// 遷移関数:今の状態から許される「次の姿」だけを、when で明示する(else を書かない)
fun submit(state: PermitState, by: String): PermitState = when (state) {
    is PermitState.Draft -> PermitState.Submitted(by)
    is PermitState.RevisionRequested -> PermitState.Submitted(by)
    is PermitState.Submitted,
    is PermitState.Approved,
    is PermitState.Rejected -> error("この状態からは申請できません: $state")
}

fun approve(state: PermitState, by: String): PermitState = when (state) {
    is PermitState.Submitted -> PermitState.Approved(by)
    is PermitState.Draft,
    is PermitState.RevisionRequested,
    is PermitState.Approved,
    is PermitState.Rejected -> error("この状態からは承認できません: $state")
}

fun reject(state: PermitState, reason: String): PermitState = when (state) {
    is PermitState.Submitted -> PermitState.Rejected(reason)
    is PermitState.Draft,
    is PermitState.RevisionRequested,
    is PermitState.Approved,
    is PermitState.Rejected -> error("この状態からは却下できません: $state")
}

fun requestRevision(state: PermitState, reason: String): PermitState = when (state) {
    is PermitState.Submitted -> PermitState.RevisionRequested(reason)
    is PermitState.Draft,
    is PermitState.RevisionRequested,
    is PermitState.Approved,
    is PermitState.Rejected -> error("この状態からは差し戻せません: $state")
}

「それぞれの手続きは、when で『今の姿から許される、次の姿だけ』を書きます。is——渡された値が、どの"姿"かを確かめる言葉です。else は、書きません」

is を、カンマで並べている所がありますね」と僕は、submit の下のほうを指した。

「ああ、それは『どれか一つに当てはまれば、同じ処理でいい』という意味です。SubmittedApprovedRejected のどれから申請しようとしても、同じエラーで弾いて構わないので、まとめて書いています」

else で残りを全部まとめてしまえば、楽ではないですか」と僕は訊いた。

else を書くと、“この姿は全部扱いました"という確認を、魔導具——コンパイラのことです——がしてくれなくなります。新しい姿を後で1つ足したとき、else があると、そこへ紛れ込んで、扱い忘れに誰も気づけません。else を書かないから、姿が増えた瞬間、扱っていない場所すべてで、魔導具が『まだ決めていません』と教えてくれるんです」

これが、when網羅性チェック——sealed class を相手にした when で、姿の扱い漏れがあるとコンパイルが通らなくなる仕組みだと、ルークは付け加えた。不正な遷移を試みると、error(...)——理由を添えてその場で処理を止める、Kotlin の短い書き方——で弾かれる。error(...) は、内側で IllegalStateException——「今の状態では、その操作はできない」という例外——を投げる。だから模擬詠唱では、assertFailsWith<IllegalStateException> で、ちゃんと弾かれたかを確かめる。

「前に別の見習いさんから、“アルゴリズムを差し替える"仕組みを聞いたことがあります。それとは違うんですか」

「似ていますが別物です。差し替えの仕組みは、どの状態でも同じ操作が使えます。今回は逆で、今の状態が、使える操作そのものを決めるんです。今の姿が、次に許される手続きを決める——これが、State という定石の骨格です。今回は、その骨格を Kotlin の型で固めた形になります」

State。今の状態が、次に許される操作そのものを決める——ルークが名付けたその定石は、僕の許可証台帳にも、そのまま当てはまった。

「本物の許可証台帳に触れる前に、紙の許可証を一枚だけ、机の上で動かしてみます」

ルークはそう言って、模擬詠唱——本番の前に、安全な形で試しに動かして確かめる作法——のコードを書いた。

 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

fun main() {
    // ①下書き → 申請で Submitted に正しいデータで遷移する
    run {
        val draft: PermitState = PermitState.Draft
        val submitted = submit(draft, "リオ")
        assertEquals(PermitState.Submitted("リオ"), submitted, "①: 申請すると Submitted(申請者名) になる")
    }

    // ②申請中 → 承認で Approved の承認者名が一致する
    run {
        val submitted: PermitState = PermitState.Submitted("リオ")
        val approved = approve(submitted, "組合長")
        assertEquals(PermitState.Approved("組合長"), approved, "②: 承認すると Approved(承認者名) になる")
    }

    // ③申請中 → 却下で Rejected の却下理由が一致する
    run {
        val submitted: PermitState = PermitState.Submitted("リオ")
        val rejected = reject(submitted, "素行に懸念")
        assertEquals(PermitState.Rejected("素行に懸念"), rejected, "③: 却下すると Rejected(却下理由) になる")
    }

    // ④差し戻し→再申請で、新しい Submitted に戻れる(往復)
    run {
        val submitted: PermitState = PermitState.Submitted("リオ")
        val revisionRequested = requestRevision(submitted, "添付不足")
        assertEquals(PermitState.RevisionRequested("添付不足"), revisionRequested, "④: 差し戻すと RevisionRequested(理由) になる")
        val resubmitted = submit(revisionRequested, "リオ")
        assertEquals(PermitState.Submitted("リオ"), resubmitted, "④: 書き直して再申請すると、新しい Submitted に戻れる")
    }

    // ⑤異常系・境界: 下書きから承認しようとすると弾かれる
    run {
        val draft: PermitState = PermitState.Draft
        assertFailsWith<IllegalStateException>("⑤: Draft から approve は弾かれる") {
            approve(draft, "組合長")
        }
    }

    // ⑤': 異常系・境界: 却下済みから再申請しようとしても弾かれる
    run {
        val rejected: PermitState = PermitState.Rejected("素行に懸念")
        assertFailsWith<IllegalStateException>("⑤': Rejected から submit は弾かれる") {
            submit(rejected, "リオ")
        }
    }

    println("ALL TESTS PASSED")
}

assertEquals は、動かした後の姿が持つデータが、狙いどおりか——承認者名や却下理由が一致しているかを確かめます。assertFailsWith は、許されない手続きを試したとき、ちゃんと止まってくれるかを確かめます」

一つずつ、ルークが読み上げてくれた。下書きから申請すれば、申請中の姿になり、申請者名が残る。承認すれば承認済みの姿になり、承認者名が残る。却下すれば却下の姿になり、理由が残る。差し戻しにしても、書き直して再申請すれば、また申請中の姿に戻れる。そして——下書きのまま承認しようとすると、ちゃんと止まる。

結界の中に、「ALL TESTS PASSED」の灯がともった。

戻し忘れる場所が、もう無い

書き直された許可証の仕組みを、僕は受け取った。3つ、4つあった独立した印は消え、残ったのは、一つずつの"姿"と、その姿だけが持つデータだった。

「……戻し忘れる場所が、もう無いんですね」

先週の事故が、少しだけ、遠くなっていく気がした。

「前は、印を一つ戻す一行を、僕が忘れました。今度は、忘れる場所自体が、コードの中に存在しない」

あの門の手前で立ち止まってもらった申請者のことを、僕はまだ覚えている。

「起きてから、直すんじゃない……起こせない形に、しておけばよかったんです」

新しい姿を足しても、魔導具が教えてくれる

「もう一つ、確かめたいことがあります」

僕は、ずっと引っかかっていたことを切り出した。「許可証には、審査が長引くと"失効"する仕組みも要る、と聞いています。もし今、Expired という姿を1つ足したら、どうなりますか」

ルークが sealed class に、data object Expired を一行、書き足す。

その瞬間、机に広げていた submitapproverejectrequestRevision——すべての when に、赤い印が並んだ。『'when' expression must be exhaustive'——姿の扱い漏れがある』というコンパイルエラーだ。

「ほら。新しい姿を足しただけで、“まだ決めていない場所"を、魔導具が全部、先に教えてくれます」

僕は、その赤い印の並びに、安堵に近いものを覚えた。

「……起きてから見つけるんじゃない。作った瞬間に、教えてもらえるんですね」

隅の仕掛け箱は、もう蓋を閉じて静まっていた。マグナスさまの手には、次の遊び道具——今度は小さな鍵束——が握られている。ルークに向かって何か言うでもなく、ただ鍵を一つ、鍵穴に合わせては外す仕草を、繰り返していた。ヒントはあの箱一つと、一つの問いだけ。答えを聞いた覚えは、ついぞ無い。

マグナスさまは、仕掛け箱の蓋を一つ、かちりと閉じてみせて、ぽつりと言った。

「一度に開く蓋は、いつも一つだ。今開いている蓋が、次に開く蓋を決めておる」

窓口へ戻った僕は、まず Expired の一件を、実際の台帳に足した。赤いコンパイルエラーが並んだ場所を、一つずつ埋めていく。埋め終えたとき、ふと気づいた——もう、あの事故を思い出して手が止まることが、無い。

印を増やすのではなく、姿を型で決め直す。それだけで、忘れる場所そのものが、消えていた。


📖 見習い魔術師ルークの魔導書メモ(Luke’s Spellbook Notes)

  • 修復した呪文(クラス/モジュール名): PermitState(許可証の姿の sealed class)、submit/approve/reject/requestRevision(遷移関数)
  • 乱れの要因(アンチパターンの課題):
    • 許可証の状態を、3つの独立した BooleanisSubmitted/isApproved/isRejected)で管理していた。フラグ同士に依存関係が無いため、「承認済みかつ却下」のような不正な組み合わせが型の上で作れてしまい、reject() の一行漏れ(isApproved を戻し忘れる)で実際に発生した。判定関数が印を見る順序次第で、事故が起きても表示上は隠れてしまった。
  • 過剰設計の反省(背伸びの過ち):
    • あり得る組み合わせを、巨大な判定関数で一つ残らず先回りして潰そうとした。だがこれは症状を検出して弾いているだけで、フィールドの矛盾そのものは直っていない。新しい手続き(差し戻し)が1つ増えただけで組み合わせが倍増し、同じ種類の書き忘れ(印を戻す一行の欠落)を、防御を積んだはずの2度目でも再発させてしまった。
  • 魔術の定石(学んだ設計パターン):
    • State+Kotlin の sealed class。今の状態が、次に許される操作を決めるという State の骨格を、sealed class の型階層と when の網羅性チェックで固めた。各状態(Draft/Submitted/RevisionRequested/Approved/Rejected)は固有のデータだけを持ち、矛盾する組み合わせをそもそも構文として書けなくする。遷移関数は else を書かず全サブタイプを列挙するため、新しい状態を1つ足すと、扱い漏れのある箇所すべてでコンパイルエラーが起き、書き忘れが実行前に見つかる。
  • 模擬詠唱の記録(検証のポイント):
    • 正常な遷移(申請・承認・却下・差し戻し→再申請)で固有データが正しく残ること、下書きから直接承認しようとする等の不正な遷移が error() で弾かれることを確認した。
  • ちなみに:
    • 状態の種類が少なく、振る舞いの違いも単純なら、フルセットの State 化はかえって過剰なこともある。Kotlin には sealed class のほかに sealed interface もあり、複数の sealed 階層を1つの型に組み合わせたいときに使う。
  • 師匠マグナスの格言:
    • 「一度に開く蓋は、いつも一つだ。今開いている蓋が、次に開く蓋を決めておる。」
comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。