この記事は私をシニアエンジニアにしてくれた「真のアジャイル開発」体験記の25日目の記事です。このアドベントカレンダーは「ある機能開発チームでスクラム, XP, DevOps を一度に実践したら真のアジャイル開発ができた」という内容です。執筆者は全てプログラミングをするパンダです。

「真のアジャイル開発」の経験に基づくチーム運営

25日間続けたアドベントカレンダーのラストは、「真のアジャイル開発」ができたという経験を活かして自分がどのようにチームと向き合っているのかという姿勢を紹介します。

このアドベントカレンダーで紹介したプロジェクトの後、ある新規機能を開発するプロジェクトにアサインされました。チームメンバーはままです。そこにもう一つ別のチームが合流しました。合計2チームで1年半、リリースを3回に分けて実施した大きなプロジェクトでした。

エンジニアリングマネージャーの提案により大規模スクラム(LeSS。Large-Scale Scrum)の手法を取り入れ、2チームでスクラム開発をしました。プロダクトオーナーとデザイナーとエンジニアたち全員が一緒になって仕様を策定するところから始まり、数日間かけてユーザーストーリーマッピングという、ユースケースをユーザーストーリー形式で記述して全て洗い出して見積もりをしました。すると2年かかるという見積もり結果になりましたが、結果的には1年半でリリースができました。

自分はこのプロジェクトにシニアエンジニアとして参加しました。ドメインモデリングやデータモデリングを主導したり、フロントエンドのコードを書いたり、バックエンドのコードをレビューしたり、一歩ずつ後ろに下がっていったスクラムマスター(エンジニアリングマネージャー)の代わりにスクラムイベントをリードしました(ただし、流石に2チームとも全部は見切れないため、1チームに主軸を置いてもう1チームは別のメンバーが主導していたため自分は統括の役割をしていました)。

開発期間は1年半、関わった人はプロダクトオーナー2名(メインとサブ)、デザイナー3名(同時ではなくフェーズごとに入れ替わり)、エンジニアはヘルプの方も含めて10名以上の関わった長いプロジェクトでした。もちろん全てがうまくいったわけではありませんでしたし、自分も「新人シニアエンジニア」として未熟なところから色んなことを学ばせてもらった1年半でした。

この大きな機能のリリース後、今は別の機能開発チームに所属しています。このチームは自分も含めてエンジニアが5名で、そのうち新入社員が2名という構成です。2024年の5月ごろから本格的に開発が始まりました。2024年の年末の今はリリースに向けて佳境です。そんな中でまた自分は別のプロジェクトにアサインされ、今は開発を進めつつ兼務で両方のチームを見ています。

チームメンバーによってチームのあり方は様々である

前出の1年半にわたる機能開発が終わった2024年2月ごろ、自分はある程度のプロジェクトならうまく回せるだろうと思い込んでいました。しかし、それは過去に学んだことを適用するだけであり面白みがありません。どのようなメンバー構成であったとしてもうまくいくチーム運営の手法はないだろうか、その手法は自分がすでに体得して使いこなせる手法の外に何かないだろうかと漠然と考えていました。

このように考えていたところ、「哲学対話」という身近なことをテーマにした実践的な哲学の手法(当初は子供のための哲学として考案された手法です)を実践するサークルに参加した時に、ああ、求めていたのはこれだと思いました。哲学対話の詳細は省きますが、会話の中で知っていると思っていたことを改めて探究すると言う哲学対話を実践することで、結局自分は何も知らないのだな、自分も知らないし他の人も知らないのだと言う感覚を得ました。ソクラテスの無知の知(不知)です。

そして人は知らないからこそ探究するのです。「すべての人間は、生まれつき、知ることを欲する」とは大哲学者アリストテレスの言葉です。知らないからこそ、知ろうとする。知っていることで現実に対処するのではなく、知っていると思っていた現実に改めて向き合って、現実をつぶさに観察してより良く知ろうとする態度、それを元に考え抜く態度。これこそが哲学的な姿勢だと理解しました。

この姿勢が重要なのはチーム運営でも同じです。

チームメンバーの一人一人を知り、チームに合わせて適切な手法を選ぶ

ここで紹介したプロジェクトでは、スクラム、XP、DevOpsをすべて一度に実践しました。しかし、「ゆとり」の項目で紹介したように後半はペアプログラミングばかりだと自分一人の時間がなくなると疲弊したメンバーもいました。彼は鋭い発言をするし人に対して前向きなものの、物静かで考えるのが好きなタイプです。

このように、良いとされるプラクティスを最初から導入することは危険があります。そうではなく、チームメンバーの一人一人と向き合い、自己を開示して相手を知る対話をすることで、より良いチームを作ることができるのです。一人一人と向き合うという点ではマネージャーの仕事と似ているかもしれません。

例えばあるプラクティスを導入するにしても、一度チームで失敗してから導入することを心がけています。開発初期に取り返しのつかない失敗というものはほとんどありません。この時期にチーム内でたくさん失敗をした後、振り返り会で「ここは良くなかった」「どうしたら良かったのだろう」という疑問をチームメンバーが持つように見守ります。

チームの課題が一人一人の自分ごとになったら、人の課題を当事者として考えられるようになったと判断したら、自分が知っている手法の中から「こういうやり方をすると解決できそうだ。これをチームで実践してみるのはどうか」と提案するのです。その際に注意するのは、その課題が表出するきっかけを作ったのが個人であっても絶対に個人の責任にしないことです。

そうではなく、「これはチームの誰にとっても起きうる課題だった」と一言添えて、チームみんなで解決していこうと伝えるのです。これを毎スプリント繰り返していくことで失敗しても怒られない、怒られないから何でもチームに相談できる。怒られないから新しいことに挑戦しようという前向きな文化が形成できるのです。

