ジェネレーティブAIを探求する

ジェネレーティブAI、特にLLM(大規模言語モデル)は、一般の意識の中に急速に広がっています。多くのソフトウェア開発者と同様に、私もその可能性に興味をそそられますが、長期的には私たちの職業にとって正確に何を意味するのか確信が持てません。現在、私はThoughtworksで、このテクノロジーがソフトウェアデリバリーの実践にどのように影響するかについての私たちの取り組みを調整する役割を担っています。同僚と私が学んで考えていることを説明するさまざまなメモをここに投稿する予定です。

過去のメモ

ツールチェーン(2023年7月26日)

ツールチェーン

まずはツールチェーンから始めましょう。まだ進化中のパターンとテクノロジーが存在する新しい分野では、私は物事がどのように組み合わさっているかのメンタルモデルを構築しようとします。それは私に押し寄せる情報の波に対処するのに役立ちます。この領域ではどのような種類の問題が解決されているのか?それらの問題を解決するために必要な一般的なパズルピースはどのような種類か?物事はどのように組み合わさっているのか?

ツールの分類方法

以下は、コーディングをサポートするためにLLM(大規模言語モデル)を使用するツールの現在のメンタルモデルの側面です。

支援タスク

  • より迅速に、かつコンテキスト内で情報を見つける
  • コードの生成
  • コードに関する「推論」(コードの説明、またはコード内の問題の説明)
  • コードを別のものに変換する(例:ドキュメントテキストまたは図)

これらは、コーディング支援に関して最も一般的に取り組まれているタスクの種類ですが、ソフトウェアデリバリーライフサイクルの他のタスクに範囲を広げれば、さらに多くのタスクがあります。

インタラクションモード

私は3つの主要なインタラクションモードを見てきました

  • チャットインターフェース
  • インラインアシスタンス、つまりコードエディタでの入力
  • CLI

プロンプト構成

プロンプトの品質は、バックエンドで使用されるLLMの適合性と組み合わせて、ツールの有用性に大きな影響を与えます。プロンプトエンジニアリングは、純粋にユーザーに任せる必要はありません。多くのツールがバックエンドでプロンプト技術を適用しています。

  • ユーザーがゼロからプロンプトを作成する
  • ツールがユーザー入力と追加のコンテキスト(例:開いているファイル、再利用可能なコンテキストスニペットのセット、またはユーザーへの追加の質問)からプロンプトを構成する

モデルのプロパティ

  • モデルが学習に使用されたもの
    • コードおよびコーディングタスクで特に学習されたか?どの言語か?
    • いつ学習されたか、つまり情報の最新度はどの程度か
  • モデルのサイズ(これがどのような点で重要であるか、またコーディングのような特定のタスクに「適切な」サイズがどれであるかはまだ非常に議論されています)
  • モデルでサポートされているコンテキストウィンドウのサイズ。これは基本的にプロンプトとして受け入れることができるトークンの数です
  • モデルまたはホストされているバックエンドに追加されたフィルター

出自とホスティング

  • 製品会社がホストするLLM APIを備えた商用製品
  • LLM APIサービスに接続するオープンソースツール
  • LLM APIサービスに接続する自己構築ツール
  • ファインチューニングされた自己ホスト型LLM APIに接続する自己構築ツール

この領域のツールの一般的な例と、それらがこのモデルにどのように適合するかを以下に示します。(このリストは、これらのツールの推奨または他のツールの却下ではなく、単に側面を説明するのに役立つことを目的としています。)

ツール タスク インタラクション プロンプト構成 モデル 出自/ホスティング
GitHub Copilot コード生成 インラインアシスタンス IDE拡張機能によって構成される コードでトレーニング済み、脆弱性フィルター 商用
GitHub Copilot Chat すべて チャット ユーザーチャット+開いているファイルで構成 コードでトレーニング済み 商用
ChatGPT すべて チャット すべてユーザーによって実行 コードでトレーニング済み 商用
GPT Engineer コード生成 CLI ユーザー入力に基づいて構成されたプロンプト OpenAIモデルの選択 オープンソース、OpenAI APIに接続
「チームAI」 すべて Web UI ユーザー入力とユースケースに基づいて構成されたプロンプト 最も一般的にOpenAIのGPTモデルを使用 チームがユースケースのためにメンテナンスし、OpenAI APIに接続
MetaのCodeCompose コード生成 インラインアシスタンス エディター拡張機能によって構成される 内部ユースケースとコードベースでファインチューニングされたモデル 自己ホスト型

今日、人々は何を使用しており、次は?

今日、人々は、コードエディターでのコーディング支援(例:GitHub CopilotまたはTabnine)と直接チャットインタラクション(例:ChatGPTまたはCopilot Chat経由)の組み合わせを最も一般的に使用しています。エディターのコンテキストでのインラインアシスタンスは、おそらく今日、コーディング支援にLLMを使用する最も成熟した効果的な方法であり、他のアプローチと比較して優れています。これにより、開発者は小さなステップで自然なワークフローをサポートできます。小さなステップにより、品質をより注意深く追跡してレビューしやすくなり、機能しない場合は簡単に次に進むことができます。

オープンソースの世界では、より大きなコードの塊を生成するためのプロンプト構成を提供するツール(例:GPT Engineer、Aider)で多くの実験が行われています。チームが特定のユースケースに合わせて調整した小さなプロンプト構成アプリケーションの同様の使用も見てきました。たとえば、再利用可能なアーキテクチャと技術スタック定義をユーザーストーリーと組み合わせて、タスクプランまたはテストコードを生成します。これは、同僚のXu Haoがこちらで説明しているものと似ています。このようなプロンプト構成アプリケーションは、最も簡単に利用でき、比較的強力であるため、現在ではOpenAIのモデルで最も一般的に使用されています。データに対するより多くのコントロールを求めているため、実験はオープンソースモデルと大規模なハイパースケーラーがホストするモデルにますます移行しています。

