トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

リファクタリングの道標:リファクタリングを通じて組織を改善していく

こんにちは。トレタ技術顧問の奥野 (@okunokentaro) です。先日のブログ記事『奥野さんと社員のリファクタリング部屋 -リポジトリ層のディレクトリをどう作る?-』に対して多くの反響をいただき、ありがとうございます。いただいたご意見やご質問の一つひとつが非常に貴重であり、大変感謝しています。

いくつか個別に質問をいただくことがあったのですが、それらの質問を要約すると「リファクタリング後のコードもまだツッコミどころだらけでは?」や「リポジトリ層という名前がまずおかしいのでは?」という二つの点に集約されました。これらの質問はとても理解できます。今回の記事では、具体的な個別の質問への個別の回答は控えさせていただきますが、全体的な方針とテーマについてお伝えした上で、トレタが取り組んでいるリファクタリングは何を目指しているのかというお話をします。

前提と目的

今回のブログ記事『リファクタリング部屋』で取り上げているコードは、実運用中のアプリケーションの実際のコードで、すでに顧客が存在するプロダクトです。このアプリケーションは複数人で開発されましたが、現在は抜けたメンバーもいるため、開発当時の状況が分かりづらい箇所もあります。

また、本アプリケーションの開発中には筆者は別プロダクトのリードエンジニアを務めていたため、本プロジェクトへの参入からまだ日が浅く、山積している課題を目の当たりにしている状況です。すでに運用中であるため、すべての変更は何よりもエンバグを防ぐことが最優先とされ、ただでさえ自動テストが少ない状況で、何をリファクタリングするにしても慎重な検証が求められます。

このような状況が続くと、リファクタリングのひとつひとつにも労力が掛かり、難読なコードは新しく参加するスタッフに対しても良い印象を与えません。機能追加が進まないだけでなく、現状のバグ修正すら困難となったまま新しいスタッフを迎え入れるのは非常に忍びないです。

そのため、少しでも現存のスタッフ、そして今後参加する未知のスタッフのために環境を整えていくことが、今回のリファクタリングの目的です。

積み重ねを重視する

今回のリファクタリングは、増改築のための基礎工事というよりは、ひたすら庭の雑草むしりに近いものです。限られた時間の中で、テストも少なく大胆なリファクタリングを実施したときのエンバグのリスクが大きいことも考慮すると、なるべく確実と思われる手法で、なおかつ短工数で大きな効果を生みたいところです。

TypeScriptでの開発を進めていることから、幸い変数名や関数名の修正、ディレクトリの移動やファイル名の修正などは低リスクで実施できます。短すぎる、あるいは長すぎる名前が多く、主旨が判然としない処理も、名前を見直すだけで見通しがよくなることがあります。

また、テストがあまり作成されていないためテストを増やしていきたい気持ちも強いのですが、依存関係の管理がなされていないことから、モックテストを気軽に追加することすら困難です。

このような手に負えない状況から、低リスク短工数で実施できて効果の大きいリファクタリングを考えたときに、残されたスタッフが読み解けないコードを少しでも読解できるようにする必要があると判断しました。どんな改善も読解と観察から始まりますが、まずその読解が困難であるためです。

よって、TypeScriptのコンパイルエラーが出ないように改名や切り出しを繰り返し、ブラウザ上で動作検証を行うという、地味な流れではありますが長期戦を覚悟した確実な積み重ねを重視します。

なぜリファクタリング後のコードもツッコミどころだらけなのか?

リファクタリングをテーマとした記事を掲載した上でこのような質問をいただくのは申し訳ないところではありますが、いくつか背景を説明したいと思います。

先の節でもお話しした通り、このシリーズでは地味な改善を積み重ねていくことを重視しています。そして、1日のリファクタリング作業に割ける時間も限られており、本アプリケーションの他の機能開発や、他プロジェクトへのフォローなど複数の作業を並行している中での地道な作業となります。記事のベースとなっている武市さんとの1on1リファクタリングタイムも、無制限に1日中実施できるわけではありません。

そのため、「記事にして皆さんに読んでいただくこと」を目的に、ある特定のコードを極端に磨き上げるようなことはしていません。むしろ、泥臭い現場の中途半端さを包み隠さずに届ける気概でいます。今回の例では、リファクタリング後のコードもまだ名前が怪しかったり、そもそもサンプルに登場するオブジェクトのプロパティ構成が理解しにくいままだったりと、記事で触れていない部分のコード断片も多くの「ツッコミどころ」に溢れていたと思います。それは承知しています。