しかも、チームに必要なプラクティスだけを自然と導入できるので、やっていても意味がないとみんなが感じるようなプラクティスは自然と採用されないことになります。こうすることで「儀式」に時間を取られることを避けられるのです。

自分はこれを積み上げ型のプラクティス導入と呼んでいます。最初から「ずっとペアプロをしよう」など決めてしまって、チームメンバーそれぞれの特徴を考慮せずにトップダウンでプラクティスを導入してしまうのは悪手です。積み上げ型のプラクティス導入をしたチームは、その時の自分たちにフィットしたプラクティスだけを実施します。このようなチームは導入したプラクティスにより弱みを押さえ、強みを伸ばすことになるため、より強いチームになります。

良いチームのあり方は一通りではない

いいチームを作るためには何をすればいいのでしょうか。それはチームビルディングです。チームビルディングをしっかりしましょう。

チームビルディングがうまくいき、これを言ったら馬鹿にされるかもと恐れることなくチームで発言ができる心理的安全性が醸成されたら、そして「これ何か変だな」「これもっとうまくやれるな」と感じたことをチームで改善を続けることができたら、人間関係の面倒さはほとんどなくなってしまいます。

人間関係の面倒さがなくなると、チームみんなが開発に集中できます。開発に集中できるとベロシティが安定し、リリースの見通しが立てやすくなります。すると突発的なことにも柔軟に対応できますし、誰かが困っている人を助けるというチームワークも発揮できるようになります。そうなれば、みんながいいチームだと言ってくれるようになるでしょう。

トルストイのアンナ・カレーニナは「すべての幸せな家庭は似ている。不幸な家庭は、それぞれ異なる理由で不幸である」という有名な書き出しから始まります。チームメンバーの一人一人と向き合うことができると、次の言葉の意味も自ずと明らかになるでしょう。

「いいチームの作り方は似ている。しかし、いいチームはそれぞれ異なる理由でいいチームなのである」

以上、「私をシニアエンジニアにしてくれた『真のアジャイル開発』体験記」でした。25日間にわたってお読みくださり、ありがとうございました。

advent calendaragile
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

この記事はPAY Advent Calendar 2024 24 日目の記事です。

はじめに断っておきますが、Stripe という決済サービスはとても使いやすい SDK が用意されており、ドキュメントも丁寧で開発者体験が良かったです。この記事は Stripe の苦い思い出ではなく、自分の DB 設計ミスの話です。同じ失敗をする人が少しでもいなくなることを願って昔の失敗談を共有します。

初めての 0→1 開発。社内で SaaS の新規開発プロジェクトを任された

ソフトウェアエンジニアになってちょうど 3 年が経った頃、2 社目での出来事です。社内で新規プロジェクトが立ち上がりました。上の人が前からこういうサービスを作りたい!と考えていた SaaS の開発です。

当時、自分はバックエンドのコードを書いていました。しかし、個人開発で Web サイトを作るうちにフロントエンドの面白さに目覚め、以前からフロントエンドもやってみたいとマネージャーに話していました。すると熱意を買ってもらえたのか、バックエンドとフロントエンドの両方を任せてもらえました。

プロジェクトのメインメンバーはプロダクトオーナー、デザイナー、そして自分の 3 名でした。システムのコアの部分はテックリードが担当し、自分はユーザー管理、決済周りとフロントエンド全般を担当しました。また、インフラは別のチームのエンジニアにヘルプで入って貰って ECS で構築しました。

当時はあたかもスタートアップのような働き方をしていました。

ビジネスのアイデアはふわっとしたものが上から降りてきただけだったので、詳細はプロダクトオーナーを始め自分たちで話し合って詰めていきました。デザインも 0 から作りました。侃侃諤諤あーだこーだと議論してプロトタイプを作って、それを元にユーザーインタビューを 5 件ほど行いました。

東京のオフィスを飛び出して、チームみんなで丸の内や埼玉の大宮のユーザー候補の方のところに直接出向いて行きました。できるだけ多くのインサイトを得ようと自分たちのプロダクトを実際に使うところを後ろから見させてもらったり、普段の業務フローやプロダクトの使い心地のインタビューをさせてもらったのは良い思い出です。

バックエンドの設計と Stripe の利用

システム設計について、DB 設計はシンプルで 5 テーブルもなかったと思います。テックリードは口数が少ないものの自分たち普通のエンジニアが考えていることの 2 歩も 3 歩も先のことを静かに、しかし鋭く指摘する方でした。

そんなテックリードにテーブル設計をレビューをしてもらったときはとても緊張しました。それでも保持するデータがシンプルなため「いいと思います」ということで大きな手直しなく無事レビューは通りました。

バックエンドに関してはルーティングがあるくらいの薄いフレームワークを使いました。そこに ORM と DI ライブラリを入れてアプリケーションを構築しました。

SaaS であるため決済周りは外部サービスを使うことになりました。いくつか候補があったのですが、Stripe を使うことに決まりました。Stripe の使い方や月額課金の更新などの質問をすると、日本オフィスの人が日曜日でも返信してくれました。なんてサポートが手厚いんだと感動した覚えがあります。

他の候補は GMO ペイメントゲートウェイ、Veritrans だったと思います。本記事は PAY.JP のアドベントカレンダーですが、PAY.JP の存在は当時知りませんでした。

外部連携であるため Stripe の SDK からこちらの部分は Adapter パターンを採用して、SDK のバージョンアップに柔軟に対応できるようにしました。Stripe が SDK で持っている Customer や Card オブジェクトをそのまま使うのではなく、自分たちの作る Plain なクラスに変換して、SDK への依存を一部に留めることを意識しました。

