$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

「みんなのGo言語」を読んだ

Go言語の学習のため、A Tour of Goに引き続き、「みんなのGo言語」を読んだ。


「みんなのGo言語」はGo言語を実践的に使うためのTipsがいろいろまとめられていて非常に良かった。Go言語のA Tour of Goをやったけど、次にどうすればよいかわからない、具体的にGoの良い書き方が分からないという人が読むと非常に勉強になりそう。

僕はまだGoをそこまで書いていないので、「第1章Goによるチーム開発のはじめ方とコードを書く上での心得」と「第4章コマンドラインツールを作る」が非常に参考になった。例えば以下のようなものが参考になった(数字はKindleのロケーション番号)。

  • Goプロジェクトのプロジェクト構成やディレクトリ構成 519, 2389
    • バイナリをメインの成果物とする場合と、ライブラリをメインの成果物にする場合の二種類
    • 第1章、第4章両方で言及されている
  • 依存管理をするためのglideというツールの使い方 563
  • Makefileの具体例 609
  • Goらしいコード 656
    • panicを使わず、errorをちゃんと返す
    • 正規表現を避けてstringsを使う
    • mapではなくちゃんとstructを定義する
  • Build Constraintsやビルドタグを使って、ビルド対象のソースコードを切り替える手法 776
  • CLIを作るためのライブラリの使い方
    • flagパッケージ 2432
    • mitchellh/cliの使い方 2771
  • main関数に書くような主な処理はRun関数を定義してそこに記述し、Run関数はExitを呼ぶのではなく、そのステータスコードを返す 2926
  • CLIステータスコードのテスト手法 3016
  • CLIの出力メッセージのテスト手法 3034


この本を読んで、なんとなくGo言語に対する素地が出来たように思うので、次は 「みんなのGo言語」が良かったので、自分のためだけのCLIツールを作ってみた - えいのうにっき を見習って、簡単なCLIツールを作って学習を進めていきたい。

読書ノート

1章 Goによるチーム開発のはじめ方とコードを書く上での心得

  • goimportsを高速化するためのdragon-imports 407
  • Goはディレクトリ単位でパッケージが切られるため、mainパッケージを除き、ディレクトリ名はソースコード内に記述されるパッケージ名と同一であることが強く推奨されている 512 ※
  • Goプロジェクトのディレクトリ構成の代表的な一例 519
    • トップレベルはGoのパッケージとして利用される
    • cmd/以下にバイナリビルド用のmainパッケージが配置される
    • サブパッケージが必要な場合はディレクトリを掘る
    • Makefileをビルドのほかタスクランナー的に使う
    • 他の構成が2371にもある
  • ファイル分割は任意で良いが、typeを定義する場合、typeごとに1つのファイルに切り出すという良いプラクティスがある 527
  • 依存管理ができるglide 563 ※
    • govendorというツールもある
  • Makefileの参考になる例 609
  • Goらしいコード 656
    • panicを使わず、errorをちゃんと返す
    • 正規表現を避けてstringsを使う
    • mapではなくちゃんとstructを定義する
  • goのbuildの方法 776
  • Build Constraintsやビルドタグを使って、ビルド対象のソースコードを切り替える手法 776

基本として押さえておきたい

2章 マルチプラットフォームで動作する社内ツールのつくり方

  • runtime.GOOSに実行されるOS名が格納されている 1130
  • Build Constraintsの指定方法いろいろ 1142
    • ファイル名による指定
    • +buildコメントによる指定

3章実用的なアプリケーションを作るために

  • バージョン番号をバイナリに埋め込む 1534 ※
    • go build -ldflags
  • goでのIOのバッファリング 1602
  • 複数の出力先に一気に書き込む 1798
  • Goからos/execパッケージを利用して外部コマンドを実行する 1961
  • タイムアウトの実装 2048
  • シグナルを扱う 2109
  • goroutineを外部から停止する 2200

ツールを作る時にこれどうやるんだろうと迷った時に参考になりそう

4章コマンドラインツールを作る

