Open17

Go言語プログラミングエッセンス

ohtacaesarohtacaesar

スライスの要素を削除する方法

append

n := 50
a = append(a[:n], a[n+1:]...)

部分参照+copy

アロケーションが発生しないので上より良い

n := 50
a = a[:n+copy(a[n:], a[n+1:])]
ohtacaesarohtacaesar

Unicode文字列を扱う

stringではなく[]runeを使うと再代入できたりする

func TestString(t *testing.T) {
	s := "あいうえお"
	rs := []rune(s)
	if len(rs) != utf8.RuneCountInString(s) {
		t.Error(len(rs))
	}

	rs[0] = 'か'
	if string(rs) != "かいうえお" {
		t.Error(s)
	}
}
ohtacaesarohtacaesar

エスケープ解析

  • ポインタが関数の外に返されるか自動で判断し、スタックからヒープに切り替える
  • C言語にはエスケープ解析がないため、関数内の変数のポインタを返すとスタックを参照してクラッシュする
ohtacaesarohtacaesar

defer

  • defer実行に使う変数は、defer指定時にキャプチャされる
    • deferに無名関数を指定する場合、無名関数内で使う変数はキャプチャされない
  • 指定順と逆に実行される
ohtacaesarohtacaesar

select

  • 複数のチャネルからデータを同時に待つのに使える
  • defaultを記述するとブロッキングされない
    • データの入力がない場合に、別の処理を行うのに使える
ohtacaesarohtacaesar

goモジュール

  • /examples/でのみ依存しているパッケージがある場合、/examples/go.modを作ることで/go.modに依存を記述しなくて済む
  • replaceでローカルディレクトリのパッケージを参照できる
ohtacaesarohtacaesar

interfaceを実装しているかチェックする

インターフェースの型を持つ変数に、実装型のポインタ型のnilを代入しておくとIDEが検知してくれる

package main

var _ A = (*B)(nil)

type A interface {
	Do()
}

type B struct {
}

func (b *B) Do() {
}
ohtacaesarohtacaesar

ランタイムでpanicが発生するケース

  • ゼロ除算
  • nilポインタのデリファレンス
  • 配列の境界外アクセス
ohtacaesarohtacaesar

recover

  • defer内で実行してpanicから復帰する
  • panicが発生したかは、recover()の返り値で判断する
  • ランタイムpanicの場合、recover()の返り値にはerror型が格納されている
func TestPanic(t *testing.T) {
	defer func() {
		if e := recover(); e == nil {
			t.Fatal("no panic")
		} else {
			log.Printf("%+v", e)
		}
	}()
	var i int
	log.Println(1 / i)
}

ohtacaesarohtacaesar

go:embed

  • 実行ファイルに他のファイルを埋め込める
  • コンパイル時にパスを読み込み、実行時にバイト列として格納される
  • 文字列やディレクトリも扱える
package main_test

import (
	_ "embed"
	"strings"
	"testing"
)

//go:embed embed_test.go
var code string

func TestEmbed(t *testing.T) {
	line := code[:strings.Index(code, "\n")]
	if line != "package main_test" {
		t.Error()
	}
}
ohtacaesarohtacaesar

オプション引数

おもにコンストラクタの話

Functional Options Pattern

  • 「structのポインタを引数とする関数」に別名をつける(Option)
  • Optionを返す関数を作成する
    • 設定内容に合わせ、WithABCのような名前にする
    • 引数
      • 生成するstructのフィールドに設定する値
    • 返り値
      • 与えられたstructポインタのフィールドに値をセットする関数
  • コンストラクタ
    • 可変長引数で複数のOptionを受け取れるようにする
    • 与えられたOptionをすべて実行し、生成するstructのフィールドをセットする
package main_test

import "testing"

type FOP struct {
	a string
	b string
}

type Option func(*FOP)

func WithB(s string) Option {
	return func(a *FOP) {
		a.b = s
	}
}

func NewFOP(a string, opts ...Option) *FOP {
	s := &FOP{a: a}
	for _, opt := range opts {
		opt(s)
	}
	return s
}

func TestFunctionalOptionsPattern(t *testing.T) {
	a := NewFOP("a", WithB("b"))
	if a.a != "a" {
		t.Error("a")
	}
	if a.b != "b" {
		t.Error("b")
	}
}

Buidler Pattern

よくあるやつ

ohtacaesarohtacaesar

template

  • FuncMapで自作関数を追加できる
  • ParseFiles, ParseGlobでファイルからテンプレートを読み込める
    • go:embedと組み合わせて使えそう
  • defineでテンプレートに名前をつけられる
  • 命令直後に-をつけると前後の改行を無効化できる
    • {{- .Text -}}
      
ohtacaesarohtacaesar

ジェネレータの合流

package main_test

import (
	"fmt"
	"log"
	"testing"
)

func generator(msg string) <-chan string {
	ch := make(chan string)
	go func() {
		for i := 0; ; i++ {
			ch <- fmt.Sprintf("%d: %s", i, msg)
		}
	}()
	return ch
}

func join(generators ...<-chan string) <-chan string {
	ch := make(chan string)
	for _, g := range generators {
		go func(g <-chan string) {
			for {
				ch <- <-g
			}
		}(g)
	}
	return ch
}

func TestJoin(t *testing.T) {
	ch := join(
		generator("a"),
		generator("b"),
		generator("c"),
		generator("d"),
		generator("e"),
	)

	for i := 0; i < 10; i++ {
		s := <-ch
		log.Println(s)
	}
}
ohtacaesarohtacaesar

チャネルを使ったスロットリング

バッファのキャパシティサイズで同時実行数を制御する

func TestThrottling(t *testing.T) {
	limit := make(chan struct{}, 5)

	wg := sync.WaitGroup{}
	for i := 0; i < 10; i++ {
		go func(i int) {
			wg.Add(1)
			defer wg.Done()
			limit <- struct{}{}
			log.Printf("no: %d", i)
			time.Sleep(2 * time.Second)
			<-limit
		}(i)
	}
	time.Sleep(1 * time.Second)
	if l := len(limit); l != 5 {
		t.Error(l)
	}
	wg.Wait()
}
ohtacaesarohtacaesar

test

  • go test -shortで簡易なテストのみ実行する
    • オプションが有効かはtesting.Short()で判別できる
    • オプションが有効な場合、t.SkipNow()で詳細なテストをスキップさせる
  • google/go-cmpを使うと、差分を簡単に可視化できる
  • t.Setenvで一時的な環境変数をセットできる