スタメンの技術的負債解消戦略

1. これはなに

こんにちは、リファクタリング大好きなミノ駆動です。2023年7月より株式会社スタメンにジョインしました。

この記事は、今後スタメンにおいてサービスの技術的負債を解消する設計戦略についてまとめたものです。

2. 背景、課題

株式会社スタメンは2016年創業。主要サービスであるTUNAG(ツナグ)は、企業のエンゲージメントの構築、つまりお互いを知って理解し、信頼し合う組織を作るための社内コミュニケーションを活性化させるプロダクトです。TUNAGのバックエンドはRuby on Railsで開発され、ローンチから7年をむかえつつあります。

これまでTUNAGは、プロダクトをいかに伸ばすかに注力してきた一方、内部品質や開発効率など「開発者体験」に関する課題が後手に回っていました。本来プロダクトチームはユーザーにとっての本質的な価値にのみフォーカスできる状況が理想ですし、開発者体験が悪いと良いユーザー体験を提供することができなくなっていきます。

開発者体験を悪化させる大きな要因のひとつが技術的負債です。具体的には変更容易性の低下です。変更容易性とは、なるべくバグを埋め込まず、素早く正確にコード変更できる度合いです。変更容易性が低いと開発生産性が低下してしまいます。

3. 設計戦略解説

サービスの変更容易性を向上し、開発生産性の継続的向上を果たすため、以下の図に示す設計推進活動を私主導のもとこれから実施していきます。

%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'background': '#000000'
    }
  }
}%%
graph TD
  A[開発生産性の<br>継続的向上] --- B[変更容易性の<br>継続的向上]
  B --- リファクタリング
  B --- C[変更容易性<br>設計スキルの向上]
  リファクタリング --- D[費用対効果の<br>高い箇所]
  D --- コアドメイン
  D --- H[負債レベルの高い箇所]
  コアドメイン --- 中長期事業計画
  コアドメイン --- E[有識者への<br>インタビュー]
  コアドメイン --- コンテキストマップ作成
  H --- K[ツールによる<br>負債計測]
  H --- G[変更失敗率の<br>高い箇所]
  H --- I[変更の苦しい箇所]
  コンテキストマップ作成 --- F[ドメインエキスパートへの<br>インタビュー]
  コンテキストマップ作成 --- ソースコード解析
  F --- 目的
  F --- 登場概念
  F --- アクター
  F --- プロセスサイクル
  C --- 設計ガイドライン
  C --- ハンズオン勉強会
  設計ガイドライン --- データモデル
  設計ガイドライン --- アーキテクチャ

かなり多岐にわたるため説明が長くなりますが、ぜひ最後までお読みいただければと思います。

変更容易性の向上には大きく2軸の活動があります。既存の技術的負債を解消するリファクタリングと、今後の新たな負債を抑止するための変更容易性設計スキルの向上活動があります。

3.1 リファクタリング

リファクタリングとは、外部から見た挙動を変えずに、プログラム構造を整理することです。変更容易性の高い構造に整理することがリファクタリングのゴールです。そのため、変更容易性の高いあるべき構造を設計したり、設計のためにドメインを分析したりと、多岐にわたるさまざまな活動が必要です。

3.1.1 費用対効果の高い箇所の特定

リファクタリングはただ実施すればいいというわけではありません。例えば粗悪なコードであっても、今後ほとんど仕様変更の見込みがない箇所を一生懸命リファクタリングして意味があるものでしょうか?リファクタリングは将来の変更コストを低減する活動です。従って、将来あまり変更されない箇所をリファクタリングしてもコストがかかるだけで役に立ちません。開発リソースは有限なので、限られたリソースの中でリファクタリングの費用対効果の高い箇所を狙う必要があります。

費用対効果の高い箇所の特定には、主に以下2つを複合的に考慮して判断します。

  • コアドメイン
  • 負債レベルの高い箇所

3.1.1.1 コアドメイン

どんな商品やサービスにも、「これがウリだ!」と呼べるような中心的価値があります。中心的価値を発揮する事業領域を、ドメイン駆動設計ではコアドメインと呼びます。コアドメインは差別化が図られ、企業の競争優位性を発揮する領域です。サブ的な領域よりも投資優先度が高いです。当然設計コストも同様です。競争優位性をより高めるために技術的負債を解消し、開発生産性を高めることが肝要です。

ただし、サービスが大きくなると何が中心的価値なのかだんだん分からなくなっていきます。また、時代によって人々の関心事は移り変わっていきます。特にコロナ禍前後でドラスティックに世の中の価値観が変化したのは皆さんの記憶に新しいところだと思います。何が中心的価値なのかを見定める必要があります。そのためには以下の調査や分析が必要になります。

  • 中長期事業計画
  • 有識者へのインタビュー
  • コンテキストマップ作成
3.1.1.1.1 中長期事業計画

中長期事業計画とは、中長期的な経営ビジョンを実現するために事業としてやるべきことを計画したものです。中長期的にどのように事業価値を伸長していくのか、進むべき方向性が定義されます。そしてソフトウェアの変更容易性は、中長期にわたって開発生産性に影響を及ぼします。コアドメインを見定めるため、また変更容易性設計の投資価値を見定めるためにも中長期事業計画のinputは重要です。