かなり参考になった。とりあえずGoを学ぼうとすると簡単なCLIを作ってクロスビルドしてみるのが手っ取り早い気がするので、この章は参考になる。

  • リポジトリ構成 2389 ※
    • バイナリをメインの成果物とする場合
    • ライブラリをメインの成果物とする場合
  • flagパッケージ 2432
  • mitchellh/cliの使い方 2771
  • main関数に書くような主な処理はRun関数を定義してそこに記述し、Run関数はExitを呼ぶのではなく、そのステータスコードを返す 2926 ※
    • mainではRunを起動してos.Exitするだけ
    • os.Exitが呼ばれて後処理が行われないなどの問題が避けられる
    • テストもしやすい
  • ステータスコードのテスト 3016
  • 出力メッセージのテスト 3034

5章 The Dark Arts Of Reflection

動的に型情報を使うためのTips。必要になったらちゃんと読む。

6章 Goのテストに関するツールセット

  • testingパッケージ入門 3746
  • ベンチマーク入門 3853
  • reflect.DeepEqual 3973
  • テストにおける変数または手続きの置き換え 4105
    • モックの手法

A Tour of Goをやって演習問題を解いてみた

Goをやることになったので、とりあえずA Tour of Goをやって、演習問題を解いてみた。直近ではgoroutineは使わなそうだったので、ひとまずそれ以外の演習問題をした。

回答は https://github.com/shibayu36/golang-playground/tree/master/go-tour に置いている。

Exercise: Loops and Functions

for文の使い方を学べる。for文の初期化ステートメントと条件式を使えば解ける。

package main

import (
	"fmt"
	"math"
)

func Sqrt(x float64) float64 {
	z := 1.0
	for diff := 1.0; math.Abs(diff) > 1e-10; {
		diff = ((z * z) - x) / (2 * z)
		z = z - diff
	}
	return z
}

func main() {
	fmt.Println(Sqrt(3))
	fmt.Println(math.Sqrt(3))
}

Exercise: Slices

  • rangeをうまく使うと綺麗にかけた
  • 多次元配列だと、ループでmakeしなければならない
  • uint8()でキャストしないといけない
package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	pic := make([][]uint8, dy)

	for y := range pic {
		pic[y] = make([]uint8, dx)
		for x := range pic[y] {
			pic[y][x] = uint8((x + y) / 2)
		}
	}

	return pic
}

func main() {
	pic.Show(Pic)
}

Exercise: Maps

  • string.Fieldsを使ってワード分割
  • mapの値を増やしていくだけで良い
package main

import (
	"strings"
	"golang.org/x/tour/wc"
)

func WordCount(s string) map[string]int {
	words := strings.Fields(s)
	wordToCount := make(map[string]int)
	for _, word := range words {
		wordToCount[word]++
	}
	return wordToCount
}

func main() {
	wc.Test(WordCount)
}

Exercise: Fibonacci closure

クロージャで変数を二つ持っておくと綺麗に書ける。

package main

import "fmt"

func fibonacci() func() int {
	a1, a2 := 0, 1
	return func() int {
		ret := a1
		a1, a2 = a2, a1 + a2
		return ret
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

Exercise: Stringers

typeにメソッドを生やす練習

package main

import "fmt"

type IPAddr [4]byte

func (ipAddr IPAddr) String() string {
	return fmt.Sprintf("%d.%d.%d.%d", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3])
}

func main() {
	hosts := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip)
	}
}

Exercise: Errors

Errorメソッドを生やすだけで簡単に独自エラーを定義できる。

package main

import (
	"fmt"
	"math"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", e)
}

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}

	z := 1.0
	for diff := 1.0; math.Abs(diff) > 1e-10; {
		diff = ((z * z) - x) / (2 * z)
		z = z - diff
	}
	return z, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

Exercise: Readers

インターフェース定義に合わせて実装する練習。

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

func (r MyReader) Read(buf []byte) (int, error) {
	for i := range buf {
		buf[i] = 'A'
	}
	return len(buf), nil
}

func main() {
	reader.Validate(MyReader{})
}

Exercise: rot13Reader

非公開メソッドで変換を書きつつ、Readを実装。

package main

import (
	"io"
	"os"
	"strings"
)

type rot13Reader struct {
	r io.Reader
}

func (reader *rot13Reader) Read(buf []byte) (int, error) {
	n, err := reader.r.Read(buf)
	if err != nil {
		return 0, err
	}

	for i := 0; i < n; i++ {
		buf[i] = reader.rot13(buf[i])
	}
	return n, nil
}

func (reader *rot13Reader) rot13(b byte) (byte) {
	if ('a' <= b && b <= 'm') || ('A' <= b && b <= 'M') {
		return b + 13
	}
	if ('n' <= b && b <= 'z') || ('N' <= b && b <= 'Z') {
		return b - 13
	}
	return b
}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}

Exercise: Images

自分でImage typeを定義しつつ、インターフェース仕様を満たすように実装。

package main

import (
	"image"
	"image/color"

	"golang.org/x/tour/pic"
)

type Image struct {
	width  int
	height int
}

func (i Image) ColorModel() color.Model {
	return color.RGBAModel
}

func (i Image) Bounds() image.Rectangle {
	return image.Rect(0, 0, i.width, i.height)
}

func (i Image) At(x, y int) color.Color {
	v := uint8((x + y) / 2)
	return color.RGBA{v, v, 255, 255}
}

func main() {
	m := Image{256, 256}
	pic.ShowImage(m)
}

まとめ

今回はA Tour of Goをして演習問題を解いてみたので、その回答を載せてみた。続いてみんなのGo言語を読んでいきたいと思う。

golangの編集環境を再整備する

最近golangをやり始めたので、編集環境を再整備した。

基本設定

昔自分で書いた以下の二つを再度やったら、これだけでかなり快適な環境になった...昔の自分のブログ便利...残しておくものだな...

blog.shibayu36.org
blog.shibayu36.org

ハードタブの表示の設定

あと、Emacsでwhitespace-modeを利用してハードタブなどを可視化するようにしているのだけど、これがgoだとうざく見えてしまうので、上の設定に引き続き、その部分だけ調整した。


以下がwhitespace-modeの設定。whitespace-modeの設定 - syohex’s diaryEmacs の whitespace-mode でタブと行末スペースと全角スペースに色付けするメモ - 牌語備忘録 -pygoあたりを参考にした。

(require 'whitespace)
(setq whitespace-style
      '(face       ;; faceで可視化
        trailing   ;; 行末
        tabs       ;; タブ
        spaces     ;; スペース
        space-mark ;; 表示のマッピング
        tab-mark))
(setq whitespace-display-mappings
      '((space-mark ?\u3000 [?\u25a1])
        ;; WARNING: the mapping below has a problem.
        ;; When a TAB occupies exactly one column, it will display the
        ;; character ?\xBB at that column followed by a TAB which goes to
        ;; the next TAB column.
        ;; If this is a problem for you, please, comment the line below.
        (tab-mark ?\t [?\u00BB ?\t] [?\\ ?\t])))

;; スペースは全角のみを可視化
(setq whitespace-space-regexp "\\(\u3000+\\)")
(set-face-attribute 'whitespace-trailing nil
                    :foreground "DeepPink"
                    :background nil
                    :underline t)
(set-face-attribute 'whitespace-tab nil
                    :foreground "LightSkyBlue"
                    :background nil
                    :underline t)
(set-face-attribute 'whitespace-space nil
                    :foreground "GreenYellow"
                    :background nil
                    :weight 'bold)

(global-whitespace-mode 1)


で、あとはこのwhitespace-styleのtabsの設定が、go-modeのときだけなくなるようにすればよい。次のように書いたら良い。

(defun shibayu36/go-mode-hook ()
  ;; golangではハードタブを可視化しない
  (setq whitespace-style
      '(face
        trailing
        spaces
        space-mark)))
(add-hook 'go-mode-hook 'shibayu36/go-mode-hook)

まとめ

今回はEmacsgolangの編集環境の再整備をした。とはいっても昔の設定をそのまま使うだけで非常に良い編集環境ができあがるので、golangはすごい。