What's this?
普段携わっている、 「圧倒的にいちばん速く覚えられる 英単語アプリmikan」 を作っているスタートアップ
でGo言語の社内勉強会を開きましたので資料を共有します。
iOS版 App Storeページ
Android版 Google Playページ
What's Go?
-
2009年11月にGoogleが公開
-
特徴
- オープンソース
- シンプル
- コンパイルが早い
- 並列処理をサポート
- ガベージコレクションによるメモリ管理
- 安全性が高い
- 継承がない
- 容易なエラーハンドリング
- C言語Like
-
HP
https://golang.org/
(見たほうが良い!)
Goを利用したOSS
- Docker: https://github.com/docker/docker
- Kubernetes: https://github.com/kubernetes/kubernetes
- Hashicorp
- Terafform: https://github.com/hashicorp/terraform
- Packer: https://github.com/mitchellh/packer
コードとか読んでみると勉強になるかも
Install Go
$ brew install go
.zshrc
もしくは.bashrc
等に以下を追加してsource ~/.zshrc
コマンドで設定を適用。
# Go
export GOPATH=${HOME}/.golang
export PATH=${PATH}:${GOROOT}/bin:${GOPATH}/bin
インストールできたか確認
$ go version
go version go1.8 darwin/amd64
Hello, World!
基本的なテンプレートは以下のようになります。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
実行
$ go run hello.go
または
$ go build hello.go
$ ./hello
入門
- 型
- 変数・定数
- 演算子
- for
- if, switch
- 関数
- 定義方法
- 可変長パラメータ
- 名前付き戻り値
- 関数リテラル
- 遅延実行
- 構造体、インタフェース
- 配列
- スライス
- マップ
- エラーハンドリング
- エラー処理
- パニック、リカバリ
- Goルーチン
- チャネル: 作成、クローズ
型
- 論理値型: bool
- 数値型: int,float64
- 文字列型: string
(+α) 型
- 論理値型
- bool
- 数値型
-
uint8、uint16、uint32、uint64
-
int8、int16、int32、int64
-
float32、float64
-
complex64、complex128
-
byte
-
rune
-
uint
-
int
-
uintptr
-
- 文字列型
- string
変数
var a string
a = "hoge"
var b string = "hoge"
c := "hoge"
var (
s string
i1, i2, i3 int
)
定数
const a float32 = 0.123
const π flaot64 = 3.14159
const (
win string = "ウィンドウズ"
mac string = "Mac"
)
(+α)定数の一括代入
const (
A int = 1
B
C
)
const (
ZERO = iota
ONE
TWO
THREE
FOUR
)
演算子
- 算術演算子
- +, -, *, /, %
- &, |, ^, &^
- <<, >>
- 比較演算子
- ==, !=, <, <=, >, >=
- 論理演算子
- &&, ||, !
- 受信演算子
- <-ch
for, if, switch
x := 100
for x > 0 {
x--
fmt.Println("Count: ", x)
}
x := 1
if x > 0 {
fmt.Println(x)
}
i := 3
switch i {
case 1: fmt.Println("hoge")
case 2:fmt.Println("fuga")
case 3: fmt.Println("piyo")
default: fmt.Println("default")
}
(発展) for
// Pattern1
for value := 0;; {
value++
fmt.Println(value)
if value % 10 == 0 {
break
}
}
// Pattern2
sum := 0
for i := 0; i < 10; i++ {
sum += i
fmt.Println(sum)
}
// Pattern3
items := map[int]int{0:10 , 1:20}
for key, value := range items {
fmt.Println("key:", key, " value:", value)
}
// Pattern4
dictionary := map[string]int{
"a": 123,
"b": 456,
"c": 789,
}
for key, value := range dictionary {
fmt.Println("key:", key, " value:", value)
}
関数
- 宣言方法
- 可変長パラメータ
- 名前付き戻り値
- 関数リテラル
関数 宣言方法
package main
import (
"fmt"
)
func add(x int, y int) int {
return x + y
}
func addAndSub(x, y int) (int, int) {
return x + y, x - y
}
func main() {
x := add(100,200)
fmt.Println(x)
y,z := addAndSub(100,200)
fmt.Println(x,y)
}
関数 可変長パラメータ
package main
import "fmt"
func sample(args ...int) {
fmt.Println(args)
}
func main() {
sample(1, 2, 3, 4)
}
1 [2 3 4]
関数 名前付き戻り値
func f()(dog string, cat string, bird string){
dog = "wan"
cat = "nya-"
bird = "ka-"
return
}
d, c, b := f()
関数リテラル
関数を変数に代入して記述すること
swap := func(a, b int) (int, int) {
return b , a
}
fmt.Println(swap(3, 5))
遅延実行
宣言する関数の終了直前に実行する。よくDBのCloseなどに使用する。
package main
import "fmt"
func main() {
defer fmt.Println("これはmain()関数抜け出す直前に実行")
fmt.Println("この辺は順番に実行")
}
構造体
package main
import "fmt"
type Person struct {
First string
Last string
}
func (p *Person) Name() string {
return p.First + " " + p.Last
}
func main() {
person := &Person{"Taro", "Yamada"}
fmt.Println(person.Name())
}
インタフェース
package main
import "fmt"
type Animal interface {
Cry()
}
type Dog struct {}
func (d *Dog) Cry() {
fmt.Println("わん")
}
type Cat struct {}
func (c *Cat) Cry() {
fmt.Println("にゃー");
}
func do(a Animal) {
a.Cry();
}
func main() {
dog := new(Dog)
cat := new(Cat)
do(dog)
do(cat)
}
配列
// Pattern1
array1 := [5]float64{}
// Pattern2
array2 := [10]int{3,2,4}
// Pattern3
array3 := [...](string){"A", "B", "C"}
// Pattern4
var date[7] string
data[0] = "Sunday"
data[1] = "Monday"
︙
スライス
- 配列と同様にインデックスでアクセスできる
- 配列を間接参照
- スライス自体は要素を持たない
// Pattern1
slice := []int{3,2,4}
// Pattern2
x := [5]string{"a", "b", "c", "d", "e"} // 配列を宣言
var s1 = []string // スライス型の変数を宣言
s1 = x[:] // 配列全体をスライス
fmt.Println(s1)
fmt.Println(s1[1:3])
fmt.Println(s1[2:])
中間演習
構造体のスライスからループで要素を取り出して各要素のメソッドを呼び出して、構造体の名前と名字をスペース区切りで1つの文字列として出力するコードを書こう。
実装方針
- 名前と名字を要素に持つ構造体を定義
- main関数の最初に構造体の各要素に具体的な値を代入していく。
- main関数の最後にループで構造体の各要素を取り出して、構造体のメソッドから名前を出力する関数を呼び出す。
- 名前を出力する関数
- 構造体を引数として、名前と名字の2つの値の返り値を返す関数
- 上で求めた値を使ってスペース区切りで名前と名字をつなげる関数。これが実際に出力される行の出力値
(実行結果: 例)
$ go run name.go
Tarou Tanaka
Jirou Satou
Saburou Suzuki
解答例
終わったら公開。
マップ
他の言語ではハッシュやディクショリナリーと呼ばれるものに近い。
height := make(map[string] int)
height["tanaka"] = 163
height["suzuki"] = 175
height["yamada"] = 183
weight := map[string] int {
"tanaka": 163,
"suzuki": 175,
"yamada": 183,
}
fmt.Println(weight)
fmt.Println(weight["suzuki"])
エラーハンドリング
f, err := os.Open("sample.txt")
if err != nil {
fmt.Println("Error", err.Error())
}
基本的には、例外処理を書かずに、関数に第2引数にエラーがある場合はその要素が値として入るようにする。
パニック
致命的なエラーのためエラーハンドリングをさせる必要がない。もしくはエラーが起きてもリカバリが必要な時。
f, err := os.Open("/tmp/sample.txt")
if err != nil {
panic("Error")
}
リカバリ
package main
import "fmt"
func func1(b bool) {
defer func() {
fmt.Println("defer start.")
if err := recover(); err != nil {
// パニック中だった
fmt.Println("recovery!")
}
fmt.Println("defer end.")
}()
if b {
panic("Occure panic!")
}
}
func main() {
func1(false)
func1(true)
}
出力結果
defer start.
defer end.
defer start.
recovery!
defer end.
中間演習2
以下の条件を満たす関数を作成してmain関数から呼び出そう
-
sample.txt
というファイルを新規作成 - 作成されたテキストファイルに与えられた引数の文字列を書き込む
呼び出し例
func main() {
writeTxt("hoge")
}
Goルーチン
- チャネル: 作成、クローズ
- Goは並列処理がめちゃくちゃ簡単に書ける!
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Begin.")
// チャネル 作成
channel1 := make(chan bool)
channel2 := make(chan bool)
channel3 := make(chan bool)
go func() {
// 1秒かかるコマンド
fmt.Println("Second1: Start")
time.Sleep(1 * time.Second)
fmt.Println("Second1: End")
channel1 <- true
}()
go func() {
// 2秒かかるコマンド
fmt.Println("Second2: Start")
time.Sleep(2 * time.Second)
fmt.Println("Second2: End")
channel2 <- true
}()
go func() {
// 3秒かかるコマンド
fmt.Println("Second3: Start")
time.Sleep(3 * time.Second)
fmt.Println("Second3: End")
channel3 <- true
}()
// 終わるまで待つ クローズ
<- channel1
<- channel2
<- channel3
fmt.Println("Finished.")
}
最終課題
以下の条件を満たす処理を書こう。
- 書く処理は並列で処理を行う。
-
sample0.txt
,sample1.txt
,sample2.txt
というファイルを作成する。 - 上記のファイルのそれぞれに
Tanaka
,Yamada
,Suzuki
という文字列を書き込む。
解答例
終わってから公開。
快適な環境つくり
- goファイルはデフォルトでLintが適用されるようにしてしまおう!(Vim ver.)
- vim上で各種操作をする(NeoBundle ver.)
goファイルはデフォルトでLintが適用されるようにしてしまおう!(Vim ver.)
.vim/after/ftplugin以下に[拡張子].vim
というファイルを作り、vimのconfigを書く。
$ mkdir -p ~/.vim/after/ftplugin/
$ vim ~/.vim/after/ftplugin/go.vim
set noexpandtab
set tabstop=4
set shiftwidth=4
vim上で各種操作をする(NeoBundle ver.)
インストール
.vimrc
に以下のようにvim-go-extra
を追記する。
$ vim ~/.vimrc
NeoBundle 'vim-jp/vim-go-extra'
入力したらEscをして以下を入力してインストールする。
:NeoBundleInstall
コマンド
- :Fmt
- :Import
- :Godoc
こんな記事も
vimのGoサポートが手厚くて打ち震えている
http://qiita.com/shiena/items/870ac0f1db8e9a8672a7
(+α) Go言語標準 コマンド 1/2
- build
- goファイルをビルド
- clean
- 生成ファイルを除去
- doc
- ドキュメントを表示
- env
- Goで使用されている環境変数を表示。
- bug
- バグ報告画面に移動
- fix
- パッケージにgo tool fixを実行します
- fmt
- Lintを適用
- generate
- ソースを処理してGoのファイルを生成します
(+α) Go言語標準 コマンド 2/2
- get
- ライブラリのダウンロード
- install
- パッケージのコンパイルとインストール(依存関係含む)を行います
- list
- パッケージのリストを表示します
- run
- goファイルを実行(バイナリは生成しない)
- test
- テストコードの実行
- tool
- 指定したgo toolを実行します
- version
- Go言語のバージョンを表示
- vet
- コンパイルでは見つけられないヒューリスティックなバグも見つけられることがある。ただし、結果は保証されない。
Go言語 便利コマンド
- gofmt
- フォーマットし直してくれる
- golint
- Lintツール
- goimports
- ソースコード中で使用しているけどインポートしていないライブラリをインポートしてくれる
- godoc
- ドキュメントを表示してくれる
- gorename
- 変数名や関数名をリネーム
- guru
- ソースコードを静的解析
- gocode
- コード補完用のエンジンを提供
- godef
- 定義ジャンプするためのツール
- gotags
- ctags互換のタグを生成
gofmt
フォーマットを適用。
gofmt -w main.go
wオプションを付けないと実行結果を表示するだけでファイルは変化しない。付けると上書きされる。
golint
Lintツール
下記のコマンドでインストール
$ go get -u github.com/golang/lint/golint
実行
$ golint sample.go
goimports
ソースファイル中で使用しているけど、importされないパッケージをimportする。
下記のコマンドでインストール
$ go get golang.org/x/tools/cmd/goimports
実行
goimports -w main.go
暗黙的に宣言が必要なライブラリのインポートはできないっぽい。
godef
インストール
$ go get -v github.com/rogpeppe/godef
$ go install -v github.com/rogpeppe/godef
~/.vim/plugin/godef.vim
に以下のURL中のソースコードを追加
https://github.com/dgryski/vim-godef/blob/master/plugin/godef.vim
$ mkdir ~/.vim/plugin
$ vim ~/.vim/plugin/godef.vim
.vimrcに以下を追加。デフォルトだと画面が分割されて使いにくい。
let g:godef_split = 0
実行
vimを開いた状態で下記コマンドを実行する。
:Godef <関数名>
godoc
ドキュメントを表示。下記のコマンドでオフラインでもドキュメントが読める。
godoc -http=:8080
Goを活かしたコードを書こう!
- panic()を使わない。
- errorを返してエラーチェックを行う。
- 正規表現を避ける。
- stringsパッケージを使う。
- mapを避ける。
- structでtypeを定義する。
- reflectを避ける。
- 巨大なstructを作らない。継承させようとしない。
- 「継承より移譲」← オブジェクト指向に対する警句
- 再利用可能な小さな部品を組み合わせてデータ構造を定義する。
- オブジェクト指向な設計でやるなら埋め込み等の機能を使う。
- 並行処理を使いすぎない
- コードの可読性の低下。レースコンディション。
- テストとCI
-
go vet
、golint
-
- ビルドとデプロイ
go build
- モニタリング
-
runtime
パッケージ - 各種メトリクスを取得して定常的に計測しておくと良い。
-
みんなのGo言語【現場で使える実践テクニック】 大型本 – 2016/9/9
その他
- パッケージ管理
- Webフレームワーク
- URL集
パッケージ管理
- dep
- Glide
- gom
(+α)パッケージ管理
-
dep
-
Glide
-
gom
-
その他
- manul
- Godep
- Govendor
- godm
- vexp
- gv
- gvt
- govend
- Vendetta
- trash
- gsv
(参照)https://github.com/golang/go/wiki/PackageManagementTools
Webフレームワーク
- Gin
- Martini
- Revel
(+α)Webフレームワーク
- beego
- bone
- goji
- echo
- その他
- macaron
- go-json-rest
- Bxog
- pat
- fasthttprouter
- lion
- golf
- chi
- httptreemux
- baa
- go-ozzo
- go-restful
- lars
- gorilla
- Gramework
- gem
- neo
- httprouter
- tango
- vulcan
- possum
- gongular
- denco
- traffic
- ace
- fasthttp-routing
- go-tigertonic
- fasthttp
- r2router
- gear
- possum
(参照) https://github.com/smallnest/go-web-framework-benchmark
URL集
-
フレームワークのベンチマーク。サンプルコードとしても良いかも
-
入門としてわかりやすい
-
オブジェクト指向Like風設計
以上
参考
基礎からわかる Go言語 単行本(ソフトカバー) – 2012/11/21
古川 昇 (著)
みんなのGo言語【現場で使える実践テクニック】 大型本 – 2016/9/9