今回の記事を通じて、まずはファイルの細分化を目指し、Repositoryとは何かという既成概念に囚われずに改善に取り組むことを優先しました。その上でJavaScriptの第一級関数を取り扱える特性を活かし、ファイルや処理を細分化することによって、名前を付ける対象を減らし、より可読性と情報量に優れた名前を付け直せるようにすることがテーマでした。

リファクタリングには正解も不正解も存在しないと思っています。その行いが時を経てどう改善につながるか、あるいは逆に悪い方向に向かってしまうか、それだけのことです。今回は積み重ねを重視する上で、悪い方向に滑っていかないようにフォローすることを重視しました。読者によってはリファクタリング後のサンプルコードが「不正解」に映ったかもしれませんが、トレタの現場ではこれを「前進した」と捉えています。

また、一つのコード断片を極限まで磨き上げてしまうと、今回ご紹介していない他の多くのツッコミどころだらけなコードが、すべて取り残されることになってしまいます。そうではなく、アプリケーション内のすべてのコードに対して満遍なく実施しやすく、かつ負担も少ないという「効くリファクタリング」を考えるようにしています。それがファイル分割の指針であったり、関数分割の指針であったりします。お手本のコードを「少しよくする」ことで、他のコードもお手本通りに「少しよくする」ことができ、全体が底上げできたらまた新たなお手本を一つ選び「少しよくする」という、地道な底上げをひたすら繰り返していくことを目指しています。

そもそもリポジトリになっていないのではないか?

ブログについてもうひとつよくいただいた質問が、「リポジトリ層」という表現や、コード中に登場するRepositoryについての取り扱いに対する疑問でした。この質問もとてもよく理解できます。

現状のコードについてリファクタリング案を出す時、他にスタッフがおらず素案もない場合は筆者が手順をすべて考えて実施していますが、今回はトレタ在籍2年の武市さんが主導でした。そのため、リファクタリングの全体的な方針や理想像はまず武市さんが考え、それに基づいて筆者が壁打ちに付き合うという関係性になっています。

ここで技術顧問という立場として、筆者は提案者である武市さんの案を真っ向から否定することを避けています。つまり「リポジトリ層を設けたい」という相談について、リポジトリ層とは呼べないから提案を却下するという流れには意図的にしていません。そして「その提案はリポジトリ層とは呼べない」といった批評もまずはこらえます。

そうではなく、武市さんの現在の経験と知識からリポジトリ層を設けたいと考えたこと自体を尊重し、その判断に至った経緯をヒアリングしました。

それによると、今回はテストがとても少ない上にGraphQLのライブラリとの密結合がひどく、テストを書くこともままならない点を改善したい。そのためにディレクトリを新規に作り、そこに細分化したファイルを入れたい、とのことでした。ここで、そのディレクトリにRepositoryと名付けることが最善なのかという議論はあえて行わず、まずディレクトリを分けるという判断を最大限に尊重しました。その上で、ブログ内ではcreateServiceRepository()という関数で巨大なオブジェクトを生成する是非についてのみ触れ、別の細分化を提案しています。

トレタの目指すリファクタリングの好循環とは

武市さんの経験量、知識量と筆者のそれには差がありますが、筆者の知識をもって提案を否定することは簡単です。リポジトリ層を設けるという提案自体を否定して、筆者がすべてのレールを敷くことだって可能です。しかし、今回本当に改善したいのはアプリケーションコード自体ではなく、そのアプリケーションコードの改善を通じて円滑になる組織そのものなのです。

冒頭でも説明した通り、新しいスタッフが参入しやすい環境作りを目的としており、コードの改善はあくまでも組織改善の手段のひとつとして捉えています。コードの改善は通過点であり、目的ではないのです。

そのためには、コードの改善の最大化よりも、長期的な組織の関係性を良好にしていくことが重要と判断しました。そのため、武市さんの提案に対しては否定ではなく、更なる知識や事例の共有によってフォローすることに努めました。リポジトリ層という表現がたとえ最適でなくとも、まずはその案をもとに短工数かつ高利益となる改善を探していく。それが今回のファイル分割や改名の第一歩であり、この姿勢がトレタの目指すリファクタリングの好循環を生むための姿勢です。

