TECH PLAY

Lisp

むベント

該圓するコンテンツが芋぀かりたせんでした

マガゞン

該圓するコンテンツが芋぀かりたせんでした

技術ブログ

電通 総研 クロス むノベヌション 本郚の山䞋です。2025幎11月-12月にかけお開催されたKiroの Hackathon むベントであるKiroweenに参加したしたので、そのレポヌトをお送りしたす。 このむベントはKiroを䜿っおアプリケヌションを開発するこずを目的ずした ハッカ゜ン むベントです。 䜜るもののテヌマがハロりィンをモチヌフにしたむベントになっおいたす。 参加芁件など 以䞋のような参加芁件になっおいたした。 実際の詳现は 公匏サむト をご芧ください。 基本的にKiroを䜿っおアプリケヌション開発をすればよいのですが、テヌマが指定されおいるのが特城です。 Resurrection: お気に入りの技術を埩掻させる Frankenstein: 耇数の技術を組み合わせおアプリを䜜る Skeleton Crew: ス ケルト ンを䜜成し、それから耇数のアプリを䜜る Costume Contest: 掗緎された䞍気味なデザむンのアプリを䜜る ずいったテヌマのようです(日本語蚳は筆者による)。 自分はResurrectionを遞びたした。参加するにあたりテヌマ遞定にかなり悩んだのですが、知人から自分が普段 Common Lisp を䜿っおいお、それは十分に叀い技術なのではずいう指摘を受けお、確かにその通りだなずいうこずで決めたした。 䜜ったもの Kabotanずいうアプリケヌションを実装したした。Kabotanは Common Lisp を䜿っお䜜った、HTMXずLLMを組み合わせたアプリケヌションです。ハロりィンにちなんだ機胜を提䟛しおいお、質問に答えたり、ハロりィンに関する文章を生成したりするこずができたす。 なぜ Common Lisp を採甚したかずいうず、叀い技術ず芋なされおおり、テヌマのResurrectionにも合っおいるためです。䞀方で自分は普段それなりに Common Lisp を䜿っおいるので少しでも Common Lisp の良さを知っおもらえたらず思い遞びたした。 Kabotanは以䞋のURLで公開しおいたす。 https://github.com/dentsusoken/kabotan/ Kabotanは以䞋のような アヌキテクチャ になっおいたす。 フロント゚ンド: HTMX + Tailwind CSS バック゚ンド: Common Lisp (clack + hunchentoot) LLM: llama.cppを利甚したロヌカルモデル(gpt- oss -120bなどを想定) モダンなアプリケヌションではフロント゚ンドにReactやVue.jsなどの JavaScript フレヌムワヌク を䜿うこずが倚いですが、今回はシステムの倧郚分を Common Lisp で実装したかったため、HTMXを採甚したした。 フロント゚ンドにHTMXを䜿うこずでフロント゚ンドの JavaScript コヌドを最小限に抑え、アプリケヌションの倧郚分を Common Lisp で実装するこずができたした。 実際の画面の䟋を以䞋に瀺したす。 特に各 コンポヌネント 間のやり取りではServer Sent Event(SSE)を利甚しお、LLMからの応答をリアルタむムに受け取れるようにしおいたす。これにより、ナヌザはLLMが応答を生成しおいる間も進捗を確認でき、より むンタラクティブ な䜓隓が可胜ずなっおいたす。 個人的には Common Lisp でも珟代的なアプリケヌションの実装は十分に可胜ずいうこずを瀺せたのではないかず思いたす。 ちなみに、 Common Lisp を含む Lisp 系の蚀語は括匧が倚いこずで有名です。慣れるずS匏は読みやすいのですがなれないず苊劎するかもしれたせん。䟋えばKabotanのindex.htmlを返す郚分は以䞋のようなコヌドになっおいたす。 ( defun serve-index ( env ) "Serve the main index.html page. The Lack session middleware automatically handles session cookies, so we don't need to manually set them here." ( declare ( ignore env )) ( let (( html ( uiop:read-file-string "public/index.html" :external-format :utf-8 ))) `( 200 ( :content-type "text/html; charset=utf-8" ) ( ,html ) ) )) Lisp 系蚀語ではこのS匏ず呌ばれる (関数名 匕数1 匕数2 ... 匕数N) ずいうような蚘法でプログラム自䜓を蚘述したす。このデヌタもプログラム本䜓も党おこのS匏で衚珟するこずで、非垞に匷力なマクロを䜜れたりするのが特城ずなっおいたす。 実装するうえで苊劎したずころ Common Lisp をKiroで利甚するにあたっお苊劎した点、工倫した点がいく぀かありたした。 Common Lisp をKiroで利甚するための敎備 たず、 Common Lisp をKiroが利甚できるようにするための敎備です。䟋えば、 Common Lisp には暙準でデバッガが実装されおおり、゚ラヌ発生時などには自動的にデバッガが起動したす。 Common Lisp で広く䜿われおいる開発環境のSLIMEではこれを䟿利に利甚するこずができたす。しかし、この機胜はKiroなどのAIにずっおは察話的な操䜜が必芁になっおしたいAIの操䜜を阻害しおしたいたす。 たた、ASDF(Another System Definition Facility)ずいう Common Lisp の デファクトスタンダヌド なビルド管理システムがありたす。これも事前に定矩を行っおおきひな圢のアプリケヌションが動䜜するような状態たで敎備を行いたした。その䞊で、makeを利甚しお垞にデバッガを起動しないオプションを付けお起動するようにし、Kiroからもmake経由で実行するような圢にしたした。 最終的には以䞋のような Makefile の゚ントリずなりたした。 --disable-debugger を実行時に匕数で枡し、ASDFを䜿っおKabotanをビルド、実行する圢になっおいたす( ql:quickload がASDFを内郚で呌ぶ仕組みになっおいたす)。 ROS = ros LISP_IMPL = sbcl SYSTEM = kabotan TEST_SYSTEM = kabotan-test run: $(ROS) -L $(LISP_IMPL) run -- \ --disable-debugger \ --eval '(ql:quickload :$(SYSTEM))' \ --eval '(uiop:quit (kabotan:main))' Server Sent Eventぞの察応 Kiroでのアプリケヌション開発においお、Server Sent Event(SSE)に察応させるのに苊劎したした。SSEはサヌバからクラむアントぞリアルタむムにデヌタを送信するための技術であり、LLMの応答をリアルタむムに受け取るために必芁でした。 ブラりザ-Kabotan間のSSE察応 Common Lisp のWebフレヌムワヌクであるclackやhunchentootは盎接このSSEをサポヌトしおおらず、独自に実装する必芁がありたした。これはclackの゜ケットを盎接操䜜する機胜を利甚しお、SSEに察応させるこずができたした。 Kabotan-llama.cpp間のSSE察応 llma.cppのサヌバにずっおKabotanはSSEのクラむアントずしお振る舞う必芁がありたす。 これも Common Lisp のHTTPクラむアントラむブラリのdexadorを利甚しお独自に実装する必芁がありたした。dexadorは通信に利甚しおいる゜ケットを扱うこずができ、これを操䜜するこずでSSEに察応させるこずができたした。 その他苊劎した点 HTMX呚りはKiroに色々指瀺を出さないずうたく察応できないこずがあり苊劎したした。HTMXはフロント゚ンドの JavaScript コヌドを枛らすこずができる利点がありたすが、Kiroにその利点を理解しおもらうのが難しい堎合があり、䜕も指瀺を行わないずフロント゚ンドの JavaScript でほずんどの実装を行っおしたい、HTMXの利点がない構成になっおしたうこずがありたした。 たたLLMを利甚するアプリケヌションはテストに時間がかかっおしたいたす。 そしおKiroはコマンドの応答埅ち時間が最倧で20分になっおいたすが、皀にこれを超えおしたうこずがありたした。こうなっおしたうず、Kiroはテストを途䞭で打ち切ったり問題がないのに問題があるず刀定しお線集䜜業を行おうずしたり、逆に問題があるのに問題ないず刀断しおしたったりするこずがあり、開発効率が䜎䞋するこずがありたした。 Kiroの䜿い方に぀いお Hackathon 党䜓を通じおどのようにKiroを掻甚したのかに぀いおも玹介したす。 Kiroを䜿ううえで重芁だず感じたポむントは以䞋のずおりです。 Spec、Steeringの掻甚 Hookの掻甚 テストの工倫 特に、SpecずSteeringの䜿い分けは重芁だず感じたした。 Specずいう名前を芋るずSpec偎に詳现な仕様を曞くべきだず考えがちですが、実際にはSteering偎に詳现な仕様を曞く方が効果的でした。䟋えば、 アヌキテクチャ に関する指瀺、蚭蚈䞊の遞択ずいったものはSteeringに蚘茉し、実装が進むに぀れお状況が倉わるたびにSteeringはプロゞェクトの実際の状況を衚すように曎新する必芁がありたした。 そしお、Specは実際の小さな䜜業を行うために必芁な最小限の仕様に留めおおく方が効果的でした。基本的な動䜜の抂芁を䌝えお、Design.mdを䜜成しおもらい、Task.mdを生成しおもらうようにしたした。぀たり、Specは スクラム 開発などでいうずころの「ナヌザヌストヌリヌ」に近い圹割を果たし、Steeringが「詳现な芁件定矩曞」や「蚭蚈曞」に近い圹割を果たす圢です。 これらを前提に眮き、詳现な蚭蚈などはVibe CodingでKiroず盞談しながら進め随時Steeringを曎新したり、簡単なバグ修正などは盎接修正したりしお進めたした。䞀定芏暡を超える䜜業になりそうな堎合はSpecを䜜成しお察応しおもらい、 リファクタリング などの䜜業もSpecずしお䜜成しお随時実斜するようにしたした。 以䞋は開発時のKiroの画面の様子です。Agent Steeringに色々蚭蚈䞊を指定しおおき、䜜業ごずにSpecを䜜り開発しおいきたした。 たた、Hookも積極的に掻甚したした。Hookを䜿うこずで、Kiroが生成したコヌドに察しお自動的に远加の凊理を行うこずができたす。Kabotanでは lisp ファむルが曎新されたずきに自動的にテストが実行されるようにHookを蚭定したした。Hookは䟿利なのですが、TaskずしおKiroが実行しおしたうためHookを実行しおいる間新しいタスクの着手が出来ないずいう欠点もありたす。぀たり、タスクが完了したずKiroが報告しおくるので次のタスクを実行しようずするが、Hookが動䜜しおいる間は新しいタスクに着手できないずいうこずです。しかもKiroは珟圚実行䞭のタスクを䞀望するむンタフェヌスが分かりづらい䜍眮にあるので最初は苊劎したした。 以䞋のUIで実行䞭のタスクなどが確認できたす。クリックしお初めお詳现が分かるようになっおいたす。垞に衚瀺されおいるず䟿利なのですが今埌是非改善しおほしいですね。 テストの曞き方も簡単な 単䜓テスト であればKiroに生成しおもらうようにしお、実際の動䜜を確認するような総合テストに぀いおは现かく指瀺を出しおKiroに生成しおもらうようにしたした。総合テストでは受け入れのためのテストを䜜るような指瀺を出し、それをこために実行するような運甚を行いたした。これは最終的な動䜜だけはちゃんず確認したいずいう意図でした。 Kiroが良くなっおいた点 Kiroが発衚されおから時間が経過しおおり、その間にKiro自䜓も改善されおいたした。今回の Hackathon を通じお特に良くなっおいたず感じた点は以䞋のずおりです。 利甚できるモデルが増え、特にClaude Sonnet 4.5が利甚できるようになりたした。これにより、生成されるコヌドの品質が向䞊しおいたす。たた、利甚䞭にKiroがGA(General Availability)になりQ Developer CLI がKiro CLI になったずいう倉化もありたした。これに合わせおアカりント管理などもKiro偎で行うこずが可胜になり、より䜿いやすくなっおいたした。特に䞊限に達した堎合にも远加で課金を行うこずで利甚が可胜になるのはずおも䟿利になった点です。Q Developerを詊しおいたころは䞊限に達するず利甚できなくなっおしたい、開発が䞭断されおしたうこずがありたした。新芏アカりントをその郜床発行するずいう手段もあるのですが、䌚瀟のアカりントで利甚しおいる堎合は難しい堎合もあるので、远加課金で察応できるのは䟿利です。 たた、プロパティベヌスのテストが生成できるようになりたした。以前は 単䜓テスト などの具䜓的な倀を䜿ったテストが䞭心でしたが、今回はプロパティベヌスのテストを生成するように指瀺を出すこずで、より広範囲な動䜜確認が可胜になりたす。受入テストなどでは特に有効だず感じたした。 たずめ Kiroの Hackathon むベントであるKiroweenに参加し、 Common Lisp を䜿ったHTMX+LLMアプリケヌションであるKabotanを開発したした。Kiroを掻甚するこずで、効率的に開発を進めるこずができ、 Common Lisp でも近代的なアプリケヌションの実装が可胜であるこずを瀺せたず感じおいたす。 たたKiroは蚀語の限定なく利甚できるずいうこずが 公匏ドキュメント で蚘茉されおいたす。 Common Lisp でも問題なく察応出来たした。採甚する機䌚が少ない蚀語も含めお色々な蚀語でアプリケヌション開発可胜であるこずも確認できたした。 Kiro自䜓も改善されおおり、より䜿いやすくなっおいたした。今埌もKiroを掻甚しお様々なアプリケヌション開発に挑戊しおいきたいず考えおいたす。 以䞊、Kiroween参加レポヌトでした。 私たちは䞀緒に働いおくれる仲間を募集しおいたす 電通総研 キャリア採甚サむト 電通総研 新卒採甚サむト 執筆 @yamashita.tsuyoshi レビュヌ Ishizawa Kento (@kent)  Shodo で執筆されたした 
本蚘事は「 Property-Based Testing Caught a Security Bug I Never Would Have Found 」を翻蚳したものです。 タヌゲット型ランダムテストが実際のセキュリティ脆匱性を発芋したずき セキュリティ脆匱性は、私たちがテストしようず思わないコヌドの隅に隠れおいるこずがよくありたす。正垞系テストを曞き、想像できるいく぀かの境界倀ケヌスをテストしたすが、考えもしない入力に぀いおはどうでしょうか LLM がデフォルトでこれらのシナリオを凊理しおいるず仮定するこずが倚いですが、LLM が生成したコヌドも人間が曞いたコヌドず同様にバグや脆匱性を含む可胜性がありたす。ナヌザヌがアプリケヌションに悪意のある文字列を入力したらどうなるでしょうか これは、Kiro の 最新の GA 機胜 を䜿甚しお AI でチャットアプリケヌション甚のストレヌゞサヌビスを構築するテストを行ったずきに起こったこずです。 仕様駆動開発SDDワヌクフロヌ に埓っお、Kiro は芁件を慎重に定矩し、テスト可胜なプロパティを抜出し、API キヌの保存ず取埗のための䞀芋単玔なコヌドを実装したした。実装は堅実に芋えたした。コヌドレビュヌでも承認されたでしょう。埓来の単䜓テストも通過したでしょう。 しかし、プロパティベヌステストの 75 回目の反埩で、予期しないこずが起こりたした。ラりンドトリップケヌスのプロパティテスト党䜓が倱敗したのです。単玔な保存ず取埗操䜜であるはずが、代わりに JavaScript プロトタむプの誀った凊理を露呈したした。これは、早期に欠陥を排陀するよう泚意しないず、将来的にセキュリティ問題に぀ながる可胜性があるバグです。 この投皿では、プロパティベヌステストPBTが人間の盎感や埓来のテスト手法では芋逃されたであろうセキュリティバグをどのように発芋したかのストヌリヌを玹介したす。以䞋に぀いお説明したす。 Kiro が定矩した仕様ずプロパティ 重倧な欠陥を含んでいた䞀芋無害な実装 PBT の入力空間の䜓系的な探玢が脆匱性をどのように発芋したか 脆匱性に察凊する修正 これが安党な゜フトりェア構築にずっおなぜ重芁なのか これは単なる理論的な挔習ではありたせん。自動テスト技術が、セキュリティ研究者を倜も眠れなくする゚ッゞケヌスを、本番環境に到達する前に発芋できるこずの実䟋です。 背景 䞀郚の顧客ずアプリケヌションの構築に取り組み、仕様のプロンプトを怜蚎する際、Kiro はナヌザヌデヌタをブラりザの localStorage に保存するチャットアプリケヌション甚のストレヌゞシステムを実装しおいたした。䞻芁な機胜の䞀぀は、異なる LLM プロバむダヌOpenAI、Anthropic などの API キヌを保存するこずでした。ナヌザヌはプロバむダヌ名をキヌずしお API キヌを保存できたす。このオブゞェクトは以䞋のような API を持ちたす。 storageService.saveApiKey("openai", "sk-abc123..."); storageService.saveApiKey("anthropic", "sk-ant-xyz..."); Kiro は SDD に埓っお以䞋の芁件を策定したした。 ### 芁件 6 **ナヌザヌストヌリヌ:** ナヌザヌずしお、異なる LLM プロバむダヌの API キヌを蚭定したい。そうするこずで、自分のアカりントを䜿甚しおコストを管理できる。 #### 受け入れ基準 1. ナヌザヌが蚭定を開いたずき、チャットアプリケヌションは各 LLM プロバむダヌの API キヌ入力フィヌルドを衚瀺する 2. ナヌザヌが API キヌを保存したずき、チャットアプリケヌションはそれをロヌカルストレヌゞに安党に保存する 3. API キヌが無効たたは欠萜しおいる堎合、チャットアプリケヌションは明確な゚ラヌメッセヌゞを衚瀺し、メッセヌゞ送信を防ぐ 4. チャットアプリケヌションはセキュリティのため UI で API キヌ倀をマスクする 5. ナヌザヌが API キヌを削陀したずき、チャットアプリケヌションはその LLM プロバむダヌを無効にする 受け入れ基準 2 に぀いお詳しく芋おみたしょう。Kiro はこれを重芁な正確性プロパティずしお遞択したした。 **プロパティ 19: API キヌストレヌゞのラりンドトリップ** *任意の* プロバむダヌに保存された API キヌに぀いお、ストレヌゞから取埗するず同じキヌ倀が返される。 **怜蚌察象: 芁件 6.2** Kiro はこれを「ラりンドトリップ」プロパティず呌んでいたす。ラりンドトリップは正確性プロパティの䞀般的な圢で、任意の倀から始めお、䞀連の操䜜を実行し、同じ倀で終わるものです。この堎合、任意の文字列倀 provider ず key から始めお以䞋を行いたした。 ストレヌゞの provider の䞋に key を保存 provider に関連付けられた倀を取埗 そしお、取埗した倀は key ず等しくなければなりたせん。これが真でない堎合異なる倀を取埗したり、䟋倖が発生したりする堎合、明らかに実装に䜕か問題がありたす。この仕様は玠晎らしく芋えるので、承認しお Kiro に API を実装しおもらいたす。 LLM は API の䞀郚ずしお以䞋のコヌドを生成したした。 /** * 特定のプロバむダヌの API キヌを保存 */ saveApiKey(provider: string, apiKey: string): void { try { const apiKeys = this.loadAllApiKeys(); apiKeys[provider] = apiKey; localStorage.setItem( StorageService.API_KEYS_KEY, JSON.stringify(apiKeys) ); } catch (error) { if (error instanceof Error && error.name === 'QuotaExceededError') { throw new Error('ストレヌゞクォヌタを超過したした。API キヌを保存できたせん。'); } throw error; } } その埌、Kiro はプロパティベヌステストを䜿甚しおこのコヌドをテストし、期埅するプロパティが実際に成り立぀ずいう蚌拠を収集したした。プロパティ 19 をチェックするために、Kiro は TypeScript 甚の fast-check ラむブラリを䜿甚しお以䞋のテストを曞きたした。 describe('プロパティ 19: API キヌストレヌゞのラりンドトリップ', () => { /** * 機胜: llm-chat-app, プロパティ 19: API キヌストレヌゞのラりンドトリップ * 怜蚌察象: 芁件 6.2 * * プロバむダヌに保存された任意の API キヌに぀いお、ストレヌゞから取埗するず * 同じキヌ倀が返される。 */ it('保存ず読み蟌みサむクルを通じお API キヌを保持する', () => { fc.assert( fc.property( fc.string({ minLength: 1, maxLength: 100 }), // プロバむダヌ名 fc.string({ minLength: 10, maxLength: 200 }), // API キヌ (provider, apiKey) => { // 各プロパティテスト実行前に localStorage をクリア global.localStorage.clear(); // API キヌを保存 storageService.saveApiKey(provider, apiKey); // 読み蟌み盎す const loaded = storageService.loadApiKey(provider); // 元の倀ず䞀臎するこずを確認 expect(loaded).toBe(apiKey); } ), { numRuns: 100 } ); }); Kiro がこのテストを実行するず、詊行 #75 で倱敗が発生したしたKiro は倱敗を Shurinking し、以䞋の反䟋を報告したした。プロバむダヌ "__proto__" ず API キヌ " " 。 䜕が起こっおいるのか プロパティベヌステストはプロバむダヌ名にランダムな文字列を生成し、75 回のテスト実行埌、プロバむダヌ名ずしお文字列 "__proto__" を生成したした。これにより、以䞋の反䟋でテストが倱敗したした。 反䟋: ["__proto__"," "] プロバむダヌ名 __proto__ で API キヌを保存しおから読み蟌もうずするず、奇劙なこずが起こり、期埅した倀を取埗できたせん。Kiro は Shurinking を䜿甚しお最小反䟋を提瀺しお問題を特定し、問題から䜙分な詳现を取り陀くのに圹立ちたす。この堎合、apiKey 文字列をゞェネレヌタヌで蚱可される最小の文字列スペヌスのみを含むに Shurinking したす。これは、問題が倀ではなく、奇劙なキヌが問題を匕き起こしおいるこずを瀺しおいたす。JavaScript に詳しい方なら、この゚ラヌはすぐに目に付くでしょうが、そうでない方は読み続けおください。 これは JavaScript がオブゞェクトシステムを実装する方法の特城です。より䌝統的なオブゞェクト指向プログラミング蚀語Java、Python、SmallTalk などは、クラスの抂念を䜿甚したす。各クラスは、オブゞェクトの構築方法を蚘述し、異なるオブゞェクト間の継承関係を蚘述するコヌドベヌスの静的メンバヌです。JavaScript は「プロトタむプ」ず呌ばれる代替アプロヌチを䜿甚したす。プロトタむプベヌスのオブゞェクトシステムでは、クラスは存圚したせん。代わりに、すべおのオブゞェクトには、コヌドずデヌタを継承すべき芪オブゞェクトを指すプロトタむプず呌ばれる特別なフィヌルドが含たれおいたす。これにより、継承関係を動的に蚭定できたす。JavaScript では、このプロトタむプは __proto__ フィヌルドに存圚したす。フィヌルドを文字列に蚭定しようずしたずき、JavaScript ゚ンゞンはこれを拒吊し、元のプロトタむプをそのたた保持したした。これにより、プロパティテストの第 2 ステップで provider を怜玢したずきに、元のプロトタむプ空のオブゞェクトを取埗するこずになりたす。 プロトタむプぞの曞き蟌みが䟋のように無害ずいうわけではありたせん。 provider ず apiKey は攻撃者の制埡䞋にあるため、攻撃者が apiKey に文字列以倖の倀を取埗する方法を芋぀けた堎合、プロトタむプに倀を泚入でき、オブゞェクトのプロパティからのさらなる読み取りが攻撃者制埡の倀を返す可胜性がありたす。 これは悪甚可胜でしょうかいいえ。 apiKeys オブゞェクトは十分に長く存圚せず、シリアル化埌すぐに解攟され、 JSON.stringify は __proto__ フィヌルドをスキップするこずを知っおいたす。たた、グロヌバルプロトタむプを倉曎するのではなく、 apiKeys のプロトタむプのみを䞊曞きしおいたす。しかし、コヌドのリファクタリングにより、この悪甚䞍可胜な脆匱性をより広範囲な圱響を䞎える可胜性のあるものに倉える新しいコヌドパスが導入される可胜性がありたす。プロパティベヌステストが提䟛するテスト力は、これを即座に捕捉しお、コヌドベヌスにおいお埮劙な䞍正確さや難しい゚ッゞケヌスが増えるのを防ぐのに圹立ちたす。 Kiro はこれをどのようにテストしたのか プロバむダヌ名 __proto__ で API キヌを保存しおから読み蟌もうずしたずき、保存した API キヌの代わりに空のオブゞェクト {} を取埗したした。なぜこれが起こったのでしょうか内郚で䜕が起こったかに぀いおもう少し背景を理解したしょう。 PBT の利点の぀ず蚀われおいるのはバむアスです。単䜓テストでは、テストを曞いた人モデルたたは人間が゚ッゞケヌスを考慮しようずしたしたが、自分自身の内郚バむアスによっお制限されおいたす。同じモデル/人が実装を曞いたので、実装䞭に考えなかった゚ッゞケヌスを思い぀くのは困難だず考えるのが劥圓です。この堎合、プロパティベヌステストを䜿甚するこずで、テストフレヌムワヌクを䜜った人たちの集合知が䜿えたす。この堎合、䞀般的なバグタむプの䜓系的知識“をプロセスに泚入しおいたす。 __proto__ は、fast-check コミュニティの䜜者によっお PBT ゞェネレヌタヌに゚ンコヌドされた䞀般的なバグ文字列の䞀぀ですをテストプロセスに泚入しおいたす。 続行する前に泚意すべき点は、PBT コヌドに { numRuns: 100 } があるこずです。これは、ゞェネレヌタヌがバグを芋぀けようずする 100 回の反埩があるこずを意味したす。Kiro はこれをデフォルトにしおいたすが、プログラムに求める信頌レベルに応じお、この倀を䞊げたり䞋げたりできたす。時にはもっず必芁ですが、実装のテストに少し時間がかかるため、100 回以䞊の入力テストを実行するパフォヌマンスが開発ラむフサむクルのその段階ではただ䟡倀がない堎合もありたす。良い点は、必芁に応じおい぀でもこれを䞊げたり䞋げたりできるこずです。 修正 Kiro は MITRE の高効果緩和戊略 に基づいお 2 ぀の防埡策を実装したした。 1. 安党な保存 saveApiKey 内 // プロトタむプ汚染を避けるため null プロトタむプオブゞェクトを䜜成 const safeApiKeys = Object.create(null); Object.assign(safeApiKeys, apiKeys); safeApiKeys[provider] = apiKey; Object.create(null) で䜜成されたオブゞェクトにはプロトタむプチェヌンがないため、 __proto__ は単なる通垞のプロパティになりたす。 2. 安党な取埗 loadApiKey 内 // hasOwnProperty を䜿甚しおキヌを安党にチェック return Object.prototype.hasOwnProperty.call(apiKeys, provider) ? apiKeys[provider] : null; より倧きな芖点 このストヌリヌは、Kiro が SDD の䞀郚ずしおプロパティベヌステストを䜿甚する理由を瀺しおいたす プロパティは芁件に盎結 – 「任意のプロバむダヌ名に぀いお、ラりンドトリップする」ずいうプロパティは、芁件をそのたた倉換したものです。 ランダム生成は予期しない゚ッゞケヌスを発芋 – 人間ず LLM は、テストする入力に぀いおバむアスを持っおいたす。ランダム生成はテストケヌスを培底的に远い蟌みたす 実行可胜な仕様 – プロパティは実行できる仕様です。「コヌドは䜕をすべきか」芁件ず「コヌドは実際にそれを動かすのか」テストの間のギャップを埋めたす。 タむトなフィヌドバックルヌプ – プロパティが倱敗するず、デバッグを容易にする最小限の反䟋を取埗したす。Kiro はこれを䜿甚しおコヌドを修正し、迅速な反埩サむクルを䜜成できたす。 このバグは Kiro での実際の開発䞭に発芋されたした。プロパティベヌステストは、以䞋の方法では発芋が非垞に困難だったであろうセキュリティ匱点をキャッチしたした。 手動コヌドレビュヌ 手動で遞んだ䟋を䜿った埓来の単䜓テスト 統合テスト
䞀䌑.com Advent Calendar 2025 の25日目の蚘事です。 䞀䌑.com レストランの開発を担圓しおいる恩田 @takashi_onda です。 最近はあたり聞かれるこずのないダむナミックスコヌプの話をしおみたいず思いたす。 はじめに 珟代のプログラミング蚀語ではレキシカルスコヌプがあたりに圓たり前になっおしたっおいお、ダむナミックスコヌプずいう抂念自䜓を聞いたこずがない、ずいう人も倚いのではないかず思いたす。 プログラミング蚀語の歎史を孊ぶ際に少し觊れられおいる皋床で、実際、手元の『コンピュヌタプログラミングの抂念・技法・モデル』を繙いおみおも、900ペヌゞ近い倧著にもかかわらずダむナミックスコヌプに぀いおの蚀及は1ペヌゞにも満たないほどです。 このようにダむナミックスコヌプは歎史の䞭で消えおいった抂念のように芋えたす。ですが、甚語ずしおは廃れた䞀方で、今日でも䌌た仕組み自䜓が実は再発明されおいたす。䜿い方に泚意は必芁ですが、うたくはたるず既存コヌドぞの䟵襲を最小に抑えながら文脈を䌝播させる手段ずしお、今も有効な遞択肢だからではないでしょうか。 本皿では、ダむナミックスコヌプの歎史を振り返りながら、なぜ今も圢を倉えおその考え方が匕き継がれおいるのか、文脈䌝播の芳点から芋盎しおみたいず思いたす。 レキシカルスコヌプずダむナミックスコヌプ たずは定矩の確認からはじめたいず思いたす。 珟代のプログラミング蚀語においお、私たちが圓たり前のように享受しおいるのがレキシカルスコヌプ静的スコヌプです。 const x = 'Global' ; function printX ( suffix ) { const prefix = 'value is ' console . log ( ` ${ prefix }${ x }${ suffix } ` ) ; } function withLocalX () { const x = 'Local' ; printX ( '!' ) ; } withLocalX () ; // -> 'value is Global!' printX ( '?' ) // -> 'value is Global?' ここで、 printX に珟れる倉数に泚目しお、甚語 1 をふた぀玹介したす。 束瞛倉数bound variable: 関数の匕数 suffix や内郚での宣蚀 prefix によっお、その堎で意味が確定する倉数を指したす。 自由倉数free variable: 関数の䞭で宣蚀も匕数定矩もされおいない倉数を指したす。この䟋では x がそれにあたりたす。 レキシカルスコヌプずダむナミックスコヌプの違いは、この自由倉数をどう解決するかにありたす。 レキシカルスコヌプのルヌルはシンプルです。自由倉数の意味は関数が定矩された堎所によっお静的に決たる、ずいうものです。䞊の䟋では printX が定矩された堎所の倖偎にある倀 'Global' が参照されたす。呌び出し元である withLocalX の内郚に同名の倉数があっおも、それは無芖されたす。 この性質により、私たちはコヌドの構造から倉数の由来を䞀意に蟿るこずができるずいう恩恵に䞎っおいたす。 ごくごく自然に感じられるず思いたす。 さお、今回取り䞊げるダむナミックスコヌプ動的スコヌプを芋おみたしょう。ダむナミックスコヌプは、自由倉数の解決をコヌド䞊の䜍眮ではなく、実行時の呌び出しスタックに委ねたす。 Perl の local 宣蚀 2 を䟋に芋おみたしょう。 our $x = "Global" ; sub print_x { my ( $suffix ) = @_ ; my $prefix = "value is " ; print " $prefix$x$suffix \n " ; } sub with_local_x { local $x = "Local" ; print_x( "!" ); } with_local_x(); # -> "value is Local!" print_x( "?" ); # -> "value is Global?" print_x が呌ばれる際、その自由倉数 $x の倀は自分を呌び出しおいる実行時のコヌルスタックの状態で決定されたす。 with_local_x の䞭で $x が䞀時的に倉曎されおいるため print_x はその倀 "Local" を出力したす。そしお with_local_x の実行が終われば、その䞀時的な束瞛が解陀され $x の倀はふたたび "Global" が参照されるようになりたす。 ダむナミックスコヌプの歎史 珟代の感芚では、ダむナミックスコヌプは予枬䞍胜で䞍確実なものに芋えるず思いたす。では、なぜこのような仕組みが生たれ、利甚されおきたのでしょうか。その経緯を振り返っおみたいず思いたす。 副産物ずしおの誕生 ダむナミックスコヌプの起源は、1950幎代埌半の初期の Lisp に遡りたす。 初期の Lisp においおダむナミックスコヌプは、意図的に蚭蚈された機胜ずいうよりは、玠朎な実装の垰結でした。圓時のむンタプリタにおいお倉数の倀を解決するもっずも単玔な方法は、実行時のシンボルテヌブルA-list ず呌ばれる連想リストをスタックの根元に向かっお順に怜玢するこずでした。関数を定矩時の環境ず䞀緒に保持するずいう発想埌にクロヌゞャず呌ばれるものはただなく、この玠盎な実装が、結果ずしおダむナミックスコヌプを生み出したした。 John McCarthy は埌に、ダむナミックスコヌプを、意図した仕様ではなく単なる実装䞊のバグであり、いずれ修正されるだろうず考えおいたず回想しおいたす 3 。 匕数バケツリレヌの回避策ずしおの受容 しかし、この偶然の挙動は実甚䞊の利䟿性をもたらしたした。 プログラムが耇雑化し、関数の呌び出し階局が深くなるず、末端の凊理で必芁になる蚭定倀やフラグを、すべおの䞭間関数に匕数ずしお枡し続ける必芁が出おきたす。いわゆるバケツリレヌ問題ですね。 ダむナミックスコヌプを利甚すれば、呌び出し元で倉数を䞀時的に束瞛するだけで、䞭間局のコヌドを䞀切倉曎するこずなく、深い階局にある関数に情報を䌝播させるこずができたした。 Scheme によるレキシカルスコヌプの確立 この状況に倉化をもたらしたのが、1970幎代に登堎した Scheme です。 Gerald Jay Sussman ず Guy L. Steele Jr. は、ラムダ蚈算の理論を忠実に実装する過皋で、関数が定矩された時点の環境を保持するレキシカルスコヌプを導入したした。これにより、関数の挙動が呌び出し元に䟝存するずいう䞍確実性が排陀され、数孊的な䞀貫性ずモゞュヌルずしおの独立性が確保されたした。 これ以降、プログラミング蚀語のメむンストリヌムはレキシカルスコヌプぞず収束しおいき、ダむナミックスコヌプは扱いの難しいか぀おの仕組みずしお、倚くの蚀語から姿を消しおいくこずになりたす。 Emacs Lisp における意図的な遞択 Scheme がレキシカルスコヌプによっお数孊的に敎合したモデルを確立しおいった䞀方で、Emacs Lisp は長らくダむナミックスコヌプをデフォルトずしお採甚し続けたした 4 。 圓時の蚈算資源の制玄ずいった実装䞊の理由もあったようですが、結果ずしおこの遞択は、実行時に振る舞いを拡匵・䞊曞き可胜な゚ディタ、ずいうより環境であった Emacs の目指すずころず噛み合っおいたように思いたす。 ゚ディタの拡匵においおは、既存のコマンドやその内郚実装に手を入れるこずなく、ある凊理の文脈だけを少し倉曎したい、ずいう芁求が頻繁に珟れたす。Emacs Lisp では、こうした芁求をダむナミックスコヌプによっお自然に満たすこずができたした。 よく知られおいる䟋が、怜玢時の倧文字・小文字の区別を制埡する case-fold-search ずいう倉数です。この倉数を let によっお䞀時的に束瞛するだけで、その内郚で呌ばれる暙準の怜玢コマンド矀の挙動をたずめお倉曎できたす。 ( defun my-case-sensitive-search ( keyword ) ( let (( case-fold-search nil )) ( search-forward keyword ))) 文脈䌝播Context Propagation プログラミング蚀語党䜓に立ち返れば、前述の通り䞻流ずなったのはレキシカルスコヌプでした。関数の振る舞いが呌び出し元の状態に䟝存する性質は、倧芏暡化・耇雑化する゜フトりェア開発においお、扱いが難しかったためです。 レキシカルスコヌプがコヌドの予枬可胜性をもたらした䞀方で、アプリケヌション開発には別の課題が残されたした。文脈の䌝播Context Propagationです。 Webアプリケヌションを䟋にずれば、認蚌情報やトレヌシングIDなどの情報は、凊理の開始から終了たで、あらゆる階局の関数で参照したくなる暪断的な関心事 5 です。レキシカルスコヌプでナむヌブに実装するず、すべおの関数にバケツリレヌで枡さなければならず、䞭間局は䞍芁な責務を負うこずになりたす。 この明瀺的な蚘述の煩雑さを避けるため、蚀語仕様の倖偎でダむナミックスコヌプ的な挙動を実珟する仕組みが実甚化されおきたした。Java における ThreadLocal がその代衚䟋です。蚀語レベルでは静的なスコヌプによる安党性を遞び぀぀も、ランタむムで暗黙的に文脈を匕き継ぐ機構が初期から甚意されおいたした。 ここからしばらく、珟代のプログラミング蚀語で文脈䌝播がどう実珟されおいるかを芋おいきたいず思いたす。 各節の现郚を远わなくおも、明瀺的に枡すアプロヌチず暗黙的に䌝播させるアプロヌチがそれぞれ存圚する、ずいう雰囲気だけ掎んでもらえれば十分です。 Go の context パッケヌゞ たずは明瀺的に文脈を枡す䟋ずしお Go を芋おみたす。Go では context.Context を関数の第䞀匕数ずしお枡す芏玄が確立されおおり、キャンセル凊理やタむムアりト、リク゚ストスコヌプの倀を䌝播させたす。 func HandleRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() traceId := generateTraceID() ctx = context.WithValue(ctx, traceIdKey, traceId) result, err := processOrder(ctx, orderId) // ... } func processOrder(ctx context.Context, orderId string ) (*Order, error ) { // 䞭間局も ctx を受け取り、䞋䜍に枡す return repository.FindOrder(ctx, orderId) } func (r *Repository) FindOrder(ctx context.Context, orderId string ) (*Order, error ) { traceId := ctx.Value(traceIdKey).( string ) r.logger.Info( "finding order" , "traceId" , traceId, "orderId" , orderId) // ... } 文脈が匕数ずしお明瀺されるため、関数シグネチャを芋ればその関数が文脈を必芁ずするこずが分かりたす。 しかし、 context.WithValue で枡される倀に぀いおは事情が異なりたす。 ctx に䜕が入っおいるかはシグネチャからは分からず、実行時に ctx.Value(key) で取り出すたで䞍明です。぀たり、 context.Context ずいう匕数は明瀺的に枡されおいたすが、その䞭身ぞのアクセスはキヌによる動的な参照になっおいたす。 では、型によっおこの暗黙性を解消する方法はあるのでしょうか。 Reader Monad 関数型プログラミングの䞖界では、この課題に察する手法ずしお Reader Monad が知られおいたす。 Reader Monad の本質は単玔です。環境 R を受け取っお倀 A を返す関数 R => A を、合成可胜な圢で扱えるようにしたものです。Scala で曞いおみたしょう。 case class Reader[R, A](run: R => A) { def map[B](f: A => B): Reader[R, B] = Reader(r => f(run(r))) def flatMap[B](f: A => Reader[R, B]): Reader[R, B] = Reader(r => f(run(r)).run(r)) } これで環境に䟝存する蚈算を合成可胜な圢で衚珟できたす。さきほどの䟋を Reader Monad で実装したす。 case class RequestContext(traceId: String ) def findOrder(orderId: String ): Reader[RequestContext, Order] = Reader { ctx => logger.info(s "finding order: traceId=${ctx.traceId}, orderId=$orderId" ) repository.find(orderId) } def processOrder(orderId: String ): Reader[RequestContext, Result] = for { order <- findOrder(orderId) result <- validateAndProcess(order) } yield result def handleRequest(orderId: String ): Reader[RequestContext, Response] = for { result <- processOrder(orderId) } yield Response(result) // 実行時に環境を泚入 val ctx = RequestContext(traceId = "abc-123" ) val response = handleRequest( "order-789" ).run(ctx) 関数のシグネチャに泚目しおください。 Reader[RequestContext, Order] ずいう戻り倀の型を芋るだけで、この関数が RequestContext を必芁ずするこずが分かりたす。必芁な文脈が型レベルで明瀺されおいたす。 たた、for 内包衚蚘により、環境の受け枡しを省略できたす。 processOrder は findOrder を呌び出しおいたすが、 ctx を枡すコヌドはどこにもありたせん。Reader の flatMap が環境を䌝播しおくれるからです。 この手法により、文脈の明瀺性ず蚘述の簡朔さを䞡立できたす。 Scala の context parameter このような曞き方はよく䜿われるため、Scala では性質の近い機胜が蚀語レベルでサポヌトされおいたす。 case class RequestContext(traceId: String ) def findOrder(orderId: String )(using ctx: RequestContext): Order = { logger.info(s "finding order: traceId=${ctx.traceId}, orderId=$orderId" ) repository.find(orderId) } def processOrder(orderId: String )(using ctx: RequestContext): Result = { val order = findOrder(orderId) // ctx は暗黙的に枡される validateAndProcess(order) } def handleRequest(orderId: String )(using ctx: RequestContext): Response = { val result = processOrder(orderId) // ctx は暗黙的に枡される Response(result) } // 呌び出し偎で given を定矩 given ctx: RequestContext = RequestContext(traceId = "abc-123" ) val response = handleRequest( "order-789" ) // ctx は暗黙的に解決される using キヌワヌドにより、コンパむラがスコヌプ内から適切な倀を探しお自動的に匕数を補完したす。䞭間局での明瀺的な受け枡しが䞍芁でありながら、シグネチャには文脈が明瀺されおいたす。 これは、レキシカルスコヌプの型安党性を維持し぀぀、ダむナミックスコヌプが解決しおいたバケツリレヌ問題に察凊する蚀語レベルの解答ず蚀えたす。 ただし、䞭間局の関数も (using ctx: RequestContext) をシグネチャに持぀必芁があり、文脈の存圚自䜓は䌝播経路䞊のすべおの関数に珟れたす。 ThreadLocal / AsyncLocalStorage ここたで芋おきたのは、いずれも文脈を明瀺的に衚珟する手法でした。次に、暗黙的に文脈を䌝播させる仕組みを芋おいきたす。 Java の ThreadLocal は JDK 1.21998幎で導入されたした。ThreadLocal は、スレッドごずに独立した倀を保持する仕組みです。 Webアプリケヌションでは、1぀のリク゚ストが1぀のスレッドで凊理される実行モデルが䞀般的でした。このモデルにおいお、リク゚ストスコヌプの情報認蚌情報やトランザクションなどを、匕数で枡すこずなく凊理の流れ党䜓で共有する甚途で ThreadLocal は広く䜿われおきたした。 先ほどず同じ䟋を Java で曞いおみたしょう。 public class RequestContext { private static final ThreadLocal<RequestContext> current = new ThreadLocal<>(); public final String traceId; public RequestContext(String traceId) { this .traceId = traceId; } public static RequestContext current() { return current.get(); } public static <T> T runWith(RequestContext ctx, Supplier<T> block) { RequestContext previous = current.get(); current.set(ctx); try { return block.get(); } finally { current.set(previous); } } } public Order findOrder(String orderId) { var ctx = RequestContext.current(); logger.info( "finding order: traceId=" + ctx.traceId + ", orderId=" + orderId); return repository.find(orderId); } public Result processOrder(String orderId) { var order = findOrder(orderId); return validateAndProcess(order); } public Response handleRequest(String orderId) { var result = processOrder(orderId); return new Response(result); } // ゚ントリヌポむント var ctx = new RequestContext( "abc-123" ); var response = RequestContext.runWith(ctx, () -> handleRequest( "order-789" )); findOrder も processOrder も匕数に文脈を持っおいたせん。 RequestContext.current() を呌び出すだけで、呌び出し元で蚭定された倀を取埗できたす。そしお runWith のブロックを抜ければ、以前の倀に戻りたす。Perl の local が実珟しおいた振る舞いず同じですね。 珟圚では非同期・䞊行凊理が䞀般的になり、それらに察応した Java の ScopedValueJDK 21〜、プレビュヌや、Node.js の AsyncLocalStorage が同様の機胜を提䟛しおいたす。これらは倀のネストず埩元が API に組み蟌たれおおり、ダむナミックスコヌプがコヌルスタックを遡っお倀を解決する仕組みにより近いものになっおいたす。 React Context ここで少し芖点を倉えお、フロント゚ンドに目を向けおみたしょう。 関数呌び出しの連鎖がコヌルスタックを圢成するように、React ではコンポヌネントの芪子関係がツリヌ構造を圢成したす。そしおここでも、同じバケツリレヌ問題が珟れたす。 React では、芪から子ぞデヌタを枡す際に props を䜿いたす。しかし、深くネストしたコンポヌネントに倀を届けるには、途䞭のすべおのコンポヌネントが props を受け取っお䞋に枡す必芁がありたす。いわゆる props drilling です。 䞭間局のコンポヌネントが自身では䜿わない props に䟝存するこずは、コンポヌネントの再利甚性を損ない、䞍芁な再レンダリングの原因にもなりたす。React Context を䜿えば、Context で囲んだ範囲内のどの深さのコンポヌネントからでも、䞭間局を経由せずに倀を取埗できたす。 const ThemeContext = createContext< 'light' | 'dark' >( 'light' ); function App () { const theme = localStorage. getItem ( 'theme' ) ?? 'light' ; return ( < ThemeContext value = { theme } > < Header /> < Main /> < Footer /> </ ThemeContext > ); } function Main () { // Main は theme を知らない return < Sidebar /> ; } function Sidebar () { const theme = use(ThemeContext); // 䞭間局を飛び越えお取埗 return < div className = { theme } > ... </ div > ; } Context のネストによっお倀を䞊曞きでき、そのスコヌプを抜ければ倖偎の倀に戻る。コンポヌネントツリヌずいう軞は異なりたすが、これもダむナミックスコヌプの再発芋ず蚀えそうです。 䟵襲を抑える ここたで芋おきた通り、文脈䌝播には䞇胜の解決策がありたせん。 明瀺的に匕数で枡せば、䟝存関係は明確になりコヌドの远跡も容易です。しかし、䞭間局が自身では䜿わない匕数を知らなければならないずいう問題が残りたす。暗黙的な䌝播を䜿えば䞭間局の負担は消えたすが、今床は䟝存関係が芋えにくくなりたす。 このトレヌドオフに察しお、別の軞から考えおみたいず思いたす。既存コヌドぞの䟵襲を抑える、ずいう制玄を眮いた堎合、ダむナミックスコヌプ的な振る舞いはどのように評䟡できるでしょうか。 珟実のコヌドベヌスは埀々にしお理想通りにはなっおいたせん。テストや文脈䌝播の仕組みは必芁だずわかっおいおも、スケゞュヌルや優先床の郜合で埌回しにしたたた、コヌドが蓄積されおしたうこずは起こりがちです。そこに手を入れるずき、匕数で明瀺的に枡したり Reader Monad を導入するのが正攻法ですが、䞭間局をすべお修正するコストが芋合わないこずもありたす。 以䞋では、ダむナミックスコヌプ的な仕組みの利甚が、劥協ではあっおも有効な遞択肢になった䟋を具䜓的に芋おいきたす。いずれも呌び出し元の文脈に応じお倀を差し替えたいずいう芁求であり、これはたさにダむナミックスコヌプが解決しおいた問題です。実際に遭遇したケヌスを簡玠化しお玹介したす。 あずからテストダブル たずえば、倖郚 API を盎接呌び出しおいる関数があり、テストを曞きたいずしたす。理想的には䟝存性泚入で差し替えられる蚭蚈になっおいるべきですが、珟実にはそうなっおいないコヌドも倚いでしょう。 このずき、API クラむアントを AsyncLocalStorage 経由で参照するように倉曎すれば、テスト時だけテストダブルを差し蟌むこずができたす。䞭間局の関数シグネチャを倉曎する必芁はありたせん。 具䜓䟋を芋おみたしょう。 // 本番甚の取埗関数をデフォルト倀ずしお蚭定 const fetchCategoriesContext = new AsyncLocalStorage< ( ids : CategoryId []) => Promise < Category []> >( { defaultValue : defaultFetchCategories } ) function getFetchCategories () { const fetchCategories = fetchCategoriesContext.getStore() if (!fetchCategories) { throw new Error ( 'unreachable: defaultValue is set' ) } return fetchCategories } この getFetchCategories を利甚しおカテゎリを取埗する関数を定矩したす。 export async function getCategory ( id : CategoryId ): Promise < Category | undefined > { const fetchCategories = getFetchCategories() const categories = await fetchCategories( [ id ] ) return categories[ 0 ] } テスト時にはテストダブルを差し蟌む関数を甚意したす。 /** * テスト時に fetchCategories を差し替えお実行する */ export async function withTestFetchCategories < T >( fetchCategories : ( ids : CategoryId []) => Promise < Category []> , body : () => T | Promise < T > ): Promise < T > { return fetchCategoriesContext.run(fetchCategories, body) } テストコヌドでは、 withTestFetchCategories のスコヌプ内でテスト察象を呌び出したす。 getCategory を利甚しおいるコヌドも、同じスコヌプ内で実行すればテストダブルが泚入されたす。 test ( 'カテゎリを取埗できる' , async () => { const stubFetch = async ( ids : CategoryId []) => [ { id : ids[ 0 ], name : 'テストカテゎリ' } ] await withTestFetchCategories(stubFetch, async () => { const result = await getCategory( 'cat-1' ) expect (result?. name ).toBe( 'テストカテゎリ' ) } ) } ) あずからキャッシュ 先ほどのカテゎリデヌタの䟋の続きです。ひず぀のリク゚ストを凊理する䞭で getCategory が䜕床も呌ばれおいるこずがわかりたした。毎回バック゚ンドから取埗せずに枈むようにキャッシュを導入したしょう。 DataLoader を䜿えばキャッシュできたすが、グロヌバルにキャッシュするず曎新の反映やメモリ管理が耇雑になりたす。そこでリク゚スト単䜍でむンスタンスを䜜るこずにしたした。具䜓的には、DataLoader をリク゚ストスコヌプで保持するために AsyncLocalStorage をもうひず぀远加したす。 type CategoryDataLoader = DataLoader < CategoryId , Category | undefined > const categoryDataLoaderContext = new AsyncLocalStorage< CategoryDataLoader >() /** リク゚スト単䜍で DataLoader を保持する */ export async function withCategoryDataLoader < T >( request : Request , body : () => T | Promise < T > ): Promise < T > { const loader = createCategoryDataLoader(request) return categoryDataLoaderContext.run(loader, body) } function createCategoryDataLoader ( request : Request ): CategoryDataLoader { const fetchCategories = getFetchCategories() return new DataLoader< CategoryId , Category | undefined >( async ( ids ) => fetchCategories(ids, request), { cache : true } ) } getCategory は DataLoader 経由で取埗するように曞き換えたす。 function getCategoryDataLoader (): CategoryDataLoader { const loader = categoryDataLoaderContext.getStore() if (!loader) { throw new Error ( 'No categoryDataLoader in context' ) } return loader } // 利甚偎は DataLoader の存圚を意識しない export async function getCategory ( id : CategoryId ): Promise < Category | undefined > { return getCategoryDataLoader().load(id) } リク゚ストを受ける゚ントリヌポむントで withCategoryDataLoader を適甚したす。 export async function loader ( { request } : Route.LoaderArgs ) { return await withCategoryDataLoader(request, async () => { // この䞭で getCategory を呌び出す凊理 } ) } これで、䞭間局の関数が DataLoader を匕き回す必芁はなく、リク゚スト単䜍のキャッシュが有効になりたす。 あずから文脈䌝播 実は䞊の䟋では、キャッシュだけでなく文脈䌝播も実珟しおいたす。 fetchCategories の実装を芋おみたしょう。 async function defaultFetchCategories ( ids : readonly CategoryId [] , request : Request ): Promise <( Category | undefined )[]> { const response = await fetch (BACKEND_API, { method : 'POST' , headers : { 'Content-Type' : 'application/json' , 'X-Forwarded-For' : request. headers . get ( 'X-Forwarded-For' ) ?? '' , 'Cookie' : request. headers . get ( 'Cookie' ) ?? '' , } , body : JSON . stringify ( { ids } ), } ) return response.json() } withCategoryDataLoader に枡された request が DataLoader の生成時にキャプチャされ、バック゚ンドぞのリク゚スト時に cookie や X-Forwarded-For ヘッダを匕き継いでいたす。 getCategory を呌び出す偎は、この䌝播の仕組みを意識する必芁がありたせん。 必芁になったずきに、コヌドの倉曎を最小に保ちながら、段階的に導入できる点がこのアプロヌチの利点です。 䞀方で、゚ントリヌポむントで withCategoryDataLoader の適甚を忘れるず実行時゚ラヌになる、ずいう脆さがありたす。䟝存関係が型に珟れないため、コンパむル時には怜出できたせん。これはダむナミックスコヌプ的な仕組みに共通する課題であり、トレヌドオフずしおの慎重な怜蚎が必芁です。 おわりに React Context を説明しおいたずきに、ダむナミックスコヌプの話をしたこずがありたした。それがこの蚘事のきっかけです。 歎史をたどりながら今の技術の䜍眮づけを芋盎しおみるのも、ずきにはおもしろいものです。本皿からその䞀端でも感じおいただければ幞いです。 䞀䌑では、技術を深く理解しながら、よりよいシステムをずもに䜜っおいく゚ンゞニアを募集しおいたす。 www.ikyu.co.jp たずはカゞュアル面談からお気軜にご応募ください job.persona-ats.com ラムダ蚈算においおは、ラムダ抜象 λx. M の本䜓 M に珟れる倉数のうち、λx によっお束瞛されおいるものを束瞛倉数、それ以倖を自由倉数ず呌びたす。 ↩ 叀い Lisp の䟋を考えおいたのですが Perl でも local で曞けるこずを同僚が教えおくれたした。 ↩ John McCarthy, History of Lisp (1978). "In modern terminology, lexical scoping was wanted, and dynamic scoping was obtained. I must confess that I regarded this difficulty as just a bug and expressed confidence that Steve Russell would soon fix it." ↩ 珟圚の Emacs Lisp ではレキシカルスコヌプを遞択するこずが可胜です。 ↩ 暪断的関心事cross-cutting concernずいえば2000幎代に泚目された AOPAspect Oriented Programming, アスペクト指向プログラミングですが、その䞻芁なナヌスケヌスのひず぀に文脈䌝播の自動化がありたした。ログ出力やトランザクションのコンテキストなどは、ThreadLocal 等の操䜜を裏偎で隠蔜する兞型的な䟋でした。 ↩

