$30 off During Our Annual Pro Sale. View Details »

事業の試行錯誤を支える コードを捨てやすくして システムをシンプルに保つ設計と工夫

zuckey_17
October 27, 2023

事業の試行錯誤を支える コードを捨てやすくして システムをシンプルに保つ設計と工夫

Kaigi on Rails 2023 での発表資料です。

https://kaigionrails.org/2023/talks/zuckey/

zuckey_17

October 27, 2023
Tweet

More Decks by zuckey_17

Other Decks in Programming

Transcript

  1. 事業の試⾏錯誤を⽀える
    コードを捨てやすくして
    システムをシンプルに保つ設計と⼯夫
    2023-10-28 / Kaigi on Rails 2023
    株式会社スタディスト 松村和輝@zuckey_17
    1

    View Slide

  2. 2
    松村 和輝 @zuckey_17
    🙅「ざっきー」 🙆「ずっきー」
    ⾃⼰紹介
    https://twitter.com/zuckey_17
    https://github.com/zuckeyM-17
    https://blog.zuckey17.org/
    https://medium.com/@kazuki.matsumura
    2020年1⽉~現在 株式会社スタディスト
    ● ⼩売企業向けの新規事業 『ハンクラ』の⽴ち上げリードエンジニアとして⼊社
    ● 現在は開発チームのマネージャーとプロダクトマネージャーを兼務

    View Slide

  3. 新規事業にアサインされ
    Webアプリケーションを作ることになりました。
    Ruby on Rails を採⽤しますか?
    3

    View Slide

  4. 4
    ● 少⼈数での開発でも⾼い⽣産性を出しやすい
    ● 企業向けで、特定業種向けのサービスのため、爆発的に、想定以上にトラフィックが
    伸びる可能性は低そう
    ● すでに会社の他プロダクトでRailsで使われており、⼈の追加やスワップが容易そう
    ● Ruby on Rails が zuckeyが最も慣れているかつ、好きなフレームワークだ
    新規事業でRuby on Railsを採⽤したときの頭の中(2020年当時)

    View Slide

  5. 5
    新規事業でRuby on Railsを採⽤したときの頭の中(2020年当時)
    ● 少⼈数での開発でも⾼い⽣産性を出しやすい
    ● 企業向けで、特定業種向けのサービスのため、爆発的に、想定以上にトラフィックが
    伸びる可能性は低そう
    ● すでに他プロダクトでRailsで使われており、⼈の追加やスワップが容易そう
    ● Ruby on Rails が zuckeyが最も慣れているかつ、好きなフレームワークだ
    スピード感を持って
    事業の試⾏錯誤を⽀えることができる

    View Slide

  6. ● パイロット顧客(2, 3社)を⾒つける
    ○ = プロダクトのコンセプトに共感してくれて、実際に現場で利⽤してくれる顧客
    ● 「この⼈たちの業務を楽にするために開発していくぞ!!」
    ○ 実業務の利⽤による貴重なフィードバックがありがたい
    ○ 実際に楽になった、便利だったという声が嬉しい
    ○ 売上が⽴つ、嬉しい
    ● 特定顧客への特殊(かもしれない)業務に寄り添う
    ○ ⽴ち上げ初期では継続的な進化(機能追加)への期待が⼤きい
    ○ ただ、顧客⾃⾝もその機能が本当に必要かどうかは知らない
    新規事業⽴ち上げ初期の開発の様⼦
    6

    View Slide

  7. ⽅針転換は試⾏錯誤の証
    回数をこなして事業を前に進めたい
    7

    View Slide

  8. 作り込みをすべてそのまま残したら
    ⽅針転換に対応できない
    8

    View Slide

  9. ● 試しに実装してみて、だめだったら簡単にやめられる
    ● 徐々に変えていきたいのではなく、Drastic(劇的)に変えることができる
    新規事業で重要なソフトウェアの特性
    9

    View Slide

  10. 変化に強いソフトウェアをつくるための多くの概念やキーワードを⾒かける。
    ● SOLID原則
    ● 結合度‧凝集度
    ● DDD(Domain Driven Design)
    ● 進化的アーキテクチャ
    ● など
    チームでの共有‧教育には時間がかかる。
    営業‧カスタマーサクセスのチームを含めた場で伝えるのが難しい。
    変化に強いソフトウェア
    10
    Design Principles and Design Patterns
    構造化設計
    Domain-Driven Design: Tackling Complexity in the Heart of Software
    進化的アーキテクチャ 絶え間ない変化を⽀える

    View Slide

  11. コードや機能を捨てやすくし、
    システムをシンプルに保つ
    11

    View Slide

  12. https://twitter.com/lukehannontv/status/794581557357015040
    不要だと判断された機能は削除しないと利⽤者の満⾜度も下がる
    12

    View Slide

  13. コード/機能削除の事例を紹介
    13

    View Slide

  14. 対象のソフトウェアについて
    14

    View Slide

  15. 15
    ● 遠隔地に複数店舗を運営している⼩売企業
    ● 本部から店舗に作業指⽰(=施策)を配信
    ● 店舗は作業状況や商品棚の画像などを返す
    ● 店舗に赴かないと現場の状況がわからず、
    学びを次の施策に活かせない
    ハンクラ
    OK
 OK
 OK
 OK
 OK

    NG
 OK
 OK
 OK
 OK

    OK
 OK
 NG
 OK
 OK

    OK
 OK
 OK
 OK
 NG

    本部

    View Slide

  16. 16
    Single Page Application
    by Vue.js
    Web Server
    & ActiveJob Server

    View Slide

  17. 事例1
    施策の統計情報、サマリ情報を⾒たい
    17

    View Slide

  18. ● データが溜まると、イイ感じにまとめて
    ⾒たいという要求はあるある
    ● 施策について店舗群がどのように反応し
    たかを把握したい
    事例1. 統計情報、サマリ情報を⾒たい
    18

    View Slide

  19. 19
    事例1. サマリテーブルを作ってそれを参照できるようにする
    施策 = scheme
    施策サマリ =
    scheme_summaries

    View Slide

  20. 20
    事例1. シンプルに作るとこんな感じ
    Web Server
    evidence
    提出
    enqueue
    UpdateSchemeSummaryJob
    evidence
    scheme_summaries
    update
    summary
    Web Server
    API
    Worker
    create
    evidence

    View Slide

  21. 21
    事例1. サマリのテーブルの周りを更新/参照する系
    SchemeSummaryContoroller
    SchemeSummarySerializer
    SchemeSummary
    UpdateScheme
    SummaryJob
    scheme_summaries
    API

    View Slide

  22. 事例1. 閲覧する⼈、閲覧タイミングによって得たい情報は異なる
    22
    時間軸
    施策終了
    【商品部】
    商品が売れ、売上が上が
    ることに責任を持つ
    陳列までのリードタイムが
    気になる
    【店舗運営部】
    店舗の健全な運営、コストの
    削減に責任を持つ
    作業負荷が想定通りで
    再提出が異常に多くないか
    【実施中】
    思いもよらない不具合があり、
    緊急対応をする可能性
    督促をする閾値を越えないか
    【終了後】
    売上実績と比較してどうか
    他の施策と比べてどうか

    View Slide

  23. 23
    事例1. ⾒せたい場所に応じてAPIは分けたい
    SchemeStats
    Contoroller
    SchemeStat
    Serializer
    SchemeSummary < AR::Base UpdateSchemeSummaryJob
    scheme_summaries
    SchemeProgresses
    Contoroller
    SchemeProgress
    Serializer
    SchemeSalesReports
    Contoroller
    SchemeSalesReport
    Serializer
    Sales 系 Model
    すべてのデータが⼊った scheme_sumamries からそ
    れぞれの利⽤箇所で必要なデータを取り出して使う

    View Slide

  24. 24
    事例1. 画⾯が不要になったら部分的に削除
    SchemeStats
    Contoroller
    SchemeStat
    Serializer
    SchemeSummary < AR::Base UpdateSchemeSummaryJob
    scheme_summaries
    SchemeProgresses
    Contoroller
    SchemeProgress
    Serializer
    SchemeSalesReport
    Serializer
    SchemeSalesReports
    Contoroller
    Sales 系 Model
    どのカラムが必要なくなったのかが
    ⾃明ではなく、不要になったロジックの
    消し忘れが発⽣してしまう可能性もある
    売上系のデータは顧客の基幹システムから
    落として、Excelで分析するので、この画⾯はいらない

    View Slide

  25. 事例1. 1つのテーブルを使い回すのではなく⽤途に対してテーブルを作る
    SchemeStats
    Contoroller
    SchemeStat
    Serializer
    SchemeProgress
    < AR::Base
    scheme_progresses
    SchemeProgresses
    Contoroller
    SchemeProgress
    Serializer
    SchemeSalesReports
    Contoroller
    SchemeSalesReport
    Serializer
    Salesのデータ
    SchemeStat
    < AR::Base
    scheme_stats
    SchemeSalesReport
    < AR::Base
    scheme_sales_reports
    UpdateScheme
    SalesReportJob
    UpdateSchemeStatJob
    UpdateSchemeProgressJob

    View Slide

  26. 26
    事例1. こんな感じ
    Web Server
    提出
    enqueue
    UpdateSchemeStatJob
    UpdateSchemeProgressJob
    scheme_stats
    scheme_progresses
    scheme_sales_reports
    update
    Web Server
    Worker
    Batch
    enqueue
    UpdateSchemeSales
    ReportJob
    API
    create
    evidence

    View Slide

  27. 事例1. 画⾯が不要になったらテーブルごと削除
    SchemeStats
    Contoroller
    SchemeStat
    Serializer
    SchemeProgress
    < AR::Base
    scheme_progresses
    SchemeProgresses
    Contoroller
    SchemeProgress
    Serializer
    SchemeSalesReports
    Contoroller
    SchemeSalesReport
    Serializer
    Salesのデータ
    SchemeStat
    < AR::Base
    scheme_stats
    SchemeSalesReport
    < AR::Base
    scheme_sales_reports
    UpdateScheme
    SalesReportJob
    UpdateSchemeStatJob
    UpdateSchemeProgressJob
    売上系のデータは顧客の基幹システムから
    落として、Excelで分析するので、この画⾯はいらない
    ファイルごと
    削除すればOK

    View Slide

  28. 28
    事例1. テーブルを中⼼に作ると削除に踏み切りやすい
    ● 1つのテーブルを使い回すのではなく⽤途
    に対してテーブルを作る
    ● 素直に作ることができていれば、テーブル
    を削除したら更新‧参照しているController
    や Job もその単位で削除できる
    ● ActiveRecord が中⼼にあるRails はこの考え
    ⽅があっていそう
    Contoroller
    Serializer
    Model
    (ActiveRecord::Bas
    e)
    Job
    Table
    Contoroller
    Serializer
    Model
    (AR::Base)
    Job
    Table

    View Slide

  29. 29
    事例1. テーブルを中⼼に作ると削除に踏み切りやすい
    ● 1つのテーブルを使い回すのではなく⽤途
    に対してテーブルを作る
    ● 素直に作ることができていれば、テーブル
    を削除したら更新‧参照しているController
    や Job もその単位で削除できる
    ● ActiveRecord が中⼼にあるRails はこの考え
    ⽅があっていそう
    Contoroller
    Serializer
    Model
    (ActiveRecord::Bas
    e)
    Job
    Table
    Contoroller
    Serializer
    Model
    (AR::Base)
    Job
    Table
    ファイルやディレクトリ単位で削除できるようにする
    影響範囲の考慮漏れのリスクが少ない
    => 削除のハードルを下げる

    View Slide

  30. 事例2
    ⼩売だけではなくメーカーにも実施結果を共有したい
    30

    View Slide

  31. ● 商品が店頭に思ったとおりに並んでいるかどうかはメーカーも⾒たい
    ● 各施策について閲覧したい情報は”ほとんど”同じ
    ● メーカー企業では実店舗に臨店して、商品の陳列状況をチェックしたり並べ⽅を修正
    する役割の⼈も存在する
    ○ => こういう⼈も救いたいよね!
    事例2. ⼩売だけではなくメーカーにも実施結果を共有したい
    31

    View Slide

  32. 32
    ● 企業テーブルに企業種別(⼩売企業 or メー
    カー)をつけて、画⾯上では種別ごとに情報
    を適宜出し分ける
    ● ⼩売本部ユーザー / メーカーユーザーでテー
    ブル、認証などのロジックは共有して省エネ
    事例2. 最短で実現したいなと思ったとき

    View Slide

  33. 33
    ● ⼩売と同じようなテーブルをメー
    カーのためにも作る
    ● 認証画⾯などもそれぞれ別で作る
    事例2. ⼩売は⼩売、メーカーはメーカーで作る

    View Slide

  34. ● メーカー企業を巻き込んだら、ステークホルダーが増えてスピードダウン
    ○ NDAを両者と結んで、ちょうどいい新商品が出たタイミングでお試し実施して効
    果実感してもらえると良いですね => 導⼊までのリードタイムが増加
    ● そもそも⼀番やりたかったことは、⼩売企業の実⾏⼒の向上だったよね
    34
    事例2. そして1年後にわかる、メーカー向け画⾯不要説

    View Slide

  35. ● 「“ほとんど”⾒るものは同じ」というのは、⽢すぎるヨミ
    ○ 実際に使ってみたら、⼩売だけ、メーカーだけに表⽰したいものが多々あった
    ● もし、カラムの企業種別で分けていたら...
    ○ View / Serializer などの表⽰部分に分岐が増える
    ○ v-if=”retailer.isRetailer(or Maker)”とかになるのはつらい...
    ■ メーカーを削除するとき、削除漏れが発⽣する可能性が⾼まる
    35
    事例2. ユーザーが違う = 表⽰部分に分岐が増える

    View Slide

  36. 36
    ● ⼩売と同じようなテーブルをメー
    カーのためにも作る
    ● 認証画⾯などもそれぞれ別で作る
    事例2. ⼩売は⼩売、メーカーはメーカーで作る

    View Slide

  37. 37
    ● メーカー関連のコードをディレクトリ / ファイルごと削除する
    ○ frontend/maker
    ○ app/controllers|serializers|jobs|views|mailers/maker
    ○ spec/requests|jobs/maker
    ○ app|spec/models|factories/maker.rb|maker_user.rb|scheme_m
    aker.rb|maker_user_authentication.rb
    ○ config/routes/maker.rb
    事例2. メーカーと名のつくものをほぼノールックで削除

    View Slide

  38. 38
    ● メーカー関連のコードをディレクトリ / ファイルごと削除する
    ○ frontend/maker
    ○ config/routes/maker.rb
    ○ app/controllers|serializers|jobs|views|mailers/maker
    ○ spec/requests|jobs/maker
    ○ app|spec/models|factories/maker.rb|maker_user.rb|scheme_m
    aker.rb|maker_user_authentication.rb
    事例2. メーカーと名のつくものをほぼノールックで削除
    表示部分も含めて冗長だなと思っていても
    別々に作っていれば、
    この単位で削除することができる。

    View Slide

  39. 39
    事例2. この単位で削除することができる

    View Slide

  40. 40
    ● そもそも混ぜるな危険なものを混ぜない
    ○ ⽬的ごちゃ混ぜのサマリテーブルを作ったり
    ○ retailers テーブルに 企業種別で ⼩売 or メーカーのカラムをもたせたり
    ● 短期的に記述量が少なく⾒えて速そうでも、削除まで考えるとむしろ遅い
    ○ 分けるべき概念を、コードの分岐を増やさずに根っこから分ける
    ○ 冗⻑に⾒えることもあるが分岐が増えそうであれば疑ってみる
    削除を前提に設計する

    View Slide

  41. 41
    ● そもそも混ぜるな危険なものを混ぜない
    ○ ⽬的ごちゃ混ぜのサマリテーブルを作ったり
    ○ retailers テーブルに 企業種別で ⼩売 or メーカーの情報をもたせたり
    ● 短期的に記述量が少なく⾒えて速そうでも、削除まで考えるとむしろ遅い
    ○ 分けるべき概念を、コードの分岐を増やさずに根っこから分ける
    ○ 冗⻑に⾒えることもあるが分岐が増えそうであれば疑ってみる
    削除を前提に設計する
    削除することを織り込んでおけば
    設計⼿法のエッセンスは取り⼊れられている。

    View Slide

  42. 42
    削除しすい設計はわかった。
    じゃあ後はこれを使ってやっていくだけですね!

    View Slide

  43. 43
    「え、せっかく作ったもの壊すんですか?」

    View Slide

  44. 44
    「別のお客さんが使うかもしれないから
    残しておいたほうがいいのでは?」

    View Slide

  45. ● たとえ機能や画⾯が使われておらず顧客に価値を提供できていないように⾒えても
    ⼯数をかけたモノを削除する意思決定をすることは難しい
    ○ 損失回避バイアス
    機能の削除を後押しするための⼯夫
    45

    View Slide

  46. ● 複雑なシステムをメンテナンスするコスト、機能追加時により多くのコストがかかる
    ことについて最も把握しているのは開発者
    ● 開発者が機能や画⾯の削除をしたいとただ主張するだけでは削除は進まない
    ○ not リファクタリング、内部品質の改善
    ● 意思決定を促す⼯夫が必要、開発者からアプローチできることがある
    機能の削除を後押しするための⼯夫
    46

    View Slide

  47. ● 機能のON/OFFを切り替える⼿法
    ○ A/Bテスト、限定的、段階的なリリースなどに使われることが多い
    ○ ハンクラでは企業ごとに機能のON/OFFをつけることができる
    ○ 特定の企業に出してみて反応を⾒ることができる
    ■ 影響を抑えられるため⽐較的気軽に機能を出し⼊れできる
    ● だめだったら削除する / 良さそうだったら積極的に全体に公開する
    という運⽤を前提とする
    Feature Flag
    47

    View Slide

  48. ● minispec
    ● 開発着前、詳細な設計の前に⽤意する
    「仕様案」のようなもの
    ○ 解決すべき課題、規模感を事前に把握
    ● KPIに活⽤度などを⼊れることによって、
    思った通りにいかなかったときにとるアク
    ションを決めておく
    ● 閲覧数‧利⽤頻度が思ったように増えなけ
    れば削除や⼿を⼊れることも検討
    開発着⼿前に minispec を利⽤して期待値を調整する
    48

    View Slide

  49. ● ログの集計‧閲覧を誰でもできるようにする
    ● ハンクラではRedashでログとDBを統合して各機能‧画⾯の利⽤状況を把握できるよう
    にしている
    ○ 思ったように利⽤されているか?
    ○ されていなければ、顧客にヒアリングしてみる
    ■ 実は機能や知らなかっただけかもしれない
    ■ 定量‧定性の情報をそれぞれ集められるようにする
    利⽤状況を誰でも把握できるようにする
    49

    View Slide

  50. 1 事業初期は試⾏錯誤の回数を増やすために、コードや機能は積極的に削除
    していきたい
    2 Rails においてコードを削除しやすくするには、
    テーブルごと情報を分けて、閲覧側に分岐を極⼒いれない
    3 捨てる意思決定の背中を押す雰囲気を作るのも開発者のしごと
    50
    まとめ

    View Slide

  51. 51
    Rails でスピード感を持って
    事業を前に進めましょう💪

    View Slide