Gofixの紹介
Introducing Gofix by Russ Cox
次のGoリリースでは、いくつかの基本的なGoパッケージに大きなAPI変更が入ります。 HTTPサーバーハンドラを実装しているコード、 net.Dialを呼び出しているコード、 os.Openを呼び出しているコード、 あるいはreflectパッケージを使っているコードは、 新しいAPIに合わせて更新しない限りビルドできなくなります。 リリースがより安定して頻度も少なくなった今、このような状況はよくあることになるでしょう。 これらのAPI変更はそれぞれ別の週次スナップショットで行われたので、個別には対応可能だったかもしれませんが、 まとめて見ると、既存のコードを更新するにはかなりの手作業が必要になります。
Gofixは、既存のコードを更新する手間を減らす新しいツールです。 ソースファイルからプログラムを読み込み、古いAPIの使用箇所を探し、現在のAPIを使うように書き換えて、 プログラムをファイルに書き戻します。すべてのAPI変更が古いAPIの機能をそのまま保持しているわけではないため、 gofixが常に完璧な変換をできるとは限りません。gofixが古いAPIの使用箇所を書き換えられない場合は、 その箇所のファイル名と行番号を示す警告を出力するので、開発者がそのコードを確認して書き換えられます。 gofixは単純で繰り返しの多い面倒な変更を引き受けてくれるので、開発者は本当に注意を払うべき変更に集中できます。
大きなAPI変更を行うたびに、機械的に可能な範囲でその変換を担うコードをgofixに追加していきます。 新しいGoリリースに更新してコードがビルドできなくなったときは、ソースディレクトリに対してgofixを実行するだけで済みます。
gofixは自分自身のAPIへの変更に対応できるように拡張することもできます。gofixプログラムは、 それぞれが特定のAPI変更を担当する「fix」と呼ばれるプラグインを動かす、シンプルなドライバーです。 現時点では、新しいfixを書くにはgo/astの構文木に対してある程度のスキャンと書き換えの処理が必要で、 その量はおおむねAPI変更の複雑さに比例します。実際に見てみたい方には、 netdialFix、 osopenFix、 httpserverFix、 reflectFixが、 複雑さが増していく順に並んだわかりやすい例になっています。
もちろん私たちもGoのコードを書いていて、私たちのコードも皆さんのコードと同様にこれらのAPI変更の影響を受けます。 通常、私たちはAPI変更と同時にgofixのサポートを書き、それを使ってメインのソースツリー内の利用箇所を書き換えます。 gofixは他のGoのコードベースや個人のプロジェクトの更新にも使っていますし、新しいGoリリースに合わせてビルドする際には、 Google社内のソースツリーの更新にすらgofixを使っています。
一例として、gofixはfmt/print.goのこのようなスニペットを書き換えられます。
switch f := value.(type) {
case *reflect.BoolValue:
p.fmtBool(f.Get(), verb, field)
case *reflect.IntValue:
p.fmtInt64(f.Get(), verb, field)
// ...
case reflect.ArrayOrSliceValue:
// バイトスライスは特別扱いです。
if f.Type().(reflect.ArrayOrSliceType).Elem().Kind() == reflect.Uint8 {
// ...
}
// ...
}
新しいreflect APIに合わせるとこうなります。
switch f := value; f.Kind() {
case reflect.Bool:
p.fmtBool(f.Bool(), verb, field)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p.fmtInt64(f.Int(), verb, field)
// ...
case reflect.Array, reflect.Slice:
// バイトスライスは特別扱いです。
if f.Type().Elem().Kind() == reflect.Uint8 {
// ...
}
// ...
}
上記のコードはほぼすべての行が何らかの形で変化しています。この書き換えに関わる変更は広範囲に及びますが、 そのほとんどは機械的な作業であり、まさにコンピュータが得意とする類いの仕事です。
gofixが実現できているのは、GoがGoのソースファイルを構文木へとパースする機能と、 その構文木を再びGoのソースコードとして出力する機能を標準ライブラリで備えているからです。 Goの出力ライブラリは公式のフォーマット(通常はgofmtツールが強制するもの)でプログラムを出力するので、 gofixは余計なフォーマットの変更を発生させることなくGoのプログラムに機械的な変更を加えられます。 実のところ、gofmtを作った主要な動機の一つは(おそらく波括弧をどこに置くかという論争を避けることに次ぐものですが)、 gofixのようにGoのプログラムを書き換えるツールの作成を簡単にすることでした。
gofixはすでになくてはならない存在になっています。特に、最近のreflectの変更は自動変換なしには受け入れがたいものだったでしょうし、 reflectのAPIはどうしても作り直す必要がありました。gofixのおかげで、既存コードを変換するコストを気にすることなく、 間違いを修正したりパッケージのAPIを根本的に見直したりできるようになりました。 私たちと同じように、皆さんにもgofixを便利で役立つものだと感じてもらえれば幸いです。
By Russ Cox