Slide 1

Slide 1 text

開発効率向上のためのリファクタリングの ⼀歩⽬の選択肢 ~コード分割~ JJUG CCC 2024 Fall 1

Slide 2

Slide 2 text

2 • kintoneとは • コード分割とは何か • コード分割の前後におけるコードの違い • コード分割後、開発においてどのようなメリットが出たか • 今後の課題と今起きている動き • まとめ ⽬次

Slide 3

Slide 3 text

3 • kintoneとは • コード分割とは何か • コード分割の前後におけるコードの違い • コード分割後、開発においてどのようなメリットが出たか • 今後の課題と今起きている動き • まとめ ⽬次

Slide 4

Slide 4 text

4 kintoneとは サイボウズが世界中にチームワークを広めるために開発している 「業務改善プラットフォーム」 のクラウドサービス

Slide 5

Slide 5 text

5 kintoneとは ドラッグ&ドロップで簡単に作成できるアプリ機能 業務データを柔軟に蓄積し、社内で共有‧⾒える化 運⽤しながらの変更も容易

Slide 6

Slide 6 text

6 kintoneとは データやプロジェクトに紐付けたコミュニケーション機能 データの蓄積に加え、コミュニケーションも円滑化

Slide 7

Slide 7 text

7 kintoneとは 顧客の業務に合わせて拡張可能な ノーコード‧ローコード開発プラットフォーム 外部サービスとの連携などを可能にするAPIを開発‧提供

Slide 8

Slide 8 text

8 kintone開発チームの体制 機能領域ごとの担当チーム • ドメイン‧コード理解の促進のため分担 • エンジニアはいずれかの領域チームに所属 • エンジニア‧QAエンジニア‧スクラマスターで構成 アプリの作成/設定 アプリの利⽤ ナビゲーション・ コミュニケーション システム管理・ 外部サービス連携 拡張機能(カスタマイ ズ/プラグイン)基盤 レコード基盤 私はここ

Slide 9

Slide 9 text

9 2011年のリリースから10年以上が経過し、ソースコードの量も膨⼤に サブチームの担当領域間のコードの依存関係は複雑 これらの事実が開発の障壁になっていることを体感する場⾯は少なくない • コードの調査や影響範囲の確認が⼤変 • 実装時の考慮事項が増える • 新⼈メンバーがコードを把握するのに時間がかかる 機能開発における課題

Slide 10

Slide 10 text

10 アプリ利⽤機能 kintoneの機能例:アプリ利⽤機能 アプリを⾒る⼈

Slide 11

Slide 11 text

11 カテゴリー機能 kintoneの機能例:アプリ利⽤機能 カテゴリーが階層形式で表⽰される アクター アプリを⾒る⼈ ユースケース カテゴリー情報の取得

Slide 12

Slide 12 text

12 アプリ設定機能 kintoneの機能例:アプリ設定機能 アプリを設定する⼈

Slide 13

Slide 13 text

13 カテゴリー設定機能 kintoneの機能例:アプリ設定機能 アクター アプリを設定する⼈ ユースケース カテゴリー設定の 取得‧更新

Slide 14

Slide 14 text

14 複数のコンテキストを含みクラスが肥⼤化 アプリ設定機能 カテゴリー設定に関する コントローラー カテゴリー設定更新処理 アプリ利⽤機能 レコードに関する コントローラー カテゴリー情報取得処理 カテゴリーデータクラス カテゴリーに関するサービスクラス カテゴリー設定取得処理 アプリを⾒る⼈ アプリを設定する⼈ クラスが肥⼤化 カテゴリー情報を取得 カテゴリーの設定‧取得

Slide 15

Slide 15 text

15 影響範囲の調査に時間がかかる アプリ設定機能 カテゴリー設定に関する コントローラー カテゴリー設定更新処理 アプリ利⽤機能 レコードに関する コントローラー カテゴリー情報取得処理 カテゴリーデータクラス カテゴリーに関するサービスクラス カテゴリー設定取得処理 アプリを⾒る⼈ アプリを設定する⼈ カテゴリー情報を取得 カテゴリーの設定‧取得 ⼀⽅の変更が もう⼀⽅に影響を

Slide 16

Slide 16 text

