Go言語入門:よくある質問 -Implementation Vol.4-

スポンサーリンク
Go言語入門:よくある質問 -Implementation Vol.4- ノウハウ
Go言語入門:よくある質問 -Implementation Vol.4-
この記事は約17分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語のよくある質問 について解説しています。

スポンサーリンク
  1. 背景
  2. Implementation
    1. 使用していない変数/importについての警告を止められますか?
    2. 解説
        1. この問題は何について説明しているの?
        2. 基本的な用語
        3. なぜGoはエラーにするのか?
          1. 理由1: 使用していない変数はバグの兆候
          2. 理由2: 使用していないimportはコンパイルを遅くする
        4. 他の言語との比較
          1. C/C++の場合
          2. Javaの場合
          3. Goの場合
        5. なぜ警告ではなくエラーなのか?
          1. 理由1: 「文句を言う価値があるなら、修正する価値がある」
          2. 理由2: 警告の氾濫を防ぐ
        6. 開発中の回避策
          1. 問題: 開発中は一時的に使わないコードがある
          2. 解決策1: ブランク識別子 _
          3. 解決策2: コメントアウト
          4. 解決策3: 実際に使う(おすすめ)
        7. importの場合
          1. 問題例
          2. 解決策1: ブランク識別子で「使う」
          3. 解決策2: コメントアウト
          4. 解決策3: 必要になってから追加(ベスト!)
        8. goimports: 自動importマネージャ
          1. goimportsとは?
          2. インストール
          3. 使い方
          4. 動作例
        9. エディタ/IDEとの統合
          1. VS Code
          2. GoLand (JetBrains)
          3. Vim/Neovim
          4. Emacs
        10. gopls: Go言語サーバー
        11. 実践例
          1. シナリオ: HTTP サーバーを書く
          2. シナリオ: デバッグコードを削除
        12. よくあるパターン
          1. パターン1: 複数の戻り値、1つだけ使う
          2. パターン2: forループのインデックスを使わない
          3. パターン3: デバッグ用の変数
        13. ブランク識別子の他の用途
          1. 用途1: インターフェース実装の確認
          2. 用途2: パッケージの初期化だけしたい
        14. Goのコンパイラオプションについて
          1. なぜオプションで無効化できないのか?
        15. まとめ
          1. Goが厳格な理由
          2. 開発中の対処法
          3. goimportsを使おう!
          4. Goの哲学
  3. おわりに 

背景

Go言語を学んでいると「なんでこんな仕様になっているんだろう?」「他の言語と違うのはなぜ?」といった疑問が湧いてきませんか。Go言語の公式サイトにあるFAQページには、そんな疑問に対する開発チームからの丁寧な回答がたくさん載っているんです。ただ、英語で書かれているため読むのに少しハードルがあるのも事実で、今回はこのFAQを日本語に翻訳して、Go言語への理解を深めていけたらと思い、これを読んだ時の内容を備忘として残しました。

Implementation

使用していない変数/importについての警告を止められますか?

使用されていない変数の存在はバグを示す可能性があり、一方で使用されていないimportはコンパイルを遅くするだけです。これは時間の経過とともにプログラムがコードとプログラマーを蓄積していくにつれて、かなりの影響になる可能性があります。これらの理由から、Goは使用されていない変数やimportを持つプログラムのコンパイルを拒否し、短期的な利便性と長期的なビルド速度およびプログラムの明確さをトレードオフしています。

それでも、コードを開発している時には、これらの状況を一時的に作り出すことはよくあり、プログラムがコンパイルされる前にそれらを編集して削除しなければならないのは煩わしいことがあります。

これらのチェックをオフにするか、少なくとも警告に軽減するコンパイラオプションを求める人もいます。しかし、そのようなオプションは追加されていません。なぜなら、コンパイラオプションは言語のセマンティクスに影響を与えるべきではなく、Goコンパイラは警告を報告せず、コンパイルを妨げるエラーのみを報告するからです。

警告がない理由は2つあります。第一に、文句を言う価値があるなら、コードで修正する価値があります。(逆に、修正する価値がないなら、言及する価値もありません。) 第二に、コンパイラに警告を生成させると、コンパイルがうるさくなり、修正すべき本当のエラーを隠してしまう可能性がある弱いケースについて警告する実装を奨励することになります。

