GitHub Copilotの無償プランが提供され、GitHubにアカウントがある人々は、ChatGPTもしくはClaudeという生成AIの支援を受けたプログラミングが可能になった。有償プランが提供されてから2年9ヶ月経つので体験レビューも多いのだが、機会なので(ちょっと)いじってみたので感想を記しておきたい。
1. やってみた具体的な作業
いじったと言っても、GPT 4oでソースコードの補完とレビューしか使っていない。
ソースコードの補完は、(CTRL + Iで)ダイアログを呼び出して書いて欲しいコード(やコードの変更)を説明するか、#ではじまる注釈を書いてから(それで出てくるポップアップに従い、ショートカットで)Copilotを呼び出すと、追記もしくは変更の提案を出してくる。それを採用するか、他を提案させるか、止めるかを選択して使う。
まず、Rのコードで試したところ、(ソースコードを反転させて)ここの部分を関数にしろとか、この関数の戻り値に他の変数を追加しろと言う指令に、そのまま動く適切な変更を提案してきた。Cのコードでも、コマンドライン引数を表自しろと入門本にありそうな例を注釈したら、ほぼ動くコードを提案してきた*1。
次に、もう少し複雑な例として、Makefileで試してみた*2。2つの変数SRC_AとSRC_Bにそれぞれ複数のファイルをリストしてある状態で、"Write a code that compiles files listed in SRC_A with the -O3 optimization flag for each file and those in SRC_B with the -O2 optimization flag"とCopilotに説明したところ、指示に沿った提案もしてくる一方、沿っていない提案も出してきた。また、指示に沿ったコードも、そのまま動くコードとは限らないし、(これは悪いことではないが)予想していたコードとは、異なるアプローチで解決して来た。なお、日本語で命令してもほぼ同様の提案を返してきた。
ソースコードのレビューは、レビューして欲しい部分を反転させて指示すると、Copilotが問題と判断した箇所とその説明を指摘し、変更の提案を複数してくる。それらを採用するか、他を提案させるか、止めるかを選択して使う。
Rのテストコード作成ルーチンで試してみたのだが、5つ指摘してきて、4つは妥当な提案であった。例えば、二重ループで処理していたところを、ビルトイン関数を組み合わせた処理で簡潔にすることを提案してきた。ただし、1つは変数の次元を把握できていない不適切な提案であった。
2. 感想
興味深いし、そこそこ有用。実際のところ、プログラミングではドキュメントやコード例を調べている時間は長く、試行錯誤になってしまう部分もある。慣れていないプログラミング言語のソースコードをいじる機会も多い。Github Copilotを使うことで大幅に時間を短縮できる可能性がある。少なくとも、典型的な場合は有用だ。機械的な作業もしてくれているので、労力の削減も出来そうだ。
ソフトウェア開発環境は、デバッガー、プロファイラ、バージョン管理ツール、テストツール、ドキュメント生成、バグトラッキング…など、学習することが多いものが増えてきており、全体としてかなりの初心者キラーになっているわけだが、生成AIによるプログラミング支援は珍しく初心者に優しい方向の新技術である。
ただし、プログラミングの知識が不要に云々と言う話にはまだまだならない。Copilotに指示を与えるのに、プログラミングの知識が要る。Copilotの提案が必ず適切とは限らないので、提案を判断するのに、プログラミングの知識が要る。テストやデバッグが不要になったわけではない。そもそもCopilotに与える自然言語の表現力に限界がある。
画像生成AIのように、法的な問題を引き起こすのではないかと言う危惧もあるが、典型的な処理をコピペ猿真似した事例で著作権法違反になった事例は聞かないので、プログラミング支援と言える程度の長さを生成するのに留める分には問題は起きない。画像生成AIのように、チグハグな生成物になっている場合もあるわけだが、狙ったように動かない場合は変更して使うので、使ったら低品質のソフトウェアになると言うものでもない。
GitHub Copilotによるプログラミング支援は、プログラマの知識不足を補い、労力を削減してくれる。使い込んでいったらもっと粗が見えてくると思うが、粗が詳しく見えるまでガンガンと使い込もう。使っているハードウェアや電力といった資源の量は環境に優しくないらしいので、世界中のプログラマが本格利用したら何か問題を引き起こすかも知れないが、そのときは利用料金が上がるだけである。
*1元のCのコード(教科書的な例):
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char *argv[]){
# print all arguments
}
Github Copilotの提案:
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char *argv[]){
for (int i = 0; i < argc; i++) {
printf("Argument %d: %s\n", i, argv[i]);
}
*2元のMakefile:
SRC_A = a1.f90 a2.f90 a3.f90
SRC_B = b1.f90
Github Copilotの提案①(指示と異なる):
SRC_A = a1.f90 a2.f90 a3.f90
SRC_B = b1.f90
OBJ_A = $(SRC_A:.f90=.o)
OBJ_B = $(SRC_B:.f90=.o)
%.o: %.f90
$(FC) -c $< -o $@
a_program: $(OBJ_A)
$(FC) -O3 -o $@ $(OBJ_A)
b_program: $(OBJ_B)
$(FC) -O2 -o $@ $(OBJ_B)
.PHONY: clean
clean:
rm -f $(OBJ_A) $(OBJ_B) a_program b_program
Github Copilotの提案②(指示に近いが、そのままは動かない):
SRC_A = a1.f90 a2.f90 a3.f90
SRC_B = b1.f90
OBJ_A = $(SRC_A:.f90=.o)
OBJ_B = $(SRC_B:.f90=.o)
%.o: %.f90
$(FC) -c $< -o $@ $(if $(filter $<,$(SRC_A)),-O3,-O2)
all: $(OBJ_A) $(OBJ_B)
$(FC) $(OBJ_A) $(OBJ_B) -o my_program
clean:
rm -f $(OBJ_A) $(OBJ_B) my_program
私が書いたコード(比較用):
SRC_A = a1.f90 a2.f90 a3.f90
SRC_B = b1.f90
OBJ_A = $(patsubst %.f90, %.o, $(SRC_A))
OBJ_B = $(patsubst %.f90, %.o, $(SRC_B))
all: $(OBJ_A) $(OBJ_B)
define COMPILE_A
$(FC) -c $(1) -o $(2) -O3
endef
$(OBJ_A): $(SRC_A)
$(foreach fn, $^, $(call COMPILE_A, $(fn), $(patsubst %.f90, %.o, $(fn))))
define COMPILE_B
$(FC) -c $(1) -o $(2) -O2
endef
$(OBJ_B): $(SRC_B)
$(foreach fn, $^, $(call COMPILE_B, $(fn), $(patsubst %.f90, %.o, $(fn))))
0 コメント:
コメントを投稿