当時の自分はケント・ベックに傾倒していたため、全てのクラスを TDD で作りました。サービスは必ずインターフェースを作り、public メソッドは 1 クラス 1 つにしました。DI 機構も入れて外から必要なクラスを注入する形になっています。サービスロケーターを回避していたためオブジェクトの差し替えが簡単にでき、サービスクラスのテストも容易でした。Stripe はモックを用意してくれているので、外部連携ながらもモックを使ってテストすることも簡単でした。

OpenAPI の定義を書いて Prisma でモックサーバーを動かしたり、サーバーを立てて API をコールするテストフレームワークをテックリードが導入してくれたため、ユニットテストはインテグレーションテストのみならず、API レスポンスを検証するテストコードも書き溜めていったり(テックリードはこれを E2E テストと呼んでいました)、テストのカバレッジが高く堅牢な作りだったと思います。

今から考えるとディレクトリ構成面でもコードの設計面でもテストコードの面でもまだまだブラッシュアップできたと思います。当時の自分はドメインモデリングの力が弱く、Stripe の Customer や Subscription、Card などのクラスの写しを作ってしまっただけだったことや、アクティブレコードパターンで闇雲に JOIN することは避けられたものの、1 テーブル 1Repository で作ってしまっていたのは反省ポイントです。

それでも当時はボブおじさんの書籍を熱心に読み漁っていたため、SOLID 原則やクリーンアーキテクチャを強く意識した作りにしていました。3 年目にしては変更に強い柔軟な設計になっていたと自負しています。

Stripe の利用

Stripe で利用した機能について触れておきます。そもそも開発するのは個人向けの SaaS であり、月額課金を実施したいのでした。毎月末に翌 1 ヶ月分の請求をする月額プランと、月額プランより少しお得な年額プランを用意していました。月の途中に加入しても 1 ヶ月分の利用料が引き落とされるため、月初に加入するのがお得という仕組みです。

Stripe の SDK で利用したクラスは Customer, Plan, Product, Subscription などです。Stripe のモデリングは本当によく出来ていて便利でした。自分もしっかりモデリングをするぞと息巻いて取り組もうとしたものの、Stripe のモデリングのレベルが高すぎてそのまま使わせてもらおうと思ったのでした(今から考えると工夫の余地はありそうですが)。Stripe APIのドキュメントには大変お世話になりました。

Customer は顧客です。API をコールすると以下のような顧客データを取得できます。データは Stripe のドキュメントのものです。

{
  "id": "cus_NffrFeUfNV2Hib",
  "object": "customer",
  "address": null,
  "balance": 0,
  "created": 1680893993,
  "currency": null,
  "default_source": null,
  "delinquent": false,
  "description": null,
  "discount": null,
  "email": "jennyrosen@example.com",
  "invoice_prefix": "0759376C",
  "invoice_settings": {
    "custom_fields": null,
    "default_payment_method": null,
    "footer": null,
    "rendering_options": null
  },
  "livemode": false,
  "metadata": {},
  "name": "Jenny Rosen",
  "next_invoice_sequence": 1,
  "phone": null,
  "preferred_locales": [],
  "shipping": null,
  "tax_exempt": "none",
  "test_clock": null
}

名前や住所、メールアドレスなどを持っています。API 経由で作成したり取得した Customer を自分たちで作成したクラスに置き換えます。ここが Adapter の役割です。

流石に前職で書いたコードをそのまま紹介するのは良くないので、大体こんな感じというものを TypeScript 風の疑似コードで書きます。

Plain Old Java Object のように、自分たちのコードで Customer クラスを定義しました。

class Customer {
  constructor(
    customerId: CustomerId,
    address: Address,
    mailAddress: MailAddress,
    name: Name
    // ...
  ) {}

  // ドメインロジック
}

そこに Stripe から取得した Customer をマッピングします。クラス名はもっとしっかりしたものだったと思うのですが、かなりうろ覚えなので雰囲気だけ掴んでもらえればと思います。

import { type Customer as StripeCustomer } from 'stripe'

class CustomerAdapter {
  public static function createCustomer(stripeCustomer: StripeCustomer): Customer {
    return new Customer(
        new CustomerId(stripeCustomer.id),
        new Address(stripeCustomer.address),
        new MailAddress(stripeCustomer.mailAddress),
        new Name(stripeCustomer.name),
        ...
      )
  }
}

SaaS であったため、特に Subscription を活用しました。Stirpe サポートへの問い合わせも Subscription の使い方に関するものを一番多く聞いた覚えがあります。リリース後は「初月無料」や「1 月に加入すると、年額プランは 10%OFF」など、最初になかった機能を後から追加するたびに疑問が湧いたらすぐにサポートの方を頼らせていただいた記憶があります。

Subscription の cancel_at_period_end という解約予約フラグや、trial_end という無料のトライアル期間の終わりの日時を設定するものや(この時間が過ぎた瞬間に課金されます)、Coupon とか Discount を活用したのを覚えています。

ビジネスの要求に応じてソフトウェアは変化していくのだ、そのソフトウェアの変化を起こすのがソフトウェアエンジニアなのだとこの時深く実感しました。

Stripe の Subscription

Stripe のSubscription Objectは本当によくできているため、基本的な構造を解説します。

Subscription はその名の通り定額課金を実現するためのオブジェクトです。これは以下のような値を保持しています(重要な部分以外は省略しています)。