しかし、状況に対処するのは簡単です。開発中に使用していないものを残しておくために、ブランク識別子を使用してください。

import "unused"

// この宣言はパッケージからアイテムを参照することで
// importが使用されているとマークします。
var _ = unused.Item  // TODO: コミット前に削除!

func main() {
    debugData := debug.Profile()
    _ = debugData // デバッグ中のみ使用。
    ....
}

今日では、ほとんどのGoプログラマーはgoimportsというツールを使用しており、これはGoソースファイルを自動的に書き換えて正しいimportを持つようにし、実際には使用されていないimportの問題を排除します。このプログラムはほとんどのエディタやIDEに簡単に接続でき、Goソースファイルが書き込まれたときに自動的に実行されます。この機能は、上記で説明したようにgoplsにも組み込まれています。

解説

この問題は何について説明しているの?

Goでプログラムを書いていると、こんなエラーに遭遇します:

package main

import "fmt"
import "os"  // 使っていない!

func main() {
    fmt.Println("Hello")
    x := 42  // 使っていない!
}

コンパイル結果:

./main.go:4:2: imported and not used: "os"
./main.go:7:2: x declared and not used

「警告じゃなくてエラー!? コンパイルできない!」という疑問に答えます。

基本的な用語
  • unused variable: 宣言したけど使っていない変数
  • unused import: importしたけど使っていないパッケージ
  • 警告 (warning): 問題を指摘するが、コンパイルは続行
  • エラー (error): コンパイルを停止させる問題
  • ブランク識別子: _ (アンダースコア)、値を捨てるために使う
  • goimports: importを自動管理するツール
なぜGoはエラーにするのか?
理由1: 使用していない変数はバグの兆候
func calculateTotal(price, quantity int) int {
    disocunt := 10  // typo! discount のつもり
    total := price * quantity
    return total
}

問題:

  • disocuntは使われていない
  • たぶんdiscountのタイポ
  • 割引が適用されていない!

Goがエラーにすることで:

./main.go:2:2: disocunt declared and not used

→ すぐにバグに気づける!

理由2: 使用していないimportはコンパイルを遅くする
import "fmt"
import "net/http"
import "database/sql"
import "encoding/json"
import "time"
import "sync"
import "io"
// ... 20個のimport、でも実際は1つしか使っていない

func main() {
    fmt.Println("Hello")
}

問題:

  • 使っていないパッケージもコンパイラが処理
  • ビルド時間が増加
  • 数ヶ月後、誰も削除しない
  • どんどん遅くなる…

Goの解決策:

エラー: 使っていないimportは削除してください
  ↓
常にクリーンなコード
  ↓
ビルドが速い!
他の言語との比較
C/C++の場合
#include <stdio.h>
#include <stdlib.h>  // 使っていない
#include <string.h>  // 使っていない

int main() {
    int x = 42;  // 使っていない
    printf("Hello\n");
    return 0;
}

結果: 警告が出るかもしれないが、コンパイルは成功

問題:

  • 警告を無視する文化
  • コードがどんどん汚くなる
  • 本当の問題が埋もれる
Javaの場合
import java.util.List;  // 使っていない
import java.util.Map;   // 使っていない

public class Main {
    public static void main(String[] args) {
        int x = 42;  // 使っていない - 警告のみ
        System.out.println("Hello");
    }
}

結果: 警告が出るが、コンパイルは成功

IDEの機能:

  • 黄色い波線で警告
  • でも、コンパイルは通る
Goの場合
import "fmt"
import "os"  // 使っていない

func main() {
    x := 42  // 使っていない
    fmt.Println("Hello")
}

結果: エラー。コンパイル失敗!

./main.go:4:2: imported and not used: "os"
./main.go:7:2: x declared and not used
なぜ警告ではなくエラーなのか?
理由1: 「文句を言う価値があるなら、修正する価値がある」
警告の世界:
  開発者: "ああ、警告ね。後で直そう"
  (数週間後)
  開発者: "警告? いつものことだから無視"
  (数ヶ月後)
  ビルドログ: [1000 warnings]
  開発者: "どれが重要? わからない..."

エラーの世界:
  開発者: "エラー! すぐ直さないと"
  (すぐ修正)
  コード: 常にクリーン!

Goの哲学:

修正する価値がある → エラーにする
修正する価値がない → 何も言わない