語彙の選択は難しいところです。色の濃い語彙ほど、組織を超えた共通認識がすでに世界中で形成されているものですから、あらゆる書籍の中から語彙だけを掻い摘んで採用することが不適切になる場面も存在します。筆者としては、多くのアーキテクチャ解説書籍について読解するときに「真似するために読むのではなく、よそさまの事情と思って読むとよい」と伝えています。そして「よそはああやっている。うちはどうやろう?」を考えるための足掛かりになればよく、書籍を語彙リストとして使ってはならないと説明しました。

正直なところ、「奥野さんがリポジトリ層という名前をスルーして他の部分についてだけ指摘しているのは珍しい」という意見もいただいております。「リポジトリ層」という語彙を記事のタイトルに含めることで読者から指摘が生まれるだろうことも承知していました。筆者の性格をよくご存知だなと思いながらも、今回の記事はあくまでも現場の複数日のやり取りの中の1コマであることをご了承いただけると幸いです。

改善はまだ始まったばかり

『奥野さんと社員のリファクタリング部屋』はシリーズを想定しており、まだまだ続いていく予定です。自転車の補助輪が外れていく姿を眺めるように、焦らずに見守っていただけると嬉しいです。今回はたくさんのフィードバックを頂きありがとうございました。また次回もお楽しみに。

奥野さんと社員のリファクタリング部屋 -リポジトリ層のディレクトリをどう作る?-

「奥野さんと社員のリファクタリング部屋」は、リファクタリングに励むトレタの社員と技術顧問の奥野さん ( @okunokentaro ) の間で実際に行われた会話を切り取った開発現場実録コンテンツです。

技術顧問: 奥野さん
三度の飯よりリファクタリングが好き。
今回の質問者: 武市さん
トレタ在籍2年。沖縄在住のフロントエンジニア

今回の質問

今回は初期リリースを終えたWebアプリケーション(Next.js)のプロダクトを担当している武市さんから、複数人で開発を進めてきて統率が取れなくなったディレクトリ構造のリファクタリングについての質問です。

APIで外部とやり取りしている部分をリファクタリングして、クリーンアーキテクチャに沿ってリポジトリを作ろうと考えています。

その中で、GraphQL APIレスポンスの結果を変換するアダプター関数(adaptGetIServiceItemsAggregateResult)はどこのディレクトリに置くのがいいですか?

下記はGraphQLリクエストを行うリポジトリの関数です。

src/server/boundary/repository/service/serviceRepository.ts

export const createServiceRepository = (
  gqlClient: GqlClientType,
  client: HasuraClient,
): ServiceRepository => {
  return {
    updateServiceWithExternalKeys: async (
      id: string,
      updateData: UpdateData,
      externalKeys: Record<string, string>,
    ): Promise<UpdateServiceWithExternalKeysResult> => {
      const mutationDocument = makeServiceMutationDocument(externalKeys);
      const result = await client.request<
        UpdateServiceResult,
        UpdateServiceVariables
      >(mutationDocument, {
        id,
        name: {
          ja_JP: updateData.name,
        },
        displayName: {
          ja_JP: updateData.displayName,
        },
        printName: {
          ja_JP: updateData.printName,
        },
      });
      return adaptUpdateServiceWithExternalKeys(result);
    },
    getIServiceItemsAggregate: async (
      itemManagementGroupId,
      name,
    ): Promise<GetIServiceItemsAggregateResult> => {
      const result = await gqlClient.getIServiceItemsAggregate({
        itemManagementGroupId,
        name: {
          ja_JP: name,
        },
      });
      return adaptGetIServiceItemsAggregateResult(result);
    },
  };
};

「単一責任の原則」から外れていないか

奥野

createServiceRepository()という関数の返り値がupdateServiceWithExternalKeys()getIServiceItemsAggregate() という関数を持つオブジェクトになっているのはどういった意図ですか?

武市

サービスリポジトリの中でサービスに関わることをすべてやってしまいたいと思ったからです。

奥野

それはgetIServiceItemsAggregate() という単独の関数でもよさそうにみえました。わざわざcreateServiceRepository() という関数でオブジェクトを返さなくてもいいかもしれない。

見たところ、引数のgqlClienthasuraClient は異なるユースケースのためのクライアントのようです。この2引数を両方必要とする関数がありません。つまりこのリポジトリは「単一責任の原則」から外れていて、テストする時にどちらか必要のないモックも作る必要があるんですよ。