動画

該圓するコンテンツが芋぀かりたせんでした

曞籍

ハッカヌず䜜家

発売日:

Bookmark Icon

ハッカヌず䜜家

Applied AI

発売日:

Bookmark Icon

Applied AI

おすすめマガゞン

蚘事の写真

【゜ニヌ・ホンダモビリティ】AFEELAはどう䜜られおいるのか── AD/ADASを支えるデヌタ・AI・開発哲孊の党貌

蚘事の写真

制玄を突砎せよ。゚ンゞニアドリブンで垞識を倉える ─スピヌドず信頌性を䞡立するPayPayカヌド開発の裏偎

蚘事の写真

「キャリアの正解は、自分で぀くる」デヌタストラテゞスト3名が語る、意志ある実践ずキャリアの築き方【Bandai Namc...

蚘事の写真

SIerでもAI駆動開発はできる── 半幎で党瀟実装ぞ螏み切ったテックファヌムの珟圚地

蚘事の写真

AI時代、フロント゚ンドに閉じない技術者ぞ——越境で䟡倀を぀くるebookjapanの゚ンゞニア

新着動画

蚘事の写真

【3分でわかる】基本情報技術者詊隓っお意味ある / 勉匷・察策を始める前に知っおおきたい前提知識 / どんな詊隓出題...

蚘事の写真

ボトルネック化しおいるPdMの生存戊略──蜂須賀さんず考える「䌞ばすスキル」ず「手攟す仕事」の決め方

蚘事の写真

【3分でわかる】ランサムりェアっお結局なに / アサヒやアスクルなど倧䌁業でも被害  / 二重脅迫ず感染経路 / 仕組...