曖昧な中間(警告)は存在しない!

理由2: 警告の氾濫を防ぐ
// C言語の例
warning: unused variable 'x'
warning: unused variable 'y'
warning: implicit conversion from 'int' to 'float'
warning: possible uninitialized variable
warning: deprecated function
warning: ...
warning: ...
(100個の警告)
error: segmentation fault  // ← これが本当の問題!

問題: 本当に重要なエラーが埋もれる

Goのアプローチ:

エラー: x declared and not used

→ 修正 → クリーン → 新しいエラー(もしあれば)が明確!

開発中の回避策
問題: 開発中は一時的に使わないコードがある
func main() {
    // 今はデバッグ中
    debugData := collectDebugInfo()  // 後で使う予定
    
    // 本来のコード
    fmt.Println("Hello")
}

エラー:

debugData declared and not used

「まだ使ってないけど、後で使うのに!」

解決策1: ブランク識別子 _
func main() {
    debugData := collectDebugInfo()
    _ = debugData  // "使った"ことにする
    
    fmt.Println("Hello")
}

動作:

  • _ は「値を捨てる」という意味
  • でも、変数は「使われた」と見なされる
  • コンパイルエラーが消える!
解決策2: コメントアウト
func main() {
    // debugData := collectDebugInfo()  // 後で使う
    
    fmt.Println("Hello")
}

シンプルだが、コメントを外すのを忘れがち。

解決策3: 実際に使う(おすすめ)
func main() {
    debugData := collectDebugInfo()
    fmt.Printf("Debug: %+v\n", debugData)  // 一時的に表示
    
    fmt.Println("Hello")
}

デバッグにも役立つ!

importの場合
問題例
package main

import (
    "fmt"
    "net/http"  // 後で使う予定
    "database/sql"  // 後で使う予定
)

func main() {
    fmt.Println("Hello")
}

エラー:

imported and not used: "net/http"
imported and not used: "database/sql"
解決策1: ブランク識別子で「使う」
package main

import (
    "fmt"
    "net/http"
    "database/sql"
)

var _ = http.Get  // TODO: 削除する
var _ = sql.Open  // TODO: 削除する

func main() {
    fmt.Println("Hello")
}

注意: コミット前に削除すること!

解決策2: コメントアウト
package main

import (
    "fmt"
    // "net/http"  // 後で使う
    // "database/sql"  // 後で使う
)

func main() {
    fmt.Println("Hello")
}
解決策3: 必要になってから追加(ベスト!)
package main

import "fmt"

func main() {
    fmt.Println("Hello")
}

// 後で http が必要になったら、その時追加!
goimports: 自動importマネージャ
goimportsとは?

magic ツール:

  • 必要なimportを自動追加
  • 不要なimportを自動削除
  • importを自動整理
インストール
go install golang.org/x/tools/cmd/goimports@latest
使い方

ファイルを修正:

goimports -w main.go

ディレクトリ全体:

goimports -w .
動作例

編集前:

package main

import "os"  // 使っていない

func main() {
    Println("Hello")  // fmt.Println のつもり
}

goimports実行:

goimports -w main.go

編集後:

package main

import "fmt"  // 自動追加!

func main() {
    fmt.Println("Hello")  // 修正はしないが、importは追加
}
エディタ/IDEとの統合
VS Code

settings.json:

{
    "[go]": {
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
            "source.organizeImports": true
        }
    }
}

動作:

  • ファイル保存時に自動実行
  • importが自動整理される
  • 開発者は何もしなくてOK!
GoLand (JetBrains)
設定 → Tools → File Watchers
→ goimports を有効化

保存時に自動実行。

Vim/Neovim
" .vimrc または init.vim
let g:go_fmt_command = "goimports"
Emacs
;; .emacs または init.el
(setq gofmt-command "goimports")
(add-hook 'before-save-hook 'gofmt-before-save)
gopls: Go言語サーバー

goplsは、エディタとGo toolsを繋ぐ言語サーバー:

エディタ ←→ gopls ←→ Go tools
                       (goimports等)

機能:

  • 自動補完
  • エラーチェック
  • import整理
  • リファクタリング

ほとんどのモダンエディタで自動的に動作:

  • VS Code
  • Vim/Neovim
  • Emacs
  • Sublime Text