16 新⼈メンバーにとって理解が難しいクラス アプリ設定機能 カテゴリー設定に関する コントローラー カテゴリー設定更新処理 アプリ利⽤機能 レコードに関する コントローラー カテゴリー情報取得処理 カテゴリーデータクラス カテゴリーに関するサービスクラス カテゴリー設定取得処理 アプリを⾒る⼈ アプリを設定する⼈ アプリの利⽤‧設定 両⽅の理解が必要 カテゴリー情報を取得 カテゴリーの設定‧取得

Slide 17

Slide 17 text

17 2011 年のリリースから 10 年以上が経過し、ソースコードの量も膨⼤に サブチームの担当領域間のコードの依存関係は複雑 これらの事実が開発の障壁になっていることを体感する場⾯は少なくない • コードの調査や影響範囲の確認が⼤変 • 実装時の考慮事項が増える • 新⼈メンバーがコードを把握するのに時間がかかる 機能開発における課題 開発効率低下に対する解決策の1つがコード分割

Slide 18

Slide 18 text

18 • kintoneとは • コード分割とは何か • コード分割の前後におけるコードの違い • コード分割後、開発においてどのようなメリットが出たか • 今後の課題と今起きている動き • まとめ ⽬次

Slide 19

Slide 19 text

19 コードの可読性の向上を⽬的として開始した取り組み コード分割ではコードを以下の状態にすることを⽬指す • kintoneの機能に沿ってコードをまとめる • それぞれの機能が関わらない場合は、対応するコードも関わらない (= 依存しない) ようにする サブチームの担当領域の機能に関するコードの塊を作成し、依存関係を整理 コードの可読性を向上させ、開発効率の向上を図る コード分割で開発効率の向上を図る

Slide 20

Slide 20 text

20 機能が関わらないコードは依存させない アプリ設定機能 カテゴリー設定に関する コントローラー カテゴリー設定更新処理 アプリ利⽤機能 レコードに関する コントローラー カテゴリー情報取得処理 カテゴリーデータクラス カテゴリーに関するサービスクラス カテゴリー設定取得処理 アプリを⾒る⼈ アプリを設定する⼈ ⼀つのクラスが 複数のユースケースを担う ↓ • 可読性の低下 • 影響範囲の拡⼤ • 検討事項のコスト拡⼤

Slide 21

Slide 21 text

21 機能が関わらないコードは依存させない カテゴリー設定に関する コントローラー カテゴリー設定更新処理 kintone.appsettings kintone.app レコードに関する コントローラー カテゴリー情報取得処理 カテゴリー情報 データクラス カテゴリーの利⽤に関する サービスクラス カテゴリー設定取得処理 カテゴリー設定情報 データクラス カテゴリーの設定に関する サービスクラス 関わらせない ❌ ❌ アプリ設定機能 アプリ利⽤機能

Slide 22

Slide 22 text

22 機能が関わらないコードは依存させない カテゴリー設定に関する コントローラー カテゴリー設定更新処理 kintone.appsettings kintone.app レコードに関する コントローラー カテゴリー情報取得処理 カテゴリー情報 データクラス カテゴリーの利⽤に関する サービスクラス カテゴリー設定取得処理 カテゴリー設定情報 データクラス カテゴリーの設定に関する サービスクラス 関わらせない ❌ ❌ アプリ設定機能 アプリ利⽤機能

Slide 23

Slide 23 text

23 • kintoneとは • サーバーサイドコード分割とは何か • コード分割の前後におけるコードの違い • コード分割後、開発においてどのようなメリットが出たか • 今後の課題と今起きている動き • まとめ ⽬次

Slide 24

Slide 24 text

24 コード分割前 クラス間の依存関係はルール化されておらず、各機能のコード同⼠が複雑に依存 →コードの変更による影響範囲の把握が⼤変、可読性の低下など 開発効率の低下に影響 コード分割前の状態

Slide 25

Slide 25 text

25 カテゴリー機能を例としたコード分割前の状態 カテゴリー情報を取得 カテゴリーの設定‧取得 CategoryService CategoryData RecordController CategoryController アプリを⾒る⼈ アプリを設定する⼈

Slide 26

Slide 26 text

26 コード分割後 アプリ設定⽤のパッケージ、機能毎のサブパッケージを作成 機能に対応するコードは、対応する機能のパッケージ内に存在している状態 クラス間の依存のルールを定義 • アプリ設定パッケージ外から内側のクラスを直接参照するのは禁⽌ • アプリ設定以外の領域からアプリの設定への参照が必要な箇所は 専⽤のインタフェースを作成 その他の領域はこのインタフェースを通じて必要な情報を取得する コード分割後の状態

Slide 27

Slide 27 text

27 カテゴリーに関する機能をアプリ設定⽤パッケージ内に配置 アプリを⾒る⼈ アプリを設定 する⼈ カテゴリー情報を取得 カテゴリーの設定‧取得 appsettings.category パッケージ アプリ設定⽤のパッケージを作成し、機能のサブパッケージを作成 機能に対応するコードは、対応する機能のパッケージ内に存在している状態 RecordController CategoryService CategoryData CategoryController カテゴリーに関するクラスは カテゴリー⽤パッケージに配置

Slide 28

Slide 28 text

28 依存関係のルールを違反するユースケース アプリを⾒る⼈ アプリを設定 する⼈ appsettings.category パッケージ RecordController CategoryService CategoryData CategoryController アプリを⾒る⼈の ユースケースが 依存関係のルールを違反 アプリの設定に関する ユースケースのため依存可能 カテゴリー情報を取得 カテゴリーの設定‧取得

Slide 29

Slide 29 text

29 アプリの設定パッケージ外からの参照を許可するインタフェースを作成 データクラスも分割 CategorySettingService CategorySettingData CategoryInfoService CategoryInfoData Impl DI を⽤いて 依存を注⼊ CategoryController appsettings.category パッケージ アプリを⾒る⼈ RecordController カテゴリー情報を取得 カテゴリーの設定‧取得 アプリを設定 する⼈

Slide 30

Slide 30 text

30 ArchUnitを⽤いて依存を禁⽌ CategorySettingService CategorySettingData CategoryInfoService CategoryInfoData Impl CategoryController appsettings.category パッケージ アプリを⾒る⼈ RecordController カテゴリー情報を取得 カテゴリーの設定‧取得 ArchUnitを⽤いて 依存を禁⽌ ❌ アプリを設定 する⼈

Slide 31

Slide 31 text

31 コード分割後の形 CategorySettingService CategorySettingData CategoryInfoService CategoryInfoData Impl CategoryController category アプリを⾒る⼈ RecordController appsettings パッケージ アプリを設定 する⼈

Slide 32

Slide 32 text

32 コード分割後の形 CategorySettingService CategorySettingData CategoryInfoService CategoryInfoData Impl CategoryController category アプリを⾒る⼈ RecordController appsettings パッケージ アプリ設定⽤パッケージ その下に機能毎のパッケージ を作成 機能に対応するコードはすべて 対応するパッケージ内に配置 アプリを設定 する⼈

Slide 33

Slide 33 text

33 コード分割後の形 CategorySettingService CategorySettingData CategoryInfoService CategoryInfoData Impl CategoryController category アプリを⾒る⼈ RecordController appsettings パッケージ インタフェース‧データクラスを 作成 アプリを設定 する⼈

Slide 34

Slide 34 text

34 • kintoneとは • サーバーサイドコード分割とは何か • コード分割の前後におけるコードの違い • コード分割後、開発においてどのようなメリットが出たか • 今後の課題と今起きている動き • まとめ ⽬次

Slide 35

Slide 35 text

35 コード分割後、開発においてどのようなメリットが出たか 他領域からの依存を整理した結果、普段の開発にて以下のメリットが⽣まれた • 影響範囲がわかりやすくなった • クラスの責務が明確になった • 素早く機能開発できるようになった

Slide 36

Slide 36 text

36 影響範囲がわかりやすくなった 分割前のコードでは、どんなクラスからでもアプリの設定に関するクラスへの依存が可能だった そのため、参照するクラスの数が多い上、適切でない参照もある → 影響範囲の把握や既存コードへの考慮など、機能実装に⾄るまでが⻑く、開発速度に影響 分割後は、他領域からの依存の⼊り⼝が限定されている • 影響範囲の把握が容易になり、他領域への考慮について考えやすくなった • 他領域から参照されたくないメソッドも安⼼して作成できるようになった

Slide 37

Slide 37 text

