Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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 からそ れぞれの利⽤箇所で必要なデータを取り出して使う

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

事例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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

事例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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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. メーカーと名のつくものをほぼノールックで削除

Slide 38

Slide 38 text

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. メーカーと名のつくものをほぼノールックで削除 表示部分も含めて冗長だなと思っていても 別々に作っていれば、 この単位で削除することができる。

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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