「開発生産性の教科書」という本を執筆しました

こんにちは!ファインディ CTOの佐藤(@ma3tk)です。

表題の通り、約1年半ほどの期間をかけて「エンジニア組織を強くする 開発生産性の教科書 ~事例から学ぶ、生産性向上への取り組み方~」(以降、開発生産性の教科書)という本を執筆しました。

本日(2024年7月11日)発売となりましたので、改めて「開発生産性」に対する思いをお伝えしたり、本の内容の一部をご紹介したいと思います。

「開発生産性の教科書」のご紹介

エンジニア組織を強くする 開発生産性の教科書

本の概要は次のとおりです。

項目 詳細
タイトル エンジニア組織を強くする 開発生産性の教科書 ~事例から学ぶ、生産性向上への取り組み方~
著者 佐藤 将高、Findy Inc.
発行 技術評論社
定価 2,860円(税込)
発売日 2024年7月11日
ISBN 978-4297142490
購入 Amazon / 楽天ブックス
全国書店、その他オンライン書店
電子版 Gihyo Digital Publishing / Amazon / 楽天ブックス

全国書店にてお買い求めいただけますので、是非チェックしてみてください。

そもそもなぜ本を執筆しようとしたのか

元を辿ると、2019年末頃からFindy Team+(当時はFindy Teams)というサービスを僕が開発し始めた時に遡ります。

「エンジニア組織で困っていることってなんですか?」

この問いをおおよそ20〜30社のCTOやVPoE、開発部長やEMの皆様とお話してきました。

エンジニア組織課題を深堀りさせていただく中で、よりよい組織を作っていきたいという思いを受け取りました。少子高齢化や開発内製化の動きを受けエンジニア採用が難しくなる中で、在籍しているメンバーの技術力を向上し、組織の開発生産性を向上させることに興味関心をいただきました。

  • 「何をどうしたら組織が良くなるのかがわからない」
  • 「生産性を上げる必要があるが、初手に困っている」

お伺いする中で、開発生産性の向上に直結する打ち手で迷うことが多いと学びました。ファインディでも開発生産性の向上をどう実現するか、Findy Team+のサービス利用者の悩みを解決するお手伝いをさせていただいたく上でどう価値提供ができるかを模索し続けてきました。

「開発生産性」の理解が難しいように感じてしまう

サービスを通じた価値提供を続ける中で自分自身が感じたことは、「開発生産性という言葉は多義語で、どう定義するといいのかわからない」ということでした。

開発生産性についてずっと調べ続けていくと、Four KeysやSPACEといった概念が出てきて混乱したり、「数値化して本当にエンジニアにとって嬉しいのか?」というような疑問を自分自身でも考えるようになりました。今となっては理解が進みましたが、初学のタイミングでは整理に時間を要しました。同じように難しく感じてしまう方もきっといらっしゃると思います。

開発生産性についてどこかで学んだことがある方は多くはないはずです。1年ほど前からエンジニアの方を始め、プロダクト開発に関わる様々な方・経営者の方に開発生産性に関する知見を共有するためには本を書くことで「開発生産性に対しての理解を向上する」ことが実現できそうであると思い立ちました。

今回の本では、「開発生産性」という言葉の難しさを少しでも紐解き、どんな組織でも取り組みやすい入門書として出版するに至りました。

「開発生産性を上げるために大事なこと」を多くの人に知ってもらいたい

Findy Team+のサービスをリリースする中で、「監視されるのではないか?」「数値を可視化するリスクがあるのではないか?」というご意見が特に多いです。

本書によって開発生産性を向上させるための認識を統一し、誤解を減らしたいと思っております。本書の中にも「監視するのではなく、認識を揃える」という内容も記述しています。

組織の定量化にフォーカスが当たりがちですが、開発生産性を上げて顧客に価値を多く提供することや、もっと言えば売上やKPI向上へつながるような開発に集中できることが大事だと思っています。そのため、定量化そのものよりも定量化して過去と比較することで「どこに課題がありそうか?」を知るための道しるべのようなものかと思っています。

開発生産性指標を向上させるためにやってはいけないアンチパターン - Findy Tech Blog の記事にも書いているように、数値に影響しないようにハックをすることではなく、現在のプロダクト開発においてどこがボトルネックかの目星をつけ、定量的にすることで他者との認識を揃えることが本質だと思っています。

数値だけを追ってしまうと監視につながってしまいかねませんし、数値に現れにくい「社内のメンバーの積極的なヘルプ」や「積極的なミーティングのファシリテーション」などを誰もやらなくなってしまいます。これでは本末転倒ですね…!

こういった誤解や誤用があるため、本書によってそれを少しでも減らしたいと考えています。そして、これまで以上に事業貢献につながる開発ができる世の中で溢れてほしいと考えています。

本書の特徴:入門から実践まで網羅している教科書であること

改めて、本書の特徴としては大きく3つあります。

1. 体系的に整理された開発生産性の知識が学べる

前述したようにFindy Team+の開発を通じて、プロダクトオーナーとして「開発生産性」について色々調べてきました。しかし、「開発生産性」について体系的にまとまっているものがあまりなかったため、改めて自分の理解度向上も兼ねてまとめなおしました。

2. 実践への第一歩として、始めやすさにフォーカスした

海外の訳書などはアカデミックに研究が重ねられており、非常に良質な本があります。特に、LeanとDevOpsの科学 は「開発生産性」について学ぶうえで是非読んでほしい一冊です。一方でどう始めるとよいのかについて自分はもっと情報が欲しかったこともあり、「開発生産性の教科書」は取っ付きやすさを大事にし、具体的にどうするといいのかを私自身の実体験や企業事例を交えながらなるべく読みやすい言葉遣いで記載しています。是非どちらも読んでいただくと「開発生産性」に対する理解が深まると思います。

3. 成功へのヒントとして事例を5社集めまとめた

また、ファインディ社を含め5社の事例を集めました。

  • BuySell Technologies社:生産性の高さをIR資料に掲載し、社内外での評価を高めたことで、エンジニア採用にも好影響を与え、候補者からの関心を引いたお話
  • ツクルバ社:開発生産性を向上させるための基盤を作ったことで、iOSアーキテクチャの改修プロジェクトを成功させ、変更行数の削減やサイクルタイムの短縮を実現したお話
  • クラスメソッド社:開発生産性を向上させるための予算獲得や施策実行をしたことで、エンジニア組織全体のモチベーションも向上し、エンジニアリングの効率化と品質向上を実現したお話
  • ワンキャリア社:SPACEフレームワークの実現や毎日のリリース体制への移行を進めたお話
  • ファインディ社:CI時間の短縮やテストコードの拡充によるサービスの安定性と開発フローの改善の話

具体的にどう進めるとどんな効果があるかについても読める一冊に仕上げております!

また、執筆にご協力いただいた各社のご担当者様に、心より感謝申し上げます。

開発生産性の向上とこれから

本記事では開発生産性の教科書の一部をご紹介させていただきました。

開発生産性を向上することで、個人の市場価値も上げられたり、組織のKPI/売上がよりよいものになるだけではなく、組織そのものの雰囲気がよくなることにもつながってくるはずです。あくまでも数値は健康指標として扱いながら、課題を見つけ出し解決に向けて進めることが大事だと思っています。

ファインディは「挑戦するエンジニアのプラットフォームをつくる」というビジョンの元、様々なエンジニアの皆さまやエンジニア組織においてより前向きに挑戦できる環境を一緒に作れたらと思っています。

改めて、今回の本を執筆するにあたりご協力いただいた方・編集の皆様、ありがとうございました。「開発生産性の教科書」が皆様の挑戦に役立てられたら嬉しいです。是非興味を持っていただけたらお手元にとっていただけると幸いです。

購入は以下からどうぞ!

また、こういったイベントもありますのでよかったらご参加どうぞ〜!

d-plus.connpass.com

Findyのフロントエンドにおけるメッセージ画面のパフォーマンス改善

こんにちは、ファインディ株式会社でフロントエンドのリードをしております 新福(@puku0x)です。 この記事では、IT/Webエンジニアの転職・求人サイト Findyのメッセージ画面の改善についてご紹介します。

メッセージ画面の課題

Findyのメッセージ画面は数年前にデザインを刷新しました。

現在では、3種類のメッセージを表示するようになっています。

  • 通常のマッチングのメッセージ
  • プレミアムスカウトのメッセージ
  • Findyからのメッセージ

機能拡充される一方で「読み込みが多い」「画面遷移がサクサク動くようにして欲しい」との声をいただく機会が増えていきました。

実際に触ってみると確かに読み込み画面が頻繁に表示され、画面遷移に時間がかかっていることがわかりました。

メッセージ画面の改善

Apollo Clientキャッシュの利用

最初に取り組んだのはキャッシュの利用です。

Findyのフロントエンドには既にApollo Clientが組み込まれていたので、データ取得の際に fetchPolicy: 'cache-and-network' を指定して体感速度の向上を狙いました。

www.apollographql.com

この改修により、ページ切替時の読み込み時間(ローカル環境)は約1,200ms→約900msと高速化されました。

読み込み画面の条件修正

当時の実装では、キャッシュの有無に関係なく読み込み画面を表示するようになっていました。

新しい実装では、読み込み中かつキャッシュが無い場合のみに修正しています。

const { data, loading } = useQuery(..., { fetchPolicy: 'cache-and-network' });

<Loading isLoading={loading && !data} />

この改修では、ページ切替時の読み込み時間への直接的な影響はありませんが、体感時間は最大で300ms(読み込み画面のアニメーションの再生時間)ほど削減されていると思われます。

ページ設計の最適化

当時の実装では、前述した3種類のページを個別のファイルで実装し、内部にメッセージの一覧と詳細コンポーネントを持つという構成でした。

このため、ページ遷移の度に一覧と詳細コンポーネントの両方が再描画され、体感速度に課題を残していました。

解決にはページ設計の見直しが必要ですが、LayoutsはFindyのフロントエンドがまだApp Routerに移行できていないため利用できず、getLayoutパターンも画面全体の再描画を防ぐことが難しいため見送りとなりました。

手詰まりかと思われましたが、他のページで採用されたCatch-all Segmentsの実装を参考に、Optional Catch-all Segmentsを使った実装が今回の要件に合致することがわかったため採用することにしました。

nextjs.org

新しい実装では、[[...keys]]/index.page.tsx に3種類のページ実装が集約され、ページ遷移の際はメッセージ詳細コンポーネントのみが差し代わるようになっています。

この改修により、メッセージ一覧の不要な読み込みが削減されたため、ページ切替時の読み込み時間(ローカル環境)はメッセージ詳細のキャッシュが無い場合でも約500ms、キャッシュがある場合は読み込み時間をほとんど感じさせないほど大幅に高速化されました。

まとめ

キャッシュの活用やページ設計の見直しにより、メッセージ画面のパフォーマンスを向上させることに成功しました。

改善できて嬉しい一方、Optional Catch-all Segments自体はPages Routerの基本的な機能でもあるので「よく調べてから実装していれば...」と反省する点もあります。

ドキュメントはちゃんと読みましょう(自戒)😇

メッセージ画面以外についてもパフォーマンス改善を進めて参りますので、今後もよろしくお願いします。

今回は採用を見送りましたが、App Router移行やReact 19の新機能を活用して更に快適に動作するフロントエンドが実現できればと考えております。

ご興味のある方・挑戦してみたい方はぜひ↓のリンクからご応募ください。私達と一緒にFindyをより良いサービスにしませんか?


ファインディは積極的にエンジニアを採用しています。CI/CD を始め、Four Keys、開発生産性、技術トレンド、転職市場など興味のある方は、お気軽にカジュアル面談を受けてみてください。

ファインディの採用情報はこちら ↓

herp.careers

FindyにおけるDataformの活用とその効果

こんにちは。 2024/05よりファインディ株式会社にデータエンジニアとして入社した田頭(tagasyksk)です。本記事では、データ変換サービスであるDataformについてその活用方法や導入後の効果についてご紹介します。

弊社では、現在次のような構成でデータ基盤を構成しており、BigQuery内でのデータ変換にDataformを利用しています。

この構成を踏まえてご覧いただければ幸いです。それでは見ていきましょう!

Dataformについて

サービスの説明については、公式ドキュメントを引用します。

Dataform は、データ アナリストが BigQuery でデータ変換を行う複雑な SQL ワークフローを開発、テスト、バージョン管理、スケジュール設定するためのサービスです。

例えば、

  • BigQueryでスケジュール実行しているSQLをGit管理したい
  • 依存関係のあるSQLを順番に実行してほしい
  • クエリの実行結果が意図した挙動を行うかどうかをテストしたい

というようなタイミングで活用できます。

導入の背景

Dataform導入前の課題点として、SpreadsheetやGASから利用されているクエリを管理できていませんでした。その結果、利用者が想定しているデータと実際にクエリで取得しているデータが異なっていたり、正しくないテーブルを参照しているケースが発生していました。

このような背景のもと、次のメリットを踏まえてDataformを導入しました。

データ基盤に必要な機能が揃っており、簡単に運用を始められること

Dataformは無料でありながら、次のようなデータ基盤に必要な機能が揃っています。

  • 依存関係を定義してDAGを構成できる
  • 特定のタグが付与されたテーブルでワークフローを構成できる
  • データリネージを可視化できる
  • Gitと連携してクエリ管理ができる
  • Google CloudのIAMでカラム単位での権限制御ができる

Google Cloudのマネージドサービスなので、余計なメンテナンスをする必要もありません。

クエリ作成のハードルが非常に低いこと

ソフトウェア開発の経験がないアナリストやBizサイドにとって、データ基盤への学習コストが低いことは非常に重要です。

Dataformでは.sqlxというSQLを拡張したファイル形式でクエリを開発します。

config {
    type: "table",
    "schema": "stg_hoge",
    "tags": [
        "stg_hoge"
    ],
    "description": "ユーザー",
    "columns": {
        "id": "",
        "user_name": "ユーザー名",
        "created_at": "作成日",
        "updated_at": "更新日",
    },
    "assertions": {
        "nonNull": [
            "id",
            "user_name",
            "created_at"
        ],
        "uniqueKey": [
            "id"
        ]
    }
}

SELECT
    id,
    user_name,
    created_at,
    updated_at
FROM
    ${ref("lake_hoge", "users")}

sqlxは普通のSQLとdescriptionやassertionを含む簡単なconfigで作成可能なので、学習コストが非常に低いです。

Gitを用いたクエリ管理に関しても、Dataform側がバックエンドで行ってくれます。ブラウザ内でクエリ開発からPR作成までが完結するため、Gitのコマンドを覚える必要がありません。

導入後の効果

Dataform導入によって、Bizサイドが作成したクエリをアナリスト・データエンジニアが適切にレビューできるようになりました。 mart層のモデルの数もDataform導入後約4倍となり、開発が活発になっていることが分かります。

また、社内メンバーにDataformの利点について聞いてみたところ、次のような声も聞きました。

  • 1モデルあたり1ファイルの作成で済むので、開発の負担が減った
  • 実行ログをDataformコンソール上で確認できるので、BigQueryと実行ログの横断が楽になった

FindyでのDataform運用

ここからはDataformを運用していく上でどのような課題が生じ、どのように解決しているかを詳しく書いていきます。

導入しての課題

Dataform導入当初は、次のようなフローでクエリを開発していました。

データ基盤を運用していく中で、次の課題が生じました。

  • Production環境にあるテーブルに対してワークスペースからモデルを実行できてしまう

    Dataformのコンソール画面上から、まだレビューしていないモデルを実行できてしまうのは、意図しないスキーマの変換やデータ変換のバッティングが起きてしまいます。データエンジニア側としても望ましい状態ではありませんでした。

  • 新しいワークフロー用のタグの追加忘れが頻発した

    Dataformではsqlx内に記述するタグを指定してワークフローを構成します。ワークフローの管理はTerraformで行っていたため、Dataform側のPRからはタグが追加されているか分からず、データ更新時にタグの追加忘れが発覚することがありました。

  • テーブル変更時、影響範囲が不透明でレビューしづらい

    クエリを修正する際、修正したテーブルがどのテーブルから参照されているかを確認できていませんでした。計算ロジックの変更がどのテーブルに伝播するのか分からず、レビューが不十分なままマージされてしまうことがありました。

改善点

クエリ開発フローを次のように変更しました。

緊急対応以外の開発は全てdevelop環境で行なうように取り決め、リリース作業を導入しました。また、CIパイプラインの中で次のような項目を自動でチェックし、事故を防止しています。

  • 変更されたsqlxファイルを検出し、クエリとAssertionをDevelop環境で自動実行
  • 変更モデルに依存しているテーブルを検出し、PR上に表示

    dataform compileで取得してきたjsonをパースし、変更ファイルのテーブルを検索することで検出しています。

      - name: Detect Referenced Table
        run: |
          DIFF_FILES=$(echo "${{ steps.get_change_files.outputs.diff_files }}")
          echo "referenced_tables<<EOF" >> $GITHUB_OUTPUT
          for file in $DIFF_FILES; do
            dataset=$(jq -r ".tables[] | select(.fileName == \"$file\") | .canonicalTarget.schema" definition.json)
            table=$(jq -r ".tables[] | select(.fileName == \"$file\") | .canonicalTarget.name" definition.json)
            referenced_tables=$(cat definition.json | jq -r ".tables[] | select(.dependencyTargets[]?.schema? == \"$dataset\" and .dependencyTargets[]?.name? == \"$table\") | .canonicalTarget | .schema+\".\"+.name" | awk '!a[$0]++')
            echo "**$file**" >> $GITHUB_OUTPUT
            echo "$referenced_tables" >> $GITHUB_OUTPUT
          done
          echo "EOF" >> $GITHUB_OUTPUT
  • 開発に利用したワークスペースを自動で削除する
  • Dataform CLIのdry-runでProductionの依存テーブルに問題が無いか確認1

  • ワークフローで付与されているタグとsqlxファイル上で付与されているタグを比較し、ワークフローに無いタグをリリースPRで表示

    ワークフロー構成で利用されているタグは、Google Cloud側から提供されているAPIで取得できます。

    cloud.google.com

    APIで取得したタグとsqlxに付与されているタグを突合し、ワークフローに追加し忘れたタグが無いか確認しています。追加忘れが見つかった場合は、Terraformで管理しているリリース構成に新しいタグを追加して対応しています。

以上のような改善活動の結果、データの更新漏れやエラーが減りました。アラート対応が減ったことで、データエンジニアが他の業務に時間を割けるようになりました。 リリース導入前後でアラート数を集計してみたのですが、導入前1ヶ月のワークフローで生じたエラーは54件なのに対し、リリース導入後1ヶ月では25件と半分以下になっていました。

今後の展望

データの品質向上

クエリのレビュー体制を整えることができましたが、クエリやデータの品質にはまだ課題を残しています。

GASやスプレッドシート内で発行されていたクエリをDataformの管理下に置くことはできましたが、DRYの原則に反していたり、可読性に課題のあるクエリがまだ存在しています。

データ品質については、異常なデータを検知してアラートを出す仕組みが整える必要があります。Dataformではassertionやtestといった形式でテストを書くことができますが、現状はデフォルトでサポートされているassertion(nullチェックやuniqueKeyなど)を組み込んでいる程度です。

これからも社員が増えていくことが予想される中で、データ品質をどうやって維持・向上していくかはこれからも模索していきたいです。

データモデリング

データ利用者がスムーズに分析を進めていくために、データモデリングは必要不可欠です。しかし、弊社ではモデリングに精通した人材がいないのが現状です。

5月からデータエンジニアとアナリストで定期的に集まってデータモデリングの勉強会を行い、チーム内のナレッジを揃えながら少しずつクエリの見直しを進めています。

※一緒にデータモデリングをしていきたい方がいましたら、是非カジュアル面談にご応募ください!データモデリングを一緒にやっていきましょう!

終わりに

いかがでしたでしょうか?今回は弊社でのDataformの活用について書きました。

弊社ではデータ基盤を共に育てていくメンバーを募集しています。少しでも興味が湧いた方はカジュアル面談お待ちしております!

herp.careers

herp.careers


  1. Dataform CLIがver3.0未満だとdry-runで検証できるのはDataform内での依存関係やテーブルの有無のみで、declarationで宣言したデータソースがBigQuery上に存在しない場合などでエラーが起きません。(最近のリリースで出来るようになったみたいですが、Dataform CLIのver3.0以上はbreaking changeが多いのでまだ試せていません...)現状は、データソースを参照するようなクエリはリリース時に目視チェックすることでカバーしています。

デベロッパーエクスペリエンス(DevEx)とは: 開発生産性Conference 2024 に向けて

こんにちは! ファインディの @Taka-bow です。

まもなく「開発生産性Conference 2024」が開催されます。2日目のキーノートスピーカーであるNicole Forsgren博士は、昨年はビデオ越しのご登壇でしたが、今回は来日してくださる予定です。

昨年は「SPACE:生産性フレームワーク」の研究についてご紹介いただきましたが、今回はどのようなお話を伺えるのでしょうか?

ご講演のタイトルは

Mastering Developer Experience: A Roadmap to Success
(デベロッパーエクスペリエンスを極める:成功へのロードマップ)

とのこと。大変楽しみです。

dev-productivity-con.findy-code.io

博士は、書籍 "Accelerate" *1 の筆頭著者としても広く知られていますが、最近はデベロッパーエクスペリエンス(DevEx: Developer Experience)研究の第一人者としても注目されています。

そこで、そもそもデベロッパーエクスペリエンスとは何か?博士らの論文を引用しながら紐解きたいと思います。

デベロッパーエクスペリエンスの科学的な研究

Nicole Forsgren 博士らがデベロッパーエクスペリエンス(以降、DevEx)に関して発表した論文はいくつかありますが、今回は最近の2つの論文にフォーカスします。

1つ目は、昨年5月 Association for Computing Machinery(ACM)で発表された

"DevEx: What Actually Drives Productivity: The developer-centric approach to measuring and improving productivity"
『DevEx: 何が実際に生産性を向上させるのか: 生産性を測定し改善するための開発者中心のアプローチ』

2つ目は、今年1月に発表された

"DevEx in Action: A study of its tangible impacts"
『実践的 DevEx:その具体的な影響に関する研究』

どちらの論文も、DevEx の「質」が、開発生産性に大な影響を与えているという仮説を、科学的に証明しようと試みています。

デベロッパーエクスペリエンスの3次元

DevEx とは、ソフトウェアエンジニア(ここで指す開発者)が日々経験している業務、言い換えれば「エンジニアの業務体験や環境」そのものを指しています。

そして、影響を与える要因を「DevExの3次元」フレームワークと呼びます。

DevExの3次元 参照: Noda, A., Storey, M. A., Forsgren, N., & Greiler, M. (2023). DevEx: What Actually Drives Productivity: The developer-centric approach to measuring and improving productivity. ACM Queue, Vol.21, No.2, p.35-53.

それぞれ、

  1. Feedback Loops (フィードバックループ)
  2. Cognitive Load (認知負荷)
  3. Flow State (フロー状態)

と言います。これを意識することで DevExの改善ステップがより具体的にできます。

Feedback Loops (フィードバックループ)

フィードバックループは、テスラのような自動運転の自動車をイメージすると分かりやすいでしょう。

自動運転車は、前方や左右の障害物、車間距離などを判断するために、センサーやカメラから得られる情報を高速で処理するフィードバックループが必要です。この処理が遅いと、障害物にぶつかってしまう可能性がありますよね。

Teslaのコクピット Ian Maddox, CC BY-SA 4.0, via Wikimedia Commons

ソフトウェア開発組織は、デリバリーの遅延を減らしてアウトカムの流れを最適化しようとしています。遅延が減ることで、フィードバックと学習が迅速に行われ、速やかな軌道修正が可能となります。

研究によれば、頻繁なデプロイとリードタイムの短縮がパフォーマンス向上に繋がるとされています。迅速なフィードバックループは、開発者が障害なく作業を進めるのに役立ち、逆に遅いフィードバックループは中断や遅延を引き起こし、開発者のフラストレーションを増します。

フィードバックループを高速にするための改善案として、次の点が挙げられます。

  1. 開発ツールの高速化:ビルドやテストの時間を短縮。
  2. 人の引き継ぎプロセスの改善:コードレビューや承認の迅速化。
  3. 組織構造の最適化:チーム間の相互作用を合理化し、遅延を減らす。

これらの改善により、ソフトウェア開発の効率と生産性を向上させることができます。

Cognitive Load (認知負荷)

2011年に旧Twitterで話題になった案内板がありました。

その案内板がこちら

新宿エルタワーの案内板
参照: https://note.openvista.jp/2011/redesigning-shinjuku-building-sign, CC BY-SA 4.0

さて、トイレに行くにはどちらに進めば良いのでしょう?前?右?左?と混乱する人が大勢いたそうです。

(正解は左)

少なくとも、ほとんどの人がぱっと見て分からない状態は「認知負荷が高い」と言えるでしょうね。

余談ですが、この案内板はその後認知科学の分野で研究もされ発表されました。

2016年度 日本認知科学会 第33回大会【矢印を用いた「組み合わされた方向サイン」のわかりやすさ: 構造,アイコン,加齢の効果】:研究リンク

ソフトウェア開発は複雑であり、ツールや技術が増えることで開発者の認知負荷が増加しています。認知負荷とは、タスクを実行するために必要な精神的な処理量を指します。

難しいタスクや新しいフレームワークの理解に取り組む場合、この負荷は特に高まります。また、情報の提示方法や精神的な処理が必要な場合も負荷は増加します。

高い認知負荷は、開発者が顧客に価値を提供する妨げとなります。不十分なドキュメントや複雑なシステムにより、開発者はタスク完了に多くの時間と労力を費やすことになります。

認知負荷を軽減するには、次の改善案が挙げられます。

  1. 不要な障害の排除:開発プロセスから不必要な障害を取り除く。
  2. 整理されたコードとドキュメント:理解しやすいシステムを構築し、コンテキストやタスクの切り替えを減らす。
  3. 専任のDevExおよびプラットフォームチーム:使いやすいセルフサービスツールを提供し、開発とリリース手順を合理化する。

これにより、開発者の認知負荷を軽減し、効率的に価値を提供できる環境を整えることができます。

Flow State (フロー状態)

弓道における基本的な射法の8つの動作を射法八節(しゃほうはっせつ)と言います。

  1. 足踏み(あしぶみ)
  2. 胴造り(どうづくり)
  3. 弓構え(ゆがまえ)
  4. 打起し(うちおこし)
  5. 引分け(ひきわけ)
  6. 会(かい)
  7. 離れ(はなれ)
  8. 残心(ざんしん)

この中でも、6番目の「会」は、他が具体的な動作にも関わらず、これだけが少し違います。簡単解説!射法八節 によると、

引分けが完成し、矢を放つ機会を待つのが「会」です。丹田に力を入れ、自然な呼吸を心がけます。肩と肘の高さに注意しましょう。体全体のバランス、重心に気を配り、的をしっかりと見つめます。

実際の動作は5秒ほどキープする必要があるそうですが、これがまさにフロー状態だと思います。周囲の音や考え事などの雑念があれば「会」とはならず、矢は的を外れてしまうでしょう。

「会」の状態 Pierre-Yves Beaudouin / Wikimedia Commons

開発者は「フローに入る」や「ゾーンに入る」といった表現を使います。

これは、活動中に集中力と活力を持ち、完全に没頭するフロー状態を指します。この状態を頻繁に経験することは、生産性の向上、イノベーションの促進、従業員の成長に繋がります。研究によれば、仕事を楽しむ開発者はより高いパフォーマンスを発揮し、質の高い製品を生み出します。

フロー状態を妨げる主要な要因は、中断や遅延です。その他にも、自律性の欠如、目標の不明確さ、そして刺激的でないタスクが影響します。

フロー状態を作りやすくするためには

  1. 中断の最小化:会議をまとめて行い、計画外の作業を避け、支援依頼をまとめて処理する。
  2. 自律性の確保:開発者に自律性を与え、充実感のある挑戦的なタスクを提供する。
  3. 積極的なチーム文化の構築:リーダーはフロー状態を重視し、自律性とチャレンジの機会を提供する環境を作る。

これにより、開発者がフロー状態に入りやすくなり、生産性と製品の質を向上させることができます。

DevExの測定

「DevExの3次元」は、測定可能な領域を特定するためのフレームワークを提供します。開発者の認識やワークフローを捉えるだけでなく、全体的なKPI(主要業績指標)や「北極星指標」を含めることが重要です。表1では、包括的なKPI指標と3つの次元に沿った具体的な認識およびワークフロー測定の例を示しています。

表1

フィードバックループ 認知負荷 フロー状態

認識

(人間の態度と意見)

- 自動テストの速度と出力に対する満足度 - コードベースの複雑さの認識 - 集中して中断を避ける能力の認識
- ローカル変更を検証するのにかかる時間に対する満足度 - 本番システムのデバッグの容易さ - タスクやプロジェクト目標の明確さに対する満足度
- 本番に変更をデプロイするのにかかる時間に対する満足度 - ドキュメント理解の容易さ - オンコールの際の中断性の認識

ワークフロー

(システムとプロセスの行動)

- CI結果を生成するのにかかる時間 - 技術的質問に対する回答を得るのにかかる時間 - 会議や中断なしでブロックする時間の数
- コードレビューのターンアラウンドタイム - 変更をデプロイするために必要な手動ステップ - 計画外のタスクやリクエストの頻度
- デプロイリードタイム(変更を本番にリリースするまでの時間) - ドキュメントの改善頻度 - チームの注意を必要とするインシデントの頻度

KPI

(北極星指標)

- ソフトウェア提供の全体的な容易さの認識
- 従業員のエンゲージメントや満足度
- 認識された生産性

最新の調査結果

DX社によるDevExの横断的調査が行われました。この調査は、DX社の顧客の中から研究調査に協力した219人からの回答を基にしています。アンケートに回答した人のうち、170人(77.6%)がテクノロジーを主な事業とする企業の社員であり、200人(91.3%)が従業員数500人以上の企業(中堅または大企業のしきい値)に勤務していました。

以下に、調査・分析結果の重要なポイントを示します。

フロー状態

  • 深い作業に時間を割く開発者は、生産性が50%向上する。
  • 集中時間を確保し、中断を最小限に抑えることが重要。
  • 魅力的な仕事をしている開発者は、生産性が30%向上する。
  • タスク配分を再考し、燃え尽き症候群を防ぐことが必要。

認知的負荷

  • コードの理解度が高い開発者は、生産性が42%向上する。
  • コードを明確でシンプルにし、十分に文書化することが重要。
  • 直感的で使いやすいツールやプロセスは、革新性を50%向上させる。

フィードバックループ

  • コードレビューのターンアラウンドタイムが早いと、革新性(innovative)が20%向上する。
  • 緊密なフィードバックループは、技術的負債を50%減少させる。
  • 質問に迅速に回答することが、技術的負債を減らす鍵となる。

DevEx 改善のためにはどうやって組織に投資させるか

研究の結果、DevExの改善は個人、チーム、そして組織にとってプラスの結果を生み出すという証拠が明らかになりました。しかし、このデータを見ただけでは、組織が改善に向けて舵を切るとは限りません。

そこで、データドリブンかつ継続的な改善を始めるための5つのステップが提案されています。

  1. 現在の開発者体験のデータ収集
    開発者の体験を把握するためにデータを収集する。これには、アンケートや専用ツールを使用して現在の課題や改善点を明らかにすることが含まれる。

  2. データに基づいて目標設定
    収集したデータを元に、改善すべきポイントを特定し、目標を設定する。ビジネスの優先事項とも照らし合わせる。

  3. チームの成功をサポート
    設定した目標を達成するために、チームに必要な支援を提供する。目標を共有し、進捗を定期的に確認する。

  4. 進捗を共有し、投資を評価
    開発者や関係者に進捗を報告し、投資の効果を評価する。学んだことや驚いた点も共有し、改善策を調整する。

  5. プロセスを繰り返す
    再びデータを収集し、プロセスを見直して改善する。3~6ヶ月ごとにこのサイクルを繰り返す。

デベロッパーエクスペリエンス(DevEx)改善の重要性

日本では、デベロッパーエクスペリエンス(DevEx)の改善に関する研究はまだまだこれからだと言われています。しかし、これこそが今後のIT業界の競争力を大きく左右する重要な要素となるでしょう。

日本CTO協会が毎年実施している「開発者体験ブランド力」の調査は、すでに一部の企業にとって重要な指標となっていますが、今後はさらに実際のDevExに基づいたエビデンスデータを活用することが求められます。

例えば、アメリカやヨーロッパのIT企業では、DevExの改善が企業文化の一環として浸透しており、優秀な人材の確保や社員の満足度向上に直結しています。

論文 "DevEx: What Actually Drives Productivity" には実際のeBayとファイザーの実例が載っています これにより、企業のイノベーション能力や市場での競争力が劇的に向上しています。

日本のIT企業もこの潮流に乗り遅れることなく、DevExの重要性を認識し、積極的に改善に取り組む必要があります。

具体的な取り組みとしては、開発ツールやプロセスの改善、継続的なフィードバックループの確立、エンジニアの教育とスキルアップの支援などが挙げられます。これらの施策を通じて、開発者がストレスなく効率的に仕事を進められる環境を整えることができます。また、データに基づいた評価と改善を繰り返すことで、より具体的で効果的なDevExの向上が期待できます。

またチーム・トポロジーのイネーブリングチームとしてDevExチームを立ち上げてもよいかもしれません。

実際のDevExデータが調査されるようになれば、日本のエンジニアのDevExへの投資が見直されるかもしれません。

DevExの継続的改善サイクルが回り、結果として、企業全体の生産性や創造性も高まり、ひいては日本のIT業界全体の競争力が強化されるはずです。

経営者やマネジャーの皆様が率先してDevEx改善に取り組み、次世代のIT産業をリードする企業としての地位を確立するチャンスだと私は思います。

*1:Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations"(邦題「LeanとDevOpsの科学[Accelerate] テクノロジーの戦略的活用が組織変革を加速する」)

開発生産性指標を向上させるためにやってはいけないアンチパターン

こんにちは!ファインディでFindy Team+開発チームのEMをしている浜田です。

昨今、開発生産性を高めるための取り組みを行っている組織が増えてきていると感じています。 開発生産性を向上させるためには、まずは定量的に可視化することが重要です。 可視化することで現状を把握して、開発組織の伸びしろを発見したり、課題を明らかにし、改善活動に取り組みやすくなります。

一方、定量的な指標に焦点を当てすぎてしまい本質的ではない対応をしてしまい、指標は向上したものの実際の生産性は向上していなかったり、むしろ悪化してしまうこともあります。

この記事では、開発生産性指標を向上させるためにやってはいけないアンチパターンについて紹介します。

デプロイ頻度を向上させるために、デプロイプロセスは変更せずに実施回数を増やした

デプロイ頻度はDORAが提唱するDevOpsの4つの指標(Four Keys)の1つであり、開発生産性の高い開発組織はこの数値が高いとされています。 そのため、開発生産性を向上させようと考えたときに最初に取り組むことが多い指標です。

デプロイ頻度はただ向上させるだけであれば簡単に向上させることができます。 現状のデプロイプロセスのまま実施回数を増やせば良いのです。

デプロイする回数を増やすだけでデプロイ頻度が向上して開発生産性が向上することもありますが、ほとんどの場合はデプロイ頻度だけが向上するだけで開発生産性は向上しません。

高いデプロイ頻度は、CI/CDが自動化されていたり、小さなパッチ単位でコードベースに変更を入れることができていたり、テスト自動化により変更のリスクが低減されているなど、様々なプラクティスが実践できている結果として得られるものです。

これらの前提を満たさないままデプロイ頻度を向上させることは、変更障害率が上がって障害対応する時間が増加したり、デプロイをすること自体の手間で開発する時間が減ってしまうなど開発生産性を悪化させる可能性があります。

無理やりデプロイ頻度だけを増やすのではなく、前述したプラクティスを実践するとことで、無理なくデプロイ頻度を向上させていきましょう。

リードタイムを短縮するために、全て実装し終わってからコミットする

開発生産性を可視化する際、リードタイムを指標として設定することは有効です。 なお、リードタイムには様々な定義がありますが、今回はGit/GitHubを活用し、コミットしてからメインブランチにマージされるまでの時間をリードタイムと定義します。

上記のリードタイムの定義には欠点があります。Gitの仕組み上ブランチを切った時間が記録されないため、開発に着手してからファーストコミットまでの時間が入っていません。 そこを逆手にとって、リードタイムを短縮するために全て実装し終わってからコミットするというアンチパターンが生まれます。

ただ、このようなことをしても数値だけが短縮されるだけで開発に使っている時間は変わりません。 それどころか、コミットを適切な粒度で行わないことで、コミットログが見づらくなったり、少し前の状態に戻したりすることが困難になります。 本来やりたいことは正しい指標を取得して伸びしろを見つけて本質的な改善に繋げることです。

このような数値ハックは、ただ無意味なだけではなく、コード管理の履歴が活用しづらくなったり、正しく数値を可視化していたら気づけるはずだった課題を見逃してしまう可能性もあるためやめましょう。

リードタイムを短縮するために、必要以上に細かいプルリクエストを作成する

リードタイムを短くするプラクティスとして、1つ1つの変更を小さくすることがあります。 1つ1つの変更が小さいことで、実装の時間が短くなり、影響範囲が小さいためテストも容易になり、リードタイムが向上します。

変更サイズが小さいことを可視化するためには、プルリクエスト数を指標とすることがおすすめです。 変更サイズが小さい→さくさくプルリクエストがマージできる→プルリクエスト数が増加。というロジックです。

ただ、プルリクエスト数を指標とした場合、必要以上に細かいプルリクエストを作成するアンチパターンが生まれます。

例えば一括変換でできた機械的な修正であれば、それが複数ファイルに渡っていたとしても1プルリクエストにまとめるべきです。 これをファイルごとにプルリクエストを分割した場合、プルリクエスト数は増加しますが、分割の手間やレビュアーの負荷が増加することで開発生産性は悪化します。

プルリクエスト数はあくまで適切な粒度に分割できていることを確認するための指標なので、数だけを追い求めないようにしましょう。

プルリクエストの粒度については別の記事で詳しく紹介していますので、ぜひご覧ください。

tech.findy.co.jp

レビュープロセスに時間がかかるので、コードレビューを省略してマージする

リードタイムに着目した際、コードレビューに時間がかかっていることが課題として浮かび上がることがあります。

この課題を正攻法で解決する場合、コードレビューに時間がかかっている理由を分析して、課題を解消する必要があります。

以下によくある課題と解決例を挙げます

  • レビュアーが特定の人物に集中しているのでレビュアーを増やして分散する
  • プルリクエストのサイズが大きいことでレビューに時間がかかるので、プルリクエストのサイズを小さくする
  • インデントや書き方の指摘が多いので、リンターやフォーマッタを導入して機械的に検知できる指摘は自動化する
  • 指摘が多くなりやり取りに時間がかかっているので、ペアプロ/モブプロを導入する

一方でレビューにかかっている時間を最も簡単に削減する方法はコードレビューを省略することです。

ただし、コードレビューを省略することはお勧めできません。 コードレビューをすることでバグの発見やコード品質の向上につながったり、コードの内容を実装者とレビュアーで共有することでコードの属人化を軽減できたり、レビュアーの目を通すことで第三者が読みやすいコードになり保守性が向上します。

コードレビューはとても有意義な活動なので、省略せずに根本解決することで有意義にレビューしつつリードタイムを向上させましょう。

プルリクエストのサイズを小さくするためにテストコードを別のプルリクエストにする

意味のある最小単位でプルリクエストを作成して、小さい単位でサクサクマージしていくことは開発生産性を向上させるための有効なプラクティスです。

プルリクエストを小さくする際、テストコードを別のプルリクエストに分けたくなることがあります。 1つ目のプルリクエストでテストコードなしのコードのみ実装して、2つ目のプルリクエストでそのテストコードを書くという方法です。

この方法はプルリクエストのサイズを小さくできますが、一時的とはいえテストコードで守られていないコードがメインブランチに混入することになります。 テストで守られていないコードは何かしらの変更で不具合が混入しても気づくことができないため、品質低下につながります。 また、すぐにテストコードが追加されれば良いですが、別にすることでテストコードの実装を忘れてしまう可能性も高まります。

コードと対応するテストコードは1つのプルリクエストに含め、テストコードで守られていないコードがメインブランチに混入しないようにしましょう。

変更障害率を下げるために、デプロイ頻度を下げる

変更障害率はDORAが提唱するDevOpsの4つの指標(Four Keys)の1つであり、デプロイを起因とした本番障害の発生確率を指しています。 また、開発生産性に関係なく、変更障害率は関係者全員が低く保ちたいと願っているのではないでしょうか。

本番障害が発生する最大の原因はデプロイで不具合を混入させてしまうことです。 そのため、デプロイすることに慎重になり、デプロイ頻度が低下してしまうことがあります。

しかし、変更障害率を下げるためにデプロイ頻度を下げることはアンチパターンです。

デプロイ頻度を下げて、1回のデプロイに含まれる変更量が増えることで次の問題が発生します。

  • 変更内容が複雑になり、相互作用や予期せぬ結果が発生する可能性が高くなり、本番障害が発生するリスクが高まる
  • テスト範囲が広くなるため、テスト漏れが発生する可能性が高くなり、本番障害が発生するリスクが高まる
  • 障害が発生した際、原因特定に時間がかかり、平均修復時間が増加する
  • 影響範囲が大きくなるため、障害の影響範囲が広くなる可能性が高まる

変更障害率を下げるためのプラクティスは、デプロイ1回あたりの変更量を小さくすることです。 変更量が小さいことで、変更範囲を正確に把握できるので、テストが容易になってテストのリードタイムを短縮できたり、テスト精度が向上することで品質が向上します。 また、頻繁にデプロイするためには自動テストを充実させることも重要です。自動テストが充実していることで既存機能が壊れていないことを自動的に確認できるためリードタイムの向上や品質の向上につながります。

デプロイを過度に恐れず、デプロイあたりの変更量を小さくしたり、自動テストを充実させるなど開発生産性を向上させるプラクティスを取り入れることで変更障害率を下げましょう。

変更障害率を下げるために、hotfixでの対応を出し惜しみする

変更障害率を可視化するために、hotfixの数から算出することがあります。

前述した通り、変更障害率は誰もが低く保ちたい指標です。 そのため、本来はhotfixで即対応した方がベストだとわかっている場合でも、通常フローで対応してしまうことがあります。

hotfixを使わないことで、変更障害にカウントされないので結果として変更障害率は下がるかもしれません。 ただし、hotfixを使わなかったからといって発生した障害がなるなるわけではありません。 また、本来は変更障害率に計上されるべきものを計上しないことで、振り返りなどで課題に気づきづらくなり、自分たちの成長機会を妨げる可能性があります。

開発生産性の可視化を自分たちの成長機会に繋げるためにも、数値を良くすることばかりに注目するのではなく、正しく取るように心がけましょう。

まとめ

ここまでで紹介したアンチパターンは開発生産性の数値を高めるための組織の能力に注目せずに、数値を向上させることだけを目標に設定してしまったために発生しているものが多いです。

このことは「グッドハートの法則」として広く知られています。

以下、ChatGPTによる説明です。

グッドハートの法則(Goodhart’s Law)は、経済学者チャールズ・グッドハートが提唱した概念で、「特定の統計的指標が政策目標として使われると、その指標はもはや信頼できるものではなくなる」という法則です。 つまり、ある指標が目標となると、人々はその指標を達成するための行動を取り始めるため、指標自体の本来の意味や有用性が失われるということです。

定量的な目標を設定することは開発生産性の現在地を把握するためには有効ですが、目標を達成するために本来備えておくべき能力を理解せずに数値だけを追い求めないようにしましょう。

なお、開発生産性の高い組織を目指すために備えるべき能力は、Google CloudのDevOpsの能力がとても参考になります。

また、DORA Core Modelにも開発組織のケイパビリティと開発生産性指標との関係が示されていますので、ぜひ参考にしてみてください。

出典: DORA’s Research Program


6月28日(金)・29日(土)に『LeanとDevOpsの科学』の著者であるNicole Forsgrenの来日、テスラ共同創業者元CTOの登壇など、国内外の開発生産性に関する最新の知見が集まるConferenceを開催します。 開発生産性に関する他の企業の取り組みや海外の事例に興味がある方は、ぜひお申し込みください!

dev-productivity-con.findy-code.io

LLM Embeddingを活用した問い合わせBotを社内向けに導入して効率化した話

こんにちは。
FindyでMLエンジニアをしているyusukeshimpo(@WebY76755963)です。
今回はLLM Embeddingを活用した自動応答Botを開発&導入し、社内の問い合わせ業務を効率化することができたので、その取り組みを紹介します。

Botを開発することになった背景

弊社ではSlackを使用し、自社サービスに関する社内質問に回答するチャンネルを運用しています。
主にビジネスサイドからの技術的な疑問にエンジニアが答える仕組みです。

質問はテンプレートを使って送信され、エンジニアが回答しますが、このワークフローには次のような問題が発生しています。

  • 同じ質問が異なる人から届いてしまう
  • 質問の度にエンジニアの工数が発生してしまう

半期で70件以上の質問が発生し、1件につき1~1.5時間かかることもあります。
多くの質問は社内ドキュメントで解決可能ですが、検索がしづらく利用されていません。

質問に関連するドキュメントを渡すことで、エンジニアに質問する前に自己解決を促すことができます。
これがBot導入の背景です。

Bot導入のためのアプローチ

Botの要件について

Botを導入する理由は、質問対応でエンジニアの労力を減らすことです。
しかし、Kibela(社内の情報共有ツール)のQ&A集やSlackの問い合わせ履歴などの社内ドキュメントは全て構造化されておらず、検索性に乏しいという課題があります。

また、Botが全てを解決するのは難しく、ハルシネーション(AIが実際には存在しない情報を生成する現象)の問題もあります。
これらを踏まえ、クイックに実装できる要件を考えました

そこでBotの機能としては、

  • 質問の内容に関連しそうな社内ドキュメントへのリンクを送る
  • 上記で解決できたかを質問者にフィードバックしてもらう
  • 解決できない場合はあらためてエンジニアへ質問を投げる

というものにしました。

Botの構成

上記の要件を踏まえて、Botの仕組みは次のようにしました。

① データの取得
② データの構造化
③ 類似度計算とBotの回答
④ 質問者からのフィードバック

以下に、各処理について簡単に説明します。

①データの取得

最初にデータの取得をKibelaとSlackからAPIを使って行います。
過去きた質問と同じ質問に回答できることや社内の基本的なドキュメントで解決することを考えて、KibelaとSlackチャンネルのメッセージ履歴をBotで使用するデータにしました。

②データの構造化

今回の開発で最も重要なことの一つに、テキストデータの構造化処理があります。
フォーマットが異なる、または整備されていないテキストを扱うため、地道な作業で対応しました。

以下に、非構造の社内ドキュメントを構造化するプロセスを図示しています。

このプロセスを経て、最終的にKibelaとSlackから次の情報を持った構造化データを作成します。

  • ドキュメントタイプ(Kibela or Slack)
  • テキスト
  • ドキュメントのURL
  • テキストのEmbedding(質問との類似度計算用)

このように構造化することで、質問内容に合わせた関連ドキュメントを検索することができるようになります。

テキストデータ処理後、コサイン類似度計算のためにOpenAIのtext-embeddingを使用しました。
作成した構造化データはBot内で保持します。

③類似度計算とBotの回答

質問者からのインプットである質問文をEmbeddingしたものと、構造化データ内のEmbeddingデータとのコサイン類似度を計算し、類似したドキュメントのリンクを渡せるようにします。
Botの返信内容として、質問と類似度の高いドキュメントのURLを採用します。

④質問者からのフィードバック

最後に、質問者が想定している回答を、Botが応答できているかどうか確認するプロセスを設けました。
ここでは、望んでいた応答が得られたかどうかをフィードバックしてもらい、フィードバック別にその後のフローを分けています。
フィードバック後のフローについてはこの後の工夫した点のところで、詳細を説明します。

Bot導入時の工夫

Botの導入の際に工夫した点が以下2点あるので、それについても紹介をさせてください。

  • ワークフローの変更
  • データの準備

ワークフローの変更(フィードバックプロセスの追加)

ハルシネーション問題に対応する為、Botはテキストで返答するのではなく類似度のリンクを返信するようにしています。
これにより、質問者がリンク先のドキュメントを参考に自己解決を促すようにしました。

しかし、Botが提案したドキュメントで解決しきれない場合があった為、既存のワークフローを「自己解決できなかった場合に、エンジニアへ質問として受け付けられる」よう工夫しました。

上記工夫を取り入れた変更後のワークフローを以下図のように変更しました。
このように、質問者がBotの応答で自己解決できるかをチェックし、解決できない場合に「いいえ」を選択するとエンジニアへ質問が飛ぶ仕組みとしています。

Slack APIによるフィードバックの作成方法

回答が役に立ったかどうかのフィードバックを作成する際に、Slack APIによる以下実装の要領で、質問者に手軽に回答してもらえるボタンを用意する事ができました。

@app.action("feedback_yes")
def handle_feedback_yes(ack, body, say):
    ack()
    thread_ts = body["message"]["ts"]
    say("あなたは「はい」を押しました\nフィードバックをありがとうございます!他にお問い合わせがある場合はフォームからお願いいたします。", thread_ts=thread_ts)

@app.action("feedback_no")
def handle_feedback_no(ack, body, say):
    ack()
    thread_ts = body["message"]["ts"]
    user_group_id = "hoge" # SlackグループID(メンション先)
    say(f"あなたは「いいえ」を押しました\n<!subteam^{user_group_id}>\n上記のお問い合わせが来ているのでご対応お願いいたします!", thread_ts=thread_ts)

このように、フィードバックステップを簡易に組み込む事ができたおかげで、Botでどのくらい自己解決を促す事ができたかどうかを後述のように可視化できました。
導入時点で組み込んでおいた事で、評価用プロセスの追加実装や別で実装する等の手間を省く事もできました。

データの準備

先述の通り、今回の仕組みによるBotを実装するにあたり、応答精度を高めるには様々な形式で保存されているテキストデータのクリーニングや加工は重要なポイントの1つでした。
これまで、Botを構築し社内ドキュメントを検索するような用途で参照する事を想定していなかった為に、今回の用途に合わせてデータを丁寧に加工する事で、応答精度を高める事ができました。

今回紹介した仕組みで自動応答Botのデータとして応用する事を検討する場合は、ドキュメントが構造化されやすく整理されているかが意識されていると実装コストが削減できるようになると感じました。
例えば、Kibelaのような自由記述のテキストであれば、テンプレートを用意する等して形式を統一すると前処理が楽になります。

社内ドキュメントの整備には手間がかかるため敬遠されがちですが、このような用途に利用できることがわかれば、社内ドキュメントの整備に時間を割きやすくなるように思います。  

Bot導入の結果

社内の問い合わせオペレーションを改善できた

実際にBotを導入してみて工数の削減ができたのかを集計してみました。
導入後2ヶ月で、お問い合わせ対応にかかる所要時間を1/3減少する事に貢献している事がわかりました。

オペレーションの改善になったポイント

このように効率化できたポイントは、問い合わせ対応のワークフローを改善できたことです。
今まではどんな質問でも問い合わせとして送信していたものを、Botによる自己解決を促すプロセスを組み込んだワークフローにできた事で、本来であれば自己解決する事ができた質問を炙り出す事ができるようになったからだと振り返っています。
Botを単純に導入するだけでなく、今回の評価プロセスのように「どんな課題に対してどのようにBotを組み込み効率的な仕組みを作るか」を意識することが大切です。

Botの精度は今後社内ドキュメントを充実させることで改善をしていきますが、質問への対応時間削減は実現できたので導入した甲斐はありました。
Botで社内向けの問い合わせ対応の効率化をしたいという方はぜひ参考にしていただければと思います!

最後に

以上が社内お問い合わせの自動化Botの開発についてでした。
参考にしていただければ幸いです。

また、弊社では機械学習エンジニア・データエンジニアなど一緒に働いてくれるメンバーを募集しております。
興味がある方は↓からご応募してしていただければと思います。

herp.careers

Findyの爆速開発を支える「システムを守るテストコード」の実例3選

こんにちは。

Findy で Tech Lead をやらせてもらってる戸田です。

弊社では本番環境へのデプロイを1日に複数回実行していますが、本番環境での不具合の発生率は低いです。

次の画像は弊社のあるプロダクトの直近1年のFour Keysの数値です。

平均で1日2.3回の本番デプロイを行っていますが、変更障害率は0.4%程度を維持しています。単純計算ですが、1年で障害が2件程度の水準です。

また、平均修復時間は0.3hとなっており、障害が発生しても20分以内には復旧できていることがわかります。

この数値を維持できている理由の1つにテストコードの品質があると考えています。

システムで発生する不具合を自動テストが検知することで本番環境への不具合の混入を事前に防ぐことができ、仮に不具合が発生したとしても修正内容が他の箇所に影響が出ないことをテストコードが保証してくれるため迅速に修正できるからです。

弊社ではテストカバレッジよりも、テストで何を守るのか?という観点を重視しています。その上でテストカバレッジの90%超えは珍しいことではありません。

しかし、実際にはカバレッジを意識したことはなく、システムを守るテストを意識した結果、勝手にカバレッジが上がっていただけなのです。

我々としては「全てのテストコードが通る」ことよりも、「コケるべき時にコケるテストコード」の方が重要だと定義しています。

この記事では、弊社で実践している「テストを通すためのテストコード」ではなく、「システムを守るテストコード」の書き方をいくつか紹介していきます。

それでは見ていきましょう!

実例紹介

関数の実行状態のチェック

特定の処理を実行したあとに、結果に応じてコールバック関数を実行するようなケースは少なくありません。

コンポーネントに関数を渡し、ボタンをクリックした時にその関数を実行するようなケースもあるでしょう。

このようなケースの場合、関数を特定の処理で実行する際に、その関数が適切に扱われているのかを守るテストケースが必要になります。

例えば正常系の次のようなテストコードがあるとします。

const mockOnSuccessCallback = jest.fn();
const mockOnErrorCallback = jest.fn();
const { result } = renderHook(() => useHoge({ onSuccessCallback: mockOnSuccessCallback, onErrorCallback: mockOnErrorCallback }));

act(() => {
  result.current.handleSubmit();
});

expect(mockOnSuccessCallback).toHaveBeenCalledWith('test');

hooksの関数を実行し、成功時に引数で渡したコールバック関数がパラメータ付きで実行されることを確認しています。

一見何の変哲もないテストコードですが、このテストコードには次の視点が抜けており、システムを守るテストとしては不十分です。

  • 成功時の関数が複数回実行されたとしても通ってしまう
  • エラー時のコールバック関数が実行されたとしても通ってしまう

こういったケースでは、弊社では次のようなテストコードが推奨されます。

const mockOnSuccessCallback = jest.fn();
const mockOnErrorCallback = jest.fn();
const { result } = renderHook(() => useHoge({ onSuccessCallback: mockOnSuccessCallback, onErrorCallback: mockOnErrorCallback }));

act(() => {
  result.current.handleSubmit();
});

expect(mockOnSuccessCallback).toHaveBeenCalledTimes(1);
expect(mockOnSuccessCallback).toHaveBeenCalledWith('test');
expect(mockOnErrorCallback).not.toHaveBeenCalled();

このテストコードにより、「成功時に1回だけコールバック関数がパラメータ付きで実行され、エラーのコールバック関数が実行されない」ことを守ることができます。

この2つのテストコードのカバレッジは変わりませんが、システムを守ることができる内容が変更前よりも増えており、アプリケーションの振る舞いをより厳密に守ることができるようになりました。

出力対象外のチェック

特定の情報のみを返す処理がある場合、その情報が正しく取得できることを確認するテストケースが必要になります。

特定のユーザー情報を取得し、そのユーザーに紐付く各種情報を取得する処理はよくあるケースです。

このようなケースの場合、特定の情報のみを取得できることを守るテストケースが必要になります。

例えば次のようなテストコードがあるとします。

RSpec.describe User do
  describe '.skills' do
    let!(:user) { create(:user) }
    let!(:user_skills) { create_list(:user_skill, 3, user:) }

    it 'returns user skills' do
      expect(user.skills).to eq(user_skills)
    end
  end
end

特定のユーザーデータに紐付くスキルデータを取得することを確認しています。

一見何の変哲もないテストコードですが、このテストコードには次の視点が抜けており、守るテストとしては不十分です。

  • 他のユーザーのスキルデータが混在してしまう可能性を否定できていない
  • 対象のユーザーがスキルデータを持っていない場合の挙動を確認できていない

特に他のユーザーのスキルデータが混在してしまうケースが発生してしまった場合、最悪の場合インシデントにもなりかねません。

こういったケースでは、弊社では次のようなテストコードが推奨されます。

RSpec.describe User do
  describe '.skills' do
    let!(:user) { create(:user) }

    before do
      # NOTE: 他ユーザーのレコードが対象外になることを守る
      create_list(:user_skill, 3, user: create(:user))
    end

    context 'when user has skill' do
      let!(:user_skills) { create_list(:user_skill, 3, user:) }

      it 'returns user skills' do
        expect(user.skills).to eq(user_skills)
      end
    end

    context 'when user not has skill' do
      it 'returns empty array' do
        expect(user.skills).to eq([])
      end
    end
  end
end

このテストコードにより、「他のユーザーのスキルデータが混在してしまう可能性を否定」し、「対象のユーザーがスキルデータを持っていない場合でも正常に処理を実行できる」ことを守ることができるようになりました。

対象のレコードをチェックすることだけではなく、対象外のレコードが本当に対象外になっているのかどうか、レコードが存在しなかった場合にエラーが起きないかどうかまで確認することで、アプリケーションの振る舞いをより厳密に守ることができるようになりました。

チェックする値の厳密化

特定の値を返す関数がある場合、実行時に期待する値が返ってくることを確認するテストケースが必要になります。

例えば次のようなテストコードがあるとします。

RSpec.describe User do
  describe '.has_multi_skills' do
    let!(:user) { create(:user) }

    before do
      # NOTE: 他ユーザーのレコードが対象外になることを守る
      create_list(:user_skill, 3, user: create(:user))
    end

    context 'when user has multi skills' do
      before { create_list(:user_skill, 3, user:) }

      it 'returns true' do
        expect(user.has_multi_skills).to be_truthy
      end
    end

    context 'when user has skill' do
      before { create(:user_skill, user:) }

      it 'returns false' do
        expect(user.has_multi_skills).to be_falsy
      end
    end

    context 'when user not has skill' do
      it 'returns false' do
        expect(user.has_multi_skills).to be_falsy
      end
    end
  end
end

対象のユーザーが複数のスキルデータを持っている場合にtrueを、それ以外の場合にはfalseが返ってくることを確認しています。

パッと見で特に問題は無さそうですが、be_truthy be_falsy の仕様には注意が必要です。

it { expect(true).to be_truthy }      # passes
it { expect("hoge").to be_truthy }    # passes
it { expect(nil).to be_truthy }       # fails
it { expect(false).to be_truthy }     # fails

it { expect(false).to be_falsy }      # passes
it { expect(nil).to be_falsy }        # passes
it { expect("hoge").to be_falsy}      # fails
it { expect(true).to be_falsy }       # fails

be_truthy be_falsy はbooleanの値以外でも実行結果が変わります。

そのため、例えば関数が返す値がbooleanではなく文字列になってしまった場合にテストが通ってしまう可能性があります。

何かしらの値が入っていることを確認するテストケースであれば問題ありませんが、今回のケースではbooleanの値を厳密にチェックすることが要求されます。

こういったケースでは、弊社では次のようなテストコードが推奨されます。

RSpec.describe User do
  describe '.has_multi_skills' do
    let!(:user) { create(:user) }

    before do
      # NOTE: 他ユーザーのレコードが対象外になることを守る
      create_list(:user_skill, 3, user: create(:user))
    end

    context 'when user has multi skills' do
      before { create_list(:user_skill, 3, user:) }

      it 'returns true' do
        expect(user.has_multi_skills).to be true
      end
    end

    context 'when user has skill' do
      before { create(:user_skill, user:) }

      it 'returns false' do
        expect(user.has_multi_skills).to be false
      end
    end

    context 'when user not has skill' do
      it 'returns false' do
        expect(user.has_multi_skills).to be false
      end
    end
  end
end

このテストコードにより、関数が返す値をbooleanの値で厳密にチェックすることが可能になり、関数の実装が壊れてしまった場合にテストがコケて教えてくれるようになります。

テストライブラリのマッチャーは便利で多用しがちですが、それらの仕様を理解し適切に利用することが重要です。

まとめ

いかがでしたでしょうか?

テストコードは全て通ると安心しますが、本質はそこではなくコケるべき時にコケてくれるテストコードを書くことが重要です。

今回挙げた例はほんの一例です。弊社ではこの様にシステムを守るテストを重視しており、既存コードへの修正を行ったとしてもほとんどのケースをテストコードでカバーすることが出来ています。そのため安心して機能追加、リファクタリングなどを行うことができます。

現在、ファインディでは一緒に働くメンバーを募集中です。

興味がある方はこちらから ↓ herp.careers