{
  "id": "sub_1MowQVLkdIwHu7ixeRlqHVzs",
  "object": "subscription",
  "billing_cycle_anchor": 1679609767, // 支払いサイクルの始まり
  "created": 1679609767,
  "currency": "usd", // 決済通貨
  "current_period_end": 1682288167,
  "current_period_start": 1679609767,
  "customer": "cus_Na6dX7aXxi11N4", // Customer ID
  "items": {
    "object": "list",
    "data": [
      {
        "id": "si_Na6dzxczY5fwHx",
        "object": "subscription_item",
        "created": 1679609768,
        "plan": {
          // Subscription の Plan
          "id": "price_1MowQULkdIwHu7ixraBm864M",
          "object": "plan",
          "active": true,
          "amount": 1000,
          "billing_scheme": "per_unit",
          "created": 1679609766,
          "currency": "usd",
          "interval": "month", // 月単位の決済
          "interval_count": 1,
          "product": "prod_Na6dGcTsmU0I4R" // 商品(何に対して課金しているか)
        },
        "price": {
          // 金額
          "id": "price_1MowQULkdIwHu7ixraBm864M",
          "object": "price",
          "active": true,
          "created": 1679609766,
          "currency": "usd",
          "product": "prod_Na6dGcTsmU0I4R",
          "type": "recurring",
          "unit_amount": 1000
        },
        "quantity": 1,
        "subscription": "sub_1MowQVLkdIwHu7ixeRlqHVzs",
        "tax_rates": []
      }
    ]
  },
  "latest_invoice": "in_1MowQWLkdIwHu7ixuzkSPfKd", // 請求書のID
  "start_date": 1679609767,
  "status": "active", // サブスクリプションのステータス
  "trial_end": null,
  "trial_start": null // (もしあれば)無料期間
}

Stripe の Subscription は、Customer ID を持ちます。これによりどの Customer のサブスクか辿れます。次に Subscription Item を持ちます。これは Plan と Price を持ちます。Plan は月額プランや年額プラン、free プランや Business プランのように期間や金額(Price)を自由に設定できるものです。

Stripe を活用する前は、Subscription 自体が決済サイクルの期間や金額を持っているのかなと思っていました。しかし、蓋を開けてみるとこのようにしっかりオブジェクトが分かれており、しかもそれぞれで必要十分な、確かにその通りに分けた方が便利だと思えるような責務の分け方でした。

これが全世界で活用されるサービスを提供するグローバル企業のハイレベルなモデリングなのかと驚き、憧れたことを今でも覚えています。自分にとって Subscription オブジェクトはその最たるものでした。

フロントエンドの構築、リリースと運用、そして退職へ

フロントエンドでも 2019 年末という比較的早い時期に Next.js の導入を決めて、公式ドキュメントを熟読してアプリケーションを構築しました。また、Storybook でデザイナーとコミュニケーションを取ったり、E2E のみならずコンポーネントテストすらもこのサービスには少し過剰ではないかと考え、フロントエンドでも Unit テストを書くことで、フロントでも 90%以上のカバレッジを保ちました。

Renovate を導入して依存パッケージのバージョンアップを 2 週間ごとに実施するなど、フロントは運用面の体制を整えていました(バックエンドのログの取り方やアラートの通知周りは同僚に教えてもらいました)。

サービスをリリースしてから離職するまで 1 年間ほど運用しました。リリース後は業務委託のエンジニアがもう一人増え、そこから 1 年間はずっとペアプロで開発していました。1 年間機能追加を続けていってもバグは 2,3 件しかなかったように思います。それも表示崩れが 1 件、フロントのデグレが 1 件のような小さいもので、システムが停止するとか決済が失敗するとか、過剰に金額を取ってしまうというような大きなトラブルは起こりませんでした。

もちろん成功ばかりではありません。追加開発をしながら突発的な差し込み対応をこなしたり、エンジニア不足で社員が運用する管理画面の作成ができなかったり運用を自動化できなかったところもあり、「この時はこの手順でこれを実行するんだ」という秘伝のスクリプトもいくつか生まれてしまいました。しかし、常にペアプロとペアオペをしていたため、作業の属人化はしておらず引き継ぎで困ることはほとんどなかったことはまだ救いでした。

優秀で素晴らしい同僚たちから毎日良い刺激を受けながら、本当に貴重な経験を積ませてもらいました。リリースしてから 1 年後に退職しましたが、退職の時も初期設計で安定した良いシステムを残せたのではないかと内心誇らしく思っていました。

退職 2 週間前に受けた同僚からの指摘に愕然とする

とまあ自画自賛が続いたわけですが、失敗談はこれくらい前振りが大きい方が落差があっていいですよね。

自分の退職 2 週間前にある同僚からテーブル構成について指摘を受けました。「あれ、このテーブル、User と Strip アカウントって別管理してないんですか?」

それを聞いてえっ?と思いました。記憶は曖昧ですが、確かに User テーブルのカラムは auto increment の id と stripe account の id(customer id だったかな) を持っていました。流石に subscription や card の下何桁のようなテーブルは分けていたような気がします。

その人の指摘は、User テーブルと Customer テーブルは分けて、Customer テーブルに User の id を持たせた方が良いというものだったと思います。そうしないと、Stripe のユーザーと自分たちのサービスのユーザーが一体になってしまって良くないとのことです。

それを聞いて自分はあっ、と思いました。大きく複雑なものではないけれど、全力を発揮して良いシステムを作りたい、そしてそれが出来たと思ったのに、よりによって一番変更のしにくい DB でやらかした。しかもそれに気付かされたのが退職の 2 週間前。退職までに時間があれば自分のスキマ時間を使ってでも絶対対処してリカバリーするという気持ちはあったのに、もうどうしようも出来ない…。

思い返すとユーザーの何かのメタ情報も Stripe 側に保存していたような記憶があります。このメタ情報もこちら側で持つべきデータでした。Stripe のダッシュボードが便利だったんです…。

失敗談はなかなか表に出てきません。というより自分が全然書いてないだけです。普通にいっぱい失敗してます。ここで懺悔致します。

当時は ChatGPT もありません。頼れるのは先輩と書籍、そして Google 検索のみです。自信がないときはもっと周りの力を頼りましょう。

前職の同僚たちとは自分の退職後も密に連絡を取っており、この件に関してその後何か言われたことはないです。サービス自体も人気があるようでまだまだ稼働中です。ただ、あの後テーブル構成がどうなったかとても気になるので、次に同僚に会ったときは恐る恐る聞いてみようと思いました。

若手エンジニア時代の失敗、Stripe を使った SaaS 開発の苦い思い出でした。

プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

この記事は私をシニアエンジニアにしてくれた「真のアジャイル開発」体験記の24日目の記事です。このアドベントカレンダーは「ある機能開発チームでスクラム, XP, DevOps を一度に実践したら真のアジャイル開発ができた」という内容です。執筆者は全てプログラミングをするパンダです。

プロジェクトを振り返る

ここまでスクラム、XP、DevOpsの定義と実践を解説してきました。リリースをした時、チームは達成感に包まれていました。総じてうまく行ったプロジェクトだったとチームメンバーみんなが実感しました。

元々「2,3週間後のゴールデンウィーク明けに出してほしい」と言われていた機能です。その中でも焦りから疲弊したメンバーもなく、ビジネス側も遅いから早く作ってくれと開発陣を責めることもありませんでした。

開発サイドとビジネスサイドが二人三脚で業務フローとそれを支援するシステムを構築したことは疑いありません。アジャイルソフトウェア開発宣言の「個人と対話」「動くソフトウェア」「顧客との協調」「変化への対応」の全てを実現できたと思いました。

さて、この機能のリリースをしてから2年半が経ちました。もう後日談を語れる時期です。リリース後に起きた失敗とよかったことを振り返ることで、また次に繋げたいと思います。

失敗を振り返る

まずは失敗を振り返ります。

本番で想定外のバグが一件出たため、急いで修正パッチを作ってリリースした

自分たちが作った機能は、社内メンバーが社員向けの管理画面から使うものでした。この機能は顧客情報に関わるものだったので、セキュリティチームに早い段階からスプリントレビューに参加してもらい、安全性の高い業務フローを構築したことはスプリントレビューの項目で紹介した通りです。これ以降、どんな機能だったか明言はしていないので想像で補ってお読みいただければと思います。

この機能を社内メンバーが2,3回ほど利用した時でした。この機能専用のalertチャンネルに通知が来ました。エラーが起きたらSlackに通知するようにしていたのです。エラーパターンはいくつか想定済みだったので、エラーメッセージにエラーの原因を記載しており、エンジニアに質問しなくても作業の担当社員が自分で復旧できるように運用フローも整備していました。。

しかし、今回のエラーは毛色が違いました。作業担当者がいくら自分の入力を見ても間違っていないというのです。そこでエンジニアにヘルプが求められました。自分たちから見ても確かに入力データに間違いはありません。コードにバグがあったかなと思って調べても正常に動作しているようです。

みんなでああでもない、こうでもないと議論していたところ、一人のエンジニアが閃きました。彼によると「自分たちはメールアドレスのバリデーションをするときに、RFCに準拠したプログラミング言語の標準関数を使っている。ここでエラーが出てるんだ。docomoやauの古いアドレスは、RFCに準拠していない古いものもあると聞いたことがある」とのこと。チームでさらに確認したところ、彼の説明は正しいことがわかりました。その後、急いでパッチを作ってリリースしました。

自分たちは、文字通り全てのクラスをTDD(テスト駆動開発)で実装しました。しっかりテストを書いているので大丈夫だと胸を張ってリリースをしたものの、それでも本番運用時にバグが出てしまいました。しかし、このバグはどうやったら最初から想定できたのだろう。いや、最初から想定することは無理だったのだろうか。私は自問を続けました。

この問題意識を抱えたままいくつかテストに関する記事を読んでいると、TDDは入力例を元に実装を進める設計手法でありテスト技法ではないため、想定外の入力ではバグが混入するという話や、そもそも本番でバグは起きると考えて、そのバグにいかに早く気づいて復旧するかが重要だというアイデアに出会いました。後者はDevOpsの平均復旧時間の話ですが、リリース前は自分の中でテストと結びついていなかったのです。

このことについて、以下の記事で詳しく解説をしました。

というよりも、以下の記事を執筆できたのは、この古いメールアドレスによって発生したバグがきっかけでした。自分の中で失敗だと思ったものの次に繋がった経験でした。

ビジネス側でスプレッドシートに手順書が作成されていた

これはまだ自分の中で解決策が見当たらないものです。社内の人が使うツールなので、直感的に使いやすい画面を作ろうと思ってUI/UXはかなり工夫をしました。

リリース後にたまたまビジネス側の人がその画面を操作するのを見るタイミングがありました。どうやって使っているのだろうと思って見ていると、なんとスプレッドシートで作られた手順書を逐一見ながら一つずつ慎重に操作をしていたのです。

確かに慎重になる気持ちはわかります。しかし、手順書を作るのも更新するのも手間ではあります。使いやすいように無駄な作業を減らすような画面を作ろうと頭を捻ったのに、フロントエンドに関わるものとしてその姿は少しショックでした。手順書があると画面に表示している説明も読み飛ばされるのだろうなと思いました。その説明も一文一文わかりやすいようにブラッシュアップしたものだったのですが…。

ただ、今から考えると業務で扱うソフトウェアと自分が趣味で使うソフトウェアの操作の重みは違うのだろうと思います。戻るボタンを押して全てをやり直せるものでもなく、担当者としては間違ったらどうなるかわからないがとりあえず面倒なことになると想像して慎重になるのでしょう。また、スプリントレビューに参加してくれていたビジネス側の方の仕事が忙しくなり、その業務が他の人に引き継がれたということも大きな原因だと思います。

この辺りは自分の中で明確な答えは持てていないです。ただ、リリース後に実際にどう使われるのかという視点は持ち続けないといけないと思いました。

