Upgrade to Pro — share decks privately, control downloads, hide ads and more …

入門『状態』補遺編 #kaigionrails_kakoben / "state transi...

Avatar for ShinkuFencer ShinkuFencer
October 05, 2026
140

入門『状態』補遺編 #kaigionrails_kakoben / "state transitions" for beginners with Rails

Kaigi on Rails 2026 過去登壇勉強会( https://connpass.com/event/406332/ ) でお話したスライドです。

【スライド末尾で紹介されているURL】

状態論 (1) | Marginalia
https://blog.lacolaco.net/posts/theory-of-state-01/
負債のメタファと2026年 / Debt Metaphor in Agentic Engineering Age 202609 Edition https://speakerdeck.com/twada/debt-metaphor-in-agentic-engineering-age-202609-edition

Avatar for ShinkuFencer

ShinkuFencer

October 05, 2026

More Decks by ShinkuFencer

Transcript

  1. 素直にコードを書くと以下のようになる class LightBulb def initialize @is_on = false @brightness =

    0 end def turn_on return if @is_on @is_on = true @brightness = 50 end def brightness_up return unless @is_on @brightness = [@brightness + 10, 100].min end def brightness_down return unless @is_on @brightness = [@brightness - 10, 0].max end end def turn_off return unless @is_on @is_on = false @brightness = 0 end 12
  2. 新たな操作を増やす • • 新しく「現在ONならOFFに、OFFならONにする」操作を増やす シンプルに額面通り実装すると以下のようになる def toggle @is_on = !@is_on

    end • 良さそう...に見えるが、turn_onの実装と比較すると明るさの処理がない ので既存の状態変化と整合しない def turn_on return if @is_on @is_on = true @brightness = 50 end OFFからONになるときに 明るさを50に設定する 15
  3. 新たな操作を増やす • turn_onやturn_offの振る舞いを踏まえると、以下のような実装になる def toggle if @is_on @is_on = false

    @brightness = 0 else @is_on = true @brightness = 50 end end • • 既存の振る舞いにあわせ 値の代入処理を追加 このように新しい状態変化の操作を増やしたときに他の変化との整合性を とる必要があるが、見落としが起こりうる 状態の変化を見渡しやすく、管理しやすくするにはどうすると良いか? 16
  4. 「状態の変化」を管理しやすくするには • この考え方をコードで表現すると以下のようになる def transition(state, action) # 操作前の状態(state)と操作(action)から # 操作後の状態を返却する

    end next_state = transition(state, :toggle) • 電球の場合はON/OFFと明るさの組み合わせが状態なので、ひとまとまり の state として扱う 20
  5. 状態を取り扱うためのクラスを用意する class LightingSetting attr_reader :is_on, :brightness def initialize(is_on:, brightness:) @is_on

    = is_on @brightness = brightness freeze end end ON/OFFと明るさを 一まとまりの値として保持する 21
  6. transitionのコード def transition(state, action) case [state.is_on, action] when [false, :turn_on]

    LightingSetting.new(is_on: true, brightness: 50) when [true, :turn_off] LightingSetting.new(is_on: false, brightness: 0) when [false, :toggle] LightingSetting.new(is_on: true, brightness: 50) when [true, :toggle] LightingSetting.new(is_on: false, brightness: 0) when [true, :brightness_up] next_brightness = [state.brightness + 10, 100].min LightingSetting.new(is_on: true, brightness: next_brightness) when [true, :brightness_down] next_brightness = [state.brightness - 10, 0].max LightingSetting.new(is_on: true, brightness: next_brightness) when [true, :turn_on], [false, :turn_off], [false, :brightness_up], [false, :brightness_down] state else raise ArgumentError, "unsupported transition" end end 22
  7. transitionのコード def transition(state, action) case [state.is_on, action] when [false, :turn_on]

    LightingSetting.new(is_on: true, brightness: 50) 「操作前の状態」と「操作」 で 「操作後の状態」が導き出される when [true, :turn_off] LightingSetting.new(is_on: false, brightness: 0) when [false, :toggle] LightingSetting.new(is_on: true, brightness: 50) when [true, :toggle] LightingSetting.new(is_on: false, brightness: 0) when [true, :brightness_up] next_brightness = [state.brightness + 10, 100].min LightingSetting.new(is_on: true, brightness: next_brightness) when [true, :brightness_down] next_brightness = [state.brightness - 10, 0].max LightingSetting.new(is_on: true, brightness: next_brightness) when [true, :turn_on], [false, :turn_off], [false, :brightness_up], [false, :brightness_down] state else raise ArgumentError, "unsupported transition" end end 23
  8. transitionのコード def transition(state, action) case [state.is_on, action] when [false, :turn_on]

    LightingSetting.new(is_on: true, brightness: 50) when [true, :turn_off] LightingSetting.new(is_on: false, brightness: 0) when [false, :toggle] LightingSetting.new(is_on: true, brightness: 50) 考慮した結果として変化がない場合と そもそも考慮していないことが 区別しやすくなる when [true, :toggle] LightingSetting.new(is_on: false, brightness: 0) when [true, :brightness_up] next_brightness = [state.brightness + 10, 100].min LightingSetting.new(is_on: true, brightness: next_brightness) when [true, :brightness_down] next_brightness = [state.brightness - 10, 0].max LightingSetting.new(is_on: true, brightness: next_brightness) when [true, :turn_on], [false, :turn_off], [false, :brightness_up], [false, :brightness_down] state else raise ArgumentError, "unsupported transition" end end 24
  9. LightBulbクラスのインターフェイスはそのままで class LightBulb def initialize @setting = LightingSetting.new(is_on: false, brightness:

    0) end def turn_on @setting = transition(@setting, :turn_on) end def brightness_up @setting = transition(@setting, :brightness_up) end def toggle @setting = transition(@setting, :toggle) end private def transition(state, action) #略 end end 25
  10. 局所的な変更を状態変化全体と照らし合わせる • • • • 個々の要求を正しく理解していても、要求同士の相互作用を俯瞰できない と見落としが起きやすい Coding Agentに実装を任せる範囲が広がっても、システムがどう状態変 化するかは把握しておきたい

    そのために、状態変化を「操作」と「操作前の状態」という共通の観点で 整理しておくと、全体を把握しやすい 実装の詳細を逐一追わなくても、どの状態でどの操作がどう振る舞うかを 把握しておけば、要求同士の整合性を確認しやすい 状態変化を、人が理解・確認しやすい形にしておくことは引き続き重要 32
  11. Thanks! 参考資料: 状態論 (1) | Marginalia https://blog.lacolaco.net/posts/theory-of-state-01/ 負債のメタファと2026年 / Debt

    Metaphor in Agentic Engineering Age 202609 Edition https://speakerdeck.com/twada/debt-metaphor-in-agentic-engineering-age-202609-edition しんくう / shinkufencer a.k.a ikuo.takada @shinkufencer コード日進月歩 https://shinkufencer.hateblo.jp/ 34