つまり、getIServiceItemsAggregate() のテストをしたいだけなのにupdateServiceWithExternalKeys() の準備もしないとテストが始められない状況ってことです。

サービスリポジトリはオブジェクト定数ではなく、あくまでもディレクトリとしてrepository/service/getIServiceItemsAggregate.ts を作るといいと思います。

export const getIServiceItemsAggregate = async (
  gqlClient,
  itemManagementGroupId,
  name,
): Promise<GetIServiceItemsAggregateResult> => {
  const result = await gqlClient.getIServiceItemsAggregate({
    itemManagementGroupId,
    name: {
      ja_JP: name,
    },
  });
  return adaptGetIServiceItemsAggregateResult(result);
};

武市

なるほど。

奥野

今って処理をなるべく細かくしたいという段階ですね。細かくすればするほど一つのテストがしやすくなるためそうしようというモチベーションなのに、これでは小さくしたものをくっつけてまた大きくしちゃってます。

武市

たしかに。リポジトリというものに全部詰め込んだ方が、と思ったんですが。

奥野

リポジトリというのはあくまでも考え方で、外部のシステムに依存するものがリポジトリという場所にまとまっているといいよね、という考え方です。

まず、リポジトリ層というものがどういう経緯で生まれたかという歴史的背景をわかっていた方がいいと思います。

リポジトリ層はもともとJavaなどで取り入れられたクラスベースの考え方として広まっています。Javaはクラスしか扱えないからこそ、リポジトリクラスにインスタンスメソッドを生やす、必然的にコンストラクタに初期値を渡す、すなわちそこで依存性を備えるということをせざるを得なかったんです。

我々はTypeScriptプログラミングをしているので、必ずしもJavaの作法を真似する必要はなく1関数1ファイルというTypeScriptなりの書き方をすることもできます。

なので、repository/service/ というリポジトリのディレクトリの中にそれぞれの関数ファイル を作るという方法もありますよ。

repository/
    ├ service/
        ├ updateServiceWithExternalKeys.ts
        ├ getIServiceItemsAggregate.ts

細かく分けたいなら、今はとにかく細かく分けることに注力するといいと思います。


1責務 1 関数1ファイル

奥野

ファイルを分割することで、アダプタをどこ置くかという問題は自然に求まります。

関数を細かく分ければ分けるほどアダプターはそこでしか使わなくなる。なのでディレクトリをさらに細かくしていって、そこにアダプター関数を置けばいいということになります。

repository/
    ├ service/
        ├ updateServiceWithExternalKeys/
            ├ updateServiceWithExternalKeys.ts
            ├ adaptUpdateServiceWithExternalKeys.ts
        ├ getIServiceItemsAggregate/
            ├ getIServiceItemsAggregate.ts
            ├ adaptGetIServiceItemsAggregateResult.ts

これでなにが嬉しいかというと、adapt関数にそんな長い名前を付ける必要があるかと、いう問題の解決となる。

これまでなぜ名前を長くしないといけなかったかというと、今まで同じファイルに同居しており区別するための名前を付ける必要があったから。同じファイルにいないのであればadaptResult()で十分ですね。

もうちょっと言うと updateServiceWithExternalKeys.tsっていうのもupdate.tsで良いし、getIServiceItemsAggregate.tsgetAggregate.tsで十分です。IServiceItemsという名前はそれ自体がちょっと怪しいですね…。

repository/
    ├ service/
        ├ updateServiceWithExternalKeys/
            ├ update.ts
            ├ adaptResult.ts
        ├ getIServiceItemsAggregate/
            ├ getAggregate.ts
            ├ adaptResult.ts

もし武市さんが名前つけていて「この名前ダサいな」と思ったってことは何か良くないこと、微妙なことやってる証拠なんですよ。名前がおかしいってことは、そこで扱っている粒度がおかしいっていうことですね。

武市

疑問を持つタイミングが違ってました。リポジトリを作る際にgraphqlClinethasuraClientを注入しないといけない段階でキモいなと思ってたんですよ。

奥野

「キモい」という感覚はすごくいい一歩で、やっぱりキモいものって引数構成とか戻り値構成でどこか間違えてるんです。

オブジェクト指向を踏襲するとか、Dependency Injectionの考え方を踏襲するっていうのはすごくいい考え方ではあるんだけど、それは全てSOLID原則のS(単一責務の原則)を守ってこそ。 そういう各原則とかを守れてないあやふやなオブジェクトの状態であれこれ考えても、元が整っていないとツラいですね。 1責務 1 関数1ファイルという粒度にどんどん削ぎ落としていく。そうあるべき姿に持っていけば、リポジトリという巨大なオブジェクトは作る必要がなくなります。

武市

なるほど。先ほどキモいという言葉使ったんですが「違和感」 というものもうちょっと大事にしていこうと思います。


依存性注入とカリー化

奥野

getIServiceItemsAggregate()という関数を単独で切り出すと、引数がgqlClient itemMangementGroupId name の3つになりますが、もとのcreateServiceRepository()gqlClientの部分をわけていたのは、パラメーターではなくDependency Injection をやろうとしたということですよね。そこを分離させることは 賛成で、gqlClientもパラメーターっぽく見えるのは嫌ですよね。そこで使うのがカリー化です。

武市

依存だけ先に引数で渡すという話ですか?

奥野

その通り。TypeScriptで依存性注入する時はclass constructorでやるかカリー化した関数の引数でやるかが多くて、それをうまくできるような関数設計に寄せていくと綺麗にまとまっていきます。

type GetIServiceItemsAggregateResult = {}; // ダミー

type Return = (
  itemManagementGroupId: string,
  name: string,
) => Promise<GetIServiceItemsAggregateResult>;

export const getAggregate = (gqlClient: GqlClientType): Return => {
  return async (itemManagementGroupId, name) => {
    const result = await gqlClient.getIServiceItemsAggregate({
      itemManagementGroupId,
      name: {
        ja_JP: name,
      },
    });
    return adaptResult(result);
  };
};
type UpdateServiceWithExternalKeysResult = string;

type Return = (
  id: string,
  updateData: UpdateDate,
  externalKeys: Record<string, string>,
) => Promise<UpdateServiceWithExternalKeysResult>;

export const update = (client: HasuraClient): Return => {
  return async (id, updateData, externalKeys) => {
    const mutationDocument = makeServiceMutationDocument(externalKeys);
    const result = await client.request<
      UpdateServiceResult,
      UpdateServiceVariables
    >(mutationDocument, {
      id,
      name: {
        ja_JP: updateData.name,
      },
      displayName: {
        ja_JP: updateData.displayName,
      },
      printName: {
        ja_JP: updateData.printName,
      },
    });
    return adaptUpdateServiceWithExternalKeys(result);
  };
};

名前を短くして見通しやすくする

奥野

関数名を短くしたら全体的に名前を短くする工夫もするといいですね。

makeServierMutationDocument()という名前も、もしこの関数だけでしか使わないのだったら makeDocument()にしたらいいし、idstring型ではなくItemManagementGroupId 型ってわかれば別に変数名として長々と付ける必要はないから idで十分とか、読みやすくできる部分はどんどん読みやすくしていくと、さっきより読みやすくなった。

type GetIServiceItemsAggregateResult = {};
type Return = (
  id: ItemManagementGroupId,
  name: string,
) => Promise<GetIServiceItemsAggregateResult>;

export const getAggregate = (gqlClient: GqlClientType): Return => {
  return async (id, name) => {
    const result = await gqlClient.getIServiceItemsAggregate({
      itemManagementGroupId: id,
      name: { ja_JP: name },
    });
    return adaptResult(result);
  };
};
type ServiceId = string;
type Return = (
  id: ItemManagementGroupId,
  updateData: UpdateDate,
  externalKeys: Record<string, string>,
) => Promise<ServiceId>;

const update = async (client: HasuraClient): Return => {
  return async (id, updateData, externalKeys) => {
    const doc = makeDocument(externalKeys);
    const variables = {
      id,
      name: { ja_JP: updateData.name },
      displayName: { ja_JP: updateData.displayName },
      printName: { ja_JP: updateData.printName },
    } satisfies UpdateServiceVariables;
    const result = await client.request<
      UpdateServiceResult,
      UpdateServiceVariables
    >(doc, variables);
    return adaptResult(result);
  };
};

奥野

長い名前ってことは 長く説明しないと伝わらない概念ってことであって、それは概念整理できてないってことなんですよ。概念整理ができると、開発者同士で前提の共有ができるため短い名前でも通じるんですよね。

なのでなぜupdate()の一言で済むかというと、パスがrepository/service/updateだからなんですよ。/serviceの中にいるアップデート関数 だからサービスのアップデートなんだってわかる。

それはコンテキストが補えてるんですよね。

