interface{} を使う理由をレビューでどう見抜くか
interface{} を使う理由をレビューでどう見抜くか
Go言語における interface{}
は、任意の型を受け取れる「完全抽象型」です。
柔軟さの裏側に、型安全性・責務の曖昧化・設計意図の不透明化という課題が隠れています。
この記事ではレビューアー視点で
- interface{} 使用の設計意図の読み解き方
- 妥当性の評価基準
- 具体的なレビューコメント事例
を順に整理していきます。レビューアー育成教材として、実務レビューと同等の視点で書きます。
良い実装例から設計意図を読み解く
まずは、正しくinterface{}を使うケースを整理します。
1. JSONライブラリにおける汎用パーサー
package parser
import (
"encoding/json"
"io"
)
// DecodeJSONは任意の構造体にJSONをデコードする汎用関数
func DecodeJSON(r io.Reader, v interface{}) error {
return json.NewDecoder(r).Decode(v)
}
このコードは、標準ライブラリでもよく見られる典型です。
ポイントは以下です:
- データの具体型は利用者側で決定
- DecodeJSON自体は汎用的なI/O処理に集中
- interface{}は「呼び出し側が実型を知っている」前提の抽象
汎用ライブラリの入出力で 「型を受け取る立場」の設計では、interface{}の利用は十分妥当です。
2. プラグイン実装のダイナミックロード
type Plugin interface {
Execute(args interface{}) error
}
この例は、プラグイン拡張ポイントを設計する際の柔軟性重視パターンです。
- 実装側が自由にargs構造を決められる
- コア側は一切型を固定しない
- interface{}は「型を知らない前提」の抽象化
「制約しないことが要件」の場合、interface{}が有効。型安全性は各実装側へ委譲する設計意図です。
良くない実装例
ここからは現場でよく見る誤用例に、レビューアー視点の指摘を加えて解説します。
例1: 特定用途なのに任意型を使ってしまう
func SaveUser(data interface{}) {
@Reviewer特定型(User型)にキャスト前提で設計されています。最初からUser型を引数に取る関数にしましょう。interface{}は不要です。> func SaveUser(u User) {> saveToDB(u)> } u := data.(User)
saveToDB(u)
}
例2: 動的分岐に逃げたロジック
func HandleEvent(e interface{}) {
switch v := e.(type) {
case UserCreated:
handleUserCreated(v)
case UserDeleted:
handleUserDeleted(v)
default:
log.Println("unknown event")
}
}
@Reviewerこの分岐はポリモーフィズム未活用の手続き的実装です。イベント型ごとにインターフェースを定義し、各型で共通ハンドラを実装しましょう。
例3: JSONマップ偏重
func ProcessData(input map[string]interface{}) {
id := input["id"].(string)
value := input["value"].(int)
fmt.Printf("ID: %s Value: %d\n", id, value)
}
@Reviewer入力構造が固定されているなら構造体定義しましょう。マップ経由は型安全性も補完も犠牲にします。> type Data struct {> ID string `json:"id"`> Value int `json:"value"`> }
例4: ジェネリクス未活用の古い実装
func MapAny(slice []interface{}, f func(interface{}) interface{}) []interface{} {
var result []interface{}
for _, v := range slice {
result = append(result, f(v))
}
return result
}
func MapAny[T any](slice []T, f func(T) T) []T {
var result []T
for _, v := range slice {
result = append(result, f(v))
}
return result
}
Go1.18以降はジェネリクス対応が可能です。型安全にリファクタリングしましょう。
レビュー観点チェックリスト
チェック項目 | 確認ポイント |
---|---|
任意型の妥当性 | 仕様として「型を知らない必要」があるか? |
型アサーション乱用 | 特定型へのキャスト前提なら直に型宣言可能では? |
型スイッチ依存 | interface分岐よりもポリモーフィズム設計可能では? |
マップ汎用構造 | map[string]interface{}は構造体定義で代替可能では? |
ジェネリクス活用 | Go1.18以降は型パラメータ導入で安全性向上可能では? |
あとがき:柔軟さに逃げず、意図を固定せよ
interface{}はGoにおける最後の抽象逃げ場です。
「柔軟にしておいた方が後から楽になる」という誘惑に負けやすい構造ですが、レビューの本質はむしろ逆です。
- 柔軟さに逃げず
- 型に意図を刻み
- 抽象を構造化する
レビューアーは常に「未来の利用者の負荷」を考慮し、型安全・保守性・責務分離を軸に改善提案する技術的ファシリテーターを目指しましょう。