Go の開発環境を作る

Go の開発環境について、調べた内容を纏めておきます。

環境

Go のインストール先は WSL 上の Ubuntu となります。利用するエディタは VS Code です。

Go のインストール

公式ドキュメントを参考に、Go を Ubuntu に インストールします。

Download and install

ダウンロード。

$ wget https://go.dev/dl/go1.19.1.linux-amd64.tar.gz

旧環境を削除してからインストール。

$ sudo rm -rf /usr/local/go
$ sudo tar -C /usr/local -xzf go1.19.1.linux-amd64.tar.gz

プロファイルに Go の PATH を追加。

$ echo 'export PATH="$PATH:/usr/local/go/bin"' >> ~/.bash_profile
$ source ~/.bash_profile

確認。

$ go version
go version go1.19.1 linux/amd64

プロジェクトを作成してコードを実行

サンプルコードを書いて実行してみます。下記が参考のチュートリアル

Tutorial: Get started with Go

新規プロジェクト用のディレクトリを作成。

$ mkdir ~/sample
$ cd ~/sample

go module の初期化。Go における モジュール の概念については後述。

$ go mod init sample
go: creating new go.mod: module sample

go mod init

実行するコードを用意。

ファイル構成

sample
├── double
│   └── double.go
├── go.mod          # このファイルは go mod init 時に自動作成される
└── main.go

sample/main.go

package main

import (
    "fmt"
    "sample/double"
)

func main() {
    fmt.Println(double.Double(2))
}

sample/double/double.go

package double

func Double(x int64) int64 {
    return x * 2
}

実行。

$ cd ~/sample
$ go run .
4

go run

コンパイルだけ実行。

$ cd ~/sample
$ go build .
$ ls -l sample
-rwxr-xr-x 1 zunda zunda 1814702 Sep 29 14:20 sample
$ ./sample
4

go build

モジュールとパッケージとは

モジュール とは、パッケージ の集合であり、Git で管理されるリポジトリの単位のこと。go.mod ファイル内に記述された module path により定義されている。

A module is a collection of packages that are released, versioned, and distributed together. Modules may be downloaded directly from version control repositories or from module proxy servers.

A module is identified by a module path, which is declared in a go.mod file, together with information about the module’s dependencies. The module root directory is the directory that contains the go.mod file. The main module is the module containing the directory where the go command is invoked.

パッケージ とは、同一ディレクトリに存在するソースファイルたちを指すもの。Go における名前空間、みたいな理解でいいのだろうか。ソースコード内冒頭の package 式で宣言されている。

Each package within a module is a collection of source files in the same directory that are compiled together. A package path is the module path joined with the subdirectory containing the package (relative to the module root).

Modules, packages, and versions

「1ディレクトリ=1パッケージ」となるので、ディレクトリ名と同一のパッケージ名とするのが慣例であるが、ディレクトリ名とパッケージ名を異なる名前としても動くらしい。また、main パッケージ 内にある main 関数 が、Go プログラムのエントリーポイントとなる、とのこと。

A complete program is created by linking a single, unimported package called the main package with all the packages it imports, transitively. The main package must have package name main and declare a function main that takes no arguments and returns no value.

Program execution begins by initializing the main package and then invoking the function main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.

Program execution

go.mod ファイルとは

go mod init コマンド実行時のモジュール初期化時に、モジュールのルートディレクトリ内に作成されます。今回の場合、ファイル内記述は下記。

sample/go.mod

module sample

go 1.19

用意されている設定たち。

  • module directive
  • go directive
  • require directive
  • exclude directive
  • replace directive
  • retract directive

設定されている module path や、Go のバージョン、ソースコード内で利用する 3rd party パッケージのバージョン等が定義されるようです。

go.mod files

go mod コマンドにて操作するとのこと。

$ go help mod
Go mod provides access to operations on modules.

Note that support for modules is built into all the go commands, not just 'go mod'.
For example, day-to-day adding, removing, upgrading,and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.

Usage:

        go mod <command> [arguments]

The commands are:

        download    download modules to local cache
        edit        edit go.mod from tools or scripts
        graph       print module requirement graph
        init        initialize new module in current directory
        tidy        add missing and remove unused modules
        vendor      make vendored copy of dependencies
        verify      verify dependencies have expected content
        why         explain why packages or modules are needed

Use "go help mod <command>" for more information about a command.

VS Codeの拡張

Go の開発チームが作ってくれている Extension があり、これを利用すれば問題ないようです。

Go in Visual Studio Code

下記がインストール方法。

Go 開発用に Visual Studio Code を構成する

フォーマッター

Go には gofmt というフォーマッター用のコマンドが標準で用意されている。検査したいファイルを指定して実行すると、フォーマットされたコードを出力してくれます。

Gofmt formats Go programs. It uses tabs for indentation and blanks for alignment. Alignment assumes that an editor is using a fixed-width font.

Without an explicit path, it processes the standard input. Given a file, it operates on that file; given a directory, it operates on all .go files in that directory, recursively. (Files starting with a period are ignored.) By default, gofmt prints the reformatted sources to standard output.

gofmt

その他、準標準ライブラリで goimports があり、これは gofmt のスタイルの他、import 文部分のフォーマットをしてくれます。

goinports

VS Code の Extension では標準でフォーマッターが有効となっており、ファイル保存時に自動的に変換してくれます。デフォルトで利用しているスタイルが分かりにくかったのですが、Go の標準 language server である gopls のデフォルトが goimports であるため、おそらく goimports を利用しているものと考えています。

ここで変えられます。

local is the equivalent of the goimports -local flag, which puts imports beginning with this string after third-party packages. It should be the prefix of the import path whose imports should be grouped separately.

Formatting - gopls

リンター

リンターにおいても、go vet というコマンドが標準で用意されています。

go vet

例えば、以下のようにコードを変更してみます。double.Double 関数の引数を変更。

sample/main.go

package main

import (
    "fmt"
    "sample/double"
)

func main() {
    fmt.Println(double.Double("2")) // 引数を文字型にする
}

go vetを実行してみる。

$ go vet .
# sample
vet: ./main.go:9:28: cannot use "2" (untyped string constant) as int64 value in argument to double.Double

VS Code の Extension では staticcheck という 3rd party のパッケージがデフォルトで有効となっています。

staticcheck

リンターのライブラリは幾つかあるようですが、何が良いかなんてまるで分からず、以下はサイボウズさんの技術ブロクより。

その中でYakumoチームではstaticcheckを採用しました。 採用理由は、staticcheckはデフォルト設定でも程よいルールが適用され、golintほど過剰な指摘がないという点です。 またstaticcheckはGoogleが公式でスポンサーをしており、社内のNecoチームがすでに利用していたというのも理由の1つです。

ここで挙げたツールとgolintは、指摘内容が異なる点に注意する必要があります。 これらのツールはバグとなりうるコードを指摘するのに対して、golintはスタイルチェッカーです*1。 例えばgolintはexportされた関数へのドキュメントコメントを強制しますが、staticcheckではデフォルトでそのチェックが無効になっています。 またstaticcheckは、err確認漏れ (if err != nil)を指摘してくれますが、golintにはその機能がありません。

Goの静的解析ツールをgolintからstaticcheckに移行した話


とりあえず、こんな感じで。気づいた都度、追記していくかもです。