原価計算システム開発のデリバリー速度を改善した話

はじめに

はじめまして、キャディ株式会社で原価計算システム(詳細は後述)の QA 兼 PM的ロールを担当している朱(@shugenshugen)と申します。

キャディには2019年の1月にジョインし、約1年間事業開発やデータ分析基盤の整備に携わった後、プロダクトチームに転属されました。

今回お話しするのは、自分が所属しているチームにて直近半年ほどで見られた「プロダクトのデリバリー速度向上のための取り組み」についてです。

TL;DR

はじめに、どんな課題があってどのように解決したのかについて簡単にまとめます。

まず、課題については明確にデリバリー速度の遅さがありました。 (開発当初のリリース目標を複数回に渡って超過しており、チームに求められる開発スピードに追いつけていない状態でした…)

これを分解すると、

  • テスト工程のボトルネック化
  • 開発プロセスの非効率性

が原因として存在しました。

対してチームが採ったアプローチは大きく以下の2つです。

  • テスト自動化によるボトルネックの解消
  • 開発の前工程への染み出しによる開発プロセス効率化

取り組みの結果として、当初は約1ヶ月かかっていた原価計算ロジックのメジャーアップデートが、約1週間で実現できるようになりました。

以降の項では、プロダクト・チームが置かれていた状況をもう少し詳細に解説しつつ、各開発フェーズでどのような取り組みを行ってデリバリー速度を改善していったかを深掘っていこうと思います。

どんなプロダクトなの?

具体的な取り組みの話に入る前に、キャディで開発している「原価計算システム」について少し解説します。

現在キャディでは製造業の受発注領域に特化して、取引を効率化するために複数のプロダクトの開発を進めています。 その内の1つが「原価計算システム」です。

(キャディの開発体制や他のプロダクトについてはこちらの記事も参考に!)

高度な「見積」業務を民主化する

キャディでは「お客さまから図面をいただき、製品を製造して納品するまでの全責任を負う」というビジネスモデルを採用していますが、その取引プロセスの中で製品1つ1つに適切な価格を算出する必要があります。

そして、キャディが主に取引の対象としているのは製造業の中でも「特注品」と呼ばれるカテゴリの製品のため、製品の価格といっても一品一様であり価格算出の難易度が非常に高いことが特徴として挙げられます。 実際に、この「価格を算出する」業務(= 見積)は、一般的な町工場だと社長やベテラン社員の方しか担当できない非常に専門性の高い業務として知られています。

そこで、キャディではソフトウェアの力を活用して「見積」業務を民主化することに取り組んでいます。

加工をモデル化して誰でも扱えるように

「見積業務の民主化」をするための中心的なアプローチとして取り組んでいるのが、加工のモデル化です。

これだけだといまいちピンと来ない方がほとんどだと思うので、具体例を使って説明をします。

例えば、キャディで扱っている製品領域の1つに「板金加工品」というものがあります。ここで言う板金加工とは、金属の板を曲げたり溶接したりして、目的の製品の形に仕上げていく加工の種類です。

(画像は弊社サービスページより)

さて、このようなボックスの価格はどのように計算されるのでしょうか? 加工の大まかな流れは以下の通りです。

  • 平らな金属の板に穴を開ける(穴あけ)
  • 板を曲げてボックス状にする(曲げ)
  • 曲げた板同士を溶接して隙間を無くす(溶接)
  • 製品の見栄えを良くするために色をつける(塗装)

かっこ内はそれぞれ加工の工程と呼ばれ、各工程についていくらかかるのかを表すのが製造原価という概念です。 基本的にはこの原価の積み重ねで製品の価格は計算されます。

かなり単純化されたチャートですが、この製品の価格は上記のようなモデルで表現できそうです。 実際には、用いる材質や製品の形状によって工程ごとに多種多様なパラメータが存在したり、原価も段取り費用と実加工費用に分解できたりなど、より複雑な要素が価格算出には関わってきます。

加えて更に複雑性を増しているのが、こうした加工の捉え方が製造業というドメイン内で決して統一されている訳ではないという事実です。 実際に、同じ図面について異なる町工場さんに作り方や製作にかかる費用をヒアリングしても、全く違う製作方法を前提とした金額回答が得られることが良くあります。(そしてこの捉え方の違いこそが各町工場の持つ強み弱みであり、競争力の源泉だったりします。)

キャディは町工場のアグリゲーター的な存在であるという立場上、様々な強み弱みを持つ町工場に対して的外れではない製品価格の見積を行い、最適な発注するという責務を負っています。 そのためには、上記のように業界内でも統一が図れていない幅広い加工に関する知識について、本質的な部分を抽出して繰り返しドメインモデリングを行うことで、誰にでも使えるシステムに落とし込んでいく必要があります。

そして(まだ道半ばではありますが)キャディは、この取り組みを業界内でもほぼ唯一と言って良いほどの精度で成し遂げている存在であると考えています。 「見積」が一般的には長年の経験を必要とする非常に高度な業務であることは先述しましたが、キャディでは入社してまず最初に受ける研修が原価計算システムを活用して「見積」が出来るようになることであり、実際に社内(ビジネス本部)のほとんどの人材が業務をこなせるようになっているほど、共通言語として浸透しています。

そして本格システム化へ…

原価計算システムはキャディのビジネスにおけるコアとも呼べる存在のため、その構築には創業当初から長い時間をかけて投資がなされてきました

しかし、どんなシステムでも共通することだと思いますが、長期に渡って開発が続けられた場合には得てして負債が溜まります。 立ち上げ当初はPDCAの回しやすさを重視してなるべくライトな構成で作られていた原価計算システムのプロトタイプも、類に漏れずその道を辿ることになりました。

事業の拡大に伴って取り扱える加工の幅が広がるにつれて、初期に構築したモデルの保守性・拡張性の低さに起因する品質保証の難しさ、保守運用の職人芸化が顕在化してきました。

またパフォーマンスやUX面でも課題を抱えており、システムの利用が社内のオペレーションのボトルネックとなりつつありました。

こうした課題を踏まえて、

  • 保守性・拡張性を考慮したモデルの再設計
  • 実行速度の速いRustによるバックエンドの書き換え
  • UI/UXの全面的見直し

などからなる本格システム化計画が始動し、現在の「原価計算システム」が形作られることになります。

デリバリー速度改善の3STEP

前置きが長くなってしまいましたが、ここから本題の「デリバリー速度の改善」について書いていきます。

その際に、主にテスト周りの仕組みや開発プロセスのあり方に焦点を当てて、開発期間を以下の3つに分割しようと思います。

  1. チカラワザ期
  2. 整備期
  3. Shift Left 期

チカラワザ期

まず最初の期間が、プロダクトの1stリリースまでの「チカラワザ期」です。

ここで生じた課題が、開発の終盤に差し掛かってテスト工程のボトルネック化が顕在化してきたというものです。 もともとチームで設定していたリリース目標に対して、原価計算ロジックのテストにかかる工数を少なく見積もり過ぎていたことが露見し、数回に渡って目標を後倒しにせざるを得ない状況に追い込まれてしまいました。

もともとあった仕組み

この時点でチームに存在していたテストの仕組みは、以下の通りです。

  • 原価計算を担当するコンポーネント内で実装されていた単体テスト
  • Pythonで実装された手動テストツール

両者の住み分けについて補足しておくと、前者の単体テストは工程ごとに最低限のカバレッジしか担保していなかったため、リリースに際して網羅的な品質保証の仕組みとして手動テストツールが誕生した、という経緯があります。

プロジェクトの前提にあった不確実性

これを聞くと「最初からテストの仕組みを担保した状態で開発を進めることは出来なかったの?」という至極真っ当な疑問を持たれる方がいらっしゃるかと思いますが、ここに本プロジェクトにおける最大の不確実性が潜んでいました。

それは、新規開発を進めている最中にも、プロトタイプ側では事業領域の拡大に合わせてモデルの開発が活発に進むという無慈悲な事実です。 (実際に、開発に着手してからプロダクトの1stリリースまでに、プロトタイプ側のメジャーバージョンは20回以上アップデートされていました。)

この前提は当時の事業運営上回避できないポイントだったため、新規開発側では常に要件が不安定な状態での開発を余儀なくされました。 そのため、開発初期には原価計算ロジックについてはテスト駆動開発で開発を進めようと検討がされていましたが、そもそも正となるテスト結果が変動する以上は自動テストのメンテナンスコストの方が上回ってしまうという結論に至り、テストの工程自体も開発終盤に押し込められる結果となりました。

突破口となった施策

最終的に「テスト工程のボトルネック化」という課題自体の解消は次の「整備期」を待つことになりますが、手動テストを用いたチカラワザに頼るしかなかった開発終盤を突破することになった施策があるため紹介しようと思います。

それは、プロトタイプとの差分の許容値を定量的に定め、リリースの基準としたことです。

具体的には、プロトタイプを用いて価格算出した過去案件の実績データを用いて、その製品価格との差分が一定の割合以内に収まっていることとしました。

これによって、それまでは工程レベルでわずかにでも価格差分が生じていればバグとして対応しなければならなかったのに対し、金額インパクトが小さいものについてはリリース後に後追いで修正する、という意思決定が容易にできるようになりました。

これが開発リソースのフォーカスに繋がり、数ヶ月遅れではありますが無事にプロダクトのリリースに至りました。

整備期

次に、先述した「テスト工程のボトルネック化」を仕組みの整備で解決しようとしたのが「整備期」です。

課題の分解

ボトルネック化において実際に生じていた課題を分解すると、以下のようになります。

  • 単体テストの負債化
  • 高負荷な手動テスト
  • テストデータのアップデート工数

まず、単体テストの負債化については先にも述べたようにテスト結果そのものが不安定な状態で開発を続けていたため、プロトタイプ側で原価計算ロジックのアップデートがある度に、関連する単体テストの結果部分も書き換える必要がありました。 この書き換えの工数が地味に重く、実装の約半分近くを単体テストのアップデートに費やしていたという声もあったほどです。

次に、手動テストの負荷については新規開発の過程でモデルの再設計を行っていたため、プロトタイプと新規開発後のモデルで工程ごとの計算結果の粒度が異なるという事象が生じました。 そのため、異なる粒度間での比較を実現するために手動テストを実行する度に手元で追加の計算をするという対応を取ることになり、実行の負荷が非常に高い状態でした。(関連して、新規開発側でモデルのアップデートを行った際にリグレッションテストを実行するのもほぼ不可能になってしまい、デグレの検知が遅れて終盤の開発を圧迫する遠因にもなりました。)

最後にテストデータのアップデートについてですが、当時は手動テストの実行時にプロトタイプから製品ごとの価格の算出結果をCSVファイルとして出力して、手動テストツールに与えるような仕組みになっていました。 その際にプロトタイプのアップデートがあると膨大な数のCSVファイルも更新することになり、このテストデータの更新作業に毎回丸1日かけて人力で取り組む必要がありました。

解消に向けた取り組み

以上のような課題の解決に向けて、チームでは大きく以下の2つのアプローチで取り組みを行いました。

  • 保守性が低い単体テストは勇気を持って捨てる
  • 代わりとなる自動テストの整備

1点目の単体テストの切り捨ての是非については、チーム内でも繰り返し議論があったポイントです。 実装工数を圧迫しているのは誰の目で見ても明らかだったのですが、複雑な原価計算ロジックの開発を進めるエンジニアの視点に立つと、ビルド時に合わせて実行できて結果もすぐに分かる単体テストは、やはり心強い味方でした。

それでも、今後もロジックのアップデートは定常的に行われていくという前提に立つと、これまでのようにカバレッジの低い単体テストを高いメンテナンスコストを払って維持し続けることはROIが低いよねという結論に至り、勇気を持ってこれを捨てる意思決定をしました。

代わりに注力したのが、2点目で挙げている自動テストの整備です。

まず実行負荷が高い手動テストツールに代わる仕組みとして、工程間の価格を自動で比較できるテストツールを新規で開発しました。 その際に意識したのが、必要であればプロトタイプ側にも開発の手を入れるという点です。それまでは目の前のユーザーに直接価値を提供するような機能にリソースをフォーカスさせる形で開発を進めていましたが、以降はたとえ直接的にはユーザー価値に寄与しない部分だとしても(そして長期的には無くなる仕組みだとしても)、デリバリー速度向上に繋がるのであれば時間をかけてでも仕組みを作った方がトータルの工数は下がるはず、と方針を転換できたという意味で転機だったように思います。

テスト自動化の文脈でもう1つ実施したのが、テストデータのアップデートの自動化です。 改善としては一見些細なもので自動化自体も2日ほどで実現できたのですが、これにより作業時間を確保できるようになり、他の改善活動に時間が回せるようになったという点ではインパクトの大きい施策でした。

Shift Left 期

最後の期間が、直近まで続く「Shift Left 期」です。

「整備期」の取り組みが功を奏し、テスト工程が開発全体のボトルネックになることはなくなりましたが、デリバリー速度は依然として求められる水準に達していた訳ではありませんでした。

前提として、新規開発直後のプロダクトには原価計算ロジックをアップデートするためのインターフェースが存在していなかったため、リリース後のロジック更新はプロトタイプで一度実装したものを再度新規開発先に移植する、というフローをとる必要がありました。

結果的に、リリース後最初のメジャーアップデートに要した時間は1ヶ月を超えました。 これは以前の開発スピードからするとかなりの進歩を見せてはいたものの、ビジネス上の要請からはまだまだ遅いと言わざるを得ず、以降はデリバリー速度を更に短縮していくために開発プロセスそのものの改善を進めていくことになります。

残った課題

この時点で残っていた課題は

  • ウォーターフォール的な開発プロセスの非効率性
  • モデルの初期設計起因の保守性の低さ

の2つです。

1つ目について、原価計算ロジックのアップデートでは

要件となるプロトタイプの加工モデルの更新を行う → 新規開発先で実装が必要になる差分を洗い出しチケット化する → 実装 → テストを実施しバグを書き出す → バグ修正 & 再度のテスト実施

という一連の流れを経る必要があり、開発プロセスの長大化によって発生するコンテキストスイッチの積み重ね開発アイテムの管理コストが大きな非効率性を生んでいました。

2つ目の保守性の低さについては、新規開発のタイミングで解消し切れなかったロジックの複雑な依存関係仕様バグが、繰り返しバグを生んだり、仕様確認の工数を肥大化させる要因になっており、ボディブローのように効いてきたタイミングでした。

やったこと

対して、行った取り組みは以下の3つです。

  • テスト駆動開発による開発プロセスの圧縮
  • モデルの設計段階からレビューに加わる
  • 計画的なリファクタリング

まず、コンテキストスイッチや開発アイテムの管理コストを最小化するために、実装の段階からあらかじめ用意されたテストケースを元に自動テストをこまめに実行して、バグが発生していた場合は合わせて修正するようにプロセスを変更しました。 このようなテスト駆動開発的な手法は、以前に自動テストの仕組みを整備したからこそ採用できたアプローチでした。

次が、プロトタイプから新規開発先へ加工モデルを移植するプロセスにおける改善です。 ここで1点補足をすると、プロトタイプにおける加工モデルの実装と新規開発先での実装は担当するチームが分かれているという前提があります。これは、前者のチームにはより深いドメイン知識やビジネスサイドとの折衝といった機能が求められるのに対して、後者にはRustを始めとするエンジニアリングの知識が求められるため、分業による効率性を優先した結果このような開発体制に落ち着いたという経緯があります。

この分業体制は本格システム化が実現する前は、キャディのビジネスモデルにおける要でもある加工のモデルの成長速度を落とすこと無く新規開発側の開発も並行して進めることができるというメリットがありましたが、いざプロダクトがリリースされて以降は単純に両チーム間のコミュニケーションコストが足かせになったり、それぞれのチームに部分最適になった開発が進められてしまうといったデメリットの方が勝るようになってきました。

そこで、徐々に両者の組織的な融合を図っていくための第一歩として、プロトタイプ側で加工モデルを設計するタイミングで新規開発側の開発視点から設計にフィードバックをする機会を設けるようにしました。 これによって、仕様バグになりそうなポイントを未然に潰すことができたり、実装を同時並行で進めることで新規開発側の開発プロセスそのものを前倒したりすることも可能になってきています。

最後の改善事項として、直近では開発プロセスの改善で生まれた余剰時間を計画的なリファクタリングに充てるようにしています。 リファクタリング自体は新規開発を進める途上でも必要に応じて行っていましたが、あくまでも個々のエンジニアが実装の片手間で行うに留まっており、チームとして負債を計画立てて返済していくという形では実施できていませんでした

対して、現在では理想の加工モデルの形をチームで議論し、そこに向かってやるべきリファクタリングの内容を列挙したり、週次のプランニングでタスクの一部として明確に組み込むなど、意識的にリファクタリングを進めることができています。

以上のような改善の甲斐もあってか、直近あった原価計算ロジックのメジャーアップデートでは実装に着手してから約1週間でアップデートを完了させることができました。これは諸々の改善を行う前からは想像のつかないペースであり、デリバリー速度の向上として一定の成果を残せたのではないかと考えています。

余談

章題にもなっている「Shift Left(Shift left testing)」とはソフトウェア開発の文脈で、開発ライフサイクルの終盤で実施されることが多いテスト工程をより上流の要件定義や設計の段階で行うことで、手戻りによるコストを削減したり、設計へのフィードバックを通じてソフトウェアの品質を向上させたりする活動のことを指します。

本章で挙げたテスト駆動開発によるテスト実施タイミングの前倒しや、モデルの設計へのレビュー(静的テスト)は、この Shift Left の活動の一環と呼ぶことができるでしょう。

似たような概念として、製造業には「フロントローディング」という用語があります。 フロントローディングとは、製品開発のプロセスにおいて上流工程である設計の初期段階で最も作業負荷をかけることで、最終的な品質を高めたりトータルのコストを削減する取り組みです。

MONOist 「設計者がフロントローディングという怪物に立ち向かうための“3つの武器”」より

これは正に Shift Left に通ずる考え方ですが、ソフトウェア開発というデジタルの世界だけでなく、実物を作るモノづくりの世界でも成立する概念なのです。

実を言うと自分は、最初は Shift Left という概念は知らずにフロントローディングという言葉を使ってこの発表をしていました。そこで、「これはソフトウェアでは Shift Left だよね」という指摘をいただいて知識が広がったという経験があったため、この項を書き加えました。 このように、ソフトウェア開発とモノづくりという似て非なる領域の知見が Connecting the dots 的に繋がる瞬間があるのが、キャディで開発をしていて面白いところでもあります。

(ちなみに更なる余談ですが、モノづくりの世界でソフトウェア開発における「テスト」に相当するのが、CAD(Computer-Aided Design)や CAE(Computer-Aided Engineering)といった技術たちです。これらについては同じチームに専門家がいるので、そのうち解説記事を書いてくれることを密かに期待しています。)

学びの抽象化

今回の開発から得られた学びを抽象化すると、以下のようになります。

  • プロダクトに求められる品質を定量化し、指標とすべし
  • 自動テストへの投資を怠るな、小さな改善を侮るな
  • 前工程への染み出しで品質をコントロールせよ
  • チームで改善のムーブメントを維持し続けよう

第一に、バグが一切存在しないプロダクトというものは存在しません。片っ端からバグを潰していくのはアンチパターンです。 大切なのは、許容できるバグとそうでないバグを切り分けるための定量的な基準を定めることです。これができれば開発リソースをフォーカスさせて一時的な突破力を手に入れることができます。(あくまでも一時的なものであり、本質的な問題の解決には以降の取り組みが欠かせないという点がミソです)

第二に、自動テストへの投資は早すぎると思われるくらいのタイミングから始めるべきです。開発を振り返ると、テスト結果が常に変動するという制約があった以上、初期に自動テストの仕組みを構築しても無駄になってしまった可能性が高いとは言えそうです。しかし、少なくとも開発終盤で手動テストを実施するタイミングでは、愚直に手を動かすよりは仕組みの構築に投資すべきだったと考えています。このような仕組みの類は時間が経つにつれて複利で効果を発揮するものだし、開発が長期化した際には避けられないリファクタリングにも自動テストは不可欠です。どうせ後でやることになる仕組みの構築は、多少は全体の進捗を止めてでも先に持ってくるべき、というのが現時点での自分の意見です。

また、同じく仕組みの構築という文脈で、小さな効率化・自動化の力を見くびってはいけないという学びもありました。開発プロセス改善において「これをやっておけば一度に効率化が進む」といった銀の弾丸は存在せず、小さな改善の積み重ねが最終的なデリバリー速度向上に寄与するのだという肌感が得られました。ゆえに、日々の開発の中でどこがボトルネックになっているのだろう?と考え続け、効率化・自動化できそうな部分があれば積極的に取り組んでいくことが重要なのだと思います。

第三に、既存の枠組みに閉じた改善では早晩効率化の限界が訪れるため、プロセスの外に踏み込んだ動きを取ることも重要だということです。実装やテストをいくら効率化しても、そもそもの要件や設計がイケていなければ理想的なデリバリーはできません。そんなときは与えられた前提を疑い、視野を広く保って開発プロセス全体を Re-Engineering すべきです。

最後に、以上で挙げたような取り組みはプロダクト・チームが置かれている状況によって着手すべきか否かや優先順が大きく変わると思います。その見極めを行いつつも、改善に向けたムーブメントをチームで絶えず維持し続けることが最終的な成果に繋げる上での最も重要な要素であると自分は考えています。

これから

終わりに、今後の開発で目指したいことをまとめます。

事業成長に負けない「本質的な速さ」を追求する

これまでは「プロトタイプで実装された原価計算ロジックを新規開発先のプロダクトに移植するまでのリードタイム」をデリバリー速度の指標として扱っていました。

しかし、今後は新しい「原価計算システム」が正となる時代がやってきます。

目標としていたデリバリー速度の向上は一定の水準で達成できたと言えそうですが、有り難いことに事業はそれ以上のスピードで成長し続けており、現状の原価計算システムでは価格算出できない領域が日々増えていっています

これに応えるためには、より本質的なデリバリー速度の改善に向けて、既存の2つの開発チーム間の組織的な融合を進めていくことや開発体制のスケーラビリティを確保することが必須です。

というわけで…

We are hiring !

カジュアル面談のお申し込みはこちらから!

Twitter(@shugenshugen)のDMからもお気軽にご連絡ください!

参考文献