それ以降の機能開発では本物の顧客参加を実現できていない

スプリントレビューでは社内のビジネス側の担当者を顧客として、社内ツールを一緒に作り上げていました。しかし、その後のプロジェクトの機能開発ではどれも顧客に開発中のものを見せることができず、またtoCという性質のため顧客と一言でいっても様々な属性の方がいます。その結果、ユーザーインタビューやヒアリングをしているプロダクトオーナーを顧客に見立てて、スプリントレビューでソフトウェアに対するフィードバックをもらうようになりました。

しかし、プロダクトオーナーは仕様策定をする上に、デザイナーとUI/UXの検討をしています。するとプロダクトオーナーは顧客よりも機能について知りすぎている上に、実際の顧客のユースケースと乖離した場面を想定することもあり得ます。頭の中で構築した仮説は、顧客だけが直面している前提を含んでいないtまえ、だいたい間違っているというのが私の持論です。プロダクトオーナーはどうしても顧客の代表にはなれないのです。

自分がエンジニアとして入社したどの会社でもユーザーインタビューは実施されたりされなかったりでした。実施されなかったから誰かがサボっているというつもりは全くありません。ただ、ユーザーに自分たちの開発中の機能を使ってもらってフィードバックを得られることはかなり稀だと感じています。このため、社内ツールの開発で得た経験は貴重だと思うと同時に、機会があればその状況を再現したいと常に思っています。

よかったことを振り返る

他にも良かったことを振り返ります。

機能拡張の際に「改修しやすい」と褒めてもらえた

XPの「インクリメンタルな設計」の項目でリファクタリングについて触れました。自分はプルリクエストをマージした後も、より良い設計はないかと常にリファクタリングの機会を伺っていました。このため、開発全体を通してクラスや処理が追加されてもシンプルな設計を保ち続けられたと思います。

機能のリリース後、ビジネス側から機能追加の要望が上がってきました。エンジニアリングマネージャーはその話を聞いて、優先度の高いところにエンジニアを当てたいから業務委託の人を採用してその追加対応だけやってもらおうと判断しました。

実際、マネージャーは業務委託の方と契約をして機能追加の依頼をし、自分たちはコードレビューを任されました。果たしてどうなるだろうかとアサインされたプルリクエストを見てみると、コードの意図を読み取ってもらえて自然なコードになっていました。自分たちからはこうやって作ってくださいと説明することはなかったのに、です。

元々バリエーションがあるコードだったので、そのバリエーションを追加してくださいという依頼だったこと、明らかにここから処理が始まってここでバリエーションの分岐をしているのだとわかる設計にしていたことが功を奏しました。コードレビューの負担も少なく、些細な点だけを指摘するだけで済みました。何度かコードレビューをするうちに機能は完成していました。

後からマネージャーが教えてくれたのですが、その業務委託の方は「設計がわかりやすかったのでコーディングをスムーズに進められた」と言ってくださったそうです。リファクタリングを繰り返し実施して良かった思えた瞬間でした。

シニアエンジニアとして役職を貰えた

このアドベントカレンダーは「私をシニアエンジニアにしてくれた「真のアジャイル開発」体験記」です。自分はこの機能をリリースした後、シニアエンジニアという役職を貰いました。自分の実力はまだそこまでのものではないと思っていたので、肩書をもらえたことに驚きました。

確かに開発プロジェクトの間のマネージャーとの1on1で、「パンダさんのペアプロの相手が勉強になる、前向きに仕事に向き合える、と言っていましたよ」とフィードバックを貰っていました。しかし、自分はありがたいと思いつつ、それは自分の力でもなんでもなくてみんなで燃え尽きずにうまく開発を進めたいという気持ちと、XPやDevOpsを文字通りエクストリームに全部実践してみたらどうなるのかという好奇心から来たものでした。

このため、自分が何かを成し遂げたのだと思うことは全くなく、ただ偉大な先人たちがこれをやるとうまくいくよと伝えてくれたプラクティスを実践しているだけだという気持ちで望んでいただけでした。その後しばらくはシニアエンジニアが自分の身に余る肩書きだ、何をすればこの肩書きに見合ったエンジニアになれるのかと葛藤しながら仕事を進めていくことになりますが、それはまた別の話です。

とにもかくにも、このように肩書をつけてもらって評価していただいたことには感謝しています。これも良いメンバーに恵まれたプロジェクトだったからだと今でも思っています。

このPJに関わったエンジニア全員が活躍している

実は2024年の末に社内の人事が発表されました。その異動表を見て私は驚きました。なんと、このアドベントカレンダーで紹介してきたプロジェクトで一緒に開発していたエンジニアのうち2人が昇進して役職をもらったのです。

このプロジェクトでは合計5名のエンジニアが参加していました。残りの2人のうち、1人は今年の四半期のMVPを獲得していました。もう1人は業務委託の方で、その方が所属している会社の都合で去年退職されていました。しかし、正社員として残っているエンジニアは漏れなく社内で活躍しているのです。

さらに当時のプロダクトオーナーは、他のプロダクトオーナーのマネージャーに昇進しました(社内ではPdMのマネージャー)。エンジニアリングマネージャーもこのプロジェクトをきっかけに、当時社内でまだあまり実践されていなかったスクラム開発に成功したマネージャーとして一目置かれています。

ソフトウェア開発においては、まずはソフトウェアを使ってくれる顧客の喜びが一番です。開発プロジェクトの成功はそれに左右されると言っても過言ではありません。しかし、作る側も社内で評価されるということはなんとも嬉しいことではありませんか。