高度なプロンプト構成を超えた次のステップとして、人々は今後の改善にモデルコンポーネントに多くの希望を寄せています。より大きなモデル、またはより小さくてもより具体的にトレーニングされたモデルは、コーディング支援に適していますか?コンテキストウィンドウが大きいモデルを使用すると、より多くのコードをフィードして、コードベースのより大きな部分の品質とアーキテクチャについて推論できるようになるでしょうか?組織のコードでモデルをファインチューニングすることがどれほどの規模でペイオフになるのでしょうか?オープンソースモデルの分野では何が起こるのでしょうか?今後のメモのための質問です。

Kiran Prakashのインプットに感謝します


中央値 - 3つの関数の物語(2023年7月27日)

中央値 - 3つの関数の物語

これは、中央値関数を生成する際の小さな物語であり、LLM支援コーディングの有用性と制限をどのように示しているかを示しています。

TypeScriptコードベースで数値リストの中央値を計算する必要がありました。通常行う「中央値関数javascript」のインターネット検索を開始する代わりに、GitHub Copilotに支援を求めてみました。

// calculate the median of a list of numbers

Copilotは最初に適切なメソッドシグネチャを生成してくれました

public static median(values:number[]): number {

そして、その関数の3つの異なる実装を提案しました。

提案1

public static median(values:number[]): number {
    if(values.length === 0) return 0;

    values.sort((a,b) => a-b);

    var half = Math.floor(values.length / 2);

    if (values.length % 2)
    return values[half];

    return (values[half - 1] + values[half]) / 2.0;
}

この実装は正常に機能します。ただし、入力パラメーターvaluessort関数で変更されています。この例を同僚に見せたところ、賛否両論の回答がありました。「それは非常に悪い、通常テストを書くことを忘れることで、デバッグが非常に難しいエラーになる可能性がある」と言う人もいれば、「必ずしも悪いことではない、場合による」と言う人もいました。IDEの静的コード分析ツールがこれを問題としてマークするかどうか疑問に思っていましたが、ESLintもSonarもフラグを立てませんでした。

提案2

function median2(values: number[]) {
    const sorted = values.slice().sort((a, b) => a - b);
    const middle = Math.floor(sorted.length / 2);
    if (sorted.length % 2 === 0) {
        return (sorted[middle - 1] + sorted[middle]) / 2;
    } else {
        return sorted[middle];
    }
}

これは機能し、slice()が入力配列のコピーを返すため、入力パラメーターを変更するという問題はありません。

提案3

function median3(values: number[]) {
    const sorted = values.slice().sort((a, b) => a - b);
    const middle = Math.floor(sorted.length / 2);
    return sorted[middle];
}

一見すると、これは他のものよりも優れています。短く、循環的複雑度がゼロです。私たちはそれを「よりエレガント」であると考える傾向があります。私が書いたテストでも機能しました。素晴らしい!

しかし、後で、偶数と奇数の両方の数値でテストすることが重要であることに気づきました。私が書いたテストは奇数の長さの入力を使用していましたが、この実装は偶数の場合では失敗します。この例は、生成されたコードに対して妥当なテストを作成できるようにするために、私たちが何をしているかを理解することが依然として重要であることを示すのに最適です。完全な機能をカバーするテストを作成するには、中央値がどのように機能するかを知る必要があります。

これはインターネットからコピーするのと同じではないでしょうか?

このようなユースケースは、人々がこの「ステロイド上のオートコンプリート」に見出す利点の1つを示す完璧な例です。このような質問に対する答えを得るために、フローやツールチェーンを離れる必要はありません。それ以外の場合は、どこかからコピー&ペーストし、そのコードも徹底的にレビューしてテストを作成する必要があります。したがって、リスクは同じです。そうですよね?

唯一の違いは、Copilotでは、コードのソースがわからないということです。たとえば、StackOverflowの場合、スニペットの品質に関する追加のデータポイント、つまり賛成票の数があります。

ちなみに、「提案1」は、小さな欠点があるにもかかわらず、このトピックに関するStackOverflowの質問に対する最も高い投票数の回答が提案したコードとほぼ同じです。ただし、入力パラメーターの変更はコメントでユーザーによって指摘されています。

テストを生成するか、コードを生成するか?または両方か?

では、反対はどうでしょうか。最初にCopilotにテストを生成するように依頼した場合はどうでしょうか?それをCopilot Chatで試したところ、「偶数の長さで「提案3」で失敗するテストを含む、非常に素晴らしいテストセットが得られました。

it("should return the median of an array of odd length", () => { ... }

it("should return the median of an array of even length", () => { ... }

it("should return the median of an array with negative numbers", () => { ... }

it("should return the median of an array with duplicate values", () => { ... }

中央値のような非常に一般的で小さな関数の場合、テストと関数の両方に生成されたコードを使用することを検討するでしょう。テストは非常に読みやすく、カバレッジについて推論するのは簡単でした。さらに、入力の長さが偶数と奇数の両方の場合を考慮する必要があることを思い出すのに役立ちました。しかし、より複雑でカスタムコードが多い関数については、品質管理の手段として自分でテストを書くことを検討するでしょう。特に大きな関数では、ツールから部分的なシナリオを得て、不足しているものを補うのではなく、最初から構造化された方法でテストケースを検討したいと考えます。

ツール自体が、生成されたコードの欠陥を修正するのに役立つのでしょうか?

Copilot Chatに「提案1」を入力パラメータを変更しないようにリファクタリングするように依頼したところ、妥当な修正が返ってきました。しかし、この質問は、私がコードのどこを改善したいのかを既に知っていることを意味しています。

また、ChatGPTに「提案3」の何が問題なのか、あるいは改善できることは何かを、より広く尋ねました。その結果、入力の長さが偶数の場合には機能しないということがわかりました。

結論

  • 生成された提案を判断するには、自分が何をすべきかを知っている必要があります。この場合、生成されたコードに対して妥当なテストを書くには、中央値の計算方法を理解する必要がありました。
  • ツール自体が、生成されたコードの何が問題なのか、あるいは改善できるのかについての答えを持っている可能性があります。これは将来的にツールを改善するための道となるのでしょうか。それとも、AIツールとの堂々巡りの会話を繰り返す運命にあるのでしょうか?
  • 品質管理上の理由から、テストと実装の両方を生成することには懐疑的でした。しかし、テストを生成することで、後でコードを破棄する場合でも、見落としていたテストシナリオのアイデアを得ることができました。また、関数の複雑さによっては、シナリオについて簡単に推論できるのであれば、生成されたテストを使用することも検討するかもしれません。

Aleksei Bekh-IvanovとErik Doernenburgの洞察に感謝します


インラインアシスタンス - より役立つのはいつか? (2023年8月1日)

インラインアシスタンス - より役立つのはいつか?

Thoughtworksで現在最も広く使用されているコーディング支援の形式は、IDEでのインラインコード生成です。ここでは、IDE拡張機能が開発者がIDEで入力する際に提案を生成します。

「これは役に立つのか?」という質問に対する簡単な答えは、「役に立つ場合もあれば、そうでない場合もある」です。¯\_(ツ)_/¯ インターネット上では、「これで作業がとても速くなった」という意見から「役に立たないのでオフにした」という意見まで、開発者の幅広い意見が見られます。これは、これらのツールの有用性が状況によって異なるためです。また、有用性の判断は、期待値の高さによって異なります。

「有用」とはどういう意味か?

このメモの目的上、「有用」とは、「生成された提案が、ツールなしの場合よりも *より速く*、*同等の品質* で問題を解決するのに役立っている」と定義します。これには、コードの作成だけでなく、生成された提案のレビューと調整、および品質上の問題が発生した場合の後日の手直しも含まれます。

提案の有用性に影響を与える要因

注:これは主にGitHub Copilotでの経験に基づいています。

より一般的な技術スタック

安全な水域: 技術スタックがより一般的であるほど、モデルのトレーニングデータに含まれる議論やコード例が多くなります。これは、生成された提案が、JavaやJavascriptのような言語の方が、Luaのような新しく議論が少ない言語よりも役に立つ可能性が高いことを意味します。

ただし: 同僚のErik Doernenburgは、Rustで「コパイロットを困難な地形に連れて行く」という彼の経験について書きました。彼の結論は、「全体として、Rustのようなあまり一般的ではないプログラミング言語でも、より複雑なデータ構造を使用するコードベースでは、Copilotは役に立った」ということです。

より単純で一般的な問題

安全な水域: これは少し定義が難しいです。「単純」とはどういう意味か、「一般的」とはどういう意味か?例を挙げて説明します。

  • 一般的な問題: 前のメモで、中央値関数の生成例について議論しました。私はそれを非常に一般的な問題であり、したがって生成に適したユースケースだと考えています。
  • 私たちのコンテキストに適用される一般的なソリューションパターン: たとえば、リストのマッピング、グループ化、ソートのチェーンなど、リスト処理を必要とする問題の実装に成功しました。
  • 定型句: ExpressJSサーバー、Reactコンポーネント、またはデータベース接続とクエリ実行のような定型句の設定を作成します。
  • 反復パターン: 新しいコンストラクターやデータ構造の作成、テストスイートでのテスト設定の繰り返しなど、非常に一般的で反復的なパターンのあるものの入力をスピードアップするのに役立ちます。私は伝統的にこれらのことに多くのコピーアンドペーストを使用していますが、Copilotはそれをスピードアップすることができます。

Copilotを2か月以上使用していた同僚が、まだライセンスを持っていない人とペアプログラミングをしたとき、「反復的なコードを手で書かなければならないのは非常に苦痛だった」と感じました。このステロイドを使用したオートコンプリート効果は、IDE機能、ショートカット、複数のカーソルモードなどの使用にすでに非常に慣れている開発者にとってはあまり役に立たない可能性があります。また、コーディングアシスタントが反復的なコードの苦痛を軽減すると、リファクタリングへの意欲が低下する可能性があることに注意してください。

ただし: より複雑な問題に取り組み始めたときに、提案を破棄する場合でも、コーディングアシスタントを使用していくつかのアイデアを検討することができます。

提案のサイズが小さい

安全な水域: 生成された提案が小さいほど、レビューに必要な労力が少なく、開発者は提案されている内容を簡単に理解できます。

提案が大きくなるほど、それを理解するのに費やす時間が増え、自分のコンテキストに合わせるために変更する必要が生じる可能性が高くなります。大きなスニペットは、より大きなステップを踏むように誘惑するため、テストカバレッジを見落としたり、不要なものを導入したりするリスクが高まります。

ただし: この要因と他の要因との相互作用が多いと思います。特に、問題を解決する方法がすでにわかっている場合は、小さなステップが役立ちます。したがって、経験が浅いため、または問題がより複雑であるため、まだ計画がない場合は、より大きなスニペットが計画を開始するのに役立つ可能性があります。

より経験豊富な開発者

安全な水域: 経験は依然として重要です。開発者の経験が豊富であるほど、提案の品質を判断し、効果的に使用できる可能性が高くなります。GitHub自身が述べているように、「忘れてしまったことを得意とする」。この調査では、「場合によっては、ジュニア開発者はツールを使用した場合、ツールなしの場合よりもタスクに7〜10%長く時間がかかった」ことがわかりました。

ただし: これまで収集した観察結果のほとんどは、より経験豊富な開発者によって行われています。したがって、これは、現在、トレードオフについて最も確信がない点の1つです。私の仮説は、上記で述べた他の要因から安全な水域であるほど、ツールが経験の浅い開発者を誤った方向に導く可能性が低くなり、彼らに有利になる可能性が高くなるということです。ペアプログラミングやその他の形式のコードレビューは、リスクをさらに軽減します。

エラーに対するより高い許容度

すでに、提案の品質と正確さを判断できることの重要性について触れました。広く報告されているように、大規模言語モデルは情報を「幻覚」したり、この場合はコードを幻覚したりする可能性があります。誤った場合の影響が大きい問題やユースケースに取り組んでいる場合は、提案をレビューすることに特に注意する必要があります。たとえば、最近WebアプリケーションでCookieを保護する作業をしていたとき、CopilotはContent-Security-Policy HTTPヘッダーの値を示唆しました。私はこの分野での経験が浅く、これはセキュリティ関連のユースケースだったため、Copilotの提案をそのまま受け入れたくはなく、代わりに信頼できるオンラインソースで調査をしました。

結論

コーディング支援には安全な水域がありますが、この議論からわかるように、有用性を決定する複数の要因と相互作用があります。コーディング支援ツールを効果的に使用することは、トレーニングコースやブログ記事から簡単に学べるスキルではありません。ツールを一定期間使用し、安全な水域の内外で実験し、このツールが自分にとっていつ役に立つのか、いつ切り上げて自分でやるのかを感覚的に身につけることが重要です。

洞察とフィードバックをいただいたJames Emmott、Joern Dinkla、Marco Pierobon、Paolo Carrasco、Paul Sobocinski、Serj Krasnovに感謝します


インラインアシスタンス - どのように邪魔になるのか? (2023年8月3日)

インラインアシスタンス - どのように邪魔になるのか?

前のメモで、コーディング支援が役立つ可能性のある状況について説明しました。このメモは2つで1つです。ツールが邪魔になる可能性があると私たちが気づいた2つの方法を紹介します。

悪いまたは古い慣習の増幅

IDE内のコーディングアシスタントの長所の1つは、周囲のコードベースのスニペットを使用して、追加のコンテキストでプロンプトを強化できることです。プロンプトを強化するためにエディターで適切なファイルを開いておくことが、提案の有用性を向上させる上で非常に大きな要因であることがわかりました。

ただし、ツールは良いコードと悪いコードを区別できません。関連性があると思われるものは何でもコンテキストに注入します。(このリバースエンジニアリングの取り組みによると、GitHub Copilotは同じプログラミング言語で開いているファイルを探し、いくつかのヒューリスティックを使用して、プロンプトに追加する類似のスニペットを見つけます。)その結果、コーディングアシスタントは、コードベースの悪い例からコードをコピーし続けるチームの開発者になる可能性があります。

また、インターフェースをリファクタリングした後、またはコードベースに新しいパターンを導入した後、アシスタントが古い方法から抜け出せなくなる可能性があることがわかりました。たとえば、チームは「依存性注入にFactoryパターンを使用し始める」のような新しいパターンを導入したいと考えているかもしれませんが、ツールは、コードベース全体や開いているファイルで依然として普及しているため、依存性注入の現在の方法を提案し続けています。これを汚染されたコンテキストと呼び、これを軽減するための良い方法がまだありません。

結論

AIが私たちのコードベースでプロンプトコンテキストを改善しようとする意欲は、祝福であり呪いにもなりえます。これは、開発者が生成されたコードをあまり信頼しすぎず、それでも自分でレビューして考えることが非常に重要である多くの理由の1つです。

レビューの疲労と自己満足

コーディングアシスタントを使用するということは、何度も小さなコードレビューを行う必要があるということです。通常、コーディングを行う際、私たちのフローは、コードを積極的に書き、頭の中の解決策計画を実装することに重点が置かれています。それが、コードを読んでレビューするという作業が加わることで、認知的に異なるものになり、また、ほとんどの人にとって、積極的にコードを生成するよりも楽しくないものです。これにより、レビュー疲れが生じ、アシスタントによってフローが強化されるというよりも中断されるという感覚につながる可能性があります。開発者の中には、休憩するためにツールを一時的にオフにする人もいるでしょう。あるいは、疲労に対処しないと、コードレビューがずさんになったり、自己満足に陥ったりする可能性があります。

レビューにおける自己満足は、いくつかの認知バイアスの結果である可能性もあります。

  • 自動化バイアスとは、「自動化システムからの提案を好み、自動化なしで行われた矛盾する情報がたとえ正しくても無視する」傾向のことです。GenAIアシスタントで良い経験や成功を重ねると、それを過度に信頼し始める可能性があります。
  • また、AIコーディングアシスタントを使用していると、サンクコストの誤謬がねじれた形で作用していると感じることがよくあります。サンクコストの誤謬とは、「お金、労力、または時間が投資されたら、その取り組みを継続する傾向が強くなる」と定義されています。この場合、私たちは実際には自分自身で時間を投資しているのではなく、むしろ時間を節約しているのです。しかし、ツールから複数行のコード提案を受け取ると、その提案が必ずしも適切ではないとわかっていても、自分でコードを書くのに5分かけるよりも、提案を機能させるために20分費やす方がより合理的に感じる可能性があります。
  • コードの提案を見ると、それを見なかったことにはできず、他の解決策を考えるのが難しくなります。これは、アンカリング効果によるものです。アンカリング効果とは、「個人の決定が特定の基準点または「アンカー」によって影響を受ける」ときに発生します。そのため、コーディングアシスタントの提案は、まだ解決策がわからないときにブレインストーミングを行うのに役立ちますが、ブレインストーミングが実りをもたらさず、リフレッシュするために脳をリセットする必要がある場合は、アンカリング効果を意識することが重要です。

結論

時には、アシスタントから休憩しても大丈夫です。また、ナビゲーションシステムに言われるがままに車を湖に乗り入れるような人にならないように注意する必要があります。

「コンテキスト汚染」効果とレビュー疲れについて説明してくれたThoughtworks CanadaのPaul Sobocinski氏を中心とする「Ensembling with Copilot」グループに感謝します。:Eren、Geet、Nenad、Om、Rishi、Janice、Vivian、Yada、Zack

このメモに対するレビューコメントを寄せてくれたBruno、Chris、Gabriel、Javier、Roselmaに感謝します。


コーディングアシスタントはペアプログラミングの代わりにはならない(2023年8月10日) 

コーディングアシスタントはペアプログラミングの代わりにはならない

以前のメモで示されたように、私はGenAI搭載のコーディングアシスタントを開発者ツールチェーンへの非常に便利な追加物だと考えています。特定の場合にはコードの記述を明らかにスピードアップし、行き詰まった状態から抜け出し、より速く物事を覚えたり調べたりするのに役立ちます。これまでのメモは主にIDEでのインラインアシスタンスに関するものでしたが、チャットボットインターフェイスを追加すると、さらに便利なアシスタンスの可能性が広がります。特に強力なのは、IDEに統合されたチャットインターフェイスであり、プロンプトで明示する必要のないコードベースの追加コンテキストによって強化されています。

しかし、私はその可能性を認識していますが、正直なところ、コーディングアシスタントをペアプログラミングの代替として語る人々にかなり不満を感じます(GitHubは、Copilot製品を「あなたのAIペアプログラマー」とさえ呼んでいます)。Thoughtworksでは、チームをより効果的にするために、長年にわたってペアプログラミングとペアリング全般を強く支持してきました。それは、プロジェクトの開始点として使用する「理にかなったデフォルトプラクティス」の一部です。

コーディングアシスタントをペアプログラマーとして捉えることは、このプラクティスを軽視するものであり、ペアリングのメリットに対する広く普及している単純化された理解と誤解を助長します。私は、ペアリングについて語るために使用するスライドのセットと、このサイトで公開されている包括的な記事を振り返り、そこで言及しているすべてのメリットを1つのスライドにまとめました。

Title: Benefits and purposes of pair programming, in 5 categories: 1. “one plus one is greater than two”, for things like knowledge exchange or onboarding; 2. Flow, for things like keeping focus and limiting work in process; 3. Avoid waste, referencing the 7 wastes of software development; 4. Continuous Integration, as in integrating multiple times a day, mentioning shorter code review loops; and 5., Practice skills needed on highly effective teams, like task organisation, empathy, communication, feedback

コーディングアシスタントがここで最も明確な影響を与えることができるのは、最初の「1 + 1 は 2 より大きい」という点です。それらは私たちが立ち往生しているときに助けになり、オンボーディングを改善し、戦術的な作業をより迅速に行うのに役立ち、全体的なソリューションの設計、つまり戦略的な面に集中できるようになります。また、「このテクノロジーはどのように機能するのか?」という意味で、知識の共有にも役立ちます。

しかし、ペアプログラミングは、集団的なコード所有権とコードベースの歴史に関する共有知識を生み出すタイプの知識共有についても含まれます。それは、どこにも書き留められていない、したがって大規模言語モデルも利用できない暗黙知を共有することです。ペアリングは、チームのフローを改善し、無駄をなくし、継続的インテグレーションを容易にするのにも役立ちます。コミュニケーション、共感、フィードバックのやり取りなどのコラボレーションスキルを実践するのに役立ちます。また、リモートファーストのチームでは、お互いの絆を深める貴重な機会を提供します。

結論

コーディングアシスタントは、ペアプログラミングの目標とメリットのごく一部しかカバーできません。それは、ペアリングが個々のコーダーだけでなく、チーム全体を改善するのに役立つプラクティスだからです。適切に行われれば、コミュニケーションとコラボレーションのレベルが向上し、フローと集団的なコード所有権が向上します。LLM支援コーディングのリスクは、ペアでこれらのツールを使用することで最も軽減されるとさえ主張できます(以前のメモの「どのように妨げになるか」を参照)。

ペアをより良くするためにコーディングアシスタントを使用し、ペアリングの代替として使用しないでください。


GitHub Copilotを使用したTDD(2023年8月17日) 

GitHub Copilotを使用したTDD

作成者:Paul Sobocinski

GitHub CopilotのようなAIコーディングアシスタントの出現は、テストが不要になることを意味するのでしょうか?TDDは時代遅れになるのでしょうか?これに答えるために、ソフトウェア開発におけるTDDが役立つ2つの方法、つまり、優れたフィードバックを提供することと、問題を解決する際の「分割統治」の手段を調べてみましょう。

優れたフィードバックのためのTDD

優れたフィードバックは、迅速かつ正確です。どちらの点においても、適切に記述された単体テストから始めることに勝るものはありません。手動テストでも、ドキュメントでも、コードレビューでも、そして、ジェネレーティブAIでさえもです。実際、LLMは無関係な情報を提供し、ハルシネーション(幻覚)を起こすことさえあります。TDDは、AIコーディングアシスタントを使用する場合に特に必要です。私たちが記述するコードに迅速かつ正確なフィードバックが必要なのと同様に、AIコーディングアシスタントが記述するコードにも迅速かつ正確なフィードバックが必要です。

問題を分割統治するためのTDD

分割統治による問題解決とは、小さな問題を大きな問題よりも早く解決できることを意味します。これにより、継続的インテグレーション、トランクベース開発、そして最終的には継続的デリバリーが可能になります。しかし、AIアシスタントが私たちの代わりにコーディングを行う場合、これらすべてが本当に必要なのでしょうか?

はい。LLMが1つのプロンプトで必要な機能を正確に提供することはめったにありません。そのため、反復開発はまだなくなることはありません。また、LLMは、連鎖思考プロンプトを介して問題を段階的に解決すると、「推論を引き出す」(リンクされた調査を参照)ようです。LLMベースのAIコーディングアシスタントは、問題を分割統治する場合に最高のパフォーマンスを発揮し、TDDはソフトウェア開発でそれを行う方法です。

GitHub CopilotのTDDのヒント

Thoughtworksでは、年初からTDDでGitHub Copilotを使用してきました。私たちの目標は、ツールの使用に関する一連の効果的なプラクティスを実験、評価、進化させることです。

0. はじめに

TDD represented as a three-part wheel with 'Getting Started' highlighted in the center

空のテストファイルから始めることは、空のコンテキストから始めることを意味しません。私たちは多くの場合、いくつかの簡単なメモを含むユーザーストーリーから始めます。また、ペアリングパートナーと開始点について話し合います。

これはすべて、Copilotがオープンファイル(テストファイルの先頭など)に入れるまで「見え」ないコンテキストです。Copilotは、タイプミス、箇条書き、文法が不十分な場合でも機能します。しかし、空のファイルでは機能しません。

効果的だった開始コンテキストの例をいくつか示します。

  • ASCIIアートのモックアップ
  • 受け入れ基準
  • 次のようなガイディング仮説
    • 「GUIは不要」
    • 「オブジェクト指向プログラミングを使用する」(関数型プログラミングではなく)

Copilotはオープンファイルをコンテキストとして使用するため、テストファイルと実装ファイルの両方をオープン(たとえば、横並び)にしておくと、Copilotのコード補完能力が大幅に向上します。

1. 赤

TDD represented as a three-part wheel with the 'Red' portion highlighted on the top left third

まず、説明的なテスト例の名前を記述することから始めます。名前が説明的であるほど、Copilotのコード補完のパフォーマンスが向上します。

Given-When-Then構造は、3つの点で役立つことがわかりました。まず、ビジネスコンテキストを提供するように促します。次に、Copilotがテスト例に対して豊富で表現力豊かな命名推奨を提供できるようにします。第三に、ファイルの先頭コンテキスト(前のセクションで説明)からCopilotが問題を「理解」していることを明らかにします。

たとえば、バックエンドコードに取り組んでいて、Copilotがテスト例の名前を「given the user…clicks the buy buttonとコード補完する場合、これは、ファイルの先頭コンテキストを「GUIは想定しない」または、「このテストスイートはPython FlaskアプリのAPIエンドポイントとインターフェースする」と指定する必要があることを示しています。

注意すべきその他の「落とし穴」

  • Copilotは、一度に複数のテストをコード補完する場合があります。これらのテストはしばしば役に立ちません(削除します)。
  • テストを追加すると、Copilotは一度に1行ずつではなく複数行をコード補完します。多くの場合、テスト名から正しい「準備」ステップと「実行」ステップを推測します。
    • 注意点:正しい「アサート」ステップを推測することはめったにないため、「グリーン」ステップに進む前に、新しいテストが正しく失敗していることに特に注意します。

2. グリーン

TDD represented as a three-part wheel with the 'Green' portion highlighted on the top right third

これで、実装でCopilotの助けを得る準備ができました。既存の、表現力豊かで読みやすいテストスイートは、このステップでCopilotの可能性を最大限に引き出します。

そうは言っても、Copilotはしばしば「小さなステップ」を踏むことに失敗します。たとえば、新しいメソッドを追加する場合、「小さなステップ」とは、テストに合格するハードコードされた値を返すことを意味します。現在まで、Copilotにこのアプローチを取らせることはできていません。

テストのバックフィル

「ベビーステップ」を踏む代わりに、Copilotは先回りして、しばしば関連性はあるものの、まだテストされていない機能を提供します。その対策として、私たちは不足しているテストを「後埋め」しています。これは標準的なTDDの流れからは逸脱していますが、この対策によって深刻な問題が発生したことはまだありません。

削除と再生成

更新が必要な実装コードについては、Copilotを活用する最も効果的な方法は、実装を削除し、コードを最初から再生成させることです。これがうまくいかない場合は、メソッドの内容を削除し、コードコメントを使用してステップバイステップのアプローチを記述すると役立つことがあります。それでもうまくいかない場合は、Copilotを一時的にオフにして、手動で解決策をコーディングするのが最善の方法かもしれません。

3. リファクタリング

TDD represented as a three-part wheel with the 'Refactor' portion highlighted on the bottom third

TDDにおけるリファクタリングとは、動作(および動作するコードベース)を維持しながら、コードベースの保守性と拡張性を向上させるための段階的な変更を行うことを意味します。

この点において、Copilotの能力は限定的であることがわかりました。2つのシナリオを考えてみましょう。

  1. 「試したいリファクタリングの動きがわかっている場合」:IDEのリファクタリングショートカットや、マルチカーソル選択などの機能を使う方が、Copilotよりも早く目的を達成できます。
  2. 「どのリファクタリングの動きをすべきかわからない場合」:Copilotのコード補完機能では、リファクタリングをガイドすることはできません。ただし、Copilot ChatはIDE内で直接、コード改善の提案を行うことができます。私たちはその機能を試行し始めており、小規模で局所的な範囲で有用な提案ができる可能性があると考えています。しかし、(単一のメソッド/関数を超える)大規模なリファクタリングの提案については、まだ大きな成功を収めていません。

リファクタリングの動きはわかっているものの、それを実行するために必要な構文がわからない場合があります。たとえば、依存関係を注入できるようにするテストモックを作成する場合です。このような状況では、コードコメントでプロンプトを入力すると、Copilotがインラインで回答を提供できます。これにより、ドキュメントやウェブ検索にコンテキストを切り替える手間が省けます。

結論

「ゴミを入れればゴミが出る」というよく言われる言葉は、データエンジニアリングだけでなく、生成AIやLLMにも当てはまります。言い換えれば、入力の質が高ければ高いほど、LLMの能力をより有効に活用できます。私たちの場合、TDDは高いレベルのコード品質を維持します。この高品質な入力によって、Copilotのパフォーマンスは、そうでない場合よりも向上します。

したがって、CopilotをTDDとともに使用することをお勧めします。上記のヒントが、その際に役立つことを願っています。

Thoughtworksカナダで始まった「Copilotとのアンサンブル」チームに感謝します。彼らは、このメモで取り上げた調査結果の主な情報源です。Om、Vivian、Nenad、Rishi、Zack、Eren、Janice、Yada、Geet、Matthew。


GenAIは他のコードジェネレーターとどう違うのか?(2023年9月19日)

GenAIは他のコードジェネレーターとどう違うのか?

キャリアの初期の頃、私はモデル駆動開発(MDD)の分野で多くの仕事をしていました。私たちは、ドメインやアプリケーションを表すモデリング言語を考案し、その言語でグラフィカルまたはテキスト形式(カスタマイズされたUML、またはDSL)で要件を記述していました。次に、これらのモデルをコードに変換するコードジェネレーターを構築し、開発者が実装およびカスタマイズする指定領域をコード内に残していました。

しかし、このスタイルのコード生成は、組み込み開発の一部の領域を除いて、あまり普及しませんでした。その理由は、抽象化のレベルが中途半端であり、ほとんどの場合、フレームワークやプラットフォームなどの他の抽象化レベルよりもコスト対効果が高くないためだと思います。

GenAIによるコード生成の何が違うのか?

ソフトウェアエンジニアリングの仕事で私たちが継続的に行う重要な決定の1つは、実装の労力と、ユースケースに必要なカスタマイズと制御のレベルとの間で適切なバランスをとるために、適切な抽象化レベルを選択することです。業界として、実装の労力を削減し、より効率的になるために、抽象化レベルを上げようとし続けています。しかし、それには、必要な制御のレベルによって制限される、一種の目に見えない力場があります。ローコードプラットフォームの例を挙げると、抽象化レベルを上げて開発労力を削減しますが、その結果、特定の種類のシンプルで簡単なアプリケーションに最も適しています。よりカスタムで複雑なことを行う必要があるとすぐに、力場にぶつかり、抽象化レベルを再び下げる必要があります。

An illustration of typical abstraction levels in software, starting from 0s and 1s at the bottom, going up via programming languages, frameworks, platforms, and low code applications. A squiggly line at the top marks the "Invisible force field for customizability and extensibility".

GenAIは、その力場を打ち砕こうとする別の試みではないため、新たな可能性を切り開きます。代わりに、コンパイラーやコードジェネレーターのような構造化された言語やトランスレーターを正式に定義する必要なく、すべての抽象化レベルで人間がより効果的になるようにすることができます。

An illustration of typical abstraction levels in software, starting from 0s and 1s at the bottom, going up via programming languages, frameworks, platforms, and low code applications. A vertical box on the right marked "Natural Language" has arrows going into all of the abstraction levels, indicating that GenAI can help create all of those levels.

GenAIを適用するために抽象化レベルを高くすればするほど、ソフトウェアを構築するための全体的な労力は少なくなります。ローコードの例に戻ると、いくつかのプロンプトだけでフルアプリケーションを構築できることを示す素晴らしい例が、この分野にはいくつかあります。ただし、これは、カバーできるユースケースの点で、ローコード抽象化レベルの同じ制限が伴います。ユースケースがその力場にぶつかり、より多くの制御が必要な場合は、より低い抽象化レベルに戻り、より小さなプロンプト可能なユニットに戻る必要があります。

抽象化レベルを再考する必要があるか?

私がソフトウェアエンジニアリングにおけるGenAIの可能性について推測する際のアプローチの1つは、自然言語プロンプトと、ターゲットの抽象化レベルとの間の抽象化の距離について考えることです。上記でリンクしたGoogleのAppSheetデモでは、非常に高レベルのプロンプト(「チームが旅行リクエストを追跡するのに役立つアプリを作成する必要がある[…]フォームに入力[…]リクエストはマネージャーに送信する必要がある[…]」)を使用して、機能するローコードアプリケーションを作成しています。たとえば、SpringとReactのフレームワークコードで同じ結果を得るために、そのようなプロンプトで何段階下のターゲットレベルまでプッシュできるでしょうか?または、SpringとReactで同じ結果を得るために、プロンプトをどれだけ詳細に(そして抽象的でなく)する必要があるでしょうか?

ソフトウェアエンジニアリングにおいてGenAIの可能性をより有効に活用したいのであれば、GenAIが橋渡しするための「プロンプト可能」な距離を構築するために、従来の抽象化レベルを全体的に再考する必要があるかもしれません。

このメモに対する洞察に満ちたレビューコメントをいただいた、John Hearn、John King、Kevin Bralten、Mike Mason、Paul Sobocinskiに感謝します。


最新のメモ:コーディングアシスタントの信頼性の低さにどう対処するか

2023年11月29日

コーディングアシスタントの有用性のトレードオフの1つは、その信頼性の低さです。基盤となるモデルは非常に汎用的であり、手元のタスクに関連するデータと関連性のないデータを含む、膨大な量のトレーニングデータに基づいています。また、大規模言語モデルは物事を捏造し、一般的に「幻覚」と呼ばれるものを起こします。(余談ですが、「幻覚」という用語については、これがこれを説明するのに実際には正しい心理学的な比喩ではないということ、また、そもそもモデルを擬人化するため、心理学用語を使用すること自体について、多くの議論があります。)

その信頼性の低さは、2つの主なリスクを生み出します。コードの品質に悪影響を与える可能性があり、時間を浪費する可能性があります。これらのリスクを考慮すると、コーディングアシスタントの入力に対する信頼度を迅速かつ効果的に評価することが重要です。

アシスタントの入力に対する信頼度をどのように判断するか

以下は、提案を使用することの信頼性とリスクを評価しようとするときに、私の頭の中で通常浮かぶ質問の一部です。これは、コードを入力中の「自動補完」の提案と、チャットからの回答の両方に当てはまります。

迅速なフィードバックループがあるか?

回答または生成された情報が機能するかどうかをすぐに確認できればできるほど、アシスタントが時間を無駄にしているリスクは低くなります。

  • IDEはフィードバックループを支援できるか?構文強調表示、コンパイラーまたはトランスパイラーの統合、リンティングプラグインがあるか?
  • テスト、または提案されたコードを手動で実行する簡単な方法があるか?あるケースでは、HTMLページで折りたたみ可能なJSONデータ構造を最適に表示する方法を調査するために、コーディングアシスタントチャットを使用していました。チャットは、私が聞いたことのないHTML要素について教えてくれたので、それが存在するかどうか確信が持てませんでした。しかし、それをHTMLファイルに入れてブラウザで読み込むのは簡単だったので、確認できました。反対の例を挙げると、聞いたことのないインフラストラクチャコードの検証のためのフィードバックループは通常、はるかに長くなります。

信頼できるフィードバックループがあるか?

AI入力のフィードバックループの速度だけでなく、そのフィードバックループの信頼性についても考えています。

  • テストがある場合、そのテストにどれほど自信があるか?
  • 自分でテストを書いたか、それともAIアシスタントで生成したか?
  • AIがテストを生成した場合、これらのテストの有効性をレビューする能力にどれほど自信があるか?書いている機能が比較的シンプルでルーチンであり、使い慣れた言語である場合は、より複雑または大規模な機能よりも、もちろんはるかに自信があります。
  • アシスタントを使用しているときに誰かとペアリングしているか?彼らはAI入力に対して追加の意見とレビューを提供し、私の信頼を高めます。
  • テストカバレッジに確信が持てない場合は、アシスタント自身を使用して自信を高め、テストするエッジケースをさらに要求することもできます。これは、以前のメモで説明した中央値関数の重要な欠落テストシナリオを見つけることができた方法です。

誤差範囲はどれくらいか?

また、行っていることに対する誤差範囲はどれくらいかについても考えています。誤差範囲が小さいほど、AI入力に対してより批判的になります。

  • 新しいパターンを導入するときは、コードベースの全体的な設計にとって、より大きな影響範囲であると考えています。チームの他の開発者はそのパターンを採用し、コーディングアシスタントもコードに入ると、チーム全体でそのパターンを再現します。たとえば、CSSでは、GitHub Copilotがflexboxレイアウトを頻繁に提案することに気づきました。しかし、レイアウトアプローチを選択することは大きな決定であるため、これを使用する前にフロントエンドの専門家やチームの他のメンバーと相談したいと考えています。
  • セキュリティに関連するものはすべて、当然、誤差範囲が小さくなります。たとえば、Webアプリケーションに取り組んでおり、「Content-Security-Policy」ヘッダーを設定する必要がありました。私はこの特定のヘッダーについては何も知らなかったので、最初にCopilotチャットに尋ねました。しかし、その主題の性質上、その回答に依存したくなかったので、代わりにインターネット上の信頼できるセキュリティ情報源に行きました。
  • このコードはどのくらい長く使われるのか?プロトタイプや使い捨てのコードに取り組んでいる場合は、実稼働システムに取り組んでいる場合よりも、あまり疑問を持たずにAI入力を使用する可能性が高くなります。

非常に最新の情報が必要か?

回答がより最新で、より具体的(例:フレームワークのバージョン)である必要があるほど、その回答が間違っているリスクが高くなります。これは、探している情報がAIにとって利用できないか、区別できない可能性が高いためです。この評価では、手元のAIツールが、トレーニングデータだけでなく、より多くの情報にアクセスできるかどうかを知ることも重要です。チャットを使用している場合は、オンライン検索を考慮に入れる機能があるのか、それともトレーニングデータに限定されているのかを認識しておきたいと考えています。

アシスタントに時間制限を与える

時間を無駄にするリスクを軽減するために、私が採用するアプローチの1つは、一種の最後通告を与えることです。提案が少しの追加労力で私に価値をもたらさない場合は、先に進みます。入力がすぐに役に立たない場合は、アシスタントを擁護してさらに20分かけて機能させようとするのではなく、常にアシスタントについて最悪のことを想定します。

私が思い浮かべる例は、AIチャットを使ってmermaid.jsのクラス図を生成しようとしたときのことです。私はmermaid.jsの構文にあまり詳しくなく、提案されたものをなんとか動かそうと試行錯誤し、もしかしたらマークダウンファイルへの記述方法が間違っているのかと思っていました。結局のところ、構文が完全に間違っていたのですが、それには10分ほど経ってからオンラインドキュメントを確認してようやく気づきました。

アシスタントのペルソナを作る

このメモを準備するにあたり、アシスタントのペルソナを作ることで、責任を持って、できるだけ時間を無駄にせずに利用する方法を考えるのに役立つのではないかと思い始めました。もしかしたら、AIを擬人化することが、この場合には実際に役立つかもしれません。

信頼性の低さの種類について考えると、AIのペルソナは次のような特性を持つと想像できます。

  • 手助けしたがっている
  • 頑固
  • 非常に博識だが、経験不足(ダンジョンズ&ドラゴンズのファン向けに言うと、知能は高いが、賢さは低い)
  • 自分が何かを「知らない」ときにそれを認めない

私は画像生成ツールを使って、熱心なビーバーや頑固なロバのバリエーションをいくつか試しました。これが一番気に入ったものです(Midjourneyで「熱心な頑固なロバ、幸せな本、コンピューター、漫画風、ベクターベース、フラットな色面」)。

a cartoonish picture of a very excited donkey among a stack of books

ペルソナに楽しい名前をつけて、チームで話すこともできます。「ダスティはセッション中にうるさい知ったかぶりだった、しばらくオフにしなければならなかった」とか、「ダスティがいてくれてよかった、昼食前にタスクが終わった」とか。しかし、「ダスティがそのインシデントを引き起こした!」とは絶対に言ってはいけません。なぜなら、ダスティは基本的に未成年であり、コミットする資格がないからです。私たちは最終的にコミットに責任を持つ親のようなものであり、「親は子供に対して責任を負う」のです。

結論

状況評価のリストは、コーディングアシスタントを使用するたびにすべて適用するには多すぎるように思えるかもしれません。しかし、これらのツールを使えば使うほど、私たちは皆、上達していくと信じています。私たちは、経験に基づいて、コーディングをするときに、このような多次元的な要素を考慮した迅速な評価を常に行っています。私は、上記のような状況に遭遇すればするほど、いつアシスタントを使用し、いつ信頼すべきかの判断がうまくなってきました。いわば、「熱いストーブに触れる」回数が増えるほどということです。

また、「もしAIアシスタントが信頼できないなら、なぜそもそもそれを使う必要があるのか?」と思うかもしれません。Generative AIツールを使用する際には、私たちが持つべき考え方の転換があります。「通常の」ソフトウェアと同じ期待を持って使用することはできません。GitHub Copilotは、必要なものを100%提供してくれる従来型のコードジェネレーターではありません。しかし、40〜60%の状況では、目的の40〜80%まで達成できます。これは依然として有用です。これらの期待を調整し、熱心なロバの行動と癖を理解する時間を与えれば、AIコーディングアシスタントからより多くのものを得られるでしょう。

フィードバックと意見をくれたBrandon Cook、Jörn Dinkla、Paul Sobocinski、Ryder Dainに感謝します。

このメモは、GitHub Copilotを有効にして、マークダウンファイルで書かれました。アイデアや言い回し、行き詰まった時に役立ちますが、提案が最終的なテキストに残ることはめったにありません。私はChatGPTを類語辞典として、そしてロバの良い名前を見つけるために使用しました。