37 影響範囲がわかりやすくなった カテゴリー設定⽤REST API開発 カテゴリー情報を取得 カテゴリーの設定‧取得 appsettings.category CategoryInfoService CategoryInfoData Impl アプリを⾒る⼈ アプリを設定 する⼈ RecordController CategorySettingService CategorySettingData CategoryController CategorySettingAPI Service ⼿を⼊れるべき範囲が明確 影響がないことが明確

Slide 38

Slide 38 text

38 影響範囲がわかりやすくなった カテゴリー設定⽤REST API開発 appsettings.category CategoryInfoService CategoryInfoData Impl アプリを⾒る⼈ アプリを設定 する⼈ RecordController CategorySettingService CategorySettingData CategoryController CategorySettingAPI Service パッケージ外から意図してな い使⽤をされる⼼配がない カテゴリー情報を取得 カテゴリーの設定‧取得

Slide 39

Slide 39 text

39 影響範囲がわかりやすくなった appsettings.category CategoryInfoService CategoryInfoData Impl アプリを⾒る⼈ アプリを設定 する⼈ RecordController CategorySettingService CategorySettingData CategoryController 参照箇所を辿ることで 影響範囲の把握が可能 実装クラスに変更がある カテゴリー情報を取得 カテゴリーの設定‧取得

Slide 40

Slide 40 text

40 クラスの責務が明確になった 分割前 ⼀つのデータクラスやサービスクラスは様々なユースケースで使⽤されていた あるユースケースでは必要ない情報だが、別のユースケースでは必要、といった理由でクラスに 多くの属性や振る舞いが定義されていた → 可読性の低下や影響範囲の拡⼤ 分割後 ⼀つのクラスが担う責務が明確になり、不要な属性や振る舞いが定義されなくなった データクラスが不必要に使いまわされることがなくなった • 可読性の向上 • コード変更による影響範囲の縮⼩ • 実装時の考慮事項の軽減

Slide 41

Slide 41 text

41 クラスの責務が明確になった • 例: カテゴリー ⽚⽅のアクター起因の変更が、もう⼀つのアクターに影響 CategoryService CategoryData カテゴリー情報を取得 カテゴリーの設定‧取得 • CategoryData get() • CategoryData getIfActive() アプリを⾒る⼈ アプリを設定 する⼈ RecordController CategoryController

Slide 42

Slide 42 text

42 クラスの責務が明確になった • 例: カテゴリー CategoryService CategoryData カテゴリー情報を取得 カテゴリーの設定‧取得 • CategoryData get() • CategoryData getIfActive() アプリを⾒る⼈ アプリを設定 する⼈ RecordController CategoryController それぞれのメソッドに違う⽬的 → 認知負荷が⾼く クラスの可読性が低下

Slide 43

Slide 43 text

43 クラスの責務が明確になった 例: カテゴリー それぞれのクラスのアクターが分割され、クラス‧メソッドの責務が明確に → 各クラスのコードを読む際に考慮するユースケースが混ざらない CateogrySettingService CategorySettingData CateogryInfoService CateogryInfoData Impl • CategoryInfoData getIfActive() • CategorySettingData get() アプリを⾒る⼈ アプリを設定 する⼈ RecordController CategoryController appsettings.category カテゴリー設定に関する クラス‧メソッド

Slide 44

Slide 44 text

44 クラスの責務が明確になった 例: カテゴリー CateogrySettingService CategorySettingData CateogryInfoService CateogryInfoData Impl • CategoryInfoData getIfActive() • CategorySettingData get() アプリを⾒る⼈ アプリを設定 する⼈ RecordController CategoryController appsettings.category メソッドが移動 それぞれのgetメソッドの満たしたいユースケースが明確に クラスも⼩さく簡潔に → クラスの可読性が向上

Slide 45

Slide 45 text

45 クラスの責務が明確になった もしカテゴリー設定にメモ機能が追加されると 管理者⽤メモ 複数の設定者がいる場合 設定の意図を残すための 機能

Slide 46

Slide 46 text

46 クラスの責務が明確になった もしカテゴリー設定にメモ機能が追加されると CateogrySettingService CategorySettingData appsettings.category CateogryInfoService CateogryInfoData Impl • CategoryInfoData getIfActive() • CategorySettingData get() • List categories • String memo 新しいプロパティを追加 → パッケージの外に 影響がない CategoryController RecordController 実装時にカテゴリーの設定以外を 考えなくて良い アプリを⾒る⼈ アプリを設定 する⼈

Slide 47

Slide 47 text

47 クラスの責務が明確になった もしカテゴリー設定にメモ機能が追加されると CateogrySettingService CategorySettingData appsettings.category CateogryInfoService CateogryInfoData Impl • CategoryInfoData getIfActive() • CategorySettingData get() • List categories • String memo CategoryController RecordController アプリを⾒る⼈ アプリを設定 する⼈ メソッドを修正 → 影響範囲が抑えられる 機能追加‧修正による影響範囲を抑えられる クラスの肥⼤化も抑制

Slide 48

Slide 48 text

48 クラスの責務が明確になった もしカテゴリー設定にメモ機能が追加されると CateogrySettingService CategorySettingData appsettings.category CateogryInfoService CateogryInfoData Impl • CategoryInfoData getIfActive() • CategorySettingData get() • List categories • String memo CategoryController RecordController アプリを⾒る⼈ アプリを設定 する⼈ 修正箇所が素早くわかる 必要以上の調査が不要

Slide 49

Slide 49 text

49 素早く機能開発できるようになった コード分割によって、 • 影響範囲の把握が以前より容易になった • クラスの責務が明確になった • アプリ設定のパッケージとしてどのような振る舞いをするべきか、といった考え⽅で 開発が可能になった → 設計から実装に移るまでの時間が短くなった 機能がユーザーに提供されるまでのリードタイムが短縮

Slide 50

Slide 50 text

50 可読性の向上の他にも得られたメリット コード分割の⽬的は「コードの可読性の向上」であり、実際に可読性の向上を実感した 他チームからもインタフェースが公開されていて読みやすい、⾃分たちも取り組みたいと⾔う 声が上がった 可読性の向上という⽬的は達成したと⾔えそう 加えて、可読性の向上の他にも開発効率の改善につながる効果を得られた • 影響範囲の把握が容易 • 実装に⾄るまでのリードタイム短縮 開発効率向上のためのリファクタリングの⼀歩⽬として良かった

Slide 51

Slide 51 text

51 新⼈の機能とコード理解の促進 kintone開発チームに配属された直後からこのコード分割の取り組みに参加 そのため、kintoneの機能もコードも理解がほぼゼロだった しかし、コードを適切に分割するには分割対象の機能やコードを理解する必要がある • 機能を構成するためにどんなクラスが存在しているか • DB にどのような形のデータが永続化されているのか • クライアントからどのようなデータがリクエストで送られているのか などなど

Slide 52

Slide 52 text

52 新⼈の機能とコード理解の促進 アプリ設定領域が他の領域に対してどのような価値を提供しているのかを理解する必要がある 理解が浅いと、公開する必要のない情報を公開してしまい、適切な分割ができない 不要な情報を 公開する可能性 CateogrySettingService CategorySettingData CateogryInfoService CateogryInfoData Impl CategoryController アプリを⾒る⼈ アプリを設定 する⼈ RecordController appsettings.category

Slide 53

Slide 53 text

53 新⼈の機能とコード理解の促進 • 分割対象の機能‧コードの理解 • 分割対象を参照している他領域の機能‧コードの理解 これらの必要な理解を埋めるために、コードや仕様書を読む必要がある → コード分割を通して機能とコードの理解が深まった 新⼈メンバーの機能とコード理解促進のオンボーディングタスクとしても良かった

Slide 54

Slide 54 text

54 モブプログラミングでの作業も助けに コード分割の作業はモブプログラミングで⾏った 既存コードは理解が難しい状態 • 既存コードの理解に必要以上に時間をかけなくて済む • 配属されたチーム以外の領域の機能について教えてもらえる • 予想外の依存の理由をサクッと説明してもらえる 経験豊富な⽅とのモブプログラミングで、同じ認識を持った上で質問でき効率よく理解を深められた

Slide 55

Slide 55 text

55 • kintoneとは • コード分割とは何か • コード分割の前後におけるコードの違い • コード分割後、開発においてどのようなメリットが出たか • 今後の課題と今起きている動き • まとめ ⽬次

Slide 56

Slide 56 text

56 今後の課題 • 今のコードが最適ではない • 現在のテストでは、確実に他領域を壊していないと判断できない

Slide 57

Slide 57 text

57 機能に沿ってコードを分割し、依存関係がルール化され、違反している箇所がない状態 しかし分割を優先して、既存メソッドの整理等は後回しで進めたため、最適な形ではない 例:アプリ設定パッケージが公開しているインタフェースに同じ型のオブジェクトを返す get 系メソッドが複数存在 コードを分割し、依存関係を整理することで可読性が向上 → まだ改善すべき箇所が残っている 今のコードが最適ではない appsettings.category FieldAccessControlSettingService Impl • List getFieldRights • List getAccessRights AccessControlService AppTemplateService どちらを使うべきか わかりづらい

Slide 58

Slide 58 text

58 アプリの設定の機能変更による変更差分はパッケージ内に抑えられるようになった ただ、内部実装ではアプリ設定パッケージ外への振る舞いが内側に依存している箇所がある 現在のテストでは、確実に他領域を壊していないと判断できない 変更差分がアプリ設定パッケージの内側のクラスに閉じていても 本当にパッケージ外に影響がないと⾔い切れない アプリを設定 する⼈ CateogrySettingService CategorySettingData CateogryInfoService CateogryInfoData Impl CategoryController appsettings.category アプリを⾒る⼈ RecordController パッケージ内の クラスに依存

Slide 59

Slide 59 text

59 現在のテストでは、確実に他領域を壊していないと判断できない アプリを設定 する⼈ CateogrySettingService CategorySettingData CateogryInfoService CateogryInfoData Impl CategoryController appsettings.category アプリを⾒る⼈ RecordController 全体に対するテスト アプリ設定領域テスト 変更差分 パッケージの外に 影響が及ぶ可能性

Slide 60

Slide 60 text

60 現在のテストでは、確実に他領域を壊していないと判断できない アプリを設定 する⼈ CateogrySettingService CategorySettingData CateogryInfoService CateogryInfoData Impl CategoryController appsettings.category アプリを⾒る⼈ RecordController 全体に対するテスト アプリ設定領域テスト このクラスの振る舞いが壊れてない ことを検査すれば⼗分 パッケージ外への振る舞い

Slide 61

Slide 61 text

61 現在のテストでは、確実に他領域を壊していないと判断できない アプリを設定 する⼈ CateogrySettingService CategorySettingData CateogryInfoService CateogryInfoData Impl CategoryController appsettings.category アプリを⾒る⼈ RecordController 全体に対するテスト アプリ設定領域テスト メソッドのリファクタ テストの拡充に取り組み中 パッケージ外への振る舞い

Slide 62

Slide 62 text

62 影響範囲の部分に絞ったテストを実⾏することが可能 アプリを設定 する⼈ CateogrySettingService CategorySettingData CateogryInfoService CateogryInfoData Impl CategoryController appsettings.category アプリを⾒る⼈ RecordController 全体に対するテスト アプリ設定領域テスト 新しく拡充するテスト 変更差分が閉じている場合 影響範囲の部分に絞ったテストを実⾏することでテストの実⾏時間短縮を実現できそう

Slide 63

Slide 63 text

63 現在、機能修正を本体コードに取り込む前にプロダクト全体に対するE2Eテストを実⾏ かなりの量があり、⼀回の実⾏に⼀時間ほどかかる もし、テストが失敗していたら実装を修正し、再度実⾏… というサイクルを回している → E2Eテストが成功することを確認するまでに時間がかかっている 別の取り組みでアプリ設定領域のテストが整理されている状態 E2Eテストの部分実⾏によるテスト時間短縮 https://blog.cybozu.io/entry/2024/08/13/110000 テストの実⾏時間が⻑い 別の課題の解決のきっかけに

Slide 64

Slide 64 text

64 • コードの可読性の向上を⽬的としてアプリの設定の機能に関するコードの分割に取り組んだ • 分割した結果、コードの可読性の向上を実感 • その他にも開発効率の向上につながるメリットが⽣まれた • コード分割のおかげで取り組むことができるさらなる改善案も⽣まれた まとめ 開発効率向上のためのリファクタリングの⼀歩⽬として良かった

Slide 65

Slide 65 text

© Cybozu, Inc. 65