【Go】Go言語Web開発プロジェクト Part2.5 - 箸休めにdepによるパッケージ管理改善とgolintなどで良いGoのコードを書いていく -


前回のあらすじ

前回はログイン認証の部分まで最低限開発する事ができました。
前回の記事から一ヶ月経過してしまい、そろそろ次のステップに進まないといけません。今日はこれまでやってきた事と、この一ヶ月の間でGo言語の勉強会にいくつか参加し得た知見、またGo言語における良いコードを書けるようになるためにはどうすれば良いか、参考文献を色々調べていったのでそれを共有したいと思います。

gooseをyamlファイル経由でコマンド実行できるようにする(Reject)

現在、私のプロジェクトでは、bitbucketからforkされたこちらのgooseを使っています。
https://github.com/pressly/goose
こちらはコマンド実行のみをサポートしているため、YAML経由でマイグレーション実行ができるようにPRを作成していました。
...結論を言うとRejectされました。理由はこちらです
https://github.com/pressly/goose/pull/118

しかし、今回のPRからパッケージ管理ツールはGo言語のオフィシャルチームが開発しているdepを使う事がこれからは一択かなと思い、パッケージ管理ツールをgomからdepに置き換えることにしました。

depのオフィシャルドキュメントについてはこちら→https://golang.github.io/dep/

depのインストール

環境を再編成するために、まずはdepをインストールします。

% goenv exec go get -u github.com/golang/dep/cmd/dep 

その次はプロジェクトで利用するパッケージの一元管理ファイルGopkg.toml,Gopkg.lockファイルを生成します。

Gopkg.toml
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
#   name = "github.com/user/project"
#   version = "1.0.0"
#
# [[constraint]]
#   name = "github.com/user/project2"
#   branch = "dev"
#   source = "github.com/myfork/project2"
#
# [[override]]
#   name = "github.com/x/y"
#   version = "2.4.0"
#
# [prune]
#   non-go = false
#   go-tests = true
#   unused-packages = true

required = ["github.com/pressly/goose/cmd/goose"]

[[constraint]]
  name = "github.com/lib/pq"
  version = "1.0.0"

[[constraint]]
  name = "github.com/yosssi/ace"
  version = "0.0.5"

[prune]
  go-tests = true
  unused-packages = true

基本的には、direct dependency(プロジェクトのコードから直接されている)依存関係を[[constraint]]に記載します。
今回の場合、gooseに関してはそうではないため、requiredで必要なパッケージのソースを直接インストールします。

% dep ensure

その後、dep ensureで一括インストールします。しかし、dep ensureでは、srcを取得するのみでパッケージをインストールしてくれる訳ではありません。
詳しくはこちら→https://dev.classmethod.jp/go/dep/

以下、クラスメソッド株式会社のdepの記事から引用

実行ファイルをインストールしたい場合はパッケージのインストールディレクトリ(mainパッケージが置かれているディレクトリ)に移動し(cd vendor/github.com/user/thing/cmd/thing) go install . でインストールする必要があります。

gooseをインストールする場合は、以下のようにします。

% cd vendor/github.com/pressly/goose/cmd
% goenv exec go install .

以上より、Dockerfileを以下のように更新しました。

Dockerfile
FROM golang:1.11.0

RUN apt update

# PostgreSQLのClientをインストール
RUN apt install -y postgresql-client

WORKDIR /go/src/dairy_report
ADD src/dairy_report/Gopkg.toml Gopkg.toml
ADD src/dairy_report/Gopkg.lock Gopkg.lock
# depだけgo getで取得
RUN go get -u github.com/golang/dep/cmd/dep

RUN dep ensure --vendor-only
WORKDIR /go/src/dairy_report/vendor/github.com/pressly/goose/cmd/goose/
RUN go install .

WORKDIR /go/src/dairy_report

Dockerコンテナでdep ensureする場合は、--vendor-onlyをオプションとして追加する必要があります。
詳しくはこちら→https://github.com/golang/dep/issues/796

また、docker-compose.ymlではsrc/配下のソースコードをホストのものとマウントとして共有し、vendorディレクトリをコンテナ専用のボリュームとして格納したいので以下のように定義しました。

docker-compose.yml
version: '3'
volumes:
  vendor:
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: go run main.go
    volumes:
      - ./src/dairy_report/:/go/src/dairy_report/
      - vendor:/go/src/dairy_report/vendor
(中略)

docker-composeの設定は終わりました。次はコード整形とレビューについてのお話です。

フォーマット関連の整形は自動的に修正してもらったり、コメントレビューしてほしい

筆者はRails歴が長いため、コード整形やプロジェクトのローカルルールなどによるコード違反なものは、rubocopを利用して修正・あるいはコメントを残してもらっています。
本当はフォーマットも一貫性のあるコードを開発者が自分で気づかなきゃいけないと思っていますが、発見するのは結構大変なもので、フォーマット以外にも自分で気づいて直さなきゃいけないコードもあったりします。よって、ここ最近はコード修正の本質ではない(?)部分は機械的に直してもらった方が良いと言うのが僕の思想です。機械的に見つけてもらうことにより、ある程度負担が減り、読み手を意識したコードを書くための修正にかけられる時間が増えていきます(このあたりはリーダブルコードやプログラミング作法を読んでようやく意識できるようになってきました)。

ということで、フォーマットくらいは機械的に修正してもらおうとそういうツールがないか調べたら、なんとGoではデフォルトでgofmtというものが存在し、それにコードを喰わせることにより自動的に修正されます。

goenv経由でgofmtを実行します。

% goenv exec gofmt -d main.go                                                                                                                                                   (git)-[feature/add_login]
diff -u main.go.orig main.go
--- main.go.orig    2018-11-11 00:22:28.000000000 +0900
+++ main.go 2018-11-11 00:22:28.000000000 +0900
@@ -1,12 +1,12 @@
 package main

 import (
+   "database/sql"
    "fmt"
+   _ "github.com/lib/pq"
+   "github.com/yosssi/ace"
    "log"
    "net/http"
-   "github.com/yosssi/ace"
-   "database/sql"
-   _ "github.com/lib/pq"
 )

 func login_handler(w http.ResponseWriter, r *http.Request) {
@@ -33,7 +33,7 @@
        fmt.Println("username:", r.FormValue("username"))
        fmt.Println("password:", r.FormValue("password"))

-       user := User{ r.FormValue("username"), r.FormValue("password")}
+       user := User{r.FormValue("username"), r.FormValue("password")}
        db, err := sql.Open("postgres", "host=db user=dairy_report password=dairy_report dbname=dairy_report_development sslmode=disable")
        if err != nil {
            fmt.Println(err)

-dオプションで差分を取ることができます。この場合ですと、まだ標準出力に表示されただけなので、さらに-wオプションで直接ソースを書き換えるようにします。

%  goenv exec gofmt -w main.go
% git diff
diff --git a/src/dairy_report/main.go b/src/dairy_report/main.go
index 75bfcc5..35375ca 100644
--- a/src/dairy_report/main.go
+++ b/src/dairy_report/main.go
@@ -1,12 +1,12 @@
 package main

 import (
+       "database/sql"
        "fmt"
+       _ "github.com/lib/pq"
+       "github.com/yosssi/ace"
        "log"
        "net/http"
-       "github.com/yosssi/ace"
-       "database/sql"
-       _ "github.com/lib/pq"
 )

 func login_handler(w http.ResponseWriter, r *http.Request) {
@@ -33,7 +33,7 @@ func login_handler(w http.ResponseWriter, r *http.Request) {
                fmt.Println("username:", r.FormValue("username"))
                fmt.Println("password:", r.FormValue("password"))

-               user := User{ r.FormValue("username"), r.FormValue("password")}
+               user := User{r.FormValue("username"), r.FormValue("password")}
                db, err := sql.Open("postgres", "host=db user=dairy_report password=dairy_report dbname=dairy_report_development sslmode=disable")
                if err != nil {
                        fmt.Println(err)

なお、ここのフォーマットルールは以下のルールに基づいています。
http://go.shibu.jp/effective_go.html

フォーマットルール以外のルールにおけるコメント追加

機械的に修正できない部分によるGo言語のコードルールは以下のようなものがあります。
https://gist.github.com/knsh14/0507b98c6b62959011ba9e4c310cd15d

こちらの細かなルールはコメントとして残して気づかせて欲しい派(?)なため、reviewdogと言うツールをインストールしてコードレビューしてもらいます。
reviewdogについてはこちら→https://github.com/haya14busa/reviewdog

そろそろTravis CIを入れても良い頃だったので導入することにしました。

.travis.yml
env:
  global:
    - REVIEWDOG_VERSION="0.9.11"
sudo: false
language: go
go:
  - 1.11.2
install:
  - go get -u golang.org/x/lint/golint
  - mkdir -p ~/bin/ && export export PATH="~/bin/:$PATH"
  - curl -fSL https://github.com/haya14busa/reviewdog/releases/download/$REVIEWDOG_VERSION/reviewdog_linux_amd64 -o ~/bin/reviewdog && chmod +x ~/bin/reviewdog

script:
  - golint ./... | reviewdog -f=golint -reporter=github-pr-review

Github Tokenについては以下の設定を行います。

reviewdogのサンプル例では.travis.ymlに直接トークンを入力していますが、ここでは.travis.ymlの方に保存しておきます。

以上により一旦reviewdogの導入は成功しました。

なお、ちょうどこの作業を行うにあたって、TypeName.IsAliasのundefined methodエラーにあたってしまったので、Goのバージョンを1.11.0 -> 1.11.2に更新しました。
詳しくはこちら→https://github.com/golang/go/issues/28291

まとめ

とまぁ、色々書きましたがプロジェクトの進行はあんまり進んでいません汗
とは言え、ここ最近色々なGo言語の勉強会に参加させていただいて何となくですがGo言語による開発をしやすくなるための知見を学ぶ事ができました。
そろそろ次の機能を追加しようと考えてた矢先だったため、これからどんどんコードを書いていこうと思います。
それでは。

参考文献

https://dev.classmethod.jp/go/dep/
https://github.com/golang/dep/issues/796
Effective Go
https://gist.github.com/knsh14/0507b98c6b62959011ba9e4c310cd15d
https://github.com/haya14busa/reviewdog

シリーズ

Part2 ← Part2.5