TECH PLAY

株式会社豆蔵

株式会社豆蔵 の技術ブログ

100

1. はじめに # まず、この記事を書こうと思ったきっかけですが、次の2点になります。 最近読み始めた書籍『 ソフトウェアエンジニアガイドブック 』で、著者が「業務日誌をつけること」を勧めているから。 私自身も業務日誌をつけているので、改めて理由(動機)を考えてみましたが、単に習慣でつけているだけで、目的意識があって業務日誌をつけていないと考えたため。 ですので、この記事で次の2点を自分なりにまとめてみます。 業務日誌をつけることで何が嬉しいのか、また、逆に何が嬉しくないか。 業務日誌をつけるためのポイント。 エンジニアの方はプロジェクトで業務日誌をつけられている方も多くいらっしゃるかと思いますが、 そうでない方にも、この記事を通じて、業務日誌に興味を持っていただければと幸いです。 2. 業務日誌をつけることで何が嬉しいのか😄 # こちらについて、書籍の内容と私自身の考えについて記載します。 1. 書籍に書かれていた業務日誌を勧める理由(要約) # まず、書籍に書かれていた内容を要約すると、以下の3つです。 作業が管理しやすくなる。 作業の一覧を作成しておくと、作業の優先順位がつけやすい。 また、作業が割り込んできた時にも、作業の調整がしやすくなる。 一日の終わりに仕事を終えることに満足感がある。 その日の作業内容を記録することで、一日の仕事を客観的に捉えることができる。 業績評価に利用できる。 自分の過去の作業を振り返ることができる(半年前の作業とかはすぐには思い出せない)。 面談などで上司に説明する際の資料となる(成果を効率的に説明できる)。 2. 上記に対する私の所感 # 上述のお勧めの理由に関する私の所感としては、以下のとおりです。 1.の「作業を管理しやすくなる」というのは実感があります。作業を列挙すると優先順位が決めやすくなります。 2.の「仕事を終えることに満足感がある」についても分かります。作業が数日に渡る場合でも、その日ごとの作業内容が分かるので、作業が進んでいるように感じられます。 3.の「業績評価に利用できる」については、そういった発想自体がなかったので、これは試してみたいと思います。 3. 私が業務日誌を勧める理由 # 次に、私自身が業務日誌を勧める理由を挙げたいと思います。 1日の作業を始めやすくなる。 事前に作業予定を立てておくと、当日、何から作業を始めればいいかがわかりやすくなると思います。 とくに、休日を挟むなどして時間が空いた場合に効果的だと思います。 成果を意識しながら、作業へ取り組むようになる。 業務日誌には、作業結果(成果)を記載することになります。 ですので、作業時に成果を出さないと、業務日誌に記載する内容が欠落することになります。 すると、作業時に成果を出すことを意識して作業に取り組むように意識づけることになると思います。 インサイト(気づき)が得られる。 毎日、作業の記録をつけていると、色々と思うところがあったりもします。 上述のように、作業に対して、明確な成果がないことに気づくこともあります。 また、優先度の低い着手中の作業が、延々と残っていることに気づく場合もあります。 3. 業務日誌をつけることで何が嬉しくないか😖 # ここまでは、業務日誌の良い点について記載してきました。 しかし、実際に業務日誌をつけ始めると、以下に挙げるような問題が発生することもあると思います。 1. 業務日誌をつけることが負担になる # 業務が忙しいときほど、業務日誌を書く時間を確保するのが難しくなります。 つい後回しにしてしまい、気づけば数日分まとめて書く羽目になることも少なくないと思います。 私も社会人1年目の時に、OJT担当の先輩に言われて、業務日誌をつけていました。 ただ、炎上したプロジェクトの開発中、 夜23時過ぎの退社前に業務日誌を書くのは嬉しくなかったです。 2. 何を書けばいいかはっきりしない # 「TODO」「優先度」「進捗」など、どこまで細かく書けばよいのか迷うことがあります。 TODOリストとの違いも曖昧 [1] で、単なる作業の羅列になってしまうことも。 上述の業務日誌(社会人1年目)は、部内限定公開のブログサービスを使用していました。 ただ、ブログ(フリーフォーマット)だったので、何をどのように書くか、よく悩んでいたように思います。 3. 何のために書いているのか分からないことも # 業務の中で指示されて業務日誌をつけることもありますが、 目的や理由が明確でないと、モチベーションが続かないかもしれません。 上述の業務日誌(社会人1年目)の場合、 先輩曰く、「毎日業務日誌をつけることでインサイト(気づき)が得られるから」ということでした。 ただ、それでも上記の要因があったので、 正直、業務日誌をつけるのが苦痛に感じられることも多々ありました。 4. 業務日誌をつけるためのポイント✍️ # 最も重要なのは、業務日誌をつけることが負担とならないこと、に尽きると思います。 そのために、以下のようなポイントを押さえておくといいかもしれません。 1. 記載内容は事前に決めておく(テンプレ化する。) # 毎日、書くことになるので、内容は事前に決めておくといいと思います。 私は、「本日の作業内容」と「明日の作業内容」を書くようにしています。 2. 書きすぎない。 # 多くのことを書こうとすると、負担感が出ますので、 必要最低限なことだけを書くようにしたほうがいいと思います。 私は基本的に、「本日の作業内容」と「明日の作業内容」を記載していますが、 必要がある場合のみ、次の内容を追加で記載しています。 作業の成果物(誰かと作業を共有する必要がある場合のみ) 進捗度(作業が日をまたぐ場合) 完了予定日(作業に明確な締切がある場合) 3. 誰かと共有する。 # 誰かに読んでもらう前提があると、付け忘れしにくくなります。 ですので、読んでもらう必要がないとしても、業務日誌を共有するようにするといいと思います。 私も業務日誌をSlackでチームリーダーにメンションしています。 (都度リアクションをくれるので、とても助かっています😊) なお、業務日誌はテキストベースが無難かと思っています。 理由としては、その日ごとに記載内容を微妙に変えたいことがあるためです。 その辺りを踏まえると、Slackで業務日誌を作成するのは悪くないと思っています。 誰かと共有するのも簡単ですので。 5. まとめ📒 # 業務日誌をつけることには、いくつかの利点があるはずです。 ですので、業務日誌をつけておいて損はないはずです。 とはいえ、業務日誌をつけるのは難しい面もあると思うので、 継続するための工夫も必要かと思います。 この記事を読んでいただいて、業務日誌をつけることに興味を持たれましたら、 ぜひ試してみていただければと思います。 💭少し脱線しますが、私個人の見解としては、 TODOリストが「やるべき作業を管理するためのもの」だとすると、 業務日誌は「その作業が業務として意味を持っているかを見直すためのもの」 だと言えるかもしれません。 一口に作業と言っても、業務(仕事として)行うのであれば、 ちゃんと意味のある作業に取り組む必要があるはずです。 ↩︎
あけましておめでとうございます。 2025年もご愛読いただきありがとうございました。本年も豆蔵デベロッパーサイトをよろしくお願いします。 記事数・執筆者数 # この3ヶ月で49本の記事が投稿され、記事数は843になりました。新たに4名が執筆デビューし、累計75名になりました。 テーマ別の記事 # ロボット # /robotics/twincat/introduction-chapter2/twincat-introduction-chapter2/ /blogs/2025/12/10/ceres-solver/ /robotics/bizen/bizen_introduction/ ローカル LLM # /blogs/2025/10/14/local_rag_on_lm_studio/ /blogs/2025/10/15/local_rag_on_lm_studio_part2/ プログラミング言語 # /blogs/2025/10/16/csharp_di/ /blogs/2025/12/09/jspecify/ Q Developer # /blogs/2025/11/12/qdev_beginner_day1/ /blogs/2025/11/17/qdev_beginner_day2/ /blogs/2025/11/21/qdev_beginner_day3/ /blogs/2025/11/28/qdev-aidd-spec-kit/ /blogs/2025/12/11/amazon_q_collaborative_writing/ 仕様駆動開発 # /blogs/2025/12/16/github-spec-kit-iac-entry/ /blogs/2025/12/24/kiro-sphinx-sdd/ CI/CD # /blogs/2025/11/16/github-immutable-releases/ /blogs/2025/11/17/cd-pipeline-for-ubuntu-desktop/ テスト # /blogs/2025/12/17/kiro_ide_pbt/ /blogs/2025/12/19/edu-user-dept-training-1/ パブリッククラウド # /blogs/2025/12/12/google_cloud_entry/ /blogs/2025/12/22/aws-controltower-deepdive/ /blogs/2025/12/22/aws-controltower-deepdive/ /blogs/2025/12/23/aws_all_certified_2025/ /in-house-project/sss/webhook-with-sqs/ モデリング # /blogs/2025/12/05/modeling-forum-2025-report/ アーキテクチャ # /blogs/2025/12/08/hexagonal_questions/ アジャイル # /blogs/2025/12/04/scrum-ai-1/ /blogs/2025/12/18/sushi/ GitHub Copilot # /blogs/2025/10/30/intro-to-github-copilot-cli/ 開発環境構築 # /blogs/2025/10/13/write-java-with-vscode-2025/ 個人開発 # /blogs/2025/10/13/python-game/ /blogs/2025/12/01/porting-an-electron-app-to-tauri2/ /blogs/2025/12/03/customize-markdown-preview-style-with-agent-skills/ 書籍紹介 # /blogs/2025/12/02/think-again/ ヘルスケア # /blogs/2025/12/25/Vitality/ アドベントカレンダー2025 # アドベントカレンダー2025が開催され、バラエティ豊かな19記事が公開されました。公開された各記事は本記事内でも紹介しています。 /events/advent-calendar/2025/ さいごに # 以上、2025年度第3四半期のサマリーでした。 よかったら フィード の購読、 X や Bluesky でのフォローもお願いします。 Facebook でも本サイトの注目記事をはじめ豆蔵に関するイベントを紹介しています。 note にも時々本サイト関連の記事が掲載されています。
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第25日目の記事です。 ※本件はテック系の記事ではありません。 食事を気にしていても週一でバーガーキングのワッパーは食べる石原です。 デベロッパーの皆さん、歩いてますかー! 本件は普段の生活の中で以下が思い当たる方を対象としています。 仕事はリモートワーク 休日は家にいる時間の方が多い 別段運動する習慣もなし 1日の平均歩数は1,000歩以下 ジャンキーで美味しいものを食べるのが大好き 「体を動かしたい」という気持ちはあるが、行動に移せずにいる これらはすべて、数ヶ月前までの私そのものです。 そんな私が現在(2025年12月時点)では以下のような生活に変わりました。 毎日12,000歩のウォーキング、またはジムでの筋トレを継続 「歩くこと」が全く苦にならなくなった 食生活で総カロリーやPFCバランスを自然と意識するように 体重は2kg減 食欲は以前よりさらに増して健康的に 今回は、私がこのような変化に至った経緯と、「運動したいけど一歩が出ない」と同じ悩みを持つ皆さんに、ぜひ共有したいエピソードを書いていこうと思います。 きっかけはVitality # 2025年9月末、総務の方から全社向けに「Vitalityプログラム」の参加者募集の案内が届きました。 どうやら健康経営の一環として外部サービスを導入し、社員の健康を促進したいという試みのようです。 元々「体を動かしたい」と思ってはいたものの、きっかけがなくて動けなかった自分。そんな私の背中を強烈に押したのは、案内の中にあったこの一言でした。 「ランキング表彰を実施し、忘年会で豪華景品を授与予定です!」 これを見た瞬間、「やるしかねぇ!」と思った自分は即日で参加を表明し、2025年10月から私のウォーキング生活が幕を開けたのです。 Vitalityとは? # 住友生命が提供する健康増進プログラムで、豆蔵が導入したのは「 Vitalityスマートプログラム(福利厚生タイプ) 」です。 詳細については公式HPに譲りますが、簡単に言うと「歩数や心拍数をアプリで計測してポイントを貯める」仕組みです。貯まったポイントに応じて、一週間ごとに以下のような特典(リワード)がもらえます。 スターバックスの500円クーポン 各コンビニの500mlペットボトルのお茶 / ローソンの飲むヨーグルト 各種団体への寄付 など デイリーで獲得できるポイントは(イベントを除いて)最大 60pt 。 取得条件は以下の通りです。(各項目のうち最も高いポイントのみが適用され、重複獲得は不可) 私の目標は至ってシンプル。 「毎日60ptを死守していれば、入賞できるはず!」 そう信じて、12月の忘年会まで「毎日60pt」を取り続ける生活が始まりました。 (結果的にはほぼ1万2000歩を歩くことで取得しました) 社内での取り組み # 豆蔵社内ではVitality参加者に向けて、Slackに専用チャンネルを開設し、定期的にランキングを発表する取り組みが行われました。 ランキングの名前について、希望者についてはあだ名が設定できたので、私は「汗かいた後のビールは最高🍺」で登録しました。 用意したアイテム # 「1日1,000歩」から脱却するために、私が実際に揃えた「三種の神器(+α)」を紹介します。 1. スマートウォッチ(ウェアラブルデバイス) # スマートフォンでも歩数は測れますが、 心拍数 は計測できません。Vitalityのポイント効率を考えると、「歩数」を稼ぐよりも「高強度の運動(心拍数上昇)」を30分行う方が圧倒的にタイパが良いです。 (歩数だと大体30分で4000歩、1万2000歩なら1.5時間が必要) 元々「スマートウォッチはいらない派」だったのですが、いざ付けてみると自分の活動が可視化され、モチベーション維持に大きく貢献してくれました。未導入の方には強くおすすめします! 2. ランニング・ウォーキングシューズ【最重要】 # ここへの投資が一番重要と言っても過言ではありません。 運動不足の人が普通のスニーカーで長時間歩くと、高い確率で足腰を痛めます(経験談)。最近のシューズはクッション性が凄まじいので、まずはここから新調しましょう。 以下は私が実際に購入した靴の紹介です。 Fresh Foam X 880v14 GORE-TEX® (New Balance) 黒スニーカーなので普段使いもOK。GORE-TEX素材なので、急な雨でも足元が快適なのが最高です。 Cloudmonster (On) クッション性が異次元です。「雲の上を歩く」ような感覚で、長距離を歩く予定の人には全力でおすすめします。 3. YouTube プレミアム # ウォーキングの時間を「運動の時間」ではなく「娯楽の時間」に変えてくれる魔法のツールです。 オフライン再生 と バックグラウンド再生 ができるため、通信量を気にせず動画を「聴きながら」歩けます。 Tips: 「スマート一時保存機能」をオンにしておけば、お気に入りのチャンネルの新着動画が自動でダウンロードされるので、家を出る前の準備も不要になります。 4. その他の小物 # 帽子: 寝起きの爆発した髪を隠してくれる頼れる相棒。蒸れやすいので通気性重視で選びましょう。 動きやすい服装: 基本は何でもOKですが、軽いアウターがあると体温調節がしやすく、脱いでも荷物にならないので便利です。 ワイヤレスイヤホン: 動画視聴のお供に。ただし、歩道が狭い場所では安全のために「片耳視聴」や「外音取り込みモード」を活用しましょう。 毎日歩くコツ # 2ヶ月間、ほぼ毎日1万歩以上を歩き続けて見えてきた、習慣化のポイントをまとめます。 1. 動画視聴の時間を歩く時間に # 普段、家で1時間以上じっくり動画を見ている方に朗報です。その時間をそのまま歩く時間にしましょう。 YouTube Premiumなどの機能を使い、動画をあらかじめダウンロードして「聴きながら歩く」だけで、いつの間にかウォーキングが完了します。 最初は「画面を見ないと楽しめないのでは?」と思っていましたが、やってみると音声だけで満足できるコンテンツは意外と多いものです。 気になる動画を見つけたら その場で視聴せずに 、ぜひダウンロードして歩きながら聴いてください。 2. 「シューズ」への投資を惜しまない # 「歩くだけなら普段の靴でいいや」と思っていませんか? 私はそれで失敗しました。 専用ではないスニーカーで歩き始めた結果、以下のような事態に。 物理的な故障: わずか2週間でスニーカー2足のアウトソールが剥離。 身体のバグ: 歩くたびにアキレス腱周辺に痛みが発生。 「歩く」というタスクを継続するための 最重要ハードウェア は靴です。悪いことは言いません、快適なウォーキング・ランニングシューズを1足用意しましょう。これだけで継続率は格段に上がります。 3. 「分割」して難易度を下げる # 1時間連続で歩こうとするとハードルが高いですが、「朝に30分、夜に30分」のように分割すると、精神的な負荷がグッと下がります。 特に 朝のウォーキング は、体内時計をリセットし睡眠の質を向上させる効果もあるため、エンジニアの健康管理には特におすすめです(冬の朝は少し気合が必要ですが!)。 4. 色んなところを歩く # 自分のお気に入りの道を毎日歩くのもおつなものですが、時には別の場所を歩くと新たな発見や気づきがあって良いものです。 遊歩道: 近くにあったらまず歩いてみたい道No.1。新たな隠れ家を見つけた子供のような嬉しさを感じます。 駅(特に普段歩いていかない所): ちょっと離れた駅まで歩くと、知らない道に出会えます。「意外と近いぞ」という気づきが、移動の選択肢を広げてくれます。 大型商業施設: イオンやコストコ、アウトレットなど。歩きながらウィンドウショッピングも楽しめます。 公園、神社、寺など: 季節を感じられるスポットはメンタルにも良い影響があります。 5. 歩く速さを変えてみる # 歩き慣れてきたら、スピードを変えてみるのも1つの楽しみです。 もちろんスピードを速めるほど身体への負荷は高くなるのですが、歩きながら自分でどうすれば速く、楽に歩けるかを考えながら色々試行錯誤するだけでマンネリ化は大分減ります。 6. 雨の日でも歩く # 余程の嵐とかは別ですが、多少の雨なら撥水加工が施されたウェアを用意しておくだけで傘をささずに歩けます。(GORE-TEX素材の靴もおすすめ) 後は1日雨でも時間によって強弱は結構あるので、時々雨の様子を確認しながら歩くと意外と歩けるイメージがあります。 どうしても無理そうな時はおとなしく家で筋トレとか有酸素運動をしましょう。(私は家でエアロバイク漕いでました) ウォーキングを始めて実感したメリット・デメリット # メリット # 体力が劇的に向上した(HPの底上げ) 以前はたまに出社するだけで、午前中のうちに「残りHP 30%」という感覚でした。しかし最近では、満員電車に揺られてもそこまで疲れを感じません。 数値で見る身体の変化: 体重-2kg、VO2Max(最大酸素摂取量)が「平均以下」から「平均以上」へ向上、安静時心拍数も低下。「ただ歩くだけ」でも心肺機能は確実に鍛えられるようです。 活動量の増加: 1日1〜2時間をウォーキングに充てていますが、体力がついた分、活動できる時間も増えました。「朝に江ノ島ツーリング→帰宅後に銭湯→夜はウォーキング」といったアクティブな休日もこなせるようになり、使った時間以上のリターンを感じています。 睡眠の質が改善: 不規則だった生活リズムが整い、夜になると自然と眠りにつけるようになりました。 「近所の解像度」が上がった これまで見落としていた近所の風景を知れるのも楽しみの一つです。実際、隣駅まで歩いている途中で偶然新しいジムのオープンを知り、それがきっかけで筋トレも始められました。 デメリット # 出費の増加 シューズやウェアを揃え始めると、こだわりたいものがどんどん増えていきます。さらに活動範囲が広がると欲しいガジェットも増え、お財布への攻撃力は意外と高めです(本人は満足していますが!)。 アキレス腱の痛み 最近の悩みは、歩き始めのアキレス腱の痛みです。体が温まると和らぎますが、冷えや疲労の蓄積も影響しているようです。シューズやフォームを試行錯誤していますが、時には「適切な休養」も重要なタスクだと痛感しています。 エピローグ # 毎日1万2000歩歩いた結果、忘年会では見事2位にランクイン! いただいた 豪華賞品 は以下でした。 Vitalityタオル Vitalityマルチラージバッグ (SUMITOMOの文字が入った)ブランケット 【参加賞】豆蔵オリジナルスクイーズボトル(個人的にはこれが一番嬉しかった) これってほぼ全部住友生命からもらったグッズ... 今回手にしたウォーキング習慣、それこそが最大の賞品だったのかもしれません。 忘年会が終わったらウォーキングも少し落ち着こうかと思っていた矢先、社内の方からお声がけをいただき、「 第12回東京エクストリームウォーク100 」に来年の5月出場することにしました! 小田原から東京の有明まで、100kmを制限時間26時間で歩き抜く過酷な大会なので、今以上に歩けるように準備したいと思っています。 今からならまだ間に合いますので(ちなみに私は初参加です)、ご興味ある方はぜひ一緒に挑戦しましょう!
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第24日目の記事です。 はじめに # AWSは2025年11月18日、AIエージェント型IDE Kiro(https://kiro.dev) の一般提供を発表しました。 筆者は以前プレビュー版を使っての レビュー記事 を執筆しました。 それ以来Kiroのファンとなり、現在は実業務でKiroを使用しています。 AIエージェント型IDE「Kiro」の登場により、個人開発やプロトタイピングの速度は劇的に向上しました。 しかし、開発規模が大きくなるにつれて、「AIへの指示(コンテキスト)」をどのように管理するかという新たな課題も生まれています。 筆者は、この課題を解決するためにドキュメントビルドツール「Sphinx」を導入しました。 この試みは今のところうまくいっています。今回は、その開発スタイルを紹介します。 1. 従来のKiro開発における「規模の壁」 # Kiroは非常に優秀ですが、ある程度規模の大きい開発プロジェクトに適用しようとすると、以下の2点でコンテキスト管理の限界に直面します。 ① requirements.md の肥大化と混乱 Kiroの基本的な使い方は、 .kiro/specs ディレクトリ内の requirements.md に要件を記述することです。 しかし、機能が増えるにつれてこのファイルは数百行・数千行に膨れ上がり、AIが文脈を見失ったり、人間側もメンテナンスが困難になります。 ② ディレクトリ分割のジレンマ これに対する策として、 .kiro/specs ディレクトリ内に機能ごとのサブディレクトリを作り、そこに requirements.md を分散させる方法があります。 しかし、これには 「鶏と卵」の問題 があります。開発初期の要求分析段階では、どのような機能分割が最適かはまだ見えていません。 Kiroと会話しながら要件を詰めたい段階で、事前にディレクトリ構造をカチッと決めるのは不向きであり、柔軟性を損ないます。 2. 解決策:Sphinxによる構造化ドキュメント管理 # そこで提案したいのが、Python製ドキュメントツールとして有名な Sphinx を、要求仕様書や設計書の管理・ビルド基盤として採用する方法です。 Sphinxは単なるマニュアル作成ツールではありません。テキストベースで構造化されたドキュメントを管理できるため、Kiroとの親和性が極めて高いのです。 3. Sphinx導入の5つのメリット # 要求仕様書や設計書をSphinxプロジェクトとして管理することで、具体的に以下の効果が得られます。 人間にとっての可読性向上(HTML/PDF化) 複数のテキストファイルをビルドし、見やすいHTMLやPDFとして出力できる 開発者やステークホルダーは、コードのようなテキストファイルではなく、整理された「仕様書」としてブラウザで全体像を確認できる 文書構造によるコンテキスト整理 Sphinxの toctree 機能を使えば、ファイルを機能やレイヤーごとに分けて管理しつつ、1つの体系的なドキュメントとして統合できる Kiroに指示を出す際も「今回は auth.rst (認証機能)を参照して」と明確にスコープを限定できる テキストベースだからKiroが読める Sphinxのソースコードはプレーンテキストであるため、Kiroはプロジェクト内のドキュメントを直接読み込み、理解できる 「仕様書そのもの」がAIへのプロンプトとして機能する 図解(画像)によるコンテキスト共有 Sphinxプロジェクト内に配置した画面モックアップ、ER図、分析モデルなどの画像をKiroは認識できる 「この図に従って実装して」と指示することで、テキストだけでは伝わりにくいニュアンスを共有可能となる --> Caution 画像サイズが大きすぎると読み込めない場合があります。(筆者の実体験より) 概要から詳細仕様への自動生成フロー 人間が「システムの概要」や「業務フロー」をざっくりと書き、それをベースにKiroへ「各機能のユースケース記述を作成して」と依頼できる AI自身にドキュメントを書かせ、仕様を固めてから実装に移る「上流工程の自動化」が可能になる 4. 実践:Kiro × Sphinx # 実際にKiroへどのように指示を出せばよいか、具体例を紹介します。 4.1. 新規にプロジェクトを作成する場合の初期プロンプト例 # Specモードで下記のプロンプトを打ち込みます。 アルバムアプリを作成したい。 docs/requirements内にSphinxで要求仕様書を作成して欲しい。 HTMLのテーマはsphinx-rtd-themeを使用して。 PodmanでビルドできるようにDockerfileとHTML, PDFビルド用のバッチファイルを作成して。 Dockerfileのベースイメージはsphinxdoc/sphinx-latexpdfを使用して。 Kiroは要求仕様書を作成するためのプロジェクトを開始します。 ある程度、要求仕様書に書く内容がイメージできている場合は、文書の構成を指示しても良いでしょう。 requirements.mdと要求仕様書は日本語で書いて ドキュメント構成は下記のようにして index.rst # 全体目次 ├ overview/ │ └ index.rst # システム概要 ├ usecase/ │ ├ index.rst # ユースケース目次 │ ├ uc01-login # UC01_ログインする | ├ uc02-browse # UC02_アルバムを閲覧する | ├ uc03-upload # UC03_コンテンツをアップロードする | ├ uc04-edit # UC04_コンテンツを編集する | ├ uc05-delete # UC05_コンテンツを削除する | └ uc06-manage-users # UC06_ユーザーを管理する ├ screen/ │ ├ index.rst # 画面仕様目次 │ ├ login.rst # ログイン画面 │ ├ main.rst # メイン画面 │ ├ edit.rst # 編集画面 │ └ manage-users.rst # ユーザ管理画面 └ changelog/ └ index.rst # 改訂履歴 --> Information 要求仕様書に書く内容は、プロジェクトにより変わってくると思います。 絶対この構成でなければならないというものではないので、柔軟に対応しましょう。 上記の指示で作成された仕様書が下記です。 何も要件を入力していないですが、AIが勝手に想像して仕様を書いてくれています。 バッチファイルは軽微な修正が必要でしたが、HTMLやPDFにビルドするときれいに出力されます。 PDFにはスタイルの不自然なところがありますが、Kiroと相談しながら進めれば、専門的なTexの知識がなくても修正は可能です。 4.2. 要求仕様書の修正プロンプト例 # 要求仕様書の修正は、手書きで修正可能なほか、 下記のようにSpecモードで要件を列挙して修正させることも可能です。 docs/requirements内のアルバムアプリの仕様ですが、下記のようにしたい ユーザー認証はGoogleアカウントを使用すること。 管理者権限のあるユーザーのみ、ログイン可能なユーザーを追加・削除ができること。 管理者権限のあるユーザーは、バックエンドアプリケーションの設定ファイルで可能なこと。 写真だけでなく、動画もアップロード可能であること。 アップロード可能なファイルの上限は100MBであること。 アップロード可能なファイルの拡張子はJPG, PNG, HEIC, MP4, MOVであること。 ファイルのメタ情報から日付を取得し、『/data/pict/<YYYYMMDD>』というパターンのディレクトリを作成し、その中にファイルを保存すること。 ファイルのサムネイル画像を作成し、『/data/thumb/<YYYYMMDD>』というパターンのディレクトリを作成し、その中にサムネイル画像を保存すること。 写真の一覧はサムネイル画像を表示すること。 サムネイル画像のサイズは縦幅、横幅が300ピクセル以下となること。 4.3. 設計書の作成プロンプト例 # 要求仕様書が書けたら設計書も作成します。Specモードで下記の指示を出します。 docs/requirements内のアルバムアプリの仕様に基づいて、 docs/design内にSphinxでアーキテクチャ設計書を作成して欲しい。 フロントエンドはAngular、バックエンドはASP.NET Coreで。 実行環境と開発環境はPodmanコンテナ上で動くこと。 上記の指示で作成された設計書が下記です。 Dockerイメージにsphinxcontrib-mermaid拡張機能がインストールされていたため、ドキュメントをビルドするとKiroがMermaidで描いた図も表示されました。 4.4. 実装フェーズでのプロンプト例 # 実装フェーズでは、Specモードで下記のように指示を与えます。 docs\requirementsの要求仕様書と、 docs\designの設計書を参考に、 アルバムアプリのログイン機能を実装して これだけの指示で、Kiroはログイン機能に必要な情報を探し出してrequirements.mdを作成してくれます。 これ以降は、下図のフローに従って実装まで進めていくだけです。 まとめ # Sphinxを導入することで、要求仕様書や設計書を「人間が読みやすい形式」と「AIが処理しやすい形式」の両方で管理できるようになります。 今回ご紹介した手法であれば、開発規模の大きなプロジェクトでもKiroを扱いやすくなるのではないでしょうか。 本記事を今後の開発の参考にしていただければ幸いです。
これは、 豆蔵デベロッパーサイトアドベントカレンダー2025 第23日目の記事です。 はじめに # 2022年12月12日に「 AWS認定資格を12個すべて取得したので勉強したことなどをまとめます 」という記事を投稿してから3年が経ちました。 この記事では、その後の3年間でどのような変化があったか、そして新しく追加された認定区分についてまとめます。 --> Information 秘密保持契約(NDA) があるため、詳細な試験内容については触れることができませんので、ご了承ください。 3年間の振り返り # 2022年10月にAWS認定12冠を達成してから、3年間が経過しました。この間、いくつかの認定が有効期限を迎え、更新が必要になりました。 認定の更新について # AWS認定は有効期限が3年です。2022年に取得した認定は、2025年には更新が必要になります。 今回更新対象となった資格は、5資格です。 プロフェッショナル2資格の更新 プロフェッショナル2資格(ソリューションアーキテクト、DevOpsエンジニア)は下位資格の更新も含むため、期限となる6月より余裕をもって1月に更新しました。 プロフェッショナルレベルの認定は、下位のアソシエイトレベルの認定(Solutions Architect – Associate、Developer – Associate、SysOps Administrator – Associate)も同時に更新されるため、一度の更新で複数の認定を維持できるメリットがあります。 AWS Certified Solutions Architect – Professional 内容は以前と変わらず、Organizationを中心としたアーキテクチャ、セキュリティ、運用など幅広い知識を問われました マルチアカウント戦略やガバナンス、コスト最適化、災害復旧など、エンタープライズレベルの設計に関する問題が多く出題されました 問題文は相変わらず長文で、要件を整理しながら読む必要がありました AWS Certified DevOps Engineer – Professional こちらも以前と変わらず、ソリューションアーキテクトとセットで受験すると勉強した知識などを活かせました CI/CDパイプラインの設計、インフラストラクチャの自動化、モニタリングとログ管理などが主な出題範囲でした 廃止となるはずだったCode Commitが出てきて「今更これを問う意味はあるのか?」と疑問に思いましたが、撤回されたので意味ありました 専門知識3資格の再取得 専門知識3資格(セキュリティ、機械学習、高度なネットワーキング)は準備期間に余裕を持たせ、有効期限を気にせず失効後に順次再取得しました。 専門知識資格はプロフェッショナル資格と異なり、下位資格の更新には含まれないため、個別に更新する必要があります。 高度なネットワーキングは苦手で 再受験キャンペーン にお世話になりました。 AWS Certified Security – Specialty 受験したタイミングではSCS-C02でしたが、以前と特に変わったと思う点はありませんでした IAM、セキュリティグループ、ネットワークACL、WAF、GuardDuty、Security Hubなど、セキュリティに関する幅広い知識が問われました マルチアカウント環境でのセキュリティガバナンスや、コンプライアンス要件を満たすための設計に関する問題も多く出題されました SCS-C03では生成AI関連のセキュリティも増えるようなので、カバーする範囲が増えそうです AWS Certified Machine Learning – Specialty 2026年3月31日に廃止が発表されました 内容は以前と変わらず、機械学習が中心でした Amazon SageMakerを中心とした機械学習パイプラインの構築、モデルの訓練とデプロイ、モニタリングなどが主な出題範囲でした データの前処理、特徴量エンジニアリング、モデルの評価方法など、機械学習の基礎知識も必要でした AWS Certified Advanced Networking – Specialty 今回の更新で一番内容が変わっていた気がします 以前はDirect ConnectやVPN接続が中心でしたが、今回はAWS Global Accelerator、CloudFront、Route 53などのグローバルネットワークサービスに関する問題が増えていました マルチリージョン環境でのネットワーク設計や、ハイブリッドクラウド環境での接続性に関する問題も多く出題されました Transit GatewayやGateway Load Balancerなど、比較的新しいサービスに関する問題も増加していました 廃止になった専門知識3資格 廃止となり、取得日から3年たった日に無効化されていきました。 これらの資格は、新しい認定区分への統合や、AWS認定プログラムの再編成により廃止されました。 有効期限が切れるまでは認定として有効でしたが、更新の機会はなく、自然に失効していきました。 AWS Certified Database – Specialty AWS Certified Data Analytics – Specialty AWS Certified SAP on AWS – Specialty 3年間で得た経験 # 2022年の記事では「実際にAWSを触ってシステムを構築したわけでもないので、今後は実際のシステム構築で使える技能を付けていきたい」と書きました。 この3年間で、実際のプロジェクトでAWSを活用する機会はありましたが、まだ簡単なアプリケーションの方式設計程度にとどまっています。 実務でのAWS活用 実際のプロジェクトでは、以下のような場面でAWS認定で学んだ知識が役立ちました: 既存システムの理解 すでに動いているAWSシステムを理解する際に、認定で学んだ知識が大いに役立ちました どのAWSサービスが使われているか、サービス間がどのように連携しているかを理解する際に、認定で学んだ各サービスの特徴や使い方の知識が参考になりました システムの構成図やドキュメントを読む際に、AWS認定で学んだ知識があることで、理解がスムーズになりました システムの動作確認や調査 既存システムの動作確認やトラブルシューティングの際に、認定で学んだ知識が役立ちました IAMロールやセキュリティグループの設定を確認する際にも、認定で学んだ知識が理解の助けになりました 設計レビューやドキュメント理解 他のメンバーが設計したAWS構成や、既存システムのドキュメントを理解する際に、認定で学んだ知識が役立ちました 認定で学んだベストプラクティスやセキュリティの知識があることで、設計の意図や注意点を理解しやすくなりました 認定知識と実務のギャップ 認定で学んだ知識と実務には、いくつかのギャップがあることも実感しました: 実務では複数のサービスを組み合わせる必要がある 認定試験では個別のサービスの知識が問われますが、実務では複数のサービスを組み合わせてシステムを構築します サービス間の連携や、データフローの設計など、より実践的な知識が必要だと感じています コストとパフォーマンスのバランス 認定試験では「最適な解決策」を選ぶ問題が多いですが、実務ではコストとパフォーマンスのバランスを考慮する必要があります 実際のプロジェクトでは、予算の制約や、既存システムとの互換性など、試験では考慮されない要素も重要です 実際の構築・運用経験の不足 認定で学んだ知識は主に設計や概念に関するもので、実際の構築や運用の経験はまだ不足しています 今後は、実際にAWSを使ってシステムを構築し、運用する経験を積んでいきたいと考えています 新認定区分について # 2022年以降、AWS認定には新しい区分が4つ追加されました。そのうち3つはすでに取得済みで、デベロッパーサイトの記事にもしています。 2024年3月23日: AWS Certified Data Engineer - Associate 2024年9月26日: AWS Certified AI Practitioner 2024年12月6日: AWS Certified Machine Learning Engineer - Associate 今回話題に挙げるのは、4つ目の新認定区分である生成AIデベロッパープロフェッショナルです。 AWS Certified Generative AI Developer – Professional # AWS Certified Generative AI Developer – Professional は、Amazon BedrockなどのAWSサービスを使用した、本番環境に対応するAIソリューションの構築およびデプロイにおける高度なスキルを証明するプロフェッショナルレベルの認定です。 試験の概要 カテゴリ : Professional ベータ試験期間 : 205分 ベータ試験形式 : 85問、択一選択問題および複数選択問題 ベータ試験料金 : 150 USD(日本では22,000円(税込み)) ベータ対象言語 : 英語および日本語 なお、再受験キャンペーンは使えませんが、通常の半額バウチャは使えます。 対象となる受験者 この認定は、2年以上のクラウド経験があり、キャリアを高めたいと考えている開発者に最適です。 対象となる受験者は、以下の要件を満たしている必要があります: AWSで、またはオープンソーステクノロジーを使用して本番環境グレードのアプリケーションを構築した2年以上の経験 一般的なAI/MLまたはデータエンジニアリングの経験 生成AIソリューションの実装における1年の実務経験 AWSコンピューティング、ストレージ、ネットワーキングサービスの経験 AWSセキュリティのベストプラクティスとID管理についての理解 AWSのデプロイとInfrastructure as Codeツールの経験 AWSモニタリングおよびオブザーバビリティサービスについての知識 AWSコスト最適化原則の理解 ベータ試験について ベータ試験なので機械学習エンジニア-アソシエートやAIプラクティショナーの時と同じく認定ガイドに沿って準備を進めました。 ベータ試験は通常の試験とは異なり、試験内容や形式が確定する前の段階で受験する試験です。 AWS認定では、標準バージョンの試験で問題が使われる前にベータ試験を使用して試験問題の質が検証されます。 ベータ試験の合格者は新しい認定の最初の取得者になります。 最初の5,000名の試験参加者には、合格時に特別なEarly Adopterバッジが贈られます。 そのため、認定ガイドをしっかりと確認し、出題範囲を理解した上で準備することが重要です。 準備方法 ベータ試験の準備として、以下のような方法で学習を進めました: 認定ガイドの確認 出題範囲と各ドメインの重み付けを確認し、重点的に学習すべき領域を把握しました 生成AIデベロッパープロフェッショナルの場合、Amazon Bedrock、Amazon SageMaker、AWS Lambda、Amazon API Gatewayなどが主な対象サービスでした 公式ドキュメントとハンズオン Amazon Bedrockの公式ドキュメントを読み込み、主要な機能や使い方を理解しました AWS Skill Builderの関連コースや、ハンズオンラボで実際にサービスを触ってみました 生成AIアプリケーションの構築パターンや、ベストプラクティスを学習しました 関連認定の知識の活用 AI PractitionerやMachine Learning Engineer – Associateで学んだ知識を活用しました Solutions Architect – Professionalで学んだアーキテクチャ設計の知識も役立ちました セキュリティやモニタリングに関する知識も、生成AIアプリケーションの設計に必要でした 実務経験の活用 実際のプロジェクトでは簡単なアプリケーションの方式設計程度しかできていませんが、その経験が問題の理解に少しは役立ったと思います ただし、実務では限定的な経験にとどまっているため、認定ガイドでカバーされている範囲を広く学習する必要がありました 受験してみた 受験してみました。 しっかり準備したつもりでしたが、思っていた内容と異なっていました。 試験の難易度と特徴 問題数と時間 205分で85問という構成で、1問あたり約2.4分の時間配分が必要でした 問題文と選択肢が非常に長文で、文章から構成図をイメージするのに時間がかかりました 複数のサービスを組み合わせた複雑なシナリオが多く、全体像を把握するのに時間がかかりました 出題内容の特徴 Amazon Bedrockを中心とした生成AIアプリケーションの構築に関する問題が多く出題されました 本番環境での運用を考慮した設計(スケーラビリティ、セキュリティ、コスト最適化など)が問われました 複数の選択肢が正解に見える問題が多く、ベストプラクティスに基づいた判断が求められました エラーハンドリングやモニタリング、ログ管理など、運用面での考慮事項も多く出題されました 時間配分の課題 ギリギリまで時間がかかり、見直しの時間がほとんど取れませんでした 文章を読むのが遅いのを直さなければいけないと、いつも感じています 長文の問題を素早く理解し、要点を整理する能力が重要だと実感しました 結果と今後の対策 X(旧Twitter)などでは合格報告をよく見かけますが、残念ながら不合格でした。 正式リリースまでに、以下の点を改善して準備を進めたいと思います: 長文問題への対応 問題文を素早く読み、要点を整理する練習を重ねる 構成図やアーキテクチャ図を素早くイメージできるようにする 実践的な知識の強化 Amazon Bedrockを使った実際のアプリケーション構築経験を積む 本番環境での運用を考慮した設計パターンを学習する 関連サービスの深い理解 AWS Lambda、Amazon API Gateway、Amazon CloudFrontなど、生成AIアプリケーションでよく使われるサービスの詳細な理解を深める セキュリティ、モニタリング、コスト最適化などの観点から、ベストプラクティスを学習する 認定取得のモチベーション維持 # 3年間、認定を維持し続けるためには、継続的な学習が必要です。 以下のような方法でモチベーションを維持してきました: 継続的な学習方法 # 定期的なAWSサービスのキャッチアップ AWSの公式ブログや、re:Inventのセッション動画を定期的に視聴して、新しいサービスや機能をキャッチアップしています AWS Skill Builderのコースを活用して、体系的に知識を更新しています 特に生成AI関連のサービス(Amazon Bedrock、Amazon Qなど)は、頻繁にアップデートがあるため、継続的な学習が必要です 実務でのAWS活用 実際のプロジェクトでAWSを使うことで、認定で学んだ知識を実践で活用し、理解を深めています 実務で直面した課題を解決する過程で、新しい知識やベストプラクティスを学んでいます 認定で学んだ知識が実務で役立つことを実感することで、学習のモチベーションが維持されます 新しい認定区分への挑戦 新しい認定区分が追加されると、新しい知識を学ぶ機会として積極的に挑戦しています ベータ試験に参加することで、新しい認定の最初の取得者になることを目指しています 新しい認定区分への挑戦は、学習のモチベーションを高める良いきっかけになります モチベーションの源泉 # また、 2026 Japan All AWS Certifications Engineers のクライテリアを満たすことが大きなモチベーションとなっています。 2023年、2024年、2025年と3年連続で対象となり、引き続きすべてのAWS認定資格を保持し続けることが目標です。 この目標を達成するためには、以下のような取り組みが必要です: 認定の更新計画 各認定の有効期限を管理し、更新が必要な認定を事前に把握しています プロフェッショナル資格は下位資格も更新されるため、優先的に更新するようにしています 専門知識資格は個別に更新する必要があるため、計画的に準備を進めています 新しい認定区分への対応 新しい認定区分が追加されたら、できるだけ早く取得するようにしています ベータ試験に参加することで、早期に取得できる可能性があります 新しい認定区分への対応は、すべてのAWS認定資格を保持し続けるために必要です コミュニティとの交流 X(旧Twitter)や技術ブログなどで、同じ目標を持つ人たちと情報交換をしています 認定取得の経験や、学習方法などを共有することで、モチベーションを維持しています コミュニティとの交流は、学習の継続を支える重要な要素です まとめ # 2022年にAWS認定12冠を達成してから3年が経過し、いくつかの認定の更新が必要になりました。 また、この間に新しい認定区分が追加され、AWS認定の選択肢が広がりました。 認定を取得することはゴールではなく、継続的な学習のきっかけとして活用していきたいと考えています。 今後も、新しい技術や認定区分にチャレンジし続けていきたいと思います。
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第22目の記事です。 こんにちは!AWSの世界を探求する皆さん。 突然ですが、 AWS Control Tower 使っていますか? 今私が携わっているプロジェクトにおいてAWS Control Tower(以降Control Tower)を導入したアカウント運用の話があり、個人ではあまり使う機会が少ないサービスだと感じたので、この機会に使っていく中で判明した挙動について書き留めておこうと思います。 そもそもControl Towerってどんなサービスなの?という話を軽くしておくと、 AWS Control Towerは、AWSのベストプラクティスに基づいたマルチアカウント環境(ランディングゾーン)を自動的にセットアップし、継続的なガバナンスを提供するサービスになっています。 この記事で紹介するのは主にControl Towerの「Account Factory」と「Account Factory Customization(以降AFC)」による「アカウント作成」の挙動についてです。 プロジェクトとして、Account Factoryの処理をAWS CLIコマンドを直接たたくことで実現する必要があったため、AWSマネジメントコンソール(以降マネコン)からアカウント作成を実行した時の裏側で走る処理について、AWS CloudTrail(以降CloudTrail)のイベント履歴を追うことでAWS CLIコマンドで実現する方法を明確にしていきました。 そこで以降では「Account Factory」と「AFC」の具体的な処理フローについてまとめていきたいと思います。 また、AWS CLIコマンドではマネコン上で操作した時と同じように再現はできないことも合わせてお伝えしていきたいと思います。 前提 # 冒頭にて本記事での内容について述べましたが、以下については詳しくふれません。 そのため、ある程度AWSになれていて、AWS Organizations(以降Organizations)やAWS IAM Identity Center(以降Identity Center)などのアカウント・ユーザー管理系サービスの知識、用語の理解がある方向けの内容になっています。適宜注釈や参照リンク、軽い説明は混ぜていきますが、ご了承ください。 Control Towerの開始方法や操作、関連用語 Organizationsの機能やIdentity Centerの機能と操作、関連用語 AWS Service Catalogについての機能や操作、関連用語 また、検証にあたり以下については設定済み・リソースがあるという前提とOrganizationsとIdentity Centerを絡めた組織的なアカウント運用・管理を前提にした内容で記事を書いています。 AWSルートアカウントからIdentity Centerを有効化し、AWSルートアカウントにAdmin権限でアクセスできるユーザーを設定している 参照: 管理アクセスを持つユーザーを作成する Control Towerのランディングゾーンを開始している AWSルートアカウントで有効化したIdentity CenterとControl Towerの間でアクセス制御統合をしていること AWS Service Catalog(以降Service Catalog)の製品「AWS Control Tower Account Factory」が存在していること AFCを利用するためのブループリント [1] ハブアカウントが存在する 参照: カスタマイズのための設定 1. Account Factoryの正体は「Service Catalog製品」 # Control Towerを知っている方や使ったことがある方はご存じだと思いますが、アカウント作成を実行するにはControl Towerのコンソール画面にある「Account Factory」から操作を行います。 実はこれ、Control Tower独自の機能というよりも、 Service Catalog というサービスをラッピングしたものなんです。 大まかに何をしているのか # Control Towerの管理アカウント(ランディングゾーンを開始したアカウントのこと)で Service Catalog のコンソールを開いてみてください。「製品」のリストに 「AWS Control Tower Account Factory」 という製品があるはずです。 マネコン上からControl Towerで「アカウント作成」ボタンを押すことは、裏側では 「Service Catalogの『AWS Control Tower Account Factory』という製品を起動している」 こととイコールになります。 後ほどより具体的に説明していきます。 なぜ Service Catalog なのか? # ここでなぜわざわざControl TowerからService Catalogを介してアカウントを作成するのか疑問に思った方もいると思います。普通にOrganizationsからアカウント作ればいいのでは?と思いますよね。 このようにアカウントを作成することにはメリットがあります。(でなければわざわざサービスとして提供しなくてもいいですからね。。) 権限の分離 本来、アカウント作成やIAM設定には強力な権限(AdministratorAccess相当)が必要です。しかし、経理担当者や各プロジェクトリーダーにそんな強権を渡したくないですよね? Service Catalogを使えば、 「この『Account Factory製品』を使う権限」だけをユーザーに渡せばOK です。(正確にはControl Towerを操作する権限を渡しておけばOkです。) つまり、管理アカウントにログインするユーザーに、OrganizationsやIdentity Centerを操作する権限は付与しなくてよいということになります。 コントロールとベースラインの適用 Control Towerを経由することで、単にアカウントを作成するだけでなく、組織的な管理下に置かれた「統制の効いた」アカウントとして払い出すことができます。これは、企業が複数のアカウントを安全かつ効率的に運用するために不可欠な要素です。 具体的には、アカウント作成時に「コントロール(旧ガードレール) [2] 」や「ベースライン [3] 」と呼ばれるセキュリティ設定やログ設定が自動的に適用されます。これにより、管理者は個々のアカウント設定を確認する手間を省くことができ、常にポリシー(AWSのベストプラクティスに基づくセキュリティ基準のこと)に準拠した状態を維持できます。 2. アカウント作成時の「裏側の挙動」 # では、実際にアカウント作成ボタンを押したとき、裏側でどのようなフローが走っているのか、CloudTrailのイベント履歴を元に正確なフローを見ていきましょう。 以下の処理は、主にSTS(Security Token Service)を使用して一時的かつ強力な権限にスイッチしながら行われます。 また、各リソースの作成確認(Describe、List系のコマンド)は随時並行して処理が走っています。 STEP 0: 設定と製品の確認 # まず初めに、 DescribeAccountFactoryConfig が実行され、現在のControl TowerにおけるAccount Factoryの設定情報が取得されます。 これに基づき、以下の手順でService Catalog製品の確認が行われます。 SearchProductsAsAdmin 所有者が「AWS Control Tower」であるService Catalog製品を検索します。 DescribeProduct 検索で見つかった製品(Account Factory)の詳細を取得します。 この時、製品の中身(AWS CloudFormationテンプレート、以降CloudFormationテンプレート)が新しいバージョンになっている場合、 CreateProvisioningArtifact が実行され、製品の更新処理が走ることがあります。 STEP 1: Account Factory製品の起動 (ProvisionProduct) # Control Towerにおけるアカウント作成の実体である ProvisionProduct が実行されます。 これにより、「AWS Control Tower Account Factory」製品の起動が開始されます。 STEP 2: 管理アカウントへの登録 (CreateManagedAccount) # 製品の起動の中で、作成されたアカウントをControl Towerの管理下に置くための CreateManagedAccount 処理が走ります。 これにより、単なるOrganizationsのアカウントとしてだけでなく、Control Towerによって管理・監視される「Managed Account」として登録されます。(作成時にしてたOU配下に登録されます。) STEP 3: ユーザーとグループのセットアップ (CreateUser) # Identity Centerにおいて、新規作成されたアカウントへアクセスするためのユーザー作成、グループへの追加などの処理が行われます。 STEP 4: ベースラインとコントロールの適用 # AWS CloudFormation StackSets(以降CloudFormation StackSets)が発動し、STEP 0で確認されたベースラインやコントロール(各種セキュリティ設定やログ設定など)が、アカウントに対してデプロイされます。 STEP 5: アクセス権限の割り当て (CreateAccountAssignment) # 最後に、新規作成されたアカウントに対して、Identity Centerの許可セット(Permission Set)とユーザー(またはグループ)の割り当てが実施され、ユーザーがログイン可能な状態になります。 上記のようなステップを踏んで実際の「Account Factory」の処理が進みます。 あまり複雑になりすぎないよう簡潔にまとめましたが、Service Catalog製品の起動からベースラインなどの適用まで一貫して実施されていることがお分かりいただけたかと思います。 3. Account Factory Customization (AFC) # 続いて、Account Factoryの拡張版ともいえるAFCについてのフローを見ていきましょう。 「Control Towerの標準設定だけじゃ足りない!アカウント利用開始時にIAMロールや必要なAWSリソース構成も最初から入れておきたい!」というような、 AWSが用意しているデフォルトのService Catalog製品で展開されるリソースに加えて独自の設定内容をアカウント作成時に適用したいという要望に応えるのが Account Factory Customization (AFC) です。 Account FacotryがAWSマネージドな処理で、AFCがカスタマーマネージドな処理というとらえ方でよいかと思います。(厳密には違いますが、イメージはそんな感じです。) AFCの仕組み:Service Catalog on Service Catalog # AFCを使うと、Account Factory実行時に使用されるデフォルトのブループリントとは別に、 「追加のカスタムブループリント」 を指定できるようになります。 ※補足をしておくと、Account Factory時にはAWS内部的にデフォルトのブループリントが使用されています。それと対比して、AFC時に使うブループリントをここではカスタムブループリントと呼んでいます。 公式リファレンスにも表記がされているのですが、例のごとく「ブループリント=デフォルトのブループリント」だったり、「ブループリント=カスタムブループリント」だったり表記ゆれがあるので、明確に区別しておきます。 この「ブループリント」ですが、実態は Service Catalog製品として登録されたCloudformatinoテンプレート です。 管理アカウント、あるいは「ハブアカウント」にあるカスタムブループリントを、デフォルトのブループリントを実行した後に適用してくれます。 つまり、Control Towerがアカウント新規作成時にデフォルトで作成してくれるリソースを維持しつつ、独自に追加したいリソースも入れることができるということです。 ※カスタムブループリントは公式的に「ハブアカウント」に置くことが推奨されています。 AFC実行時の裏側の挙動 # さて、ここでも背後の挙動を追ってみましょう。通常のAccount Factoryとは少し異なる動きを見せます。 STEP 0(設定確認)までは通常時と同様ですが、その後の動きに特徴があります。 CreateManagedAccount(の実行) AFCの場合、通常の ProvisionProduct は実行されずに、 CreateManagedAccount が実行されます。 このリクエストパラメータには、通常のAccount Factoryの情報に加え、「blueprints」(事前にハブアカウントに作成しておいたService Catalog製品の情報、カスタムブループリントのこと)が含まれています。 IdCユーザー関連処理 通常と同様に、新規作成アカウントへアクセスするためのIdentity Centerユーザー作成処理などが走ります。 ベースラインとカスタムブループリントの適用 CloudFormation StackSetsによって、標準のコントロールやベースライン(デフォルトのブループリント)が適用されます。 これは推測になってしまいますが、 指定したカスタムブループリントはこのタイミングで適用される と考察しています。 なぜこのような考察に留まってしまったのかは以下の「ブラックボックスな部分」にまとめました。 アクセス権限の割り当て 最後にユーザーやグループへのアクセス権限割り当てが行われます。 ブラックボックスな部分 興味深い点として、カスタムブループリントの適用に関する明確なAPI呼び出し(例:ハブアカウントからの製品取得など)がCloudTrail上では確認できませんでした。 しかし、事実としてハブアカウント上のService Catalogではカスタムブループリント製品が実行された形跡が残っているため、CreateManagedAccount実行の背後で別の処理(製品の取得など)が走り、適用のタイミングとしてはデフォルトのブループリント適用の後にカスタムブループリントを適用しているのではと考察しました。 何度か試して確認ができなかったので、CloudTrailのイベント時系列から上記のような処理になるのではないかと予想しています。 4. プロジェクトでの実例と直面した壁 # ここからは、実際のプロジェクトで直面した課題とその解決策についてお話しします。 今回のプロジェクトでは、 Control Towerを開始した管理アカウント(アカウントA)とは別のAWSアカウント(アカウントB)から、AFC機能を使ってアカウントを作成したい という要件がありました。 試みたこと # 当初は、「APIが用意されているはずだから、クロスアカウントでもSTSでスイッチロールしてコマンドを叩けば大丈夫だろう」と安直に考えていました。 しかし、現実はそう甘くありませんでした…。 AFC実行用のCLIコマンドが存在しない まず、AFCの処理フロー等価である CreateManagedAccount というCLIコマンドが存在しません。 Control TowerのAPIリファレンス を探しても相当するコマンドは見つかりませんでした。 Service Catalogコマンドの限界 CLIでAccount Factoryを実行するために servicecatalog provision-product コマンドを利用することは可能です。 しかし、このコマンドでは product-id (製品ID)を 1つしか指定できません 。 つまり、通常の「AWS Control Tower Account Factory」製品を指定すると、AFCで利用したい「カスタムブループリントの製品ID」を同時に渡すことができないのです。 そのため、「デフォルトブループリントの適用→カスタムブループリントの適用」という連動した動きが再現できなくなります。 デフォルトブループリントを適用した後にもう一度カスタムブループリントを指定してコマンドを実行すれば?と思うかもしれませんが、これだと新規作成アカウントの情報が連携できていないので、カスタムブループリントの内容がアカウントB上で実行されるだけになります。 クロスアカウントの制約 さらに、クロスアカウントで実行する場合、ハブアカウントに存在しているService Catalog製品(カスタムブループリント)の情報が取得できないという制限もありました。 ハブアカウントからカスタムブループリント製品を管理アカウント側に共有(厳密には製品を管理するポートフォリオを共有)して試してみましたが、これもうまくいかなかったです。(そもそもSearchProductsAsAdmin CLIコマンドにおいて共有した製品が管理アカウント上で認識できなかったです。。) 辿り着いた解決策 # 最終的に、API(CLI)ベースで要件を満たすために以下のアプローチを採用しました。 通常のAccount Factoryでアカウントを作成 まず、 provision-product コマンドを使用して、標準のAccount Factory製品を起動し、Control Tower管理下のアカウントを作成します。これだけならアカウントBからでも実行可能です。 CloudFormation StackSetsでカスタマイズを適用 アカウント作成が完了した後、別途 CloudFormation StackSets を使用して、AFCで適用したかったカスタムリソース(IAMロールなど)を新アカウントに適用します。 一発でAFCを実行することはできませんでしたが、手順を分割することで「Control Tower管理下の統制の効いたアカウント」かつ「独自のカスタマイズが施されたアカウント」をAPIベースで作成することができました。 5. Control Towerのメリットと「手の届かない」部分 # ここまで、Account Factoryの挙動やカスタマイズについて深掘りしてきました。 Control Towerは、セキュリティのベストプラクティスに沿った環境を簡単に構築・維持できる非常に強力なサービスです。ボタン一つ(あるいはコマンド一つ)で、ログ集約やアクセス制御が整ったアカウントが手に入るのは素晴らしいことです。 しかし、その「自動化」と「標準化」の裏返しとして、 手の届かない部分 も存在します。 Control Towerの限界(変更できない設定) Control Towerによって自動作成・管理されるリソースの中には、ユーザー側で設定変更ができない(または推奨されない)ものがあります。 全ては挙げれませんが、私が実際に設定変更をしようとしてできなかったものとして以下の2点があります。 CloudTrail用S3バケットの設定 ログアーカイブアカウントに作成されるS3バケットのライフサイクルポリシーなどを自由に変更することができません。要件に合わせてログ保存期間を変えたい場合など、柔軟な対応が難しいことがあります。 AWS Configアグリゲーター 組織全体のConfigルールを集約するアグリゲーターも自動生成されますが、この設定もユーザーが自由にカスタマイズすることは難しい部分です。 一度Control Towerによって自動で作られたアグリゲーターを削除してみましたが、ドリフト検出されて常に警告がでてやや鬱陶しかったです。。 その他にも、Control Towerによって作成・管理されているCloudformationテンプレートで展開されているリソースには手を加えない方が無難でしょう。 これらの「手の届かない部分」があることを理解した上で、Control Towerの標準機能でどこまでカバーし、どこからは独自の実装(例えば別途S3バケットを作る、独自のConfigルールを追加するなど)で補うかを判断することが、Control Towerとうまく付き合っていくコツだと感じました。 まとめ # 本記事では、AWS Control TowerのAccount FactoryとAFC機能について、その裏側で実行されている処理フローを解説してきました。 Account Factoryの実体は、 Service Catalog製品 として提供されており、アカウント作成のリクエストは、Service Catalogを経由して AWS Organizations や AWS CloudFormation StackSets に連携されます。 これらの一連の処理によって、アカウント作成からセキュリティ設定(コントロールとベースライン)の適用、Identity Centerへのユーザー登録までが自動化されています。 Account Factory Customization (AFC) も同様に、Service Catalog製品として登録した (カスタム)ブループリント を利用することで、標準の設定に加えて独自のリソースを自動的にデプロイする仕組みを提供しています。 Control Towerは一見ブラックボックスに見えるかもしれませんが、その裏側で動いているのは、IAM(Identity Centerを含む)、CloudFormation、Service Catalogといった基本的なAWSサービスです。 この構造を理解しておくことで、エラー発生時のトラブルシューティングや、より高度なカスタマイズが必要になった際の対応力が大きく向上するはずです。 Control Towerは頻繁にアップデートが行われており、APIによる運用の柔軟性も徐々に向上していくと予想しています。 特にAFC機能については、現状ではAPI連携において一部制約がありますが、将来的にはCLIやSDKを通じたよりシームレスな自動化が可能になることを期待したいところです。 引き続き最新の動向をウォッチし、より効率的なマルチアカウント運用を目指していきたいと思います! 注釈 # ブループリント (Blueprint) : AWSアカウントの構築やカスタマイズに使用される、事前に構成されたテンプレート(実態は主にCloudFormationテンプレート)。 ↩︎ コントロール (Controls) : AWS環境全体(OU単位)に適用されるガバナンスルール(旧称: ガードレール)。予防的コントロール(SCP)と発見的コントロール(Config/AWS Lambda)などがあります。 ↩︎ ベースライン (Baseline) : OUなどのターゲットに適用されるリソースとその設定のグループ。 ↩︎
少し公開が遅くなってしまいましたが、これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第15日目の記事です。 はじめに # 豆蔵は長年にわたり、ロボットシステム開発の支援およびコンサルティングを行ってきました。 オリジナルロボット「BEANus」シリーズなどで培ってきた技術力は、単なるメカニクスの制御にとどまらず、高度なソフトウェア技術との融合にあります。 今回、三井化学株式会社と共同開発した中食(なかしょく)工場向け食品盛り付けロボット美膳®(びぜん)は、まさにその結晶です。 詳細なプレスリリースは こちら をご覧ください。 私は、主にソフトウェアアーキテクトとして美膳®の開発に携わりました。 今回、ロボット開発では珍しいクリーンアーキテクチャ(Clean Architecture)を採用し、ドメイン中心設計を実現しました。 本記事では、食品盛り付けロボット美膳®の紹介と、それを支えるソフトウェアアーキテクチャについて紹介します。 美膳®の紹介 # 美膳®は、中食業界で深刻化する労働力不足を解消するために開発された、食品盛り付けロボットシステムです。 豆蔵がシステム設計、メカ・エレキ設計、そしてAI・ビジョン・モーションを含むソフトウェア開発を担当し、三井化学が高機能樹脂素材の提供やロボットハンドの開発、製造・販売を担当するという、両社の強みを活かした共同開発によって生まれました。 美膳®が解決したい社会課題 # 下図に示すように、中食工場では多くの盛り付け作業が人手で行われており、労働力不足や人件費高騰が深刻な問題となっています。 このような背景から、盛り付け工程の自動化が強く求められています。 画像提供:株式会社豆蔵 美膳®で出来ること # 美膳®は、弁当や惣菜の盛り付け作業を自動化します。 具体的には、以下のような高度な作業を高速かつ正確に行います。 食材の認識とピッキング : 番重(食材コンテナ)内にランダムに置かれた食材を、AIビジョンで瞬時に認識し、最適な把持位置を判断してピッキングします。 容器への追従盛り付け : ベルトコンベア上を流れる容器の位置・姿勢・速度をカメラを使ってリアルタイムに認識し、ロボットアームが追従しながら正確な位置に食材を盛り付けます。 双腕による協調動作 : 左右のアームが互いの状況をリアルタイムに共有し、上流での作業漏れを下流でカバーしたり、作業負荷を分散させたりするなど、双腕ならではの柔軟な連携により生産性を最大化します。 特徴 # 美膳®には以下の大きな特徴があります。 1. 業界最速レベルの生産性 中食工場での人の作業スピードは1時間あたり約2,000食と言われていますが、美膳®はこの人の作業と同等の生産能力(2,000食/時)を実現しました。従来の盛り付けロボットの多くが1,200食/時程度であったのに対し、大幅な生産性向上を達成しています。 2. 迅速な段取り替え 多品種少量生産が求められる中食工場では、頻繁なライン変更が必要です。美膳®はキャスター付きで移動が容易であり、ロボットハンドも簡単に交換できる設計になっています。これにより、生産品目が変わっても短時間でラインを切り替えることができます。 3. 軽量・コンパクト 三井化学の高機能樹脂素材を採用することで、軽量化と高剛性を両立しました。また、双腕ロボットでありながら小型化を実現しており、既存の盛り付けラインの限られたスペースにも導入しやすくなっています。 4. 人との協働 非接触の外装センサーを搭載することで、人が近づくと自動で減速・停止する機能を備える予定です。これにより、安全柵なしで人とロボットが同じ空間で作業することが可能になります。 --> Information ※本機能は現在開発中であり、現行品には搭載されていません。 ソフトウェアアーキテクチャの紹介 # 私はソフトウェアアーキテクトとして、この美膳®の開発に携わりました。 よって、ここからはソフトウェアアーキテクチャの概要について説明します。 美膳®における設計思想 # 美膳®は単にメカが動くだけでなく、AIによる認識、複雑なモーション制御、そしてそれらを統合するシステム全体が高度に連携して動作しています。 このシステムの核となるのは、 「食品を認識し、把持し、盛り付ける」という「食品盛り付け業務(ドメイン)」 です。 このドメイン知識をシステムの中心に据え、特定のハードウェアといった技術的詳細に依存しない設計を目指しました。 アーキテクチャ設計においては、以下の点を重視しました。 ドメイン中心 : 「食品盛り付け」という業務ロジックを最重要視し、技術的詳細から分離すること 保守性 : 長期間にわたる運用と機能追加に耐えうる構造であること テスト容易性 : ハードウェアに依存せず、ロジック単体でのテストが可能であること 独立性 : フレームワークやセンサ・インジケーターなどの外部要素への依存を最小限に抑えること 特に、独立性を重視した理由は、 美膳®には食品盛り付けロボットシステムのプラットフォームとしての役割 も期待されており、将来的に新しいセンサ技術などが登場した際に、容易に置き換えられる柔軟性が求められたためです。 以上より、次節で説明する クリーンアーキテクチャ(Clean Architecture) の思想を取り入れた設計を採用しました。 クリーンアーキテクチャとは # クリーンアーキテクチャ(Clean Architecture)は、Robert C. Martin (Uncle Bob) 氏によって提唱されたソフトウェア設計思想です。 同心円状の図で表現されることが多く、外側から内側に向かってのみ依存関係を持つ「依存性のルール(Dependency Rule)」が最大の特徴です。 The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle. (出典: The Clean Architecture | The Clean Code Blog ) このアーキテクチャにおいて、最も内側に位置するのは「エンティティ(Entities)」や「ユースケース(Use Cases)」といった ドメイン(ビジネスロジック) です。 データベースやWebフレームワーク、UI、そしてロボットにおけるデバイスといった詳細な技術要素は、すべて外側に配置されます。 つまり、 「ドメインが詳細(インフラ)に依存するのではなく、詳細がドメインに依存する」 という構造を作ることが、クリーンアーキテクチャの核心です。 これにより、技術の流行り廃りやハードウェアの変更といった外的要因から、システムの核となるビジネスロジックを守ることができます。 具体的な設計の紹介 # システム物理配置 # システムの物理配置図を以下に示します。 ステレオタイプ<<app>>の要素がソフトウェアの実行単位となります。 よって、以下の3つが主要なソフトウェアの実行単位となります。 名前 説明 GUIApp GUIを有するアプリケーションである。ユーザ向けのGUIを提供する。コントローラPC上で実行され、操作パネルのタッチパネル上に表示される。 ControllerApp 本システムのコアアプリケーションである。機能的にはシステムの状態制御や登録データの管理、画像処理を行う。 その手段として、コントローラPCに接続されたデバイスの制御、Databaseの操作を行う。 また、GUIAppから接続可能なサーバー機能を有する。また、生産運転中に限り、画像処理結果を供給するビジョンサーバー機能を有する。 MotionController ロボットモーション制御を主に担うアプリケーションである。また、DIO接続デバイスや安全基盤との結合も担う。モーション制御に関する機能を持つサーバー機能を有し、ControllerAppがクライアントとして接続される。また、生産運転実行中はControllerAppのビジョンサーバーに接続し、画像処理結果を取得する。 --> Information GUIAppとControllerAppが何故別々の実行単位なのか 非機能要件で将来的に操作パネルをタブレット端末など別デバイスに変更する可能性があるため、GUI部分を分離している。 --> Information コントローラPCとロボットコントローラが分かれている理由 PoC段階でロボットコントローラとして KEBA社 のコントローラを使用していたため。 コンポーネント構成 # 美膳®のソフトウェアコンポーネント設計について解説します。 全体は、クリーンアーキテクチャのように同心円の表記ではありませんが、同様の思想に基づいて設計されています。 クリーンアーキテクチャの同心円にて中心方向に依存が向かうのと同様に、依存方向は下に向かっており、下層にはEntitesやInteractorなどのドメインロジックが配置されています。 コンポーネント 説明 ControllerApp 関連コンポーネントを結合し、実行可能にするアプリケーションコンポーネント。 ControllerAPI 外部(GUIAppや上位システム)との通信を担うAPI層。 Adapter 外部サービス(ビジョン・モーションなど)の実装コンポーネント。 Controllers メインのビジネスロジックを束ねるコンポーネント。 Controller ユースケース(Interactor)を統合・制御するコンポーネント。一般的なクリーンアーキテクチャのController(Interface Adapters)とは異なり、ここでは複数のInteractorを束ねる「アプリケーションサービス」に近い役割を担う。 StateMachine システムの状態遷移を管理するコンポーネント。Interactorで実現される機能に対して横断的にシステム状態遷移を実現する。 Port Adapterのインターフェース定義コンポーネント。外部サービスのインターフェースとなる。 Interactor ユースケースに応じたビジネスロジックフロー(機能)を実現する中心的なコンポーネント。 Entities ドメインモデルやデータ構造を定義するコンポーネント。 VisionController 画像処理関連機能を有するコンポーネント。 VisionAPI 画像処理機能を外部に提供するAPI。 MotionControllerAPI MotionControllerの機能を外部に提供するAPI。REST APIによるMotionControllerへの指令送信や状態取得をラップする。 Common 再利用性の高い共通で使用される汎用的なコンポーネント群。ロギング、数値計算、スレッド制御などの機能を集約している。 この構成における重要なポイントは、 Port (インターフェース) と Adapter (実装) の分離 です。 例えば、ビジョンシステムやモーション制御といった外部要素は、Portコンポーネントで定義されたインターフェースを通じてのみアクセスされます。 実体である Adapterは、このインターフェースを実装する形で提供されます。 これにより、Interactorなどのビジネスロジックは、具体的なカメラの機種やロボットコントローラの通信プロトコルを知る必要がなくなります。 結果として、ビジネスロジックが分離され、ハードウェアの変更が容易になり、テストの効率化と保守性の向上を実現しています。 おわりに # 今回は、中食工場向け盛り付けロボット美膳®の概要と、その裏側にあるソフトウェアアーキテクチャについてご紹介しました。 ロボット開発はハードウェアとソフトウェアが密接に関わるため複雑になりがちですが、適切な設計パターンを適用することで、もっと楽しく、効率的に開発できるようになります。 この記事が、日々ロボット開発に奮闘されているエンジニアの皆さんのヒントになれば嬉しいです。 豆蔵では、こうしたモダンな設計思想を取り入れたロボット開発を推進しています。 「ちょっと話を聞いてみたい」「ウチのロボットも何とかして」という方は、ぜひお気軽にご連絡ください。
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第19日目の記事です。 こんにちは&はじめまして、教育グループのやさぐれ豆ぱんだこと、おのでらです。 ユーザー部門(ビジネス部門)の皆様、ユーザーテスト(受け入れテスト)の依頼が来るたびに、 正直、こう思ったことはありませんか? 「情シスがちゃんとテストしたはずなのに、なんで私たちが改めてテストしなければいけないの?」 忙しい業務の合間を縫ってのテスト作業。「バグがないか確認するだけなら、プロである情報システム部門(以下、情シス)だけで完結してほしい」「これって二度手間じゃないの?」 ……そう感じてしまう気持ち、とてもよく分かります。 しかし、実は 「皆様がやらなければならない、代わりのきかない理由」 があるのです。 その理由を体感していただくために、少しだけ 「2つのチェック」 にお付き合いください。 チェック1:間違い探し # まず、以下の2つの画像を見比べてみてください。 「クマの設計書と完成イメージ [1] 」と、それを元に「実際に作られたクマの完成品(成果物)」です。 (↑クマの設計書) (↑クマの完成品(成果物)) この2つを見比べて、違う箇所を探してみてください。 「ここが違う」「あそこの色が違う」……いくつか見つかりましたか? 見つかった方は、どこが違うか心の中でメモしておいてください。 チェック2:目的の確認 # では次に、全く別の視点で考えてみてください。 そもそも、この「クマ」は何のために作ったのでしょうか? 実は、以下のような要件(目的)があったとします。 【目的】 会社の受付に置き、来訪されるお客様からの「会社の好感度」や「知名度」を上げるためのマスコットを作りたい。 この目的を聞いた上で、改めて先ほどの「ブロックのクマ」を見てください。 そして、 「これを自社の受付に置くべきかどうか」 を判断してみてください。 いかがでしょうか? 「おもちゃのようなブロックを受付に置くのは、会社のイメージに合わないのでは?」 「好感度を上げるという目的には、もっと洗練されたデザインの方がいいのでは?」 「なぜクマなの?というかパンダだよね?頼んでないクリスマスツリーもついてきてるよね?」 「いやいや、ウチもうマスコットキャラクター居るし」 おそらく、多くの人が 「この目的には適していない」 と感じたのではないでしょうか。 ここで種明かし:2つのテストの違い # さて、皆様には今、2種類のチェックを行っていただきました。 実はこの2つが、そのまま「情シス」と「ユーザー部門」の役割の違いを表しています。 1つ目のチェック=「システムテスト(情シス)」 # 最初に行った「間違い探し」は、 情報システム部門が実施する「システムテスト」 です。 これは「元々の設計書と見比べて、設計書通りに正しく作られているか」を確認する作業です。 情シスは、設計書通りにブロックが組まれているか(バグがないか)は完璧にチェックできます。しかし、それが「受付に置くのにふさわしいか」までは判断できません。 2つ目のチェック=「ユーザーテスト(皆様)」 # 次に行った「目的の確認」こそが、 皆様、ユーザー部門の皆様が実施する「ユーザーテスト」 です。 ユーザーテストで重要なのは、設計書通りかどうかのチェックではありません。 「要件定義で挙げた課題(好感度アップ)が本当に解決するのか」「このシステムは今の業務や目的に適しているのか」 を確認することです。 --> ちなみに「間違い探し」の箇所は 口の色がピンクじゃなくて赤 体の色が茶色じゃなくて白黒(≒クマじゃなくてパンダ) 舌が長い 左耳が長い 設計書に無い「クリスマスツリー」が作られている(誰だ、途中で要件追加したのは!?) まとめ:だから、皆様の「視点」が必要です # 1つ目のチェックで「設計通り完璧なクマ」ができていたとしても、2つ目のチェックで「受付にはふさわしくない」と判断されれば、ビジネスとしては失敗です。 そして、「これは受付に置けないな」と判断できるのは、普段その業務を行い、顧客と接している ユーザー部門の皆様だけ なのです。 「情シスがテストしたのに……」と感じることもあるかと思いますが、これからのユーザーテストでは、ぜひ 「業務のプロとしての視点」 を大切にしてください。 「バグを探す」のではなく、 「このシステムは、私たちの仕事を本当に良くしてくれるか?」 という視点で触っていただくことが、プロジェクト成功の鍵となります。 --> お知らせ 今回ご紹介した「クマの事例」は、 ユーザー部門向け研修 でお伝えしている内容のほんの一部です。 この研修では、単なる「テストの手順」や「要件定義のコツ」といったテクニック論ではなく、 なぜ、ユーザー部門がプロジェクトに関わらなければならないのか なぜ、情シス任せではプロジェクトが失敗するのか という 「本質的な役割と責任」 について深く掘り下げて解説します。 「システム導入のたびに『やらされ感』を感じている」というユーザー部門の方はもちろん、 「ユーザー部門にもっと当事者意識を持ってほしい」と感じている情報システム部門の方 にも、ぜひご覧いただきたい内容です。 お互いの「視点」を合わせ、プロジェクトを成功に導くために。 詳細は以下のリンクからご確認ください。 ▼ 研修の詳細・お申し込みはこちら https://www.mamezou.com/services/hrd/user_department_training ※本記事の画像作成には、ヨシリツ株式会社の知育玩具「[LaQ(ラキュー)]」を使用しています。 https://www.laq.co.jp/ ↩︎
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第18日目の記事です。 はじめに # 生成AIの進歩等により、人間にはますます知識よりも知恵が必要だと感じるビジネスソリューション事業部アジャイルグループの岸本大輔です。 アジャイル開発宣言が公開されてもうすぐ四半世紀。人によっては「まだまだこれから」「もうあたりまえ」あるいは「今となっては古い」など様々な意見があるようですが、重要なのは「よりよい方法を見つけだそうとし続けている」ことだと思います。 SAFeやLessやScrum@ScaleやDA等、アジャイルをスケールする工夫もそれぞれ進化しています。用語やこだわりポイントは多少違いますが、お互いリスペクトしながら参考にされているので少し抽象化して見ると似ている部分も多いですね。経済学や心理学等、様々な分野の知見もうまく活かしながら「よりよい方法」を見つけだそうとし続け、永遠のβ版になっている印象です。 それでも「スクラムのイベントが形骸化している」という悩みを持つチームもあります。「アジャイル」「スクラム」「リーン」などの名前ややり方だけではなく「何のために?」「どうなると幸せ?」について考えていると、ソフトウェア開発以外でも似たようなことがたくさんあり、それらを見つけて参考にするのが「気付き」や「刺激」になると思います。 ということで今回はお鮨屋さんを舞台に、アジャイルについて考えてみたいと思います。アジャイル初心者向けの知識習得には適していませんが、スクラムマスタやアジャイルコーチの考え方の引き出しを増やすための「気付きのきっかけ」になるとうれしいです。 以前、 よりよい方法を見つけだそうとし続けている: 自由があればよりよく進化する でも、「何でもアジャイルに見える例」をいくつかご紹介いたしましたが、今回のお話は お鮨屋さんに行くたびにアジャイルについて考えてしまう症状 が出てしまう可能性がございますので、あらかじめご了承いただけますようお願いいたします。 人生に必要な知恵はすべて幼稚園の砂場で学んだ # ちょっと昔の本ですが、タイトルがキャッチ-で覚えていらっしゃる方も多いのではないでしょうか? 実際、幼稚園の砂場で多くのことを学んでいたと思います。人生(そしてアジャイル)について。 昼寝の時間までのタイムボックス。こんなこともあったのでは? 砂場で「山を作りたいな~」「おもしろそうぼくも作る~」「わたしも~」みんなでモブワーク。 「ぼくは水をかける」「じゃあわたしは山を固めるね」「ぼくはトンネル掘るね~」「じゃあわたしは反対側から掘るわ」と自己管理。 「砂の固め方が弱くてつぶれちゃったね」「水かけすぎちゃってごめんね」ってふりかえり。 「じゃあ次はもっと大きい山にしてトンネル2本掘ろうか?」「楽しみ~♪」って次に何をするかの計画を相談。 そしてミルクを飲んでみんなでお昼寝♪ アジャイルに必要な知恵はすべて鮨屋のカウンターでも学んだ # みなさんお鮨はお好きでしょうか? お鮨屋さんではテーブル席で握りセットを頼みますか? それともやはりカウンターで一貫ずつ頼みますか? 例えば2人でテーブル席に座って、メニューで握りの「松セット」「竹セット」「梅セット」の写真と価格を見て、どれにするか選んだ場合、それぞれ一人前の個数やネタの種類や価格がはっきりしていて、全て揃ってからまとめて配膳(リリース)されます。なんだかウォーターフォールっぽいですね。 いきなりまとめて出てくるので、もし一貫食べて期待外れでもどうしようもありません。 ではカウンターに座った場合はどうでしょうか? 「飲み物は何にされますか?」みたいな感じで会話が始まり「今日は天然ひらめの縁側が入ってますよ~」ってことで「じゃあ塩炙りで」と答えつつネタケースに目を走らせる。 食べたいものから順番に食べたいタイミングで注文し、お腹がふくれて満足すればいつでもごちそうさま♪ もし期待外れなら早々に店を出ることもできます。 他のお客さんも、それぞれお気に入りのお鮨を頬張っています。 カウンターの中で握っている鮨職人さんは、周りの何人かのお客さんの注文を聞いて、基本的には注文された順番でお客さんに提供しますが、炙ったり多少時間がかかる注文は前後することもあります。 でもなぜかそれで揉めることはあまり無いんですよね~ 鮨職人さんの状況がお客さんたちに全部見えている(見える化されている)のが大きいんだろうなぁと思います。 もちろん聞こえる化も有効です。他のお客さんが注文してる時に割り込んで注文したりしないですよね?(自粛的排他制御?) 他のお客さんが「このウニおいしい~」って言ってるとウニが食べたくなるし、ネタケースを見てトロサーモンが少なくなってたら次に頼もうと思っていた寒ブリやめて、先にトロサーモンを頼んでみたり... 優先順位は自然に次々変わっていきますよね~ お客さんが食べる速度と、鮨職人さんの握る速度。だいたい3~5人ぐらいのお客さんを相手に握れる感じでしょうか。 ネタ等の都合により多少前後することはあっても、注文の順番は結構重要です。 それぞれのお客さんごとにプロダクトバックログがありつつ、それらを時系列でマージして1人の鮨職人さんが握るイメージ。 赤だしやお茶やお酒類などの注文は、鮨職人さん以外の店員さんが非同期で持ってきてくれることが多いですね。 注文するお客さんはそれぞれプロダクトオーナー。握ってくれる鮨職人さんは開発者。親方(店主)はスクラムマスターという感じでしょうか? (店を拡大したい場合、「カウンター5席&鮨職人さん1人」の単位で増やしていけば大規模アジャイル?) 「またこの鮨屋で食べたいなぁ」と思うか思わないかは、何で変わるでしょうか? # お鮨の味はもちろん、価格や店の雰囲気や鮨職人さんの対応等、全ての顧客体験が影響しますよね? ネタが新鮮で調理器具や食器も清潔で衛生上問題が無いというのはもちろん、頼んだお鮨の味が期待以上においしいという「体感できる価値」が品質。 鮨職人さんが特に気をつけるべきことはなんでしょうか? 食中毒等の発生防止や身だしなみ等、衛生面。 お客さんに快適に過ごしていただく言葉遣いや振る舞い等、接客面。 観光地の近くのお店であれば、英語での観光地紹介も多少はできた方がよさそうですね。 そしてもちろんカウンターに立つにふさわしい鮨職人としての調理スキルや品位。 でも、もしも「お客様と鮨職人さんの間に入ってやたら管理したがるタイプの親方がいる鮨屋」だったら? # 親方が気になることはなんでしょうか? 一貫の米粒の数が360~370になっていることでしょうか? 鮨職人さんが握ってお客さんの前に出したヒラメの握りを「ちょっとリリースしても問題がないか検査します」と言って横取りして米粒を数えたりしますでしょうか? 顕微鏡で一貫一貫、お客さんの目の前で確認し「菌はいません。安心して召し上がってください」と言いたいでしょうか? それでお客さんは安心しますでしょうか? 逆に不安になるのではないでしょうか? 鮨職人さんがカウンターでお客様に鮨を握っている「今」 親方が本当にやるべきことは何でしょうか? # もちろん食品衛生法や食品表示法、水産物の規格基準や労働安全衛生関連等の遵守が必要です。そしてHACCPに沿った衛生管理も2021年6月以降義務化されています。 特にお鮨屋さんは生魚を扱うため、食中毒リスクが高く、徹底した衛生管理が必須です。 でもお客さんの体験品質も重要ですよね。 完了の定義、準備完了の定義、受入基準のように分けるとこんな感じでしょうか? 完了の定義(店全体での品質基準)  法令・安全基準   食品衛生法遵守:調理場の衛生、従業員の健康管理、食材保管温度。   HACCP対応:工程ごとの危害要因分析と管理基準。   アレルゲン・原材料表示:テイクアウトや宅配時の必須情報。  店舗体験(ユーザーエクスペリエンス)   清潔なカウンターと器具。   鮨職人の所作が美しく、雰囲気が落ち着いている。   提供タイミングはお客様のペースに合わせる。 準備完了の定義(握り始める前に満たす必要がある条件)  ネタが規定温度で保管されている。  調理器具が洗浄・消毒済み。  鮨職人の手洗い・衛生確認完了。  注文内容が明確で、お客様の好み(わさび有無など)確認済み。 受入基準(鮨ネタごとの品質基準)  「ユーザーストーリーごとの受入条件」に近い考え方。  お客さん個人ごとにネタに対する期待値は異なりますが、それに対してどの程度超えられるのかが重要。  おいしさを数値的に表すのは難しいですね。同じ鯛でも産地や季節や個体によって異なりますし...  鯛(白身魚)   鮮度:透明感と弾力あり。   温度:冷蔵0~5℃。   切り付け:筋目を活かした美しい断面。  うなぎ   加熱:中心温度75℃以上で1分以上。   タレ:焦げすぎず均一な照り。   提供:温かいうちに出す。  ウニ   色:鮮やかな黄色~オレンジ。   匂い:海の香り。異臭なし。   盛り付け:崩れないよう丁寧に。 店内の清掃や、シャリやネタの準備は、もちろん暖簾を上げる前に完了させますが、開店から時間がたてば、ネタの補充や鮨職人の休憩なども必要になります。 一般の飲食店でも「大きな声で店員に指示する店主がいるお店はちょっと落ち着かない」と思われる方が多いと思いますが、落ち着いた静かなお鮨屋さんならなおさらですよね。 熟練した鮨職人さんは状況さえ把握すれば何をすべきかもわかります。ですので状況に「気付いてもらう」だけで十分です。もし何をすべきかわからない時はその場は親方が対応し、閉店後に反省会。 親方は静かに目や耳で店内の状況を観察しながら、目配せや目立たない振る舞いで、阿吽の呼吸で鮨職人さんたちとコミュニケーションをとり、絶えずよりよい顧客体験になるよう配慮されています。 この心地よさはおいしい日本酒も効いてるのかなと思いつつ、そろそろ満腹です。ごちそうさま♪ おわりに # 海外でも人気のお鮨。一人前の鮨職人さんになるには10年以上の修業が必要と言われていますが、最近では年齢や性別関係なく専門の学校で短期間に効率よく技術習得もできるようですね。 ここでもよりよく流れるように進化するという「コンストラクタル法則」が当てはまりそうです。 確かな技術の上で、さらにお客さんに満足いただけるように、自分たちで自由に工夫する。そのフィードバックはリアルタイムに返ってくる。 よりよい方法を見つけだそうとし続ける自由は誰も奪えない。 お鮨屋さんのカウンターで鮨職人さんが握ったお鮨を、お客さんがおいしそうに食べているのを見ていると、アジャイルについてもいろいろ考えてしまいます。 もうすぐ2026年。みなさまにとってよりよい年になりますように!
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第17日目の記事です。 1. はじめに:なぜ今、PBTを試すのか # プロパティベーステスト(以下 PBT)は、 仕様から抽出された「満たすべき性質(property)」を任意の入力・状態・操作系列に対して検証する テスト手法です。PBT は、従来の事例ベーステストと 相互補完的な関係にある ことが知られています [1] 。 正直、この説明だけでピンと来る人は多くないのではないでしょうか。私自身も、以前から興味はあったものの、満たすべき挙動の形式化が難しい、実装コストが高そうと感じて手を出せずにいました。 しかし、2025年11月17日に GA となった Kiro では、IDE 機能として 「プロパティベーステストによる仕様の正確性検証」 が導入されました [2] 。この新機能により、PBT 導入のハードルが下がったように見えたため、 実際に手を動かして体験してみることにしました。 Kiro の PBT 機能については、公式ドキュメント「 Correctness with Property-based tests 」を参照してください。 2. なぜ PBT なのか:EBT との決定的な違い # 書籍『実践プロパティベーステスト』 [3] では、PBT の特徴を次のように説明しています。 テストのための例をたくさん書いたり、コードに投げるためのランダムなデータを生成したりするのではなく、コードに潜む思いもよらなかった新しいバグを見つけるための手法である 具体例で考えてみましょう。従来の事例ベーステスト(EBT)では「1 と 3 を足したら 4 になる」のような特定の例を確認します。一方 PBT では「どんな数字を 2 つ足しても、順番を変えて足した結果と同一になる」という普遍的な性質を定義します。 左図は EBT、右図は PBT のイメージです。EBT はテストケースを手動で作成し検証する一方、PBT はプロパティを定義してランダムな入力で検証します。 つまり、EBT は 予測可能なバグ に強く、PBT は 予測できなかったバグ を発見できる可能性があります。この違いは、後半の UI 表示テストで実感することになります。 3. 題材に「チケット管理」を選んだ理由 # Kiro + PBT の解説記事として、「部屋移動ゲーム」を題材にした Medium 記事 [4] があります。 このゲームは、以下の構造により PBT に非常に向いています。 状態 × 操作 × 不変条件(Property) 同じ構造は、 業務アプリケーション にも頻出すると考え、 今回はその一例である、「チケット管理」を題材とすることにしました。 チケット管理には以下の特徴があります。 状態遷移の制約 終端状態の不変条件 再実行・順序依存性 異常系の保証 これらは 事例ベーステストでは拾いにくいが、PBT と相性が良い性質 です。 そのため、実務への応用を意識してチケット管理を題材に選びました。 4. 仕様:極めてシンプルなチケット管理 # 今回は、あえて仕様を極限まで単純にしました。 チケットの状態 # Status = { Open, InProgress, Done } 上記の3つのみ 許可される状態遷移 # Open → InProgress InProgress → Done 上記以外の遷移はすべて無効 Done は終端状態であり、どの操作も受け付けない この最小仕様を、Kiro に入力していきます。 5. Kiro による Property-Based Testing の実行 # 5-1. 要件文書(requirements.md)の生成 # まず、Kiro IDE に以下の要件を入力します。 - GUI ベースの簡易チケット管理アプリを作成する - 永続化は不要、検証用の最小アプリとする - チケットは必ず3状態のいずれかを持つ - 状態は「未対応→対応中→完了」の一方向のみ遷移可能 - 完了状態から他状態へは遷移できない - 状態更新はボタン押下で切り替える - 機能は作成・一覧・状態更新に限定 - 状態遷移ルールを不変条件として PBT で検証したい 以下のような要件文書が出力されました。 受入基準が EARS 記法 [5] (「Easy Approach to Requirements Syntax: 要件定義の構文ルール)で明確に言語化 されています。 要件文書の抜粋 ### 要件3 **ユーザーストーリー:** ユーザーとして、チケットの状態を更新したい。作業の進捗を正確に反映するため。 #### 受入基準 1. WHEN ユーザーが未対応チケットの状態更新ボタンを押した時 THEN Ticket_System SHALL そのチケットを対応中状態に変更する 2. WHEN ユーザーが対応中チケットの状態更新ボタンを押した時 THEN Ticket_System SHALL そのチケットを完了状態に変更する 3. WHEN チケットが完了状態の時 THEN Ticket_System SHALL 状態更新ボタンを無効化する 4. WHEN 状態遷移が実行される時 THEN Ticket_System SHALL 未対応から対応中、対応中から完了の順序のみを許可する 5. WHEN 完了状態のチケットに対して状態変更が試行された時 THEN Ticket_System SHALL その操作を拒否し現在の状態を維持する 特に注目したのは、無効な遷移は拒否される、完了状態では操作できない、状態遷移の順序制約といった 後続の Property 定義につながる記述 が明確になっていた点です。 5-2. 設計文書(design.md)と Property 定義 # 設計書生成時、Kiro は次のことを宣言しました。 設計文書の前半部分を作成しました。次に、受入基準の分析を行ってから正確性プロパティを定義します。 出力結果の抜粋は以下でした。 Property が設計成果物として明示的に出力 されています。 設計文書の抜粋 ### プロパティ1: チケット作成時の初期状態 *任意の*有効なタイトルに対して、新しいチケットを作成すると、そのチケットは常に未対応状態で作成される ### プロパティ2: チケット作成時のリスト追加 *任意の*チケットリストと有効なタイトルに対して、新しいチケットを作成すると、リストのサイズが1増加し、新しいチケットがリストに含まれる ### プロパティ3: チケット表示内容の完全性 *任意の*チケットに対して、そのチケットをレンダリングした結果には、タイトルと現在の状態情報が含まれる ### プロパティ4: 有効な状態遷移の実行 *任意の*チケットと有効な遷移(未対応→対応中、対応中→完了)に対して、状態更新操作を実行すると、チケットの状態が期待される次の状態に変更される ### プロパティ5: 無効な状態遷移の拒否 *任意の*チケットと無効な遷移に対して、状態更新操作を実行しても、チケットの状態は変更されない ### プロパティ6: 完了状態での操作無効化 *任意の*完了状態のチケットに対して、状態更新ボタンは無効化され、操作が実行できない ### プロパティ7: システム不変条件の維持 *任意の*操作(作成、状態更新)の実行後、システム内の全てのチケットは有効な状態(未対応、対応中、完了のいずれか)を持つ 結果として、状態遷移だけでなく、初期状態の不変性、表示内容の完全性、システム全体の不変条件など、 人が明示的に頼んでいない Property まで含めて 7 件 が定義されました。 5-3. 実装計画(tasks.md)と Property のトレーサビリティ # 以下のような実装計画が出力されました。 実装計画の抜粋 - [ ] 2. ビジネスロジック層 - [ ] 2.1 状態遷移ロジックのテスト作成 - **プロパティ4: 有効な状態遷移の実行** (要件 3.1, 3.2, 3.4) - **プロパティ5: 無効な状態遷移の拒否** (要件 3.5, 4.2) - _要件: 3.1, 3.2, 3.4, 3.5, 4.2_ - [ ] 2.2 状態遷移関数の実装 - 有効な遷移の判定ロジック - 状態更新処理の実装 - テスト実行と通過確認 - _要件: 3.1, 3.2, 3.4, 3.5_ tasks.md では、Property、要件、テスト、実装タスクが対応付けられており、 「なぜこのテストが存在するのか」 が追跡可能になっていました。 また、TDD によるテストファーストの原則を採用したため、以下の流れが自然に形成されています。 テスト作成 → 実装 TDDを用いたAI駆動開発については、以下の記事も参照してください。 https://developer.mamezou-tech.com/blogs/2025/11/28/qdev-aidd-spec-kit/ 5-4. 実装と PBT の具体例 # 状態遷移の Property は、fast-check [6] を用いた PBT として実装しました。 記述されたテストコードサンプルを提示します。任意のタイトル、任意の有効状態に対して、 遷移の性質が必ず成立すること を検証しています。 TypeScript と fast-check によるテストコード例 test('未対応から対応中への遷移が正しく実行される', () => { fc.assert( fc.property(validTitleArb, (title) => { const ticket: Ticket = { title, status: TicketStatus.PENDING }; // 未対応から対応中への遷移は有効 const isValid = isValidTransition(ticket.status, TicketStatus.IN_PROGRESS); const nextStatus = getNextStatus(ticket.status); expect(isValid).toBe(true); expect(nextStatus).toBe(TicketStatus.IN_PROGRESS); }), { numRuns: 100 } ); }); 5-5. 動作確認 # 完成した画面にいくつかチケットを登録したり、エラーを発生させてみて、動作確認を行いました。 6. まとめ:Kiro で PBT を実装して分かったこと # 正直に言うと、 状態遷移だけでは PBT の凄さは分かりにくい と感じました。 今回の状態遷移のルールは単純なため、事例ベーステストでも十分に確認できてしまうからです。 しかし、UI 表示に関する Property で状況が一変します。 想定外だった発見 # チケットタイトル表示の Property テストでは、 連続する空白が HTML によって正規化されることや、 前後の空白・空白のみのタイトルの扱いが曖昧であることなど、 仕様として意識していなかった問題 が検出されました。 これらは、手動テストや事例ベーステストでは、まずテスト観点として挙がらなかったと思います。 この経験から、 PBT の価値は「網羅的に試すこと」ではなく、 「人が想定していなかった入力や前提を炙り出すこと」 にあると実感しました。 7. おわりに # Kiro を使うことで、以下のことを実感しました。 PBT 導入の心理的・実装的ハードルは確実に下がった。 Property を中心に仕様・設計・テストをつなげられる。 今回は単体レベルでしたが、 結合テストやシステムテストでの活用 も価値がありそうです。 今回使用したリポジトリは以下で公開しています。 (予告なく公開停止する可能性があります) https://github.com/hironori-maruoka/kiro-pbt-sample Takuto Wada. Property-based Testing の位置付け / Intro to Property-based Testing . Speaker Deck. ↩︎ Amazon Web Services. Kiro:生成 AI で IDE とコマンドライン機能を強化する新ツールが一般提供開始 . AWS ブログ, 2025. ↩︎ Fred Hebert, Leonid Rozenberg. 実践プロパティベーステスト ― PropErとErlang/Elixirではじめよう . ラムダノート, 2023. ↩︎ Matheus Evangelista. Building Smarter with Kiro: A Hands-On Look at Property-Based Testing . Medium, 2025. ↩︎ Alistair Mavin. EARS: The Easy Approach to Requirements Syntax . ↩︎ fast-check. fast-check . ↩︎
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第16日目の記事です。 1. はじめに # 9月に Kiroを使った仕様駆動開発の記事 を執筆してから、約3か月経ちました。 その間に、生成AIを取り巻くエコシステムやツールは驚くべき速度で進化を続けています。 最近話題のGeminiも、リリース当初と比べると、業務利用を視野に入れられるレベルまで急速に成熟していると感じています。 今回はGitHub が提供する仕様駆動開発(Spec-Driven Development; SDD)のツール「 Spec Kit 」と、Model Context Protocol(MCP)に対応した AI クライアントである Gemini CLI を組み合わせて、インフラストラクチャを生成する開発手法を試してみます。 前回の記事では「Kiro × Claude」で SDD + Terraform を扱いました。 本記事ではこれを「Spec Kit × Gemini」に置き換えたうえで、特にビジネスの現場では重要となるコスト最適化・コスト把握を AWS Pricing MCP Server を使って行えるようにします。 これらを組み合わせることで、設計思想(Why)から具体的な実装(What)、そしてコードとアーキテクチャの品質保証までを一気通貫で行う、インフラ構築フローをSpec Kitでどこまで実現できるのかを検証していきます。 2. 記事のゴール # 本記事のゴールは次の通りです。 Spec Kit を使いこなせるようになる GitHub Spec Kit の 4 段階プロセス(Specify, Plan, Tasks, Implement)に沿って、インフラの仕様を定義し、具体的なコード(IaC)に落とし込む流れを体験します。 SDD を踏まえたインフラ構築フローを理解する 設計思想(Why)から実装(What)、そして品質保証までを一気通貫で行う、AI を活用した新しいインフラ構築のやり方を学びます。 コストを含めた品質保証を実現する方法を知る AWS Pricing MCP Server を組み合わせ、Terraform コードから構築されるインフラのランニングコストを見積もりつつ、仕様レベルでコスト制約を織り込むアプローチを理解します。 実践的なインフラを構築できるようになる 上記フローを通じて、S3 と CloudFront を用いた静的ウェブサイトと、そのアクセス状況を可視化できる CloudWatch メトリクスを持つ、実践的なリソースを構築できる状態を目指します。 3. 事前知識 / 前提 # 本記事は、次のような読者を想定しています。 対象読者 インフラ構築・運用や、クラウド環境の設計に興味がある方 SDD(Spec-Driven Development)や IaC(特に Terraform)に興味がある方 生成 AI を用いた開発フローを、インフラ領域でも試してみたい方 本記事で扱わないこと GitHub Spec Kit 自体のインストール手順や細かな設定方法 SDDにおける細かいプロンプト指示の内容 MCP Server(例: AWS Pricing MCP Server)のセットアップ方法の詳細 生成 AI(Gemini など)のアカウント作成や各種設定の手順 Terraform や AWS のコードの解説・公開 実施環境 Mac (M1) / macOS Sequoia 15.5 [1] VSCode: 1.107.0 Gemini CLI: 0.20.2 Gemini 2.5 系列モデル(Flash / Pro など) Spec Kit v0.0.90 これらの前提知識・環境は事前に整っているものとして、本記事では「どう組み合わせて活用するか」という観点に焦点を当てます。 4. 「Vibe-Coding」の限界と見過ごされるコスト # AIコーディングエージェントの能力は日々向上していますが、その利用方法の多くは、開発者の曖昧な指示(Vibe)に依存した「Vibe-Coding」に留まっています。 このアプローチでは、コードがコンパイルしなかったり、開発者の意図を正確に反映しなかったりといった、目先の生成エラーに注目が集まりがちです。 とりわけ深刻なのは、一見正常に動作しているコードが、Vibe-Coding によって大量に生まれてしまう点です。 これらのコードは、次のような「見過ごされがちなコスト」を内包しています。 属人化とブラックボックス化 生成時のプロンプトや前提条件が十分に残らないため、「なぜその設計になっているのか(Why)」がコードから読み取れません。 結果として、他の開発者には触りづらいブラックボックスとなり、修正・改善のたびに大きな認知コストが発生します。 アーキテクチャの一貫性低下 その場その場の Vibe-Coding で機能追加を続けると、システム全体のアーキテクチャが当初の設計意図から徐々にずれていきます。 IaC の場合、環境ごとに微妙に異なる定義が増え、「どれが正なのか」を後から整理・リファクタリングするコストが跳ね上がります。 レビュー・監査コストの増大 レビュー担当者は、コードを読むたびに「そもそも何のための変更か?」を会話履歴やコミットメッセージから逆算する必要があります。 セキュリティやコンプライアンスの観点では、どのコードがどの前提・ポリシーに基づいて生成されたかが追いきれず、チェックに時間がかかります。 このように、Vibe-Codingは目先の生産性を一時的に向上させるかもしれませんが、長期的な保守性や品質、セキュリティの観点では、むしろコストを増大させる要因になりえます。 5. 仕様駆動開発(SDD)の哲学 # Vibe-Codingがもたらす長期的な課題に対し、GitHubは公式ブログでSpec Kitを発表し、新たな開発パラダイムとして「仕様駆動開発(SDD)」を提唱しました。 GitHubは、その哲学について次のように述べています。 "That’s why we’re rethinking specifications — not as static documents, but as living, executable artifacts that evolve with the project. Specs become the shared source of truth. When something doesn’t make sense, you go back to the spec; when a project grows complex, you refine it; when tasks feel too large, you break them down." (だからこそ私たちは仕様を再考しています。それは静的なドキュメントではなく、プロジェクトと共に進化する生きた、実行可能な成果物として。仕様は共有された真実の源となります。何か理解できないことがあれば仕様に戻り、プロジェクトが複雑になればそれを洗練させ、タスクが大きすぎると感じたら分解するのです。) この引用が示すエッセンスは、次の2つの重要な概念に集約されます。 仕様は「生きた実行可能な成果物」である 一度作られて陳腐化する静的なドキュメントではなく、プロジェクトの進化に合わせて更新され続け、ツールやAIがコードを生成・テスト・検証する際に直接利用する「実行可能」なファイルそのもの、という考え方です。 仕様は「信頼できる唯一の情報源」である コードがどう動作すべきかを定義した「契約」として機能します。開発者は迷ったときに立ち戻る原点となり、AIエージェントにとっては振る舞いを決定する上での絶対的な拠り所となります。 このように仕様と実装の一貫性を強く保つことこそが、Vibe-Codingの混沌に秩序をもたらし、AIの力を最大限に引き出す鍵である、というのがGitHubの示す方向性です。 6. GitHub Spec Kitの役割 # Spec Kit は、この SDD を AI コーディングエージェント(GitHub Copilot、Gemini CLI、Claude Code など)のワークフローに取り込むための オープンソースのツールキット です。 /speckit.specify :仕様(Spec)を作成する /speckit.plan :技術スタックやアーキテクチャ方針を整理する /speckit.tasks :作業単位に分解したタスクリストを作る /speckit.implement :タスクに基づいて実装を進める といったコマンドを通じて、「仕様 → 計画 → タスク → 実装」の流れを構造化し、AI に対しても一貫したコンテキストを与えられるようにします。 7. Spec Kitの構造化された開発ワークフロー # Spec Kit の大きなメリットは、「どの生成 AI を使うか」に依存しすぎず、共通のワークフローで開発を進められる点です。 現実には、複数の生成 AI を同時に利用することはコスト面で難しい場合も多く、組織として利用できる AI が限定されるケースもありますが、Spec Kit のワークフローはそうした制約の中でも再利用しやすい構造になっています。 Spec Kitは、AIとの協調作業を円滑にするため、次の4段階の明確なプロセスを定義しています。各フェーズは、特定のコマンドを通じて実行されます。 1. Specify(仕様作成)― WhatとWhyの定義 /speckit.specify コマンドを使用し、技術スタックではなく、 ユーザーのジャーニー、期待される成果、成功基準 に焦点を当てて仕様(Spec)を定義します。 仕様は、利用者や要件が変わるたびに更新される「生きた成果物」として扱われ、インフラの構成もこの Spec を起点に設計されます。 2. Plan(計画作成)― 制約の組み込み /speckit.plan コマンドで技術的な方向性を決定します。 技術スタック、アーキテクチャ、コンプライアンス要件、性能目標 など、エンタープライズ制約を AI エージェントに提供し、 包括的な技術実装計画を plan.md として生成させます。ここで MCP Server からの制約も統合されます。 3. Tasks(タスク分解)― レビュー可能な単位へ /speckit.tasks コマンドを使用し、Plan に基づき、 独立して実装・テスト可能な小さな作業単位 (レビュー可能な差分)に分解します。 依存関係や並列実行可能なタスク [P] が明示されることで、「どこから手を付けるべきか」が分かりやすくなります。 4. Implement(実装と検証)― 焦点を絞った変更 /speckit.implement コマンドで、エージェントがタスクリストに従って実装します。開発者は大規模なコードの塊ではなく、 特定の問題を解決する 焦点の絞られた変更 をレビューします。これにより、AI が生成した変更を人間が理解・検証しやすい単位に保つことができます。 7.1. Spec KitとKiroのプロセス対応 # Spec Kit と AWS Kiro はどちらも SDD を支援するツールですが、フェーズ名や成果物の切り方が少し異なります。本記事では、おおよそ次の対応関係を意識して解説します。 観点 Spec Kit のフェーズ / コマンド 主な成果物(Spec Kit) 参考: Kiro のプロセス / ファイル例 要件・意図の明確化 Specify / /speckit.specify プロダクトの目的・前提・スコープなどを整理した仕様ファイル(例: spec.md ) Requirements フェーズ / requirements.md 実現方式の検討 Plan / /speckit.plan アーキテクチャ方針・技術選定・インフラ構成などをまとめた計画ファイル(例: plan.md ) Design フェーズ / design.md 作業単位への分解 Tasks / /speckit.tasks 実装・テスト可能な単位に分解されたタスクリスト(例: tasks.md ) Tasks / Implementation のタスク一覧 実装の実行・検証 Implement / /speckit.implement タスクリストを基にしたコード・テスト・設定ファイル等の変更 Implementation フェーズ全体(フックやタスク実行) ※ Kiro は「Requirements → Design → Implementation」の 3 フェーズ構成で、Implementation の中にタスク実行やコード生成が含まれます。一方、Spec Kit は Tasks と Implement を分離することで、「タスク分解」と「実装実行」を明示的に分けている点が特徴です。 8. AWS Pricing MCP Server によるコスト制約の実現 # 本章では、 AWS Pricing MCP Server の役割と、それを Spec Kit のワークフロー、とくに Implement フェーズのあとに統合することで、コストの観点からどのように制約をかけられるか解説します。 8.1. Model Context Protocol (MCP) の役割 # MCP および MCP Registry を利用することで、Gemini CLI などの MCP 対応クライアントから AWS Pricing MCP Server といった外部サービスに接続できます。 Spec Kit の「Spec / Plan / Tasks / Implement」というワークフローの後にMCPを追加することで、外部ツールからの制約や推奨事項(本記事では主にコスト情報)を自然に統合します。 8.2. AWS Pricing MCP Serverによるコスト把握 # AWS Pricing MCP Server は、AWS の料金情報にアクセスするための MCP Server です。 Gemini CLI から呼び出すことで、サービス種別・リージョン・利用想定などを入力し、概算の料金を問い合わせることができます。 Spec Kit の文脈では、Implement フェーズで生成された Terraform コードやアーキテクチャ案に対してコストを問い合わせることで、 どの構成が月額コストを抑えられそうか トラフィック増加時にどの程度コストがスケールしそうか どのリソースがコストのボトルネックになりそうか といった観点を、AI エージェント経由で評価させることができます。 8.3. Implementフェーズ後のコスト評価への統合 # /speckit.implement コマンドや、Implement フェーズで Terraform コードが生成・デプロイされたあとに、AWS Pricing MCP Server を呼び出し、実際のリソース構成に基づくコスト情報や制約・推奨事項を取得します。 これにより、パフォーマンスや可用性などの非機能要件に加え、想定トラフィックに対するランニングコストの見積もりを踏まえて、アーキテクチャやコードをリファインするループを回せるようになります。 Implement実行後は次のような流れで進めます。 リソース構成からコストを試算する パフォーマンスとコストのバランスを評価する 必要に応じてPlan/Specを修正する レイヤ構造として見ると、次のような関係になります。 flowchart LR subgraph Workflow["Spec Kit ワークフロー"] S["spec.md<br>What / Why"] P["plan.md<br>How + 制約反映"] T["tasks.md"] I["implement.md / 構築"] S --> P --> T --> I end subgraph Artifact["成果物"] A["Artifact(Terraform コードなど)"] end subgraph MCP["MCP サーバ群"] M1["AWS Pricing MCP Server"] end I -->|Apply| A I -->|コスト確認| M1 M1 -->|コスト最適化| I 9. 実践:CloudFrontとS3で構築する静的サイトとアクセス可視化 # 本章では、Spec Kit と Gemini CLI を用いて、S3 と CloudFront による静的ウェブサイトを構築し、そのアクセス状況が CloudWatch で可視化されるまでを追います。 まずはSDDで静的ウェブサイトを構築し、その後、CloudWatch でアクセス状況を可視化できるダッシュボードの作成という、仕様を追加します。 ここでのゴールは、単にインフラを構築するだけでなく、CloudWatch コンソールでリクエスト数のグラフが動くのを確認するところまでを実践することです。 これにより、意図(Spec)が実際の運用(Monitoring)に繋がる流れまでを意識するためです。 flowchart LR subgraph Client["クライアント"] U[ユーザー/ブラウザ] end subgraph AWS["AWS インフラ(Terraform で構築)"] CF[CloudFront ディストリビューション] S3[S3 バケット(静的サイト)] CW[CloudWatch メトリクス] end U -->|HTTPS リクエスト| CF CF -->|オリジン要求| S3 CF -->|アクセス状況を送信| CW 9.1. Gemini.mdの設定と、specify init - プロジェクトの作成 # specify init <PROJECT_NAME> でプロジェクトを作成します。 作成と同時に、次のような階層でファイルが作成されます。 原則、生成されるファイルはすべて英語となっているため、次のプロンプトと Gemini.md を設定することを推奨します。 # Gemini.md ## 使用言語 日本語を利用してください。 英語の場合は日本語に訳してください。 # プロンプト .specify配下のtemplatesの各ファイルを、日本語に直してください。 +---.gemini | \---commands | speckit.analyze.toml | speckit.checklist.toml | speckit.clarify.toml | speckit.constitution.toml | speckit.implement.toml | speckit.plan.toml | speckit.specify.toml | speckit.tasks.toml | speckit.taskstoissues.toml | +---.specify | +---scripts | | \---bash | | check-prerequisites.sh | | common.sh | | create-new-feature.sh | | setup-plan.sh | | update-agent-context.sh | | | \---templates | agent-file-template.md | checklist-template.md | plan-template.md | spec-template.md | tasks-template.md (その他多数) 9.2. Constitution ― 「規約」を定義 # まず、 /speckit.constitution を実行します。 Gemini では「憲法」と訳されますが、個人的には訳し方が好みではなかったので、「規約」とするようにこの constitution も設定しました。 簡単な指示を出すと、 <workspace-name>/.specify/memory/constitution.md が作成されます。 constitution は「このプロジェクトでは何を必須(/許容)にするか」を先に固めるためのもので、後続のSpec/ Plan/Tasksの品質を安定させる土台になります。 ここでは実際に作成した constitution.md から、特に重要な原則だけ抜粋します。 (読みやすさのため一部要約) # spec-kit-entry-s3-static-site 規約 ## Core Principles 1. セキュリティ: 最小権限(開発環境はアクセスログ取得不要) 2. 状態管理: S3などのリモートバックエンド(ロックは必須としない) 3. IaCのモジュール化: 機能単位で独立モジュール 4. 命名規則: 一貫した命名規則を必須化 5. 静的解析とテスト: `tfsec` / `tflint` + MCPチェック必須 7. ドキュメントの言語: 日本語で記述 9. コード内言語: 変数の `description` 等も日本語 9.3. Specify ― 「何を」作るかを定義 # まず spec.md を作成し、「セキュアで高速な静的ウェブサイト」という目的を定義します。 成功基準としては、例えば次のような条件を明記します。 CloudFront のエンドポイントにアクセスすると、静的コンテンツが HTTPS で配信されること spec.md は(特に非機能要件まで含めると)それなりの分量になるので、 ここでは「静的サイトを S3 + CloudFront でホスティングする」例として、実際に作成した spec.md から雰囲気が伝わる箇所だけ抜粋します。 (読みやすさのため一部要約) # 機能仕様書: S3静的サイトホスティング ## ユーザーシナリオとテスト *(必須)* ### ユーザーストーリー1 - S3バケットのプロビジョニング (優先度: P1) - 受け入れ: 静的サイトホスティングが有効なS3バケットが作成される ### ユーザーストーリー2 - CloudFrontによるコンテンツ配信 (優先度: P2) - 受け入れ: CloudFront URLへHTTPSアクセスすると `index.html` が表示される ## 機能要件 (以下、FR) - FR-003: S3バケットへの直接のパブリックアクセスはブロックされる - FR-005: CloudFrontはオリジンアクセス制御(OAC)を使用してS3にアクセスする ## 成功基準 *(必須)* - SC-001: CloudFront URL経由で `index.html` がHTTPSで5秒以内に表示される 9.4. Plan ― 「どう」作るかを計画 # /speckit.plan 実行時には、技術スタック(S3, CloudFront, OAC)やセキュリティ・運用要件など、主に非機能要件とアーキテクチャ制約を与えます。 この段階では、まだ具体的な Terraform コードが存在しないため、AWS Pricing MCP Server を直接呼び出して厳密なコスト試算は行いません。 代わりに、トラフィック量の見込みやざっくりとした予算感などを Spec / Plan に書き込み、後続の Implement フェーズで生成されるコードに対してコスト評価する前提を整えます。 これにより生成される plan.md には、例えば次のような方針が盛り込まれます。 S3 バケットはプライベートとし、直接のパブリックアクセスは許可しない CloudFront からのみアクセスできるよう、OAC(Origin Access Control)を利用する Plan は「どう作るか」をレビュー可能な形に落とし込むのが目的なので、チェックリストや構成の方針が(ある程度の粒度で)明文化されます。こちらも plan.md から抜粋します。 ( specs/<feature>/ のようなディレクトリの配置方針もここに書かれます)。 # 実装計画書: S3静的サイトホスティング ## 規約チェック - [x] 1. セキュリティ: 計画は最小権限の原則に準拠しているか?(S3パブリックアクセスを禁止し、OACを利用) - [x] 2. 状態管理: 対象環境に対して、ロック付きのリモート状態管理が構成されているか?(Terraform Cloud/S3バックエンドを前提) - [x] 3. IaCのモジュール化: 設計は再利用可能なモジュールを促進しているか?(s3, cloudfrontでモジュール分割を計画) ## プロジェクト構造 specs/001-s3-cloudfront-site/ ├── plan.md ├── spec.md └── checklists/requirements.md IaCプロジェクトとして、以下のTerraform標準レイアウトを採用します。 構造の決定: 上記のTerraformモジュール構成を採用し、環境(`environments`)と再利用可能なコンポーネント(`modules`)を明確に分離します。これにより、憲法の「モジュール化」の原則を遵守し、将来的な環境追加(例: 本番環境)にも容易に対応できます。 ```text . ├── environments/ │ └── dev/ # 開発環境用のルートモジュール │ ├── main.tf │ ├── variables.tf │ ├── outputs.tf │ └── backend.tf # リモートバックエンド設定 │ └── modules/ ├── s3-static-site/ # S3バケットと関連リソースを管理するモジュール │ ├── main.tf │ ├── variables.tf │ └── outputs.tf │ └── cloudfront-cdn/ # CloudFrontディストリビューションを管理するモジュール ├── main.tf ├── variables.tf └── outputs.tf 9.5. Tasks ― 実装タスクへ分解 # Plan に基づき、Gemini が具体的なタスクリストを生成します。例としては次のようなタスクが含まれます。 S3 バケットの作成と基本ポリシーの設定 CloudFront ディストリビューションの作成(オリジンに S3 を指定) OAI(Origin Access Identity)の作成と、S3 バケットポリシーへの組み込み 依存関係を考慮しながらタスクが並べられることで、Terraform 実装の順序も明確になります。 以下が、実際に作成された tasks.md からの抜粋です。 (フェーズ分割、依存関係、独立したテスト観点などがセットで出てくるのが特徴です。必要に応じて要約しています) # タスク: S3静的サイトホスティング > `[P]` が付いたタスクは、並列に実行できます。 ## フェーズ1: セットアップ - [x] T001 [P] `environments/dev` ディレクトリを作成する - [x] T002 [P] `modules/s3-static-site` ディレクトリを作成する - [x] T003 [P] `modules/cloudfront-cdn` ディレクトリを作成する ## フェーズ3: ユーザーストーリー1 (P1) - [x] T010 [US1] S3バケット + Website設定 + パブリックアクセスブロックを定義する ## フェーズ4: ユーザーストーリー2 (P2) - [x] T015 [US2] OAC と CloudFront ディストリビューションを定義する ## フェーズ5: ユーザーストーリー3 - TerraformによるIaC化 (優先度: P3) 目標: インフラ定義の品質と再現性を保証するためのプロセスを確立します。 独立したテスト: `terraform plan` が差分なく完了すること、静的解析ツールが警告を出さないことを確認できます。 9.6. Implement ― 実装と動作確認 # Gemini が各タスクを Terraform コードとして実装し、デプロイを行います。 デプロイ後、CloudFront のエンドポイントに数回アクセスし、静的コンテンツが HTTPS で配信されることを確認します。 この確認作業をもって、最初の仕様で定義した成功基準が満たされたことを検証します。 さらに、生成された Terraform コードやデプロイされたリソース構成を入力として、AWS Pricing MCP Server を呼び出し、ランニングコストの概算を取得します。 これにより、 想定していた予算内に収まりそうか ボトルネックになりそうなサービスはどこか といった観点を踏まえて、Plan / Tasks / Implement を再度見直すフィードバックループを回すことができます。 9.7. 要件・リソースの追加 # さて、前項で静的サイトの作成・公開は無事に完了しました。 ここからは、サイトのアクセス状況を CloudWatch で可視化する要件を追加してみます。 追加する仕様(成功基準の例)は、次のようになります。 アクセス後、CloudWatch の CloudFront メトリクスでリクエスト数がカウントされること これに対応して、Plan / Tasks には例えば次のタスクを追加します。 CloudWatch で確認するためのメトリクスやログ設定の有効化 Implement フェーズでは、これらのタスクに基づいて Terraform コードを拡張し、再度デプロイを行います。 デプロイ後、CloudFront のエンドポイントにアクセスし、CloudWatch の CloudFront メトリクス画面で「Requests」グラフにデータが反映されることを確認します。 この確認作業をもって、追加した仕様が満たされたことを検証します。 10. SDD が特に有効な 3 つのシナリオ # Spec Kit の背景にある Spec-Driven Development は、特に次の 3 パターンで役立つと GitHubの公式ブログ で提示しています。 Greenfield(ゼロからの新規開発) これから新しくシステムを作る場面では、「コードから考える」のではなく「意図と制約から考える」ことが重要です。 仕様(Spec)をきちんと書き、Plan / Tasks / Implement を順に進めることで、アーキテクチャの迷子になりにくくなります。 Feature work(既存システムへの機能追加) 既存のインフラやシステムに対して機能追加を行う場合は、影響範囲の洗い出しや既存ポリシー・命名規約との整合といった点を、Spec / Plan フェーズで明文化しておくことで、レビューやロールバックがしやすくなります。 仕様駆動開発が最も威力を発揮する分野だと GitHub 側が推しています。 Legacy modernization(レガシーの近代化) 手作業やスクリプトで運用されているレガシーシステムをモダンなアーキテクチャや IaC に移行する際、暗黙知になっている仕様や制約を Spec / Plan として言語化しながら段階的に置き換えていくのに、SDD は相性が良いとされています。 11. 利用してみた感想 # 実際に Spec Kit を Kiro と比較しながら使ってみて、次のような違いを感じました。 レビューの厳格さ Spec Kit は、仕様・計画・タスク・実装の各段階でこまめに人間にレビューを求めてきます。AI がまとめた内容を読み込みながら一歩ずつ進めるスタイルのため、手戻りは少ない一方で、進行のテンポはややゆっくりになります。 Kiro の進め方 Kiro は、AI 側がある程度まとめた状態まで一気に進めてから人間にレビューを返してくる印象で、少ないやり取りでテンポよく進めたい場合に向いています。 一方で、今回の検証では次のような点も気になりました。 Constitution(規約)を完全には守らず、たまにルールから外れた提案をしてしまうことがある Kiro と比べると、SDD の各フェーズ(Requirements / Design / Implementation)の境界が曖昧になりやすい これらは、Spec Kit と組み合わせる AI エージェントの実装やプロンプト設計にも依存する部分が大きいと感じています。 総じて、 ゼロから新しいシステムをじっくり設計したい(Greenfield) チームで仕様・タスクまでをきちんとレビューしながら進めたい といったケースでは、Spec Kit の方がフィットしやすいと感じました。 一方で、 まずは SDD を軽めに体験してみたい AWS プロダクト中心の構成で、Kiro のテンプレートやガイドに乗って進めたい といった場合は、Kiro を選ぶ方が導入のハードルは低いと思います。 12. 実行可能な仕様の実現例 # 本記事で扱った S3 静的サイトの例では、次のような形で「実行可能な仕様」が実現されます。 spec.md ユースケース・制約・非機能要件をまとめたドキュメント plan.md Spec で定義した要件や、非機能要件・アーキテクチャ制約を整理した方針 使用する AWS サービスや Terraform モジュール構成の決定 tasks.md 実装・テスト・監査タスクに分解された ToDo リスト Terraform コード これらの成果物から導かれた、具体的な IaC 実装 Implement 後に AWS Pricing MCP Server を用いてコスト評価する対象 重要なのは、 コード単体ではなく、「仕様 → 計画 → タスク → コード」の流れ全体が一貫していること です。Spec Kit は、この流れを AI を介して半自動化しつつも、人間がレビューしやすい単位に分割してくれる点に価値があります。 13. 今後の発展と課題 # 最後に、今後の展望と課題を簡単に整理します。 13.1. 期待できる発展 # IDE / エディタとの連携強化 現在はAI内でコマンドを叩くのに対し、VS Code などで /speckit.* コマンドを直接叩けるようになれば、より自然なワークフローになるはずです。 複数実装案の比較 同じ Spec / Plan から複数の実装パターン(例:S3 単体 / S3 + CloudFront / S3 + CloudFront + WAF)を生成し、コスト・運用性・セキュリティなどで比較する、といった使い方も考えられます。 大規模プロジェクトへの適用 マイクロサービス群や複雑なネットワーク構成など、より大きなスコープでの Spec-Driven Development への展開も今後のテーマになりえます。 13.2. 現時点での課題・注意点 # MCP エコシステムの成熟度 MCP や各 MCP Server はまだ進化の真っ最中であり、仕様変更やバージョンアップの影響を受けやすい段階です。 セキュリティと権限管理 MCP Server の実装やデプロイ方法によっては、資格情報の取り扱いやアクセス制御に注意が必要です。 「人間のレビュー」をどう組み込むか 最終的な責任は人間にあります。Spec / Plan / Tasks / コードの各段階で、どのようなレビューを行うかをチームとして設計することが重要です。 14. 最後に # 本記事で扱った内容から、大事なポイントを一言で表すと、 「コードが起点」から「仕様・意図が起点」へと軸足を移す ということだと考えています。 コードはあくまで「仕様と意図を具現化した結果」であり、AI や MCP Server はその橋渡しを支援する存在として捉えると、Spec-Driven Development のコンセプトが理解しやすくなるはずです。 また、今回 Spec Kit を利用してみて、SDD を支援するツールそのものだけでなく、組み合わせる AI や MCP Server によって得意な領域やワークフローが変わることも実感しました。今後も、インフラ構築や運用の現場での適用例を増やしながら、より良い組み合わせ方を探っていきたいと思います。 仕様駆動開発という考え方自体は、特にインフラ構築において長期的な保守性と品質を支える重要なアプローチだと感じています。 本記事が、読者の皆さまが自分たちのプロジェクトで SDD や Spec Kit、そして AI / MCP を試してみるきっかけになれば幸いです。 執筆時点では、Windows上のGitBashでGemini CLIをnpmインストールできるのですが、文字化けを起こしたり色々とトラブルがあります。 これはWindows環境では、Gemini CLIが内部的にPowerShellを利用していることに起因するためです。Windows環境ではデフォルトのターミナルをPowerShellにすることで、それらのトラブルは解消します。 ↩︎
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第12日目の記事です。 --> Information 記事の内容に不正確な記述が含まれていたため 2025/12/15 に追記・修正を行いました はじめに # ビジネスソリューション事業部の塚野です。現在、私がアサインされている案件ではパブリッククラウドとして Google Cloud を利用しています。 もともとは AWS を業務や個人開発で使っており、インフラ構成もサービス選定も「AWS の考え方」を土台にしてきました。しかし案件アサインに際し Google Cloud を本格的に学びはじめ、その設計思想やネットワークモデルが AWS と大きく異なることに気づきました。 キャッチアップの際には、AWS の常識をそのまま Google Cloud に当てはめて混乱した経験があり、両者を横並びで比較した体系的な資料が欲しいと強く感じました。 そこで本記事では、AWS 上に用意したシンプルなデモアプリケーションを題材にしながら、 「同じ構成を Google Cloud で実現するとどうなるのか?」 という観点で、Google Cloud のプロダクトや設計思想を AWS と比較しながら紹介していければと思います。 また、本記事では AWS ユーザーを対象とし、AWS ユーザーが Google Cloud を理解する際の指針になることを目指すため、AWS 各サービスの説明は行いません。 デモアプリの構成 # 本記事で考えるデモアプリは、簡単な ToDo アプリであるとします。 典型的な三層 Web アプリケーションに、非構造化データを保存するためのオブジェクトストレージを加えた構成です。 フロントエンドには Next / Nuxt の SSR(Server-Side Rendering) を採用し、 フロントエンドとバックエンド API を同一サーバー上でホストできる構成とします。 フロントエンド: Next / Nuxt などで構築された Web UI(SSR によりサーバー側でレンダリング) バックエンド API:タスクの CRUD を提供する REST API 永続化層:RDBMS(ユーザー・タスク・プロジェクトなどのデータ) ファイルストレージ:ユーザーがアップロードするアイコン画像や添付ファイルなどを保存 本記事では AWS と Google Cloud の比較のためごく簡単なアプリケーションを題材とします。そのため高度な機械学習(Google Cloud の強みはここもあったりしますが)、メッセージング、DNS、イベント駆動アーキテクチャなどは含めず、 ネットワーキング コンピューティング データベース オブジェクトストレージ といったクラウドの基本要素にフォーカスしています。 また、実運用で考えるべきマルチAZ(マルチゾーン)やマルチリージョンな冗長化構成についても考慮しません。 AWS における構成 # まずは AWS でのデモアプリの構成です。 アプリを構築するリージョンは東京リージョン(ap-northeast-1)とします。 リージョン内にVPCを1つ作成し、VPC 内には AZ を1つ含めます。 その AZ 内にパブリックサブネットとプライベートサブネットを配置します。 さらに、Next / Nuxt の SSR やバックエンド API をホストするためのコンピューティングサービスとして EC2 をプライベートサブネット内に配置し、Auto Scaling グループを構成します。 パブリックサブネットには Application Load Balancer(ALB) を配置し、 インターネットからのトラフィックを Auto Scaling グループへルーティングします。 永続化層には、EC2 と同じプライベートサブネット内に Amazon RDS(MySQL / PostgreSQL) を配置します。 ユーザーアップロード用のファイルは Amazon S3 バケット に保存し、 プライベートサブネット内の EC2 から S3 にアクセスします。 なお、実際には次のようなネットワーク構成が必要です EC2 の外向き(Outbound)通信には NAT Gateway が必要 EC2 から S3 を安全に利用するには VPC エンドポイント(Gateway Endpoint for S3)が必要 ですが今回は構成をシンプルに保つため図からは省略しています。 また、NAT Gateway は個人利用ではコストがかかるため、EC2 をパブリックサブネットに置く構成も現実的です。しかし、AWS と Google Cloud の比較を明確にするため、本記事では EC2 をプライベートに置く構成としています。 Google Cloud における構成 # 続いて、同じデモアプリケーションを Google Cloud 上で構成した場合を見ていきます。 見た目は AWS よりもスッキリしています。 VPC やロードバランサなど、AWS でもよく見かけるリソース名が Google Cloud にも登場しますが、その配置のされ方を見ると 「おや?」 と感じる部分があるかもしれません。 この違いこそが、AWS と Google Cloud の思想的な差分であり、 以降の章ではこれらのリソースの役割や設計上の特徴を詳しく比較していきます。 ネットワーク編 # まずはネットワークリソースから見ていきます。 ここで扱うのは以下のリソースです。 VPC サブネットとゾーン ロードバランサ VPC # まず、AWS と Google Cloud で大きく異なるのが VPC のスコープです。 AWS では VPC はリージョンに紐づくリージョナルリソースです。 AZ をまたいでの構成は可能ですが、リージョンをまたいで1つの VPC の構成はできません。 一方、 Google Cloud では VPC はグローバルリソース です。 そのため、1つの VPC の中に複数リージョンを含めることができ、AWS と比べてマルチリージョンなネットワークを容易に構成できます。 AWS と比較したときの Google Cloud の大きな特徴として、グローバルリソースを前提にした設計思想があります。 Google Cloud はまず グローバルな VPC を作成し、その中でリージョンやAWSのAZに当たるゾーンといったパーティションを切っていくというトップダウンの構成をとります。 一方 AWS は VPC をリージョンごとに作成し、必要があればリージョン間を接続してマルチリージョン化します。つまりリージョンを基本単位としたボトムアップ型のアプローチです。 この設計思想の違いこそが、AWS ユーザーが Google Cloud に触れたときに最初に戸惑うポイントといえます。 サブネットとゾーン # 次に、サブネットとゾーンの扱いについて触れます。 Google Cloud では、作成した VPC に対してサブネットを追加しますが、AWS と異なり サブネットはリージョン単位のリソース です。 AWS のようにサブネット=AZ 単位ではありません。 さらに、Google Cloud のサブネットにはパブリック/プライベートの区別がありません。 サブネットに割り当てるルート(デフォルトルートをインターネットゲートウェイに向けるかどうか)と、ファイアウォールルールによってパブリック/プライベートの性質が決まります。 また、「ゾーン」は、Google Cloud における AWS の AZ に相当する最小単位のデータセンター群です。 AWS 同様リージョンを分ける単位として機能しますが、サブネットがどこに紐づくかが異なります。 AWSの場合、サブネットは AZ 単位で作られますが、Google Cloudの場合、逆にサブネット内にゾーンが存在します。 以上Google Cloud の構成を整理すると、次のようになります。 サブネットはリージョン全体に広がるため、リージョン内のすべてのゾーンから利用可能 アプリケーションをデプロイするゾーンはサブネット内で選択する パブリック/プライベートの区別はルートとファイアウォールで制御する このため、AWS の AZ ごとにサブネットを作り冗長化するモデルに比べ、 Google Cloud はサブネット構成がシンプルで、ゾーン冗長を実現しやすいのが特徴です。 ロードバランサ # ロードバランサ(LB)の設計思想も AWS と Google Cloud で異なります。 ここではL7のLBのみ言及します。AWSにおけるL7 LB はALBですが、これはリージョンリソースで各リージョンごとにエンドポイントが異なります。 したがって、グローバルに公開したい場合は Route 53 や CloudFront を併用する必要があります。 一方、Google CloudにおけるL7 LBは 外部 HTTP(S) ロードバランサ です。これにもいろいろ種類があるのですが [1] 、グローバルに公開したい場合は、 グローバルリソース として提供される LB を選択可能です。 AWS でグローバル LB を構成しようとすると複数サービスを組み合わせる必要がありますが、Google Cloud では LB 単体でグローバル公開ができる点が大きな違いです。 なお、インターネット接続の扱いも AWS と Google Cloud では少し異なります。 AWS では VPC を外部に公開するために Internet Gateway(IGW)を明示的に作成し、パブリックサブネットのルートテーブルに紐づける必要があります。一方、Google Cloud では IGW に相当するリソースをユーザーが直接意識することなく、外部 HTTP(S) ロードバランサ などの外部向けリソースを作成すると、自動的に Google のエッジネットワークがインターネットとの入口として機能します。 そのため、Google Cloud では「どこをインターネットに公開するか」を細かいネットワーク構成として指定することが少なく、AWS に比べて抽象度が高いという特徴があります。 ネットワークまとめ # ここまでを踏まえると、デモアプリのネットワーク構成は以下のようになります。 グローバルリソースとして VPC を1つ作成し、その中に東京リージョン(asia-northeast1)を含める。 東京リージョン内にリージョンサブネットを作成する。 リージョンサブネット内の特定のゾーン内(今回の場合はゾーンA)にコンピューティングやDBを配置する L7 LBはグローバルリソースとして配置する Google Cloudのネットワーク構成図 リソース AWS のスコープ Google Cloud のスコープ VPC リージョン単位 グローバルリソース サブネット AZ単位 リージョン単位 ゾーン(AZ 相当) 1つのリージョンに複数の AZ。サブネットと 1 対 1 サブネットの内部に存在。サブネットはゾーンをまたぐ ロードバランサ(LB) ALB/NLB は リージョン単位 外部 HTTP(S) LB は グローバル リソースとして配置可能 インターネット接続 IGW を VPC にアタッチし、ルートテーブルで制御 Google の外部エッジネットワーク(LB 経由)を入口として利用 コンピューティング編 # 続けて、コンピューティングサービスについて見ていきます。 アプリケーションを実際に実行するサーバーをどのように冗長化し、スケールさせるかは AWS と Google Cloud の大きな比較ポイントの一つです。 AWS では、EC2 インスタンスでフロント/バックエンドサーバーをホストし、EC2インスタンスを Auto Scaling グループに配置しています。これにより、一定条件下でインスタンス数を自動的に増減させることが可能です。 Google Cloud において EC2 にあたるコンピューティングサービスとして Google Compute Engine(GCE) があります。 GCE でも EC2 と同様に仮想マシンを利用してアプリケーションをホストしますが、スケーリングや冗長化の仕組みが少し異なります。Google Cloud では、GCE インスタンスを Managed Instance Group(MIG) にまとめることで、スケールイン/アウトや自己修復などを自動化できます。 ここで AWS と比べて特徴的なのは、Google Cloud では MIG をリージョン単位で作成できる という点です。 AWS の Auto Scaling グループが複数 AZ にまたがることで高可用性を実現するのと似ていますが、MIG は「リージョン MIG」として構成することで、リージョン内の複数ゾーンにコンピューティングリソースが自動的に分散配置されます。 これは前章で触れた「サブネットがリージョン単位」である Google Cloud のネットワーク設計にも関わっています。 AZをまたがるように配置可能なAWSのAuto Scalingグループ(左)とリージョン単位で作成可能なGoogle Cloud リージョナルMIG(右) 今回のデモアプリでは構成をシンプルに保つため、GCE インスタンスは asia-northeast1 の中の 1 つのゾーン(例:asia-northeast1-a)に配置しています。しかし実運用を想定する場合は、リージョン MIG を使って複数ゾーンにまたがって配置するのが定石です。複数ゾーンにまたがることで単一ゾーン障害に対する耐性を持たせることができます。 AWS の Auto Scaling グループと Google Cloud の MIGは、どちらもインスタンス数のスケーリングに加え、起動定義のバージョン管理、ローリングアップデートといった機能を備えています。 データベース、ストレージ編 # お次はデモアプリで利用するデータベースとストレージについて見ていきます。 デモアプリでは、ユーザーやタスクを保存するための RDB と、ユーザーがアップロードする画像ファイルなどを保存するオブジェクトストレージを利用しています。 AWS では、RDB には Amazon RDS を、ストレージには Amazon S3 を利用しています。EC2 で動くアプリケーションは、プライベートサブネットの内部から RDS に接続し、ユーザーがアップロードしたファイルを S3 に保存します。 一方、Google Cloud で同じアプリケーションを構築する場合、対応するプロダクトは Cloud SQL と Cloud Storage になります。 Cloud SQL は MySQL や PostgreSQL をマネージドで提供するサービスで [2] 、Cloud Storage は S3 と同様にオブジェクトストレージとして利用できます。 ここまでは AWS とさほど大きな違いはありませんが、ネットワーク構成に踏み込むと、Google Cloud らしい特徴が見えてきます。 まずは DB サービスの比較です。 RDS が VPC 内のサブネットにインスタンスを持つのに対し、Cloud SQL のインスタンスはユーザー管理の VPC 内には存在しません。Cloud SQL のインスタンスは Google の管理ネットワーク内にある VPC に存在します。 Cloud SQL への接続は Public IP を付与しパブリックネットワーク経由でアクセスする方法と、プライベートネットワーク経由でGoogle Cloud内部から接続する方法があります。 本記事でのデモアプリでは、プライベートなVPC内に配置した GCE からアクセスする構成を想定するため、プライベートネットワークアクセスで Cloud SQL に接続する方法をとることとします。 プライベートなアクセス方法は本アプリでは Private Service Connect(PSC)という接続方法をとることとし [3] 、VPC 内の PSC エンドポイント経由で Cloud SQL インスタンスへ接続をおこないます。 Cloud SQL では RDS のように冗長化構成をとる場合でも、レプリカをどのサブネットに置くかといった細かいネットワーク設計を利用者が意識する必要はなく、リージョンの指定のみで冗長化・レプリカ配置は Google がよしなにやってくれます。 このようにCloud SQL は内部構造がユーザーに隠蔽されている点や、冗長化方式の抽象化という観点では、AWS の Amazon Aurora に近い部分もあります。ただし、個人開発や小規模な Web アプリケーションではコスト面や構成のシンプルさから RDS が選択されるケースも多く、Amazon Aurora は多機能であるため、本記事では純粋な RDB サービスの比較対象として RDS を扱っています。 Amazon RDS はサブネットに配置され(左)、Cloud SQL インスタンスの実体はユーザー管理のサブネットにはなく Google によって管理される(右) オブジェクトストレージについても似た特徴があります。 AWS の場合、EC2 がプライベートサブネットにある場合は NAT Gateway または S3 用の VPC エンドポイント(Gateway Endpoint)を明示的に用意して、外向きトラフィックをどう流すかを設計する必要があります。 Google Cloud の Cloud Storage では、GCE などのインスタンスからアクセスする際にはサブネット単位で「限定公開の Google アクセス(Private Google Access)」を有効にすることで、外部 IP を持たないプライベートサブネットのインスタンスからでも Google Cloud の内部ネットワーク経由で Cloud Storage の API にアクセスできます。この場合、インターネットに公開されたパブリック IP を経由せずに通信が完結します。 こうした差異から見えてくるのは、AWS と Google Cloud のネットワーク設計に対する考え方の違いです。 AWS では、利用者がネットワークの経路やセキュリティを比較的細かく指定することが前提となっており、VPC・サブネット・NAT・VPC エンドポイントといった多くのリソースを組み合わせる必要があります。 対して Google Cloud は、Google 自身が世界規模で運用してきたネットワークを前提とし、ユーザーが意識しなくても安全で効率的な通信が成立するように抽象化されています。結果として、アプリケーション開発者は「どのネットワーク経路を通すか」よりも「どのサービスを使うか」に集中しやすくなります。 まとめ # 今回、同じデモアプリを題材に AWS と Google Cloud のアーキテクチャを比較してみました。 Google Cloud は、VPC やロードバランサがグローバルであるように、グローバルなネットワークや抽象化を前提としたクラウドです。 Cloud SQL のように内部構造をユーザーに見せず、シンプルな設定だけで安全に利用できるサービスが多いため、構成は AWS よりもスッキリまとまります。ただし、抽象化が強いぶん「どこからどこまでが自分の設計領域か」が直感的に理解しづらい場面もあります。 一方 AWS は、VPC・サブネット・AZ といった ネットワーク境界が明確で、ボトムアップで積み上げる構成を取りやすいのが特徴です。 インターネット接続やプライベートアクセスをユーザーが明示的に選択できるため、要件に応じて細かくコントロールできる点は大きな利点です。筆者としても、この「スコープが見えやすい構造」は理解しやすく、設計上の安心感があります。 どちらが優れているという話ではなく、Google Cloud はシンプルで抽象化されたモデル、AWS は構造が明確でコントロールしやすいモデルという違いがあります。 どちらのクラウドもそれぞれの強みがあり、アプリケーションの規模や要件に応じて最適解は変わります。本記事が、AWS を使っている方が Google Cloud を理解するきっかけになれば幸いです。 これがさらにグローバルかリージョナルリソースとして配置するか、パブリックインターネットからのトラフィックを負荷分散するか Google Cloud 内部のトラフィックを分散するかで種類が分かれます。L3/4 の LB についても同様に複数種類あってややこしいです。 何を言っているんだぜ?という感じですが、ロードバランサについてはこちらの記事で詳しく解説がなされています( AWS側の目線から理解する、Google Cloud ロードバランサの世界 )。どうしてこうなったのか。 ↩︎ RDBMS のマネージドサービスとして他に Cloud Spanner がありますが、個人開発や小規模開発ではオーバーキル気味なので今回はCloud SQLを選択します。また、Amazon Aurora のように Google Cloud にも独自データベースである AlloyDB があります。こちらは PostgreSQL のみ互換性があります。 ↩︎ Cloud SQLへのプライベートな接続方法には、Cloud SQL インスタンスに Private IP を割り当て、VPC ピアリング で接続をおこなう Private Service Access(PSA) と、ユーザー管理の VPC 内にエンドポイントを作成し、 エンドポイント経由で Cloud SQL インスタンスへ Google Cloud の内部ネットワークを通じて接続する Private Service Connect(PSC) が選択できます(ややこしい名前ですね…)。 接続方法はユースケースに合わせて PSA・PSC どちらも選択可能ですが、本記事では「VPC 内からマネージドサービスへプライベートに接続する」という構造をシンプルに示すことができる PSC によるプライベート接続を例に挙げました。 ↩︎
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第11日目の記事です! はじめに # この記事は、 Amazon Q Developerと人間の協働による実験的な取り組み です。 最初からネタばらしですが、 「この記事、ほとんどAIが書いています。」 AIを“使う側”から“協働する側”へ 。Amazon Q Developerで開発と執筆の常識が変わると思います。 何をするか: VSCodeでAmazon Q Developerをセットアップ 実際のコード作成・改善を体験 AIとの対話で技術記事を共同執筆 特徴的な点: 記事の大部分をAmazon Q Developerが執筆 コードサンプルやテストもAIが生成 人間は企画・構成・画像挿入を担当 読者が得られるもの: Amazon Q Developerの実用的な使い方 AI協働による効率的な開発手法 技術文書作成でのAI活用の可能性 それでは、Amazon Q Developerと人間の協働の旅を始めましょう。 1. 開発環境 # 今回の協働作業では、Amazon Q Developer を VSCode から使用します。 VSCode で Amazon Q Developer を使うには以下の環境が必要です。 VSCode 本体: v1.85.0 以上 サインイン用のアカウント 個人利用: AWS Builder ID (AWSアカウント不要) 会社利用: IAM Identity Center (AWSアカウント必要) ※ 会社のAWS環境で使うなら、権限やライセンスは管理者に確認しておくと安心です。 2. VSCode への拡張機能インストール # VSCode を起動します 左パネルの Extensions(拡張機能) を開きます 検索欄に Amazon Q と入力し、以下の拡張機能を選択してインストールします 以下のようなアイコンがVSCodeに表示されていれば、インストールは完了です ※ただし、まだ Amazon Q Developer へのログインが行われていないため、アイコンが赤くなっています 3. サインイン(認証) # VSCode 下部の Amazon Q アイコン をクリックし、「Sign in to get started」を選択します 「サインインオプション」から用途に合わせて選択します 個人(Personal account)の場合 → 事前に「Builder ID」を取得しておきます 会社(Company account)の場合 → 事前に「IAM Identity Center」で登録しておきます (今回は「会社の場合」で進めます) IAM Identity Centerのアカウント情報を設定します StartURL:AWS access portal URLを設定します Region :サービスのリージョンを設定します 続けて外部のAWSサイトに誘導されるので、ブラウザでログインします 以下の画面が表示されたら、アクセスを許可します VSCodeに戻ります。 下の画面のように「Amazon Q」と表示されていればサインイン完了です 4. 基本的な使い方 # チャットで質問 # Amazon Qパネルで質問や指示を入力します。 試しに「ここで何ができますか?」と質問してみます。 Amazon Q から「できることリスト」の回答が得られました。 コード補完 # ソースコードを作成していく場合、プログラムを書かずにコメントに意図を書いておくと、Amazon Qがインラインでコードを提案してくれます。 例1: 日本語コメントからクラス生成 # ユーザー情報を管理するクラスを作成 class User: def __init__(self, name, email): self.name = name self.email = email def get_display_name(self): return f"{self.name} ({self.email})" 例2: 関数の処理内容をコメントで指定 # CSVファイルからユーザーデータを読み込む関数 import csv def load_users_from_csv(filename): users = [] with open(filename, 'r', encoding='utf-8') as file: reader = csv.DictReader(file) for row in reader: user = User(row['name'], row['email']) users.append(user) return users 使い方のコツ: コメントは具体的に書く(「データを処理」より「CSVファイルから読み込み」) 日本語でも英語でも対応 Alt+C(Option+C)で手動補完も可能 5. コード作成・修正を試行 # では実際に、Amazon Q Developerでのコード作成・修正を体験してみましょう。 サンプルファイル(example.py)について # まず、今回使用するサンプルファイル example.py について説明します。このファイルは、ユーザー情報を管理するシンプルなPythonプログラムです。 example.py の機能は以下です。 ユーザー情報(名前、メールアドレス)を管理するUserクラス CSVファイルからユーザーデータを読み込む機能 メールアドレスでユーザーを検索する機能 ユーザーリストをJSON形式で保存する機能 ソースコードのひな型としてコメント部分だけを記述します。 # ユーザー情報を管理するクラスを作成 # CSVファイルからユーザーデータを読み込む関数 # リストの中から特定の条件に合うユーザーを検索する関数 # ユーザーリストをJSON形式で保存する関数 すると Amazon Q が以下のようなソースコードを提案してきます。 example.py のソースコード: # ユーザー情報を管理するクラスを作成 class User: def __init__(self, name, email): self.name = name self.email = email def get_display_name(self): return f"{self.name} ({self.email})" # CSVファイルからユーザーデータを読み込む関数 import csv def load_users_from_csv(filename): users = [] with open(filename, 'r', encoding='utf-8') as file: reader = csv.DictReader(file) for row in reader: user = User(row['name'], row['email']) users.append(user) return users # リストの中から特定の条件に合うユーザーを検索する関数 def find_user_by_email(users, email): for user in users: if user.email == email: return user return None # ユーザーリストをJSON形式で保存する関数 import json def save_users_to_json(users, filename): user_data = [] for user in users: user_data.append({ 'name': user.name, 'email': user.email }) with open(filename, 'w', encoding='utf-8') as file: json.dump(user_data, file, ensure_ascii=False, indent=2) print(f"Saved {len(users)} users to {filename}") このコードは構文的には正しく動作しますが エラーハンドリングやテストコードがない 実際に実行するメイン処理がない など、改善の余地があります。 これらの問題をAmazon Q Developerを使って解決していきます。 5.1 既存コードの理解 # まず、example.pyファイルを開いて、Amazon Qにコードの内容を質問し、Amazon Q がコードを正しく理解しているかを確認します。 Amazon Qへ以下のように指示を出します。 @example.py このファイルは何をするコードですか? Amazon Qが以下のような回答をしてくれます。 example.pyは、ユーザー情報を管理するためのシンプルなPythonプログラムです。 主な機能 1. Userクラス - ユーザー情報(名前とメールアドレス)を管理 - get_display_name(): 名前とメールアドレスを組み合わせた表示名を返す 2. CSVファイル読み込み - load_users_from_csv() - CSVファイルからユーザーデータを読み込んでUserオブジェクトのリストを作成 3. ユーザー検索 - find_user_by_email() - メールアドレスを指定して特定のユーザーを検索 4.JSON形式での保存 - save_users_to_json() - ユーザーリストをJSON形式でファイルに保存 使用例 このプログラムは、CSVファイルからユーザーデータを読み込み、必要に応じてユーザーを検索し、最終的にJSON形式で保存するといったユーザーデータの変換・管理処理に使用できます。 ただし、現在のコードにはエラーハンドリングやメイン処理がないため、実際に動作させるには追加の実装が必要です。 5.2 コードの改善提案 # 既存のコードに対して改善提案を求めてみましょう。 Amazon Qへ以下のように指示を出します。 @example.py このコードを改善する方法はありますか? Amazon Qから以下のような提案が得られます。 はい、example.pyには多くの改善点があります。以下のような改善方法をお勧めします: 1. エラーハンドリングの追加 現在の問題: ファイルが存在しない場合やCSVの形式が不正な場合にエラーで停止 改善案: try-except文でエラーを適切に処理 2. 型ヒントの追加 現在の問題: 引数や戻り値の型が不明確 改善案: from typing import List, Optional を使用して型を明示 3. docstringの追加 現在の問題: 関数の説明がコメントのみ 改善案: 各関数に適切なdocstringを追加 4. importの整理 現在の問題: import文が関数の直前に散らばっている 改善案: ファイル冒頭にまとめて配置 5. データ検証の追加 現在の問題: CSVデータの妥当性チェックなし 改善案: 必須フィールドの存在確認やメールアドレス形式の検証 6. 設定の外部化 現在の問題: エンコーディングなどがハードコード 改善案: 定数として定義 7. ログ機能の追加 現在の問題: print文のみでログ管理が不十分 改善案: loggingモジュールの使用 8. テスト可能な設計 現在の問題: メイン処理がなく、テストしにくい 改善案: if __name__ == "__main__": ブロックの追加 9. パフォーマンスの改善 現在の問題: 大量データ処理時の効率性 改善案: リスト内包表記の使用、メモリ効率の向上 これらの改善により、より堅牢で保守性の高いコードになります。特にエラーハンドリングと型ヒントの追加は優先度が高い改善点です。 5.3 実際にコードを修正 # エラーハンドリングの追加 CSVファイル読み込み関数にエラーハンドリングを追加してみます。 修正前のコード(example.py の一部)は以下です。 def load_users_from_csv(filename): users = [] with open(filename, 'r', encoding='utf-8') as file: reader = csv.DictReader(file) for row in reader: user = User(row['name'], row['email']) users.append(user) return users Amazon Qへ以下のように指示を出します。 load_users_from_csv関数にファイルが存在しない場合のエラーハンドリングを追加してください コードが以下のように修正されました。 def load_users_from_csv(filename): users = [] try: with open(filename, 'r', encoding='utf-8') as file: reader = csv.DictReader(file) for row in reader: user = User(row['name'], row['email']) users.append(user) except FileNotFoundError: print(f"エラー: ファイル '{filename}' が見つかりません") return [] except Exception as e: print(f"エラー: ファイル読み込み中に問題が発生しました: {e}") return [] return users Amazon Q が直接ソースコードを修正 し、私(人間)はソースコードに一切触れていません。 新機能の追加 ユーザーの年齢情報を管理する機能を追加してみます。 Amazon Qへ以下のように指示を出します。 Userクラスに年齢(age)フィールドを追加し、年齢で検索する関数も作成してください 以下のようにコードが追加されました。 # ユーザー情報を管理するクラスを作成 class User: def __init__(self, name, email, age=None): self.name = name self.email = email self.age = age def get_display_name(self): age_info = f" (年齢: {self.age})" if self.age else "" return f"{self.name} ({self.email}){age_info}" # 年齢範囲でユーザーを検索する関数 def find_users_by_age_range(users, min_age, max_age): """指定された年齢範囲のユーザーを検索する""" result = [] for user in users: if user.age and min_age <= user.age <= max_age: result.append(user) return result 5.4 テストコードの生成 # テストコードを追加してみます。 Amazon Qへ以下のように指示を出します。 @example.py の関数をテストするためのテストコードを作成してください 生成されたテストコード(test_example.py)は以下です。 import unittest import tempfile import os import json import csv from example import User, load_users_from_csv, find_user_by_email, find_users_by_age_range, save_users_to_json class TestUserManagement(unittest.TestCase): def test_user_creation_without_age(self): """年齢なしでのユーザー作成テスト""" user = User("田中太郎", "tanaka@example.com") self.assertEqual(user.name, "田中太郎") self.assertEqual(user.email, "tanaka@example.com") self.assertIsNone(user.age) def test_user_creation_with_age(self): """年齢ありでのユーザー作成テスト""" user = User("田中太郎", "tanaka@example.com", 30) self.assertEqual(user.name, "田中太郎") self.assertEqual(user.email, "tanaka@example.com") self.assertEqual(user.age, 30) def test_get_display_name_without_age(self): """年齢なしでの表示名テスト""" user = User("田中太郎", "tanaka@example.com") expected = "田中太郎 (tanaka@example.com)" self.assertEqual(user.get_display_name(), expected) def test_get_display_name_with_age(self): """年齢ありでの表示名テスト""" user = User("田中太郎", "tanaka@example.com", 30) expected = "田中太郎 (tanaka@example.com) (年齢: 30)" self.assertEqual(user.get_display_name(), expected) def test_find_user_by_email(self): """メールアドレスでのユーザー検索テスト""" users = [ User("田中太郎", "tanaka@example.com"), User("佐藤花子", "sato@example.com") ] # 存在するユーザーの検索 found = find_user_by_email(users, "tanaka@example.com") self.assertIsNotNone(found) self.assertEqual(found.name, "田中太郎") # 存在しないユーザーの検索 not_found = find_user_by_email(users, "yamada@example.com") self.assertIsNone(not_found) def test_find_users_by_age_range(self): """年齢範囲でのユーザー検索テスト""" users = [ User("田中太郎", "tanaka@example.com", 30), User("佐藤花子", "sato@example.com", 25), User("鈴木次郎", "suzuki@example.com", 35), User("山田三郎", "yamada@example.com") # 年齢なし ] # 25-32歳の範囲で検索 result = find_users_by_age_range(users, 25, 32) self.assertEqual(len(result), 2) self.assertEqual(result[0].name, "田中太郎") self.assertEqual(result[1].name, "佐藤花子") # 該当者なしの範囲で検索 result_empty = find_users_by_age_range(users, 40, 50) self.assertEqual(len(result_empty), 0) def test_save_users_to_json(self): """JSON保存機能のテスト""" users = [ User("田中太郎", "tanaka@example.com", 30), User("佐藤花子", "sato@example.com") # 年齢なし ] # 一時ファイルを作成してテスト with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as temp_file: temp_filename = temp_file.name try: save_users_to_json(users, temp_filename) # ファイルが作成されたことを確認 self.assertTrue(os.path.exists(temp_filename)) # ファイル内容を確認 with open(temp_filename, 'r', encoding='utf-8') as file: data = json.load(file) self.assertEqual(len(data), 2) # 年齢ありのユーザー self.assertEqual(data[0]['name'], "田中太郎") self.assertEqual(data[0]['email'], "tanaka@example.com") self.assertEqual(data[0]['age'], 30) # 年齢なしのユーザー self.assertEqual(data[1]['name'], "佐藤花子") self.assertEqual(data[1]['email'], "sato@example.com") self.assertNotIn('age', data[1]) # 年齢が設定されていない場合は含まれない finally: # 一時ファイルを削除 if os.path.exists(temp_filename): os.unlink(temp_filename) def test_load_users_from_csv_file_not_found(self): """存在しないCSVファイルの読み込みテスト""" result = load_users_from_csv("nonexistent_file.csv") self.assertEqual(result, []) def test_load_users_from_csv_valid_file(self): """有効なCSVファイルの読み込みテスト""" # 一時CSVファイルを作成 csv_data = [ ['name', 'email', 'age'], ['田中太郎', 'tanaka@example.com', '30'], ['佐藤花子', 'sato@example.com', ''] # 年齢なし ] with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv', encoding='utf-8', newline='') as temp_file: temp_filename = temp_file.name writer = csv.writer(temp_file) writer.writerows(csv_data) try: users = load_users_from_csv(temp_filename) self.assertEqual(len(users), 2) # 年齢ありのユーザー self.assertEqual(users[0].name, "田中太郎") self.assertEqual(users[0].email, "tanaka@example.com") self.assertEqual(users[0].age, 30) # 年齢なしのユーザー self.assertEqual(users[1].name, "佐藤花子") self.assertEqual(users[1].email, "sato@example.com") self.assertIsNone(users[1].age) finally: # 一時ファイルを削除 if os.path.exists(temp_filename): os.unlink(temp_filename) if __name__ == '__main__': unittest.main() 5.5 実行可能なメイン処理の追加 # example.py にmain処理が無いので追加します。 Amazon Qへ以下のように指示を出します。 @example.py に実際に動作確認できるメイン処理を追加してください 追加されたメイン処理は以下です。 if __name__ == "__main__": # サンプルデータでテスト print("=== ユーザー管理システムのテスト ===") # ユーザー作成 users = [ User("田中太郎", "tanaka@example.com", 30), User("佐藤花子", "sato@example.com", 25), User("鈴木次郎", "suzuki@example.com", 35), User("山田三郎", "yamada@example.com") # 年齢なし ] # 全ユーザー表示 print("\n登録ユーザー:") for user in users: print(f" {user.get_display_name()}") # メールアドレスで検索 print("\nメール検索テスト:") found_user = find_user_by_email(users, "sato@example.com") if found_user: print(f" 見つかりました: {found_user.get_display_name()}") # 年齢範囲で検索 print("\n年齢検索テスト (25-32歳):") age_filtered_users = find_users_by_age_range(users, 25, 32) for user in age_filtered_users: print(f" {user.get_display_name()}") # JSON保存 print("\nJSON保存テスト:") save_users_to_json(users, "sample_users.json") # 存在しないCSVファイルの読み込みテスト print("\nCSVファイル読み込みテスト(エラーハンドリング確認):") csv_users = load_users_from_csv("nonexistent.csv") print(f"読み込み結果: {len(csv_users)}件のユーザー") print("\nテスト完了!") 5.6 Amazon Q Developerの活用ポイント # Amazon Q の 効率的な使い方 のポイントは以下です。 @ファイル名 でファイル全体を参照 具体的な指示を出す(「エラーハンドリングを追加」など) 段階的に機能を追加していく テストコードも一緒に生成してもらう このように、Amazon Q Developerを使うことで、既存コードの理解から改善、新機能追加、テスト作成まで効率的に行えます。 6. まとめ # Amazon Q Developer for VSCode を使うことで以下が期待できます。 作業効率向上 : 定番の開発環境であるVSCodeを使うことで作業効率向上 コード理解 : 既存コードの動作や構造を素早く把握 コード改善 : エラーハンドリングや型ヒントなどの品質向上 機能追加 : 新しい機能を段階的に実装 テスト作成 : 自動的にテストコードを生成 実行確認 : メイン処理を追加して動作検証 日本語での指示にも対応しているため、自然な言葉でコード作成・修正ができます。 7. この記事の舞台裏 ― Amazon Q Developerとの協働執筆 # この記事は Amazon Q Developer との対話を通じて作成されました。 記事執筆の過程をご紹介します。 7.1 記事作成プロセス # 人間(筆者)の役割: 記事の構成や方向性の決定 スクリーンショット画像の撮影・挿入 最終的な内容の確認・調整 Amazon Q Developerの役割: 各章の詳細な文章作成 コードサンプルの生成 テストコードの作成 技術的な説明文の執筆 以下は、VSCode上で記事とコードを同時に執筆している作業風景です。 このように記事を執筆しながら、コード作成・修正を同時に行っています。 人間が内容の確認を行い、Amazon Q とレビューを繰り返し、Amazon Q が記事・コードの修正を行ってくれます。 7.2 協働執筆の効果 # 今回、Amazon Q Developer を VSCode上で実行し、以下のような効果を実感できました。 効率性の向上: 定番開発環境による作業効率向上 記事の骨格作成時間を大幅短縮 技術的な詳細説明を素早く生成 コードサンプルとテストを同時作成 品質の向上: 一貫した文体と構成 実際に動作するコードサンプル 包括的なテストカバレッジ 創造性の発揮: 人間は全体設計と創造的な部分に集中 AIは詳細な実装と文章作成を担当 両者の強みを活かした分業 この記事の作成プロセス自体が、Amazon Q Developerの実用性を証明しています。 Amazon Q Developer は単なるコーディング支援ツールではなく、技術者の創造的な作業全般をサポートする強力なパートナーです。 ぜひ様々な場面で活用してみてください。 この記事は Amazon Q Developer との協働により作成されました。 img { border: 1px gray solid; }
本記事は 豆蔵デベロッパーサイトアドベントカレンダー2025 第10日目の記事です。 0. はじめに # ロボット制御や画像処理の分野では、最適化問題を解く必要に迫られる場面が多々あります。 最適化問題といっても、線形計画法や組合せ最適化など、その種類や解法は多岐にわたります。 その中でも、実用上特によく扱われるのが「最小二乗問題」です。 これは、下記のような目的関数 F ( x ) F(\boldsymbol{x}) F ( x ) を最小化するパラメータ x \boldsymbol{x} x を求める問題です。 (なお、一般的に x \boldsymbol{x} x はベクトルとなります。) min ⁡ x F ( x ) = 1 2 ∑ i ∥ r i ( x ) ∥ 2 \min_{\boldsymbol{x}} F(\boldsymbol{x}) = \frac{1}{2} \sum_{i} \| r_i(\boldsymbol{x}) \|^2 x min ​ F ( x ) = 2 1 ​ i ∑ ​ ∥ r i ​ ( x ) ∥ 2 ここで、上式の r i ( x ) r_i(x) r i ​ ( x ) は残差(Residual)といい、観測されたデータ(=実測値) y i y_i y i ​ と予測値(=理論値) f i ( x ) f_i(x) f i ​ ( x ) との差を意味します。 r i ( x ) = y i − f i ( x ) r_i(\boldsymbol{x}) = y_i - f_i(\boldsymbol{x}) r i ​ ( x ) = y i ​ − f i ​ ( x ) 今回は、このような非線形最小二乗問題を効率的に解くためのGoogle社製ソルバ " Ceres Solver "について紹介します。 1. 本記事の環境について # 本記事ではUbnutu 24.04を対象とします。 筆者の環境ではWSL2を利用していますが、純正のUbuntuでも問題ありません。 --> Information 本記事ではUbuntu環境でライブラリを使用していますが、Windowsでも動作します。 詳しくは下記リンク先をご覧ください。 http://ceres-solver.org/installation.html#windows 2. 環境構築 # CeresSolverを使用するための前準備です。少し長いですがお付き合いください。 必要なライブラリのインストール # まずは、Ceres Solverのビルドに必要なツールをインストールします。 下記を1行ずつ実施して、インストールしてください。 # apt update(パスワード入力を求められます) sudo apt update && sudo apt upgrade -y # ビルドツール sudo apt install build-essential # Git sudo apt install git # CMake sudo apt install cmake # google-glog + gflags sudo apt install libgoogle-glog-dev libgflags-dev # Use ATLAS for BLAS & LAPACK sudo apt install libatlas-base-dev # Eigen3 sudo apt install libeigen3-dev # SuiteSparse (optional) sudo apt install libsuitesparse-dev ワークスペース作成 # 次に、任意の場所にワークスペースを作成しましょう。 今回はホームディレクトリに ceres_solver_ws というディレクトリを作成します。 mkdir ~/ceres_solver_ws --> Information 一般的なLinux環境での"~"(チルダ)は、ホームディレクトリを意味します。 ホームディレクトリの絶対パスは /home/${ユーザ名}/ です。 CeresSolverライブラリのクローン # ワークスペースが作成出来たら、CeresSolverのソースをクローンしましょう。 クローンする場所は任意ですが、今回は external ディレクトリ内にクローンしておきます。 この時、Submoduleについても再帰的に取得する必要があるため、 --recursive オプションを付与する必要がある点に注意してください。 cd ~/ceres_solver_ws mkdir external cd external git clone --recursive https://github.com/ceres-solver/ceres-solver --> Information サブモジュールも再帰的に取得するコマンドとして、--recurse-submodulesオプションも存在するようです。 git clone --recurse-submodules ${repo-url} CeresSolverライブラリのビルド&インストール # クローンが出来たら、CMakeを使用してビルドします。 # CeresSolverのソースに移動 cd ceres-solver # ビルド用のディレクトリを作成 mkdir build # ビルドシステムの生成(out-of-sourceビルド) cmake -S . -B build # ビルド実行 cmake --build build --> ビルドシステム生成時にエラーが発生する場合 コマンド実施に下記のエラーが発生した場合は、build-essentialパッケージをaptでインストールしてください。 CMake Error at CMakeLists.txt:33 (project): No CMAKE_CXX_COMPILER could be found. Tell CMake where to find the compiler by setting either the environment variable "CXX" or the CMake cache entry CMAKE_CXX_COMPILER to the full path to the compiler, or to the compiler name if it is in the PATH. -- Configuring incomplete, errors occurred! --> 並列ビルドによる高速化 ビルドには少し時間がかかります。下記コマンドで並列ジョブを使用して高速化可能です。 nproc コマンドは「システムが利用可能なCPUコア数を取得する」コマンドです。 この出力結果が $(nproc) と置換されます。 cmake --build build -- -j$(nproc) 下記のように達成率が100%となるようなログが出力されれば、ビルド成功です。 [ 99%] Built target robot_pose_mle [ 99%] Building CXX object examples/sampled_function/CMakeFiles/sampled_function.dir/sampled_function.cc.o [ 99%] Linking CXX executable ../../bin/sampled_function [ 99%] Built target sampled_function [ 99%] Building CXX object examples/slam/pose_graph_2d/CMakeFiles/pose_graph_2d.dir/pose_graph_2d.cc.o [ 99%] Linking CXX executable ../../../bin/pose_graph_2d [ 99%] Built target pose_graph_2d [ 99%] Building CXX object examples/slam/pose_graph_3d/CMakeFiles/pose_graph_3d.dir/pose_graph_3d.cc.o [100%] Linking CXX executable ../../../bin/pose_graph_3d [100%] Built target pose_graph_3d 最後に、ビルドした生成物をインストールします。 インストール先のデフォルトは /usr/local です。 # ビルドした生成物をインストールする sudo cmake --install build インストールが完了したら、念のために下記のコマンドを実施しておきましょう。 source ~/.bashrc 3. 簡単な最適化計算を解いてみる # CMakeLists.txtとソースファイルの作成 # では、CeresSolverを実際に使用して最適化問題を解いてみましょう。 まずは,ワークスペース ~/ceres-solver_ws に戻り、 CMakeLists.txt ファイルを作成します。 cd ~/ceres_solver_ws touch CMakeLists.txt CMakeLists.txt には下記を記述します。 エディタは自由ですが、今回はVisual Studio Codeを使用しています。 (WSLとの相性も良いのでVSCodeはおすすめ!) # CMakeの最低バージョン指定 cmake_minimum_required(VERSION 3.14) # プロジェクト名定義 project(ceres-solver-sample) # 実行ファイルをビルドディレクトリ直下に出力するように設定 # (不要な場合は削除してください) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) # 依存パッケージ指定 find_package(Ceres REQUIRED) # サブディレクトリ追加 add_subdirectory(src) 次に, src ディレクトリを作成します。 src ディレクトリ内にも CMakeLists.txt を作成します。 cd ~/ceres-solver_ws mkdir src cd src touch CMakeLists.txt src/CMakeLists.txt には下記のように記述します。 # simple-ols add_executable(simple-ols simple-ols.cpp) target_link_libraries(simple-ols absl::log_initialize Ceres::ceres) 最後に,計算を記述するC++ファイル( simple-ols.cpp )を作成します。 先述した CMakeLists.txt に記載したファイル名と合致するようにしてください。 touch simple-ols.cpp 最終的なファイル構造は下記のようになります。 ceres_solver_ws/ ├── CMakeLists.txt └── src/ ├── CMakeLists.txt └── simple-ols.cpp 最適化計算の実装 # 作成した simple-ols.cpp に処理を記述しましょう。 今回のお題としては,下式で定義された関数 f ( x ) f(x) f ( x ) の値を最小化する x x x を求めてみます。 f ( x ) = 1 2 ( 5 − x ) 2 f(x) = \frac{1}{2} (5 - x)^2 f ( x ) = 2 1 ​ ( 5 − x ) 2 x = 5 x=5 x = 5 で最小値を取ることは火を見るよりも明らかですが,これをプログラムで求めてみましょう。 実装コードは下記です。 simple-ols.cpp #include <ceres/ceres.h> #include <glog/logging.h> using ceres::AutoDiffCostFunction; using ceres::CostFunction; using ceres::Problem; using ceres::Solver; /// @brief 残差の構造体 /// @remark 最適化の対象を()演算子にて記述します struct CostFunctor { template <typename T> bool operator()(const T* const x, T* residual) const { // 今回の最適化対象式 residual[0] = T(5.0) - x[0]; return true; } }; /// @brief メイン関数 int main(int argc, char** argv) { // 初期値の定義 double initial_x = 1.0; double x = initial_x; // コスト関数の定義 CostFunction* cost_function = new ceres::AutoDiffCostFunction<CostFunctor, 1, 1>(); // 最適化問題の定義 Problem problem; problem.AddResidualBlock(cost_function, nullptr, &x); // 残差ブロックを追加 problem.SetParameterLowerBound(&x, 0, 0.0); // 入力パラメータの下限値設定 problem.SetParameterUpperBound(&x, 0, 10.0); // 入力パラメータの上限値設定 // 計算オプションの定義 ceres::Solver::Options options; options.linear_solver_type = ceres::DENSE_QR; // 密行列でのQR分解を使用する options.minimizer_progress_to_stdout = true; // 進捗出力を有効化 options.max_num_iterations = 10; // 最大反復回数 // 計算結果の定義 Solver::Summary summary; // 最適化計算を行う ceres::Solve(options, &problem, &summary); // 計算結果の出力 // 計算結果は変数xに格納されます std::cout << summary.BriefReport() << std::endl; std::cout << "x: " << initial_x << " -> " << x << std::endl; return 0; } プログラムの詳細 # コードの中で重要な部分について説明します。 コスト定義 # /// @brief コスト構造体 /// @remark 最適化の対象を()演算子にて記述します struct CostFunctor { template <typename T> bool operator()(const T* const x, T* residual) const { // 今回の最適化対象式 residual[0] = T(5.0) - x[0]; return true; } }; 今回の最適化の対象となる式を記述します。 () 演算子の中に計算式を記述する必要があります。 テンプレートTには、最適化計算時に使用される型が使用されます。 具体的には、 ceres::Jet 型というデータ型が使用されます。 double型などを使用する場合は、T型でキャストする必要がある点に注意してください コスト関数の定義 # // コスト関数の定義 CostFunction* cost_function = new ceres::AutoDiffCostFunction<CostFunctor, 1, 1>(); 上記にはコスト関数の定義を行っています。今回は計算で自動微分(Automatic Differentiation)を使用するように設定しています。 また、テンプレート引数のそれぞれの意味は下記の通りです。 第1引数: コスト関数の構造体型 第2引数: 誤差パラメータの次元数 第3引数: 最適化パラメータの次元数 今回のお題では、誤差パラメータの次元(= f ( x ) f(x) f ( x ) の次元)はスカラーのため 1 、最適化パラメータ x x x の次元は同じくスカラー量のため 1 となります。 最適化問題の定義 # // 最適化問題の定義 Problem problem; problem.AddResidualBlock(cost_function, nullptr, &x); // 残差ブロックを追加 problem.SetParameterLowerBound(&x, 0, 0.0); // 入力パラメータの下限値設定 problem.SetParameterUpperBound(&x, 0, 10.0); // 入力パラメータの上限値設定 最適化問題を定義します。 また、入力パラメータの上限値、下限値もここで設定します。 AddResifualBlockメソッドの第2引数は損失関数(Loss Function)を定義できます。 本記事では詳細を割愛しますが、詳細は下記リンク先をご覧ください。 http://ceres-solver.org/nnls_tutorial.html#robust-curve-fitting ビルドと実行 # CMakeを使用して作成したプログラムをビルドします。 cd ~/ceres_solver_ws cmake -S . -B bin cmake --build bin ビルドが成功したら、下記のコマンドで実施してみましょう。 ./bin/simple-ols これを実施すると、下記のようなログが出力されます。 iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time 0 8.000000e+00 0.00e+00 4.00e+00 0.00e+00 0.00e+00 1.00e+04 0 1.35e-05 4.40e-05 1 7.998400e-08 8.00e+00 4.00e-04 0.00e+00 1.00e+00 3.00e+04 1 8.09e-05 1.88e-04 2 8.886518e-17 8.00e-08 1.33e-08 4.00e-04 1.00e+00 9.00e+04 1 3.24e-05 2.39e-04 Ceres Solver Report: Iterations: 3, Initial cost: 8.000000e+00, Final cost: 8.886518e-17, Termination: CONVERGENCE x: 1 -> 5 最終行のログより、最適入力値は 5 であると計算できました。 また、最適入力値の場合の残差コスト( Final cost )は 8.886518e-17 であり、ほぼ0であることも計算できました。 入力パラメータの上下限値を変えてみる # 次に、入力値の範囲を 1~3 に変更して計算してみましょう。 下記の★印部分の値を 10 から 3 に変更します。 // 最適化問題の定義 Problem problem; problem.AddResidualBlock(cost_function, nullptr, &x); // 残差ブロックを追加 problem.SetParameterLowerBound(&x, 0, 0.0); // 入力パラメータの下限値設定 problem.SetParameterUpperBound(&x, 0, 3.0); // 入力パラメータの上限値設定(★) これを再度ビルドして実行してみると、最適入力は x = 3 x=3 x = 3 、残差コストは 2.000000e+00 となります。 指定した制約内でコストが最小となるような最適入力が求められることが確認できます。 参考のため、計算ログを下記に記します。 iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time 0 8.000000e+00 0.00e+00 2.00e+00 0.00e+00 0.00e+00 1.00e+04 0 1.21e-05 4.23e-05 1 2.000000e+00 6.00e+00 0.00e+00 0.00e+00 7.50e-01 1.14e+04 1 6.80e-05 1.66e-04 Ceres Solver Report: Iterations: 2, Initial cost: 8.000000e+00, Final cost: 2.000000e+00, Termination: CONVERGENCE x: 1 -> 3 4. 4自由度平面マニピュレータの逆運動学を数値的に解いてみる # 3章では入力パラメータとコストがともにスカラー量である最適化問題を解くサンプルを紹介しました。 本章では、より難しい非線形最適化問題のお題として「4自由度平面マニピュレータの逆運動学」を解いてみましょう。 4自由度平面マニピュレータとは # 下図のような4つの関節を持つ平面マニピュレータを対象に、逆運動学計算を実装してみます。 パラメータについては下記の通りとします。 各リンク長は L i L_i L i ​ とする 各関節角度の回転角度を θ i \theta_i θ i ​ とする i = 1 , 2 , 3 , 4 i=1,2,3,4 i = 1 , 2 , 3 , 4 各関節の回転正方向は「反時計回り(ccw)」とする ロボットの先端(点P)のX座標を x p x_p x p ​ 、Y座標を y p y_p y p ​ とする ロボットの先端の姿勢角度(半直線CPとX軸のなす角)を ϕ \phi ϕ とする 順運動学計算 # 対象ロボットの各軸角度 θ \boldsymbol{\theta} θ と先端位置 p \boldsymbol{p} p の関係を考えてみましょう。 ここで、それぞれのベクトルの定義は下記とします。 θ = [ θ 1 θ 2 θ 3 θ 4 ] \boldsymbol{\theta} = \begin{bmatrix} \theta_1 \\ \theta_2 \\ \theta_3 \\ \theta_4 \end{bmatrix} θ = ​ θ 1 ​ θ 2 ​ θ 3 ​ θ 4 ​ ​ ​ p = [ x p y p ϕ ] \boldsymbol{p} = \begin{bmatrix} x_p \\ y_p \\ \phi \end{bmatrix} p = ​ x p ​ y p ​ ϕ ​ ​ 上図での原点Oから点Aまでのベクトル O A ⃗ \vec{OA} O A は下記のように記述できます。 O A ⃗ = [ l 1 cos ⁡ θ 1 l 1 sin ⁡ θ 1 ] \vec{OA} = \begin{bmatrix} l_1 \cos \theta_1 \\ l_1 \sin \theta_1 \end{bmatrix} O A = [ l 1 ​ cos θ 1 ​ l 1 ​ sin θ 1 ​ ​ ] これと同様に、点Aから点B、点Bから点C、点Cから点Pまでのベクトルはそれぞれ下記のようになります。 A B ⃗ = [ l 2 cos ⁡ ( θ 1 + θ 2 ) l 2 sin ⁡ ( θ 1 + θ 2 ) ] \vec{AB} = \begin{bmatrix} l_2 \cos(\theta_1 + \theta_2) \\ l_2 \sin(\theta_1 + \theta_2) \end{bmatrix} A B = [ l 2 ​ cos ( θ 1 ​ + θ 2 ​ ) l 2 ​ sin ( θ 1 ​ + θ 2 ​ ) ​ ] B C ⃗ = [ l 3 cos ⁡ ( θ 1 + θ 2 + θ 3 ) l 3 sin ⁡ ( θ 1 + θ 2 + θ 3 ) ] \vec{BC} = \begin{bmatrix} l_3 \cos(\theta_1 + \theta_2 + \theta_3) \\ l_3 \sin(\theta_1 + \theta_2 + \theta_3) \end{bmatrix} B C = [ l 3 ​ cos ( θ 1 ​ + θ 2 ​ + θ 3 ​ ) l 3 ​ sin ( θ 1 ​ + θ 2 ​ + θ 3 ​ ) ​ ] C P ⃗ = [ l 4 cos ⁡ ( θ 1 + θ 2 + θ 3 + θ 4 ) l 4 sin ⁡ ( θ 1 + θ 2 + θ 3 + θ 4 ) ] \vec{CP} = \begin{bmatrix} l_4 \cos(\theta_1 + \theta_2 + \theta_3 + \theta_4) \\ l_4 \sin(\theta_1 + \theta_2 + \theta_3 + \theta_4) \end{bmatrix} C P = [ l 4 ​ cos ( θ 1 ​ + θ 2 ​ + θ 3 ​ + θ 4 ​ ) l 4 ​ sin ( θ 1 ​ + θ 2 ​ + θ 3 ​ + θ 4 ​ ) ​ ] これらを合わせると、XY平面上での点Pの座標は下記のようになります。 (式が長くなるためベクトル表記のままとしています) [ x p y p ] = O A ⃗ + A B ⃗ + B C ⃗ + C P ⃗ \begin{bmatrix} x_p \\ y_p \end{bmatrix} = \vec{OA} + \vec{AB} + \vec{BC} + \vec{CP} [ x p ​ y p ​ ​ ] = O A + A B + B C + C P また、ロボット先端の姿勢角度 ϕ \phi ϕ は下記のようになります。 ϕ = θ 1 + θ 2 + θ 3 + θ 4 \boldsymbol{\phi} = \theta_1 + \theta_2 + \theta_3 + \theta_4 ϕ = θ 1 ​ + θ 2 ​ + θ 3 ​ + θ 4 ​ 上式2つを合わせ、関節角度ベクトル θ \boldsymbol{\theta} θ から先端位置ベクトル p \boldsymbol{p} p への写像を f f f と定義すると、下式のように表現できます。 p = f ( θ ) \boldsymbol{p} = f(\boldsymbol{\theta}) p = f ( θ ) これがロボットの順運動学計算時に使用する式となります。 逆運動学計算 # 一方、逆運動学(Inverse Kinematics)は文字通り順運動学の逆を意味します。 つまり、 f f f の逆関数(=先端位置 p \boldsymbol{p} p から関節角度 θ \boldsymbol{\theta} θ への写像)を求める操作となります。 θ = f − 1 ( p ) \boldsymbol{\theta} = f^{-1}(\boldsymbol{p}) θ = f − 1 ( p ) 一般的に順運動学よりも逆運動学計算の方が計算量が多くなります。 ロボットの機構次第では解析的に解くことも可能ですが、一般的に自由度が多くなるほど逆運動学計算の難易度は難しくなります。 今回はこの計算をCeresSolverを使って数値的に解いてみます。 コード実装 # ディレクトリとCMakeLists.txtの作成 # srcディレクトリ直下に本問題を解くためのファイルを格納するディレクトリを作成します。 ディレクトリ名は 4dof-ik とします。 cd ~/ceres_solver_ws/src mkdir 4dof-ik cd 4dof-ik touch CMakeLists.txt 作成した 4dof-ik ディレクトリもサブディレクトリとして登録されるように、srcディレクトリ直下の CMakeLists.txt に下記を記載します。(最終行を追加してください) # simple-ols add_executable(simple-ols simple-ols.cpp) target_link_libraries(simple-ols absl::log_initialize Ceres::ceres) # 4dof-ikディレクトリをサブディレクトリとして登録する add_subdirectory(4dof-ik) データ構造体の定義 # まずはデータ構造をまとめるための構造体を定義します。 4dof-ik ディレクトリ内に、位置と姿勢をまとめた Pose 構造体と、ロボットの機構パラメータをまとめた KinematicsParameters 構造体を作成します。 cd ~/ceres_solver_ws/src/4dof-ik touch Pose.hpp touch KinematicsParameters.hpp 下記のように記述します。 Pose.hpp /// @brief 姿勢 struct Pose { /// @brief X座標 double x; /// @brief Y座標 double y; /// @brief 先端角度 double phi; Pose(double x_, double y_, double phi_) : x(x_), y(y_), phi(phi_){} }; KinematicsParameters.hpp /// @brief 機構パラメータ (リンク長) struct KinematicParameters { /// @brief 第1リンク長 double L1; /// @brief 第2リンク長 double L2; /// @brief 第3リンク長 double L3; /// @brief 第4リンク長 double L4; /// @brief コンストラクタ /// @param l1 第1リンク長 /// @param l2 第2リンク長 /// @param l3 第3リンク長 /// @param l4 第4リンク長 KinematicParameters(double l1, double l2, double l3, double l4) : L1(l1), L2(l2), L3(l3), L4(l4) {} }; 最適化計算の実装 # 次に、逆運動学計算を解くためのメインプログラムを作成します。 メインプログラム作成 touch 4dof-ik.cpp 最終的なファイル構造は下記のようになります。 ceres_solver_ws/ ├── CMakeLists.txt └── src/ ├── CMakeLists.txt ├── simple-ols.cpp └── 4dof-ik/ ├── CMakeLists.txt ├── 4dof-ik.cpp ├── KinematicsParameters.hpp └── Pose.hpp 4dof-ik.cpp の実装は下記の通りです。 4dof-ik.cpp #include <iostream> #include <ceres/ceres.h> #include <ceres/rotation.h> #include <cmath> #include "Pose.hpp" #include "KinematicsParameters.hpp" /// @brief 順運動学を行う /// @tparam T データ型 /// @param[in] kp 機構パラメータ /// @param[in] theta 関節角度ベクトル(配列) /// @param[out] x X座標 /// @param[out] y Y座標 /// @param[out] phi 先端角度 template <typename T> void compute_forward_kinematics(const KinematicParameters& kp, const T* const theta, T& x, T& y, T& phi) { x = T(kp.L1) * cos(theta[0]) + T(kp.L2) * cos(theta[0] + theta[1]) + T(kp.L3) * cos(theta[0] + theta[1] + theta[2]) + T(kp.L4) * cos(theta[0] + theta[1] + theta[2] + theta[3]); y = T(kp.L1) * sin(theta[0]) + T(kp.L2) * sin(theta[0] + theta[1]) + T(kp.L3) * sin(theta[0] + theta[1] + theta[2]) + T(kp.L4) * sin(theta[0] + theta[1] + theta[2] + theta[3]); phi = theta[0] + theta[1] + theta[2] + theta[3]; } /// @brief コスト struct IKCostFunction { Pose target_pose; KinematicParameters kp; /// @brief コンストラクタ /// @param pose 先端位置・姿勢 /// @param param 機構パラメータ IKCostFunction(const Pose& pose, const KinematicParameters& param) : target_pose(pose), kp(param) { } template<typename T> bool operator()(const T* const theta, T* residuals) const { T x, y, phi; compute_forward_kinematics(kp, theta, x, y, phi); residuals[0] = T(target_pose.x) - x; // X座標誤差 residuals[1] = T(target_pose.y) - y; // Y座標誤差 residuals[2] = T(target_pose.phi) - phi; // 姿勢角度誤差 return true; }; }; /// @brief メイン関数 int main(int argc, char** argv) { // 機構パラメータの定義 double l1 = 1.5; double l2 = 1.5; double l3 = 1.0; double l4 = 1.0; // 目標位置・姿勢の定義 double x_target = 3.2; double y_target = 0.8; double phi_target = -M_PI_4; // 初期値の設定 double theta[4] = {0.0, 0.0, 0.0, 0.0}; KinematicParameters kp(l1, l2, l3, l4); Pose target_pose{ x_target, y_target, phi_target }; // 最適化問題の定義 ceres::Problem problem; problem.AddResidualBlock( // 誤差次元数は3、最適化変数次元数は4 new ceres::AutoDiffCostFunction<IKCostFunction, 3, 4>( new IKCostFunction(target_pose, kp) ), nullptr, theta ); // 上下限値の適用(-pi ~ piとする) for (int i = 0; i < 4; i++) { problem.SetParameterLowerBound(theta, i, -M_PI); problem.SetParameterUpperBound(theta, i, M_PI); } // ソルバ設定 ceres::Solver::Options options; options.linear_solver_type = ceres::DENSE_QR; options.minimizer_progress_to_stdout = true; options.max_num_iterations = 100; // 最大反復回数 options.function_tolerance = 1e-6; // 収束判定の閾値 // 最適化計算を解く ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); // 計算結果の出力 std::cout << summary.BriefReport() << std::endl; if (summary.termination_type == ceres::CONVERGENCE) { std::cout << "✅ IK Solution Found! (Final Cost: " << summary.final_cost << ")\n"; } else { std::cout << "❌ IK Failed to Converge.\n"; } // 求まった関節角度の出力 std::cout << "theta[0] = " << theta[0] << std::endl; std::cout << "theta[1] = " << theta[1] << std::endl; std::cout << "theta[2] = " << theta[2] << std::endl; std::cout << "theta[3] = " << theta[3] << std::endl; // 求めた関節角度でどの位置・姿勢になるかを確かめる std::cout << "Check Forward kinematics calculation:" << std::endl; double x_result, y_result, phi_result; compute_forward_kinematics(kp, theta, x_result, y_result, phi_result); std::cout << "x = " << x_result << std::endl; std::cout << "y = " << y_result << std::endl; std::cout << "phi = " << phi_result << std::endl; return 0; } CMakeLists.txtに追加する # 4dof-ik ディレクトリ内のCMakeLists.txtに下記を記述します。 4dof-ik/CMakeLists.txt # 4dof-ik add_executable(4dof-ik 4dof-ik.cpp) target_link_libraries(4dof-ik absl::log_initialize Ceres::ceres) ビルド・実行 # ワークスペースに移動してビルドします。 cd ~/ceres_solver_ws cmake --build bin 実行してみます。 ./bin/4dof-ik 実行結果は下記のようになると思います。 (イテレーション結果は割愛します) Ceres Solver Report: Iterations: 7, Initial cost: 2.248425e+00, Final cost: 4.808292e-20, Termination: CONVERGENCE ✅ IK Solution Found! (Final Cost: 4.80829e-20) theta[0] = -0.414376 theta[1] = 1.25568 theta[2] = 0.609086 theta[3] = -2.23579 Check Forward kinematics calculation: x = 3.2 y = 0.8 phi = -0.785398 最終コストはほぼ0のため、指定した目標位置・姿勢を満たす関節角度を見つけることが出来ました! また、検算結果も正しそうです! 5. おわりに # 本記事では、CeresSolverを使用して最適化問題を解くプログラムを紹介しました。 本記事で紹介したサンプル以外にも、公式ページでは多くのサンプルプログラムが提供されています。 興味のある方は確認してみてください。 http://ceres-solver.org/nnls_tutorial.html#non-linear-least-squares また、今回作成したプログラムは、下記のリポジトリで公開しています。 https://github.com/hayat0-ota/CeresSolver_tutorial
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第9日目の記事です。 先月の11月に、 Spring Boot 4 と Spring Framework 7 がリリースされました。 Spring Boot 4ではnull安全性(Null-Safety)の強化が図られ、 JSpecify が標準採用されました。 そこで今回は、Spring Boot 4によるJSpecifyについて記事にしたいと思います。 JSpecifyとは # JSpecifyは、Javaにおけるnull安全性を標準化するための仕様と、そのアノテーションを提供するオープンソースプロジェクトです。 Javaプログラムのnull安全性を高め、異なるツールやライブラリ間での互換性の問題を解決するための共通的なルールブックのようなものと捉えていただければよいと思います。 JSR-305 の停滞やnull安全性に関したアノテーションの乱立を背景に、2021年にGoogleがJSpecifyプロジェクトを立ち上げ、JetBrainsやMeta、Sonarなどの複数の組織と協力して開発を進めてきました。 JSpecifyの1.0.0版がリリースされたのは2024年の7月です。 その後、Spring Boot 4.0.0-M2の リリースノート では、JSpecify準拠のアノテーションをコードベースに導入することが発表されました。 Spring Boot 3系でのnull安全性 # 筆者の勝手な想像ですが、Spring Boot 3系でJSpecifyを利用されていた開発プロジェクトは少ないのではないでしょうか。 筆者が関わってきた開発プロジェクトで、JSpecifyを利用したことは一度もありませんでした。また、筆者のまわりでもJSpecifyを利用された話は聞いたことがありません。 そのため、このタイミングでJSpecifyがくるとは、まったく予想していなかったというのが正直なところです。 では、Spring Boot 3系でのnull安全性はどのように実装していたかと言いますと、筆者はSpring Frameworkから提供されるアノテーションを利用していました。 基本的には、Spring Frameworkの @NonNullApi と @Nullable の組み合わせで利用することがほとんどでした。 まず、 package-info.java のパッケージ宣言に @NonNullApi アノテーションを付与し、そのパッケージ配下のクラス等でnullを許容するメソッドの戻り値や引数に @Nullable アノテーションを付与していました。 package-info.java @org.springframework.lang.NonNullApi package com.mamezou.blog.batch.util; // nullを空文字へ変換する。 public static String nullToEmpty(@Nullable String value) { return value == null ? "" : value; } // 空文字をnullへ変換する。 @Nullable public static String emptyToNull(@Nullable String value) { return (value == null || value.isEmpty()) ? null : value; } この他にも @NonNullFields アノテーションや @NonNull アノテーションが提供されていますが、主にはこの組み合わせで利用することが多かったと記憶しています。 JSpecifyの導入 # JSpecifyの利用方法を説明する前に、プロジェクトへのJSpecifyの導入について簡単に触れておきたいと思います。 Spring Boot 3系でJSpecifyを導入する際には、次のようにMavenプロジェクトの pom.xml にJSpecifyの依存関係を追加する必要がありました。 pom.xml <dependency> <groupId>org.jspecify</groupId> <artifactId>jspecify</artifactId> <version>1.0.0</version> </dependency> ですが、Spring Boot 4ではJSpecifyが標準採用されたため、個別にこのような依存関係を追加する必要がありません。 Spring BootのStarterライブラリが含まれていれば、JSpecifyを利用することができます。 記事の執筆に向けて作成したMavenプロジェクトの依存ライブラリを確認してみると、次のとおりとなりました。 $ mvn dependency:tree -Dincludes=org.jspecify:jspecify [INFO] --- dependency:3.9.0:tree (default-cli) @ mamezou-blog-batch --- [INFO] com.mamezou.blog:mamezou-blog-batch:jar:1.0.0 [INFO] \- org.springframework.boot:spring-boot-h2console:jar:4.0.0:compile [INFO] \- org.springframework.boot:spring-boot:jar:4.0.0:compile [INFO] \- org.springframework:spring-core:jar:7.0.1:compile [INFO] \- org.jspecify:jspecify:jar:1.0.0:compile なお、今回は ApplicationRunner を用いたCLIベースのバッチアプリケーションとしたため、Spring MVCなどのWeb開発に関したStarterライブラリは含んでおりません。 --> Information Spring Boot 4にて、Spring MVCのStarterライブラリの名称が変更されました。 Spring MVCのStarterライブラリは、Spring Boot 3系では spring-boot-starter-web でしたが、Spring Boot 4では spring-boot-starter-webmvc となります。 Spring Initializr などからプロジェクトを作成する際は特に気にすることもありませんが、直接 pom.xml や build.gradle を編集する際はご注意ください。 JSpecifyの利用 # それでは、JSpecifyが提供するアノテーションについて説明していきたいと思います。 JSpecifyにおけるnull安全性のアノテーションは、次の4つを利用します。 No. アノテーション 説明 1 @Nullable null許容であることを示すアノテーション。 2 @NonNull null非許容であることを示すアノテーション。 3 @NullMarked 一律でnull非許容とするためのアノテーション。 4 @NullUnmarked null安全性チェックの対象外とするアノテーション。 Nullable と NonNull # メソッドの戻り値や引数にnullを許容する場合は、 @Nullable アノテーションを使用します。 一方で、nullを非許容とする場合は、 @NonNull アノテーションを使用します。 利用方法としては、Spring Frameworkから提供されるアノテーションと同じです。 Spring Frameworkのアノテーションと比較して大きく異なるのが、JSpecifyの @Nullable と @NonNull は、アノテーションの定義における @Target に ElementType.TYPE_USE が指定されている点です。 org.springframework.lang.Nullable @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @CheckForNull @Deprecated(since = "7.0") @TypeQualifierNickname public @interface Nullable org.jspecify.annotations.Nullable @Documented @Target({ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) public @interface Nullable これにより、JSpecifyの @Nullable と @NonNull は、型が使用されているあらゆるところにアノテーションを付与することができます。 たとえば、次のような @Nullable の指定は、Spring Frameworkのアノテーションではビルドエラーとなりますが、JSpecifyのアノテーションではエラーになりません。 @NonNull public List<@Nullable String> getValues { // ----- <中略> ----- // } この場合、戻り値となる List の要素にnullが含まれてもよいことを示しています。 List そのものはnull非許容となります。 NullMarked # @NullMarked は、Spring Frameworkの @NonNullApi や @NonNullFields と同様となり、 package-info.java のパッケージ宣言に付与することで、パッケージ内のクラス等を一括でnull非許容として扱うことができます。 つまり、 @NullMarked を使用すれば、null非許容とする箇所に @NonNull を指定する必要がなくなりますね。 package-info.java @org.jspecify.annotations.NullMarked package com.mamezou.blog.batch.util; // @NonNull ← これは不要 public static String nullToEmpty(@Nullable String value) { return value == null ? "" : value; } 先述の「 Spring Boot 3系でのnull安全性 」のように、JSpecifyにおいても @NullMarked と @Nullable の組み合わせでnull安全性を実現することができます。 NullUnmarked # 最後に @NullUnmarked ですが、Spring Frameworkにこれ相応のアノテーションは存在せず、JSpecify特有のアノテーションとなります。 @NullUnmarked は、null安全性チェックの対象外とするためのアノテーションです。 次のようなメソッドは本来、戻り値と引数に @Nullable を付与する必要がありますが、 @NullUnmarked をクラスに付与することでnull安全性チェックの対象外とすることができます。 @NoArgsConstructor(access = AccessLevel.PRIVATE) @NullUnmarked public final class StringUtils { public static String emptyToNull(String value) { return (value == null || value.isEmpty()) ? null : value; } } @NullMarked と同様、 @NullUnmarked も package-info.java のパッケージ宣言に付与することができます。 しかし、パッケージ宣言に @NullUnmarked を指定してしまうと、null安全性チェックの対象外のスコープを広めてしまうため、パッケージ宣言は @NullMarked を基本とし、必要に応じてクラスまたはメソッドの単位に @NullUnmarked を指定するのがよいと筆者は考えます。 アノテーションの対応関係 # null安全性に関して、Spring Frameworkのアノテーションと比較しながら、JSpecifyのアノテーションについて説明してきました。 これらのアノテーションの対応関係をまとめると、下表のとおりとなります。 No. Spring Boot 4(JSpecify) Spring Boot 3系(Spring Framework) 1 org.jspecify.annotations.Nullable org.springframework.lang.Nullable 2 org.jspecify.annotations.NonNull org.springframework.lang.NonNull 3 org.jspecify.annotations.NullMarked org.springframework.lang.NonNullApi 4 org.jspecify.annotations.NullMarked org.springframework.lang.NonNullFields 5 org.jspecify.annotations.NullUnmarked 該当なし ここでひとつ、残念なお知らせがあります。 Spring Boot 4のリリースと併せて、Spring Frameworkから提供される @Nullable 、 @NonNull 、 @NonNullApi 、および @NonNullFields は非推奨( @Deprecated )となりました。 これらのJavadocを参照すると、すべてJSpecifyのアノテーションへの移行を促しています。 つまり、Spring Frameworkのアノテーションを利用している開発プロジェクトでは、Spring Bootのバージョンアップに伴ってJSpecifyのアノテーションへの置き換えが必要となります。 すでにSpring Boot 4へのバージョンアップが計画されている開発プロジェクトや、数年先にリリースを控えている開発プロジェクトでは、少なからず影響があるのではないでしょうか。 最後に # というわけでして、簡単ですがSpring Boot 4に標準採用されました「JSpecify」について説明させていただきました。 さらに詳細な利用方法については、JSpecify公式の ユーザーガイド などを参照いただきたく存じます。 Spring FrameworkやJakartaプロジェクト、JetBrains、Lombokなど、null安全性のアノテーションが乱立する中、JSpecifyプロジェクトによる標準化に向けた動きそのものは正しい取り組みと感じています。 しかし、その一方でSpring Bootを用いたアプリケーション開発において、Spring Frameworkのアノテーションが非推奨となることは予想できなかったですし、冒頭でも述べたとおりJSpecifyが巻き返してくるとは思ってもみませんでした。 Spring Bootのアップデートに伴い、Spring Frameworkにおけるnull安全性のアノテーションが非推奨となっても、アプリケーションの動作に直接影響することはないと思います。 ですが、非推奨のまま利用し続けるのは、ちょっと!ちょっとちょっと!という想いです。 また、多くの開発プロジェクトでも、非推奨のものを利用するな!が基本原則として定められていることと推察します。 ですので、これに関してはSpring Bootをアップデートする際、Spring FrameworkのアノテーションをJSpecifyのアノテーションに置き換えるというのが正しい対応と言えるでしょう。 今回は、JSpecifyのnull安全性といった少し地味目のネタではありましたが、最後までご覧いただき本当にありがとうございました。
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第8日目の記事です。 ヘキサゴナルアーキテクチャ(Ports & Adapters)って、なんとなくわかった気はするけど、どこか腑に落ちないところありませんか?私の場合はだいたい次の3点でした。 「依存は外側 → 内側」と言うけれど 入力ポートと実装で依存が逆向きに見える のはなぜ?それっていいの? 入力のアダプタはポートを実装していない のに 出力のアダプタはポートを実装している のはなにか気持ち悪い そもそも、 オニオンアーキテクチャと何が違うの? 今回の記事は、これらのモヤモヤを整理したときのメモを例を交えながら説明していきたいと思います。 1. 説明の例に使うヘキサゴナル構成 # 最初に今回の記事は次に示す「教科書的なヘキサゴナル」なパッケージ構造をもつ Spring Boot のTODOアプリを例に行います。 com.example.todohex ├─ TodoHexApplication … @SpringBootApplication │ ├─ domain … ドメインモデル(純粋Java) │ └─ Task.java │ ├─ application │ ├─ port │ │ ├─ in … 入力ポート(UseCase IF) │ │ │ ├─ CreateTaskUseCase.java │ │ │ └─ GetTaskUseCase.java │ │ └─ out … 出力ポート(Repo/Gateway IF) │ │ ├─ SaveTaskPort.java │ │ └─ LoadTaskPort.java │ └─ service … ユースケース実装 │ └─ TaskService.java │ ├─ adapter │ ├─ in │ │ └─ web … RESTアダプタ(入力側) │ │ ├─ TaskController.java │ │ ├─ TaskRequest.java │ │ └─ TaskResponse.java │ └─ out │ └─ persistence … 永続化アダプタ(出力側) │ ├─ TaskEntity.java │ ├─ SpringDataTaskRepository.java │ └─ TaskPersistenceAdapter.java └─ ... 2. 依存が逆になところがあるけどいいの? # では早速最初のモヤモヤへ。どこが「逆向きに見える」かですが、 教科書的に UseCase と Service を導出すると依存関係 [1] が逆になります。例をもとに図にすると次のように赤線の依存関係が右から左になっています。 一方でヘキサゴナルアーキテクチャに対する世の中的な解説では「モジュールの依存は外側 → 内側」といった説明がたくさん出てきます。ここで「えっ、 port と service の依存関係って外側から内側の反対に向いているけどこれっていいの?」というモヤモヤが沸いてきます。 そこで、いったん原典に立ち戻りヘキサゴナルアーキテクチャの提唱者であるアリスター・コバーン自身は何といっているかを振り返ると、彼が原典といえる 元記事 でいっていることは大ざっぱにいうと以下のようなことです。 アプリケーションは Port を通して外部と会話する その Port のプロトコルは「アプリケーションの API という形をとる」 ここでいう「API」は、メソッド呼び出しでもいいし, HTTP でも, メッセージングのプロトコルでもなんでも良い、というかなり抽象的なレベルの話をしていて、少なくとも原典では、 「入力ポートを インタフェースと実装クラス に分けろ」 「モジュールの依存矢印は 必ず外→内に向けろ 」 といった Java 的な「お作法レベル」のことはいっていません。また、最近の彼の スライド版 では型付き言語向けに “required interface” を宣言しよう Port の宣言用フォルダを用意しよう くらいはいっていますが、 依存の矢印ルールそのものには踏み込んでいません。 --> 結論:依存を外側から内側へは単なる都市伝説 アリスター・コバーンは依存の方向についてはなにもいっていません。むしろ、ポートにはインタフェースを出せといっているので、ポートとその実装の依存関係が逆になるのは自然です。この話しは「依存は外側のリングから内側のリングへだけ許可すべし」と明確にいっているクリーンアーキテクチャ [2] と同じコンテキストでヘキサゴナルアーキテクチャが語られることから生まれた都市伝説ではないかと思います。 ただ、 port.in と service を 1 つの「アプリケーション・コアの塊」として見てしまえば、 adapter.in → (port.in+service) → domain という「外→内」の構図となるので、いわゆる、クリーンアーキテクチャの1種とみても問題ないとも思います 3. アダプタってポートを実装しないの? # 次のモヤモヤはこれです。 in 側の adapter(Controller 等)は port.in を実装していない(赤の依存) out 側の adapter(DB や外部API)は port.out を実装している(青の依存) 言葉だけではわかりづらいので図で表すと次のようになっています。 同じアダプタでもポートを実装したりしなかったり、そもそも左右で対称じゃなく、なんとなく気持ち悪いですよね。というか正直これってホントにあってるの?と思ったりしたのは私だけでしょうか? 疑問があったら 原典 ということでコバーンがなんといっているか再度みてみましょう。 彼は Hexagonal を Ports & Adapters とも呼びますが、ここでいう Port と Adapter は「役割の名前」になっています。 Port: 「何のための会話か」を表す 論理的な接点 Adapter: その Port を、特定の技術(HTTP / CLI / DB / メール / ファイル…)に 接続する変換器 そして彼の スライド ではPort を Driving Ports(アプリケーションを「駆動する」側) Driven Ports(アプリケーションが「駆動される」側) に分けて説明しています。 この観点で見ると、 Driving Port 側 Adapter(UI / REST / Batch …)は「Port の定義に従ってコールする クライアント 」 Driven Port 側 Adapter(DB / メール / 外部API …)は「Port の定義を満たして処理する サーバ 」 になるため、 in 側の adapter が port を 実装していない out adapter が port を 実装 している という 非対称さは実は自然 なものとなります。 --> 結論:アダプタとポートは役割名で構文パターンのことではない Port / Adapter という名前は「入力=implements、出力=implements」という構文パターンのことではなく、 「会話の目的を表す窓口」と「外界との変換器」という役割を指している だけ。そう捉えると、実装有無の非対称さはそこまで気にならなくなります。 4. オニオンアーキテクチャと何が違うの? # 最後のモヤモヤはこれです。 結局、ヘキサゴナルとオニオンって、何がどう違うの? では、その違いが分かるようにそれぞれの全体構造をみてみたいと思います。 まずオニオンアーキテクチャ # オニオンアーキテクチャをラフに描くと次のような感じになります。 オニオンアーキテクチャの主眼は(今回の図からは分かりづらいですが、、) ドメインを中心に同心円状に層を作り 依存は外側 → 内側になるようにして ドメインを守る といったところになります。 次にヘキサゴナルアーキテクチャの構造 # 対してヘキサゴナル(Ports & Adapters)は、 境界(Port)にフォーカスしたアーキテクチャ といえます。 こうして2つを並べてみると、ヘキサゴナルアーキテクチャは構造的にオニオンアーキテクチャの application 部分とその境界まわりを port.in / port.out と adapter.in / adapter.out に細かく分解し、 入出力の境界(どこから入って、どこへ出ていくか)を強調したもの と見ることができます。 つまり一言でいうと: オニオンアーキテクチャ:層(Layer)で内側を守るアーキテクチャ ヘキサゴナルアーキテクチャ:ポートとアダプタで境界を強調するアーキテクチャ で、目指しているゴール自体はどちらも ドメイン中心 外界(UI/DB/外部システム)からの独立 テスタビリティ向上 といったところでかなり近いです。 --> 結論:ヘキサゴナルはオニオンの上級バージョン(ともいえる) 構造的な観点では「ヘキサゴナル=オニオンの application+境界部分を、port と adapter に分解して“入出力の境界を強調したバージョン”」といえます。 ただし、オニオンアーキテクチャは外側から内側に向かって層を成していく構造をその特徴にしているのに対して、ヘキサゴナルアーキテクチャは図の青線の関係が表すように外→内→外となる構造にすることで「どこから入って、どこへ出ていくか」を構造として強調することをその特徴としています。よって、もともとのコンセプトは異なるものとなります。 5. おわりに # ヘキサゴナルアーキテクチャにすることで確かにクリーンなアーキテクチャを実現することはできますが、それにはコストが掛かります。コバーン自身も スライド の中で次のことをいっています。 各 Port ごとにフィールドや DI 設定が増える 型付き言語では Port 用のインタフェースやフォルダ構成が必要 Configurator(構成ルート)の設計が必要 つまり、ヘキサゴナルアーキテクチャはきれいさと引き換えにクラスやインタフェースが増えます。個人的にドメインを分離したいだけならオニオンアーキテクチャでも十分なことも多いと思っています。 良いものが常に良い訳ではありません。自分たちが必要としているものはなにか?を考え、それにフィットするアーキテクチャを選択するのがアーキテクチャ設計では重要になります。 UML的に「依存関係」といった場合、2者間の一時的な関係(dependency)を指しますが、ここではUML的な意味ではなく、単なる使う使われる関係にあるものを「依存関係」という用語で表しています ↩︎ 筆者はボブおじさんがいっているクリーンアーキテクチャは「クリーンなアーキテクチャはかくあるべき!」的なコンセプトをいっているだけで、クリーンアーキテクチャなんていうアーキテクチャは存在しないと思っている派です。ですので、記事の中のクリーンアーキテクチャはドメインが技術詳細から分離・隔離されている「クリーンなアーキテクチャ」の意味で使用しています ↩︎
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第5日目の記事です。 はじめに # 先月26日 UMTP 主催の Modeling Forum 2025 が開催されました。 https://umtp-japan.org/event-seminar/mf2025/76554 今年は「AI時代のシン・データモデリングとは?」というテーマで多くの講演やパネルディスカッションが行われました。 筆者も当日 Zoom 参加して視聴させていただきました。この記事ではいくつかの講演の内容と感想をお伝えしたいと思います。 --> Information Modeling Forum 2025の講演やパネルディスカッションの模様は UMTP の YouTube チャンネル で視聴できます。 Modeling Forum を開催する UMTP (UMLモデリング推進協議会)は、UML モデリング技能認定試験を通したモデリング技術の普及活動、分析力・発想力・想像力強化のための「モデルベース思考法」の普及活動を行っています。UMTP は豆蔵の羽生田が会長を務めています。 https://umtp-japan.org/greeting 豆蔵デベロッパーサイトでは UMTP 認定試験の記事も公開していますので、取得を目指す方は参考にしていただければと思います。 /blogs/2023/12/11/umtp_l3_challenge/ 開催宣言 # UMTP 会長の羽生田さんから「UMTPシン・モデリング宣言」が発表されました。 当日発表された UMTP シン・モデリング宣言:講演資料のスクリーンショットから これからの時代の基本的なリテラシーは「よみ・かき・AI・モデリング」である。 と宣言し、見える化・仮説検証とアジャイル・AI との対話・モデルベース開発などでモデリングが果たす役割が謳われています。最後にリベラルアーツとしてのモデリングということで、モデリングを基本的なリテラシーとして位置付けています。 基調講演: 科学的思考におけるモデル:説明、創造、そして概念工学へ # 東京大学大学院情報学環・学際情報学府の植原亮准教授が基調講演をされました。 --> 講演の YouTube 動画と資料のリンクです。 MM2025-01 基調講演:科学的思考におけるモデル:説明、創造、そして概念工学へ MF2025 基調講演 資料 - UMTP 特定非営利活動法人UMLモデリング推進協議会 植原先生の著書「科学的思考入門」では、「第5章 科学的に説明するとはどういうことか」という章でモデルのことを扱っているそうです [1] 。 科学的思考入門 (講談社現代新書 2765) | 植原 亮 |本 | 通販 | Amazon 講演では、科学者が探求する分野としての「狭い意味での科学」と「広い意味での科学(ふだん使いの科学)」を定義し、それぞれにおいて使われるモデルの特徴が解説されました。前者の例としては遺伝子の分子モデルのように、モデルがメカニズムの説明・理解に直結しているもの、後者の例としては鉄道の路線図のように実際には複雑な現実をヒトの頭で扱いやすくするものが挙げられ、いずれも抽象化(abstraction)と理想化(idealization)が二大特徴であることが説明されました。 後半には概念工学の説明がありました。概念工学は、①分析と評価 → ②改訂 → ③社会実装 というプロセスで概念を扱うということで、自由意志や創造性などの概念の改訂について説明されていました。 感想: 概念工学というものは初耳学だった。ソフトウェアエンジニアリングのモデルは特定の業務の問題を解くということに特化しているけれども、分析と検証を繰り返し洗練(進化?)させていくところは似てるかも。 久々に大学の教養課程の講義を聴いているような気持ちになった(小並感)。 技術講演 AI readyデータ整備のためのデータモデリング # 株式会社データアーキテクト 代表取締役 真野正さんの講演です。 --> 講演の YouTube 動画は以下にあります。 MF2025-02 技術講演1:AI readyデータ整備のためのデータモデリング 「実践的データモデリング入門」の著者の方です(DB Magazine 懐かしい)。 実践的デ-タモデリング入門 (DB Magazine SELECTION) | 真野 正 |本 | 通販 | Amazon 企業のデータを生成 AI に与えて活用する上でデータマネージメントが重要であることを延べ、エンタープライズ・データモデリングをどう進めるかということがテーマの講演でした。 特に企業が使用するデータが、SoR の構造化データだけでなく、非構造データも増えている点、システムをまたがって整合性のあるデータアーキテクチャを構築する必要性とその進め方が解説されていました。 今後の展望としては、AIエージェントがデータアーキテクチャの維持、品質の担保を担い、データマネージメントは AI エージェントによる自律的なプロセスによって運用されるような世界観が提示されていました。 感想: 現実の AI 導入プロジェクトでも、AI にデータを食わせれば素晴らしい結果が得られると期待する人と、その前にデータを整備しないとガベージ・イン・ガベージアウトだよねという冷静派のせめぎ合い。これは、AI じゃなく BI の時も同じことが言われており、今後も重要課題であり続けるんだろうな。そもそもデータが膨大でシステムの数も多いので、AI に整理を手伝ってもらわないと無理だよなあ。 生成AI時代のドメインモデリング ― OOPとFPを超えて # 株式会社ウルフチーフ 代表取締役 川島義隆さんの講演です。 --> 講演の YouTube 動画は以下にあります。 MM2025-03 技術講演2:生成AI時代のドメインモデリング ― OOPとFPを超えて --> Information 川島さん Cosense を愛用されているようで、本講演で紹介された話題もまとめられています。 https://scrapbox.io/kawasima/ ドメインモデリングのレベルとして、概念・仕様・実装を区別し、仕様モデルの構成要素を以下のように定義しています。 仕様ドメインモデルは、小路の抽象レベルで記述された業務の データ 振る舞い で構成される そして、Liskov の手続き抽象に基づく仕様記述の書き方が紹介されました。 入力(データ抽象) 出力(データ抽象) requires: 入力が全域的 [2] でない場合に、その条件を書く modifies: 書き換えられる入力を書く effects: 使用される入力についての振る舞いを書く https://scrapbox.io/kawasima/%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E8%A8%98%E8%BF%B0%E3%83%9F%E3%83%8B%E8%A8%80%E8%AA%9E そして、仕様モデル駆動設計についてのお話。 アウトサイドイン開発 : 業務コアの概念がわからないまま、画面駆動/テーブル駆動で設計・開発する → 配線プログラミングになりがち インサイドアウト開発 : ①仕様モデル書く(人が) ②仕様モデルに沿った Presentation モデル(画面)を書く(AI が) ②仕様モデルに沿った Persistence モデル(DB)を書く(AI が) https://scrapbox.io/kawasima/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%80%81%E5%A4%96%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8B%E3%81%8B%3F%E5%86%85%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8B%E3%81%8B%3F 感想 DbC に似てる。実装言語でよい設計を頑張るのではなく、人は仕様記述に徹して、仕様を AI にレビューしてもらって、AI に実装言語でコードを書いてもらうのは筋がよさそう。実装言語を意識せず仕様を書ける人が強い世界。 仕様記述言語欲しいね。詳細設計書という名の、バグがあっても気づけないドキュメントを延々と書き続けている SIer は今だにいるので、そういうのが駆逐できたらいいなあ。 川島さんの仕様モデル駆動設計の本出たら買おう。 Excelデータ分析で学ぶディメンショナルモデリング~アジャイルデータモデリングへ向けて~ # 株式会社風音屋代表取締役ゆずたそ(横山翔)さんによる講演です。 --> 講演の YouTube 動画は以下にあります。 MF2025-04 技術講演3:Excelデータ分析で学ぶディメンショナルモデリング~アジャイルデータモデリングへ向けて~ 科学専門書) | ローレンス・コル, ジム・スタグニット, 打出紘基, 佐々木江亜, 土川稔生, 濱田大 ディメンショナルモデリングの話でした。ファクトとディメンションをどのように定義するか、業務内容がピボットし続ける時代だからこそ、アジャイルにデータ整備をするの大事というお話をされていました。 さらにデータサイエンティストはお高いが、データ分析は AI エージェントにかなりのアウトプットを期待できるようになっている。信頼できるデータソースを整備すれば、AI エージェントにやらせると安いし早いというお話もされてました。 感想 真野さんのエンタープライズデータモデリングの話にも似てるけど、アジャイルデータモデリングというところが特徴。 データ整備ができれば、データ分析は、AI エージェントにやってもらえばいいというのは分かる。 こんにちは!データモデリング # エークリッパー・インク代表 羽生章洋さんの講演です。 --> 講演の YouTube 動画は以下にあります。 MF2025-05 技術講演4:こんにちは!データモデリング 「楽々ERDレッスン」の羽生さんです。新しく要件定義の本を書かれたそうです。 こんにちは!要件定義①【情報活用とデータベース編】 (ビジネス×IT企画) 単行本(ソフトカバー) AI の登場によって、これまで IT 化の対象にできなかった領域についても低コストでアプリが作れるようになった。人間が上流工程を行うフロントローディングの時代。上流工程・要件定義が一層大事になっている。 IPA のデジタルスキル標準では、データベース設計をすべてのビジネスパーソンが理解すべきリテラシーとして位置付けているそうです。 羽生さんは現在デジタル人材のリテラシー向上支援をされていて、これまで IT の仕事をしてない人が「お前デジタル詳しいだろ、DX 担当な」と任命されて悩む、そういう人が多いそうです。 感想 DX 人材って不足してるんやなあ(棒)。 要件定義の中心にモデルを置きLLMが出力した要件に責任をもつ # 株式会社バリューソース 代表取締役社長 神崎善司さんの講演です。 --> 講演の YouTube 動画は以下にあります。 MF2025-06 技術講演5:要件定義の中心にモデルを置きLLMが出力した要件に責任をもつ 神崎さんは元豆蔵で筆者が入社した頃の上司でした。モデルベースのビジネスとシステムの可視化手法である RDRA を提唱して実践されています。 https://www.rdra.jp/%E3%83%9B%E3%83%BC%E3%83%A0 最近のプロジェクトでは AI エージェントに要件定義は全部任せるように舵を切ったそうです。AI が出力した要件定義を可視化して人間が検証するという段階になっているそうです。 AI にどうやって適切なコンテキストを用意し、AI の出力をいかに理解し軌道修正するかということが課題になっているとのことでした。 要件の可視化ツールとして RDRA Graph を使用して説明しておられました。 RDRA - RDRAGraphツール 感想 神崎さん AI エージェント使いこなしてるなあ。 RDRA のビューアの動きが面白くて、内容が入ってこない。 さいごに # この後の講演やパネルディスカッションは離脱してしまい聴けませんでしたが [3] 、視聴したどの講演も興味深く、AI エージェント時代を迎えてモデリングの重要性はさらに高くなっていることを確信できる内容でした。個人的には川島さんの講演がヒットでした。 Vibe Coding などの AI エージェントとの協調作業においては言語運用能力の重要性は認知されていると思いますが、AI とモデルを共有することで、AI の出力の質が高くなったり、認識齟齬が減ったりするのであればモデリングに取り組む価値はあるのではないでしょうか。 仕様駆動開発という言葉は出てきてますが、モデルベース仕様駆動開発の時代が来るのかもしれません。 本書は、「ふだん使い」の科学を身につけようというコンセプトの一般向け実用書に擬態した科学哲学の入門書とのことです。 ↩︎ 全域性:取りうる入力の全てに対して 対応する出力が存在する(ふるまいが関数として定義できる) ↩︎ パネルディスカッションの YouTube 動画も こちら に公開されています。 ↩︎
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第4日目の記事です。 はじめに # アジャイルグループの石田です。 近年、生成AIの進化は目覚ましく、私たちの働き方を大きく変えようとしています。豆蔵デベロッパーサイトでも、設計、実装、テストといったシステム開発の各工程で生成AIを活用する記事が豊富に投稿されています。(ご興味があれば #生成AI や #AIエージェント のタグもご覧ください)。 一方で、スクラムマスターとしてスクラムのプロセス自体にAIをどう活用するか、というテーマはまだ発展途上で、具体的な実践例も多くはありません。 そこで本連載では、全3回にわたり、スクラムマスターがAIとどう向き合い、チームを支援できるかのヒントを、私自身の実践を交えながら探っていきます。 連載の構成は以下の通りです。 第1回:導入(本記事) 第2回:透明性 第3回:検査・適応 第1回となる本記事では、スクラムガイド拡張パックで言及されている「人工知能」の考え方をベースに、スクラムにおけるAI活用の全体像と基本的な考え方をご紹介します。 スクラムガイド拡張パックとAI # スクラムガイド拡張パック は、2020年版スクラムガイドの包括的な副読本として作成され、2025年6月に公開された文書です。有志により日本語化もされています。 この拡張パックでは、変化し続ける市場や技術に対応するため、人工知能(AI)が「拡張パックにおけるスクラムの役割」のひとつとして定義されています。これは、AIが単なる技術トレンドではなく、スクラムの実践を強化する重要な要素として認識されていることを示しています。 AIがスクラムを強化する可能性については、下記のように紹介されています。 AIは以下を通じてスクラムを強化する可能性がある: 経験的プロセス制御:AI駆動の分析により、透明性・検査・適応が改善される。 認知的拡張:AIにより、人間のスクラムチームメンバーは戦略的・創造的・倫理的な検討に集中できる。 継続的な価値適応:AIは、リアルタイムのユーザーフィードバックとトレンドに基づき、プロダクトバックログアイテムの更新と再優先順位付けを行うことができる。 システム洞察:AIは隠れた相互依存関係を特定し、データに基づいた意思決定を改善する。 この連載では、特にスクラムマスターの役割と深く関わる一つ目の項目に焦点を当てます。 経験的プロセス制御:AI駆動の分析により、透明性・検査・適応が改善される。 (原文:Empirical Process Control: AI-driven analytics improve transparency, inspection, and adaptation.) スクラムマスターの重要な責務は、チームがスクラムの三本柱である「透明性・検査・適応」を実践できるよう支援することです。AIは、この経験的プロセス制御を、これまでになかったレベルで強化する可能性を秘めています。 まとめと次回予告 # スクラムガイド拡張パックの役割の中の「スクラムマスター」には、下記のユニークな一文が記載されています。 スクラムマスターは人間でなければならない。 (原文:The Scrum Master must be human.) これは、チームとの協働や複雑な人間関係の調整といった、人間にしか果たせない役割の重要性を示唆しています。AIはあくまでスクラムマスターの能力を拡張する「強力なパートナー」であり、私たち人間はより本質的な課題に集中できるようになります。 スクラムガイドの精神を元にスクラムに関するアドバイスをAIスクラムマスターからもらうことはできるかもしれませんが、決してスクラムマスターの仕事を代替するものではなく、その役割を強化し、チームの能力を最大限に引き出すためのパートナーである必要があります。 本記事では、AIをスクラムに「導入」するための第一歩として、その全体像と可能性を提示しました。 次回は、スクラムの三本柱の一つである「透明性」をテーマに、AIという新たな武器をどう活用し、プロジェクトの「見える化」を行うか、具体的な実践例を交えて探ります。
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第 3 日目の記事です! はじめに # Anthropicの公式ページをみたことがありますか? https://www.anthropic.com/ 配色の雰囲気が美しくて大変好きです。 さらに、最近 Claude Code とペアプログラミングをすることが多くなり、マークダウンファイルをプレビュー表示することが非常に多くなりました。要件定義やADR、技術記事の執筆などなど。 せっかく高頻度でマークダウンファイルを扱うなら、ぼくの好きなスタイルでプレビューさせたいものです。Anthropic の公式ページのような、読みやすく美しいプレビュー環境を構築できれば、執筆作業もより快適になるはず! しかし、一から自分でスタイリングを考えるのは非常に大変。。。 カラーパレット、タイポグラフィ、コンポーネントスタイルなど、考えるべき要素が多く、時間もかかります。 そんな折、Anthropic から Agent Skills という機能がリリースされました。 Agent Skills は、Claude の機能を拡張するための仕組みで、Claude に特定の作業を行う「スキル(技能)」を与えるためのツールセットです。 さらに、Anthropic が提供している公式の skills brand-guidelines なら、Anthropic のテーマを基盤に色々なスタイリングをしてくれるとのこと。これなら、自分で一から考える必要がなく、公式の洗練されたデザインを簡単に活用できるのではないかと考えました。 この記事では、Claude の Agent Skills を活用して、Markdown Preview Enhanced 拡張機能に Anthropic のブランドガイドラインを適用する方法を解説します。 この記事のゴール # この記事を読むことで、以下のことができるようになります。 Claude の Agent Skills について理解できる Agent Skills とは何か、どのように有効化して使うのかがわかります brand-guidelines スキルの使い方がわかる Anthropic のブランドガイドラインを基盤に、色々なスタイリングをしてくれるスキルです Markdown Preview Enhanced との連携方法がわかる VSCode の拡張機能と Anthropic のブランドガイドラインを組み合わせる手順を理解できます スタイル適用前 # スタイル適用前のプレビュー表示例は以下のとおりです。 スタイル適用前のプレビュー表示例 スタイル適用後 # スタイル適用後のプレビュー表示例は以下のとおりです。 スタイル適用後のプレビュー表示例 前提条件 # この記事では、以下の環境を前提としています。 使用環境 # OS : MacOS, Windows 11 VSCode : 最新版(執筆時点: 1.106.2 ) Claude アカウント : claude.ai にアクセス可能 必要な基礎知識 # VSCode の基本的な操作方法(ファイルの開き方、設定の変更方法など) claude.ai の基本的な使い方(チャット機能の利用経験) Markdown の基礎知識(見出し、リスト、コードブロックなど) 実施手順詳細 # 0. 実施手順で使用する主要な概念 # Agent Skills とは Agent Skills は、Claude の機能を拡張するための仕組みで、Claude に特定の作業を行う「スキル(技能)」を与えるためのツールセットです。この手順では、brand-guidelines スキルを使用するために Agent Skills を有効化する必要があります。Agent Skills は実験的な機能で、Pro プラン以上のユーザーが利用できます。 利用可能な公式スキル Anthropic が提供している公式スキルには、以下のようなものがあります: 最新のスキル一覧は Anthropic の公式スキルリポジトリ で確認できます。 ✅ドキュメント系スキル スキル名 説明 docx Word 文書の作成・編集(変更履歴、書式設定対応) pdf PDF の操作(抽出、作成、結合、フォーム処理) pptx PowerPoint プレゼンテーションの作成(レイアウト、テンプレート、チャート対応) xlsx Excel スプレッドシートの作成(数式、書式設定、データ分析) ✅クリエイティブ・デザイン系スキル スキル名 説明 brand-guidelines Anthropic の公式ブランドカラーと Typography をアーティファクトに適用(この記事で使用) theme-factory 10種類のプリセットテーマまたはカスタムテーマでアーティファクトをスタイリング canvas-design デザイン哲学を使用した .png / .pdf 形式のビジュアルアート作成 algorithmic-art p5.js を使用したジェネラティブアート作成(シード付きランダム、フローフィールド、パーティクルシステム) slack-gif-creator Slack のサイズ制限に最適化されたアニメーション GIF 作成 ✅開発・技術系スキル スキル名 説明 artifacts-builder React、Tailwind CSS、shadcn/ui を使用した複雑な HTML アーティファクト構築 mcp-builder 外部 API やサービスを統合する高品質な MCP サーバー作成ガイド webapp-testing Playwright を使用したローカル Web アプリケーションのテスト ✅エンタープライズ・コミュニケーション系スキル スキル名 説明 internal-comms ステータスレポート、ニュースレター、FAQ などの社内コミュニケーション文書作成 ✅メタスキル(スキル作成用) スキル名 説明 skill-creator Claude の機能を拡張する効果的なスキル作成ガイド template-skill 新しいスキル作成の出発点となる基本テンプレート スキルの呼び出し方法 Agent Skills を呼び出すには、チャットにて自然言語でスキル名を指定して依頼します: # 基本的な呼び出し方 brand-guidelines スキルを使って、○○を作成してください # スキル名を明示的に指定 skill: brand-guidelines を使用して、CSS ファイルを生成してください Claude がスキルを認識すると、必要な処理を自動的に実行してくれます。 brand-guidelines スキルとは brand-guidelines スキルは、Anthropic の公式テーマファイルを生成するための Agent Skills です。このスキルを使用することで、Anthropic の公式 UI で使用されているカラーパレットや Typography を含む CSS ファイルを生成できます。 生成される CSS ファイルの内容 生成される CSS ファイルには、以下のような要素が含まれています: カラーパレット : プライマリカラー、背景色、テキスト色など Typography : フォントファミリー、サイズ、行間など コンポーネントスタイル : ボタン、リンク、コードブロックなど ダークモード対応 : Light テーマと Dark テーマの両方に対応 Markdown Preview Enhanced とは Markdown Preview Enhanced は、VSCode の拡張機能です。 VSCode には標準で Markdown プレビュー機能が搭載されていますが、この拡張機能を使用する理由は以下の通りです: カスタム CSS の適用が容易 : 独自の CSS ファイルを簡単に読み込めます 豊富な機能 : 数式、図表、目次など、標準プレビューにない機能が多数あります 高いカスタマイズ性 : 細かい設定変更が可能です カスタムスタイル適用に関連する機能として、以下の特徴があります: style.less の編集 : 拡張機能専用のスタイルファイルを持っており、そこから外部 CSS を読み込めます リアルタイムプレビュー : スタイルの変更が即座に反映されます 複数のプレビューモード : さまざまな形式でのエクスポートにも対応しています この手順では、brand-guidelines スキルで生成した CSS ファイルを、Markdown Preview Enhanced の style.less ファイルから読み込むことで、Anthropic の公式テーマを適用します。 1. Agent Skills の有効化 # https://support.claude.com/ja/articles/12512180-claude%E3%81%A7%E3%82%B9%E3%82%AD%E3%83%AB%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B claude.ai にアクセス Claude の新規チャット画面 設定画面を開く 機能 > コード実行とファイル作成 を有効化 同画面にて、利用するスキル(brand-guidelines)を有効化 有効化の確認 claude.ai のチャット画面にて、利用可能なスキルを質問してみましょう。 2. Markdown Preview Enhanced 拡張機能のインストール # VSCode の拡張機能マーケットプレイスから、Markdown Preview Enhanced をインストールします。 https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced 3. brand-guidelines スキルによるスタイル作成 # claude.ai のチャット画面にて、brand-guidelines スキルを使用して、CSS ファイルを生成するよう依頼します。 プロンプトは適当ですが、例えば以下の内容で依頼してみました。 vscodeでmarkdownファイルをプレビューした際のスタイルにanthropicの公式テーマを適用したい。 プレビューにはmarkdown preview enhanced 拡張機能を利用する想定。 カスタムcssファイルを生成してほしい。 出力結果は以下のとおりです。 4. 生成されたスタイルの確認 # 今回生成された CSS ファイルは以下のとおりです。 anthropic-markdown-preview.css /* * Anthropic Brand Theme for Markdown Preview Enhanced * VS Code Extension Custom CSS * * Installation: * 1. Open VS Code Settings (Cmd/Ctrl + ,) * 2. Search for "Markdown Preview Enhanced: Style" * 3. Add the path to this file, or * 4. Copy this content to ~/.mume/style.less (or style.css) */ /* ============================================ Import Google Fonts ============================================ */ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&family=Lora:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&display=swap'); /* ============================================ CSS Variables - Anthropic Brand Colors ============================================ */ :root { /* Main Colors */ --anthropic-dark: #141413; --anthropic-light: #faf9f5; --anthropic-mid-gray: #b0aea5; --anthropic-light-gray: #e8e6dc; /* Accent Colors */ --anthropic-orange: #d97757; --anthropic-blue: #6a9bcc; --anthropic-green: #788c5d; /* Typography */ --font-heading: 'Poppins', Arial, sans-serif; --font-body: 'Lora', Georgia, serif; --font-code: 'JetBrains Mono', 'Fira Code', 'SF Mono', Consolas, monospace; } /* ============================================ Base Styles ============================================ */ .markdown-preview.markdown-preview { font-family: var(--font-body); font-size: 16px; line-height: 1.7; color: var(--anthropic-dark); background-color: var(--anthropic-light); padding: 2rem 3rem; max-width: 900px; margin: 0 auto; } /* ============================================ Headings - Poppins Font ============================================ */ .markdown-preview h1, .markdown-preview h2, .markdown-preview h3, .markdown-preview h4, .markdown-preview h5, .markdown-preview h6 { font-family: var(--font-heading); font-weight: 600; color: var(--anthropic-dark); margin-top: 1.5em; margin-bottom: 0.5em; line-height: 1.3; } .markdown-preview h1 { font-size: 2.25rem; font-weight: 700; border-bottom: 3px solid var(--anthropic-orange); padding-bottom: 0.4em; margin-top: 0; } .markdown-preview h2 { font-size: 1.75rem; border-bottom: 2px solid var(--anthropic-light-gray); padding-bottom: 0.3em; } .markdown-preview h3 { font-size: 1.4rem; color: var(--anthropic-dark); } .markdown-preview h4 { font-size: 1.2rem; color: var(--anthropic-mid-gray); } .markdown-preview h5, .markdown-preview h6 { font-size: 1rem; color: var(--anthropic-mid-gray); text-transform: uppercase; letter-spacing: 0.05em; } /* ============================================ Paragraphs and Body Text ============================================ */ .markdown-preview p { margin: 1em 0; text-align: justify; hyphens: auto; } /* ============================================ Links ============================================ */ .markdown-preview a { color: var(--anthropic-orange); text-decoration: none; border-bottom: 1px solid transparent; transition: border-color 0.2s ease, color 0.2s ease; } .markdown-preview a:hover { color: var(--anthropic-blue); border-bottom-color: var(--anthropic-blue); } .markdown-preview a:visited { color: var(--anthropic-green); } /* ============================================ Lists ============================================ */ .markdown-preview ul, .markdown-preview ol { margin: 1em 0; padding-left: 1.5em; } .markdown-preview li { margin: 0.4em 0; line-height: 1.6; } .markdown-preview ul li::marker { color: var(--anthropic-orange); } .markdown-preview ol li::marker { color: var(--anthropic-blue); font-weight: 600; } /* Nested lists */ .markdown-preview ul ul, .markdown-preview ol ol, .markdown-preview ul ol, .markdown-preview ol ul { margin: 0.3em 0; } /* Task lists */ .markdown-preview input[type="checkbox"] { accent-color: var(--anthropic-orange); margin-right: 0.5em; } /* ============================================ Blockquotes ============================================ */ .markdown-preview blockquote { margin: 1.5em 0; padding: 1em 1.5em; border-left: 4px solid var(--anthropic-orange); background-color: var(--anthropic-light-gray); color: var(--anthropic-dark); font-style: italic; border-radius: 0 8px 8px 0; } .markdown-preview blockquote p { margin: 0.5em 0; } .markdown-preview blockquote p:first-child { margin-top: 0; } .markdown-preview blockquote p:last-child { margin-bottom: 0; } /* Nested blockquotes */ .markdown-preview blockquote blockquote { border-left-color: var(--anthropic-blue); margin: 1em 0; } /* ============================================ Code - Inline and Blocks ============================================ */ .markdown-preview code { font-family: var(--font-code); font-size: 0.9em; } /* Inline code */ .markdown-preview :not(pre) > code { background-color: var(--anthropic-light-gray); color: var(--anthropic-dark); padding: 0.2em 0.4em; border-radius: 4px; border: 1px solid var(--anthropic-mid-gray); } /* Code blocks */ .markdown-preview pre { background-color: var(--anthropic-dark); color: var(--anthropic-light); padding: 1.25em 1.5em; border-radius: 8px; overflow-x: auto; margin: 1.5em 0; border: 1px solid var(--anthropic-mid-gray); } .markdown-preview pre code { background: none; border: none; padding: 0; color: inherit; font-size: 0.875em; line-height: 1.6; } /* ============================================ Syntax Highlighting (Custom Theme) ============================================ */ .markdown-preview pre .hljs-keyword, .markdown-preview pre .hljs-selector-tag, .markdown-preview pre .hljs-built_in { color: var(--anthropic-orange); } .markdown-preview pre .hljs-string, .markdown-preview pre .hljs-attr { color: var(--anthropic-green); } .markdown-preview pre .hljs-number, .markdown-preview pre .hljs-literal { color: var(--anthropic-blue); } .markdown-preview pre .hljs-comment { color: var(--anthropic-mid-gray); font-style: italic; } .markdown-preview pre .hljs-function, .markdown-preview pre .hljs-title { color: var(--anthropic-blue); } .markdown-preview pre .hljs-variable, .markdown-preview pre .hljs-params { color: var(--anthropic-light); } .markdown-preview pre .hljs-type, .markdown-preview pre .hljs-class { color: var(--anthropic-orange); } /* ============================================ Tables ============================================ */ .markdown-preview table { width: 100%; border-collapse: collapse; margin: 1.5em 0; font-size: 0.95em; } .markdown-preview table th, .markdown-preview table td { padding: 0.75em 1em; text-align: left; border: 1px solid var(--anthropic-light-gray); } .markdown-preview table th { font-family: var(--font-heading); font-weight: 600; background-color: var(--anthropic-dark); color: var(--anthropic-light); border-color: var(--anthropic-dark); } .markdown-preview table tr:nth-child(even) { background-color: var(--anthropic-light-gray); } .markdown-preview table tr:hover { background-color: rgba(217, 119, 87, 0.1); } /* ============================================ Horizontal Rules ============================================ */ .markdown-preview hr { border: none; height: 2px; background: linear-gradient( 90deg, var(--anthropic-orange), var(--anthropic-blue), var(--anthropic-green) ); margin: 2em 0; border-radius: 1px; } /* ============================================ Images ============================================ */ .markdown-preview img { max-width: 100%; height: auto; border-radius: 8px; margin: 1em 0; box-shadow: 0 4px 12px rgba(20, 20, 19, 0.1); } /* ============================================ Footnotes ============================================ */ .markdown-preview .footnotes { margin-top: 3em; padding-top: 1.5em; border-top: 2px solid var(--anthropic-light-gray); font-size: 0.9em; color: var(--anthropic-mid-gray); } .markdown-preview .footnote-ref { color: var(--anthropic-orange); font-weight: 600; } /* ============================================ Definition Lists ============================================ */ .markdown-preview dl { margin: 1em 0; } .markdown-preview dt { font-family: var(--font-heading); font-weight: 600; color: var(--anthropic-dark); margin-top: 1em; } .markdown-preview dd { margin-left: 1.5em; color: var(--anthropic-mid-gray); } /* ============================================ Keyboard Keys ============================================ */ .markdown-preview kbd { font-family: var(--font-code); font-size: 0.85em; padding: 0.2em 0.5em; background-color: var(--anthropic-light-gray); border: 1px solid var(--anthropic-mid-gray); border-radius: 4px; box-shadow: 0 2px 0 var(--anthropic-mid-gray); } /* ============================================ Mark / Highlight ============================================ */ .markdown-preview mark { background-color: rgba(217, 119, 87, 0.3); color: var(--anthropic-dark); padding: 0.1em 0.3em; border-radius: 2px; } /* ============================================ Abbreviations ============================================ */ .markdown-preview abbr { text-decoration: underline dotted var(--anthropic-blue); cursor: help; } /* ============================================ Selection ============================================ */ .markdown-preview ::selection { background-color: rgba(106, 155, 204, 0.3); color: var(--anthropic-dark); } /* ============================================ Scrollbar Styling ============================================ */ .markdown-preview ::-webkit-scrollbar { width: 8px; height: 8px; } .markdown-preview ::-webkit-scrollbar-track { background: var(--anthropic-light-gray); border-radius: 4px; } .markdown-preview ::-webkit-scrollbar-thumb { background: var(--anthropic-mid-gray); border-radius: 4px; } .markdown-preview ::-webkit-scrollbar-thumb:hover { background: var(--anthropic-orange); } /* ============================================ Print Styles ============================================ */ @media print { .markdown-preview.markdown-preview { background-color: white; color: black; padding: 0; } .markdown-preview a { color: var(--anthropic-dark); text-decoration: underline; } .markdown-preview pre { border: 1px solid var(--anthropic-mid-gray); background-color: var(--anthropic-light-gray); color: var(--anthropic-dark); } } /* ============================================ Mermaid Diagrams ============================================ */ .markdown-preview .mermaid { background-color: var(--anthropic-light); padding: 1em; border-radius: 8px; text-align: center; } /* ============================================ Math (KaTeX/MathJax) ============================================ */ .markdown-preview .katex, .markdown-preview .MathJax { font-size: 1.1em; } .markdown-preview .katex-display, .markdown-preview .MathJax_Display { margin: 1.5em 0; padding: 1em; background-color: var(--anthropic-light-gray); border-radius: 8px; overflow-x: auto; } /* ============================================ Admonitions / Callouts ============================================ */ .markdown-preview .admonition, .markdown-preview .callout { margin: 1.5em 0; padding: 1em 1.5em; border-radius: 8px; border-left: 4px solid; } .markdown-preview .admonition.note, .markdown-preview .callout.note { background-color: rgba(106, 155, 204, 0.1); border-left-color: var(--anthropic-blue); } .markdown-preview .admonition.warning, .markdown-preview .callout.warning { background-color: rgba(217, 119, 87, 0.1); border-left-color: var(--anthropic-orange); } .markdown-preview .admonition.tip, .markdown-preview .callout.tip { background-color: rgba(120, 140, 93, 0.1); border-left-color: var(--anthropic-green); } /* ============================================ TOC (Table of Contents) ============================================ */ .markdown-preview .md-toc { background-color: var(--anthropic-light-gray); padding: 1.5em; border-radius: 8px; margin: 1.5em 0; } .markdown-preview .md-toc-content { font-family: var(--font-heading); } .markdown-preview .md-toc a { color: var(--anthropic-dark); } .markdown-preview .md-toc a:hover { color: var(--anthropic-orange); } 5. Markdown Preview Enhanced への適用 # コマンドパレットにて、 Markdown Preview Enhanced: Customize CSS (Global) を実行 表示された style.less ファイルに、生成された CSS ファイルの内容を追記し保存 6. 動作確認と微調整 # プレビューに適用されたスタイルを確認して、気になる点があれば微調整します。 スタイルの微調整は、style.less ファイルを直接編集することで実施可能です。 あるいは、claude.ai のチャット画面にて、style.less ファイルの内容を変更するよう依頼もできます。 ぼくはフォントを少し変更しました。 --> Information Markdown Preview Enhanced には、拡張機能の設定画面からプレビューテーマを指定する機能があります( Markdown Preview Enhanced: Preview Theme / Markdown-preview-enhanced: Code Block Theme 設定)。この設定と style.less の内容が競合すると、意図したスタイルが適用されない場合があります。 もしスタイルが思い通りに反映されない場合は、これらの設定を変更してみてください。 まとめ # この記事では、Claude の Agent Skills を活用して、Markdown プレビューに Anthropic のスタイルを適用する方法を解説しました。 実現できたこと # 今回の設定により、以下のことが実現できました: Agent Skills の理解 : Claude の Agent Skills を有効化し、使いこなす方法を学びました brand-guidelines スキルの活用 : 公式スタイルの CSS ファイルを簡単に生成できるようになりました 統一された執筆環境 : Anthropic の公式スタイルを適用したプレビュー環境を構築しました さらなる発展:カスタムスキルの作成 # この記事では公式の brand-guidelines スキルを使用しましたが、別の公式スキル skill-creator を使えば、自分だけのオリジナルスキルを作成することもできます。 例えば、以下のようなカスタムスキルを作成できます: 自社のブランドガイドラインに基づいた CSS 生成スキル 特定のフレームワーク向けのコンポーネント生成スキル プロジェクト固有のドキュメントテンプレート生成スキル skill-creator スキルの使い方は、claude.ai のチャットで以下のように依頼するだけです。 skill-creator スキルを使って、○○を行うカスタムスキルを作成してください 詳細は Anthropic の公式スキルリポジトリ を参照してください。公式の skill-creator や template-skill を参考にすることで、効果的なスキルを作成できます。 なお、skill-creator を利用する場合は、brand-guidelines スキルを有効化したように、skill-creator スキルを有効化することをお忘れなく! 最後までお読みいただき、ありがとうございました。この記事が、あなたの執筆環境をより快適にする助けになれば幸いです。🎨✨
これは 豆蔵デベロッパーサイトアドベントカレンダー2025 第 2 日目の記事です! はじめに # 書籍 Think Again 僕がとても好きな書籍です。 この書籍では、 考え直す ことの重要性について書かれています。 本書の目次を以下に示しました。 Part1: 自分の考えを再考する方法 Part2: 相手に再考を促す方法 Part3: 学び、再考し続ける社会・組織を創造する方法 Part4: 結論 再考することの重要性を、 Part1 では自己に、 Part2 では他者に、 Part3 ではチーム・組織に焦点を当てて説明されています。 この記事では、 Part1 でとても印象的な下記内容について紹介したいです。 再考サイクルとは何か? 対比となる過信サイクルとは何か? 再考サイクルを継続するために必要なことは何か? 「化石化した知識」を後生大事にしていないか? # 情報技術の進歩に伴い、人の知識もどんどん増加しています。 本書によると、 1950 年を見てみると、医療の知識は 1900 年から 50 年の歳月をかけて倍増した。 それが 1980 年までには 7 年ごとのペースで、そして 2010 年までにはその半分の歳月で倍増するようになった。 とのこと。 従って、社会の変化に伴い、私たちは今まで当たり前と思っていたことをより頻繁に再考する必要があリます。 だけどなかなかそれができない。。 牧師、検察官、政治家 - 誰もが持つ 3 つの思考モード # なぜ自分の知識や見解を再考できないのか。 それは、無意識に切り替わる以下 3 つの思考モードが原因とのこと。 牧師モード: 自分の信念がぐらついている時、理想を守り確固としたものにするため、他者を説教しようとする。 検察官モード: 他者の推論に矛盾を感じれば、相手の間違いを明らかにするため論拠を探す。 政治家モード: 多くの人を味方につけたい時は、支持層の是認を獲得するためにキャンペーンやロビー活動を行う そして、これらの思考モードは互いに 循環 してしまいます。 具体的なシチュエーションを考えると、こんな感じでしょうか 👇 シチュエーション SNS 経由で知り合った「投資アドバイザー」から勧められた暗号資産投資。 「いま始めれば月利 20 %、限定枠あり」と言われ、初期資金を投入した男性。 月利 20 % はヤバいですね。。相場を逸脱しています。。 牧師モード # 自尊心が確信を生む これは自分が見つけた正しい投資法だ。 自分の考えが正しいことをちゃんと説明すれば、みんなも納得するはず。 真実はすでに見つかっていると考える。 いかに正しいかを証明するために、他者を説得しようとする。 検察官モード # 確信がバイアスを生む 仕組みを理解していない周りが間違っている。 現にこんなに利益が出ている。 相手の指摘を"無知"や"矛盾"として否定する。 自分の考えを肯定する情報しか見えなくなる。 政治家モード # バイアスが是認を生む みんながやっているから、間違いない。 反対している人は、仕組みを理解していない無知な人たちだ。 同じ考えの人たちに囲まれて安心する。 反対意見は"無知な人の意見"として無視する。 本書では、 この循環を 過信サイクル と呼んでいます。 過信サイクルは、以下に没頭するあまり 自分の見解が間違っているかもしれないと再考しなくなってしまう 点で危険です。 自分の信念を貫くこと 他者の間違いを指摘すること 多くの支持を獲得すること 仮説、実験、結果、検証 - 科学者の思考モード # では、暗号資産投資で過信サイクルに陥った男性は、どのように考えれば良かったのか。 例えば、 月利 20 % の根拠は何かと考えるべきだった。 暗号資産投資に関するより多くの見解を集めるべきだった。 多額の資金を投じる前に、少額で実験してみるべきだった。 などでしょうか。 抽象化すると、 科学者の思考モード が必要だったということです。 科学者の思考モード は、以下の 4 つの要素で構成されています。 仮説: 自分の考えを言語化する。 例: 暗号資産投資は、月利 20 % の利益を生む。 実験: 自分の考えを実験する。 例: 少額で投資してみる。 結果: 実験から得られた結果を観察する。 例: 投資額と利益額を記録する。 検証: 結果をもとに仮説を検証する。 例: 月利 20 % の根拠は何か? 他の投資法と比較してどうか? この 4 つの要素を繰り返すことができれば、過信サイクルから抜け出して、 自分の考えを再考できるようになるはずです。 再考プロセスの循環 - 再考サイクル # 科学者の思考モードによる再考プロセスにも、過信サイクルと同じような循環があるそうです。 本書ではこの循環を 再考サイクル と呼び、以下の 4 つの要素で整理しています。 謙虚さ: 知的に謙虚である。 無知を自覚する。 懐疑: 自分の考えに疑問を持つ。 「本当にこれで良いのか?」と問い直す。 好奇心: 自分が持たない情報を知りたいと思う。 見えなかった視点や情報にワクワクする。 発見: 疑問の先に、新しい視点や情報を手にする。 自分の想定より世界が広かったことを知る。 発見 により「学ぶべきこと、発見すべきことはまだたくさんある」と自覚することで、 謙虚さ を保つことができるようになります。 --> Information 人が再考しない理由 これまでのやり方と異なる そんなのできっこない 自分の経験と異なる 複雑すぎて考えたくない 過信サイクル vs 再考サイクル # 過信サイクルと再考サイクルは、以下のように対比されます。 過信サイクル 自尊心 → 確信 → バイアス → 是認 graph LR A[**自尊心**<br>自分の考えこそが<br>正しい] B[**確信**<br>疑う余地はないと<br>思い込む] C[**バイアス**<br>都合の良い情報しか<br>見えなくなる] D[**是認**<br>賛同してくれる人だけを<br>信じる] A --> B B --> C C --> D D --> A style A fill:#fdf5e6,stroke:#d2691e,stroke-width:2px style B fill:#f0f8ff,stroke:#4682b4,stroke-width:2px style C fill:#e6ffe6,stroke:#2e8b57,stroke-width:2px style D fill:#f5f5ff,stroke:#6a5acd,stroke-width:2px 再考サイクル 謙虚さ → 懐疑 → 好奇心 → 発見 graph LR A[**謙虚さ**<br>自分の無知を認める] B[**懐疑**<br>今の見解に疑問を持つ] C[**好奇心**<br>もっと知りたいという衝動] D[**発見**<br>新しい視点に気づく] A --> B B --> C C --> D D --> A style A fill:#fdf5e6,stroke:#d2691e,stroke-width:2px style B fill:#f0f8ff,stroke:#4682b4,stroke-width:2px style C fill:#e6ffe6,stroke:#2e8b57,stroke-width:2px style D fill:#f5f5ff,stroke:#6a5acd,stroke-width:2px また、同じ状況に対しても、自分がどちらのサイクルにいるかで思考や行動が変わります。 過信サイクル 再考サイクル 自分の考えを変える 心の弱わさの表れ 知的誠実さの表れ 他者に説得される 負けた気になる 真実に 1 歩近づいた 考えを変える根拠 他人の反応 理性や証拠 再考サイクルを継続するために必要なこと - 自信に満ちた謙虚さ # 過信サイクルに陥らず、再考サイクルを継続するためにはどうすれば良いか。 本書では、 バランスの取れた自信と謙虚さ が重要だと述べられています。 自信 自己信頼度 自分をどのくらい信頼しているか 注意 ! 自分のやり方をどれほど確信しているかではない 謙虚さ しっかりした知識や能力、つまり自分の過ちや不確実さを認識する力 注意 ! 自信を控えめに持つことではない ここで重要なのは、 自信と謙虚さは両立する ということです。 自信を持つことは、謙虚さを失うこと 謙虚さを持つことは、自信を失うこと ではありません。 将来の目標に達するのに十分な能力が備わっていると自信を持ちながら、 そのための正しい手段は何かと現在の自分に問う謙虚さを持つことは可能です。 つまり、自己の能力を信じながら、自分の解決方法が正しくない可能性、問題自体を誤解している可能性を認める。 そこから疑問が生じれば、既存の知識を再評価するようになり、ほどほどの自信があれば、新しい知識を追い求めることができるようになります。 自分の知識に対してではなく、自分の学ぶ能力を強く信じている状態 この状態が、 自信に満ちた謙虚さ を持った状態です。 おわりに - ソフトウェアエンジニアとしての知識と再考サイクル # ソフトウェアエンジニアは、自分の知識を武器にする職業だと思います。技術的な判断や設計の選択において、これまでに培ってきた知識や経験が意思決定の基盤となります。 最近、 リファクタリング 既存のコードを安全に改善する(第2版) 単体テストの考え方/使い方 現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法 良いコード/悪いコードで学ぶ設計入門-保守しやすい 成長し続けるコードの書き方 等々を読み、どんな設計が良い設計なのか、どんなコードが良いコードなのかを学びました。 特に、変更に強いシステムを開発するためには、手続き型のプログラミングではなく、ドメインモデルをしっかり設計してオブジェクト指向でプログラミングすることが重要だと感じました。 これらの書籍の内容に非常に納得できましたが、同時にある懸念も抱きました。それは、これら書籍の内容に矛盾する知識を排除してしまうのではないかということです。 例えば、単体テストの考え方には「ロンドン学派」と「古典学派」という対立したアプローチが存在するそうです。 このどちらか一方を「正解」として学び、もう一方を「間違い」として排除してしまうと、正解と考える学派以外の知識を受け入れられなくなってしまいそうです。 絶対的な正解があるわけではなく、場面場面に合わせた最適な手段があるだけだと思います。 自分の持つ知識をこの世の正解と捉えてしまうと、自分の持つ知識以外はすべて間違いと考えてしまいかねません。これはまさに過信サイクルの初期フェーズ、 自尊心 の始まりです。 自分の持つ知識はあくまで数ある手段の一つであり、具体的な場面における最適な手段かどうかはわからないと考えることで、再考サイクルを回すことができるのではと思います。 謙虚さを持って自分の知識に疑問を持ち、異なるアプローチや思想に好奇心を持ち、新しい視点を発見することで、より良い判断ができるようになるのではないでしょうか。