でもそれがコンテキストがないとupdateServiceWithExternalKeys()って全部の要素を名前の上で言わないと伝わらないし、名前以外の情報が足りてないってことだとわかります。だから repository/serviceいうディレクトリを切ったおかげでそれが何かっていうのがわかるようになるんです。

武市

ありがとうございます!僕もちょっと違和感があったところが解決したのでありがたいです!


PR

奥野さんがパーソナリティを務める「リファクタリングとともに生きるラジオ」も配信中です。リファクタリングに興味のある方はぜひチェックしてください!

refactoradio.com

トレタのテックトーク -プロジェクト管理における開発速度を考える回-

こんにちは。トレタ VPoEの北川です。

今回はトレタで長年行われているテックトークという開発メンバーによるフリートークの場について紹介します。

テックトークとは

隔週に一回開催し、当番の発表者が最近の気になる技術情報であったり、業務する中での技術的な学びを社内に発信する場です。 全体30分の前半はLT(ライトニングトーク)、後半はその内容について参加者内で議論、という形式で行なっています。

今週のテックトーク

今週の発表者はO/XグループPjM(プロジェクトマネージャー)の藤田さんです。

今回の発表内容は、他社のプロジェクトマネジメント事例としてリクルート社のAirワーク リニューアルプロジェクトに関する記事の紹介でした。

hatenanews.com

紹介いただいた記事の内容は、

  • 190画面もあるプロダクトのリニューアルを1年近くかかるところを6ヶ月まで短縮し、結果的に4ヶ月でやりきった
  • 4ヶ月+1ヶ月+1ヶ月という変形スプリントによるウォーターフォールとアジャイルの合わせ技
  • 50組の開発チームを並行に動けるようにする
  • コミュニケーションパスを断ち切る
    • 仕様・設計は最初に固める
    • 決めた仕様は変えない
    • 共通化はせず重複コードを許容する
    • 画面毎にAPIを作る

重複コードを許容するなど驚く内容もありますが、共通化か冗長化はどちらが良いかは時と場合によると思います。 冗長に作る方が良い面もあって、たとえば作るものがまだ明確に定まりきっていないような不確定要素が多い場合には適しています。 ベストプラクティスがまだ確立していない場合なども、まずは冗長に作っておく方が無駄に考えすぎてしまう時間を短縮できます。

すぐに自分達に取り入れられそうなものとしては、4ヶ月+1ヶ月+1ヶ月のような変形スプリントでの手法です。 トレタでも先に仕様や設計をしきってからスコープを切りスプリントを回すウォーターフォールとアジャイルを合わせたような開発を行なっていますが、ウォーターフォール+調整スプリントという手法は参考になりそうです。

紹介されていたプロジェクトでは他を犠牲にするほどの「開発速度」を最優先として進めていましたが、「開発速度の追求」という明確な目的が定められているのも特徴に感じます。 トレードオフに加えて「リスク」を取らなければ圧倒的なスピードを得ることはできません。 プロジェクト設計の目標を定めることで、リスクを取ってでも何かを捨てて何かを得る意思決定を行いやすくなる、というのは学ぶべき点です。

さいごに、藤田さんが意識していることとして「ビジネスサイドとエンジニアサイドの防護壁」というのを挙げていました。 記事には「最速で走ろうとしているエンジニアを守る防御壁の役割」という言葉がありましたが、ビジネスサイドとエンジニアサイドの間に立ち、それぞれの課題や仕様を把握するようにしたいとのことでした。

参加者の反応

  • 50組の開発チームというトレタの開発チームと比べると規模感が全く異なるが、変形スプリントなど随所に参考になる点はありそう
  • コミュニケーションコストの問題は今のトレタの開発チームの規模感でも発生しているので、途中の仕様変更を抑えたりする等、取り入れられそうな部分はありそう
  • 50組もの開発チームを束ねるPM内のコミュニケーションコストが気になる

さいごに

実は藤田さんがテックトークの発表に参加するのは今回が初でした。

今までは主にエニジニアの技術的な発表が多かったのですが、発表者の幅を広げるためにライトな内容も歓迎して発表後の質疑応答や議論の時間を多くとるように変えました。

PjMがいつもどういったことを考えているのかを知れる面もあり、エッセンスとして開発に関わる人すべてに共通する内容もあり、普段とは違う側面での学びが多い回でした。

© Toreta, Inc.

Powered by Hatena Blog