Goにおける関数分割の単位とレビューでの見極め方
関数分割の単位はどう見極めるか?長い関数のレビュー練習
関数の肥大化は、Goに限らずあらゆる言語で発生する構造的課題です。特にGoは関数がシンプルかつ宣言的に書けるため、“なんとなく短く”ではなく、“設計的に意味のある単位”で分割されているかがレビュー観点として重要です。
この記事では、レビューアーとして関数分割の是非を判断するための構造的観点、読みやすさだけでない分割の意義、そして改善の方向性を提示します。
良い実装例 - 責務に応じた関数分割
func HandleRequest(req *http.Request) error {
input, err := parseInput(req)
if err != nil {
return err
}
if !checkAuth(input.UserToken) {
return errors.New("unauthorized")
}
result, err := execute(input.Command)
if err != nil {
return err
}
return respond(result)
}
func parseInput(req *http.Request) (*Input, error) {
// 入力解析処理
}
func checkAuth(token string) bool {
// 認可チェック処理
}
func execute(cmd string) (string, error) {
// ビジネスロジック実行処理
}
func respond(output string) error {
// 応答返却処理
}
技術解説
このコードは4つの主要な責務(解析・認可・実行・応答)を分離し、それぞれを独立した関数として抽出しています。関数単体でのテストや再利用がしやすく、エラー処理も粒度を保ったまま局所的に行えています。
レビューにおいては、関数ごとの設計意図が明確かどうか、結合度が過度に高くなっていないか、再利用性が担保されているかを確認します。
良くない実装例 - 関数が肥大化して責務が不明瞭に
func HandleRequest(req *http.Request) error {
// 入力解析
input := &Input{}
body, err := io.ReadAll(req.Body)
if err != nil {
return err
}
if err := json.Unmarshal(body, input); err != nil {
return err
}
// 認可
if input.UserToken != "admin-token" {
return errors.New("unauthorized")
}
// 実行
var result string
switch input.Command {
case "echo":
result = input.Payload
case "reverse":
result = reverse(input.Payload)
default:
return errors.New("unknown command")
}
// 応答
_, err = fmt.Fprintf(req.Context().Value("writer").(io.Writer), result)
return err
}
@Reviewerこの関数は4つの異なる責務を1つに詰め込んでいます。`parseInput`, `checkAuth`, `execute`, `respond` といった処理を分割し、各機能の単体テストや修正時の影響範囲を明確化しましょう。
チェックポイント:レビューで確認すべき関数分割の観点
観点 | チェックするポイント |
---|---|
複数責務の混在 | 処理の性質が明確に異なるコードが同居していないか? |
コメントによるセクション分け | // parse , // execute のように関数内でブロック化していないか? |
ネストの深さ | if やswitch が3段以上にネストしていないか? |
ローカル変数の多さ | 7個以上の変数が宣言されていないか?用途は統一されているか? |
副作用の混在 | ネットワーク/ファイル操作などの副作用が複数責務にまたがっていないか? |
単体テストの難しさ | 一部だけをテストできる設計になっているか?セットアップの負荷が高くないか? |
抽象化の有無 | 関数が構造や制約を提供しているか?単なる処理転送になっていないか? |
追加観点:設計意図を伝えるための命名と粒度調整
関数名は処理内容を説明するのではなく「その関数が担う責任」を表現するのが望ましいです。
たとえば checkAndExecute()
よりも、checkAuth()
と executeCommand()
に分割する方が責務が明確になります。
関数分割は責務と構造のリファクタリング
関数分割の目的は単なる「短くすること」ではありません。
設計の視点から見ると、関数とは構造の最小単位であり、責務の境界線です。レビュー時には、次のような問いかけを意識すると構造的判断がしやすくなります。
- この関数は“何をする”のではなく“何を担う”のか?
- この中に設計上の“複数の視点”が混ざっていないか?
- テスト観点から見たときに“分けるべき”単位になっているか?
複雑な関数を見かけたら、その中に潜む構造上の境界線を見つけ出し、適切な関数分割を提案する。
それが、レビューアーとしての重要な役割のひとつです。