Goで開発を始める前に絶対に読んでほしいGolandの設定3選

この記事は[全部俺 Advent Calendar 2018](https://adventar.org/calendars/3702)の6日目の記事です。

# 完成形
- 開発環境をDocker化してローカル環境から切り離す
- `go fmt`や`goimports`などの各種静的解析/整形ツールをファイル保存時に自動実行するようにする
    - [File Watchers設定ファイル](https://gist.github.com/taishi-h/601b2476619714e4964f277dae626e08)をimportして各種リントを自動実行するように設定している
- `pre-commit`ファイルを設定して、`golint`がエラーを出力する場合にはcommit自体できないようにする

# 開発環境のDocker化
## Docker containerの作成
1. Dockerが入っていない場合はマシンに[Dockerをインストール](https://docs.docker.com/install/)します。
1. Golandを立ち上げて、`Plugins`から`Docker integration`をインストールし、Golandを再起動します。
1. 新規Projectを作成し、下の画像1.のように、DockerタブからDocker→imagesを選んで右クリックし、Pull Image...を選択します。
1. Repositoryにgolangを入力し、OKを押すと、golang:latestのDocker Imageがプルされるはずです。
1. imagesにgolang:latestが作成されるので、右クリックしてCreate containerを選択し、Create...を選びます。
1. 画像2.のように、Name/Image ID/Container Nameを設定・確認し、Runを押します。

画像1. Docker Hubからgolangイメージをpullする
![image.png](https://qiita-image-store.s3.amazonaws.com/0/190807/75e2247b-6f57-c49a-8c40-1cba1c912fed.png)

画像2. Docker containerを作成する
![image.png](https://qiita-image-store.s3.amazonaws.com/0/190807/45f544cb-8b58-c645-f775-7aecaa16e574.png)

## 実行の構成を編集
`Hello, World!`を表示するファイルを作成し、これをDocker環境で実行できるように実行の構成を編集します。
まず、以下の実行ファイルを作成します。

```~/hello.go
package main

import "fmt"

func main() {
	fmt.Println("Hello, World!")
}
```

次に、以下の画像のように、Run Configurationsを編集します。
- Commandを`go run /go/src/golang-sample/hello.go`に変更
- Run optionsを`-v [プロジェクトルートパス]:/go/src/golang-sample`に変更

![image.png](https://qiita-image-store.s3.amazonaws.com/0/190807/c4d157d2-7cec-261e-6907-26fcdb9ca0a5.png)

この状態でRunを実行すると、以下のようにDocker container内で`Hello, World!`が出力されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/190807/1ad825e0-aa24-5d87-6160-b441ce980acf.png)

これで実行環境のDocker化は完了です。

# ファイル保存時に自動で`go fmt`などの各種整形ツールを実行させる
## pluginのインストール
Pluginsから、[File Watchers](https://plugins.jetbrains.com/plugin/7177-file-watchers)をインストールし、Golandを再起動します。
後は、以下の整形ツールのうち、使うものについて設定してください。
【2019/09/03追記】
全部入りのファイルを以下のgistに配置しているので、こちらをローカルにファイル保存したものをimportすることでも設定完了できます。
- [File Watchers設定ファイル](https://gist.github.com/taishi-h/601b2476619714e4964f277dae626e08)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/190807/3cd7178d-46c4-88d2-e037-c43f5d2b1c22.png)

私は上の画像のように、ファイルをimportした後、File Watchersの適用LevelをGlobalにして使用しています。こうすることで、Projectごとに以下の作業を繰り返すことなく、いつも同じLinterツールを同じように使用することができます！

## go fmt/goimports
アクション検索から、`File Watchers`を検索して、Toolsの`File Watchers`に移動し、`+`ボタンを押して`go fmt`または`goimoprts`を押します。
これらのツールについては以上で完了です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/190807/b34c1c8d-bc2e-5fca-9359-77bb7a67623f.png)

## golint
`go get -u golang.org/x/lint/golint`でgolintをインストールし、File Watchersの設定画面で`<custom>`を選択し、下記のように設定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/190807/53cdf761-4a61-5a9c-a7e6-027df4b56f4f.png)

## goreturns
`go get -u sourcegraph.com/sqs/goreturns`でgoreturnsをインストールし、File Watchersの設定画面で`<custom>`を選択し、下記のように設定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/190807/05debe6d-2af5-4085-6603-7f70a118c771.png)

## go vet
File Watchersの設定画面で`<custom>`を選択し、下記のように設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/190807/a850dedf-92db-ec8d-c4b6-4e85b331b390.png)


## 実行確認してみる
以下のようなJSONファイルを読み込んで表示するGoファイルを作成してみます。
lintで自動整形されることを確認するために、わざと不要な改行を入れたり、import順番をめちゃくちゃにしたりしています。

```~/json_reader.go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"io/ioutil"
)

type Product struct {
	Id    int
	Name  string
	Price int
}

func main() {
	bytes, err := ioutil.ReadFile("vro.json")
	if err != nil {
		log.Fatal(err)
	}

	
	
	var products []Product
	if err := json.Unmarshal(bytes, &products); err != nil {
		log.Fatal(err)
	}

	for _, p := range products {
		fmt.Printf("%d : %s: %d\n", p.Id, p.Name, p.Price)
	}
}
```

このファイルについて、⌘ + Sや Ctrl + Sでファイル保存をしてみると、import順序を修正したり、不要な改行が削除され、次のようにファイルが自動整形されるはずです。

```~/json_reader.go
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
)

type Product struct {
	Id    int
	Name  string
	Price int
}

func main() {
	bytes, err := ioutil.ReadFile("vro.json")
	if err != nil {
		log.Fatal(err)
	}

	var products []Product
	if err := json.Unmarshal(bytes, &products); err != nil {
		log.Fatal(err)
	}

	for _, p := range products {
		fmt.Printf("%d : %s: %d\n", p.Id, p.Name, p.Price)
	}
}

```

地味にファイル末尾の改行も追加されたりしています。
更に、golintを適用している場合はlint結果として、以下の内容が画面下部のRunウィンドウに出力されているはずです。

```
/go/bin/golint /go/src/golang-sample/json_reader.go
/go/src/golang-sample/json_reader.go:10:6: exported type Product should have comment or be unexported
/go/src/golang-sample/json_reader.go:11:2: struct field Id should be ID
```

結果を読むと、Errorというほどでは無いが、先程のGoファイルにおいて、以下の改善点が見つかったようです。
- Productにcommentを書くべき
- Idという名前をIDという名前に変更したほうが良い

指摘されたとおりに修正を行って再度ファイル保存をすると、上記の指摘が消えることが確認できます。

# pre-commitを用いてイケてないソースコードのコミットを阻止する
## pre-commitとは？
`.git/hooks/`以下に配置されているファイル群の1つです。
`.git/hooks/`のファイル群は、何かのイベントにフックされて記載されている処理を行うものになります。
pre-commitはそのうちのcommit前に呼び出されるもので、これを編集することでcommit前にgolintを走らせ、結果がNGだった場合はコミット自体できないようにすることが可能です。

## pre-commitファイル
下記のファイルを`.git/hooks/pre-commit`として保存することで、`git commit`を失敗する環境を作ることができます。
前提として、golintがインストールされていることが必要なのでない場合は`go get -u golang.org/x/lint/golint`しておいてください。

``` [project-root]/.git/hooks/pre-commit
#!/usr/bin/env bash

failed=false

for file in "$@"; do
    # redirect stderr so that violations and summaries are properly interleaved.
    if ! golint -set_exit_status "$file" 2>&1
    then
        failed=true
    fi
done

if [[ $failed == "true" ]]; then
    exit 1
```

上記のファイルを作成した上で、先程のjson_reader.goのIDをIdに戻してコミットしようとすると、以下のような結果が出てコミットが失敗するようになります。

```terminal
~/g/s/golang-sample> git commit -m "test commit"
On branch master
Changes not staged for commit:
        modified:   json_reader.go
```


上記のようなツールを使いこなして、より良い開発環境を作成していきましょう！
他に便利なツールがある場合、コメントなどでご教授いただけると幸いです！