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

状態設計から「なんとなく」を無くそう

Masaki Hara
December 07, 2023

 状態設計から「なんとなく」を無くそう

ウォンテッドリー株式会社の社内イベント "Tech Lunch" で話した発表です。

プログラムには大小さまざまな粒度の「状態」が存在します。
状態の設計を工夫することで、コーナーケースの発生を抑止し、ユーザー体験を最適化することができます。
本発表では、私が普段どのように「状態」について考えているか、言語や環境を問わずできるだけ普遍的に使える形での言語化を試みます。本発表を通じて、「状態」をなんとなくではなく合理的に設計するためのヒントを提供します。

GoogleスライドのURL: https://docs.google.com/presentation/d/1PNzz69UV05HlKPuWGlooemnPslLbLKsyLwl3R4U_XqE/edit

Masaki Hara

December 07, 2023
Tweet

More Decks by Masaki Hara

Other Decks in Programming

Transcript

  1. © 2023 Wantedly, Inc.
    状態設計から
    「なんとなく」を無くそう
    Wantedly Tech Lunch
    Dec. 7 2023 - Masaki Hara

    View full-size slide

  2. © 2023 Wantedly, Inc.
    目的
    ● 状態の設計が悪いと、コーナーケースが発生しやすくなります
    ● コーナーケースはプロダクトの成長にともない顕在化し、ユー
    ザーとサポートと開発者に牙を剥きます。
    ● 状態を合理的に設計することで、問題を未然に抑止しましょ
    う。

    View full-size slide

  3. © 2023 Wantedly, Inc.
    抽象的に考える
    ● 本発表では「状態」という概念をあえて抽象的に扱います
    ○ 具体例も出しますが、具体例に引っぱられすぎないように注意してください。
    ● 異なる粒度の問題に普遍的に使える考え方を提示することを
    試みます
    ○ 古典力学は惑星運動と地上の力学を同時に説明することに成功しました。このような普
    遍性を提供することがここでの目標です。

    View full-size slide

  4. © 2023 Wantedly, Inc.
    枠組み
    以下の3点を意識的に決定しよう
    ● 状態空間
    ● 状態の置き場所
    ● 状態の表現

    View full-size slide

  5. © 2023 Wantedly, Inc.
    1: 状態空間

    View full-size slide

  6. © 2023 Wantedly, Inc.
    枠組み
    以下の3点を意識的に決定しよう
    ● 状態空間
    ● 状態の置き場所
    ● 状態の表現

    View full-size slide

  7. © 2023 Wantedly, Inc.
    状態空間
    「状態空間」をここでは以下の (Q, Σ, δ)とする:
    ● 状態集合 Q
    ● 入力の集合 Σ
    ● 入力の作用 δ: Q × Σ → Q
    例を見てみよう→

    View full-size slide

  8. © 2023 Wantedly, Inc.
    小さい状態空間: いいねの状態
    ● 状態集合 Q = { 未いいね, いいね済 }
    ● 入力の集合 Σ = { いいねする, いいねを外す }
    ● 入力の作用 δ:
    未いいね いいね済
    いいねを外す
    いいねを外す
    いいねする
    いいねする

    View full-size slide

  9. © 2023 Wantedly, Inc.
    大きい状態空間: SQL
    ● 状態集合 Q = { テーブルの全状態 }
    ● 入力の集合 Σ = { 全てのSQL DMLコマンド }
    ● 入力の作用 δ(q, c) = (cの実行後のテーブル)

    View full-size slide

  10. © 2023 Wantedly, Inc.
    大きい状態空間: Redux
    ● 状態集合 Q = State
    ● 入力の集合 Σ = Action
    ● 入力の作用 δ ∈ Reducer

    View full-size slide

  11. © 2023 Wantedly, Inc.
    4つのアノマリー
    ● 状態空間の代表的な4つのアノマリーを紹介
    ● アノマリーがないように状態を作ってみよう
    ※ これらのアノマリーにより、前のスライドの定義を満たさないものも出てきます

    View full-size slide

  12. © 2023 Wantedly, Inc.
    加法的なアノマリー
    + -
    「到達不能」アノマリー
    ● 余分な状態
    ● 状態を消して対応
    「状態不足」アノマリー
    ?
    ● 状態が足りない
    ● 状態を足して対応

    View full-size slide

  13. © 2023 Wantedly, Inc.
    乗法的なアノマリー
    × ÷
    「重複」アノマリー
    ● 区別する必要がない状態
    ● 状態を統合して対応
    「情報不足」アノマリー
    ● 遷移先が決定できない
    ● 状態を分割して対応
    ?

    View full-size slide

  14. © 2023 Wantedly, Inc.
    情報の消失
    ● 同じ入力で同じ状態に合流するパターンにも注意
    ● 情報が捨てられていることで起きる何らかの問題を示唆して
    いる場合がある

    View full-size slide

  15. © 2023 Wantedly, Inc.
    例: 相互フォローシステム
    ● フォロー機能のあるSNSを考える
    ● フォローすると同時にフォローリクエストが飛ぶ
    ● フォローリクエストは受諾または無視できる

    View full-size slide

  16. © 2023 Wantedly, Inc.
    例: 相互フォローシステム
    未フォロー
    フォロー
    被フォロー
    フォロー
    +リクエスト
    被フォロー
    +リクエスト
    相互フォロー

    View full-size slide

  17. © 2023 Wantedly, Inc.
    例: 相互フォローシステム
    未フォロー
    フォロー
    被フォロー
    フォロー
    +リクエスト
    被フォロー
    +リクエスト
    相互フォロー
    フォロー
    被受諾
    被無視
    被フォロー
    受諾
    無視
    被フォロー
    フォロー

    View full-size slide

  18. © 2023 Wantedly, Inc.
    例: 相互フォローシステム
    未フォロー
    フォロー
    被フォロー
    フォロー
    +リクエスト
    被フォロー
    +リクエスト
    相互フォロー
    アンフォロー
    アンフォロー
    被アンフォロー
    被アンフォロー
    アンフォロー

    View full-size slide

  19. © 2023 Wantedly, Inc.
    問題
    ● この「相互フォロー」システムには何か問題がありますか?

    View full-size slide

  20. © 2023 Wantedly, Inc.
    解答例: State Smellの発見
    ● 以下で状態の合流 (=情報の消失) が起きている
    未フォロー
    フォロー
    フォロー
    +リクエスト
    アンフォロー
    アンフォロー

    View full-size slide

  21. © 2023 Wantedly, Inc.
    解答例: State Smellの発見
    ● →情報が消失したあとの遷移に注目する
    未フォロー
    フォロー
    フォロー
    +リクエスト
    アンフォロー
    アンフォロー
    フォロー
    +リクエスト
    フォロー

    View full-size slide

  22. © 2023 Wantedly, Inc.
    解答例: State Smellの発見
    ● →無視されてもアンフォロー&フォローで復活できる
    未フォロー
    フォロー
    アンフォロー
    フォロー
    +リクエスト
    フォロー
    被無視

    View full-size slide

  23. © 2023 Wantedly, Inc.
    解答例
    ● フォローリクエストの「無視」を突き抜ける戦略があることがわ
    かった
    ○ ただし、これが実際に問題かどうかはプロダクト判断になる。
    ○ 大事なのは、意図をもって判断を下すことができるようになること
    ● では、直すとしたら?

    View full-size slide

  24. © 2023 Wantedly, Inc.
    解答例: State Smellの解消
    ● やりたいこと: 「フォロー」の結果を変えたい
    未フォロー
    フォロー
    フォロー
    +リクエスト
    アンフォロー
    アンフォロー
    フォロー
    +リクエスト
    フォロー
    フォロー
    フォロー

    View full-size slide

  25. © 2023 Wantedly, Inc.
    解答例: State Smellの解消
    ● → 「情報不足」アノマリーが起きている
    未フォロー
    フォロー
    フォロー
    +リクエスト
    アンフォロー
    アンフォロー
    フォロー
    +リクエスト
    フォロー
    フォロー
    フォロー

    View full-size slide

  26. © 2023 Wantedly, Inc.
    解答例: State Smellの解消
    ● 状態を分割して対応
    未フォロー
    フォロー
    フォロー
    +リクエスト
    アンフォロー
    アンフォロー
    フォロー
    +リクエスト
    フォロー
    フォロー
    フォロー
    未フォロー
    +被無視

    View full-size slide

  27. © 2023 Wantedly, Inc.
    モデルと向き合うときに大事なこと
    ● モデルを観察して、怪しい箇所を探す
    ○ これはある程度形式的にできる
    ● → 具体例に落としこんで、プロダクトオーナーの視点で考え直

    ○ これはモデルの外で起きる
    ○ プロダクトオーナー自身でなくてもある程度はやってみよう

    View full-size slide

  28. © 2023 Wantedly, Inc.
    2: 状態の置き場所

    View full-size slide

  29. © 2023 Wantedly, Inc.
    枠組み
    以下の3点を意識的に決定しよう
    ● 状態空間
    ● 状態の置き場所
    ● 状態の表現

    View full-size slide

  30. © 2023 Wantedly, Inc.
    状態ストア
    状態には保存する場所が必要
    → 状態ストア の概念

    View full-size slide

  31. © 2023 Wantedly, Inc.
    状態ツリー
    大きい状態 = 小さい状態の組み合わせ
    RealWorld
    Server
    Client
    Human
    RDB
    Redis
    Cookie
    Location
    Memory
    Users
    Posts
    Session
    Cache
    Auth

    View full-size slide

  32. © 2023 Wantedly, Inc.
    状態ツリー
    ● 小さい状態の置き場を考えること
    = 大きい状態の状態空間設計
    ● → 大きい状態をモデリングすることで同じ考え方が適用でき

    View full-size slide

  33. © 2023 Wantedly, Inc.
    状態置き場を考えるときの原則
    ● 原則1: 異なる状態ストアは異なるライフサイクルを持つ
    ● 原則2: ストア間の同期を信用してはいけない

    View full-size slide

  34. © 2023 Wantedly, Inc.
    2-1: 状態ストアのライフサイクル

    View full-size slide

  35. © 2023 Wantedly, Inc.
    原則1
    異なる状態ストアは異なるライフサイクルを持つ
    → そのストアが「いつ初期化され」「いつ消去されるのか」を意識
    して選択しよう

    View full-size slide

  36. © 2023 Wantedly, Inc.
    原則1
    例: ダークモード
    ● Webサイトでダークモードとライトモードを選択できるようにし
    たい。
    ● 状態をどこに保存するか?

    View full-size slide

  37. © 2023 Wantedly, Inc.
    原則1
    ● サーバー側 (ユーザー設定):
    ○ ユーザーが作られたときに初期化され、退会によって消去される。
    ○ → 匿名の場合を考慮する必要がある
    ● ブラウザ localStorage/Persistent Cookie:
    ○ 新しいブラウザでアクセスしたときに初期化される。
    ○ → 同じユーザーでも、複数のブラウザを利用していれば別の値が入る。
    ● ブラウザ sessionStorage/Session Cookie:
    ○ ブラウザ起動時に初期化され、終了時に消去される。
    ○ → ブラウザを再起動したときに永続化されない

    View full-size slide

  38. © 2023 Wantedly, Inc.
    原則1
    ● URL (query / fragment)
    ○ URL発行によって初期化される。
    ○ → URLを別のユーザーに共有しても永続化される。
    ○ → 逆に、別タブにその影響は漏れ出さない。
    ● History data
    ○ URLと近いが、URL共有で引き継がれない。

    View full-size slide

  39. © 2023 Wantedly, Inc.
    例題
    以下が発生するシナリオを考えてください。
    ● ユーザー設定よりもlocalStorageが長生きする場合
    ● localStorageよりもHistory dataが長生きする場合

    View full-size slide

  40. © 2023 Wantedly, Inc.
    解答例
    解答例
    ● 同じブラウザでログアウトして別のユーザーで再ログインした
    場合
    ○ ログアウト時にlocalStorageを消さない場合。消す場合でも、
    localStorageを
    devtoolsでコピーするなどのシナリオは考えられる
    ● history.pushで別ページに遷移して設定を変更後、ブラウザ
    バックで元のページに戻った場合

    View full-size slide

  41. © 2023 Wantedly, Inc.
    具体例から最適解を考える
    ● 差異が生じる具体的なケースがイメージできれば、プロダクト
    オーナー視点で判断ができるはず
    ● たとえば……
    ○ ユーザーが自分の好みを持ち運べるようにしたい
    ? → サーバーがいいかも
    ○ デバイスごとに事情が違うかも? → localStorageがいいかも
    ○ どちらかが常に正解というわけではない。
    大事なのは、意図をもって判断を下すことができるようになること

    View full-size slide

  42. © 2023 Wantedly, Inc.
    2-2: 状態ストアの同期

    View full-size slide

  43. © 2023 Wantedly, Inc.
    原則2
    原則2: ストア間の同期を信用しない
    → 絶対に守りたい相関があるなら、同じストアに配置する
    さもなくば既存のトランザクションを使うか、
    頑張ってトランザクションを組む (茨の道)

    View full-size slide

  44. © 2023 Wantedly, Inc.
    隠れ状態
    隠れ状態
    ● 複数のストアが正しく同期されている間は起きない状態
    ● コマンドが一部のストアにだけ適用されることで起きる
    本発表における「状態空間」の数学的な定義に基づいて説明するなら :
    状態の直積として入力 Σが共通 (Σ = Σ
    1
    = Σ
    2
    ) なものを考えていたが、入力が独立に行われるような
    直積 (Σ = Σ
    1
    ⨿ Σ
    2
    ) を考えることもできる。このような直積では、元の状態空間で到達可能な状態の
    組み合わせであれば直積空間でも到達可能であるといえる。

    View full-size slide

  45. © 2023 Wantedly, Inc.

    例(再掲): 相互フォローシステム (6状態)

    View full-size slide

  46. © 2023 Wantedly, Inc.
    例: 相互フォローシステム
    未フォロー
    フォロー
    被フォロー
    フォロー
    +リクエスト
    被フォロー
    +リクエスト
    相互フォロー
    フォロー
    被受諾
    被無視
    被フォロー
    受諾
    無視
    被フォロー
    フォロー

    View full-size slide

  47. © 2023 Wantedly, Inc.
    例: 相互フォローシステム
    未フォロー
    フォロー
    被フォロー
    フォロー
    +リクエスト
    被フォロー
    +リクエスト
    相互フォロー
    アンフォロー
    アンフォロー
    被アンフォロー
    被アンフォロー
    アンフォロー

    View full-size slide

  48. © 2023 Wantedly, Inc.
    相互フォローシステムの状態分割
    ● この状態をDBで表現するときは、フォロー状態と被フォロー状
    態は分けて管理するのが一般的
    ○ リクエストの持ち方に任意性があるが、ここでは被リクエスト側の状態として持つことを考
    える
    ● この場合はストア = グラフ上のエントリとなる
    ○ RDBでいえば1つの行に注目している

    View full-size slide

  49. © 2023 Wantedly, Inc.
    相互フォローシステムの状態分割
    片側の状態
    未フォロー 被リクエスト
    (未フォロー)
    フォロー
    被フォロー
    フォロー
    受諾
    アンフォロー
    被アンフォロー
    or 無視

    View full-size slide

  50. © 2023 Wantedly, Inc.
    相互フォローシステムの状態分割
    ● 自分側3状態 × 相手側3状態 = 9状態
    ● 実際に到達できるのは6状態なので、元の状態空間と同じに
    なる
    → これが仮定できるかどうかは場合による

    View full-size slide

  51. © 2023 Wantedly, Inc.
    相互フォローシステムの状態分割
    ● 6状態しかないと言えるのは、両ストアへの作用がアトミックに
    行われると仮定しているから
    ○ まっとうな分離レベルのトランザクションであれば問題ない
    ● アトミックでない場合は変な状態が発生しうる

    View full-size slide

  52. © 2023 Wantedly, Inc.
    相互フォローシステムの状態分割
    例: 双方が同時にフォローを行った場合のシナリオ
    ● A側のフォロー処理は以下の2手順
    ○ A→Bの状態を「フォロー」にする
    ○ B→Aの状態を「被リクエスト」にする
    ● B側も同様
    ● 両者の手順が交互に行われると……?
    ○ 両方向の状態が「被リクエスト」になってしまう

    View full-size slide

  53. © 2023 Wantedly, Inc.
    隠れ状態
    ● 隠れ状態: 状態空間の直積のうち、同期が取れている限りは
    到達しないはずの状態
    ○ A→Bが「被リクエスト」 なのに B→Aが「未フォロー」
    ○ A→Bが「未フォロー」 なのに B→Aが「被リクエスト」
    ○ A→Bが「被リクエスト」 なのに B→Aも「被リクエスト」
    ● アトミックでない場合、実際は到達可能なことが多い

    View full-size slide

  54. © 2023 Wantedly, Inc.
    同期を信用しないという選択
    ● 全ての同期が完全である必要はない
    ○ 結果整合で十分な場合などはある
    ● 同期が完全でない場合 → 隠れ状態にあっても大丈夫なよう
    に実装する
    ○ 「この組み合わせはない」という仮定を置かないように実装しよう

    View full-size slide

  55. © 2023 Wantedly, Inc.
    2-2’: キャッシュ

    View full-size slide

  56. © 2023 Wantedly, Inc.
    キャッシュ
    ● キャッシュ = あえて冗長に、複数の状態ストアに同じ情報を持
    たせること
    ○ たとえば、サーバーにある状態をクライアントの状態としてコピーする
    ● 一定の不整合を許容する見返りにメリットが得られる
    ○ パフォーマンス
    ○ ユーザー体験のチューニング
    例: ブックマークした投稿の一覧からブックマークを外したとき、一覧から即座に消えない
    ようにする

    View full-size slide

  57. © 2023 Wantedly, Inc.
    キャッシュ
    ● 意図しないキャッシュ構造ができている場合もある
    ○ React.useStateを使ったが、実は直接取得可能な状態だった
    ● ストアを作るとき、それが冗長でないかよく考えよう

    View full-size slide

  58. © 2023 Wantedly, Inc.
    3: 状態の表現

    View full-size slide

  59. © 2023 Wantedly, Inc.
    枠組み
    以下の3点を意識的に決定しよう
    ● 状態空間
    ● 状態の置き場所
    ● 状態の表現

    View full-size slide

  60. © 2023 Wantedly, Inc.
    状態の表現
    ● 状態集合をぴったり表現できるとは限らない
    ● 「状態の表現の集合」 ≠ 「状態集合」

    View full-size slide

  61. © 2023 Wantedly, Inc.
    状態の表現
    「状態の表現の集合」と「状態集合」のギャップは以下の2種類
    ●  除外 (部分集合) … 実際には出現しない表現がある。
    ●  同一視 (商集合) … 複数の表現をもつ状態がある。
    いずれのギャップも少ないほうがよい
    -
    ÷

    View full-size slide

  62. © 2023 Wantedly, Inc.
    非正規形
    ● 非正規形 = 除外によっても同一視によっても対応できる表現
    ○ 「隠れ状態」は非正規形
    ● 例: boolをintで表現する
    ○ 0, 1: 正規形
    ○ 2以上の整数: 原則として除外されるが、もし存在した場合は
    1と同一視される

    View full-size slide

  63. © 2023 Wantedly, Inc.
    ギャップは悪か?
    ● 表現の工夫によって保証することが難しい性質もある
    ○ 例: 「プロフィールの登録が完了していなければコンテンツを投稿できない」
    ● 状態集合とその表現のギャップを受け入れることも必要
    ○ 大事なのは、意図をもって判断を下すことができるようになること

    View full-size slide

  64. © 2023 Wantedly, Inc.
    まとめ

    View full-size slide

  65. © 2023 Wantedly, Inc.
    まとめ
    ● 小さな状態、大きな状態の両方に目を向けてみよう
    ● 状態空間そのもの、その置き場所、そして表現の3つに分けて
    考えてみよう
    ● 状態管理のコーナーケースを具体例に落としこみ、プロダクト
    のあり方に翻って合理的な判断をしよう
    ○ 大事なのは、意図をもって判断を下すことができるようになること

    View full-size slide