新規プロダクト開発記 〜どうしたら業務知識をコードに落とし込めるのか〜

はじめに

はじめましてこんにちは。2021 年 11 月にコミューンに入社した中野です。現在は SuccessHub というコミューンの新たなプロダクトを開発しています。

この記事では新規プロダクトの開発を通して、筆者が「どうしたら業務をコードに落とし込めるのか」実践したことをお話しします。

背景

SuccessHub の開発を始めて数ヶ月経った頃、カスタムフィールドという機能を作ることになりました。 カスタムフィールドとは簡単に言うと、クライアントが保持している顧客データを自由なフォーマットやカラム名にカスタマイズしてテーブルUIに表現できる機能です。

[カスタムフィールド] 日付をYYYY年MM月 や YYYY年MM月DD日 で表示したり、ヘルススコアを閾値設定して表示するなどデータごとにさまざまな表現をカスタマイズできる

クライアントはここからヘルススコアなどの顧客の状態を確認して、そのまま Slack やメール送信などのアクションに移ることができます。

以降、カスタムフィールドという機能を実装するにあたって、業務の知識をどうやってコードに反映させていったかについて紹介していきます。

まずは書いてみた

最初に、筆者が業務の知識を正しく把握せずに書いた、初期のコードを紹介します。

ひとまず必要そうなものを揃えて、CustomField というクラスを作りました。

export class CustomField {
  constructor(
    private readonly id: CustomFieldId,
    private readonly name: CustomFieldName,
    private readonly setting: CustomFieldSetting
  ) {}
  ...
}

また、カスタムフィールドの設定(CustomFieldSetting)と顧客データを突き合わせたいので、 以下のようなクラスを作って CustomFieldCustomer という二つの集合を一つのクラスに内包してみました。

export class CustomFieldTable {
  constructor(
    private readonly customField: CustomField,
    private readonly customer: Customer
  ) {}

  // データと設定を突き合わせて新しいリストを生成する関数
  createCustomFieldList = (): CustomFieldList => ...
}

クラス図

しかし、これらのモデルにいざ振る舞いを持たせようとするとうまく実装できませんでした。 以下のような問題が原因として挙げられます。

  1. CustomField の他に CustomFieldTableCustomFieldList など似た名前の概念が出てきて、それぞれどのような振る舞いを持つべきか考えられない
  2. CustomFieldTable の下に CustomField があるような階層関係が、パッとみて理解しにくい
  3. そもそも CustomFieldCustomer をまとめただけの CustomFieldTable クラスに「データと設定を突き合わせる」ような振る舞いをさせることに違和感がある

これらの問題を考えた時に、そもそも「カスタムフィールド」って何を指しているんだっけ? ということすら曖昧であることに気が付き、業務知識を一から捉え直してみることにしました。

業務知識をヒアリングする

まず、カスタムフィールド自体が何を表現しているかが曖昧だったので、プロダクトマネージャーやデザイナーにヒアリングをしました。 その上で、一つ一つの要素について曖昧な定義の確認を行います。

Q: カスタムフィールドとは何か?

A: カスタマイズ可能なテーブルUI全体の機能を指している。

Q: テーブルのカラムとは何か?

A: テーブルUIのヘッダ部分。カラム名や並び順を自由に設定できる。

Q: テーブルのデータとは何か?

A: テーブルUIのボディ部分。データの表現は自由に設定できる。

Q: データの形式や表現方法にはどのようなパターンがあるか?

A: 文字列や数値、アイコン画像付きの文字列などがあり、データの形式によって設定可能なパターンが異なる。

次にヒアリングした内容をもとに、モデルを考えていきます。

核となるモデルを作る

最初に中心となるモデルを考えます。

Q: カスタムフィールドとは何か?
A: カスタマイズ可能なテーブルUI全体の機能を指している。

であることから CustomField という核となるモデルを作ります。 さらに、CustomFiled の中に ColumnTableData という2つの要素を見出します。

CustomField テーブル全体
┗ Column テーブルのヘッダ部分
┗ TableData: データの集合であり、テーブルのボディ部分

モデル同士の関係や階層を確認する

次に、核となるモデルの配下にあるモデルを考えます。

Q: テーブルのカラムとは何か?
A: テーブルUIのヘッダ部分。カラム名や並び順を自由に設定できる。

であることから、「カラム」と呼んでいるものは実際には複数のカラムの集合(Columns)のことであり、 カラム一つ一つが、名前や自身の並び順を持っていることがわかります。

CustomField: テーブル全体
  ┗ Columns: Columnの集合であり、テーブルのヘッダ部分
    ┗ Column: 単一のカラム
      ┗ Name: カラムの名称
      ┗ Order: カラムの並び順

またTableData は複数のContents(テーブル内の一行)を持っていて Contentsは複数の Content(単一のセル)を内包しています。

それらを踏まえて階層を表現すると以下のようになります。

CustomField: テーブル全体
  ┗ Columns: Columnの集合であり、テーブルのヘッダ部分
    ┗ Column: 単一のカラム
      ┗ Name: カラムの名称
      ┗ Order: カラムの並び順
  ┗ TableData: Contentsの集合であり、テーブルのボディ部分
    ┗ Contents: Contentの集合であり、テーブル内の一行
      ┗ Content: 単一のセル

コードで表現する

核となるモデルや階層関係が捉えられたら、実際にコードを書き始めます。

以下、作成したモデルを簡略化して書いておきます(言語はTypeScript)

export class CustomField {
  constructor(
    private readonly columns: CustomFieldColumns,
    private readonly tableData: CustomFieldTableData,
  ) {}
  ...
}
...
export class CustomFieldColumns {
  constructor(
    private readonly list: CustomFieldColumn[],
  ) {}
  ...
}
export class CustomFieldColumn {
  constructor(
    private readonly name: CustomFieldName,
    private readonly order: CustomFieldOrder
  ) {}
  ...
}
export class CustomFieldTableData {
  constructor(
    private readonly contents: CustomFieldContents
  ) {}
  ...
}
export class CustomFieldContents {
  constructor(
    private readonly list: CustomFieldContent[]
  ) {}
  ...
}

また、

Q: データの形式や表現方法にはどのようなパターンがあるか?
A: 文字列や数値、アイコン画像付きの文字列などがあり、データの形式によって設定可能なパターンが異なる。

これらのパターン分けをコードで表現するために「データ」と「設定」から一つの Content を生成するFactoryクラスを作ることにしました。

export class CustomFieldContentFactory {
  ...
  factory = (
    customer: Customer,
    setting: CustomFieldSetting
    ): CustomFieldContent => {
    if (setting.text)
      return new CustomFieldTextContent(customer)
    if (setting.textWithIcon)
      return new CustomFieldTextWithIconContent(customer)
    if (setting.number)
      return new CustomFieldNumberContent(customer)
    ...
  }
  ...
}

なぜ業務知識を理解することが必要なのか

モデル設計に慣れているエンジニアであれば、このくらいちょっと仕様を確認すればすぐにコードで表現できる(あるいはもっと良いモデリングができる)かもしれません。

しかし、いきなりコードを書き始めて失敗してしまうことが多かった筆者は、業務に一番詳しい人にヒアリングをしてモデルを一つ一つ捉えていきました。 結果的に、業務について理解することでモデルの命名が直感的になり、モデル同士の対称性や階層を考えやすくなりました。

また、実装都合の設計にならないために、できるだけエンジニアの言葉ではなく業務における言葉を通じて会話をすることで

  • 実装する機能(業務)にはどんな要素やイベントが含まれているのか
  • それらはどのように関係しあっていてどのような階層を持つのか
  • 業務としての用語はプログラム上ではどのような名称で表現されるべきか

をよりシンプルに考えられるよう努めました。 そのようにして作られたモデルは、業務を知らない人が見てもなんとなく理解できるものになっているはずです。

終わりに

業務は作るプロダクトにおいて多種多様なので、「このパターンを覚えておけばいい」と言うような正解はなく、 用件に対して正しく物事を捉える力を鍛えていくことが大切だと思います。

また、開発を進めていくうちにチームメンバーの業務への理解が深まったり、 業務そのものが確立されていって定義がはっきりしていくこともあるので(特に立ち上げ時期のプロダクトの場合) 日々業務の知識をとらえ直すことを辞めずに、少しづつコードを改善していく必要があると感じています。

コミューンではエンジニアを募集中です!少しでも興味のある方は気軽にカジュアル面談に申し込んでみてください!

commmune-careers.studio.site