golang 知っておくと役に立つ5つのTips


## はじめに
CYBIRDエンジニア Advent Calendar 4日目担当の[@ntrv](https://qiita.com/ntrv)です。
3日目は[@keitarou](https://qiita.com/keitarou)の[コードレビューで意識していること(する時/される時)](https://qiita.com/keitarou/items/17a7669482b2ba35ffb4)でした。
コーディング規約やデッドコードなどの, あまり本質的ではない部分についてはlinterで解決したいですねー。

## golang 5つのTips

* golangを操る上で知らなければいけない!!というわけではありませんが, 知っておくと便利な5つのTipsをご紹介いたします。
    * Golang周りに詳しい方には退屈な記事になります。。

1. 外から変数を取り込む
1. 複数パッケージからcoverprofileを生成する
1. どの場所がカバーされているかを手軽に確認する
1. 複数のcoverprofileを簡単に1つにまとめる
1. クロスコンパイルしバイナリをパッケージングする

## 1. 外から変数を取り込む

* 以下のようなソースコードが存在したとします。
    * もし`compileDate`の中身がある場合はそのまま表示し, 中身がない場合は実行ファイル自身の変更日時を`Print()`から出力します。 

```go:compile_date.go
package cli

import (
	"time"
	"os"
	"fmt"
)

var (
	// CompileDate ... Injected compiled date, 
	compileDate string
)

// pseudoCompileDate ... Return modification time itself.
// If error has occurred, return current date
func pseudoCompileDate() time.Time {
	info, err := os.Stat(os.Args[0])
	if err != nil {
		return time.Now()
	}
	return info.ModTime()
}

// Print ... Output CompileDate to Stdout
// default is created date itself.
func Print() {
	if compileDate != "" {
		fmt.Printf("CompileDate: %v\n", compileDate)
	} else {
		fmt.Printf("CompileDate: %v\n", pseudoCompileDate())
	}
}
```

* 以下のようにビルド日時を
    * このとき`date`の実行結果から得られた文字列が`compileDate`変数に入ります。
    * ここで`github.com/ntrv/hogehoge`はこのソースの置いてあるリポジトリになります。

```bash
go build . -ldflags "-X \"github.com/ntrv/hogehoge/cli.compileDate=$(date)\"
```

### ユースケース
* バイナリのビルド日時や, ビルドした際のgitのリビジョンを埋め込むことができます。

## 2. 複数のパッケージからcoverprofileを生成する

* `go test`の結果をcoverprofileに出力し, 色々することを考えます。
* しかし複数パッケージのcoverprofileは一度に直接生成することは出来ません。

```
$ go test -v ./... -cover -coverprofile=ntrv.coverprofile
cannot use test profile flag with multiple packages
```

* そこでパッケージの数だけ`go test`を回すことを考えます。
    * Makefile内で動的にタスクを追加する。
    * bashの`for`で実行してもいいのですが, あるパッケージ単体だけでもテストできるようにします。

### 方法

* 第一引数`$(1)`を受け取り, `test_$(1)`というタスクを作ります。
* ここでは`$(1)`はパッケージ名を想定しております。
    * coverprofileという名前のディレクトリにプロファイルを吐き出すようにしております。

```make:Makefile
define test_template
.PHONY:test_$(1) 
test_$(1): deps
	go test -v -race -cover -covermode=atomic -coverprofile=coverprofile/$(1).coverprofile ./$(1)/...
endef
```

* 以下のようにテンプレートを呼び出すことで`test_foo`, `test_bar`, `test_buz`といったタスクが生成されます。
    * foreachの第一引数にindex, 第二引数にリストを指定しtest_templateにパッケージ名を与えます。

```make:Makefile
NOVENDOR_PKG = foo bar buz # 本来はシェルコマンドを使用し動的にパッケージ名のリストを得るようにする
$(foreach pkg, $(NOVENDOR_PKG), $(eval $(call test_template, $(pkg))))
```

* さらに生成されたタスク`test_*`を実行するためのタスク`test`を作成します。

```make:Makefile
TEST_TARGET = $(foreach target, $(NOVENDOR_PKG), test_$(target)) # => test_foo, test_bar, test_buz
.PHONY:test
test: $(TEST_TARGET)
```

* これでパッケージの数だけcoverprofileが生成できるようになり, さらに必要に応じて個別のパッケージに対しても実行可能になります。

### ユースケース
* 得られたcoverprofileを使用し,以下のようなサービスからテストに関する情報をより詳細に見ることが出来ます。
    * `go tool cover`
    * [CodeCov](https://codecov.io/)
    * [Coveralls](https://coveralls.io/)

### 参考サイト
* <http://uchimanajet7.hatenablog.com/entry/2015/03/20/211352>

## 3. どの行がカバーされているかを簡単に確認する。

* いちいち, githubにプッシュせずともローカルでカバレッジの変化やカバーされている様子を確認したい時があります。そんなときに使用できます。

### コマンドライン上から確認する

* 慣れ親しんだコマンドライン上から簡単に確認できます。

```
$ go tool cover -func=coverprofile/fizzbuzz.coverprofile
github.com/ntrv/hoge/fizzbuzz/call.go:6:           Call            100.0%
github.com/ntrv/hoge/fizzbuzz/condition.go:4:      IsFizz          100.0%
github.com/ntrv/hoge/fizzbuzz/condition.go:9:      IsBuzz          100.0%
github.com/ntrv/hoge/fizzbuzz/condition.go:15:     IsFizzBuzz      100.0%
github.com/ntrv/hoge/fizzbuzz/fizzbuzz.go:13:      New             100.0%
github.com/ntrv/hoge/fizzbuzz/print.go:10:         FprintFizzBuzz  100.0%
github.com/ntrv/hoge/fizzbuzz/print.go:17:         PrintFizzBuzz   100.0%
github.com/ntrv/hoge/fizzbuzz/range.go:4:          SetRange        100.0%
github.com/ntrv/hoge/fizzbuzz/word.go:4:           GetFizz         100.0%
github.com/ntrv/hoge/fizzbuzz/word.go:9:           GetBuzz         100.0%
github.com/ntrv/hoge/fizzbuzz/word.go:14:          GetFizzBuzz     100.0%
total:                                             (statements)    100.0%
```

### ブラウザ上から確認する

* ブラウザ上から確認すると, どこがカバーされているか一目瞭然です。

```
$ go tool cover -html=coverprofile/fizzbuzz.coverprofile
```


## 4. プロファイルを結合する

* coverprofileがパッケージごとにバラバラに生成されているので, これを1つにまとめCodeCovやCoverallsに送信し全体のカバレッジが見れるようにします。
* コマンドで一つにまとめてもよかったのですが, 面倒だと思ったので以下のようなツールを使用しました。
    * <https://github.com/sozorogami/gover>

```bash
# coverprofileフォルダ内のプロファイルを一つにまとめる
gover . coverprofile/*.coverprofile
```

## 5. クロスコンパイルし, バイナリをパッケージングする

* ネット上にはgoxを使用している例が多いのですがREADMEとまとめてzipやtarでパッケージングしたかったので, goxcを使用しています。
* 日本語だとなかなか資料が少なくオプションが多く複雑なので, goxcの使い方を簡単に紹介いたします。

```bash
# -build-ldflags: コンパイラにldflagsオプションを渡します。
# -d: 生成物の入るフォルダを指定します。
# -n: バイナリ名をここで指定します。
goxc -build-ldflags=$(LDFLAGS) -d=bin -n=${BINNAME}
```

* また設定ファイルは以下のように使用しております。
    * またTaskSettingsについては`goxc -help archive`等で確認可能

```json:.goxc.json
{
  "ConfigVersion": "0.9", # この設定ファイルのバージョンを指定
  "PackageVersion": "0.0.1", # パッケージ名に使用できる
  "PrereleaseInfo": "snapshot", # パッケージ名に使用できる
  "Tasks": [
    "xc",
    "copy-resources",
    "archive", 
    "rmbin"
  ],
  "Os": "linux darwin windows", # 対応するOSを列挙
  "Arch": "amd64", # 対応するアーキテクチャを列挙
  "Resources": {
    "Include": "README*,LICENSE*,INSTALL*", # 成果物の中に含むようにする
    "Exclude": "*.go" # 成果物の中に含まない
  },
  "BuildSettings": {
    "LdFlagsXVars": {
      "version": "indev" # ここでもldflags "-X "相当の設定が可能
    }
  },
  "TaskSettings": {
    "xc": {
      "autoRebuildToolchain": false,
      "validateToolchain": false,
      "verifyExe": false
    },
    "archive-tar-gz": {
      "include-top-level-dir": "!windows",  # ディレクトリごとまとめてしまうかどうか
      "platforms": "linux,darwin" # タスクを実施するプラットフォームを指定
    },
    "archive-zip": {
      "include-top-level-dir": "!windows",
      "platforms": "!linux,!darwin"
    }
  }
}
```

## 最後に
* 以上となります！自分自身記事を書くことで, golangの周辺技術を学び直すことができました！
* Goはツールセットが標準整備("Battery Included")されているのがいいところですよね！

---

* CYBIRD エンジニア Advent Calendar(http://qiita.com/advent-calendar/2017/cybird) 明日は、[@pakchee](https://qiita.com/pakchee)さんの「プログラマーはなぜ脆弱性対策をしなければならないのか」です！楽しみですね！