自分はプロジェクトで関わった人が幸せになったらいいなと思いながら仕事をしています。このプロジェクトが終わってから時間が経っているため、あの時の影響はどれだけ残っているかわかりません。また本人が頑張っているということが一番大きな理由ではあります。ただ、あの時一緒に働いた人たちが活躍していること、その活躍が人に認められていることほど嬉しいことはありません。良い開発は良い結果につながるのだと改めて実感しました。

今回はプロジェクトのその後を振り返りました。次は今の自分がチーム作りで意識していることを紹介して、このアドベントカレンダーのまとめとします。

advent calendaragile
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

この記事は私をシニアエンジニアにしてくれた「真のアジャイル開発」体験記の23日目の記事です。このアドベントカレンダーは「ある機能開発チームでスクラム, XP, DevOps を一度に実践したら真のアジャイル開発ができた」という内容です。執筆者は全てプログラミングをするパンダです。

開発の指標とその計測方法

本記事では2022年4月から6月末までに自分たちのチームで計測した開発の指標とその集計方法を紹介します。

なぜ計測するのか: 「推測より計測」

パフォーマンス改善の金言に「推測より計測」という言葉があります。これは「何となくここが遅いという理由で、当てずっぽうにチューニングをするのは良くない。計測をしてボトルネックを特定し、そのボトルネック改善にリソースを投下せよ」という意味です。

ソフトウェア開発のパフォーマンス向上も同様です。なんとなくこれが遅い原因だろうと推測するのではいけません。私たちにはLeanとDevOpsの科学という書籍から得た知見があります。Four Keysを改善すればチームのパフォーマンスが上がるのです。

何を計測するのか: Four Keys

今回チームで作成した機能は新機能だったため、リリース時にはまだ使われていませんでした。このため、DevOpsのFour Keys のうち変更のリードタイムとデプロイ頻度を計測の対象としました。

変更のリードタイムについては、featureブランチの全プルリクエストのファーストコミットの日時とそれをマージした日時を取得します。このデータを元に最大、最小、平均日数などを計算して分析材料とします。

デプロイ頻度はdevelopmentブランチのプルリクエストをmasterブランチにマージした日時を取得して計算しました。

どのように計測したか: GitHub API を活用

データ取得のためにGraphQLのGitHub APIを使いました。データの対象期間は2022年4月から6月です。

クエリを書くに当たり、同じようなことをされている方のブログGraphQL公式ドキュメントGitHubのリファレンスを参照しました。

発行したクエリは以下です。このクエリは、あるレポジトリで指定されたラベルが付与されたプルリクエストのタイトル、最初のコミットの日時、プルリクエストがマージされた時間、コメント数を取得するものです。これは2024年現在でもGitHubのGraphQL Explorerから利用できる有効なクエリです。

query ($owner: String!, $repo: String!, $prCount: Int!, $label: String!) {
  repository(owner: $owner, name: $repo) {
    name
    pullRequests(
      first: $prCount
      labels: [$label]
      states: [MERGED]
      orderBy: {direction: ASC, field: UPDATED_AT}
    ) {
      nodes {
        ... on PullRequest {
          title
          mergedAt
          commits(first: 1) {
            nodes {
              commit {
                committedDate
              }
            }
          }
          comments {
            totalCount
          }
        }
      }
    }
  }
}

クエリの変数(Query Variables)は以下です。prCount(プルリクエストの個数)は自由に書き換えてください。

{ "owner": "xxx", "repo": "some-repository", "label": "some-label", "prCount": 10 }

このクエリを実行すると以下のような結果が返ってきます。

{
  "data": {
    "repository": {
      "name": "some-repository",
      "pullRequests": {
        "nodes": [
          {
            "title": "PR title 1",
            "mergedAt": "2022-04-07T05:09:23Z",
            "commits": {
              "nodes": [
                {
                  "commit": {
                    "committedDate": "2022-04-05T05:59:08Z"
                  }
                }
              ]
            },
            "comments": {
              "totalCount": 0
            }
          },
          {
            "title": "PR title 2",
            "mergedAt": "2022-04-08T06:27:30Z",
            "commits": {
              "nodes": [
                {
                  "commit": {
                    "committedDate": "2022-04-06T13:52:29Z"
                  }
                }
              ]
            },
            "comments": {
              "totalCount": 0
            }
          },
          {
            "title": "PR title 3",
            "mergedAt": "2022-04-08T06:31:28Z",
            "commits": {
              "nodes": [
                {
                  "commit": {
                    "committedDate": "2022-04-05T14:17:21Z"
                  }
                }
              ]
            },
            "comments": {
              "totalCount": 1
            }
          }
        ]
      }
    }
  }
}

指標を可視化する

データが揃ったので可視化をします。JSONデータを整形するスクリプトを書いてCSVファイルに出力し、Google SpreadSheetに読み込ませてグラフを作成しました。

集計した項目には以下のようなものがあります。

  • 1日のデプロイ回数(デプロイの頻度)
  • ファーストコミットからdevマージまでの平均時間(変更のリードタイム)
  • 1日のデプロイ平均回数
  • PRごとのコメント数平均

スプレッドシート1

変更のリードタイムは以下の表を元にグラフを作りました。土日などの休日の日数を引くために元データをさらに加工して計算しました。

スプレッドシート2

1日のデプロイ回数はシンプルなデータから取得できました。

スプレッドシート3

これらを元に作成したのが以下のグラフです。これらのグラフの分析は2日目の記事で紹介しているためここでは省略します。

グラフ1

グラフ2

グラフ3

グラフ4

1日に1度はデプロイしようという目標も開発の中盤と後半でほぼ達成できました。

デプロイ頻度

これらのデータとグラフを社内で共有すると好評を得られ、マネージャーがこの次の開発から毎スプリントごとに指標を自動集計してスプレッドシートに反映させるスクリプトを作成していました。チームを超えて後につながるトライができたこと、指標が改善していくのとチームメンバーが自分たちの開発スタイルに自信を持ち始めるタイミングが重なっていたことから、今後も計測する価値のある指標だと実感しました。

次回は成功だけじゃない自分たちの開発の後日談を紹介します。

advent calendaragile
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

この記事は私をシニアエンジニアにしてくれた「真のアジャイル開発」体験記の22日目の記事です。このアドベントカレンダーは「ある機能開発チームでスクラム, XP, DevOps を一度に実践したら真のアジャイル開発ができた」という内容です。執筆者は全てプログラミングをするパンダです。

技術に関するケイパビリティ

本記事ではXPの継続的デリバリの促進効果が高いケイパビリティの定義と自分たちのプロジェクトでの運用を紹介します。なおこの単語は「LeanとDevOpsの科学」の書籍に登場するもので、2024年現在では技術に関するケイパビリティと呼ばれています。しかし、2022年当時の自分たちのプロジェクトを鑑みて本記事では書籍の名称をそのまま使用します。

継続的デリバリの促進効果が高いケイパビリティの定義

継続的デリバリ(CD)を促すケイパビリティには、以下の8つが挙げられています。

  1. 本番環境の全ての成果物をバージョン管理システムで管理 アプリケーションコードやビルドスクリプトなど、成果物をGitなどのバージョン管理システムで一元管理することを指します。

  2. デプロイメントのプロセスの自動化 デプロイを手作業で行わず、完全に自動化することで効率化を図ります。

  3. 継続的インテグレーション(CI)の実装 コードに変更があるたびに、自動テストを実行し不具合を検知したり、コードを自動ビルド・デプロイする仕組みを整えることです。

  4. トランクベースの開発手法の実践 アクティブなブランチを3つ以下に制限し、ブランチの寿命を短くする(1日未満など)などのプラクティスを含む、効率的な開発手法です。

  5. テストの自動化 テストを手動ではなく自動で実施し、不具合を事前に検知する仕組みを導入します。

  6. テストデータの管理 「必要なテストデータをすぐに入手できる」「テストデータの不足でテスト量が制限されない」状態を維持します。

  7. 情報セキュリティのシフトレフト 開発初期からセキュリティを考慮し、設計やデモ段階でセキュリティチームにレビューや参加を依頼する取り組みです。

  8. 継続的デリバリ(CD)の実践 ソフトウェアをライフサイクル全体で常にデプロイ可能な状態に維持することを目指します。

継続的デリバリの促進効果が高いケイパビリティの運用

これらの中で、4の「トランクベースの開発」と7の「情報セキュリティのシフトレフト」について掘り下げます。他の項目については、自分のいる組織で既に実践済みであったり、6のように試行錯誤している段階なのでここでは割愛します。

「トランクベースの開発」の運用

トランクベースの開発は複数のプラクティスの集合体です。主なプラクティスには以下のものがあります。

  • バッチ単位の小さいタスクで開発する
  • チーム内でアクティブなブランチを3つ以下にする
  • コードフリーズをなくす
  • 1日に1回はマージする
  • コードの同期レビューを行う
  • 自動テストを実施する
  • 迅速なビルドを行う

これらのプラクティスについては、DORAのTrunk-based developmentに詳しく解説されています。

特に「バッチ単位のタスク」はスクラムのプロダクトバックログリファインメント編で、自動テストや同期レビューについてはXPの「継続的インテグレーション」「同期レビュー」編で自分たちのチームの取り組みを紹介しています。

自分たちのチームではペアプロを採用していたため、常時2~3組が並行して開発を行い、アクティブなブランチを3以下に制限できていました。コードフリーズについてはゴールデンウィークの間しかなかったので開発に全く支障は出ませんでした。。

ただし、厳密にはトランクベース開発の定義とは異なる点もありました。例えば、ブランチ運用においてはmaster、development、featureの3種類(状況によってはhotfixやrefactorも追加)を使い分けていました。このため、1日最低1回のdevelopmentブランチへのfeatureブランチのマージは心掛けており、5月と6月は2営業日を除いて全ての営業日でマージとリリースを達成しました。また、PHPを使って開発していたため、ビルドそのものが必要ないという背景もありました。この点はトランクベース開発そのものとは異なる運用の仕方です。

チームメンバーはトランクベース開発を好意的に受け止めていました。特に同期レビューや頻繁なマージによってコードのコンフリクトがほとんど発生しなかったことをプラスに感じたそうです。「開発がスムーズに進み、チーム全体でストレスが少なかった」と話すメンバーも多く、この取り組みの効果を実感しています。

8.「情報セキュリティのシフトレフト」の運用

情報セキュリティのシフトレフトについてはスクラムのスプリントレビュー編で解説した通りです。自分たちのチームでは、2週間ごとのスプリントレビューにセキュリティチームを招き、開発プロセスだけでなくビジネス側の業務フローについてもチェックしてもらいました。その結果、開発段階でセキュリティチームからのアドバイスをプロダクトに反映することができました。

特に有効だったのはリリース直前に実施したインシデント対応の予行演習です。セキュリティチームが関与してくれたおかげで、リリース後に問題が発覚して後から修正するという事態を防げたことは大きな成果です。また、こうした取り組みを通じてチーム内に「心配なことがあればまずセキュリティチームに相談する」という雰囲気が出来上がったことも非常に良かったと感じています。

このように、トランクベース開発と情報セキュリティのシフトレフトは、継続的デリバリを促進するうえで非常に重要なケイパビリティであり、自分たちのチームにおいても多くの恩恵が得られました。これらの取り組みを通して、リリースのスピードと品質の向上を同時に実現できていると感じています。

次回は、ここまでスクラム、XP、DevOpsのプラクティスを実施した自分たちのチームで、開発の生産性が実際どのようなものだったかをデータで紹介します。

advent calendaragile
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer