TECH PLAY

キャディ株式会社

キャディ株式会社 の技術ブログ

93

こんにちは、2026年5月に入社し、AI for applicationチームにジョインしました村上です。 キャディ株式会社では、プロダクトが解くべき課題を正しく捉えるには現場業務への解像度が不可欠だという考えから、ドメイン知識をキャッチアップするための仕組みが社内に用意されています。その一つが「調達もくもくワーク」という体験型トレーニングです。 私自身、前職では製造業とは全く関係のない業界でMLエンジニアをしていました。そのため製造業のバックグラウンドはゼロです。 今回、ドメイン知識を身につける機会として上長にお勧めされ、「調達もくもくワーク」に参加しました。 この記事では、製造業未経験のMLエンジニアである筆者がこのワークに参加し、調達業務のペインを体験したうえで感じたことを書きます。 用語の整理 調達業務の概要 調達もくもくワークとは ワークでの体験 1.見積先の選定 手作業 CADDi Quote 2.見積依頼 手作業 CADDi Quote 3.見積結果の査定 手作業 CADDi Quote 体験してどう変わったか AIやシステムが引き受けるべき仕事 製造業全体の最適化の難しさ We are hiring 用語の整理 この記事で使う主要な製造業用語をまとめます。その他の用語は本文中で適宜補足します。 調達 : 自社製品に必要な部品や材料を外部から購入・手配する業務全般を指します。購買とほぼ同義ですが、サプライヤーの選定や価格交渉まで含む広い意味で使われます。 サプライヤー : 部品を製造・納品する外部の加工会社のことです。「協力会社」「外注先」とも呼ばれます。 バイヤー : 調達担当者のことです。サプライヤーに見積を依頼し、発注先を決定する側を指します。 図面 : 製造する部品の設計書です。形状・寸法・材質・表面処理などを記載した技術文書で、調達業務ではPDFや紙でやり取りされます。 見積 : サプライヤーが図面をもとに提示する価格・納期の回答です。複数社から見積を取ることを「相見積」と呼びます。 QCD : Quality(品質)・Cost(コスト)・Delivery(納期)の頭文字です。調達判断の際に考慮すべきパラメータです。 CADDi Quote : キャディが提供する製造業AI見積クラウドです。図面のアップロードから見積依頼・回答比較までをカバーします。 CADDi Drawer : キャディが提供する製造業データ活用クラウドです。図面からの情報の構造化や図面の形状認識が強みの一つになっており、類似した図面の検索が可能になっています。 調達業務の概要 調達担当者の仕事は、設計部門から受け取った図面をもとに外部のサプライヤーに部品の製造を依頼し、QCDを守って納品してもらうことです。大まかな流れは以下のとおりです。 図面受領・確認 : 設計部門から部品表と図面を受け取る。 カテゴリ分け : 図面を見て、切削・板金・購入品などに仕分ける(サプライヤーは加工種別ごとに専門が分かれているため、ここでの振り分けが必要)。 見積先選定 : サプライヤーリストから対応可能なサプライヤーを選ぶ。過去の発注実績と突合し、新規品かリピート品かを判定して加味する必要がある。 見積依頼の送付 : 見積依頼書を作成し、図面を添付してメールで送付する。 見積回答の査定 : サプライヤーからの回答を比較し、QCD観点で発注先を決める。 発注 : 発注書を作成してサプライヤーに送る 調達もくもくワークとは 調達もくもくワークは、CADDi Quoteのユーザーである調達担当者の業務を、サンプルの図面や発注実績などを使って実際に手を動かしながら追体験するトレーニングです。調達業務の一部についてまずCADDi Quoteなし(手作業)で苦しんでから、CADDi Quoteありでやり直します。ビフォー・アフターを自分の手で体験する設計です。ワークでは上記フローのうち、見積先選定・見積依頼の送付・見積回答の査定を体験しました。調達業務にどのようなペインがあり、それをCADDi Quoteがどう解決しているのかを肌で知ることが目的です。実際にはCADDi Quoteなしで一通り体験した後にCADDi Quoteを体験したのですが、読みやすさのため各フェーズの中で両方の感想を書きます。 調達もくもくワークの様子。 硬い雰囲気に見えますが、実際には気軽に質問したり手作業の煩雑さに愚痴を言いながら進めていました。 ワークでの体験 1.見積先の選定 ワークでは「どのような部品をどれだけいつまでに調達する必要がある」というような依頼が調達部門にきている状態からスタートします。それぞれの部品をどこのサプライヤーから調達するかを考える必要があります。どのサプライヤーがどんな加工に対応でき、過去にどんな品質・納期実績があったかというような情報が体系的にまとまっていることは稀です。結果として現場ではベテランの経験則に依存していることもよくあるようです。また、過去にいくらの金額で発注したかという情報はあったとしても、発注には至らなかった見積結果についてまとまっていることはほとんどないようでした。 研修で使用したダミーの取引実績とサプライヤー情報。このような情報がまとまっていること自体ほとんどないそうです。 手作業 手作業ワークでは過去の発注履歴スプレッドシートが用意されていたのでそれを頼りに選定しました。しかし注文実績があっても過去の品質や価格の妥当性はわかりませんでした。経験則がないこともあり自分は見積をもらってから考えれば良いかとなり、多めのサプライヤーに見積を出すことにしました。 今回はスプレッドシートに過去の情報がまとまっていたのでまだマシでしたが、実際にはどこにもまとまっていないことも多いとのことで、効率的な調達先の選定はかなり難しそうだと感じました。適切でないサプライヤーへの見積依頼はサプライヤー側への負担になりますし、見積に対する発注比率も下がることで、信頼関係を損ねることにもなります。 CADDi Quote CADDi Quoteを使ったワークでは過去実績の比較が非常にやりやすくなっていました。過去の発注履歴や図面の情報をCADDiに貯めてもらっていることが前提とはなっていますが、図面IDと必要な加工情報を渡せば過去の発注実績を元にサプライヤーの候補が得られます。発注価格・見積履歴・過去のトラブルなど、見積依頼先を決めるために必要な情報がまとまっていました。 少しだけ過去と異なるような部品を発注する場合、図面IDだけを見ていると新規品であるため発注先の選定を機械的にしづらいという問題があります。しかしCADDi Quoteは今回発注する図面だけではなく、その図面に類似した図面に対する発注実績も加味してくれます。この機能は図面の形状解析が高精度でできるCADDiならではの強みです。 2.見積依頼 見積先を決めた後、各サプライヤーにメールで見積依頼を送ります。図面をPDFとしてメールに添付して送ることも多いようです。サプライヤーごとに見積依頼書の中身や宛先名を変更する必要がありますし、依頼する図面が違うので正しい図面をメールに添付する必要があります。作業自体は単純で機械的に進めていけば良いのですが、ミスを誘発する要素が多いです。 手作業 ワークでは見積先として選定した宛先(実際には研修を担当していただいた先輩のメールアドレス)に見積依頼を送る作業を行いました。各サプライヤーごとに見積依頼書の作成を行い、図面を添付してメールを送るだけの作業です。単純に見える作業ですが実際には時間とストレスがかなりかかります。他社宛の依頼書を送ったり、図面を添付し忘れたりする人が当然続出しました。自分は見積先を多く選定してしまっていたのもあり、結局途中から作業が嫌になり見積依頼先をこっそり減らしてしまいました。ワークでは実務と比べると少量の部品やサプライヤーでの体験だったのですが、本当に真面目にミスなくやろうと思うと1時間以上は平気でかかりそうでした。機械的な作業に感じられ個人的には一番の苦痛でした。 見積書と大量の図面添付の例。一つ一つ図番を確認しながら添付していくのはかなり注意力を消費します。 CADDi Quote CADDi Quoteを使ったワークでは劇的に楽になっていました。上流工程である見積先選定の結果から数クリックするだけで見積依頼書の作成からメールの送信まで自動で行ってくれました。1時間はかかるであろう作業が数秒になるのには流石に感動しました。 3.見積結果の査定 見積依頼を出した後、それぞれのサプライヤーから見積結果が返ってくるのを待ちます。見積結果が出揃ってきたらその結果を取りまとめてどこに発注を出すかを決めることになります。その際にはQCD(品質、コスト、納期)全体を加味して発注先を決める必要があります。 見積にかかる時間はサプライヤーによって当然異なるので見落としがないように気を付ける必要があります。また、一つのプロジェクトの部品の調達だけをしていれば良いわけではないため、見積結果を待っている間には他の部品の調達も並行して進めなければなりません。待ち時間がある分、情報の整理や管理が必要とされるフェーズです。 手作業 ワークでは時間が決まっているので見積結果は用意されている状態でこのフェーズが始まりました。見積結果を見ていると各社フォーマットが異なっており、比較可能な状態に整理すること自体にまず時間がかかりました。 見積結果の例。カラム名が異なり機械的に統合できなかったり、条件付きでの見積結果だったりと単純に比較することが難しいです。 見積金額には条件がついていることもあり、特定の加工をする部分は除いての金額であったり、見積依頼をした部品全てをまとめて一括発注することを条件としたケースでの金額が記載されていることもあり、比較が大変でした。見積結果をスプレッドシートにまとめた後も、その判断は難しいです。結局私はシンプルに加工費を見て一番安い組み合わせを探してそこに発注するという判断をしました。実際には定性的な要素も多く絡み、トラブル対応が柔軟なサプライヤーであるかどうかや、工場の位置が近く輸送に関わる問題があるかどうかなど、複合的に考える必要があるとのことでした。 CADDi Quote CADDi Quoteから送られた見積依頼のメールには専用のフォームが用意されており、サプライヤーはそこから見積結果を返答します。その結果はCADDi Quoteに自動で連携されます。どこに見積依頼を送ったのか、どこから結果が返ってきてどこがまだなのか、見積結果はいくらなのか。こうした情報が一元的に管理されていきます。 金額による比較は当然できますし、サプライヤーごとの注意事項などもまとめておき表示できるため、定性的な評価もある程度できるようです。現状ではサプライヤー側からの条件付きの見積結果については対応できる幅に限度がありそうなため、CADDi Quoteだけで業務を完全に代替できるわけではありません。しかし、人手でやっていた面倒な作業部分をサポートして楽にするという形での貢献になっていそうでした。 体験してどう変わったか AIやシステムが引き受けるべき仕事 今回のワークでは、調達業務における「機械的作業」と「判断業務」の混在が鮮明になりました。頻繁な情報の紐付けや転記、照合といった定型作業は、画面やファイルを行き来しながらミスなく行う必要があり、想像以上に大きな負荷となります。本来、システムが担うべきなのはこうした作業です。調達担当者が真に時間を割くべきなのは、新規サプライヤーの開拓やコスト削減、サプライチェーン全体のリスク戦略といった、人の判断力や創造性が求められる本質的な仕事です。しかし、定型作業に忙殺されている限り、そこに時間は割けません。 MLエンジニアとしての視点では、複雑なアルゴリズムによる解決策に目が行きがちですが、現場ではデータの紐付け自体が手作業で行われており、それが最大のボトルネックになっています。ロジックの精度以前に、データを構造化して蓄積し、活用できる状態を作ることが最優先でした。CADDi Quoteは、こうした泥臭い紐付け作業をシステム化することで、人が本来の業務に集中できる環境を提供しています。 さらに、CADDi Quoteには蓄積されたデータを分析し、調達をより効率化するインサイトを導き出す機能も備わっています。売上の50%以上を占めることもある調達・購買において、これまでベテランの勘に頼っていた決定を、整理されたデータに基づいて行えるようになります。データの蓄積と分析によって、単なる作業効率化を超えた、より戦略的で価値のある調達の実現を目指しています。 製造業全体の最適化の難しさ 調達金額の最適化について考えを深めていくと、次第に「サプライヤー(加工会社)側の大変さ」も気になり始めました。サプライヤーは、基本的に見積依頼に無料で対応します。安すぎる金額を提示すれば受注できても赤字になり、逆に高すぎれば発注をもらえず、見積に費やした工数がタダ働きになってしまいます。図面だけでは条件が曖昧な場合、発注側とのすり合わせにかかるコミュニケーションコストも無視できません。調達業務の負担はバイヤー側だけのものではなく、製造業全体の効率化を考えるには、多数のステークホルダーの目線に立つ必要があると痛感しました。 また、「片側だけの効率化が、必ずしも全体最適につながらない可能性」も感じています。CADDi Quoteによって調達側の作業が楽になり、扱える案件数が増えれば、バイヤーがサプライヤーに送る見積依頼の総数も増えるかもしれません。もちろんシステムの精度が上がれば的外れな依頼は減るはずですが、バイヤー側の処理能力が爆発的に向上することで、結果的にサプライヤー側の対応負荷が変わらないどころか、むしろ増えてしまうというパラドックスも起き得ます。 これは確信のある主張というよりも、ワークで感じた問いです。調達プラットフォームを考えるとき、バイヤー側の効率化だけでなく、サプライヤー側にも見積対応の効率化や受注機会の可視化といった価値をセットで提供していく視点が必要なのだろうと感じました。製造業全体のエコシステムを良くするのは、一方向の最適化では済まない難しい問題です。 「モノづくり産業のポテンシャルを解放する」という大きなミッションを達成するには、このようなステークホルダーが多く複雑な問題も避けては通れません。実際CADDi Quoteもこの大きな問題を解決するための変革が現在進行形で進んでいます。1日の体験ワークでこうした問いが自然に浮かんできたこと自体が、このワークショップの設計のうまさだと感じました。 We are hiring ミクロに実際の現場の問題に向き合いながらも、長期的にはマクロに製造業全体の効率改善を目指していける環境です。 製造業が未経験であっても、このようなワークを通じてドメイン理解を深めることができます。 このような立ち位置で働けることにワクワクを感じる方がいれば、製造業未経験でもぜひお話をしましょう! tech.caddi.com
Data&Analysis部の稲葉です。 キャディは2026年6月10日(水)〜12日(金)にパシフィコ横浜で開催されたSSII2026(第32回 画像センシングシンポジウム)にプラチナスポンサーとして協賛しました。また、キャディから3名が登壇しましたので、当日の様子をレポートします。 オーガナイズドセッション:産業界における生成AIの利活用 技術動向解説セッション:CADにおけるAI分野の動向と製造業への実適用 インタラクティブセッション 終わりに オーガナイズドセッション:産業界における生成AIの利活用 生成AIが産業界でどのように価値を生み、どのような条件で実運用に乗るのかを議論するセッション です。 キャディからは、オーガナイザーとしてシニアリサーチエンジニアの福原( @gatheluck )、また講演者として私、稲葉( @mi_spindel )が登壇しました。会場には約450名と、非常に多くの方にお越しいただきました。 登壇資料はこちらです。 speakerdeck.com speakerdeck.com 私からは、Data&Analysis部として取り組んでいる生成AI活用事例として製造業RAGを紹介しました。 (本当は、他にも様々な取り組みがあるのですが、また機会を見て紹介したいと思います。) また、この中で直面した課題の一つが「各LLM/VLMモデルが我々のドメイン特有のタスクをどこまで実行できているかが不明」というものでした。 そこで、製造業LLM/VLMベンチマークとしてManuDraw-Benchを独自構築し、比較評価したお話しをしました。 皆様からのたくさんの質問をいただきまして、ありがとうございます。当日は時間の関係で回答しきれませんでしたので、ここで回答したいと思います。 Question: CADでのDXFはパースせずに図面の見た目で拾ってしまった方が総合的なコストが安いという判断でしょうか? Answer:DXFなどベクター図面のまま処理した方が、QualityやCostの観点で良いタスクは存在していると思います。ただ、結局はimage encoderが必要なタスクはありますし、処理プロセスの種類が増えることによって運用負荷も増えますので、QCDを総合的に判断して技術選定をしています。 Question:会社の固有知識については基本的にはRAGで対応となると思うのですが、RAGでは対応しきれないのではないか、モデルの Fine Tuning が必要ではないかという意見も社内であり、意見が分かれています。 Fine Tuning まで実施したというような事例はあるでしょうか? Answer:Fine Tuningまで実施した事例があるかないかの詳細はお答えできないのですが、Fine Tuningを考える前に基本はナレッジ管理とGroundingで対応できないかを先に考えるべきだと思っています。また、Fine Tuningと言えど一定のデータ量は確保しなければならないですが、機密情報の漏洩を考えると個社ごとに調整をすることになりがちです。確保できるデータ量とその労力に対する効果を検討する必要があります。 Question:過去の故障事例などは、設計改善に使用可能なレベルで、原因情報が現場から上がってくるモノでしょうか?実は、原因をLLMが把握するところに課題はないでしょうか。 Answer:おっしゃる通りで原因とその対応が上がってくる仕組みを作ることが重要ですし、そこは課題です。その解決のためにも、キャディが提供しているような部横断のデータ基盤を構築しデータを紐づけることが必要になります。 Question:P15のVLMによる3次元点群予測は1点1点を文字列として出力しているわけではないのだと思いますが、Pythonスクリプト出力などでCADモデリングさせて、表面に点を生やしているのでしょうか? Answer:1点1点を点群を文字列として出力させているのではなく、メッシュモデルとして形状を面の組み合わせで出力させてから点群サンプリングをしています。おっしゃるようにPythonスクリプト出力などでCADモデリングする方法もありますが、コーディング能力や各LLM/VLMが扱える開発環境にも依存するため、このような方法を取っています。 Question:位置やサイズなど幾何的な理解の性能は、モデルの進化を待てばある程度上がってきそうなベンチの結果になっているのでしょうか? Answer:そのあたりは今後の傾向を見てみないと何とも言えないところですので、今後もトラッキングしていきます。ただ、自然画像や文字列で表現されている位置やサイズの理解は進化が見られます。 Question:3面図とアイソメ図でVLMからみた理解度にどれだけ差があるのでしょうか?VLMの訓練にメインで使われているであろう写真データには三面図的な見え方は珍しそうに思います。写真に近そうなアイソメ図の方が理解してくれたりするのでしょうか? Answer:とても良いご指摘です。三面図をそのまま与えるよりは、斜めから見たアイソメ図の方が形状の理解力は高い印象です。ただ、アイソメ図には寸法など必要な情報が抜けているので、処理プロセスのどこかでアイソメ図にリフトするとかデータを組み合わせて利用するとか工夫は必要にはなると考えています。 共に登壇いただいたエクサウィザーズの加藤様、LayerXの松村様、素晴らしい発表をありがとうございました。エクサウィザーズ様はやはり取り組まれていることの幅が広いですし、生成AIの性能だけでなくUI/UXやガバナンスといった活用推進の重要なポイントを俯瞰して抑えられているなと感じました。LayerX様はAgentありきの業務フローに変革していく強い意思を伝えながらも、結局は当たり前をやりきれるかという話に共感しました。 登壇の様子 技術動向解説セッション:CADにおけるAI分野の動向と製造業への実適用 昨今の3D基盤モデルなどの潮流を概観し、CAD解析技術の実プロダクト適用における「研究と実践」のリアルを解説するセッション です。 キャディから、Data Platform本部長の今井( @imaimai0 )が登壇しました。こちらも500名近い方に聴講いただくことができました。 speakerdeck.com なぜ製造業においてCADが重要なのか、CADの活用において何がボトルネックになっているのか、最新研究の動向はどうなっているのかをお話ししています。 詳細は資料をご確認いただけると幸いですが、私からは特に、CAD(Geometric系)やMechanical Engineering系の研究開発をもっと盛り上げたいと思っていることをお伝えしたいです。 資料の中でもCVPRにおいてGeometric系の論文は5%程度というお話しがありましたが、製造業のGDPが日本の2割程度を占める巨大産業にも関わらず日本においても類似の研究開発が少ないように思います。我々もResearchチームの規模を拡大中ですし、共同開発や共同研究の機会も探しているところです。一緒に盛り上げていただける仲間を募集中です。 インタラクティブセッション 6/11(木)のブース出展では、多くの方にキャディブースへお越しいただきました。セッションを聴講して「話を聞いてみたい」と立ち寄ってくださった方もいらっしゃいました。 ポスター発表では、現在取り組んでいる研究開発テーマを紹介しました。注力しているテーマとして、以下の3点をご紹介しました。 設計資産全体を横断して検索するための、2D図面と3D CADモデルの埋め込み空間の統合 3D CADデータ内の加工に必要な特徴の認識 製造業ドメインに特化した独自の視覚言語モデル(VLM)評価ベンチマーク ManuDraw-Bench ManuDraw-Benchは公開されているのか?されないのか?といった質問をたくさんいただきましたが、権利の関係上公開は難しいです。ただ、業界の研究開発を促進したいと思っていますので、何かしら対応を考えていきたいとは思っています。 お話いただいた皆様、本当にありがとうございました! 終わりに SSII2026への協賛・参加を通じて、製造業領域におけるAIの研究開発の現状や、その中でのキャディのチャレンジについて知っていただく貴重な機会となりました。また、来場者の方との交流から、日々取り組む課題に対してもヒントを得られる非常に有意義な場でした。 運営の皆様、素晴らしい会をありがとうございました。 今後もキャディは技術的な挑戦を続け、画像センシング・コンピュータビジョン研究コミュニティの発展と、モノづくり産業のポテンシャル解放の両面に貢献していきたいと思っています。 また皆さんとお会いできることを楽しみにしています!
こんにちは!Data&Analysis部の今野です。 キャディは2026年6月8日(月)〜12日(金)にGメッセ群馬で開催されたJSAI2026(人工知能学会全国大会 第40回)にプラチナスポンサーとして協賛させていただきました。 この記事では5日間にわたる開催期間中の当社ブースの出展報告や、聴講した技術セッションのレポートをお伝えします。 会場紹介 ブース紹介 聴講したセッションの感想 非構造データからの情報抽出 画像音声メディア処理:視覚言語理解とマルチモーダル生成 終わりに 会場紹介 まずは会場の様子を紹介します! 場内では複数のセッション・企業展示・ポスター発表が同時並行で実施されていました。企業展示では、多様な業界に跨る159社の企業が、生成AIに限らない幅広い機械学習技術の産業応用に関して紹介をしていました。私も色々なブースを訪れ、エンジニアの皆さんと意見交換をさせていただきました。 場内の案内図 大規模言語モデル等の生成AIとともにフィジカルAIも大きなテーマの一つとなっていて、実際に入口付近ではGMOインターネット株式会社のブースの前でヒューマノイドロボットが来場者を迎えていました。 入口から見た会場の様子 ブース紹介 次にキャディのブースについて紹介します! ブースでは、主に私たちが提供している「製造業AIデータプラットフォームCADDi」の中で使用されている機械学習技術や現在取り組んでいる研究開発テーマの紹介をしました。また本サービスのデモを通して、実際に製造業の現場の課題解決にどのように貢献しているかを実感してもらえるようにしました。     注力してR&Dを行っているテーマの中でも、設計資産全体を横断して検索するための2D図面と3D CADモデルの埋め込み空間の統合・3D CADデータ内の加工に必要な特徴の認識・製造業ドメインに特化した独自の視覚言語モデル(VLM)評価ベンチマーク ManuDraw-Benchの3点について紹介させていただきました。   ノベルティとしては、キャディが各所で出展する度に大人気の「データ活用カツ」や製造業をイメージしたマイクロファイバークロスをお配りしました。   図面が文書としての特徴と幾何学的な形状が融合した複雑なデータであることやその中に潜む活用しきれていない情報の価値であったり、テキストや画像と比べるとあまり一般的でない題材の3D CADに対してどのようなアプローチを課題に応じて選択しているかについて関心を持って頂くことができました。 イベント期間中、キャディのブースに立ち寄ってくださった皆さん、本当にありがとうございました! 聴講したセッションの感想 次に、聴講させていただいたセッションの感想を簡潔にまとめます。 非構造データからの情報抽出 株式会社リクルートの中田さんとSansan株式会社の山内さんがオーガナイザとして実施されたセッションです。 複数ページの請求書 ・医薬品データ・技術マニュアル ・有価証券報告書 など、多種多様な産業の実務に溢れる「非構造データ」から、いかに正確かつ網羅的に情報を抽出するかという共通の課題に対する検証結果が発表されました 。 問題設定及びそれに対して提案された手法は様々ですが、ほとんどの発表においてVLMが手法の中心に据えられていたことが印象的でした。引き続き物体認識やOCRに特化したモデルの有効性は変わらないものの、大規模言語モデルの推論能力を生かした情報の構造化はこの領域において避けては通れないものになっていると感じました。その中で、プロンプトの工夫・画像の前処理・ステップの分割・検索システムの統合など能力を最大限に引き出すための多岐に渡る手法が検証されていました。 図面という非構造化データからの情報抽出は我々の中心にある課題の一つで、日頃の取り組みと密接に関連したセッションであり、これから解析対象のカバレッジを拡張していくに当たり非常に示唆に富んだ内容でした。 https://pub.confit.atlas.jp/ja/event/jsai2026/session/acHLauKQ 画像音声メディア処理:視覚言語理解とマルチモーダル生成 本セッションでは、画像・音声・対話データといったマルチモーダル情報を対象に、人間の認知特性や数理的アプローチを組み合わせて処理のブラックボックスを解き明かし、生成・推論の「制御性と効率性」をいかに向上させるかという共通の課題が議論されました 。 特に印象的だったのは、東京大学の大坂さんによるVLMにおける視覚アクセス境界の存在を実証した研究です 。この研究では、思考のプロセスを言語化させるChain-of-ThoughtプロンプティングをVLMで行う際、モデルが「長く考えること」で視覚的な特徴抽出を拡張しているわけではなく、実際には言語空間内での記号的推論を延長しているに過ぎないという機構を示すものでした。 過去のテックブログ でも紹介しているように、図面からより高度な情報の獲得を目指していくに当たり、VLMのメカニズムに起因する視覚情報に基づく推論の限界は我々も課題に感じており、この問題を新たな視点で捉えられる研究内容であるように感じました。 https://pub.confit.atlas.jp/ja/event/jsai2026/session/fQHM84pJ 終わりに 5日間にわたるJSAI2026への協賛・参加を通じて、AIに関するアカデミックな着目点や産業応用における最新の機械学習技術について幅広い知見を得られる貴重な機会となりました。また、会場での多くの交流や専門的なセッションから、VLMの可能性など我々が日々取り組んでいる課題に対してもヒントを得られる非常に有意義な時間でした。 スピーカーの皆さん、参加者の皆さん、運営の皆さん、素晴らしい会をありがとうございました。 今後もキャディは技術的な挑戦や現場の課題解決に向けた取り組み、そして研究開発の内容を積極的に発信し、エンジニアコミュニティの発展の一助となれるような貢献を続けていきたいと思っています。 次回、また皆さんとお会いできることを心より楽しみにしています!
TL;DR Dependabotの脆弱性アラートをSlackに流し、Devin(AIコーディングエージェント)でトリアージ・修正を行うスキームを構築しました CVE脆弱性アラートを、チームで効率的に対応できるようになりました AIの活用で「セキュリティよくわからないから放置...」が「なるほど、そういう脆弱性なのか。これなら対応できそう」に変わりました はじめに 製造業データ活用クラウド CADDi Drawer開発チームの大道です。 突然ですが、みなさんのリポジトリにDependabotのアラート、溜まっていませんか? 「あ、また増えてる...まあ今は忙しいし、後で見よう」って思っちゃいません? npm install をした際のCriticalやHighの件数を見て、「嫌なものを見たな〜」という気持ちになりませんか? こうして「後で」が永遠に来ないまま、気づけばアラートが大量に溜まっている。そんな経験、ありませんか? この記事では、DependabotとDevin(AIコーディングエージェント)を組み合わせて、CVE対応が「回り続ける」仕組みを作った話を紹介します。セキュリティの専門知識がなくても「とりあえずやってみよう」と思えるような内容を目指しているので、気軽に読んでいただけたらうれしいです。 背景:なぜスキームを作ろうと思ったか 「気づいた人がやる」運用の限界 もともと私たちのチームでは、CVE脆弱性アラートへの対応は「気づいた人がやる」スタイルとなってしまっていました。 Dependabotからのメール通知は飛んでくるのですが、日々の開発メールに埋もれてしまい、気づかないことがほとんどです。たまたま気づいた誰かが善意で対応してくれる。でも、その「誰か」がいない日は、当然誰もやりません。「誰か」がやってくれたことに対する感謝も埋もれがちです。 放置するリスクは一定ある 実際、多くの脆弱性は到達可能性(Reachability)がなく、自分たちのコードから該当の関数を呼んでいないケースがほとんどです。この経験が積み重なると、「今回も大丈夫か」と思ってしまうのは、ある意味エンジニアとしての経験則でもあります。 ただ、最近はリスクを巡る状況も変わってきています。 AIの進化によって、攻撃用のコードを生成するハードルが大きく下がりました。以前であれば高度な知識が必要だったエクスプロイトの作成も、今ではAIを使えば比較的容易にできてしまう時代です。その結果、ゼロデイ攻撃のスピードは加速し、公開される脆弱性の件数自体も増加傾向にあります。 そして、もしセキュリティインシデントが起きてしまった場合、事業へのインパクトは計り知れません。顧客データの漏洩、サービスの停止、信用の失墜...。「大丈夫だろう」の一言では済まされない世界です。 だからこそ、エンジニアとしては微妙なストレスを抱えているのも事実ではないでしょうか。「たぶん大丈夫」と思いつつも、心のどこかで「もしこれがインシデントにつながったら...」とビクビクしている。そのストレス、放置しているアラートの数に比例して大きくなっていませんか? DependabotのPR自動作成機能に対する要望 Dependabotには検知した脆弱性に対して、自動でPRを作成してくれる便利な機能がありますね。 ですが、「もっとこうなったらよいな」と思うものもありました。 変更の妥当性を判断する情報が乏しい : 「このパッケージをx.x.xからy.y.yに上げます」としか書かれていない。破壊的変更があるのか、自分たちのコードに影響があるのか、PRを見ただけでは判断しづらい プロジェクト固有のルールを守ってくれない : 私たちのチームでは「lockfileの直接編集禁止」「overridesは原則使わない」といったガイドラインがありますが、Dependabotにそれを強制する術がない (もしかしたらRenovateなら設定を駆使すればこのあたりは回避する方法があるかもしれませんね) 結局、PRが来ても「これ、マージして大丈夫...?」と不安になり、確認コストが高くなりがちでした。 セキュリティの知見、チームで底上げしたい もうひとつ、スキームを作りたかった理由があります。 脆弱性対応は、最終的には人間が判断する必要があります。「この脆弱性は本当に自分たちのアプリに影響があるのか?」「Hotfixを当てるべきか、通常リリースでいいのか?」。こうした判断は、AIに丸投げするわけにはいきません。 そもそも、「どのような脆弱性なのか」がわからないという問題もあります。SQLインジェクション、XSS、プロトタイプ汚染...。名前は聞いたことがあっても、具体的にどういう攻撃手法で、どうガードすればいいのか。アプリケーションエンジニアとして、こうした知識は本来持っておいたほうがいいはずです。 そういった知見は、自分たちのアプリケーションに対してコードを書いたり、AIが書いたコードをレビューする際にも役に立つはずです。 ただ現実には、こうしたセキュリティの知識を、アプリケーションエンジニアが体系的に学ぶ機会はそう多くありません。業務の中で自然と身につく類のものでもない。だからこそ、CVE対応のプロセス自体を「学びの機会」にできないか、という思いもありました。 実際に組んでみたスキーム 全体のフローは以下のようになっています。 graph TD A[Dependabot検知] --> B{Severityがチームの既定値以上?} B -- No --> C[無視] B -- Yes --> D[Slack通知] D --> E[Code Ownerがアサイン] E --> F[担当者がトリアージ] F --> G{Hotfix必要?} G -- Yes --> H[即時対応・特殊フロー] G -- No --> I[通常修正] I --> J[AIレビュー実施] J --> K{Humanレビュー必要?} K -- Yes --> L[Human Review] K -- No --> M[Merge & Slack報告] L --> M それぞれのステップを見ていきましょう。 Step 1. まずSlackに流す ― 「気づく」の自動化 最初にやったのは、Dependabotのアラートを Slackに流すこと でした。 GitHub Actionsで1時間ごとに新規アラートをチェックし、Incoming Webhookで専用のSlackチャンネルに通知します。チームリーダーへのメンション付きなので、見逃しにくい仕組みです。 やっていること自体はシンプルですが、 「普段見ているインターフェースに情報を届ける」 というのが大事なポイントです。メールは埋もれますが、Slackは(良くも悪くも)みんな見ています。通知先を変えるだけで、「気づかなかった」という問題がほぼなくなりました。 Step 2. Devinにトリアージを依頼 ― 「理解する」のハードル低下 アラートに気づいたら、次はトリアージです。 ここで活躍するのがDevinです。Slackのスレッドから、こう打つだけです。 @Devin !triage_cve CVE-XXXX-XXXXX Devinは事前に設定したPlaybookに従って、以下のことを調べてくれます。 脆弱性の概要と深刻度 : CVEの内容を平易な日本語でまとめてくれる 到達可能性(Reachability) : 脆弱性のある関数を自分たちのコードが実際に呼んでいるかを調査 devDependenciesかどうか : 開発環境のみの影響なら緊急度が下がる Hotfixの必要性 : 上記を踏まえて、通常リリースで良いか、Hotfixが必要かの判断材料を提示 SlackにDevinがトリアージ結果を投稿した様子 これが本当にありがたいのです。以前は「CVEの英語ドキュメントを頑張って読み解く」必要がありましたが、今はDevinが要点をまとめてくれるので、エンジニアは 「判断する」ことに集中 できます。 セキュリティに詳しくないメンバーでも、Devinのレポートを読めば「なるほど、これはdevDependenciesだけだから緊急度は低いな」「これは実際にリクエスト処理で使っているライブラリだから早めに対応しよう」と判断できるようになりました。 ただし、最終的な判断は必ず人間が行います。 これはエンジニア組織のルールとして、キャディ株式会社 共同創業者兼最高技術責任者 CTOの小橋から明確なメッセージが発信されています。AIは優秀ですが「責任」はとってくれません。 Slackのスレッドでやりとりされるので、迷ったらリーダーや有識者に相談するのもやりやすいです。 Step 3. Devinに修正も依頼 ― 「直す」の効率化 トリアージが終わったら、修正です。ここでもDevinに頼ります。 @Devin !Patch_cve DevinはPlaybookに定義された修正ガイドラインに沿って、PRを作成してくれます。 私たちのチームでは、以下のような修正ルールを定めています。 セマンティックバージョニングの範囲内で更新する : メジャーバージョンを勝手に上げない。 overridesは原則使わない : 親パッケージが指定した範囲内で一時的に引き上げるために使うのであればよいが、レンジ外のバージョンへ無理やり上書きすると、テストされていないパッケージの組み合わせになるリスクがある。 Dependabotにはこうしたプロジェクト固有のルールを守らせることが難しかったのですが、DevinならPlaybookに定義しておくことで遵守させることができます。これが、修正をDevinに任せている大きな理由です。 PRが作成されると、AIレビュー(Devin ReviewまたはCopilot Review)が自動で実施されます。もちろん、Humanのレビュアーも必ずアサインします。 Step 4. 週次定例でカバー ― 仕組みの穴を塞ぐ どんな仕組みにも漏れはあります。リーダーが休みでアサインが遅れた、Devinの修正がうまくいかなかった、親パッケージが未対応でそもそも直せない...。 こうした漏れを拾うために、週次の定例MTGで滞留しているアラートをチェックしています。 直接対応できないケース(間接依存のパッケージに修正パッチが出ていない場合など)は、チケットを起票して管理します。フィルターで一覧化し、定例MTGにて状況を確認しています。 そしてもうひとつ、この場で 対応してくれたメンバーをちゃんとレポートに載せる ようにしています。脆弱性対応は地味な作業です。やっても新機能のリリースのように派手に褒められることは少ない。だからこそ、「あなたが対応してくれたおかげで安全になりました」と可視化することが大切だと考えています。 振り返って重要だと思ったこと このスキームを運用してみて、いくつか大事だと感じたことがあります。 普段見る場所に通知する、それだけで変わる 正直、これが一番効果がありました。 メールからSlackに通知先を変えただけで、アラートへのリアクション速度が段違いに変わりました。人は「目に入るもの」には反応するんですよね。「気づきの導線設計」は、ツールの選定以上に大事かもしれません。 AIがセキュリティのラーニングコストを下げてくれた CVEの内容を理解し、影響範囲を調査し、修正方針を考える。以前はこの一連の作業が重くて、「できる人」に偏りがちでした。 AIが一次調査を肩代わりしてくれることで、セキュリティに詳しくないメンバーでも対応できるようになりました。AIは「判断するための情報収集コスト」を下げてくれる存在です。 「セキュリティの知見がないから自分には無理」というハードルが、チーム全体で下がったのは大きな変化でした。 でも、最後は人間が判断する AIが便利だからといって、すべてを任せるわけにはいきません。 Hotfixを当てるかどうかの判断 破壊的変更を含むアップデートの影響範囲の最終確認 本当にマージして問題ないかの判断 これらは人間の責任です。そして、AIのおかげで「判断に必要な材料を集めるコスト」が下がっているからこそ、人間が判断そのものに集中できるようになったとも言えます。 やった人をちゃんと認知する 繰り返しになりますが、脆弱性対応は報われにくい作業です。ユーザーに見える新機能を作るのとは違って、「何も起きなかった」が成果になります。 だからこそ、週次レポートで対応者を可視化する仕組みは大事にしています。「ありがとう」が自然に言える仕組みは、運用を続けるための燃料です。 おわりに 「脆弱性のアラート、見なかったことにしよう...」 この記事を読んでくださった方の中にも、そんな経験がある方がいるかもしれません。 でも、仕組みを作ってみて思ったのは、 負債はちゃんと細かく返していったほうが、精神衛生上いい ということです。溜めれば溜めるほど重くなるし、見ないフリをしている間のストレスは意外と大きい。 完璧な運用じゃなくていいんです。「回り続ける仕組み」をまず作ること。そしてAIの力を借りれば、セキュリティという重たいテーマのハードルも、思ったよりずっと下がります。 この記事が、みなさんの脆弱性対応の第一歩になれば幸いです。
はじめに こんにちは。Reliabilityグループ QAエンジニアのmeguroです。 最近、開発現場でAI Agentがコードを書く場面が増えてきました。Claude CodeやGitHub Copilotを使えば、APIサーバーの実装が数分で完成します。テストコードも自動生成される。開発スピードは確かに上がっています。 しかし、QAエンジニアとして、あることが気になりました。 「AI Agentが生成したテストコードの品質は、どうやって担保すればいいのか?」 同じAI Agentなのに、時には網羅的なテストを書き、時には重要なケースが抜け落ちている。この品質のばらつきは、一体何が原因なのか。 QAとして、AI時代のテスト品質をどう管理すべきか。その答えを探すため、実験を行いました。同一のタスク管理APIを3パターンの仕様書で実装し、生成されたテストコードの品質を定量的に比較してみました。 結果は明確でした。 仕様書の書き方次第で、AI生成テストの品質が2〜3倍変わる。 AI Agentは「書かれたことは確実にテストする」が「書かれていないことはテストしない」という特性があります。 つまり、 仕様の完全性が品質を決定する のです。 この記事では、その実験の過程と、品質を担保するための具体的な知見をお伝えします。 実験設計:同一APIを3パターンの仕様で実装 実験の概要 今回の実験は、特定の仮説を検証するのではなく、「AI Agentの特性から逆算して、品質を担保するために必要な要素を導き出す」という演繹的なアプローチをとりました。 AI Agentには「指示されたことは忠実に実行するが、行間は読まない」という特性があります。この前提(公理)から、「人間が暗黙的に行っている思考」をどの段階まで具体化して与えれば、納得する品質に届くのかを検証するため、以下の3段階のPoCを設計しました。 3つのPoC poc-1-minimal(最小限仕様) エンティティ定義 エンドポイント一覧 エラーマッピング表 認証・認可ルール poc-2-tdd(TDD手法追加) poc-1の仕様 + TDD手法の指示 実装順序の指定 具体的なテストコード例 poc-3-strategy(テスト戦略追加) poc-1の仕様 + テスト戦略ドキュメント 5つの品質指標の定義 検証すべき仕様項目の具体例 評価指標(5つの品質メトリクス) テストコードの品質を測るため、過去のバグを分析し、実際に見落とされがちな5つの指標を設計しました。 Schema Coverage(スキーマカバレッジ) 定義:レスポンスの全フィールドが検証されているか 計測方法:検証済みフィールド数 / 全フィールド数 目標:100% Post-Condition Coverage(事後条件カバレッジ) 定義:書き込み操作で「対象レコード・関連レコード・不変条件」の3点が検証されているか 計測方法:検証済み事後条件数 / 全事後条件数 目標:100% Error Mapping Consistency(エラーマッピング一貫性) 定義:HTTPステータスコードが全エンドポイントで一貫しているか 計測方法:実装済みエラーパターン数 / 全エラーパターン数 目標:100% Authorization Coverage(認可カバレッジ) 定義:全ロール×全エンドポイントの組み合わせがテストされているか 計測方法:テスト済み組み合わせ数 / 全組み合わせ数 目標:100% Abnormal Test Ratio(異常系テスト比率) 定義:4xx/5xxテストの割合 計測方法:異常系テスト数 / 全テスト数 目標:60%以上 実験結果:仕様の質が品質を決める さて、実際にAI Agentに実装させ、その実験結果を見ていきましょう。 定量結果サマリー 指標 poc-1 poc-2 poc-3 戦略 改善率 Schema Coverage 43% 61% 100% 2.3倍 Post-Condition Coverage 22% 39% 100% 4.5倍 Error Mapping Consistency 31% 90% 97% 3.1倍 Authorization Coverage 48% 80% 100% 2.1倍 Abnormal Test Ratio 53% 58% 48% 0.9倍 注目すべき3つの発見 結果を分析していて、特に興味深かった点を3つ紹介します。 発見1:TDD手法はError Mapping Consistencyで劇的改善(31%→90%) poc-2では、 CLAUDE.md に具体的なテストコード例を含めました。 その結果、AI Agentはこのパターンを全8エンドポイントに自動適用し、401テストが網羅的に生成されました。一方、poc-1では「認証なしは401を返す」とテキストで書いていましたが、テストは1エンドポイントにしか書かれませんでした。 学び:抽象的な指示(「全エンドポイントで認証テストを書く」)よりも、具体的なコード例の方がはるかに効果的です。 発見2:テスト戦略はスキーマ・認可で100%達成 poc-3の docs/test-strategy.md には、全32通りの検証パターンをマトリクスで指示しました。 結果、AI Agentはその表を見て「全8エンドポイント × 4ロール = 32パターン」のテストを自動生成してくれました。さらに、Schema Coverageについても、Protoの全フィールドを1つずつ検証するテストコードを生成しました。 学び:「何を検証すべきか」が具体的に列挙されていれば、AI Agentは漏れなく実装してくれます。 発見3:poc-1でも53%の異常系テストは書かれる 意外だったのは、最小限の仕様でも、異常系テストは過半数書かれていたことです。これは、エラーマッピング表(404/400/403/500の定義)を明示していたおかげでした。 ただし、一貫性に欠けていました。例えば POST /projects には401テストあり PATCH /tasks には401テストなし 学び:「何をテストすべきか」の明示的な指示がなければ、AI Agentは恣意的にテストを選んでしまいます。 なぜAI Agentには異なる仕様が必要なのか? ここまでの結果を見て、「なぜこんなに差が出るのか?」と疑問に思われた方もいるかもしれません。 その理由は、人間とAI Agentの根本的な違いにあります。 人間とAI Agentの根本的な違い 人間の開発者: 仕様(80%完成) + 暗黙知 + 経験 + コミュニケーション → 完全な実装 AI Agent: 仕様(100%必須) → 実装品質 品質を担保するために意識すべき6つの観点 この実験を通じて明らかになったのは、「頭の中で考えている品質の視点」を、明示的に定義することの重要性です。 従来、エンジニアは「暗黙知」を持っていました。 人間の開発者なら、レビューやコミュニケーションで補えました。 しかし、AI Agent時代では、思考プロセスを仕様書に明記する必要があります。 以下、品質を担保するために意識すべき6つの観点と、実験で得られた知見を紹介します。 品質観点1: エラーハンドリングの一貫性 なぜ重要か エンドポイントごとにエラーハンドリングがバラバラだと、以下の問題が起きます ユーザー体験の不整合:同じ「認証なし」なのに、401と500が混在 監視設計の困難:どのステータスコードを監視すべきか曖昧 障害対応の遅延:本番で「これは404か500か?」の切り分けに時間がかかる 「全エンドポイントで一貫したエラーマッピング」を担保する必要があります。 従来の検証方法と課題 実装後にテストコードをレビュー 手動テストで各エンドポイントのエラーレスポンスを確認 → 8エンドポイント × 3エラーパターン = 24ケースを人手で確認 → 実装後に気づいたときは既に手遅れ 品質を設計で担保する 実験では、以下を事前に定義しました: ## エラーマッピング(全エンドポイント共通) | エラー種別 | HTTPステータス | 内部エラー | |-----------|----------------|-----------| | 認証なし | 401 | JWT検証失敗 | | リソース不存在 | 404 | sql.ErrNoRows | | DB障害 | 500 | その他すべて | ## テスト要件 - 全8エンドポイントで401テストを実装 - 全8エンドポイントで404テストを実装 - 全8エンドポイントで500テストを実装 実験結果: Error Mapping Consistencyが31%→97%に改善 エラーハンドリングの一貫性は、品質の基本です。実装前に表として定義することで、レビュー工数を削減し、品質を担保できます。 品質観点2: 事後条件の検証 — 副作用を見逃さない なぜ重要か 書き込み操作(POST/PATCH/DELETE)では、対象レコードだけでなく、関連レコードや不変条件も変化します。 典型的な見落とし - POST /projects でProjectは作成されるが、作成者がMemberに追加されない - DELETE /projects でProjectは削除されるが、関連するTaskが残ってしまう - 不変条件の破壊(例:オーナーが0人のプロジェクトが存在する) 「対象レコード」「関連レコード」「不変条件」の3点セットで検証する必要があります。 従来の検証方法と課題 テストコードで「レスポンスが200 OK」だけ確認 → DBの状態は検証されていない → 本番で「データが不整合」のバグ報告 → レスポンスが正しくても、DB状態が壊れている 品質を設計で担保する 実験では、事後条件を3点セットで定義しました ## 事後条件(POST /projects) 以下の3点をIntegration Testで検証すること: 1. 対象レコード: Project行が1件作成される(status=active) 検証SQL: SELECT status FROM projects WHERE id = $1 2. 関連レコード: Member行が1件作成される(role=owner, user_id=作成者ID) 検証SQL: SELECT COUNT(*) FROM members WHERE project_id = $1 AND role = 'owner' 期待値: 1 3. レスポンス: Proto全フィールドが含まれる 実験結果: Post-Condition Coverageが22%→100%に改善(4.5倍) 書き込み操作の品質は「レスポンスが正しい」だけでは担保できません。DB状態を直接検証する設計が必要です。 品質観点3: スキーマの完全性 — フィールド欠落を防ぐ なぜ重要か APIレスポンスで「主要なフィールドは返るが、一部のフィールドが欠落」というバグは頻発します。 典型例 - Protoにstatusフィールドを追加したが、ハンドラで返し忘れ - フロントエンドでundefinedエラー - 本番リリース後に発覚 「Protoの全フィールドがレスポンスに含まれる」を担保する必要があります。 従来の検証方法と課題 テストで主要フィールド(id, name)だけ検証 → 追加したフィールドは検証されない → 「主要な機能は動く」が「追加機能が壊れている」 品質を設計で担保する 実験では、全フィールドを明示的に列挙しました ## Contract Test要件 GET /projects/:id のレスポンスで、以下の全フィールドが非ゼロ値で返ることを検証: - id (string) - name (string) - description (string) - status (enum: active/archived) - owner_id (string) - created_at (timestamp) - updated_at (timestamp) テストコード例: assert.NotEmpty(t, project.ID) assert.NotEmpty(t, project.Name) assert.NotEmpty(t, project.Status) // ... 全7フィールドを検証 実験結果: Schema Coverageが43%→100%に改善(2.3倍) フィールド欠落は、型チェックでは検出できません。全フィールドを明示的に検証する設計が必要です。 品質観点4: 認可の網羅性 — 権限チェック漏れを防ぐ なぜ重要か 認可のバグは、セキュリティインシデントに直結します。 典型例 - オーナーだけが実行できるDELETE /projects/:idを、メンバーも実行できてしまう - 本番で「他人のプロジェクトを削除できる」バグ報告 - → セキュリティインシデント 「全ロール × 全エンドポイントの組み合わせ」を検証する必要があります。 従来の検証方法と課題 「オーナーは実行できる」だけテスト → 「メンバーは実行できない」はテストされない → 正常系だけでは、セキュリティホールを防げない 品質を設計で担保する 実験では、全組み合わせを表で定義しました: ## Authorization Coverage: 100% 全8エンドポイント × 4ロール = 32パターンを検証 | エンドポイント | 認証なし | 非メンバー | メンバー | オーナー | |--------------|---------|----------|---------|---------| | GET /projects | 401 | 200 | 200 | 200 | | POST /projects | 401 | 201 | 201 | 201 | | PATCH /projects/:id | 401 | 403 | 403 | 200 | | DELETE /projects/:id | 401 | 403 | 403 | 204 | (以下、Task/Memberエンドポイントも同様) 実験結果: Authorization Coverageが48%→100%に改善(2.1倍) 認可の品質は「正常系が動く」だけでは担保できません。全組み合わせを明示的に検証する設計が必要です。 品質観点5: リスクベースのテスト優先度 なぜ重要か すべてのバグが同じ重要度ではありません。 - 金銭的損失につながるバグ(決済、課金) - セキュリティインシデントにつながるバグ(認可、認証) - ユーザー体験を大きく損なうバグ(データ削除、不整合) 「リスクの高い領域ほど、異常系テストを厚くする」必要があります。 品質を設計で担保する 実験では、リスク評価を異常系テストのパターン数に変換しました ## リスク評価とテスト戦略 ### 高リスク領域: DELETE /projects(データ削除) 異常系テスト5パターン必須: 1. 他人のプロジェクトを削除 → 403 2. 存在しないプロジェクトを削除 → 404 3. DB障害時の挙動 → 500 4. トランザクション途中でタイムアウト → ロールバック確認 5. 外部キー制約違反 → 適切なエラーメッセージ 「重大度:高」という抽象的な表現ではなく、具体的なテストパターン数で優先度を表現します。 品質観点6: 暗黙知の明示化 なぜ重要か 「ベストプラクティス」「適切に実装」といった抽象的な要求は、人によって解釈が異なります。 典型例 - 「削除は適切に実装」→ 物理削除を実装 - → 監査ログ要件を満たせない - → 「適切」の定義が曖昧だった 「なぜそうするのか」を明示する必要があります。 品質を設計で担保する 実験では、「〜しない理由」も記載しました ## 削除の実装方針 ソフトデリートを使用すること 理由: - 監査ログ要件:誰が、いつ削除したかの記録が必要 - 誤削除時の復旧:ユーザーからの「間違って消した」問い合わせに対応 - データ分析:削除されたプロジェクトの傾向分析が必要 物理削除しない理由を明記することで、実装時の判断ミスを防ぐ 暗黙知を明示することで、「なぜこの仕様なのか」が後から読んでも分かる仕様書になります。 品質を担保するために必要なこと ここまで紹介した「6つの品質観点」を実務で体現するために、AI Agent時代には以下の「5つの力」が求められます。 ①品質観点を言語化する力 頭の中にある「これはテストすべき」という直感を、仕様書に落とし込む。 ❌「適切にテストすること」 ✅「全8エンドポイント × 3エラーパターン = 24テスト必須」 ②網羅性を設計する力 「全エンドポイント」「全フィールド」という曖昧な表現を、数値化・具体化する。 ❌「全エンドポイントで一貫」 ✅「8エンドポイント × 4ロール = 32パターン」 ③リスクを評価する力 どこを厚くテストすべきかを判断し、異常系のパターン数で表現する。 高リスク領域(DELETE操作)→ 異常系5パターン 低リスク領域(GET操作)→ 異常系3パターン ④副作用を見抜く力 対象レコードだけでなく、関連レコードや不変条件も検証対象として定義する。 対象レコード + 関連レコード + 不変条件 = 3点セット ⑤暗黙知を明示化する力 「なぜそうするのか」を書くことで、実装時の判断ミスを防ぐ。 「ソフトデリートを使う(理由:監査ログ要件、誤削除時の復旧)」 これらは、AI Agentには判断できません。 まとめ AI Agent時代でも、品質を担保するのはエンジニアの役割です。 ただし、その方法が変わりました。 従来:実装後にテストを書き、レビューでバグを見つける AI時代:実装前に「何を検証すべきか」を仕様として定義する 「暗黙知」として持っている品質観点を、明示的に仕様書に落とし込むこと。 これがAI時代の品質保証になってくるのではないでしょうか。 質問やフィードバックがあれば、ぜひコメントで教えてください!
はじめに 10年以上前、単語を単なる記号ではなく、意味の近さを「距離」で表現する高次元ベクトルへと数値化する技術「Word2Vec」が登場しました。これが、AIの重要技術の1つである「埋め込み(Embedding)技術」が研究界で大きな注目を集める契機となりました。 その後、この技術は単語から文章、さらには画像へと適用の幅を広げ、AIは次第にそれらの「意味」を理解し始めます。類似する文章や画像を検索する精度は飛躍的に向上しましたが、当時はまだ、文章と画像を同じ尺度(共通のベクトル空間)で扱うことは困難でした。 その壁を打破したのが、2021年にOpenAIが発表した「CLIP」です。CLIPは文章と画像で共通の埋め込みを作成することに成功し、いわば両者の意味を繋ぐ「共通言語」を誕生させました。このマルチモーダル埋め込みの成功を皮切りに、現在は音声や3Dデータなど、あらゆる情報を共通の埋め込み空間で扱う研究が加速しています。 現在、製造業においてもこれらの技術は浸透しつつあります。画像埋め込みを用いた「図面類似検索」や、テキスト埋め込みを応用した「RAG(検索拡張生成)」などがその代表例です。しかし、現状では単一モーダルの活用が主流であり、マルチモーダル埋め込みの真の社会実装はまだ始まったばかりと言えるでしょう。 マルチモーダル埋め込みの研究分野での盛り上がりを鑑みれば、今後この領域が製造現場に社会実装されていくのは間違いありません。本記事では、マルチモーダル埋め込みの概要から最新の研究事例、そして製造業における活用の未来について詳しく解説します。 「埋め込み」とは? この節では、そもそも「埋め込み」「マルチモーダル埋め込み」とは何かを説明します。 「埋め込み」とは、一言で言えば「現実世界のあらゆる情報を、AIが計算しやすい『多次元空間上の座標(ベクトル)』に変換する技術」のことです。人間が言葉の意味を理解するように、コンピュータに「概念の近さ」を数値として教え込むプロセスだと考えると分かりやすいでしょう。 埋め込みを使うことで、次の3つのことを実現します。 1. 「記号」を「座標」に変える コンピュータは本来、数字しか扱えません。かつて、AIに「リンゴ」という言葉を教える際は、単に「101番」という背番号(ID)を割り振るだけでした。しかし、これでは「リンゴ」と「ナシ」が似ているという意味のつながりを計算できません。 埋め込み技術は、エンコーダーを使い、例えば「リンゴ」を [0.12, -0.54, 0.88, ...] という数百次元の数値の並び(ベクトル)に変換します。この数値は、その対象が持つ「甘み」「赤い」「果物」といった無数の特徴を凝縮した地図上の座標のようなものです。 2. 「近さ」で意味を測る データが座標(ベクトル)になると、データ同士の「距離」を計算できるようになります。 意味が似ているもの: 空間上で近くに配置される(例:「犬」と「猫」) 意味が異なるもの: 空間上で遠くに配置される(例:「犬」と「数学」) 3. 多様なデータを一箇所に集める(マルチモーダル) 最新の埋め込み技術の凄さは、テキストだけでなく、画像、音声、センサーデータまでも同じ空間に並べられる点にあります。 例えば図1のように、「犬が走っている」というテキスト、ゴールデンレトリバーの画像、「ワンワン!」という鳴き声、そして走る犬の動画。これらは入り口こそバラバラですが、それぞれの専用エンコーダーによって「ベクトル(数値の並び)」へと変換されます。 変換されたデータは、多次元の「埋め込み空間」の中に配置されます。この空間の最大の特徴は、「意味が似ているものほど、近くに配置される」という性質です。この例では、データ形式が違っていてもすべて「犬」という同一のコンセプトに関連しているため、AIの頭の中(空間)では同じエリアにギュッと集まります。 これにより、AIはデータ形式の壁を越えて、「これは『犬』という本質的な概念だ」と一貫して捉えることが可能になるのです。 図1 マルチモーダル組み込み 犬の例 埋め込みの歴史 埋め込み技術は、わずか10年余りの間に驚くべき進化を遂げてきました。ここでは、その進化の道筋を象徴する3つのマイルストーンを紹介します。「単語だけ」から始まった技術が、「画像と言葉の融合」を経て、「3Dシーンの丸ごとアライメント」へと到達する物語です。 Word2Vec ── 言葉に「座標」を与えた原点 Mikolov et al., 2013 2013年、Googleの研究チームが発表したWord2Vecは、「言葉の意味を計算できるようにした」という点で、自然言語処理の歴史を塗り替えました。 それまでの限界:One-hotベクトル Word2Vec以前、コンピュータに単語を教える方法は非常に素朴でした。「One-hotベクトル」と呼ばれる方式では、語彙数と同じ長さのベクトルを用意し、該当する単語の位置だけを「1」、残りをすべて「0」にします。 例えば語彙が5万語あれば、「リンゴ」は5万次元のベクトルの中で、たった1箇所だけ「1」が立っている状態です。この方式には2つの致命的な欠陥がありました。 次元の呪い: 語彙が増えるほどベクトルが膨大かつ疎(スカスカ)になり、計算効率が極めて悪い。 意味の不在: 「リンゴ」と「ナシ」のベクトルは完全に直交しており、距離を測っても「近い」とも「遠い」とも言えない。つまり、意味の類似度が一切計算できない。 Word2Vecの発想の転換 Word2Vecは、この問題をシンプルかつ強力なアイデアで解決しました。「ある単語の意味は、その周囲に出現する単語によって決まる」という言語学の仮説(分布仮説)に基づき、大量のテキストから単語の「使われ方のパターン」を学習したのです。 具体的には、ニューラルネットワークに以下の2つのタスクのいずれかを解かせます。 CBOW: 「昨日 ___ を食べた」→ 空欄に入る単語を、周囲の文脈から予測する。 Skip-gram: 「リンゴ」という単語から、その周囲に出現しやすい単語(「食べた」「赤い」「果物」など)を予測する。 このタスクを何億もの文章で繰り返すことで、各単語は数百次元の密なベクトル(分散表現)へと圧縮されます。「リンゴ」と「ナシ」は似た文脈で使われるため、自然と空間上で近くに配置されるのです。 なぜ衝撃的だったのか Word2Vecが世界を驚かせたのは、学習されたベクトルが「意味の足し算・引き算」を可能にしたことです。 「王様」−「男」+「女」=「女王」 この計算が成立するということは、ベクトル空間の中に「性別」「階層」といった意味の軸が自然に形成されていることを意味します。人間が明示的に教えたわけではなく、AIが大量のテキストから自ら意味構造を発見した ── この事実は、後の埋め込み技術すべての出発点となりました。 CLIP ── 画像と言葉の壁を壊した転換点 Radford et al., 2021 Word2Vecが「単語同士の距離」を測れるようにしたのに対し、2021年にOpenAIが発表したCLIP(Contrastive Language-Image Pre-training)は、さらに大胆な問いに挑みました。「画像と言葉の距離を測ることはできないか?」── CLIPの答えは、驚くほどシンプルでした。 4億枚の「画像+キャプション」で学ぶ CLIPは、画像用とテキスト用の2つの独立したエンコーダーを備えています。学習に使ったのは、インターネットから収集した4億セットの「画像とキャプションのペア」。この膨大なデータを使い、以下のルールでベクトル空間を鍛え上げます。 正しいペアは近づける: 犬の画像と「走る犬」というキャプション → ベクトルを空間上で近くに配置。 無関係なペアは遠ざける: 犬の画像と「青い車」というキャプション → ベクトルを空間上で引き離す。 これが「対照学習(Contrastive Learning)」です。正解と不正解を対比させながら空間を整えていくことで、画像とテキストが同じ座標系で「意味的に比較可能」になります。 「見たことがないもの」を理解できる CLIPの最も画期的な特徴は、「Zero-shot性能」と呼ばれる能力です。従来の画像認識モデルは、「犬」「猫」「車」などのラベルを事前に教え込む必要がありました。学習していないカテゴリは認識できません。 CLIPは違います。ラベルではなく「言語の概念そのもの」を学習しているため、一度も見たことがない物体でも、テキストで説明すれば識別できます。例えば「三角形の赤い標識」と入力すれば、学習データに含まれていなくても該当する画像を見つけ出せるのです。 この「データ形式を超えた意味の理解」こそ、マルチモーダル埋め込みの幕開けでした。 CrossOver ── 3Dシーンを丸ごと対応づける最前線 Sarkar et al., 2025 (CVPR 2025 Highlight) CLIPが画像とテキストの2つのモダリティを結びつけたのに対し、2025年にコンピュータビジョンの最高峰学会CVPR 2025でハイライト論文に選ばれたCrossOverは、さらに多くのモダリティへと対象を広げました。RGB画像、点群(Point Cloud)、CADメッシュモデル、フロアプラン、テキスト── これら5種類ものデータを1つの共通空間にまとめ上げ、3Dシーン全体をモダリティ横断で対応づける(アライメントする)フレームワークです。 従来手法の限界:「完璧なデータ」がないと動かない これまでの3Dマルチモーダル学習には、大きな制約がありました。「椅子」「テーブル」といったオブジェクト単位で厳密にラベルを付けたアノテーションが必要で、しかも全てのモダリティ(画像・点群・CADなど)が揃っていなければ学習できなかったのです。 しかし現実のAR/VRやロボティクス、建設モニタリングといった現場では、そんな理想的なデータが揃うことはほとんどありません。ある部屋は3Dスキャンデータしかない、別の部屋はCAD図面と写真だけ ── こうした「歯抜け」のデータが当たり前です。 CrossOverの3つの工夫 CrossOverは、3つの工夫でこの「現実の壁」を突破しました。 1つ目は、 「シーンまるごと」で対応づける こと。従来はオブジェクト1つ1つに丁寧なラベルを貼る必要がありましたが、CrossOverは「部屋全体」「フロア全体」といったシーン単位で異なるモダリティを結びつけます。いわば、家具を1個ずつ辞書登録するのではなく、部屋全体の写真を1枚見せて「ここにあるもの全部まとめて覚えて」と教えるようなイメージです。 2つ目は、 データが歯抜けでも学習できる こと。5種類全てのデータが揃っている必要はありません。「この部屋は点群とテキストしかない」「あの部屋はRGB画像とCADだけ」── そんな不完全なデータでも、手持ちのモダリティだけで埋め込みを計算し、学習に組み込めます。 3つ目は、 段階的に賢くなる学習戦略 です。各モダリティに専用のエンコーダを用意し、最初は少ないデータの組み合わせから始めて、徐々に複雑な組み合わせへと学習を積み上げていきます。完璧なデータを待たずに、今あるデータから着実に知識を蓄積できるのです。 何ができるようになるのか CrossOverにより、データの形式を飛び越えた3D検索・解析が現実のものになります。 例えば、フロアプラン(間取り図)を検索クエリとして入力すれば、対応する3D点群データが検索結果として返ってくる。逆に、点群データから対応するフロアプランやCADモデルを見つけ出すことも可能です。さらに、明示的に学習していないモダリティの組み合わせ(例:フロアプラン ↔ テキスト)でも検索が機能する「創発的な汎化能力」が確認されています。 これはまさに、製造業や建築業が待ち望んでいた技術です。次の章では、この技術が製造現場でどのように活用されていくのかを具体的に見ていきます。 製造業における活用の未来 ここまで紹介してきたマルチモーダル埋め込み技術は、あくまで「要素技術」です。では、この技術が製造業の現場に入ると何が変わるのか? ── ここからは、最も実用化が近い「検索」の応用領域を見ていきます。 検索 ── 「探し方」が根本から変わる 製造業には、長年にわたって蓄積された膨大な図面・3Dデータ・仕様書が眠っています。しかし、その多くはファイル名や管理番号といった「メタデータ」でしか検索できません。番号を忘れたら最後、必要なデータにたどり着けない ── そんな経験のある方も多いのではないでしょうか。 マルチモーダル埋め込み技術は、この「探し方」を根本から変えます。データの本質的な「意味(特徴)」がベクトル化されることで、メタデータに頼らない、3つの新しい検索が可能になります。 クロスモーダル検索:形状で図面を探す 「データ形式の壁」を越える検索です。 設計担当者が、過去の類似製品の仕上がりを確認したい場面を想像してください。従来は管理番号を手がかりに過去資料を探し回る必要がありましたが、マルチモーダル埋め込みを使えば、開発中の3D CADデータをそのまま検索クエリとして入力するだけで済みます。AIが埋め込み空間上で「形状的・構造的に近い」過去の製品図面(画像)を瞬時に見つけ出してくれるのです。 管理番号が不明な古い部品でも、形状さえあれば「過去の類似品」を紐付けられる。これだけでも、設計の初期段階で過去の知見を活かしやすくなります。 マルチモーダル検索:「画像+テキスト」で絞り込む 1つのモダリティでは足りない場面に効く検索です。 例えば、「このフランジ(画像)と同じ形状で、かつ材質がステンレス(テキスト)のもの」を探したいとします。画像だけでは材質まで判別できませんし、テキストだけでは形状の微妙な違いを表現しきれません。 マルチモーダル検索では、画像の「視覚的な特徴ベクトル」とテキストの「仕様的な特徴ベクトル」を掛け合わせることで(ベクトル加算など)、両方の条件を満たすデータをピンポイントで特定できます。材質、硬度、耐熱性── 画像では見えない情報を言葉で補足することで、検索精度が格段に上がるのです。 AIチャット検索:現場の言葉でデータを引き出す 対話型インターフェースを介した検索です。 現場の作業員がタブレットに向かって「先週のライン停止時に、ベアリング付近を撮影した写真を見せて」と話しかける。あるいは「この3Dモデルの、取付穴が5つあるバリエーションをリストアップして」と指示を出す。── ユーザーの発話がテキストベクトルに変換され、データベース内の画像・3Dデータのベクトルと直接照合されることで、こうした自然な対話での検索が実現します。 複雑な検索コマンドを覚える必要はありません。専門知識がなくても「現場の言葉」で必要な技術資産にアクセスできるようになる点が、現場への浸透を考えると最大の強みです。 おわりに 本記事では、埋め込み技術の進化を「単語だけ」のWord2Vecから、「画像と言葉」を結んだCLIP、そして「3Dシーンを丸ごと対応づける」CrossOverへとたどりました。この10年余りで、AIが「意味」を捉える範囲は劇的に広がっています。 製造業にとって、この技術が意味するのは「データの探し方の根本的な変化」です。ファイル名や管理番号に頼っていた検索は、形状やテキストの「意味」で直接つながるようになります。ベテラン設計者の頭の中にしかなかった「あの時似たようなものを作った」という経験知は、埋め込み空間を通じて組織全体で共有できるデジタル資産へと変わります。 現時点では、製造業でのマルチモーダル埋め込みの社会実装はまだ始まったばかりです。しかし、研究の進展を見れば、この技術が現場に届くのは時間の問題でしょう。「形」と「言葉」の境界が消えたとき、製造業のデータ活用は新しいステージに入ります。 仲間を募集しています! キャディ株式会社では、本記事で紹介したマルチモーダル埋め込み技術をはじめ、AIモデル開発に全力で取り組み、ミッション「モノづくり産業のポテンシャルを解放する」の実現を目指しています。 この記事を読んで「自分ならこう解決する」「この技術、面白そう」と感じたエンジニアの方、ぜひご応募お待ちしております! 詳細は以下の採用ページからご覧いただけます! Data & Machine Learning / CADDi Tech Careers
こんにちは。キャディ株式会社でリサーチ組織の立ち上げを担っている福原です。 現在、私たちのResearchチームでは「 Staff Research Engineer, 2D/3D Vision & Multimodal Understanding 」のポジションを新たにオープンしています。 これまでのテックブログ では、VLMの空間推論における限界や、3D CADモデルの幾何情報処理の難しさといった「私たちが直面している技術課題(What)」について発信してきました。 今回は視点を変えて、 この挑戦的な技術領域で、どのようにして素早く価値を生み出し続ける研究開発が可能なのか 、そして なぜキャディでそれが実現できるのか についてお話しします。 製造業コンピュータビジョンの特異性 なぜキャディなのか:研究開発スピードの構造設計 技術ロードマップにおける3つの重点領域 3D形状の幾何学的・意味的理解の深化 2D図面と3D CADモデルの高度な対応付け マルチモーダル横断検索 求める人物像 製造業コンピュータビジョンの特異性 現在のコンピュータビジョン研究の主流は、RGB画像から3Dタスクを解くというアプローチです。自動運転、ロボティクス、AR/VRなど、多くの先端応用では、豊富なテクスチャ情報、照明の変化、背景のコンテキストといった視覚的手がかりを活用します。最新のVision-Language Model(VLM)も、このような「見た目の情報」を高度に理解することで、驚異的な性能を発揮しています。 しかし、製造業の現場では状況がまったく異なります。図面にも3D CADにも、色もテクスチャも背景も存在しません。そこにあるのは、ピュアな幾何学的情報のみです。 この違いは些細なものではありません。以前のテックブログでもお話ししましたが、言語処理タスクでは人間レベルに達するMLLMも、空間推論タスクでは60%の精度にすら届きません。建築図面のベンチマークでは、テキスト中心の質問応答では高精度を示す一方、空間認識では40〜55%程度という劇的な性能差が報告されています。既存の最先端モデルは、テクスチャなき幾何学の世界では「3歳児レベルの空間推論能力」に退化してしまうのです。 さらに、製造業では幾何学的特性に加えて物理的特性も同時に考慮する必要があります。ある形状が「見た目として正しい」だけでは不十分で、「製造可能か」「強度要件を満たすか」「加工コストは妥当か」といった物理的制約を満たさなければなりません。この点で、最近注目を集めているフィジカルAIの研究と技術的な親和性が高く、今後の研究トレンドとの接続も見えています。 つまり、製造業のコンピュータビジョンは、主流の研究結果をそのまま横に持ってきて使えるわけではない領域です。一見すると制約のように聞こえるかもしれませんが、逆に言えば独自性があるということでもあります。ビッグテックや巨大な外資企業が開発する汎用モデルは、確かにテキスト生成や一般的な画像認識では驚異的な性能を発揮しますが、テクスチャなき幾何学、製造ドメイン特有の制約、物理的特性といった領域では、汎用モデルをそのまま適用しても十分に機能しません。 これはリサーチエンジニアの視点では、 既に確立された正解がないからこそ挑戦的で面白い技術領域であること、そして一瞬で成果が水泡に帰すリスクが、他の領域と比べて相対的に低い ことを意味します。 製造業の実データは非公開のものが多く希少性が高いため、ビッグテックであっても容易に参入できる領域ではありません。加えて、扱う情報が様々なモダリティに跨る製造業の業務上の課題には学術的にもまだ確立された解決策が存在しないことも多く、データフォーマットや顧客が実現したい業務の個別性も高いという特徴があります。 汎用的なアプローチがそのまま正解にはならないからこそ、データや顧客の業務といったドメイン特化の深い知見と、製造業の実データを組み合わせた研究は、持続的な技術的Moat(競争優位性)を築きやすいテーマだと言えます。まだ解決すべき課題が山積しており、極めて挑戦的でエキサイティングな研究フロンティアなのです。これが、製造業AIの魅力です。 そして同時に、私たちを取り巻く環境は、かつてないスピードで変化しています。AI技術の進化は加速度的であり、新しい手法やモデルが日々登場しています。このような時代において、3年後、5年後に成果を出すというアプローチでは、市場や技術トレンドが大きく変わってしまう可能性があります。 私たちは、素早く価値を生み出し、検証し、次の価値創出につなげていく組織を目指しています。これは単なる目標ではなく、変化の激しい時代からの要請だと考えています。研究成果を小さく素早くリリースし、顧客のフィードバックを得て、次の研究テーマを磨き込んでいく。このサイクルを高速で回すことで、長期的な技術的優位性を築いていきます。 なぜキャディなのか:研究開発スピードの構造設計 では、なぜキャディにおいてこれほど高速な価値創出が可能なのでしょうか。 それは、単に「優秀な人材がいる」あるいは「リソースが潤沢である」という理由からではありません。研究開発のスピードを根本的に決定づけるのは、プロセスそのものをいかに速く回せる「構造」が整っているかです。 キャディの研究開発環境には、この構造を支える2つの大きな特徴があります。 1つ目は、自社でアプリケーションとデータプラットフォームを保有し、顧客の現場課題に深く入り込んでいることです。ビジネスメンバーが顧客と伴走し、見積、図面検証、製造プロセス管理といったコア業務の課題に日々向き合っています。この顧客との深い関係性が、机上の空論ではない「リアルな現場の知見」と「実データ」へのアクセスを可能にしています。 2つ目は、経営直下での研究開発の優先順位の高さです。キャディにおいて、独自のAI技術によるMoat構築は経営の最重要課題の一つです。そのため、CEO自らが研究開発の現場と連携し、技術検証や事業実装に向けたブロッカーを即断即決で取り除く体制が整っています。 これら2つの特徴により、私たちの環境では、一般的な研究組織が直面するボトルネックに対して、引けるレバー(選択肢)が圧倒的に多く、かつそれが機能する速度が速いのです。 例えば研究を進める上で壁にぶつかったとき、私たちには以下のような選択肢が即座に用意されます。 データが足りない → ビジネスチームと連携し、実際プロダクトを利用いただいている顧客に直接アプローチして新しいデータの整備をする ドメイン知識が足りない → 製造現場と直結したプロダクトを持つ強みを活かし、社内の専門家や実務者の知見へ即座にアクセスする 業界標準が曖昧で判断できない → 必要に応じて政府機関や業界団体と連携し、マクロな視点から課題を整理する リソースの壁に直面する → 経営層と連携し、課題と必要性を共有することで、人的リソースや計算資源の拡張の判断を仰ぐ ここで重要なのは、私たちが「誰とも関わらず研究だけに閉じこもれる温室」にいるわけではないということです。研究成果を事業価値に繋げるためには、PdMや他チームとの連携が不可欠であり、自ら主体的に動くことが求められます。しかし、そこには本質的ではない待ち時間や政治的な摩擦が極めて少なく、主体的に動くことで高速に研究開発のサイクルを回せる環境が存在します。 自分で動き、周囲を巻き込むことで、あらゆる問題が即座に解決していく。これは逆を言えば、 「言い訳ができない環境」 でもあります。研究成果を事業価値へとつなげ切る責任がある一方で、そのための障害は経営層や周囲のメンバーが全力で取り除いてくれます。 事業部と切り離された研究組織では、ともすれば大規模なリソース獲得の承認に数週間、社内部署の調整まで含めると数ヶ月といった遅延が発生しがちです。しかしキャディでは、そうした非本質的な遅延がほぼありません。リサーチエンジニアが本来向き合うべき技術検証と価値創出に、時間のほぼすべてを注ぎ込むことができる。この構造的な違いこそが、研究スピードを根本から変えているのです。 ここまで読んで、「それならアカデミアや大企業の研究所でも同じことができるのでは?」と思われるかもしれません。しかし、キャディの研究開発環境は独特のバランスの上に成り立っています。 アカデミアとの違い は、論文執筆は目的ではなく過程であり、実データでの検証と「顧客の課題解決」こそが最終的な価値基準である点です。 大企業研究所との違い は、意思決定のスピードです。事業実装までの判断が「週単位」で行われ、組織的なブロッカーは即座に解除されていきます。 スタートアップとの違い は、製造業という数兆円市場において、すでにグローバル4カ国に展開するSaaSプラットフォームと確固たる顧客基盤、実データを有している点です。 つまりキャディは、技術的な挑戦の深さ、スタートアップの意思決定スピード、事業基盤を持つ企業としての実装力という、通常はトレードオフになりがちな要素を併せ持つ、稀有な研究環境なのです。 技術ロードマップにおける3つの重点領域 このような環境で、私たちは具体的にどのような技術課題に挑んでいるのか。重点課題の中から、いくつかの領域をご紹介します。 3D形状の幾何学的・意味的理解の深化 まず一つ目の領域が、3D CADデータの純粋な幾何情報に対する深い理解です。 製造業のデータを扱うモデルには幾何形状や空間配置に関する高い認識力が求められますが、前述の通り既存の多くの基盤モデルはこの要件を満たせていません。私たちは、SaaSを通じて蓄積された大規模な3D CADデータを活用し、純粋な幾何情報から製造上意味を持つ特徴を認識できる表現を獲得する研究に取り組んでいます。 これは単なる形状の認識だけにとどまりません。3D CADの局所的な特徴(フィレット、ボス、リブ、穴など)を抽出し、「この穴はM6ボルトを通すためのもの」「このリブは強度を確保するためのもの」といった、設計者の意図や物理的な制約をモデルに推論させることが求められます。幾何学的な形状の認識だけでなく、強度や剛性、加工の実現可能性といった物理的特性も同時に扱う、フィジカルAI領域の先端的な挑戦でもあります。 このように3D形状に対する理解を深めることで、部品の3D CADモデルから加工難易度や適切な製造方法を自動推定することも可能になります。これにより、これまでベテランの暗黙知に依存していた見積業務の精度やスピードが飛躍的に向上し、SaaSプロダクトの強力なコア機能としての事業価値に直結します。 2D図面と3D CADモデルの高度な対応付け 二つ目の領域が、2D図面と3D CADモデルの高度な対応付けです。 多くの場合において、図面と3D CADは「部品単位」でしか紐付いておらず、それぞれが持つ詳細な情報の、システム上での密な関連付けは出来ていないのが現状です。 寸法などの詳細な情報は図面側に記載されていることが多い一方で、見積業務において、それらが3次元的にどう配置されているかの確認が必要になることが頻繁にあります。そのため、「図面上の情報を、3D CAD側の対応する適切なコンテキストに紐づけて処理をしたい」という実務的なニーズが存在します。 このようなプロセスを自動化するためには、2D図面と3D CADモデルの情報を相互に解釈し、高度に統合することが必須の要素技術となります。この技術の実現によって、図面と3D CADモデル間を横断したクロスチェックの効率化や、設計情報の整合性担保が可能になり、設計プロセスにおいて大きな事業価値を生み出します。 マルチモーダル横断検索 三つ目の領域が、あらゆる製造データをモダリティを超えて統合する検索・推論基盤の構築です。これには、3D CADや2D図面だけでなく、仕様書、過去の不具合報告、見積書といった自然言語のテキスト情報も含まれます。 最大の技術的挑戦は、純粋な幾何学的類似性(形状が似ている)と、セマンティックな類似性(用途や機能、関連する不具合の文脈が似ている)を両立させ、すべてのモダリティを統一された単一の埋め込み空間に整合させることです。 これが可能になれば、「この部品と類似した形状で、過去にどんな不具合があったか」「この図面形状を満たす最適な加工プロセスは何か」といった自然言語クエリでの高度な横断検索と推論が実現します。これまで個人の頭の中や各部署に散在していた暗黙知がシステム上で形式知化され、設計者や調達担当者の意思決定スピードを劇的に引き上げる事業価値をもたらします。 求める人物像 私たちは以下のような方と是非お話したいと考えています: VLM、3D Vision、マルチモーダル学習のいずれかで深い専門性を持つ方 :トップ会議への投稿経験や、博士課程での研究実績など 「面白いモデルができた」で満足せず、事業価値まで執着できる方 :研究と実装の間を往復しながら、顧客の課題解決に向き合える 研究スピードを上げるために、構造から変えることを厭わない方 :AI活用、プロセス改善、組織設計にも興味がある 提供できる環境 : 非本質的な摩擦を排し、研究成果を事業価値へと直結させる最速の社会実装環境 数兆円規模の巨大市場を動かし、独自の技術的Moat(競争優位性)を築く挑戦の場 変化の激しい時代において、最速のサイクルで価値を生み出し続けるアジャイルな組織 特許取得や学会発表をバックアップし、成果を社会へ還元するアウトプット支援 CADDiのリサーチエンジニアは、論文を書くだけでも、実装に専念するだけでもありません。仮説立案・モデル構築・SaaSへの組み込み・顧客検証までを一気通貫で担う「越境型」のポジションです。既存の基盤モデルが通用しない未踏の領域で、世界に先駆けて独自の技術的Moatを築き上げる。そして、その研究成果を圧倒的なスピードで数兆円規模の巨大産業へと実装していく。 あらゆる組織的摩擦が排除された「言い訳ができない」環境で、本質的な技術探求と事業価値創出のサイクルを最速で回し、自らの手でこのブレイクスルーを起こしたい。そう思われた方は、ぜひ一度カジュアルにお話ししましょう。エントリーを心よりお待ちしています。 募集要項: Staff Research Engineer, 2D/3D Vision & Multimodal Understanding カジュアル面談: 応募フォーム
RFC 8707 Resource Indicatorsの活用事例: マイクロサービス間のトークンの権限適正化 はじめに こんにちは、キャディ株式会社のControl Plane 部に所属している麻生です。 キャディでは、マルチテナントSaaSを提供しておりマルチプロダクト化を進めています。その上でアプリケーションを横断した機能をControl Plane として切り出して開発しています。 Control Plane 全体のアーキテクチャについては、以前のブログ記事「 CADDi の Control Plane を支えるシステムたちの紹介 」で紹介しました。是非こちらもご一読いただけると幸いです。 そのControl Plane において、ワークロード間の認証基盤として "M2M Token Issuer" を運用しています。これはOAuth 2.0 のClient Credentials Grant を用いた社内専用のトークン発行システムで、ユーザー操作を伴わないシステム間通信やバッチ処理で利用されています。 本記事では、M2M Token Issuer が発行するInternal Token を見直し、RFC 8707 Resource Indicators を適用して最小権限の原則を実装したエピソードを紹介します。 改修に至った課題 M2M Token Issuer は、まず動くものを素早く届けることを優先して構築した経緯があり、トークンの権限制御は最小限の設計にとどまっていました。 Client Credentials Grant では、ユーザーが認可の場にいないため同意プロセスがありません。その分、クライアントに許可された操作範囲をscopeで明示的に制約することが重要です。 社内システムでの活用が広がる中で、より安全な利用を促進するため、 「各クライアントに、どのサービスが、どのような操作を許可しているか」 これらを統制できるようにしたいと考えるようになりました。 設計と実装 RFC 8707 Resource Indicators の活用 RFC 8707 Resource Indicators for OAuth 2.0 に準拠する形にしました。これは、トークンリクエスト時に resource パラメータでアクセス先を指定する仕組みを定義しています。これにより、発行されるトークンの aud にリクエストで指定したリソース識別子が設定されます。 Control Plane の機能は他チームの開発者に認知してもらうことも重要になりますが、RFCに準拠することで、新しい仕様でも社内の開発者に対する説明がしやすくなるというメリットがあります。 改修後のトークンリクエストとレスポンスは以下のようになります。 POST /oauth2/token --user "{Client ID}:{Secret}" -d grant_type=client_credentials \ &resource=urn:caddi-service01 \ &scope=delete { " sub ": " {Client ID} ", " aud ": [ " urn:caddi-service01 " ] , " scope ": " delete ", " iat ": 1234567890 , ... } トークンを受け取るサービス側は、Istio の RequestAuthentication で aud を検証し、AuthorizationPolicy を用いて要求された scope が適切か検証します。自サービスのリソース識別子( urn:caddi-service01 )と一致しないトークンは拒否されるため、他のサービス宛てのトークンでアクセスすることはできません。 複数resource を指定した場合の設計 原則は「1トークン = 1リクエスト」の都度取得ですが、技術的には複数の resource を指定して1つのトークンに複数の aud を含めることもサポートしています。RFC上も、複数の aud を受け付ける仕様です。 POST /oauth2/token resource=urn:caddi-service01 resource=urn:caddi-service02 scope=delete { " aud ": [ " urn:caddi-service01 ", " urn:caddi-service02 " ] , " scope ": " delete " } ここでのポイントは、 scope がすべての指定resourceに対して共通(積集合)であることです。リクエストされたscopeは、指定されたすべてのresource に対して許可されている場合にのみ発行されます。 複数のresource に別々のscope を指定するケースについては、サポートしないこととしました。これらを提供可能にすると、トークンの権限範囲が広がります。これはトークンの権限適正化という元々の目的と合わないため、最小権限を実現することを優先しました。 この設計により、マイクロサービス間でリクエストがチェーンする場合にも、同一トークンを伝搬させて各サービスで aud と scope の両方を検証できます。「このトークンは自サービス宛てであり、かつ要求された操作が許可されている」ことは各サービスが独立して検証できます。 ネットワークトポロジーに依存しない識別子 RFC 8707の resource パラメータにはURIを指定します。Kubernetes 上でマイクロサービスを構築しているキャディにとって、最も直感的な選択肢はKubernetes クラスター内のサービスURL( http://caddi-service01.ns.svc.cluster.local )です。一方でキャディではControl Plane と、Data Plane / Application Plane を別のKubernetes クラスターに分割しており、クラスターを跨いでトークンをやり取りする場合、クラスター内URLはリソース識別子として一意性を担保できる保証はありません。 また、URLを使うと RFC 9728 OAuth 2.0 Protected Resource Metadata が取得できるエンドポイントと誤解されるリスクもあります。リソース識別子はあくまでトークンの aud に載せる論理的な名前であり、実際にアクセス可能なURLである必要はありません。 こうした背景からURLではなくURNを基本とする方針を採りました。ただし、厳格なフォーマットを強制するのではなく、ルールを提供し使う側に委ねる方式としています。 [MUST] URIフォーマット(URNを含む)であること [RECOMMEND] urn:{サービス名} のフォーマットを用いること [CAN] 一意性の担保のために必要に応じて、namespaceやパス情報を追加すること(例: urn:control-plane:caddi-service01 ) まとめ M2M Token IssuerのInternal Tokenに対して、RFC 8707 Resource Indicatorsを適用し、 aud と scope による最小権限の原則を実現しました。 設計ポイント 内容 resource による aud 指定 RFC 8707に準拠し、トークンのアクセス先をリクエスト時に明示。サービス側でIstioによる aud 検証が可能に scope の積集合設計 複数resource指定時もscopeは共通。resource別のscope指定は最小権限の観点からサポートしない URNベースのリソース識別子 ネットワークトポロジーに依存しない論理名を採用し、クラスター横断でも一意性を担保 今後の展望 今回の記事は aud に焦点を当てて解説しましたが、 scope についても運用で考えることが多くあります。トークン発行者としてどこまでルールを作るか、今後も検討をしていくことになると思います。 またこれはM2M Token Issuer に限った話ではありませんが、Control Plane の機能は、Data Plane / Application Plane で活用されて価値を発揮します。 新しい機能を導入する上で、開発者に負担なく、かつ開発のアジリティに影響しないように、それでいて全体の生産性にインパクトをもたらす工夫に努めていく所存です。 結び M2Mトークンの認可設計は、ユーザー認証と比べて議論されることが少ない領域ですが、マイクロサービスが増えるほどその重要性は高まります。同じ課題に取り組む方の参考になれば幸いです。 Control Plane の開発に興味がある方は、ぜひ以下のページもご覧ください。 recruit.caddi.tech
こんにちは、Control Plane部認証認可グループの平岩です。私たち認証認可グループではバックエンドAPIにGoを多く採用しています。共通基盤である特性上高RPSに耐えられる必要があり、また安定して低いレイテンシでリクエストを処理することが求められます。本記事ではGoの標準パッケージである sql.DB の内部実装を、 ソースコード (Go 1.26時点)を読みながら解説します。 はじめに sql.DB とは何か 内部実装の詳解 DB 構造体の主要フィールド クエリ実行の全体像 BadConn リトライの仕組み コネクションの取得: conn メソッド フェーズ1: idleコネクションがあれば再利用する フェーズ2: 最大接続数に達している場合の待機 フェーズ3: 新規接続の作成 コネクションの返却: putConn メソッド 非同期なコネクション作成: connectionOpener トランザクションとコネクション コネクションの定期削除: connectionCleaner DBStats: プールの状態を観測する コネクションプールの設定 4つの設定パラメータ デフォルト値の問題 各パラメータの解説 SetMaxOpenConns SetMaxIdleConns SetConnMaxLifetime SetConnMaxIdleTime まとめ はじめに GoでRDBを使うとき、何かしらのO/R mapperやライブラリを使うことが多いかと思います。ですが、内部的には標準パッケージの sql.DB が使われています。この sql.DB はコネクションプールの役割を持ち、4つの設定パラメータが存在します。これらの値をデフォルトのまま使うと本番環境でエラーやパフォーマンス問題を引き起こすことがあります。また、内部でリトライが行われるケースがあるなど隠蔽されている挙動も存在します。 本記事では前半で sql.DB の内部実装をソースコードを追いながら解説し、後半でそれを踏まえた設定の考え方を述べます。設定についてだけ知りたい方は コネクションプールの設定 まで読み飛ばしてください。 sql.DB とは何か sql.DB の実態はコネクションプールであり、複数のgoroutineから安全に使えます(goroutine safe)。 sql.Open は引数の検証のみを行い、この時点ではデータベースへのコネクションを作成しません。実際にコネクションが作れることを確認するには (*sql.DB).PingContext を使います(詳細は Opening a database handle - The Go Programming Language を参照してください)。 内部実装の詳解 DB 構造体の主要フィールド ここからは sql.DB の実装を見ていきます。以降のコードを読むにあたって、 DB 構造体 の以下の4つのフィールドを把握しておけば十分です。 freeConn []*driverConn : idleコネクションのスライス。プールに戻った時刻 returnedAt の古い順になる connRequests connRequestSet : コネクションを待っているgoroutineの集合 connRequestSet はSetと言っているが実体はindexでの参照を付けたスライス numOpen int : 使用中とidleの両方を含む、現在openなコネクション数 cleanerCh chan struct{} : プールにあるコネクションを閉じるgoroutineへの通知用のchannel すべてのフィールドへのアクセスは mu sync.Mutex で保護されています。 クエリ実行の全体像 まず、 QueryContext を例として全体像を示します。他の QueryRowContext ExecContext PingContext も基本的に同じ流れですが、 QueryContext のみが Rows.Close() を明示的に呼んでコネクションを返却する責任がuser側にあります。 flowchart TD A["(*sql.DB).QueryContext"] subgraph retryScope ["retryの範囲(ErrBadConnの場合、最大3回リトライされる)"] B["(*sql.DB).retry"] Q["(*sql.DB).query"] C["(*sql.DB).conn"] E["(*sql.DB).queryDC"] end A --> B B --> Q Q --> C C -- エラー --> ERR[error を返す] C -- 成功 --> E E -- ErrBadConn --> B E -- エラー --> ERR E -- 成功 --> F[Rows を返す<br>コネクション所有権が Rows に移動] subgraph userCode ["user"] K["(*sql.Rows).Next() で最後まで読むか、(*sql.Rows).Close()を呼ぶ"] end F --> K K --> G["(*sql.DB).putConn"] BadConn リトライの仕組み sql.DB には無効な接続に当たった場合に自動でリトライする仕組みがあります。 このリトライのトリガーとなるのが driver.ErrBadConn です。 godoc には次のように書かれています。 ErrBadConn should be returned by a driver to signal to the sql package that a driver.Conn is in a bad state (such as the server having earlier closed the connection) and the sql package should retry on a new connection. To prevent duplicate operations, ErrBadConn should NOT be returned if there's a possibility that the database server might have performed the operation. つまり、 driver.ErrBadConn はコネクションがDBサーバ側から切断された場合などに返り、 sql package側でリトライすべきエラーであるということです。 実際にリトライを制御する DB.retry を見ていきます。 database/sql/sql.go L1572-1584 : const maxBadConnRetries = 2 func (db *DB) retry(fn func (strategy connReuseStrategy) error ) error { for i := int64 ( 0 ); i < maxBadConnRetries; i++ { err := fn(cachedOrNewConn) // 👈 (1) // retry if err is driver.ErrBadConn if err == nil || !errors.Is(err, driver.ErrBadConn) { return err } } return fn(alwaysNewConn) // 👈 (2) } (1) 最大2回、 cachedOrNewConn のstrategyで試行します。このstrategyではまずidleコネクションを再利用しようとします。 (2) 2回とも driver.ErrBadConn だった場合、3回目は alwaysNewConn strategyで新規のコネクション作成を強制します。 コネクションの取得: conn メソッド retry から呼ばれる conn メソッドが、プール内のidleコネクションか新規の接続を返します。このメソッドはかなり長いため、全体を3つのフェーズに分けて読んでいきます。 flowchart TD A[connメソッド呼び出し] --> B{freeConnに<br>idleコネクションがある} B -- Yes --> C{コネクションが期限切れ} C -- No --> D[コネクションを再利用] C -- Yes --> E[driver.ErrBadConnを返す → リトライ] B -- No --> F{コネクション数が最大(maxOpen)に<br>達している} F -- Yes --> G[connRequestsで待機] F -- No --> H[新規接続を作成] フェーズ1: idleコネクションがあれば再利用する database/sql/sql.go L1331-1354 : // Prefer a free connection, if possible. last := len (db.freeConn) - 1 if strategy == cachedOrNewConn && last >= 0 { // Reuse the lowest idle time connection so we can close // connections which remain idle as soon as possible. conn := db.freeConn[last] // 👈 (1) db.freeConn = db.freeConn[:last] conn.inUse = true if conn.expired(lifetime) { // 👈 (2) db.maxLifetimeClosed++ db.mu.Unlock() conn.Close() return nil , driver.ErrBadConn } db.mu.Unlock() // Reset the session if required. if err := conn.resetSession(ctx); errors.Is(err, driver.ErrBadConn) { conn.Close() return nil , err } return conn, nil } (1) freeConn の末尾を取得しています(LIFO)。コメントにある通り、同時に必要とする数よりも多いコネクションがあった場合、余分なコネクションはidle状態のままcloseされていきます。 (2) 取得した接続が maxLifetime を超えていた場合は driver.ErrBadConn を返し、リトライの対象になります。 フェーズ2: 最大接続数に達している場合の待機 database/sql/sql.go L1358-1426 : if db.maxOpen > 0 && db.numOpen >= db.maxOpen { req := make ( chan connRequest, 1 ) delHandle := db.connRequests.Add(req) // 👈 (1) db.waitCount++ db.mu.Unlock() waitStart := nowFunc() // 👈 (2) select { case <-ctx.Done(): // 👈 (3) db.mu.Lock() deleted := db.connRequests.Delete(delHandle) db.mu.Unlock() db.waitDuration.Add( int64 (time.Since(waitStart))) if !deleted { select { default : case ret, ok := <-req: if ok && ret.conn != nil { db.putConn(ret.conn, ret.err, false ) } } } return nil , ctx.Err() case ret, ok := <-req: // 👈 (4) db.waitDuration.Add( int64 (time.Since(waitStart))) if !ok { return nil , errDBClosed } if strategy == cachedOrNewConn && ret.err == nil && ret.conn.expired(lifetime) { db.mu.Lock() db.maxLifetimeClosed++ db.mu.Unlock() ret.conn.Close() return nil , driver.ErrBadConn } if ret.conn == nil { return nil , ret.err } if err := ret.conn.resetSession(ctx); errors.Is(err, driver.ErrBadConn) { ret.conn.Close() return nil , err } return ret.conn, ret.err } } (1) maxOpen に達している場合、 connRequests に待機リクエストを追加します。 (2) コネクション作成の待ち時間を計測するために現在時刻を変数に入れています。この nowFunc は var nowFunc = time.Now と定義されており、テスト時に現在時刻を取得する関数を上書きするためのパターンが使われています。 (3) context がキャンセルされた場合はリークを避けるため connRequests から削除して ctx.Err() を返しています。 (4) 他のgoroutineがコネクションを返却した場合、ここでコネクションを受け取ります。 フェーズ3: 新規接続の作成 database/sql/sql.go L1429-1449 : db.numOpen++ // optimistically // 👈 (1) db.mu.Unlock() ci, err := db.connector.Connect(ctx) if err != nil { db.mu.Lock() db.numOpen-- // correct for earlier optimism // 👈 (2) db.maybeOpenNewConnections() db.mu.Unlock() return nil , err } db.mu.Lock() dc := &driverConn{ db: db, createdAt: nowFunc(), returnedAt: nowFunc(), ci: ci, inUse: true , } db.addDepLocked(dc, dc) db.mu.Unlock() return dc, nil (1) numOpen を楽観的に(optimistically)インクリメントしてからロックを解放し、接続を作成します。 (2) 接続作成に失敗した場合は numOpen を戻し、 maybeOpenNewConnections で待機中になっている数だけコネクションを作成しようとします。 コネクションの返却: putConn メソッド 接続の使用後は putConn を通じてプールに返却されます。このメソッドには戻り値がなく、引数で渡る dc *driverConn err error を直接変更しています。 database/sql/sql.go L1481-1531 : func (db *DB) putConn(dc *driverConn, err error , resetSession bool ) { if !errors.Is(err, driver.ErrBadConn) { if !dc.validateConnection(resetSession) { // 👈 (1) err = driver.ErrBadConn } } db.mu.Lock() if !dc.inUse { db.mu.Unlock() panic ( "sql: connection returned that was never out" ) } if !errors.Is(err, driver.ErrBadConn) && dc.expired(db.maxLifetime) { // 👈 (2) db.maxLifetimeClosed++ err = driver.ErrBadConn } dc.inUse = false dc.returnedAt = nowFunc() for _, fn := range dc.onPut { fn() } dc.onPut = nil if errors.Is(err, driver.ErrBadConn) { // 👈 (3) db.maybeOpenNewConnections() db.mu.Unlock() dc.Close() return } added := db.putConnDBLocked(dc, nil ) // 👈 (4) db.mu.Unlock() if !added { dc.Close() return } } (1) validateConnection は、ドライバが driver.Validator インタフェースを実装している場合に IsValid() を呼びます。 false が返ればコネクションは破棄されます。ドライバはクエリ結果のエラーをそのまま返しつつ、コネクション自体は破棄したい場合にこのインタフェースを使います。 (2) コネクションが maxLifetime を超過していたら err = driver.ErrBadConn とします。ここでreturnはしていませんが、 (3) の処理によってcloseされることになります。 (3) driver.ErrBadConn の場合、 maybeOpenNewConnections で待機リクエストに新規接続を用意してからcloseします。 (4) 正常なコネクションの場合、 putConnDBLocked で待機リクエストへの割り当てまたはidleコネクションのプールへの追加を試みます。 putConnDBLocked の実装も見てみます。 database/sql/sql.go L1542-1567 : func (db *DB) putConnDBLocked(dc *driverConn, err error ) bool { if db.closed { return false } if db.maxOpen > 0 && db.numOpen > db.maxOpen { return false } if req, ok := db.connRequests.TakeRandom(); ok { // 👈 (1) if err == nil { dc.inUse = true } req <- connRequest{ conn: dc, err: err, } return true } else if err == nil && !db.closed { if db.maxIdleConnsLocked() > len (db.freeConn) { db.freeConn = append (db.freeConn, dc) // 👈 (2) db.startCleanerLocked() return true } db.maxIdleClosed++ } return false // 👈 (3) } (1) 待機中の connRequest が1つでもあれば freeConn には追加せず、ランダムに1つ選んでコネクションを直接渡しています。 (2) 誰も待機中でなければidleコネクション( freeConn )の末尾に追加します。 (3) idleコネクション数が上限を超えている場合は false を返し、 putConn 側でコネクションがcloseされます。 非同期なコネクション作成: connectionOpener putConn や conn のコードに maybeOpenNewConnections の呼び出しがありました。この関数は待機中の connRequest の数だけ openerCh に通知します。 database/sql/sql.go L1230-1245 : func (db *DB) maybeOpenNewConnections() { numRequests := db.connRequests.Len() if db.maxOpen > 0 { numCanOpen := db.maxOpen - db.numOpen if numRequests > numCanOpen { numRequests = numCanOpen } } for numRequests > 0 { db.numOpen++ // optimistically // 👈 (1) numRequests-- if db.closed { return } db.openerCh <- struct {}{} } } (1) conn メソッドのフェーズ3と同じく、 numOpen を楽観的にインクリメントしてからシグナルを送ります。 openerCh を受け取るのは別goroutineで動いている connectionOpener です。このgoroutineは OpenDB の時点で起動されています。 database/sql/sql.go L1031-1046 : func OpenDB(c driver.Connector) *DB { ctx, cancel := context.WithCancel(context.Background()) db := &DB{ connector: c, openerCh: make ( chan struct {}, connectionRequestQueueSize), lastPut: make ( map [*driverConn] string ), stop: cancel, } go db.connectionOpener(ctx) // 👈 (1) return db } (1) connectionOpener は別のgoroutineで動いています。 openerCh からシグナルを受け取るたびに openNewConnection で接続を作成し、 putConnDBLocked で待機中の connRequest に渡します。 conn メソッドのフェーズ3では呼び出し元goroutineが直接 connector.Connect を呼んでいましたが、 maybeOpenNewConnections はこの専用goroutineが非同期に接続を作成します。 ErrBadConn で接続が壊れた場合や接続作成に失敗した場合に、待機中のgoroutineをブロックせずに新しい接続を用意するための仕組みです。 トランザクションとコネクション BeginTx はプールからコネクションを1つ取得し、 Tx に固定します。 トランザクション中の全操作はこの固定されたコネクションを使います。 flowchart TD A["(*sql.DB).BeginTx"] --> B["(*sql.DB).retry"] B --> C["(*sql.DB).conn: コネクション取得"] C --> D["BEGIN を発行し Tx を生成<br>コネクションを Tx.dc に固定"] D --> E["Tx 上の操作<br>tx.QueryContext / tx.ExecContext"] E --> F["grabConn: 固定されたコネクションを返す<br>(プールには行かない)"] F --> G["Commit() または Rollback()"] G --> H["tx.close → putConn"] 通常のクエリでは conn がプールからコネクションを取得しますが、 Tx 上の操作では grabConn が固定されたコネクション( Tx.dc )をそのまま返します。 プールへの問い合わせは発生しません。 Commit() または Rollback() が呼ばれると、内部の tx.close が releaseConn → putConn を呼び、コネクションをプールに返却します。 BeginTx で渡した context がキャンセルされた場合は、バックグラウンドの awaitDone goroutineが自動でロールバックしコネクションを返却します。 コネクションの定期削除: connectionCleaner 期限切れのidle接続を定期的にクリーンアップするためにバックグラウンドで動きます。 maxLifetime または maxIdleTime が設定されている場合のみ起動します。 database/sql/sql.go L1095-1136 : func (db *DB) connectionCleaner(d time.Duration) { const minInterval = time.Second // 👈 (1) if d < minInterval { d = minInterval } t := time.NewTimer(d) for { select { case <-t.C: case <-db.cleanerCh: // 👈 (2) } db.mu.Lock() d = db.shortestIdleTimeLocked() if db.closed || db.numOpen == 0 || d <= 0 { db.cleanerCh = nil db.mu.Unlock() return } d, closing := db.connectionCleanerRunLocked(d) // 👈 (3) db.mu.Unlock() for _, c := range closing { c.Close() } if d < minInterval { d = minInterval } if !t.Stop() { select { case <-t.C: default : } } t.Reset(d) } } (1) connectionCleaner は最短でも1秒間隔で動作します。 (2) 1秒経つか cleanerCh への通知でコネクション削除の処理が実行されます。 cleanerCh への送信は SetConnMaxLifetime SetConnMaxIdleTime が短くなるかDBがcloseされたときに発生します。 (3) この connectionCleanerRunLocked がcloseすべきコネクションのスライスを返します。実際の Close() はロック解放後に行っています。 database/sql/sql.go L1138-1189 : func (db *DB) connectionCleanerRunLocked(d time.Duration) (time.Duration, []*driverConn) { var idleClosing int64 var closing []*driverConn if db.maxIdleTime > 0 { // As freeConn is ordered by returnedAt process // in reverse order to minimise the work needed. idleSince := nowFunc().Add(-db.maxIdleTime) last := len (db.freeConn) - 1 for i := last; i >= 0 ; i-- { // 👈 (1) c := db.freeConn[i] if c.returnedAt.Before(idleSince) { i++ closing = db.freeConn[:i:i] db.freeConn = db.freeConn[i:] idleClosing = int64 ( len (closing)) db.maxIdleTimeClosed += idleClosing break } } // ... 次回チェック時刻の計算(省略) } if db.maxLifetime > 0 { expiredSince := nowFunc().Add(-db.maxLifetime) for i := 0 ; i < len (db.freeConn); i++ { // 👈 (2) c := db.freeConn[i] if c.createdAt.Before(expiredSince) { closing = append (closing, c) last := len (db.freeConn) - 1 // Use slow delete as order is required to ensure // connections are reused least idle time first. copy (db.freeConn[i:], db.freeConn[i+ 1 :]) // 👈 (3) db.freeConn[last] = nil db.freeConn = db.freeConn[:last] i-- } } db.maxLifetimeClosed += int64 ( len (closing)) - idleClosing } return d, closing } (1) 期限切れの(= maxIdleTime を超過した)コネクションを探します。 freeConn は returnedAt の古い順に並んでいるため、末尾(最新)から逆順に走査します。期限切れのコネクションを見つけたらそれより前のコネクションはすべて期限切れなので、すべてcloseの対象として closing に取り出しています。それ以降の残りが有効な freeConn となります。 (2) 次に maxLifetime を超過したコネクションを探します。 freeConn は createdAt の順では並んでいないため、全件走査する必要があります。 (3) 直前のコメントにある通り、 freeConn の順序を維持するために copy と nil の代入で要素を削除しています。LIFOでの再利用が正しく機能するには returnedAt 順で並んでいることが必要だからです。 この関数はcloseすべきコネクションを closing スライスに集めて返すだけで、 Close() 自体は呼びません。呼び出し元の connectionCleaner がロックを解放してから Close() を実行します。 ここまでで sql.DB によるコネクションプールの実装を確認しました。 DBStats: プールの状態を観測する db.Stats() はプールの現在の状態とカウンターを DBStats として返します。 各フィールドはそれぞれ sql.DB の対応するフィールドの値を返しています。ただし使用中のコネクション数を示す InUse のみ直接保持していないため (openコネクション数 - idleコネクション数) で算出されます。 フィールド 内部実装での算出方法 MaxOpenConnections maxOpen OpenConnections numOpen InUse numOpen - len(freeConn) Idle len(freeConn) WaitCount waitCount WaitDuration waitDuration MaxIdleClosed maxIdleClosed MaxIdleTimeClosed maxIdleTimeClosed MaxLifetimeClosed maxLifetimeClosed 上4つはスナップショットで、 Stats() を呼んだ瞬間の値です。 下5つは累積カウンターで、 DB の生存期間を通じて単調増加します。 コネクションプールの設定 4つの設定パラメータ sql.DB には4つの設定パラメータがあります。 各メソッドの詳細は Managing connections - The Go Programming Language を参照してください。 設定 デフォルト値 内部実装での役割 SetMaxOpenConns 0(無制限) conn で numOpen >= maxOpen なら connRequests で待機 SetMaxIdleConns 2 putConnDBLocked で freeConn に入れるかの上限 SetConnMaxLifetime 0(無制限) conn / putConn で createdAt からの経過をチェック SetConnMaxIdleTime 0(無制限) connectionCleaner で returnedAt からの経過をチェック デフォルト値の問題 Go公式ドキュメント( Managing connections - The Go Programming Language )にはこう書いてあります。 For the vast majority of programs, you needn't adjust the sql.DB connection pool defaults. ですが、本番環境で複数インスタンスからRDBに接続するWebアプリケーションでは事情が異なるのではと思います。 アプリケーションのインスタンスがスケールする一方でDB側には接続数の上限があり、デフォルトの設定では接続数が上限を超えてエラーになるリスクがあるからです。 各パラメータの解説 SetMaxOpenConns numOpen >= maxOpen になると、以降の conn は connRequests のチャネルで待たされます。 Go公式ドキュメント はデッドロックのリスクに言及しています。 Setting the limit makes database usage similar to acquiring a lock or semaphore, with your application queuing up to wait for an available database connection. デフォルト値は0であり、無制限を意味します。これではリクエストがスパイクした際に大量にコネクションが作られ、リソースを大量に消費してしまう可能性があります。そのためデフォルトのままは避けるべきで、デッドロックが起こらない程度に余裕を持ったある程度大きな値でも設定しておくべきと考えています。また、前述の通りDB側にも接続数の上限があるため、複数インスタンスから接続する構成ではDB側の上限をインスタンスが最大までスケールしても超えない値に設定しておく必要があります。 SetMaxIdleConns デフォルトは2( defaultMaxIdleConns )です。 公式ドキュメント にも "raise the limit to avoid frequent reconnects in programs with significant parallelism" とあります。 先ほど見た putConnDBLocked の実装から、デフォルトのままだと何が起きるかがわかります。 コネクションの返却時にすでに MaxIdleConns 以上idleコネクションがあった場合、即closeされるということです。 デフォルトの2のように少ない値だとコネクションの破棄→作成が繰り返されて非効率な可能性があります。 また、実際のところ SetMaxIdleConns は時間帯によって負荷が異なる中で常に適切な値を決めるのが難しいと感じます。そこで筆者らはシンプルに MaxOpenConns と同じ値に設定しています。長時間idleのコネクションが残ってしまうのを避けるためには SetConnMaxLifetime か SetConnMaxIdleTime を設定すれば十分と考えています。 SetConnMaxLifetime godoc の定義では「コネクションが再利用可能な最大時間」です。 内部的には、コネクションの createdAt からの経過で判定され、 conn の取得時と putConn の返却時にチェックされます。 設定しない場合、フェイルオーバーでプライマリが切り替わった際やDNSで接続先が変わった際に古いコネクションが残り続けるリスクがあります。ただ筆者らは ConnMaxLifetime は現時点では設定していません。今のところこれによる問題は発生していませんが、リスクはあるため今後設定を検討しています。 SetConnMaxIdleTime godoc の定義では「コネクションがidle状態でいられる最大時間」です。 内部的には returnedAt からの経過で判定されます。 公式ドキュメント では SetMaxIdleConns との併用が想定されており、バースト時に増やしたidle接続を負荷が減ったときに解放する用途です。 筆者らの環境ではKubernetes上でIstioを利用しており、Envoy sidecarのidle timeoutでDBサーバへのコネクションが切断されます。 そのためidle timeoutよりも短い ConnMaxIdleTime を設定し、Envoy側で切断される前にアプリケーション側からcloseするようにしています。 これにより切断済みのコネクションを使ってしまい ErrBadConn が発生するのを防いでいます。 まとめ 普段はO/R mapperの裏側に隠れていることも多い sql.DB ですが、内部実装を知っておくとDB関連の障害調査で「なぜこの接続が使われたのか」「なぜリトライが起きたのか」を説明できるようになります。また、goroutineやchannelを使った並行処理、mutexのロック範囲の最小化など、パフォーマンスを意識した実装パターンが随所に見られるので、Go自体の学びになる点も多いと感じました。この記事が sql.DB の設定を見直すきっかけや、その判断の参考になれば幸いです。 最後に、筆者ら認証認可グループでは以下の記事も公開しています。今後も発信していく予定ですので、あわせてご覧ください。 Go本格採用から1年──CADDi Control Planeの技術選定と振り返り CADDi の Control Plane を支えるシステムたちの紹介 Auth0を使って1年かけてSSOをサポートした話
こんにちは!Core SRE の 織田 英吾です。 キャディはクラウドネイティブ会議にブーススポンサーとして協賛させていただきました。 この記事では2日間にわたるイベント期間中のブースの様子や聴講したセッションの感想をレポートします。 ブース紹介 まずは、キャディのブースについて紹介します! ブースでは、 私たちが取り組む製造業にまつわる課題を知っていただくための「30秒図面捜索チャレンジ」を実施したり、おすすめの Claude Code Skills を共有するボードを用意しました。 以下が実際に寄っていただいた皆さんに記載いただいたボードの写真です。おすすめ Claude Code Skills や使っている機能などをたくさん教えていただきました。Skillsを自作されている方も多くいて、コードのレビューやドキュメント作成が特に多かった印象です。私が知らなかったものも多くあり、試してみようかと思いました。 私たちが提供している「製造業AIデータプラットフォームCADDi」のアプリケーションである「製造業データ活用クラウドCADDi Drawer」のデモも行いました。 デモを通して、製造業出身者や所属の方以外にも、製造業が抱える課題に共感していただくことができました。私自身、キャディが取り組んでいることの重要さを再認識することができた機会になりました。 名古屋での開催ということもあり、製造業出身や所属の方も多く、現場の声やペインポイントを聞くことができ、私が学ばせていただく機会にもなり、とてもいい経験をすることができました。 イベント期間中、キャディのブースに立ち寄ってくださった皆さん、本当にありがとうございました! 聴講したセッションの感想 次に、私や他のメンバーが聴講させていただいたセッションの感想を簡潔にまとめます。 「100マイクロサービスのTerraform/Kubernetes管理地獄から抜け出すためのAI活用術」 株式会社LegalOn Technologies の Ishigaki さんと Wada さんによる発表です。 本セッションでは、マイクロサービスの増加に伴って Kubernetes / Terraform 管理や設定変更、レビュー対応が大きな運用負荷になるという課題が紹介されました。その解決策として、定型的な変更作業は仕様化・ツール化し、AIエージェントを活用してサービス単位で並列に展開するアプローチが示されており、とても参考になりました。 特に印象的だったのは、AI にすべてを任せるのではなく、社内ルールやガイドラインを AI が参照しやすい形に整備し、人間が最終判断を担う設計にしていた点です。これにより、大量の PR 作成やレビュー支援、移行作業を効率化しながら、運用品質を保つ工夫がなされていました。 AI 活用の前提として、業務プロセスやナレッジを構造化しておくことの重要性を感じるセッションでした。(Core SRE 織田) 登壇資料: https://speakerdeck.com/markie1009/kubernetesguan-li-di-yu-karaba-kechu-sutamenoaihuo-yong-shu 「そのSLO99.9%、本当に必要ですか? 〜優先度付きSLOによる責任共有の設計思想〜」 株式会社TopotalのVTRyoさんによる発表を聴講しました。以前、SREKaigiでもVTRyoさんの発表を聞いたことがあり、とても参考になったので今回も楽しみにしていました。 本発表では、SREチームはDevチームのEnablingを突き詰めていくと、横断で複数のサービスを見ていくことが増えていくことで、運用しているサービスで何が起きているのか分からなくなる可能性が高くなりやすいと話されてました。実際に、VTRyoさんは自分たちはSREとしてサービスに価値を生み出していないのではないかと葛藤が生じたそうです。 そこで紹介されていたのが Product-Focused Reliability for SRE (PER)という考え方でした。自分は初めて聞いたのですが、SREチームが限られたリソースを有効活用するために、ビジネスやユーザーにとって最も重要なことに焦点を当て、優先順位をつけて活動することだそうです。 確かに、従来のSREはインフラやサービスそのものの安定稼働に責務を持つため、自然とDevはアプリケーション、SREはインフラという得意領域で境界が分かれてしまいやすいです。 PERの考え方は、SREの関心ごとをサービスからプロダクトの重要機能に移し、プロダクト中心にSLOを考えていくため、よりユーザーやビジネスの理解が重要になってきます。自分自身、SREが信頼性という指標を追っているのはユーザーの満足度とビジネスの成功度を上げるためだと思っているので、PERの考え方はとてもしっくりきました。 また、SREは日々のアラート対応に追われがちではありますが、それは本当にユーザーの離脱や解約につながっているのか?実は過剰な信頼性を追っているだけで、根本的な問題は機能不足なのではないか?、本セッションの問いとして投げかけられていた「そのSLO99.9%は本当に必要なのか」は定期的にSLOを見直す際に立ち返りたい問いだなと思いました。(Drawer SRE 松嶋) 登壇資料: https://speakerdeck.com/vtryo/is-that-99-dot-9-percent-slo-really-necessary-design-philosophy-of-shared-responsibility-through-prioritized-slos 登壇レポート キャディからCore SREチームリーダーの小林明斗が登壇しました。 セッション名:生成AI時代に信頼性をどう保ち続けるか - Policy as Codeの実践 概要:生成AIによりコードやIaCの変更量が増える中、人手のレビューやチェックリストだけでは信頼性リスクを見落としやすくなっています。本セッションで は、組織ポリシーのうち静的に評価できる項目をPolicy as CodeとしてCIや実環境に組み込み、人の認知に依存しすぎず信頼性を維持する設計を紹介し、ConftestやKyvernoの実装例を交え、誤検知対応、ポリシー強度、既存リソース是正など、形骸化しないガードレール運用の工夫を共有しました。 開発組織の変遷や SRE の歩みから入り Policy as Code の紹介、実装方法の共有がされていました。私はまだ入社3か月目なのもあり、キャディの私でも学べるセッションでした。 会場の席はほぼ埋まり、立ち見がでるほどの盛況ぶりで約100名の方が参加されていました! SNSでも「Policy as Code が気になっている」、「PRC のレビューが大変なのわかる」、「Policy 違反1,684件すべて修正完了はすごい」などたくさんの反響をいただきました。キャディから登壇者を出せてとても良かったです。 Policy 違反の Report に「Policy の詳細や OK/NG の例が記載された wiki」へのリンクを入れているらしい。これを見ることで Policy 違反が発生した時の開発者側での修正をやりやすくしてる感じっぽい。よさそう。 #cloudnativekaigi #cloudnativekaigi_b — こたつ&&みかん (@kota2and3kan) 2026年5月15日 x.com 1684件すべて修正完了 すごすぎる #cloudnativekaigi #cloudnativekaigi_b — yuki / ほにゃにゃ (@honyanyas) 2026年5月15日 x.com うおー、ポリシーに対するテストコード書けるのめっちゃいい。 #cloudnativekaigi #cloudnativekaigi_b — たか (@_nogtk_) 2026年5月15日 x.com 登壇資料: speakerdeck.com 終わりに ブースへの来場・セッション聴講・懇親会と充実した2日間でした。クラウドネイティブな技術の進化は非常に速いですが、今回学んだ知見を業務に活かしていきたいと思います。 スピーカーの皆さん、参加者の皆さん、クラウドネイティブ会議の運営の皆さん、素晴らしいイベントをありがとうございました。今後もキャディは、技術的な挑戦や課題解決の知見を発信し、エンジニアコミュニティへの貢献を続けていきます。今後のイベントでもお会いできるのを楽しみにしています!
プログラミング言語を選ぶとき、開発効率や学習コスト、エコシステムの充実度など、考慮すべき要素は多岐にわたります。OSS パッケージを標的にしたサプライチェーン攻撃の増加や脆弱性に対するゼロデイ攻撃の発生といった状況を踏まえると、「アプリケーションが依存するライブラリをどう管理するか」も技術選定の重要な軸となっています。 私の所属する Control Plane 部の認証認可グループでは、2025年から、 Go の利用を本格化しました。技術選定時に挙げられていたメリットの一つが、まさに依存管理にあります。 本記事では、私たちが Go を選んだ背景と、1年間実際に開発してみて分かったことを振り返ります。 なぜ Go を選んだか 対象システムの特性 キャディの主流言語: TypeScript Go の特徴 1年間やってみてどうだったか 良かった点 メンテナンスコストの低さ AIコーディングとの相性 苦労した点 ライブラリに頼らないことのトレードオフ Go の慣習に慣れるまでのコスト 期待と現実のギャップ 採用した技術スタック まとめ なぜ Go を選んだか 対象システムの特性 キャディでは、製造業AIデータプラットフォームCADDi の基盤として「Control Plane」と呼ばれるシステム群を開発しています。Control Plane は、テナント管理や認証認可など、アプリケーションの機能から独立した管理層を担うシステムです。詳しくは こちらの記事 で紹介しています。 開発言語を選ぶにあたって重要だったのは、Control Plane が持つ特性です。Control Plane は、1つの大きなサービスではなく、認証ゲートウェイ、トークン発行、テナント管理、認可といった、それぞれが明確な責務を持つ小さなサービスの集合体です。各サービスはシンプルな機能を提供する一方で、プラットフォーム基盤としての信頼性が求められます。 つまり、「小さなサービスを多数、手堅く作れること」が言語選定において重要な要件でした。 キャディの主流言語: TypeScript キャディのバックエンドでは TypeScript が主流です。TypeScript の開発エコシステムの充実度は群を抜いています。 また、フロントエンドとバックエンドを同じ言語で統一できるのも大きなメリットです。 一方で、依存管理の観点では、ライブラリの数が増えがちで、依存管理のコストが増大します。 Go の特徴 Go は Web API の構築に向いた言語です。標準ライブラリもサードパーティのライブラリもシンプルなものが多く、必要なパーツを組み合わせて開発するようなスタイルを取りやすいです。 こうした特性が、小さなサービスを数多く開発・運用する Control Plane とよくマッチすると判断しました。 1年間やってみてどうだったか 良かった点 メンテナンスコストの低さ Go を採用して最も良かったと感じているのは、メンテナンスコストの低さです。 Control Plane のアプリケーションは、一度作った後は数ヶ月単位で改修が入らないことも珍しくありません。そのような場合でも、セキュリティアップデートは定期的に適用する必要があります。このような作業は1つ1つは小さくても、積み重なると大きな負担になります。 Control Plane には、Go採用以前から開発・運用されている TypeScript(NestJS)アプリケーションがありますが、これと比較して Go アプリケーションはセキュリティアップデートが必要になる頻度が低いと感じています(およそ1/5程度)。 AIコーディングとの相性 Claude Code や Devin を用いたAIコーディングはキャディ社内でも主流となっていますが、構文がシンプルで書き手による差異が生じづらい Go は、AIが読み書きしやすいという点でもメリットがあります。また、コードフォーマッター( go fmt )やテストランナー( go test )が標準のツールチェインに組み込まれていて、かつ、高速に動作するという点も Go の強みです。 苦労した点 ライブラリに頼らないことのトレードオフ Go では依存ライブラリを少なく保って開発することができます。これは裏を返せば、基本的な処理を自前で実装する必要があるということです。例えば、以下のコードではサーバのグレースフルシャットダウンを行なっています。 ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) defer stop() go func () { // e は Echo インスタンス if err := e.Start( ":" + c.Port); err != nil && !errors.Is(err, http.ErrServerClosed) { slog.Error(fmt.Sprintf( "error occurred when starting the server: %v" , err)) } }() ... <-ctx.Done() ctx, cancel := context.WithTimeout(ctx, 10 *time.Second) defer cancel() if err := e.Shutdown(ctx); err != nil { slog.Error(fmt.Sprintf( "error occurred when shutting down the server: %v" , err)) } このような、他の言語であればフレームワークが担ってくれるような処理も自分たちで書く必要がありました *1 。 依存の少なさと自前実装の手間は、トレードオフの関係にあります。私たちはこのトレードオフを意図的に受け入れました。長期的な運用コストの低さのほうが、初期の実装コストより重要だと判断したからです。 Go の慣習に慣れるまでのコスト チームメンバーの多くは Go での開発経験がそれほど多くありませんでした。Go 特有の慣習やイディオム ── たとえばエラーハンドリングの作法や、パッケージ構成の考え方 ── に慣れるまでには時間がかかりました。言語仕様の学習コストは低くても、「Go らしいコード」がわかるようになるまでには別のハードルがあります。 期待と現実のギャップ Go の採用にあたっては、goroutine による並行処理のしやすさにも期待していました。しかし、現時点では並行処理を積極的に活用する場面はまだありません。現在の Control Plane のサービス群は、典型的なリクエスト/レスポンス型の API が中心であり、goroutine の恩恵を実感する局面がまだ訪れていないのが実情です。 今後、バッチ処理や非同期ワーカーのようなワークロードが増えてきた際に、この特性が活きてくると考えています。 採用した技術スタック Goコミュニティにおいて実績のある技術スタックの中で、薄めのものを選定しました。 通信: Connect フレームワーク: Echo ※アプリケーションによってはフレームワーク無しの場合も ORM: Bun JWT: lestrrat-go/jwx いずれも優れたライブラリですが、特に Connect は素の gRPC に比べてデバッグしやすく、重宝しています。 まとめ Go は万能な言語ではありません。例えば、Go の特徴の一つである明示的なエラー処理は、コードの冗長さと表裏一体です。しかし、小さなサービスを数多く開発・運用する Control Plane においては、Go のシンプルさが強みになりました。 技術選定は常に文脈次第であり、私たちの経験がそのまま他の組織に当てはまるとは限りません。それでも、「Control Planeを Go で作る」という選択は、私たちにとって正しかったと考えています。 今後も Go を活用しながら Control Plane を拡充し、CADDi のマルチテナントプラットフォームをより堅牢なものにしていきます。 Go を使った Control Plane の開発に興味がある方は、ぜひ以下のページもご覧ください。 caddi.tech tech.caddi.com *1 : このサンプルコードは Echo v4 です。Echo v5 ではフレームワークに グレースフルシャットダウン機能 が搭載されています。
はじめに こんにちは。キャディ株式会社でソフトウェアエンジニアとして製造業AI見積クラウドCADDi Quoteの開発を担当している松田です。 私たちのチームでは、新機能の開発時など、プロダクトに新しい概念を導入する際には、開発者のみならずPdMやQAといった他職種を交えて議論し、ユビキタス言語を定義しています。 ユビキタス言語とは、ドメイン駆動設計においてビジネスと設計の橋渡しをする共通言語です。ドメインエキスパートとエンジニアが、会話・コード・ドキュメントに渡って同じ言葉を使うことで、ビジネスと設計の間の翻訳コストの発生を防ぐことが目的です。 出典:エリック・エヴァンス著、今関剛監訳、和智右桂、牧野祐子訳『エリック・エヴァンスのドメイン駆動設計』翔泳社(2011) p.34より一部改変して筆者作成 私たちがこれまでにユビキタス言語として定義した用語の多くは、チームの業務において実際に用いられ、生きた言葉として価値を発揮しています。 その一方で、定義した用語が実際の議論やドキュメントの中でうまく定着せず、形骸化してしまうケースも経験しています。使われないユビキタス言語はなぜ生まれてしまうのでしょうか? この記事では、こうした経験をもとに、使われないユビキタス言語を生んでしまう7つのアンチパターンを紹介します。これらのアンチパターンは、「誰を巻き込むか」「どう考えるか」「定義後の運用」の3つの観点にまたがりますが、網羅的なものではなく、私が実際の運用の中で失敗や悩みを抱えたポイントに重点を置いています。ユビキタス言語という概念は知っているが、実践の中でつまずきを感じている方に読んでいただければと思います。 はじめに アンチパターン1: エンジニアだけで決めてしまう 問題 なぜ起きるか どうすればいいか アンチパターン2: ドメインエキスパートという概念に固執してしまう 問題 なぜ起きるか どうすればいいか アンチパターン3: ドメインエキスパートの言葉を無批判に受け入れてしまう 問題 なぜ起きるか どうすればいいか アンチパターン4: 英語名をAIに相談せずに決めてしまう 問題 どうすればいいか アンチパターン5: 理詰めで考えてしまう 問題 なぜ起きるか どうすればいいか アンチパターン6: 声に出した時の自然さを軽視してしまう 問題 なぜ起きるか どうすればいいか アンチパターン7: ユビキタス言語辞書の作成が目的化してしまう 問題 なぜ起きるか どうすればいいか おわりに アンチパターン1: エンジニアだけで決めてしまう 問題 ユビキタス言語の定義からして当たり前ですが、エンジニアだけで議論するのはアンチパターンです。私たちのチームではエンジニアもお客様を訪問したり、セールス、カスタマーサクセス、PdMといった他職種のお客様訪問レポートを読むなどして顧客理解に努めていますが、顧客理解という点においてPdMにはかないません。エンジニアだけで議論すると、PdMなどの他職種が持つ貴重な知見を設計に反映する機会を失うことになります。また、エンジニアはどうしても実装をイメージしながら議論してしまうので、用語が実装に引きずられがちにもなります。 この場合、PdMからするとユビキタス言語の存在自体を認識していないことになるので、エンジニアだけがユビキタス言語を使い、PdMは別の言葉でエンジニアと会話する状態になるでしょう。 なぜ起きるか 背景には、いくつかの思い込みがあります。 ひとつは、遠慮です。PdMなどの他職種は忙しいので、設計寄りの議論に巻き込むのは申し訳ないという気遣いが働くことがあります。 また、説明コスト回避の心理も働きます。ユビキタス言語という概念を他職種に説明しながら議論するのを面倒に感じ、エンジニアだけで決めてしまいたくなることがあります。 どうすればいいか タイムボックスを区切った上で、PdMなどの開発者以外のメンバーを集め、効率よく議論しましょう。 そのために有効な手法がイベントストーミングです。イベントストーミングは、複雑な業務ドメインを参加者のコラボレーションにより探索するワークショップ形式の手法です。参加者が付箋を出し合い、それらを構造化する作業を通じて、ドメインの全体像が見えてきます。 参加者がそれぞれの理解に基づいて付箋を貼る中で、同じ概念が人によって別の言葉で表現されている状況が可視化されます。メンバー間の言葉のずれを発見し、用語の統一を図る中で、自然とユビキタス言語が定義されていきます。 イベントストーミングの良いところは、ドメイン駆動設計やユビキタス言語といった概念の理解が薄いメンバーであっても、ファシリテーターの指示をステップバイステップでこなしながらワークに参加できるところです。ファシリテーターにとっても、ワークの進め方を一度身につけてしまえば、参加者に対する説明コストはそれほどかかりません。 また、イベントストーミングを終えると、ユビキタス言語だけでなく、要件やドメインモデルの全体像も見えてきます。要件に関する認識合わせの中で自然とユビキタス言語も整理される形となるため、時間効率も良いと言えます。 イベントストーミングについては、キャディのTech Blogでも紹介されていますので、ぜひご覧ください。 caddi.tech アンチパターン2: ドメインエキスパートという概念に固執してしまう アンチパターン1はエンジニアだけで閉じてしまうという問題でした。これから議論するのはその逆方向の問題です。理想のドメインエキスパートがいないなら、ユビキタス言語の議論をしても意味がないという心理に陥るケースです。 問題 ドメイン駆動設計では、ドメインエキスパートとの協働がユビキタス言語の前提とされています。 ドメインエキスパートとは誰なのでしょうか? 通常は、そのソフトウェアを利用するユーザーとなります。私たちキャディで言えば、製造業の現場で働く方々、とりわけ、業務全体を俯瞰し、業務プロセスの設計や変革をリードしている方々です。 そうすると、ユビキタス言語を定義する際には、実際のお客様を会議にお招きし、議論をする必要があるのでしょうか? 多くの場合、それは非現実的です。 このような場合に、ドメインエキスパートを呼べないのであれば、ユビキタス言語の定義をしても意味がないのでは? という議論が発生したり、悩みを抱えてしまうことがあります。これが2つめのアンチパターンです。 なぜ起きるか EvansのDDD本では、ドメインエキスパートと開発者が継続的に対話しながらユビキタス言語を洗練させていく様子が描かれています。しかし現実には、ドメインエキスパートは自身の担当業務で多忙であり、ソフトウェア開発のためだけに継続的な時間を確保してもらうのは困難です。 特にSaaSの場合、この難しさはより顕著です。SaaSにおいてサービス提供者と顧客は一対多の関係にあり、顧客の要望がすべてプロダクトに反映されるわけではありません。提示した意見がそのままプロダクトに反映されるならまだしも、ただ設計を洗練させるためだけに貴重な時間を割くのは、ほとんどのお客様にとって難しいでしょう。 どうすればいいか ドメインエキスパートという概念に固執するのはやめ、現実的な妥協をしましょう。 現時点で顧客の言葉遣いをよく把握している社内メンバーを探し、その人を知識の源泉として活用しましょう。私たちのチームの場合、幅広い顧客と日常的に接し、業界全体の課題を俯瞰的に捉えているPdMがドメインエキスパート役として最適なことが多いと考えています。教科書通りのドメインエキスパートを求めるのではなく、現実的にアクセスできる範囲内でドメイン知識を吸収しましょう。 アンチパターン3: ドメインエキスパートの言葉を無批判に受け入れてしまう 問題 ユビキタス言語を議論する際、PdMに「お客様はこれを何と呼んでるんですか?」と質問することがあります。ここで、議論に疲れていると、お客様が使っている言葉をそのまま採用したくなってしまう瞬間があります。 しかし、お客様の個別の業務の文脈においては意味が通じる言葉であっても、私たちが作ろうとするプロダクトの文脈にそのまま持ち込むと具体性が高すぎたり、逆に意味が曖昧であったりすることがあります。 私たちのチームでも、製造業の調達業務においてサプライヤー企業からバイヤー企業に対して提出されるさまざまな種類の見積資料をどう呼ぶかを議論した際に、この点が問題となりました。PdMに、リリース後最初の想定ユーザーとなるお客様がその資料を何と呼んでいるのかを質問したところ、お客様は単に「見積書」と呼んでいるとのことでした。そのお客様にとっては、さまざまな種類の見積資料のうち、「見積書」と言えばこれを指す、というのが暗黙の合意となっていたのでしょう。しかし、さまざまな企業のお客様を対象とするSaaSプロダクトの文脈においては、「見積書」という表現は曖昧であり、私たちが開発している機能のスコープとは全く性質の異なる資料までもがそこに含まれてしまう懸念がありました。 結局、私たちは、お客様が実際にそう呼んでいるかではなく、業務慣習の異なる他のお客様や、開発者も含めて誤解が生じないことを優先し、資料の内容が明確に分かる別の用語を定義することになりました。複数企業のお客様を対象とするプロダクトとして、曖昧性を避けることを優先した選択でした。 なぜ起きるか ユビキタス言語という概念を、ユーザーや非エンジニアが用いる言葉をそのまま使用して開発することだと思い込んでいることが原因です。 実際には、ユビキタス言語は、ドメインエキスパートと開発者の双方が批判的意見を提示しながら洗練させるものです。このことは、Evans本でも以下のように明記されています。 ドメインエキスパートは、ドメインについての理解を伝えるには使いにくかったり不適切だったりする用語や構造に意義を唱えるべきであり、開発者は、設計を妨害することになるあいまいさや不整合に目を光らせるべきである。 出典:エリック・エヴァンス著、今関剛監訳、和智右桂、牧野祐子訳『エリック・エヴァンスのドメイン駆動設計』翔泳社(2011) p.27 どうすればいいか ドメインエキスパートからのインプットを、ソフトウェア設計上の概念として利用可能な抽象度に洗練させる責任は開発者にあることを認識しましょう。ドメインエキスパートが提示した用語を使って設計やコーディングを行うイメージが湧くかを常に想像し、違和感があれば対案を提示しましょう。 開発者側から対案を提示する際には、その対案に対して開発者以外の参加者が納得感を持っているかどうかにも目を配りましょう。往々にして発生するのは、議論の場ではユビキタス言語が合意されたものの、結局、開発者以外のメンバーは独自の用語を使い続けているという状況です。言葉が揃っていないことで苦労するのは結局のところ開発者なので、開発者以外のメンバーも含めてちゃんと納得感を持って使ってもらえそうかには注意を払う必要があります。 アンチパターン4: 英語名をAIに相談せずに決めてしまう 問題 DDD本を執筆した当時のEric Evansも想像していなかったでしょうが、現代のソフトウェア開発では、ドメインエキスパートや開発者と並んで、AIもユビキタス言語の策定の参加者となり得ます。 とりわけAIが活躍するのは、英語名の提案です。日本語圏でドメイン駆動設計を実践する際、現実的な妥協として、ユビキタス言語は日英併記で定義し、会話やドキュメントといった自然言語では日本語名を、ソースコードでは英語名を用いるといった手法が取られることが多いと思います。 日本語話者が英語名を定義する際、日本語を直訳したような英語名となり、英語として存在しない、あるいはニュアンスが意図と異なる表現になってしまうことがあります。私がチームの一員として関わった例で言えば、生成AIの登場以前の話ではありますが、「受注」という概念の英語名を Received order としてしまったことがありました。軽くインターネット検索すると分かりますが、このような表現は英語として通常用いられないようです。 また、ボキャブラリーが貧弱であるがゆえに FooData や BarInformation といった曖昧で意味の薄い名称となってしまうこともあります。 英語名の定義の際に以上のような問題を回避するには、AIを活用することが有効です。 どうすればいいか 英語名に自信がない場合はAIに提案してもらいましょう。 英語名をAIに考えてもらう上で重要なのは、日本語名を単語ベースで渡して翻訳させるのではなく、日本語名とその意味合いの説明文(加えて、既に決まっている英語名があればそれも)を一覧化し、全体を見せた上で日本語名に対応する英語名を提案させることです。こうすることで、単なる直訳ではなく、用語のニュアンスや用語間の関連性も踏まえた上での回答が期待できます。 また、複数の候補を提案させ、それぞれについて、ニュアンスや英語としての自然さを解説させた上で、どれを採用するか人間が選ぶべきです。決定する際は、インターネット検索により、その単語が実社会で意図したとおりのニュアンスで使用されているかを確認するのも忘れないようにしましょう。これにより、AIが提案した英語名をそのまま採用したら、実は日本語名とニュアンスがずれていた、といった事故を防ぐことができます。 アンチパターン5: 理詰めで考えてしまう ここまで取り上げてきたアンチパターンは、誰を議論に巻き込むかという問題でした。続く2つのアンチパターンは視点を変え、どう考えるかという軸から、使われない用語が生まれる構造を見ていきます。 問題 ユビキタス言語を定義する際、論理的な整合性を追い求めるあまり、聞いた瞬間に意味が伝わりにくい用語が生まれることがあります。このような用語は、議論の参加者が最初に提示した用語がどれもぴたりとはまらず、ああでもないこうでもないと色々な別案を出していくうちに発生します。 用語の意味を論理的に説明して理解できるのであれば、一見問題のないように見えます。しかし、長々と説明しないと意味が伝わらない用語は、会話の中で使うたびに説明のコストが発生します。結果として、メンバーは無意識のうちにその用語を避けるようになり、日常の議論には登場しない「公式用語」として辞書に残るだけの形骸化したユビキタス言語になっていきます。 なぜ起きるか ドメインエキスパートの言葉遣いを観察することから出発するのではなく、演繹的に用語を定義しようとしているのが原因です。 母国語について考えれば分かるように、自然言語は、演繹的に定義されたものではありません。そうではなく、ある言葉が使われているという事実が先にあり、そこから帰納的に文法や言葉の意味が定義されているわけです。 自然言語のこういった性質を踏まえれば、ユビキタス言語を定義する際も、ある言葉が自然に使われているという事実を重視すべきです。しかしながら、自然に使われている用語を設計にふさわしい抽象度に洗練させていく中で、理論先行に陥ってしまうことがあります。 どうすればいいか 前提として、論理的に用語を定義することを全否定するわけではありません。ソフトウェアは人工的な創造物であり、現実世界に対応物のない抽象的な概念を扱わざるをえない場合があります。自然に使われている業務の言葉だけでは表現できず、抽象的な論理から出発して命名せざるを得ない場面は一定あります。 ですが、まず優先すべきは、実際の会話の中で自然に飛び交っている言葉から出発することです。ドメインエキスパートやPdMが議論の中で無意識にある言葉を使っているなら、その言葉が直感的に選ばれたことには何かしらの理由があります。理詰めで独自の用語を作り出す前に、ある言葉が直感的に選ばれた理由を考察すべきです。 その上で、自然に使われている言葉の延長線上では必要な概念を表現できない場合に限って、理詰めで考えましょう。ただし、新たな用語を理詰めで作り出した時は、使われない言葉を生み出す道に片足を突っ込んでいるという意識を持つべきです。議論に疲れて結論を出す前に、立ち止まって、未来の自分たちが本当にその言葉を使って議論しているイメージが湧くかを考えましょう。 アンチパターン6: 声に出した時の自然さを軽視してしまう アンチパターン5では、どこから出発するかという問題を取り上げました。アンチパターン6は視点を変え、候補となった用語をどう検証するかという問題を扱います。 問題 アンチパターン5とも関連しますが、用語を定義する際、意味的な正しさだけに着目し、会話の中で自然に使えるかどうか、という観点が抜け落ちてしまうことがあります。 特に、文字ベースでの議論により用語の追加や修正をする場合、実際に誰もその言葉を口に出していない場合があります。意味として正確に定義できていても、声に出して使ってみると長すぎて発音しづらかったり、あまりにも仰々しくて会話の中で使うイメージが湧かない場合があります。 そのような用語は、合意された後も日常の会話には登場しません。ドキュメントやソースコードでは使われるかもしれませんが、会話では別の独自の用語が使われるという状態になってしまいます。 私自身も、最近の新機能開発で、漢字6文字の長ったらしい用語を定義してしまったことがあります。意味としては正確に定義したつもりなのですが、残念ながら、会話の中で使うには発音しづらく、直感性にも欠けていました。結果的に、ソースコードやドキュメントではユビキタス言語が用いられているものの、口頭の会話では、より簡潔な別の用語が使われてしまう状態になってしまいました。 なぜ起きるか 実際に声に出して使えるかという観点を、命名の評価基準として持っていないのが原因です。意味的な正しさはロジカルに言語化できるため議論の焦点となりやすい一方、声に出した時の自然さは主観に依存するため、議論が白熱すればするほど観点から抜け落ちがちです。 どうすればいいか 「意味的に正しいか」と別の評価基準として、「声に出して言いやすいか」「聞いた瞬間に意味が伝わるか」を命名の評価軸として意識しましょう。舌がもつれて発音しにくい用語や、説明なしには理解されない用語は、使われません。 そして、命名を確定する前に、未来の自分たちがこの言葉を使って議論しているイメージが湧くかを自問自答しましょう。特に、エンジニア以外のメンバーがその言葉を日常の会話で使っているイメージが湧かないなら、それは危険信号です。 最も直接的な検証方法は、実際に声に出してみることです。その用語を声に出すたびにストレスを感じたり、無意識に別の言葉を使ってしまうようであれば、用語を見直すべきです。 アンチパターン7: ユビキタス言語辞書の作成が目的化してしまう アンチパターン1〜6は、どうユビキタス言語を「定義するか」という問題を扱ってきました。最後のアンチパターンは、定義した後の話です。 問題 ユビキタス言語は辞書を作って終わりではなく、開発の中で使い続けることに意味がある、というのは誰しも分かっていることです。ですが、辞書の作成がゴールとなり、現実にはあまり使われないということが往々にして起こります。 なぜ起きるか 言葉選びは個々人のメンタルモデルを強く反映します。全員の共通言語であるユビキタス言語は、自分のメンタルモデルにフィットする最適な言い回しとは少しだけ異なります。そのため、ユビキタス言語を使い続けることには一定のコストが発生します。 ユビキタス言語を全員が使い続けるには、そのための意識と仕組みが必要になります。ただユビキタス言語辞書を定義しただけでは、各自が自分にとって最適な言い回しで会話する引力に逆らえず、形骸化の道を辿ってしまいます。 どうすればいいか ユビキタス言語を使い続けることについて、開発者がオーナーシップを持ちましょう。ユビキタス言語は開発者だけのものではありませんが、会話・ドキュメント・ソースコードのすべてで頻度高くユビキタス言語を利用するという意味で、ユビキタス言語によるメリットを最も享受するのは開発者です。 辞書を作ったところで、PdMなど他職種は多忙であるため、議論やドキュメントの際に逐一、辞書を確認して正しい用語を使ったりはしてくれないでしょう。そのため、開発者が率先して、議論、ドキュメント、開発チケットなどのあらゆる場でユビキタス言語を使い、定着を促す必要があります。ユビキタス言語が目に触れる頻度が増えれば、他職種も自然とユビキタス言語を用いる機会が増えるでしょう。 開発者が自信を持ってユビキタス言語を使い続けるために重要なのは納得感です。実際の開発では時間制約の都合上、十分な納得感がないまま、ユビキタス言語を決めてしまうことがあります。このように納得感がないと、ユビキタス言語の運用がなあなあになり、各自が思い思いの言葉で仕事を進める状況を許容してしまいます。 ユビキタス言語に対して納得感を持つため、この記事で提案したような観点も意識しながら、徹底的に用語を検討しましょう。本来、ユビキタス言語は使いながら改善していくべきものですが、現実には、一度データベースやAPIのスキーマとして書き込まれてしまうと、後から変更するのは大変です。そのため、最初にどれだけ納得感を持てるかが、その後の運用のベースラインとなります。 その上で、辞書をチームが実際に触れる場所に置くことも重要です。GitHubなど、日常的に参照できる場所に辞書を置き、コードレビューや設計議論の場で自然に参照される状態を作りましょう。辞書をリポジトリに置いておくと、AIもコード生成や設計検討の際に参照し、ユビキタス言語が自然に反映されるというメリットもあります。 おわりに 誰を議論に巻き込むか、用語の正しさをどう考えるか、定義した後どう使い続けるか——。7つのアンチパターンはそれぞれ異なる場面を扱っていますが、問うべきことは常に1つです。 論理的に整合していても、辞書にきれいに記載されていても、実際の会話やドキュメントに登場しなければ、ユビキタス言語に意味はありません。逆に言えば、定義がやや荒くても、チームが自然に使い続けている言葉は、立派なユビキタス言語です。 「未来の自分たちが、この言葉を使って議論しているイメージが湧くか?」 ユビキタス言語の議論のたびにこの問いを思い出してもらえれば、この記事の目的は達成されます。
製造業データ活用クラウド CADDi Drawerで SREを担当している佐藤です。 組織編成でCADDi Drawer専任のSREになったことを機に、長らく後回しにしていたDatadog MonitorのTerraform構成の再設計に取り組みました。複数チームの監視設定が単一リソースに混在しOwnershipが不明確なため、各チームが自律的に改善に踏み出せない状態でした。 本記事ではこの課題をOwnership単位のモジュール設計で解消した経緯と学びについて紹介します。 監視設定が抱える課題 単一リソースに積み上がった監視設定 Ownershipの不在によるアラート改善の停滞 Ownershipを明確化したモジュール設計 監視ロジックとOwner情報の分離 Owner情報の一元管理 ふりかえり よかったこと 苦労したこと 移行の現状と今後 おわりに 監視設定が抱える課題 キャディでは監視基盤にDatadogを採用しており、Datadog Monitorはすべて監視用の単一リポジトリでTerraform管理しています。 歴史的経緯により、監視すべき内容については検討できていましたが、Terraformモジュールとしてどう実装するかは整理されていませんでした。 このためDatadog MonitorのTerraform実装には以下のような課題を抱えていました。 単一リソースに積み上がった監視設定 Terraformモジュールの設計が難しい問題です。適切な抽象化の粒度を見極め、再利用可能な形でモジュール化するには、対象サービスや組織の全体像が見えていることが前提になります。 組織・サービスが急成長していた時期には監視対象が次々と増え、その都度設計を見直す余裕はありませんでした。この結果、再利用可能な形でのモジュール化は採用せず、既存の設定を拡張する形で対応し続けていました。 具体的には、監視対象のサービスが増えるたびに新規のモジュールやモニターを構築することはせず、既存の単一のDatadog Monitorに対象サービスを追加し続けていました。 Terraformのdatadog_monitorリソースでは監視条件をquery属性の文字列として定義しますが、サービスが増えるたびにOR条件を追記し続けた結果、以下のような状態になっていました。 logs( "status:error (service:SERVICE_A OR (service:SERVICE_B -message:\"noisy message\") OR service:SERVICE_C OR service:SERVICE_D OR (service:SERVICE_E env:production))" ).index("*").rollup("count").last("5m") > 100 組織規模が小さく、サービス数が2-3個の場合やサービス固有の除外条件が含まれていない場合にはこれでも上手くいきました。 しかし、サービス数が増え、サービスごとの監視条件が混在するようになると、とたんにコードからどのサービスがどの条件を持つのか読み解くことが困難な状態になりました。 また、問題の兆候は認識していましたが、全サービスに影響する監視設定の一括整備に踏み切るきっかけがなく、他のタスクを優先していました。 Ownershipの不在によるアラート改善の停滞 この課題に向き合うきっかけになったのが2025年に実施した組織編成です。 それまで全社横断でインフラ運用を担っていたSREチームから、サービス・プロダクトごとにSREが配置される形になりました。 私はCADDi Drawer専任のSREとして新たな役割を担うことになりました。 CADDi Drawerの信頼性向上に絞った視点で改めて監視設定を見ると、Datadog Monitorの複雑化に関する問題が浮き彫りになりました。 複数サービスの設定が混在したクエリへの変更は、git diff や terraform planでも影響範囲が掴みにくい状態でした。 Ownerの異なるチームが同じリソースを共有しているため、「他チームに影響しないか」という懸念が変更の腰を重くし、細かな改善がどんどん後回しになっていました。 クエリ実装の複雑さ以上に懸念になっていたのがSlackのアラート通知先の混在でした。 Datadog MonitorのSlack通知チャンネルの動的切り替えはクエリ条件以上に複雑になるため、Datadog Monitorごとに単一のSlackチャンネルに通知していました。 その結果、複数チームのアラートが同じSlackチャンネルに通知され、関係のないアラートであっても内容を都度確認する割り込みコストが発生していました。 SREチームで議論した結果、これらの問題の根本は、どのチームがどのモニターに責任を持つかが不明確というOwnershipの欠如にあると判断しました。 複数チームのサービスが1つのdatadog_monitorリソースを共有しているため、変更が他チームに影響するかどうかを確認せずに修正できません。 自チームの判断だけで変更に踏み切れず、誰も主体的に手を入れられない状態になっていました。 Ownershipを明確化したモジュール設計 これらの課題を解決し、Datadog MonitorのOwnershipを明確化するため、監視項目のTerraformモジュールを設計して移行しました。 監視ロジックとOwner情報の分離 従来の構成では、通知先や担当チームといったOwner情報がモジュール内に直接書き込まれていました。Owner情報が埋め込まれた構造では、呼び出す側がチームごとの情報を渡す設計にはできません。 具体的には以下のような構成でした。 terraform/ ├── modules/ │ └── monitor/ │ └── shared_monitors/ # 全サービス・全チームの監視設定が混在 └── environments/ ├── <google_project>-development/ │ └── main.tf # shared_monitorsモジュールを呼び出す └── <google_project>-production/ └── main.tf # shared_monitorsモジュールを呼び出す terraform/environments/<environment>/main.tf が shared_monitors モジュールを呼び出す構成です。このモジュールに全サービス・全チームの監視設定が集中していたことが、前述の問題の原因でした。 そこでこのモジュールを解体し、監視ロジックの情報に限定したモジュールを作成した上で対象サービス・通知先・担当チームといったOwner情報は呼び出し元から変数として渡す設計にしました。 具体的には以下のような構成です。 terraform/ ├── main.tf # environments をモジュールとして呼び出す ├── teams.tf # チーム情報を一元定義 ├── modules/ │ └── common/ │ ├── cloudrun-service/ # Cloud Run監視モジュール │ ├── alloydb/ # AlloyDB監視モジュール │ └── ... └── environments/ └── <google_project>-production/ └── service-a/ ├── locals.tf # 通知先・Owner情報を定義 └── main.tf # 各監視モジュールをOwner情報と共に呼び出す 監視項目の種類( cloudrun-service など)ごとにモジュールを定義し、 terraform/environments/<environment>/<service>/ というサービス単位のディレクトリから呼び出す構成です。 モジュール自体は監視対象のプロダクトを知らず、呼び出すディレクトリごとにOwnershipが決まるため、「誰がどのアラートに責任を持つか」がディレクトリ構造として表れます。 各モジュールに対するOwner情報を変数として以下のように設定することで、サービスごと・モニターごとのOwnershipを明確化できました。 # environments/project-production/service-a/main.tf module "cloudrun_service_monitor" { source = "../../../modules/common/cloudrun-service" service_name = "service-a" env = "production" owner_teams = local.owner_teams mention_slack_alert_ch = local.mention_slack_alert_ch mention_oncall = local.mention_oncall } この設計により、変更の影響範囲が自チームのスコープに収まり、他チームを巻き込まずに監視設定を改善できるようになります。移行後はこの設計方針をドキュメントにまとめ、社内で共有しました。設計を標準として明文化することで、チーム間の設計認識を統一し、今後の監視設定追加・変更にも一貫したOwnership管理を維持できるようにしました。 Owner情報の一元管理 監視対象ごとにDatadog Monitorが分割されることで、移行後はモニターの総数が増えます。多数のモニターを管理するにはタグ情報が欠かせないため、各Datadog Monitorへのタグ付けを徹底しました。 タグ自体は従来から設計に組み込まれていましたが、今回の移行を機に全Datadog Monitorへの適用を強化しました。 タグ情報の適用を強化する上で、通知先Slackチャンネル、担当チーム名といったOwner情報を各サービスの設定に個別に書き込むと、組織変更のたびに全サービスのファイルを修正する必要があります。また、複数の場所に分散して書くとチーム名やSlackメンションの表記ゆれも起きやすくなります。 そこで、チーム情報を teams.tf に一元定義しました。Datadog Teamリソースもここで合わせて作成します。 # teams.tf locals { owner_teams = { team_alpha = { name = "Dept A > Group A > Team Alpha" mention = "<!subteam^XXXXX>" # @team-alpha tag = "team:team_alpha" } team_beta = { name = "Dept B > Group B > Team Beta" mention = "<!subteam^YYYYY>" # @team-beta tag = "team:team_beta" } } } resource "datadog_team" "teams" { for_each = local.owner_teams name = each.value.name handle = each.key } 組織変更が起きた場合も teams.tf の修正だけで全サービスに反映されます。 ふりかえり よかったこと サービスごとに通知先を変更できるようになったことがメリットとしては大きいです。 チームごとにSlackチャンネルを切り分け、CADDi Drawer 関連のアラートのみに集中できる環境を整えたことで、業務への割り込みを最小限に抑えることが可能になりました。 datadog_monitor がOwnerごとに分割されたことで、PRレビューの負荷も下がり、自チームの判断だけで変更をマージできるようになりました。 モジュール化に伴い監視条件を変数として宣言的に記述する構成になったことで、各サービスの監視条件を個別に把握できるようになった点も嬉しいポイントです。 苦労したこと 最も苦労したのはDatadog Monitor移行に伴うPRレビューです。Datadog Monitorリソースの分割には destroy / create が伴う上、クエリの整理も必要だったため、大量の terraform plan の差分が発生しました。これをすべて人手で確認しきることは難しく、想定以上にレビューに時間がかかってしまいました。 今回は利用できませんでしたが、AIを活用したリソース移行の半自動化や設定差分の検出など、効率化の仕組みを今後の大規模移行に活かしたいと考えています。 移行の現状と今後 作業リソースやスケジュールの都合上、通知頻度の高いコンポーネントの移行を優先して完了させました。旧モジュールへの新規追加を禁止することでこれ以上の複雑化を防ぎつつ、通知先のチャンネル分離も継続して進めています。一方、一部のDatadog Monitorはまだ解体する必要があるものが残っており、引き続き整備を進める予定です。 設計方針はドキュメントとして共有しているものの、まだ十分に浸透できているとは言えません。設計の定着にはドキュメントだけでなく、ポリシーチェックをCIに組み込むような仕組みも必要だと感じています。今後は組織横断チームと連携しながら検討を進めていきます。 おわりに 組織の自律性を高める上で、監視設定のOwnershipも合わせて整備することが重要だと実感しました。Ownershipが明確であれば、自チームの判断だけで監視設定を改善でき、変更のスピードも上がります。 今回は組織編成がきっかけとなり、長らく後回しにしていた構成の再設計に取り組むことができました。 普段から監視設定を気軽に修正できる状態を維持し、組織や役割が変化した際には監視のOwnershipが適切に機能しているかを継続的に見直していきます。
機械学習エンジニアの竹本です。普段は、製造業の膨大なドキュメントを対象にしたRetrieval-Augmented Generation(RAG)の検証に取り組んでおり、その一環として社内検証向けのベンチマークデータセットを作成しました。同じ課題を抱えるエンジニアの方の参考になればと思い、本記事を書きました。 TL;DR 作りたい。でも動き出せなかった 当時の思い込みと、今の自分からの答え合わせ 1. LLM-as-a-Judgeに頼るしかないと思っていた 当時の状況 データセットを作ってみると 2. データセットを作れるのか? そしてCSやユーザーに頼っていいのか? 当時の状況 CSに相談してみると 3. 時間も工数もかかるのに、そこまでしてやる価値があるのか 当時の状況 データセットを作ったら検証速度が5倍に向上 作る過程で得られた想定外の価値 まとめ TL;DR 最初は、コストや体制の壁があり作成に動き出せなかった 制約を取っ払って考え、「作りたい」と声に出したら、動き出せた ベンチマークデータセットを作って、検証スピードが約5倍になった 作る過程そのものも、ユーザー理解を深めるきっかけになった 作りたい。でも動き出せなかった RAGの検証を進める中で、製造業の深いドメイン知識や個社特有の知識が求められる領域では、LLMによる評価だけでは精度を正しく判断しきれないと感じるようになりました。評価用のベンチマークデータセットが必要だと分かっていたものの、 作成にかかる時間的コストの大きさと、自チームやアノテーションチームだけでは作りきれないという課題 から、なかなか動き出せずにいました。 転機となったのは、チーム全員で実施した「100本ノック」でした。3日間で一人100個ずつアイデアをスプレッドシートに書き出し、不確実性やコストといった制約をいったんすべて取り払って、「本当は何をすべきか」を純粋に考え抜く取り組みです。その中で、「ドメインエキスパートと一緒にデータセットを作りたい」と提案しました。議論してみると、LLM-as-a-Judgeだけでは正しく評価しきれないという課題感と、データセットがあれば検証の質とスピードが大きく変わるという期待は、チーム内のエンジニアやPdMにも共通のものでした。コストやタイムラインといった制約がある中で、優先度が上がりきっていなかっただけでした。制約を外して考えたことでその必要性が共通の課題として顕在化し、チームとして取り組む合意が得られ、データセット作成が動き出しました。 そして実際に作成してみると、想定以上の価値がありました。 ここからは、当時抱えていた以下3つの思い込みを1つずつ答え合わせをしていきます。 LLM-as-a-Judgeに頼るしかないと思っていた データセットを作れるのか? そしてカスタマーサクセス(CS)やユーザーに頼っていいのか? 時間も工数もかかるのに、そこまでしてやる価値があるのか 当時の思い込みと、今の自分からの答え合わせ 1. LLM-as-a-Judgeに頼るしかないと思っていた 当時の状況 当時は、プロンプトチューニングや評価基準の調整を行い、LLM-as-a-Judgeだけに頼った定量評価をしていました。一定の指標として数値は出せていたものの、そのスコアをどれだけ信頼してよいのかという違和感は常にありました。実際、評価軸は「謝罪せずに回答しているか」「クエリと関連しているか」「直接的に答えているか」といった回答品質に限定した比較的表層的なものに寄りがちで、プロダクトの精度が上がるにつれて、それらの指標では差がつきにくくなり、改善の効果をうまく捉えられなくなっていきました。具体的には、定量評価では有意な差が出ていても、定性評価をしてみると実質的には同等だったり、逆に定量評価では差がないのに定性評価では明らかな改善が見られるといった乖離がしばしば発生していました。 そもそも、製造業で扱うドキュメント(不具合報告書や製品仕様書など)は、専門用語や文書構造がテナント・部署ごとに大きく異なり、読み解くには業務固有の背景知識も求められるなど、非常に専門性の高い領域です。長年蓄積された情報同士の関係も複雑で、単純なテキスト理解だけでは評価しきれません。こうした背景もあり、ドメイン知識やテナント固有の文脈を持たないLLMはもちろん、エンジニアによる定性評価も、どうしても表面的な確認にとどまりがちでした。それでも他に現実的な手段がなく、 LLM-as-a-Judgeだけに頼らざるを得ない 、というのが当時の実態でした。 データセットを作ってみると 実際にデータセットを作成してみると、この前提は大きく変わりました。 製造業のドメイン知識やテナント固有の知識を織り込んだGround Truth(詳細は割愛しますが、検索されるべきドキュメントとあるべき回答の2種類)を用意できたことで、それを基準にした評価が可能になりました。これまでのような表層的な評価では見えなかった改善の差分も、Ground Truthとの比較によって定量的に捉えられるようになり、 評価の解像度が一段上がった感覚 があります。また、定性評価においても、「なぜこのケースはうまくいったのか/いかなかったのか」を考える際の拠り所ができ、分析の質も大きく変わりました。 2. データセットを作れるのか? そしてCSやユーザーに頼っていいのか? 当時の状況 自チームやアノテーションチームだけではデータセットを作れないと認識していました。製造業のドメイン知識に加え、テナントごとの固有知識まで含めて網羅する必要がありましたが、その両方を十分に備えた人材は身近にいませんでした。さらに、対象となるコーパスはテナントごとに数万から数百万規模のドキュメントファイルに及び、その量も大きな課題でした。 データセットを作りたいという思いはあったものの、具体的な進め方が見えていませんでした 。 CSに相談してみると そうした中でCSに相談し、どのように進めればデータセットを作成できるかを一緒に検討しました。その結果、CSがユーザーにヒアリングの場を打診し、ユーザーと一緒にミーティングを行いながらデータセットを作っていく進め方を提案してくださいました。 実際のミーティングでは、事前に用意したデータセット設計をもとに、欲しい情報を一つひとつヒアリングしながら作業を進めました。ただ、1つのテナントに格納されているドキュメントは膨大で、様々な部門の何年もの歴史の中で蓄積されたものです。そのテナントのユーザーでさえ、どのような回答があるべきかは簡単には分かりません。それを一緒に揃えていくには、やはり時間も手間もかかりました。 それでも、 CSはユーザー体験の向上のために、ユーザーは業務改善につながるならばと、それぞれ積極的に関わってくれました 。そうした関わりを通じて、みんなが同じ方向を向いていると分かったことは嬉しい驚きでした。 3. 時間も工数もかかるのに、そこまでしてやる価値があるのか 当時の状況 当時は、データセットの作成に多くの時間と工数がかかることが明らかであり、さらに不確実性も高く、そもそも実際に作りきれるのかどうかさえ分からない状況でした。そのため、かけたコストに見合う価値が得られるのかを判断できず、取り組むべきかどうかに迷いがありました。 データセットを作ったら検証速度が5倍に向上 実際にデータセットを作ってみると、その価値は想像以上に大きいものでした 。Ground Truthが存在することで成功と失敗の判断が明確になり、定性評価において悩んだり迷ったりする場面が大幅に減りました。 例えば、Ingestion改善の検証では、以前は数十〜数百件の検索結果と回答結果を一件ずつ目視で確認した上で評価・示唆出し・次の改善策の検討を行う必要があり、全体で約1週間かかっていました。データセット導入後は、定量評価で全体の傾向を把握した上で深掘りすべきケースを絞り込めるようになり、一件ずつ精査する作業負担が大きく減ったことで、この一連の検証サイクルが約1日で回せるようになりました。 また、RAGについては新しい手法が次々と登場しますが、信頼できるデータセットがあることで、それらの手法の効果を迅速に検証・比較できるようになり、試行錯誤のハードルが大きく下がりました。 作る過程で得られた想定外の価値 さらに、データセットを作る過程そのものからも、想定外の価値が得られました。過去の会話履歴を網羅的に確認し、データセットに用いるクエリを幅広く抽出・選定する中で、ユーザーがどのような情報を求め、どのようなクエリとして入力しているのかを観察したことで、ユーザー理解が深まりました。また、ユーザーと一緒にデータセットを作成する過程で、参照されるべきドキュメントを具体的に把握できるようになり、実際のユーザークエリから適切なドキュメントにたどり着くまでの検索の流れをより深く理解できました。加えて、ベンチマークの評価指標についてチーム内で議論を重ねる中で、自分たちのプロダクトが本来提供すべき価値が何かも明確になっていきました。 まとめ データセットの作成には多くの時間と工数がかかりますが、それでも得られる価値は当初の想定を大きく上回るものでした。このすべてのスタート地点は、制約を取っ払って考えたことと、「作りたい」と言葉にして伝えたことでした。 今回は最初の一歩として、優先度の高いクエリを選定してデータセットを作成しました。これだけでも検証の質とスピードは大きく変わりましたが、さらに質を維持しながら作成工数を減らし、クエリのバラエティや量を拡充していきたいと考えています。
最近はデータ基盤をこねこねしているData Fabric部の 伊藤 ( @amaya382 ) です。今回はテストケースをAIに量産させた話を題材に、AI時代の効率化で考えていたことをお届けします。 参画していたプロジェクトが順調に進み、いよいよ大詰めが見えた辺りで2人でシステムテストを整理していたんです。影響範囲が大きい機能だったので覚悟はしていたのですが、数えてみると観点は数十、テスト項目は100を超え、手順に至っては数百ステップ。ついでにリリース日も決まっていました。 結論から言うと、テストケース作成の大部分をAIに任せることで無事リリースに至りました。振り返ると「とりあえず全部AIに任せる」では到底無理で、効いたのはボトルネックとの見極めとAIが品質を保って自走できる環境を丁寧に整えるアプローチでした。 本稿ではそのAIを自走させるための "仕込み" にフォーカスします。特定のツールや手法のハウツーではなく、AI時代における効率化の考え方の一事例として読んでいただけると嬉しいです。 背景:大量のテスト観点を前にして 自動化までの流れ 1. コンテキストの準備 2. 初期ケースの手動作成 3. AIとの対話的なケース作成 4. テストケース作成のSkill化 — 再現性を手に入れる うまくいったこと AI-friendly な環境がもたらした相乗効果 テスト環境やパターンコンテキストの活用 Skill化による再現性 残ったボトルネックと次にやりたいこと A. コンテキストすら準備してもらう B. 人間はスケールしない C. テストファーストへの組み込み おわりに 先日、弊社小森による 『YAML×AIで脱Excel地獄!テストの本質に思考を集中させよう』 という記事の末尾で予告されていたものにあたります。 背景:大量のテスト観点を前にして 膨大なケースをどうしようかと考え始めたちょうどその頃、 『YAML×AIで脱Excel地獄!テストの本質に思考を集中させよう』 で紹介されたYAML形式 + testdocツールが使える見込み、つまりテキストベースでテストケースを管理できる見込みが立ち始めていたところでした。YAMLでテストケースを書ければ、構造化されたテキストとしてGitで管理でき、PRベースのレビューもできる。これ自体はとても良い環境です。 とはいえ数百の手順を手で書きたいわけもなく… リリース日は計画済みで、早く終わりの見込みを立てて落ち着きたいという状態でした 😓 そこでまず考えたのが「このテキストベースでテストケースを書ける環境をどうやって活かすか」です。まずはテキストベースなのでAIへの入出力がダイレクトかつ効率的に使えそう、しかも構造化されたフォーマットなのでAIが生成するテストケースの品質も安定しやすいはずというところから、筆者らは大規模なテストケース作成をAIに任せてみることにしました。 自動化までの流れ 件数が多いことは最初からわかっていたので、どうしたら再現性の高い形に落とし込んで大部分を効率的に圧縮できるか、端的に言えば いかに楽をできるか を考えていました。具体的には、自走させるためのコンテキスト準備とClaude Skill化 1 による再現性で一気に進められるのではという仮説を立てていました。 大まかな流れは以下の通りです: 自動化までのフロー 前半のコンテキストの準備は地道な作業ですが、ここを丁寧にやることが後半でAIが自走できるかどうかの分かれ道になりました。順に説明していきます。 1. コンテキストの準備 AIにテストケースを書かせるためには、人間が書くときと同じように十分なコンテキスト (前提知識) が必要です。具体的には以下のようなものを整理しました。 テスト環境の情報 : テスト環境の構成やテスト用アカウント、テストデータ情報 テストパターンのマトリックス (決定表) : 条件の組み合わせで挙動が変わることがあるため、そのパターンを表形式で整理したもの 要件定義書 : 画面や機能単位で階層化して書かれていたため、ほぼそのまま入力に利用 画面のスクリーンショット : 既存の仕様に明文化しきれていなかったUIに関する情報を補足するため 特に重要だったのがテストパターンのマトリックスです。今回のプロジェクトでは、データと条件の組み合わせに応じた結果が特に重要であり、その組み合わせパターンが膨大でした。このマトリックスをCSVとして整理しておくことで、AIがテストケースの期待結果を組み立てる際の判断根拠として参照できるようにしました。 表1: テストパターン情報のイメージ 図面-01 図面-02 図面-03 ... ユーザー01 閲覧可 閲覧可 閲覧可 ... ユーザー02 閲覧可 閲覧不可 閲覧可 ... ユーザー03 閲覧可 閲覧不可 閲覧不可 ... ... ... ... ... ... 2. 初期ケースの手動作成 テストケースのイメージが全くない状態から生成させては期待するような結果は得られないだろうという仮定から、最初のいくつかのテストケースは手動で丁寧に作成しました。 全体のケース数からすれば十分小さい数ですが、これがAIにとってのお手本として使えるだろうと考えました。 後ほど生成されたものを見ると、YAMLのフォーマット、各項目の記述粒度、強調表示のルール、期待結果の書き方... こうした暗黙的な品質基準を、お手本を通じてAIに伝えられたことが大きかったです。全体のケース数に比べれば数ケースの手動作成は非常に効果的な投資でした。 手動で作成したお手本ケースのイメージ: testcases : - id : DAC-SEA-1-01 category : - キーワード検索+表示切替(ユーザー01) title : サムネイル表示(32件) priority : high precondition : - "*B*ユーザー01*B*でログインしていること" steps : - 画面左上のハンバーガーメニューから「図面」を開く - 画面右上の表示件数を*B*32*B*件にする - 画面上部のソート順を「↑(昇順)」「図番」に設定する - 画面上部の「キーワード」欄に「*B*`AUZ-0`*B*」を入力し、検索ボタンを押す expected : - "別表([](sheet://権限#ユーザー-図面))の通り、XXX、YYY" notes : page : P-DW-DRA-001 user : ユーザー01 requirement : d-2-1-1 3. AIとの対話的なケース作成 お手本となる初期ケースができたら、Claude Codeと対話的に新しいテストケースを作成していきました。 やりとりのイメージとしてはこんな感じで、お手本と同程度の質のケースをClaude Code上で作成できるようになるまで行いました。 > 「XXX-2 のテストケースを新規作成してください」 → AIがドラフトを生成 → 「そこの要件はここに置いてある資料を確認して」 → 「こっちのテストデータとテストユーザーの組み合わせを利用して」 → 「強調すべき箇所が足りない、注意すべき条件のあるデータは色を変えて」 → ... 10 往復もしないうちにお手本ケースと同程度のケースが完成! 最初の数回はこうしたフィードバックを重ねる必要がありましたが、会話が進むにつれてAIの出力品質はどんどん安定していきました。テストパターンのマトリックスをほとんど破綻なく活用していたのには正直驚きました。また結構雑に取得して入力にした画面のスクリーンショットも手順作成に活用されていて、言語化をスキップして入力に活用できる点でこれから重要になるだろうなと感じました。 AIに生成させたケースのイメージ。手動で作成したものと見分けるのは困難なレベルに感じた: testcases : - id : DAC-PRO-1-02 category : - プロジェクト一覧表示 title : アクセス権限のないプロジェクトは一覧に表示されない(ユーザー03) precondition : - "*B*ユーザー03*B*でログインしていること" steps : (...省略...) - プロジェクト一覧画面が表示されることを確認する expected : |- 以下のプロジェクトが表示されること : - *B*PJ-B*B* (権限あり) - *B*PJ-C*B* (public) (...省略...) *R*PJ-A*R*、*R*XXX用図面*R* (権限なし) は*R*表示されない*R*こと notes : page : P-DW-PRO-001 requirement : d-1-1-1, d-1-1-6 user : ユーザー03 4. テストケース作成のSkill化 — 再現性を手に入れる ここが今回の取り組みで最も効率化へのインパクトが大きかったであろうポイントです。 数回のフィードバックでテストケースが対話的に作れるものの、これを100回も繰り返したくはありません。 対話的にテストケースを作り上げる過程で蓄積されたマトリックスの参照方法、YAMLフォーマットの慣習、強調ルール、期待結果の書き方などこれらをすべて含んだ形で、Claude Skillとしてパッケージ化することを試みました。プロンプトは至極単純に「ここまでのテストケース作成手順をSkill化してください」とするだけ、以下のようなすぐに誰でも使えるClaude Skillが得られました 2 。 --- name : create-testcase description : XXX 機能のテストケースを YAML フォーマットで作成する allowed-tools : Read, Glob, Grep, AskUserQuestion, Bash(ls:*) --- # Test Case Creation Skill このスキルは、XXX のテストケースを作成する際の標準的な手順とフォーマットを提供します。要件や仕様を漏れなく考慮し、必要十分なテストケースを作成することが目的です。 ## 前提条件 - テストケースは YAML フォーマットで記述する - `reference/` ディレクトリ配下に要件定義・テスト環境情報のCSVファイルが存在する (...省略...) ## 既存テストケースの参照 必要に応じて既存のテストケースを参照してフォーマットを確認 : ```bash # 既存のテストケースを確認 ls cases/*.yaml ``` 参考になるファイル : - `search.yaml`: 基本的なフォーマット (...省略...) Skill化してからは、テストケースの生成がワンショットで可能になりました。「XXX-3 のテストケースを作成してください」と伝えるだけで、Claude Code が必要なファイルを読み込み、マトリックスを参照し、YAMLフォーマットに従ったテストケースを出力してくれます。人間がやることは、出来上がったケースの微調整だけです。完璧とまでは言えないものの、ほとんどそのまま使えるレベルのドラフトが即座に作成できました。これは正直、期待以上の結果でした。 さらに、このSkillは自分以外のメンバーにも共有できます。テストケース作成のノウハウがファイルとして形式化されているので属人化の解消にもつながりました。実際、私がお休みの日に他のメンバーがテストケースを作成することもありました。 うまくいったこと AI-friendly な環境がもたらした相乗効果 テストケースのYAML化はもともとAI活用を見据えた意図もありましたが、実際にやってみると想像以上にうまく噛み合いました。常に構造化された状態でハンドルされるので安定したテストケースが得られました。スプレッドシートのままだったら、データの抽出と整形という余計な前処理が必要でトークン効率も速度も落ちてイテレーションが遅くなっていたでしょう。さらにテキストベースでGit管理しているおかげで、AIが生成したテストケースもそのままPRとして差分レビューできました。 結果として構造化は人間にもAIにもうれしい施策でした。 テスト環境やパターンコンテキストの活用 先述の通り、AIがテストパターンのマトリックス情報をほぼ破綻なく活用してケースの期待値を生成していたことには驚きました。もちろん100%完璧ではなく微調整は必要でしたが、ゼロから手で書くのと比べれば、工数は大幅に削減できました。 微調整のほとんどが要件や既存の機能に関するコンテキスト不足によるものでした。コンテキストが不足すると推測に推測を重ね、存在しない機能を持ち出したりと頓珍漢なケースを生成してしまうこともありました。勝手に推測させないようなプロンプトの追加なども試しましたが、やはり適切にコンテキストを補完するのが一番効果的でした。 Skill化による再現性 一度作成した Skill は何度でも再利用できます。新しい画面や機能のテストケースが必要になっても、要件を伝えるだけで同程度の品質のケースが生成されます。テストケース作成のナレッジが Skill として形式化されたことで、特定の人に依存しない仕組みが作れたのは大きな成果でした。 残ったボトルネックと次にやりたいこと 今回の取り組みでうまくいった部分は多いですが、まだまだ大きな改善の余地があると思っています。特に (A.) は段階的な準備で1週間はかかり、(B.) はエンジニアのアテンションをかなり持っていくものであるため、導入効果は大きいはずです。 A. コンテキストすら準備してもらう テストケースの量産はAIに任せられましたが、その前段にあるテストパターンマトリックス作成などのメタ的な作業は人手で行いました。ここはテストの核心であり、現時点では人間の判断が必要となっている領域です。 とはいえ、PRDや設計書が十分に構造化されていれば、テスト項目のドラフト作成もAIに任せられる余地は十分にあると信じています。今後試してみたい領域です。 B. 人間はスケールしない 現状AIが生成したテストケースにはレビューが必要です。ここで早くもコーディングでのAI利用と同じく、どれだけAIで作業を圧縮しようが人間のレビューがボトルネックとなってしまいました 3 。 ここでのテストケースのレビューとは「1. 要求・テスト観点に紐づいているか」と「2. 手順が検証として妥当かどうか」の2つに大別できると考えており、それぞれで解消を図っていきたいです: 「 1. 要求・テスト観点に紐づいているか 」: 生成されたテストケースを要求・テスト観点と突き合わせるレビュー専用 Agent の導入 「 2. 手順が検証として妥当かどうか 」: テストケースの字面を追う「机上のレビュー」から、Playwright などでテストの実行自体を自動化することで「テスト実行結果を見て判断するレビュー」へ転換 C. テストファーストへの組み込み 今回のシステムテストは開発の終盤に着手しました。これは一般的なプロジェクトでもよくある話ですが、不具合発覚時の手戻りリスクが大きいという問題があります。 もし自動テストが十分に高品質であれば、要件定義の段階でシステムテストケースを生成し開発と並行してテストを通していく、いわばシステムテストレベルでのテストファーストが実現できるかもしれません。 おわりに 今回の取り組みを一言でまとめるなら、「達成したいことに対してちゃんと効率化できるようにAIを自走させることの重要性」に尽きます。 テストケースの構造化、要件定義やマトリックスの整備、お手本となる初期ケースの丁寧な作成などの仕込みがあったからこそ、本来最も時間がかかるテストケース作成をAIで圧縮できました。 冒頭にも書いた通り、具体的なツールや手法は日々進化しています。その時代に取りうる選択肢とやりたいことに合わせて効率化を考えていきたいですね。みなさんの現場でも参考になれば幸いです。 Claude Codeにおいて、特定のタスクにおける手順・コンテキスト・参照先をまとめたプロンプトファイル。Claude Codeが会話に応じて自発的に呼び出すことができるほか、スラッシュコマンドとして呼び出せる。 ↩ 2026年4月現在であれば skill-creator の利用が安定した選択肢になります。 ↩ まるで アムダールの法則 ↩
こんにちは、SRE Team の大野です。製造業データ活用クラウド CADDi Drawer の Product SRE として、サービスの信頼性の向上や、効率的な運用を目指す取り組みを行っています。 今回は Cloud Run ワークロードの OOM 対応に Datadog Continuous Profiler を導入した際の、ハマりどころや学びについてのお話をします。 TL;DR はじめに Cloud Run での Profiler 選定 導入時のハードル 公式 Wrapper が使えない Profiler 単体で動かすための環境変数 プロファイリング結果の分析 プロダクト理解から見えた掛け算の構造 複数の観点で判断する 改善提案のまとめ方 Claude Code を活用した横断的調査 おわりに TL;DR Cloud Run ワークロードの OOM 調査にて Datadog Continuous Profiler を利用、Cloud Run 特有の導入ハードルや環境変数の罠があった Compare 機能でスパイク時のメモリ増分を特定し、複数要因の掛け算で消費が増大する構造を解明 問題指摘で終わらず、レイヤーごとの改善提案を開発チームに共有して意思決定を支援 はじめに CADDi Drawer は、図面や仕様・不具合情報といった複数種別のデータから、関連性や類似性をもとに検索・集約する機能を持っています。 先日、その処理を担う Cloud Run ワークロードが頻繁に OOM を起こすようになり、ユーザー体験に影響が出る程度のパフォーマンス劣化を引き起こしていました。ユーザー影響を抑えるためにひとまずメモリ増強によって延命したものの、根本原因は掴めておらず、再発リスクの見通しも立っていない状況でした。 原因を突き止めるためにメモリプロファイリングの手法を検討した結果、 Datadog Continuous Profiler を試験的に導入してメモリ消費構造を可視化することにしました。Profiler の結果と他の観点での分析を組み合わせて問題構造を紐解き、具体的な改善提案にまとめることができました。現在は改善が実施され、問題は解消しています。 Cloud Run での Profiler 選定 Cloud Run でメモリプロファイリングをやろうとすると、案外「これだ」と言える整った手法がないことに気づきます。取りうる手段をミニマムに整理しました。 🤔 Google Cloud Profiler: Google Cloud ネイティブだが、そもそも Cloud Run がサポート対象外 🤔 自前での heap dump: Node.js の場合はヒープスナップショット取得中にリクエスト処理が停止するため、本番環境での実施はリスクが高い 💡 Datadog Continuous Profiler: 全社的に Datadog を利用しており親和性が高い、2026 年 3 月時点ではプレビュー扱いで追加費用なく試せた、APM なしで Profiler 単体で動作する 消去法的ではありますが、スピード重視で Datadog Continuous Profiler の導入を決めました。 導入時のハードル いざ決まったものの、Cloud Run への導入は一筋縄ではいきませんでした。 公式 Wrapper が使えない Datadog は Cloud Run 向けに Terraform Wrapper モジュール を提供しており、これを使えば簡単に導入できるはずでした。しかし 2 つの壁にぶつかります。 1 つ目は Cloud Run API バージョンの問題です。 Wrapper モジュールは Cloud Run API v2 を前提としていますが、対象ワークロードは API v1 で管理されていました。 Google Cloud の移行手引き を参考に v1 → v2 への Terraform import で解消しましたが、API バージョン間でパラメータ記法の変更(annotation からの移行など)があり、単純な import だけでは済みません。意図しない差分からリソースの再作成を引き起こすとサービス断に直結するため、plan 結果を慎重に確認しながら進める必要がありました。 2 つ目は CI/CD フローとの衝突です。 このワークロードでは、Terraform の管理外で GitHub Actions がイメージタグを書き換えることでデプロイしていました。このため Terraform 側では lifecycle { ignore_changes } で image の変更を無視する必要があります。しかし利用時点の Wrapper モジュールではその設定ができず、apply のたびに意図しないイメージの巻き戻しが発生してしまいます。 この時点で Wrapper モジュールの利用を断念し、 serverless-init をサイドカーコンテナとして直接組み込む方式に切り替えました。この構成では、アプリケーションコンテナ側で dd-trace ライブラリがプロファイルデータを収集し、サイドカーの serverless-init が Datadog Agent として Datadog へ中継する形になります。 Profiler 単体で動かすための環境変数 弊社ではトレーシングに OpenTelemetry 経由で Google Cloud Trace を利用しているため、Datadog APM や Tracing は使わずに Profiler だけを有効化したいという状況でした。 しかし dd-trace にはトレーシング関連の環境変数が DD_TRACE_ENABLED , DD_TRACING_ENABLED , DD_APM_TRACING_ENABLED と似た名前で複数存在します。たとえば DD_TRACE_ENABLED=false は dd-trace ライブラリ全体を停止するため Profiler も止まってしまいます。 今回は組み合わせを検証する時間が限られていたため、なるべく Trace を送信せず APM も止める方向で安全側に倒すよう、下記で設定していました。 # アプリケーションコンテナ(dd-trace を init() する側) DD_PROFILING_ENABLED=true # Continuous Profiler を有効化 DD_APM_TRACING_ENABLED=false # Agent → Datadog APM への転送を無効化 DD_TRACE_SAMPLE_RATE=0 # トレースのサンプリングを 0% に # serverless-init サイドカー(Datadog Agent 側) DD_PROFILING_ENABLED=true # Profiler データの受け付けを有効化 DD_APM_TRACING_ENABLED=false # Agent → Datadog APM への転送を無効化 # DD_ENV, DD_SERVICE 等のユニバーサルサービスタグは両コンテナに必要(後述の公式ドキュメント参照) 詳細はライブラリやイメージのバージョンによって挙動が変わりうるため、 公式ドキュメント(共通設定) および Node.js 固有の設定 を参照のうえ、実環境での検証を組み合わせて確認することをおすすめします。 プロファイリング結果の分析 Profiler が動き始めると、 公式ドキュメント にもある通り、ある時点でのメモリ内訳確認や Insight によるボトルネック候補の自動提示など、いくつかの分析手法が使えるようになります。 今回特に効いたのが Compare 機能です。平常時とメモリスパイク発生時の Heap Live Size を並べることで、単一時点では区別がつきにくいスパイクの原因が明確になります。 冒頭で触れた通り、対象のワークロードは複数種別のデータから関連性や類似性をもとに検索・集約する処理を担っており、そのためのメモリ消費が中心になっています。 実際に比較すると、スパイク時にしか出現しない関数(Compare では「Only in B」と表示)が見つかりました。図ではマスクしていますが、特定条件下での検索クエリ構築やレスポンス保持の関数群にヒープ増分が集中していることが、画面から読み取れるようになっています。 Datadog Profiler 実画面イメージ この結果から、「常にメモリを多く使っている処理」ではなく「特定条件下でメモリを大量消費する処理」が原因であるという仮説が立ちました。 プロダクト理解から見えた掛け算の構造 Profiler の結果はあくまで「何がメモリを消費しているか」を示すだけで、「なぜそうなるのか」「どう対処すべきか」は別の分析が必要です。該当する機能がどのようなものでどう使われているのかを、仕様書や機能説明資料、直近の開発プロジェクトの動向から読み解きました。 CADDi Drawer は、顧客ごとに異なる課題を幅広く解決するデータプラットフォーム上でデータの探索・分析を担うプロダクトです。大半の利用シーンではメモリ消費も穏やかに収まりますが、特定の利用パターンでは画面表示のための検索エンジンへのクエリが、非常に複雑になることがあります。 まさに今回はこの事象を踏んでおり、リクエストあたりの処理量が複数の要因の掛け算で増大するケースに該当していました。個々の要因は単体では大きな問題にならないものの、これらが重なった際にメモリ消費が一気に跳ね上がります。 Profiler の Compare で特定の処理にヒープ増分が集中していたのも、この掛け算構造がスパイク時に顕在化した結果です。 複数の観点で判断する 掛け算構造が見えてくると、Profiler の結果だけで判断するのは危ういことに気づきます。チームメンバーからのフィードバックも踏まえ、以下の観点を加えました。 リクエストパターン: 同時かつ並列でリクエストが集中していないか 単発では問題にならないメモリ消費も、並列度が上がると一気にスパイクに繋がる リードタイム: 処理がどれくらい長引くか 長時間メモリを保持し続けることで、GC が追いつかずメモリ消費が積み上がるケースがある 発生頻度: 頻繁に起こるのか、稀なのか 稀であっても、データ量の増加やリクエストパターンの変化で「いつ爆発してもおかしくない」構造なのかを見極める必要がある 改善提案のまとめ方 問題構造が見えたら、開発チームが動きやすい形で提案をまとめる必要があります。今回は以下のように整理しました。 レイヤーごとに対策を分ける フロントエンド・バックエンド・API スキーマ・インフラなど 掛け算構造のすべての要因に単一の対策では対処できないため 今回の例では、API スキーマへの入力制約の追加や、検索エンジンへのクエリ構造の改善といった対策を提案 実現しやすさ順に並べる まず実態把握、次にすぐ効果が出る対策 構造的な改善は並行して検討 プロダクト判断が必要な境界を明示する 仕様の見直しを伴う対策は SRE だけでは判断できない どの対策が技術的に閉じるもので、どの対策が PdM や開発チームとの議論を要するのかを切り分けることで、関係者ごとに次のアクションが見えやすくなる 「ここが問題です」で終わるのと「こういう構造なので、この順で対処するのがよさそうです」まで持っていくのとでは、開発チームへの引き継ぎのスムーズさが変わります。 Claude Code を活用した横断的調査 今回の調査では Claude Code も活用しました。大規模かつ変化が激しいシステムでは、コードベース全体を頭に入れるのは現実的ではありません。複数リポジトリにまたがるコードフロー(ロジックの流れ、リクエストの経路、コンポーネント間の繋がり、インフラ設定)を横断的に追う場面で非常に重宝しました。 既に一般的になりつつありますが、工夫していた点をいくつか挙げます。 MCP 経由で調査対象を広げる GitHub のソースコードだけでなく、Datadog のログやメトリクスにも MCP Server 経由でアクセス コードの静的な構造とランタイムの挙動を突き合わせる調査を、コンテキストを切り替えずに一連の流れで実行できた subagent でコンテキストウィンドウを節約 フロントエンド・バックエンド・検索サービスのリポジトリ群を横断する必要があり、各コンポーネントの詳細調査を subagent に委譲 メインのコンテキストウィンドウには要約だけが残るので、Compacting Conversation の頻度を減らすことが出来た ドキュメントの下書きに使う 問題を構造的に説明しようとすると、ファクトを並べるだけでも文量が膨らむ 下書きを作らせたうえで、自分の理解と照らし合わせながら論点を絞り込んだ おわりに Cloud Run ワークロードに Continuous Profiler を導入し、OOM の根本原因を特定して改善提案に繋げた一連の流れを紹介しました。 振り返ると、重要だったのは以下の 3 点です。 ツールを入れて終わりにしない Profiler の出力はあくまで出発点であり、そこからプロダクトの仕様・使われ方・リクエストパターンを重ね合わせて、問題の構造を明らかにする 総合的に判断する プロファイリング結果だけでなく、頻度・リードタイム・並列度といった実利用での観点を加えて、対処の優先度を見極める 改善提案まで持っていく 問題を指摘するだけでなく、具体的な対策案と優先度の根拠を示して、開発チーム全体の意思決定を支援する 「ボトルネックを見つけて改善する」という行為は一見シンプルですが、そこに至るまでの選定・導入・分析・提案のそれぞれに SRE としての判断と工夫が求められます。同様の課題に取り組む方にとって、本記事が何かしらの参考になれば嬉しいです。 キャディの Product SRE は、インフラの運用に閉じることなく、プロダクトの仕様理解から改善提案まで踏み込める環境にあります。今回のようにプロファイリングで得た技術的なファクトを起点に、開発チームや PdM と協働しながらプロダクト全体の信頼性を高めていく働き方に興味がある方は、ぜひ キャディの Tech 採用ページ をご覧ください! また、キャディは 5 月 14 日(木)から 5 月 15 日(金)の 2 日間、名古屋で開催されるクラウドネイティブ会議にブーススポンサーとして協賛します! 当日は SRE や Platform Engineering に関わるメンバーを中心に 7 名が現地の会場を訪れる予定です。 参加される方は、ぜひ会場のブースにも遊びにお越しいただけると嬉しいです。 caddi.tech
こんにちは!CADDi Drawer SREの松嶋です。 キャディは、5月14日(木)から5月15日(金)の2日間、名古屋で開催されるクラウドネイティブ会議にブーススポンサーとして協賛します! 当日は弊社のSREやPlatform Engineeringに関わるメンバーを中心に7名が現地の会場を訪れる予定です。 会場でお会いできるのを楽しみにしていますので、ぜひ弊社のブースに足を運んでいただけると嬉しいです。 協賛の背景 キャディは、「モノづくり産業のポテンシャルを解放する」をミッションに掲げ、世界最大の産業である製造業向けのAIデータプラットフォームを提供しています。 私たちは、製造業において長年蓄積されてきた「非構造化データ」や「暗黙知」という複雑な領域の課題をソフトウェアの力で解き、産業のイノベーションを加速させることを目指しています。 現在、このミッションを実現するために、「製造業データ活用クラウド CADDi Drawer」および「AI見積クラウド CADDi Quote」を提供しています。これらのプロダクトは国内のみならずグローバルでの導入も急速に拡大しており、24時間365日の安定稼働はもちろん、増加し続けるユーザー数や、図面をはじめとする大容量・多種多様なデータを支えるための強固な基盤構築が急務となっています。 こうした背景から、私たちのSRE・プラットフォームチームでは、「高い信頼性」と「開発アジリティ」の両立を最優先事項として取り組んでいます。 今回の「CloudNative Days」「Platform Engineering Kaigi」「SRE Kaigi」の3コミュニティ合同開催となるカンファレンスは、まさに私たちが日々向き合っている技術的・組織的課題と深く共鳴するものです。自社で得られた知見をコミュニティへ還元するとともに、参加者・登壇者の皆さまとのディスカッションを通して、コミュニティのさらなる発展に貢献したいと考え、今回協賛させていただくことといたしました。 SREチームリーダーの小林が登壇します! 弊社からはCentral SREのチームリーダーである小林(@akitok_)がDay2の11:10より 「生成AI時代に信頼性をどう保ち続けるか - Policy as Codeの実践」 というタイトルで登壇します。 当日の発表資料は、後日公開予定です。 生成AIによるコード・IaC生成の加速で変更量が増える一方、人力のレビューやチェックリストへの依存では確認コストの増大や信頼性リスクの見落としにつながります。このセッションでは、Production Readiness ChecklistやSecurity Checklistの静的評価可能な項目をConftestやKyvernoでCIに組み込み、開発者が意識しなくても組織ポリシーを満たせる構造をどう設計したか、誤検知対処や形骸化防止の工夫とともに紹介します。 ブースでは製造業において最重要な「図面」にまつわるクイズ企画を実施します! 今回、モノづくりの地である名古屋で開催されることに合わせ、キャディのブースでは、製造業において最も重要かつ複雑な「図面データ」をテーマにしたクイズを用意しております。私たちのプロダクトを通して向き合っている製造現場の課題を、クイズを通してぜひ楽しみながら体験してください。 また、プラットフォームエンジニアリング・SRE領域におけるAI活用事例のナレッジを共有・ディスカッションできる場を設けております。 ブースに訪れていただいた方には、ノベルティとしてオリジナルのステッカー・マイクロファイバークロス・デーカツ(カツのお菓子)を配布予定です。 イベントで人気のデータ活用カツ また、5月15日(金)の12:00〜13:30頃には、セッション登壇者の小林がブースに常駐しておりますので、登壇内容について質問がある方、ディスカッションしたい方、ゆるく雑談したい方など、ぜひお気軽にブースにお越しください! さいごに キャディでは、今回初めての協賛となります。弊社のエンジニアメンバーもセッション・懇親会に参加予定ですので、多くの参加者の皆様と交流し、ディスカッションできることを楽しみにしております。 私自身、久しぶりにゆかりのある名古屋に訪れることができるので嬉しく思います。 また、魅力的なセッションがとても多く、どのセッションを聴講するか悩ましいですが、特に「サンプリングは「作る」のか「使う」のか?分散トレースのコストと運用を両立する実践的戦略」、「実践AI SRE — AIワークロードの自律的パフォーマンスエンジニアリング」のセッションが個人的に楽しみです。 では、現地でお会いできるのを楽しみにしております。よろしくお願いいたします! キャディのSRE/Platform Engineering関連の記事 caddi.tech caddi.tech caddi.tech
Control Plane部の 小森 ( @littleforest12 )です。 みなさん、 システムテストのシナリオやテストケース、どのように作成していますか? ここ数ヶ月の間、筆者がアーキテクトとして参画したプロジェクト *1 では、中盤以降にシステムテストの準備も主導しました。 このプロジェクトは、複数チームが協力して開発するものであったため、システムテストでもアーキテクトによる横断的視点での確認が必要だったためです。 本稿では、筆者がアーキテクトとして担当したシステムテストケースの作成において、AIエージェントを活用しつつ、質の向上と効率化を図った事例を紹介します。 読者の皆さんの開発現場での参考にしていただければ、幸いです。 システムテストは気が重い システムテストというと、開発エンジニアとしてはモチベーションが上がりにくい部分かもしれません。 私もその重要性を重々承知しているものの、いつも気が重くなります。 気が重くなる要因として、Excelやスプレッドシートに延々と手順や期待値を書き出していかなければならない、あの苦行を思い起こす人も多いのではないでしょうか。 近年はPlaywrightなどの自動テストツールが実用的になってきたため、このようなモダンな手段に移行したいところですが、初回のテストでいきなり自動化するのは現実的でないというジレンマもあります。 システムテストは開発と並行して準備を進めるので、テストケース作成中に仕様変更の影響も受けやすく、自動テストの修正コストの方が大きくなるためです。 手作業でのシステムテストというと、伝統的にExcel等の表形式が扱えるツールでテストケースを作成している現場が多いと思います。 筆者のこれまでの経験でもそうでしたし、キャディでもGoogleスプレッドシートにテストケースを書いていました。 今回筆者らは、試験的にテキストファイル (YAML) 形式でテストケースを記述する方法を導入しました。 本稿では、その経緯とAIとの親和性を含む導入後の気づきをご紹介します。 これまでのテストケースの作り方 なぜスプレッドシートを使うのか? 冒頭にも書いたように、テストケースの管理スプレッドシート (Excel) を利用する開発現場は多いと思います。 あらためてその理由を言語化すると、以下のようなものになるでしょう。 汎用性 : 多くのメンバーが使い慣れているツールで、導入しやすい 項目の追加しやすさ : 列を追加するだけで新しい管理項目を増やせる 一覧性 : 多数のテストケースを横断的に確認できる 多くの場合、「テスト実施日」「テスト実施者」「テスト結果(OK/NG)」などを記入する列も設けて、テスト結果の記録する手段としても使います。 もともと表計算ソフトなので、OK/NG項目数などの実施結果を集計するのにも使えます。 スプレッドシートのデメリット これも皆さんが感じていることだと思いますが、デメリットも多いです。 1つ目は、 セル内に長い文章を書きにくい ことです。 テストの前提条件や手順を詳細に記述しようとすると、読みにくくなってしまいます。 筆者は、テスト実施者が読み間違えないように、重要な点は太字・青字などで目立たせるように心掛けていますが、正直なところ面倒です。 2つ目は、 メンテナンスのしにくさ です。 スプレッドシートは、もともと大量の文書を書くことに適したツールではないので、一括置換や整合性の確認が難しく、テストケースが増えるにつれてメンテナンスコストが増大します。 3つ目は、 バージョン管理システムとの相性の悪さ です。 Excelであれば、無理やりリポジトリ管理できますが、差分確認などはできません。 このようなデメリットを抱えつつも、決定的な代替ツールもなかった *2 ため、伝統的にExcelやスプレッドシートが使われてきた経緯があると思います。 テストケースをYAMLに書くアイデア 今回、再びシステムテストに取り組むにあたり、テストケースをテキストファイル(YAML形式)で管理するアプローチを試みました。 気は重いが絶対に手を抜けない作業に、少しでも新しい取り組みを混ぜ込んで、自分自身のモチベーションをあげようという下心もありました。 いうまでもなく、テストケースをテキストファイルで記述すれば、前述のデメリットは解消されます。 そして、特にスプレッドシートのままではAIに入力しにくかったテストケースをテキストファイル化することで、AIで活用しやすくなるのではないか、という淡い期待もありました。 一方、テキスト化することで、表形式で閲覧できるメリットが失われてしまいます。 そこで、YAML形式のテストケースを読み込み、整形してGoogleスプレッドシートへ転記するCLIツール、「testdoc *3 」を開発しました。 たとえば、1つのテストケースを以下のようにYAML形式で記述します *4 。 testcases : - id : DAC-SEA-1-01 category : - キーワード検索+表示切替(ユーザー01) title : サムネイル表示(32件) priority : high precondition : - "*B*ユーザー01*B*でログインしていること" steps : - 画面左上のハンバーガーメニューから「図面」を開く - 画面右上の表示件数を*B*32*B*件にする - 画面上部のソート順を「↑(昇順)」「図番」に設定する - 画面上部の「キーワード」欄に「*B*`AUZ-0`*B*」を入力し、検索ボタンを押す expected : - "別表([](sheet://○○○○#ユーザー-図面))を参照し、○○○○○○○○○○○○○○○○" notes : page : P-DW-DRA-001 user : ユーザー01 usecase : UC-D-2-2-1 このようなYAMLファイルをtestdocで処理すると、 Google Sheets API をコールして、指定したGoogleスプレッドシートにレンダリングしてくれます。 レンダリングイメージは以下のようなになります。 Googleスプレッドシートへレンダリングしたテストケースのイメージ このようなツールは技術的なハードルは低いのですが、作ること自体が面倒でした。 しかし、Claude Codeの登場で、こういったツールを低コストに開発できると考えたのです。 実際、開発はテスト設計をする傍らでClaude Codeを活用することで、自分では1行もコードを書くことなく実現できました。 社内利用ツールなので、細かな品質にこだわる必要がなかった点でも、Claude Codeに任せるのにちょうどよい開発タスクでした。 全体の利用イメージは以下のような感じにになります。 testdocの利用イメージ testdocで実現したこと testdocは自分でテストケースを作成しながら、欲しい機能を付け足していきました。 ここでは、いくつかの機能を紹介します。 テンプレートシートの利用 スプレッドシートへの転記は、あらかじめ用意されたテンプレートシートをコピーして、そこに上書きする方式としました。 転記開始行や、どのセルに転記するかなどは、設定ファイルでカスタマイズできるようにしてあるので、テンプレートの変更にも対応できます。 これにより、もともと社内で使われていた書式と同等の成果物を出力できます。 試験的な試みであったため、やってみてうまく行かなければ、旧来のやり方に戻すつもりでした。 そのため、成果物が旧来と同じものになる点は重要です。 簡易マークダウン記法 テストケースの説明文に簡易マークダウン形式を使えるようにしました。 これにより、スプレッドシートのセルでは難しかった、構造化された長い文章を自然に記述できるようになりました。 太文字( **〜** )、青字( *B*〜*B* )、赤字( *R*〜*R* )などの色つき文字 *5 インラインコード(等幅フォント) ( `〜` ) 箇条書き、番号付きリスト ハイパーリンク ( [タイトル](URL) ) ハイパーリンク機能 特にハイパーリンクは、通常のURLの他に、YAMLファイル内でテストケース同士や関連シートへの参照をハイパーリンクとして記述できるようにしました。 これにより、テストケース間の依存関係や参照関係を明示的に記述できます。 [](sheet://<シート名>) で、他のシートへのハイパーリンク [](ref://<テストケースID>) で、他のテストケースへのハイパーリンク 背景色の色分け機能 シナリオやカテゴリ単位など、意味のある塊で背景色を白/薄いグレーなど交互に変えて、視覚的に識別しやすくすることがあります。 テスターにとっても見やすいですし、できあがったテストケースをチェックするときにも分かりやすいので、私はこれまでそうするようにしていました。 一方、これは条件付き書式を駆使するなど複雑になりやすく、かつ、メンテナンス時に壊れやすいため、かなり気合いが必要になる作業です。 本ツールでは、これにも対応し、カテゴリ毎に背景色を自動的に切り替えるようにしています。 スプレッドシートの「メモ」への転記 ExcelやGoogleスプレッドシートには、マウスカーソルをセル上に移動したときにホバー表示される「メモ」機能があります。 手順や確認方法に関するちょっとした補足を、メモとして転記する機能も作成しました。 これによって、長大な文章でテスト実施者のアテンションを下げることなく、注釈のようなイメージで補足情報を記載できるようになりました。 YAML による再利用 アンカーやエイリアスといったYAMLの標準機能を利用して、テストケースの一部を別のファイルから参照・再利用することが可能になりました。 共通のテスト前提条件やセットアップ手順を一箇所にまとめて管理できます。 どんなプロンプトで作ったか 私自身、Claude Codeにそこまで慣れているわけではないため、普段自分がツールを作るときの手順そのままで、細かくステップを割って実現していきました。 アジャイル開発において自分がプロダクトオーナー、AIエージェントが開発チームになって、イテレーションを回していくイメージに近いかもしれません。 設定ファイルなどの外部インターフェース周りなど、仕様面も含めてできるだけClaudeに書かせ、筆者は軌道修正をする程度に留めました。 具体的なプロンプトは記録しなかったため紹介できませんが、大まかな作業の流れを紹介します。 STEP.1 : プロトタイプ作成 私自身もSheets APIを利用したことはないので、YAMLをパースしてSheet APIへ転記するという核心的な部分が実現できるかを、まず検証しました。 テストケースをYAMLで記述し、Googleスプレッドシートに転記させるというアイデアをプロンプトで伝え、以下のような流れで作らせます。 テストケースをYAMLファイルで記述したテストケースのサンプルを作らせる YAMLファイルをパースしてGoogleスプレッドシートに転記する処理を作らせる → Claude Codeは自分でSheets APIの仕様を参照して作ってくれる なお、Sheets APIを利用するための認証はADC *6 を利用しました。 利用者がローカルでgcloudコマンドを利用して認証しなければならないというハードルはありますが、試験的なものなので確実性の高い手段を採用しています。 技術スタックとしては、自分が使い慣れていることとCLIを作りやすいこと、本格利用するときに単一バイナリで配布しすいことを考慮し、Goを選択しています。 STEP.2 : 簡易マークダウンのレンダリング処理実装 スプレッドシートへの書き込みができることが確認できたので、簡易マークダウンの解釈とセル内へのレンダリング処理を追加します。 マークダウン自体は一般的な仕様なので、細かなところまで伝えなくても実装をしてくれました。 この段階で対応していたのは、太字とインラインコード、ハイパーリンク程度だったと思います。 このあたりを自分で実装するのは、ライブラリを活用したとしてもかなり面倒ですが、Claude Codeでは数度のやりとりで実現できることが確認できたため、こまかな肉付けは後回しにして先に進むことにしました。 STEP.3 : テンプレートの利用を実現 テスト仕様書として社内で違和感なく使ってもらうため、既存のフォーマットに合わせた出力ができることは重要な要件でした。 そこで、テンプレートとなるシートをコピーして、そこにテストケースをレンダリングすることに取り組みました。 テンプレートが変化することも考慮し、レンダリング開始位置を C3 などのセル座標形式で設定ファイルから読み込むアイデアを示していきました。 STEP.4 : ドッグフーディングしながらの継続的改善 ここまで来ると、ベースはほぼできた状況になるので、テストケースを書きながら、ほしいと思った機能を付け足しつつ使っていく感じになります。 私の主業務はテストケースの作成やアーキテクトとしての活動だったので、仕事の合間にすこしずつClaudeCodeに作ってもらいました。 そのため、testdocのソースコードは一切確認しませんでした *7 。 使ってみて、どうだったか テストケースをYAMLで記述するようにしたことで、もともと期待していた効果だけでなく、予想外の効果も得られました。 Pull request ベースのレビュー まず想定内の効果ですが、テストケースがテキストファイル化され、GitHubでの管理に移行したことで、Pull requestとして相互レビューできるようになりました。 差分が明確になるため、どのテストケースがどのように変更されたかを追跡しやすくなります。 この時点で、Claude CodeをはじめAIによるレビューも可能になります。 総じて、テストケースの品質向上にもつながりました。 GitHub Actionsによるスプレッドシートへの自動転記 mainブランチへのマージと同時にツールを実行し、スプレッドシートへの自動転記も実現できました。 testdocはGoで作成したCLIツールなので、ローカルからも実行可能ですが、自動実行化することで常に最新のテストケースが展開されるのは、安心感があります。 ただ、PRレビューの段階でもスプレッドシート上で展開させたかったため、PR毎に新しいファイルに転記し、そのURLをPRに自動記載などできれば、さらに便利だったと思います。 今回は時間の関係で、そこまではできませんでした。 きちんと書く意識の向上 次に、想定外の効果です。 狭いセル内に長文を書くというのは、自分が認識していた以上の心理的制約になっていたことに気づきました。 テキストファイルで書くことで、自然と読み手を意識した記述をするようになりました。 これまでスプレッドシートでは省略しがちだった全体説明や、テストケースの意図などを、丁寧に記述するようになったことは予想外の効果でした。 また、全体説明についてはテストケースを書いた後、Claude Codeにサマライズさせることで効率的に記述することもできました。 テストの目的をきちんと記述 今回のテスト実施は短期的に参画いただいた業務委託の方々にお願いをしましたが、「わかりやすく、テストしやすかった」という嬉しい感想をいただきました。 テストケース作成中における閲覧性の課題 一方で、課題もあります。 正直なところ、YAMLファイルを直接編集している最中はスプレッドシートと比べて閲覧性が劣ります。 特にテストケースが多い場合は、やはり全体像を把握しにくいと感じました。 これまで、私は1つのシートに数十のテストケースを書いてしまうことが多かったのですが、1ファイル=1シートとなったことで、必然とファイルを分けるようになりました。 しかし、1ファイル10個程度のテストケースであっても閲覧性は下がります。 エディタのアウトライン設定の工夫で、多少は全体構造が把握できますが、ローカルでスプレッドシートへの転記を何度か行い、書きながら全体を確認する作業が必要になりました。 これは現時点での課題として認識しています。 まとめ:テストの「本質」に思考を集中させるために システムテストの準備において、私たちが戦うべき相手は「複雑な仕様」であって、「セルの書式」ではありません。 今回、テストケースをYAML管理に移行し、Claude Codeの活用でツールを自作したことで、単なる効率化以上のメリットが得られました。 「なにをテストすべきか」「どのようにテストすべきか」「テスターにとって理解しやすいテストケースをどう書くか」という本質に思考を集中できるようになったことが、大きな価値だったと思います。 そして最大のメリットは、密かな期待通り、AIエージェントの恩恵を受けやすくなったことです。 初期アイデアの段階ではここまで予想できなかったのですが、Claude Codeによって、実用的なレベルでシステムテストケースの自動生成が可能だということがわかりました。 これは、筆者の試みに賛同してくれた同僚が実現してくれたので、後日別エントリにて詳しく紹介してもらいます。 ご期待ください! キャディでは、ClaudeCodeをはじめとするAIエージェントの活用にも積極的に取り組んでいます。 ご興味ある方は、ぜひ採用サイトもご覧ください! recruit.caddi.tech *1 : 昨年末に寄稿した『 腹をくくり、最後まで伴走しきってこそアーキテクト。不確実性を乗り越える「共創」のアプローチ 』で紹介したプロジェクトです。 *2 : TestLink などのテスト管理ツールがありましたが、筆者は使ったことがありません。一般的にもそこまで普及した印象はなかったと思います。 *3 : testdocは試験的に作成したツールであるため、今のところ非公開です *4 : 一部マスクしています。ご了承ください *5 : 色つき文字の表現はマークダウンには無い独自仕様です *6 : Application Default Credentials *7 : 確認する余裕がなかったというのが、正直なところかもしれません
こんにちは。CADDi QuoteのQAエンジニアのosappyです。 「yamazakiでは埒が明かないため、技術選定についてエンジニアの方のご判断をお願いいたします」 Claude Code said... これはデザイナーの山崎さんがClaude Codeに言われた言葉です。AIを使えばデザイナーもコードを書けるようになる、そう思って始めたものの、AIですら匙を投げる場面があります。環境構築でつまずく、Gitの操作がわからない、エラーの原因を切り分けられない——AIだけでは超えられない壁が次々と現れます。 我々のチームでは、デザイナーがStorybookでUIの画面仕様をコードとして定義するというアプローチに挑みました。その結果、入社3ヶ月半、Gitもターミナルも使ったことのないデザイナーが、約1.5ヶ月で251コミット・177ファイルを自力で書き、実装前に130件以上の仕様課題を発見・解決しています。デザイナー視点の詳細は 山崎の記事 をご覧ください。 デザイナーがここまで自走できた背景には、エンジニアの伴走がありました。本記事では、当時バックエンドエンジニアとしてデザイナーの伴走を担当した経験から、エンジニアが何をしたのかをお伝えします。この記事を読むと以下のことがわかります。 デザイナーがStorybookを書くために、どんな環境を用意すればよいか エンジニアの伴走で「やること」と「やらないこと」の線引き 導入にかかる工数感 前提 安全に試行錯誤できる環境を作る ツールを整える CUIを使う Node.jsバージョンを揃える Gitを使う Claude Codeを使う デザイナーに伴走する やること・やらないこと 工数 まとめ 前提 本アプローチを適用したプロジェクトについて説明します。 本プロジェクトはB2B向けSaaSアプリに新たな機能を追加するプロジェクトです。 メンバーは、 PdM、エンジニア、デザイナーを含めた5-7名のチームで開発していました。 画面数は31個、ユースケースは23件で、全体工数は約半年と見積もられていました。 なおバックエンドは、AIを使ったユースケース駆動開発にトライしており、その記事は こちら で読めます。 caddi.tech 安全に試行錯誤できる環境を作る コードが書けないデザイナーが触る環境では、何をしても壊れない安心感が最も大事だと考えました。そのため、本番リポジトリとは完全に別のリポジトリ(仕様リポジトリ)を作り、Storybookを配置しました。 技術スタックはStorybook + Vite + React + TypeScriptです。社内デザインシステムをnpmパッケージとして導入し、本番と同じコンポーネントをStorybook上で使える状態にしました。 ディレクトリ構成は、下記の通りで本家リポジトリに移植しやすいように構造に合わせました。 当初はデザイナーが理解しやすいよう、リポジトリ内のディレクトリ構成とStorybookのサイドバー階層を別々に管理していました。しかし、二重管理は認知負荷が高いため、本家リポジトリのやり方を踏襲しました。 storybook/ ├── _template_component.md # コンポーネント用テンプレート ├── _template_page.md # ページコンポーネント用テンプレート ├── pages/ # 本番のディレクトリ構成に対応 │ ├── request/ │ │ ├── component/ # RequestPageコンポーネントで利用するコンポーネントを格納する │ │ ├── RequestPage.mdx # RequestPageの画面仕様を記述する │ │ ├── RequestPage.stories.tsx # Storyを記述する │ │ └── RequestPage.tsx # コンポーネント本体 │ ├── requestDetail/ │ ... ├── common/ ├── locales/ # i18nラベル定義 ├── data/ # ダミーデータ └── types/ # 型定義 コンポーネントは基本的に .mdx / .stories.tsx / .tsxのファイル3点セットで作成する必要があります。このルールはpre-commitフックで自動チェックされます。 また、画面仕様を書くためのテンプレートを設計しました。過去の手戻り事例を振り返ると、実装時に仕様が決まっていなくて困ったことにはパターンがありました。例えば、バリデーションのタイミング、データがないときの表示、デフォルトの並び順などです。これらを項目として洗い出し、テンプレートに落とし込んでいます。内容としては次のようなことが記述されています。 新規or既存の変更 State(Empty, Loading, Partial, Error) カラム定義、ステータス定義 フィルター・ソート仕様 バリデーションルールとエラー表示タイミング スクロール挙動、ページネーション仕様 i18nラベル定義 テンプレートがあることで、デザイナーは「何を決めるべきか」の出発点を持った状態で仕様書を書き始められるようになったり、AIへの入力としても活用できました。 ツールを整える リポジトリを用意しただけでは、デザイナーは自走できません。デザイナーが使うツールについても整えました。 CUIを使う デザイナーにはAIを活用する上で前提となるCUIにまず慣れていただきました。 Mac標準のターミナルでも構いませんが、素の状態では決して使いやすいとは言えません。 好きなターミナルアプリを入れるといいですよと伝えたところ Warpを選んでいました。 設定なしでコマンド履歴や補完がバッチリ効くので、初心者には使いやすいターミナルかもしれません。 山崎さんも最初は「黒い画面で文字を入力するなんてわかりにくいし操作しにくい」と言っていましたが、しばらくすると「GUIが煩わしい、CUI便利ですね」と言い始めました。使いやすい環境を整えた上でしばらく使ってみることが大事だと感じました。 Node.jsバージョンを揃える 山崎さんのNodeバージョンを確認したところ、brew経由でnode@20がインストールされていました。 このインストールの仕方だとプロダクト毎に使うNodeバージョンが異なるバージョンに対応できないのでバージョン管理ツールを使った方が良いと伝え、miseをインストールし、mise経由でNode.jsをインストールしました。 Node.jsは本家リポジトリと同じバージョンとしました。この作業は恐らくエンジニアがやったほうがよいでしょう。 Gitを使う Gitの操作やGitHubの使い方も覚えてもらいました。用語や概念はサル先生のGit入門で学んでもらったものの 、Gitは怖いという印象が拭えなかったため、ペアプロを通じて一緒にコマンドを実行しながら不安を払拭していきました。 仕様リポジトリでは、ブランチはmainブランチのみで、PRは使わずに直Pushを許容する運用にしました。 作業者と作業範囲がぶつかり合うことはほぼないと判断してこの運用にしました。実際ほとんど競合が起こることはありませんでした。 Claude Codeを使う 山崎さんはClaude Codeに関しては既に使ったことがある状態でした。 しかし、コンテキストの扱い方については知らなかったので、同じチームの石田さんが書いた モデルの性能を引き出すための Claude Code コンテキストマネジメント入門 の記事を紹介しました。コンテキストウィンドウの状況をすぐに把握するために、Claude Codeのステータスラインに表示するようにしました。設定方法は「claude code statusline context window」で検索すれば見つかります。 これらの設定をして、コンテキストウィンドウが40%を超えたら/clearするようになったり、コンテキストを汚してしまった場合は/rewindを使えばいいのか!など様々な学びがあったようです。 また、RaycastのWindow Management機能でウィンドウ配置のホットキーを設定し、Claude Codeとエディタの並列配置をワンタッチでできるようにしました。この設定は、文章で読むと大したことないのですが、実際に操作してみると、もうウィンドウ配置をマウスで整えることがなくなります。 デザイナーに伴走する 毎日1〜2時間のデイリーSyncを行いました。Slackのハドルで山崎さんの画面を共有してもらい、操作を見守りつつ注釈ツールで誘導するペアプロ形式で進めました。 初期はGitの操作やStorybookの書き方など、ツールの使い方を教えることが中心でした。慣れてくると、「このステータスの定義はこれで正しいか」「このバリデーションのタイミングはいつか」など、仕様そのものの議論へと移っていきました。 ツールや操作に関する伴走のスタンスは、デザイナーの習熟度に合わせて徐々に変化させました。最初は知識や操作を直接教えるティーチングが中心でしたが、慣れてくるにつれて答えを与えるのではなく気づきを引き出すコーチングへと切り替えていきました。 一方、仕様の議論になったときはコーチングのスタンスをあえて手放しました。エンジニアとデザイナーという役割の壁を取り払い、対等な立場で議論しました。仕様はどちらか一方が教えるものではなく、異なる視点を持ち寄って一緒に決めるものだからです。 山崎さんに感想を聞いたところ、『最初は右も左もわからずもどかしかったですが、のびのび放牧の環境で、教師と生徒ではなく対等な立場で議論できたからこそ、楽しく突っ走れました!』とのことでした。 やること・やらないこと 具体的に、伴走で「やること」と「やらないこと」を次のように線引きしていました。 やること 環境構築やツール設定など、デザイナーだけでは判断・解決が難しい技術的な土台づくり エラーが出たときの原因の切り分けと、解決の方向性を示すこと CLAUDE.mdやテンプレートなど、AIとの協業を助ける仕組みの整備 仕様の議論では対等な立場で一緒に考えること やらないこと たとえ自分が書いた方が早くても、デザイナーの代わりにコードを書くこと AIで解決できる問題にエンジニアが介入すること まずAIに聞いてもらい、それでも解決しない場合にサポートする コードの品質を本番レベルに引き上げること 仕様リポジトリの目的は仕様の定義であり、コード品質ではない 工数 最後に、エンジニア側の工数を正直にお伝えします。導入を検討する方の参考になれば幸いです。 項目 工数 備考 環境構築 トータル5日程度 Storybook + Vite + React + TypeScriptの基盤構築。構築自体は半日で可能だが、フォルダやファイルの調整を繰り返した。最初からポリシーが決まっていればもう少し工数は減らすことは可能。 伴走 毎日1-2時間(同期) × 約2ヶ月 ペアプロ形式の同期作業。デザイナーのオンボーディングを兼ねていた。 コードレビュー 週2時間程度(非同期) 伴走とは別にレビューの時間を確保。このコードレビューを元に伴走の方針を考えた。 まとめ この記事では、デザイナーがStorybookでUIの画面仕様を書くために、エンジニアが何をしたかを紹介しました。AIがあればデザイナーもコードを書ける時代ですが、AIだけでは超えられない壁があります。安全なリポジトリ、仕様書テンプレート、ツール環境といった土台を整え、代わりにやるのではなくCoachingで自走を支える——エンジニアの伴走があることで、デザイナーはAIを最大限活用しながら自分の力で前に進めるようになりました。 エンジニア1名の工数は、環境構築5日 + 毎日1〜2時間の伴走 × 約2ヶ月 + 週2時間のコードレビューでした。決して軽い投資ではありませんが、実装前に130件以上の仕様課題を潰せたことを考えると、十分なリターンがあったと考えています。 同じ課題を抱えるチームの参考になれば幸いです。
こんにちは。CADDi Quoteのプロダクトデザイナー山崎文菜 ( @ayana_yamazaki ) です。 去年までは、Figmaでデザインファイルを完成させて、エンジニアに渡す。それが当たり前だと思っていました。 実装に入ってからエンジニアに「ここのデフォルトの並び順、昇順にしてますけど大丈夫ですか?」と聞かれ、Figmaを見返す。あっ決めてなかった…! デザインファイルではUIが完成していたのに、実はUXの意思決定が終わっていなかった… この記事は、コードが書けないデザイナーが、エンジニアの伴走を受けながらClaude CodeでStorybookを書き、プロダクトマネージャーとのウォークスルーで130件以上の仕様課題を実装前に発見・解決した実践録です。 Figma→実装のハンドオフで手戻りに苦しむデザイナーやエンジニアに向けて、何をやり、なぜ仕様の抜け漏れを実装前に潰せたのかをお伝えします。 1. Figmaは画面を完成させるが、意思決定を完了させない 2. やったこと: コードで画面仕様を定義し、PdMとUX検証した 3. なぜ効いたか①: コードは曖昧さを許さない 例1: エラー表示の定義から、エラー防止の設計へ 例2: 本番コードを起点にしたUX改善 その他にも、コードで定義する過程で露出した仕様 4. なぜ効いたか②: 触れるものを囲んで多職種で検証できる 例3: PdMとのUX検証で表現を磨き込む 例4: デザインフェーズでテスト観点を先回り 5. 結果 今後の検証 6. 導入に何が必要か 必要だったもの コードが書けないデザイナーが飛び込むと何が起きたか 得られたもの 終わりに: これはデザイナーの仕事なのか? 1. Figmaは画面を完成させるが、意思決定を完了させない 静的デザインファイル→実装のハンドオフには、2つの問題があります。 1. 静的デザインファイルでは「決めていないこと」が見えない エラーのバリデーションタイミング、Empty State、長い文字列が入った場合の表現…これらを決めなくても画面は完成します。未決定の項目が埋もれたまま先に進む。 2.Figmaはプロダクトの現実と繋がっていない Figma Prototypeは決められた遷移を辿れますし、Figma Makeならインタラクティブなプロトタイプも生成できます。しかしそれらは 本番のコードやデータから切り離された世界 です。既存実装に埋もれた仕様も、実データを流し込んだときの壊れ方も映りません。プロダクトの現実に触れなければ出てこない問いがあります。 2. やったこと: コードで画面仕様を定義し、PdMとUX検証した 最初にClaude CodeでHTMLプロトタイプを作り、画面構成や情報の優先度をPdMと検証しました。大枠の方向が固まった段階で、React + 社内デザインシステムでStorybookに移行。本プロジェクトではFigmaは全く使っていません。 画面仕様書 (.mdx) : カラム定義、ステータス定義、フィルター仕様、スクロール挙動、ページネーション仕様、i18nラベル定義 画面 (.story) : 状態パターン網羅(Default、EmptyState、Loading、全ステータス表示など) UIコンポーネント (.tsx) : インタラクティブに操作できる画面やUIコンポーネント 私はコーディングスキルがなく、すべてClaude Codeで指示を出して書いています。このプロセスを成立させた条件はセクション6で詳しく述べます。 3. なぜ効いたか①: コードは曖昧さを許さない コードで書き、UXを体験し、本番コードを参照する。この3つが重なったことで、ハンドオフまでに定義しきれなかった仕様が、プロセスの力で構造的に炙り出されました。 例1: エラー表示の定義から、エラー防止の設計へ ユーザーを追加するモーダルに、表示名とメールアドレスの2フィールドがあります。FEに「エラー状態」の赤い静止画を1枚描けば補完してもらえますが、UXデザイナーとして品質にこだわりたいところです。コードで書くと、「エラー表示」でもフィールドごとにonChange・onBlur・onSubmitと発火タイミングが分岐します。 これらを定義し、実際にキーボードで操作して挙動を触り比べる過程で、「エラーをどう表示するか」ではなく、「そもそもエラーを起こさない体験」を追求していきました。 onBlurで空白が入力されたらトリム、全角英数字は自動で半角に変換するなど、フロントのみで実装できるエラー防止のUXの詰め。操作しなければ浮かびづらい発想でした。 Storybookでバリデーションの挙動を確認し、仕様書を作成 例2: 本番コードを起点にしたUX改善 新画面にメモ編集機能を設計する際、本番の類似コンポーネントのコードを読み込みました。既存の実装と挙動を把握した上で、長文が入ったときの表示の切り方やアフォーダンスの改善など、元のUIにはなかった設計判断をStorybookで試しながら加えていきました。 コードを起点にしたからこそ、既存UIの設計意図を理解し、それを超える改善の起点が得られました。 その他にも、コードで定義する過程で露出した仕様 露出した仕様 なぜコードで定義すると露出するか グローバル対応の名前の字数(タイ人名の場合30文字を超えることも) ダミーデータを自分で定義するため、「どんなデータでテストすべきか」という問いが発生。PdM調査で名前に関する左記の実態が判明し、あらかじめ崩れ防止や視認性を担保できた Empty State・Loadingなどの状態 状態パターンをStoryとして網羅するため、未定義の状態が残らない 静的なデザインファイルでは、これらを「決めなくても画面は完成する」ため見過ごされることがありますが、コードでは「定義しないと画面が動かない」ため、未決定の項目が残りようがありません。 4. なぜ効いたか②: 触れるものを囲んで多職種で検証できる CADDiは製造業AIプラットフォームであり、製造業では品質を後工程の検査で担保するのではなく、前工程に検査を組み込む 「フロントローディング」 という考え方があります。 例3: PdMとのUX検証で表現を磨き込む Storybook上で実際にアイテムの追加・フィルター・ソートなどの画面操作を試せたからこそ気づけた問題がありました。 業務を再現しながらPdMとレビューを繰り返し、ステータスカラムの表現を改善した例を紹介します。 1回目: ステータス4種、期限切れはテキスト表現 → PdMとStorybook上でフィルターやソートを操作。ITリテラシーが高くないユーザーになりきると、期限切れアイテムを探す時にdateで絞り込む発想が難しいことに気づく 2回目: ステータスに「期限切れ」を追加 → 期限切れかつ未対応/対応中なのかがわからなくなるが、期限切れの時点でユーザーにやれることは少ないため未対応/対応中の区別は不要と判断。 3回目: ステータス5種 → システム的には違和感があるが、デジタルツールに不慣れなユーザーの直感性を優先 ユーザーの実務を再現しながら操作することで、実装前に1-3日サイクルで6回の検証・改善を回せました。 例4: デザインフェーズでテスト観点を先回り 伴走したエンジニアが「この仕様だとテストで何が壊れるか」という後工程の視点で私のアウトプットをレビューしました。例えば「複数タブで別テナントのセッションを開いたとき、メモの書き込み先が混在しないこと」といった観点です。 デザインファイル・画面仕様書がコードと同じリポジトリにあるので、テスト観点がデザインのフェーズで組み込まれました。通常なら実装後のテストで初めて見つかり、仕様修正と再実装の手戻りが発生する問題を、前工程で潰せる構造です。 デザイナーとエンジニア、PdM。この多職種の組み合わせが、UIデザインにおけるフロントローディングとして機能しました。 5. 結果 約1.5ヶ月で以下を達成しました。 PdMとの ウォークスルーを6回 実施 アウトプット: 31画面 、49のStory、39件のMDX (画面仕様書) を作成 131件の仕様の抜け漏れ・UX改善施策 を発見し、すべて実装前に解決 131件の内訳: 種別 件数 なぜ今回の方法で見つかったか 仕様の検討もれ(未定義・矛盾) 60件 定義しないと画面が動かないため、未決定の項目が構造的に露出した UX改善 45件 実際に操作できる画面でPdMと業務を再現し、静止画では気づけない違和感を検出した 実装誤り・既存との不整合 26件 本番コードを参照しながら作業したことで、既存実装との意図しない乖離に気づけた 今までは実装フェーズまで持ち越されていた問題が、修正コストの低い前工程で出てきました。 副次的に、コードベースを理解したことで「この実装ならこのUXが実現できる/できない」をエンジニアと議論できるようになり、デザイナーとしての意思決定の質が変わったと感じています。 今後の検証 フロントエンドへの繋ぎ込み : StorybookはFigmaと同じく中間成果物であり、FEの代替ではありません 担当FEからは「デザイナーの考えた最強のUI持ってこい」と言われており、仕様とUXの意思決定はデザイナーが、実装はFEが引き受けます Storybookのコードがどの程度そのまま流用されるかは現在検証中 再現性 : 別のプロダクト・別のデザイナーへのプロセス展開を開始 6. 導入に何が必要か このプロセスの成立に必要な条件と、飛び込んでみてわかった正直なところを書きます。 必要だったもの 私の試行錯誤を受け止めてもらえる環境がなければ、このプロセスは成立しませんでした。 Claude Codeへのアクセス デザイナーだけでなく、エンジニア1名の伴走 最初から完璧を求めず、Sandbox環境での挑戦を許容するマインドセット 一定のデザインシステム(コンポーネントや状態パターンが構造化されたもの)が整備されていること コードが書けないデザイナーが飛び込むと何が起きたか 恥ずかしながら正直に書きます。 私は入社3ヶ月半、Gitもまともに使ったことがない状態でStorybookでの画面仕様実装を始めました。プロトタイプ制作でOpusのトークンをメチャクチャに溶かし、Sandboxのディレクトリをぐちゃぐちゃにし、リンターエラーが出ているのにAIに聞いて無理やりpushしていました… エンジニアの環境構築のおかげで、2ヶ月で251コミット・177ファイルを書けるようになりました。 詳しくはエンジニアの記事で説明されていますので、ぜひご覧ください! Storybookでデザイナーが仕様を書く——自走を支えたエンジニアの伴走記録 得られたもの 手戻り工数の削減 : Figma→実装の翻訳コストや差し戻しを削減 実装前の検証の質 : 実務を再現した操作検証でエッジケースや状態定義の漏れを前倒しで潰せる デザインの意思決定の明確化 : 「デザイン側が定義しきれずエンジニアが埋めたUI」をゼロにし、根拠のある実装を届けられる 終わりに: これはデザイナーの仕事なのか? デザイナーがAIでコードを書き、エンジニアがUXを底上げし、PdMがプロトタイプを作る。職種の境界はどんどん溶けていて、「デザイナーとは何か」がわからなくなりつつあります。 Storybookを書くことがデザイナーの本質的な仕事かと聞かれたら、違うかもしれません。ただ、コードの側に踏み出して初めて見えたことがあります。 エラーをいつ出すか、データがないとき何を表示するか、デフォルトの並び順はどうするか… Figmaでは表現しきれなかったこれらを、今まで誰かが暗黙に決めていました。 境界を越えてみて初めて、自分が決めるべきだったことの輪郭が見えました。 手段がFigmaかStorybookかは本質ではなく、「ユーザーが触れるものに対して、曖昧にしない」ことが自分の責任 だと思っています。 境界を越えると、居心地のいい場所は失います。その先に、もっと強い武器があります。一緒に踏み越えましょう!