3.1.1.1.2 有識者へのインタビュー

中長期事業計画だけでなく、有識者から生の声を聞くことも大事です。プロダクトマネージャーの他、経営レベルに近いメンバーに対し、コアドメインに相当する事業領域が何であるのかインタビューします。

3.1.1.1.3 コンテキストマップ作成

ソフトウェアサービスには、中心的存在となるモデルが必ずといっていいほど登場します。例えばECサイトにおける商品モデルです。このようなモデルはさまざまな状況、さまざまなユースケースで用いられます。しかし、全ての状況に対応可能な、一枚岩の万能モデルとして作り上げようとすると、ロジックが混乱する、変更が困難になるなど多くの弊害を招きます(以下は前職所属時のイベント登壇動画と資料)。

www.youtube.com

speakerdeck.com

そこで 万能モデルとは異なる設計アプローチ が必要です。背景、状況、目的が大きく異なる事業ドメインの単位でサービスをサブシステムに分解し、各サブシステムに 特化したモデル として設計することの重要性をドメイン駆動設計では説いています。状況別の各特化型モデルそれぞれが適用可能な範囲を「境界づけられたコンテキスト」と呼びます。

このようにしてコンテキストごとに分解したサブシステムの内、コアドメインに対応するサブシステムを特定し、コアにコア以外のロジックが浸潤しないよう明確に区分付けることの重要性をドメイン駆動設計では説いています。ここまでやって初めてリファクタリングの「選択と集中」を実施可能な、費用対効果の高い箇所の特定が可能になります。

コンテキスト境界がどこにあるのかはさまざまな観点での分析が必要です。例えば以下です。

  • ドメイン分析
  • ソースコード解析
3.1.1.1.3.1 ドメイン分析

コンテキストごとにどんな違いがあるのか観点はさまざまですが、例えば以下のような点で違いがあると私は考えます。

  • 目的
  • アクター
  • 登場概念
  • プロセスサイクル

コンテキストの違い

この図はECサイトを例にしたコンテキストの差異を説明した図です。

まず、大きな粒度で目的が異なります。コンテキストをまたぐような巨大な一枚岩モデルは、多目的に使われてしまうので単一責任原則違反と考えることができます。コンテキスト特化型モデルの設計は、単一目的に対応できるように設計することです。つまり、目的が大きく異なる境界がコンテキスト境界と考えることができます。

また、目的が異なると、アクターや登場概念が大幅に違ってきます。例えば、

  • 在庫管理
    • アクター : 出品者
    • 登場概念 : 入庫、出庫、安全在庫量
  • 配送
    • アクター : 配送業者
    • 登場概念 : 配送元、配送先、配送状況、配送料

このように周辺概念を整理することもコンテキスト境界の発見につながります。

また、コンテキストごとにプロセスサイクルが異なります。在庫管理では出品者が入出庫のサイクルを回します。一方で配送では配送業者が梱包し、配送先まで配送するサイクルを回します。お互いのサイクルは交わることがありません。

このような違いを探るため、ドメインエキスパートへのインタビューやイベントストーミングなどでドメイン分析します。

3.1.1.1.3.2 ソースコード解析

コンテキストの違いは当然ソースコードにも現れます。

これはある程度目処をつけて調査することが可能です。例えば、以下のようなモデルは複数のコンテキストにまたがっていることがとても多いです。

  • 巨大なモデル
  • サービスのワークフローの始めから終わりまでいるようなモデル

このようなモデルは、状態によって以下が大きく異なる特徴があります。

  • 更新対象のデータ
  • 振る舞い

これらの差異がコンテキスト境界の手がかりとなります。

3.1.1.2 負債レベルの高い箇所

リファクタリング優先度の高いのはコアドメインのロジックですが、その中でも負債レベルの高い箇所がより優先度が高まります。負債レベルの高い箇所の割り出しには、例えば以下を実施します。

  • ツールによる負債計測
  • 変更失敗率の高い箇所の特定
  • 変更の苦しい箇所の特定
3.1.1.2.1 ツールによる負債計測

技術的負債の分析はツールにより計測可能です。弊社ではCode Climate Qualityを利用しております。Code Climate QualityはGitHubと連携し、負債やファイルの更新頻度を自動でスコアリングしてくれます。技術的負債と変更容易性は未来の変更コストに影響しますから、負債レベルと更新頻度の双方が高いものほどリファクタリングの効果が高いと考えられます。

3.1.1.2.2 変更失敗率の高い箇所の特定

本番デプロイ後に修復を要するコードの割合を変更失敗率といいます。変更失敗率を計測し、失敗に関与したソースコードを割り出します。

3.1.1.2.3 変更の苦しい箇所の特定

エンジニアにとって変更の苦しい箇所を特定します。どのあたりのコードが厄介なのか、いつも変更に苦慮しているのかをアンケートなどで聞き出します。

3.2 変更容易性設計スキルの向上

負債の増加を抑止するには、エンジニアの設計スキル向上が大事です。 ガイドラインなどドキュメントを揃えたり、勉強会を開催するなどテコ入れを要します。

3.2.1 設計ガイドライン

設計にはゴールが必要です。ゴール方針となる設計ガイドラインを策定します。ゴールを据えることで、そのギャップとして技術的負債を認知できるようになります。

少なくとも以下2点のガイドラインをまず用意します。

  • データモデル設計
  • アーキテクチャ設計

3.2.1.1 データモデル設計

DBのテーブル構造が負債化しないよう、データモデルの設計方法を取りまとめておく必要があります。Railsの場合、テーブルと1:1になるActiveRecordがさまざまなレイヤと密結合になりやすく、テーブル構造が全体にダイレクトに影響を及ぼすため、特に設計に注意を払う必要があります。

設計にはTM(T字形ER手法)を用います。

www.slideshare.net

3.2.1.2 アーキテクチャ設計

アプリケーションアーキテクチャ全体の再設計も必要です。

RailsはMVCフレームワークであり、普通はRails-wayと呼ばれるお作法に則って開発が進められます。しかしRailsは、スタートアップ時の加速力を得るため結合度を犠牲する「犠牲的アーキテクチャ」と呼ばれています。そのため開発が進み数年もするとモデルが複数の意味を持ち始めたり、最新のドメイン理解に対してテーブル構造が陳腐化して乖離が激しくなったりします。Rails-wayでは負債の解消や抑止が困難になっていきます。

この解決のため、モジュラーモノリス化することを目標に、以下のようなDDDベースのアーキテクチャへの移行を検討しています。このアーキテクチャの意図や方針、各責務の役割や設計方法をエンジニアに説明するため、アーキテクチャ設計のガイドラインを策定します。

%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'background': '#000000',
      'primaryTextColor': '#6360DC'
    }
  }
}%%

classDiagram

namespace Views {
  class View
}

namespace Models {
  class ActiveRecord
}

namespace Controllers {
  class Controller
}

namespace UseCases {
  class UseCase
  class QueryService {
    <<interface>>
  }
  class Dto
}

namespace Infras {
  class RepositoryImpl
  class QueryServiceImpl
}

namespace Domains {
  class Repository {
    <<interface>>
  }
  class Aggregate
  class Entity
  class ValueObject
}

ActiveRecord <.. RepositoryImpl
ActiveRecord <.. QueryServiceImpl
View ..> Controller
Controller ..> UseCase
Controller ..> QueryService
UseCase ..> Aggregate
UseCase ..> Repository
Repository ..> Aggregate
Aggregate --> Entity
Aggregate --> ValueObject
Entity --> ValueObject
RepositoryImpl ..|> Repository
QueryServiceImpl ..|> QueryService
QueryService ..> Dto
  • (※1:全体に対して適用するのではなく、コアドメインなど負債に対して特に注意を払う必要がある箇所についてDDDベースに移行します。負債が気にならない箇所、設計コストかける旨味があまりない箇所はRails-wayでいきます。)
  • (※2:Rubyではinterfaceは心の中にしかないので、上図におけるRepositoryやQueryServiceのinterfaceは実際には存在しません。状況によってはマーカーinterfaceの代用としてmoduleを実装する場合があります。)

3.2.2 ハンズオン勉強会

設計スキルの大幅な向上には、なんといっても実際に手を動かすハンズオン勉強会が欠かせません。

プログラミングはただプログラミング入門書を読んだだけでは、実務に使えるプログラミングスキルは身につきません。仕様を満たすロジック構造を考えて実装する経験が必要です。設計スキルも同様で本を読めば身につくものではなく、泥臭く混乱したロジックをどのように整理すれば変更が容易な構造になるか、自分の頭で考えて設計する経験が必要です。

勉強会ではプロダクションコードを使ってリファクタリングの練習をします。泥臭いロジックほど設計の旨味があるためです。例えば拙著『良いコード/悪いコードで学ぶ設計入門』 gihyo.jp

に記載の設計パターンを学ぶ場合、そのパターンを適用できそうなロジックをプロダクションコードから探します。普段エンジニアが触り慣れている箇所が望ましいです。該当するロジックが見つかったら、挙動が同じで変更容易性の高い構造を設計し、コードを実装します。それぞれの成果をみんなの前で発表し、どんな工夫をしたかなどについて議論や質疑応答し、フィードバックを得ます。

私は数社でこの勉強方法を実施した結果、どこでも設計スキルの向上を果たせました。効果に再現性があると自負します。オススメです。

4. おわりに

以上説明した戦略のもと、変更容易性を向上し開発生産性を継続的に高めるべく邁進していきます。

弊社スタメンはTUNAGを中心に右肩上がりの成長を続けています。この成長をより高めるため、私に課せられた責務は非常に重要なものになっております。

2023年12月期 第1四半期決算説明資料 より

この勢いで弊社スタメンはさらなる事業拡大を目指し、採用活動をより活性化していきます。現在従業員数は約80名。今の規模の2倍、3倍、5倍とスケールさせていきます。

エンジニア絶賛募集中です。私と一緒に働いてみたい、興味がある方は、ぜひ下記にアクセスしてみてください。

herp.careers