実践例
シナリオ: HTTP サーバーを書く

ステップ1: 最初のコード

package main

func main() {
    http.ListenAndServe(":8080", nil)
}

エラー:

undefined: http

手動なら:

import "net/http"  // 手動で追加

func main() {
    http.ListenAndServe(":8080", nil)
}

goimportsなら:

  • ファイル保存
  • 自動的にimport "net/http"が追加される!
  • 何もしなくてOK
シナリオ: デバッグコードを削除

デバッグ中:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Println("Debug: starting server")  // デバッグ用
    http.ListenAndServe(":8080", nil)
}

デバッグ終了後:

package main

import (
    "fmt"  // もう使っていない!
    "net/http"
)

func main() {
    // fmt.Println("Debug: starting server")  // コメントアウト
    http.ListenAndServe(":8080", nil)
}

手動なら:

  • import “fmt” を削除
  • 忘れやすい…

goimportsなら:

  • ファイル保存
  • 自動的にimport "fmt"が削除される!
よくあるパターン
パターン1: 複数の戻り値、1つだけ使う
// エラーを無視したい場合
file, _ := os.Open("file.txt")  // エラーを_で捨てる

// 最初の値を無視したい場合
_, value := someFunction()
パターン2: forループのインデックスを使わない
// インデックスは不要
for _, item := range items {
    fmt.Println(item)
}
パターン3: デバッグ用の変数
func processData(data []byte) {
    debugInfo := analyzeData(data)
    _ = debugInfo  // デバッグ中のみ使用
    
    // 実際の処理
    result := transform(data)
    return result
}

TODO コメントを追加:

_ = debugInfo  // TODO: 本番前に削除
ブランク識別子の他の用途
用途1: インターフェース実装の確認
type Writer interface {
    Write([]byte) (int, error)
}

type MyWriter struct{}

func (w MyWriter) Write(b []byte) (int, error) {
    // 実装
    return len(b), nil
}

// コンパイル時にインターフェースを実装しているか確認
var _ Writer = MyWriter{}
用途2: パッケージの初期化だけしたい
import (
    "database/sql"
    _ "github.com/lib/pq"  // init()を実行するだけ
)

PostgreSQLドライバは、importするだけでsqlパッケージに登録される。

Goのコンパイラオプションについて
なぜオプションで無効化できないのか?

質問:-allow-unusedみたいなオプションを追加できない?」

答え:

コンパイラオプションは言語のセマンティクスを
変えるべきではない

具体的には:

// Aさんのコード(-allow-unused付き)
import "unused"
var x int  // 未使用

// Bさんのコード(オプションなし)
// → コンパイルエラー!

// 同じGoコードなのに、
// オプション次第で動いたり動かなかったり?
// それはおかしい!

Goの原則:

  • どの環境でも同じGoコードは同じ動作
  • コンパイラオプションでコードの正当性が変わらない
まとめ
Goが厳格な理由
  1. 🐛 バグの早期発見
    • 未使用変数はバグの兆候
  2. ビルド速度
    • 不要なimportを排除
  3. 📖 コードの明確さ
    • 常にクリーンなコード
  4. 🎯 本当のエラーに集中
    • 警告の氾濫を防ぐ
開発中の対処法
方法用途推奨度
ブランク識別子 _一時的に残す⭐⭐⭐
コメントアウト長期間使わない⭐⭐
goimportsimport管理⭐⭐⭐⭐⭐
すぐ削除不要なら削除⭐⭐⭐⭐
goimportsを使おう!

設定するだけ:

  1. インストール
  2. エディタに統合
  3. 保存時に自動実行

効果:

  • ✅ importの悩みゼロ
  • ✅ 常に整理されたコード
  • ✅ 開発に集中できる
Goの哲学
厳格さは優しさ
  ↓
エラーで明確に指摘
  ↓
すぐ修正
  ↓
クリーンなコード
  ↓
長期的な保守性向上

最初は「厳しい!」と感じるかもしれませんが、これはGoが長期的な品質を重視している証です。goimportsなどのツールと組み合わせることで、厳格さと開発効率を両立できます!

ベストプラクティス:

  • goimportsをエディタに統合
  • 保存時に自動実行
  • コード品質とビルド速度を維持
  • 開発は快適に!

おわりに 

本日は、Go言語のよくある質問について解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました