Macを買ったばかりの非エンジニアでもgoの開発環境を用意できるようにする


はじめに

基本的には備忘録です。
説明も書きます。
今手元にChromeとXcodeを入れただけのMacがあるのでやってみます。

環境

MacOS Mojave
これより新しいやつあるらしいんですけど、Xcodeの仕組み変わってて闇が深いので
普段Linux使っていて、Xcode詳しくない僕がやると詰みそうなので、Mojaveでやります。

brew

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

なんか多分Xcode入れろとか言われると思うので、言われた通りにAppleStoreから入れてください。
それだけはやってしまったので、詳しいことは覚えていません。

ryotanoMacBook:~ ryota$ brew -v
Homebrew 2.1.1
Homebrew/homebrew-core N/A

はい。なんか入りました。

うまくいかないときはこことかみてください。
https://brew.sh/index_ja

エディターのためにローカルの環境を作る

$ brew install anyenv
$ echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
$ echo $SHELL -l
$ anyenv install goenv

$ goenv install -l //インストールできるGoのバージョンが表示されます
$ goenv install 1.12.6 //バージョンは適宜修正してください 
$ goenv versions // マシンにインストールされているバージョンの一覧を取得できます
$ goenv local 1.12.6 //そのディレクトリ配下でのgoのバージョンを指定できます。

こんな感じでローカルのgoはインストールできます。

Dockerを使ってgoの実行環境を作る

Dockerは
Mac版
https://docs.docker.com/docker-for-mac/install/
Windows版
https://docs.docker.com/docker-for-windows/install/

でインストールしてください。

Dockerfile

Dockerfile

FROM golang:1.12.6-alpine as builder

RUN apk --update add git openssh gcc musl-dev && \
    rm -rf /var/lib/apt/lists/* && \
    rm /var/cache/apk/* && \
    go get github.com/rubenv/sql-migrate/...

ENV GO111MODULE=on

COPY ./ $GOPATH/src/github.com/team_name/project_name
RUN cd $GOPATH/src/github.com/team_name/project_name && go build

EXPOSE 8080
CMD ["./main"]

簡単に説明しておきます。
まず、Goが入ったAlpineを引っ張ってきます。
apkはAlpineのパッケージ管理ツールです。

ENV GO111MODULE=on
はgoの公式のパッケージ管理ツールです。
depが普通だったんですけど、go1.11以降はこれが主流になりました。

COPY ./ $GOPATH/src/github.com/team_name/project_name
ではローカルにあるファイルをDocker内に全部突っ込んでいます。
入れたくないものは.dockerignoreの方で調整してください。

ここでは、先にビルドしています。
せっかくビルドしたならイメージサイズのために、
イメージ内にバイナリだけ残したいなら、Alpine引っ張ってきて、
そこに選択してコピーしてあげてください。
マルチステージビルドって呼ばれてるのでググったら出てくると思います。

docker-compose

https://qiita.com/riita10069/items/5c7eac45486c8fff91fb
docker-composeを使ってMySQLを同時に立ち上げる方法についても記述しているので参考にしてください。

go.mod

パッケージの依存関係を記述していくファイル

$ export GO111MODULE=on
$ cd $GOPATH/src/github.com/team_name/project_name
$ go mod init

まぁこんな感じで作れます。

$ go mod tidy

で使ってないものパッケージを消して、入ってないけれど使っているものを入れてくれます。

  • ソースコードは$GOPATH/pkg/modに入っている。gitで管理されているけれど、基本バージョン固定
  • vendorディレクトリは必要なくなった。
  • ソースコードに依存を書き込めば、go buildでgo.modが走ってモジュールを取り込んでくれる。パッケージ管理とは。。。管理??はて?といった感じのツール
  • export GO111MODULE=onをしないとそれできないので注意。
  • Dockerfileに書き忘れないように注意

direnv

$ brew install direnv
$ eval "$(direnv hook bash)"
$ source ~/.bashrc

.envrcに記載した設定が.bashrcに入っているかのように挙動するだけです。

touch .envrc
emacs .envrc
direnv allow .

って感じです。
allowは忘れがちなので注意してください。

export GO111MODULE=on

とかそんな感じで書いてください。

goenv

goの環境は他の言語と違ってディレクトリ構成を理解していないと環境を壊しかねません。
goに限った話ではないですが、必ず、brew, anyenv, goenv, direnvを使って丁寧に管理してください。
気にかけるべきgoの環境変数は二つです。

GOROOT

GOROOTは、Goがインストールされているディレクトリを指定します。
GOROOT="/Users/riita/.goenv/versions/1.11.0"
という感じにしてください。
goenv local 1.12.0
みたいに変更してあげれば自然に変更されます。
この辺の理解が曖昧だと、goenvと.bashrcとdirenvのどの勢力によって今の環境変数になっているのかわからなくなり、色々カオスになったりするので、気をつけてください。
.bashrcもしくは.envrcにGOROOTを記載してもgoenv側の力ですぐに戻ってしまうんで、
必ず、goenv localで変更をするようにしてください。

GOPATH

GOPATHは、外部のパッケージなどのソースを取りまとめておくディレクトリです。
デフォルトでは、 $HOME/go となっています。
問題は開発ディレクトリです。
一般的にはGOPATHの内部に階層構造で作りますが、外に作っても問題ありません。
その時は、direnvを使って、その開発ディレクトリもGOPATHに加えるのを忘れないようにしてください。

Goland

JetBrainのIDEを使っている人が多いと思います。
これ使えばデバッグとかもできますので、使いこなせるとまじで強いです。
左上のGolandのPreferencesから、
Goの下にある
GOPATH, GOROOT, gomodule(vgo) integration
みたいなのは設定してあげてください。

その辺の設定はgo envでみれます。
また、go.modのファイルを右クリックしてSync Go module (vgo)しないとうまくいかない場合もあるらしいです。

そうすると、一旦モジュール群たちにパスが通ります。
デバッグにはデータベースの登録が必要になるんですよ。
今日は割愛しますが、データベースを設定してあげれば
デバッグモードでmain関数を起動するだけです。

デバッグモードにする時には、
go get -u github.com/derekparker/delve/cmd/dlv
が必要になります。

詳しくは
https://pleiades.io/help/go/debugging-code.html

この辺を参考にしてやってください。

How is the golang??

goという言語は、スクリプト言語のように書きやすいけれど、
コンパイル言語で、PythonやRubyよりも高速に動作します。

静的型付け言語でありながら、型推論を行い、型安全性と書きやすさを兼ね備えます。
C言語のようにポインターを作成できてメモリ管理もしやすくなっています。
Javaのようなわけのわからない例外処理の必要ありません。
errはオブジェクトとして返ってきます。非常に便利ですね。

オブジェクト指向を意識して作られていて、インターフェイスも簡単に作成することができますが、継承が実装されていないため、
拡張性の非常に低い親クラスを継承するということをするエンジニアが現れない点も非常に設計しやすいです。

また、goroutine/channelを用いた並列処理も非常に簡単に行うことができ、マイクロサービスに非常に適しています。

ぼくの一番好きな言語の一つです。
ただ、ポインターがcppのOptionalのような挙動をしているのが個人的には好きではないんですよね。
nillにも型情報が付与されていて、null安全でなくなってしまっているので、なんでそのような仕様にしたんだろうと思います。
工夫すればよりNULL安全に寄せることはできますが、NULL安全であるに越したことはないと思っているので。
それ以外のgolangの設計は非常によく考えられていると思っています。

皆さんもぜひgo言語を使ってサービスを開発してみてください。

-----追記

よくあるgoへの批判

map sliceが不便

それはそう。
もう少しライブラリ充実させて欲しいですが、
ぼくははutilにその辺の型変換や最大値とかのメソッドをおいています。
黒魔術がない分逆にいいのかなと。

ジェネリクスないとかありえない

多相的処理に関してはジェネリクス以外にもやり方はあります。
まぁあるに越したことはないのですが、ないならないなりに書きようはあります。

例外処理ないの不便じゃないの?

errが返ってくる方がわかりやすくないですか?
そもそもtry catchの言語でさえ、例外以外でエラー返してたりしてません?
結局だったら、errオブジェクトの方がerrorが集約されていいと思うんですよね。
あと、

if err != nil{
    New.error(err)
}

の3行が長いっていう人いるんですけど、
goland隠してくれるんで気にならないです笑

もし隠してくれなかったら、エラーのバケツリレーうざってなりますよ。

ダックタイプの時に型安全じゃないから静的型付けの意味ない

多分x(.T)のやつのことだと思うんですけど、
気になるなら静的解決できるように明示的に型変換すればいいんじゃないですか?

ヌル安全じゃない

わかる。
こういうところcppっぽいですよね。
cppのクソ仕様は実行速度が異常に早いから許されていると思うのです。
まじでこれはうざいです。

イテレータがない

chan+goroutineでイテレータのようにかけますよ。
というか公式もそれを推していたはずです。

イテレータを中断した時にメモリリークするので、ちょっと不便ですけどね。

golang イテレータ
って検索すると、
コールバック関数を使うような方法もかなり紹介されているので、そちらも参考になさってはどうでしょうか。

シンプルな言語の割にNimやKotlinよりも遅いんだけど

複数のスレッドを立てて、並行で処理する場合にもそれって当てはまっていますか?
シングルコア性能が劣っているのはわかりますし、なんで劣っているのかはよくわからないんですけど、複数スレッドではgoの書きやすさと速度は圧倒的だと思います。

ぼくの認識だと、go以上の速度を出せるのはElixirHaskellだけです。
この2つの言語は確かに素晴らしい言語ですが、めちゃくちゃ難しいのは間違いなく事実です。
ElixirHaskellのかけるエンジニアを探すのは大変でしょうが、
golangならCに非常に似ているので、情報工学科出身の人であれば誰もが馴染みのある言語です。
RubyやPythonほどではありませんが、学習コストの低い言語と言えるのではないでしょうか。