幾らか言いたい事があったので。
Go言語感想文 - なるせにっき
序 最近、敵情視察を兼ねた仕事ととしてGoでアプリケーションを書いていた。このアプリケーションがどんなものかはそのうち id:tagomoris さんがどこかで話すと思うけれど、この コンポーネント ...
http://naruse.hateblo.jp/entry/2017/06/02/203441
GoroutineとChannel
Goroutineはようするにスレッドなんですが、文法と実装の支援でより気軽に使えるのが他の言語との違いでしょうか。なので、Goroutineをどれだけほいほい使うべきかというコスト感覚を身につけることがとても大事な気がします。Rubyなどとは気持ちを切り替えていく必要があるでしょう。ぼくはまだ切り替えきれていません。
Goroutine はスレッドではありません。Goroutine はコルーチンでありスレッドです。ランタイムが必要に応じてスレッドで実行するかコルーチンで実行するかをインテリジェントに切り替えます。ユーザが意識する必要はありません。無秩序に大量に作る様な事がないのであれば気軽に作成して良いはずです。
テストについて
アサーションをコピーしなければならない理由は一つのテストケースの中で異なるテストが混在しているか、単に同様のテストがコピーして作られているのが原因ではないでしょうか。
極端な例かもしれませんが、例えば以下の FizzBuzz 関数をテストするとします。
package fizzbuzz
import "fmt"
func FizzBuzz(n int) (string, error) {
if n < 1 || n > 100 {
return "", fmt.Errorf("invalid number: %v", n)
}
switch {
case n%15 == 0:
return "FizzBuzz", nil
case n%3 == 0:
return "Fizz", nil
case n%5 == 0:
return "Buzz", nil
default:
return fmt.Sprint(n), nil
}
}
1未満や100を超える値の場合はエラーとなり、それ以外は通常通り FizzBuzz の結果を返します。これをのっぺりとテストすると
func TestFizzBuzz(t *testing.T) {
var input int
got, err := FizzBuzz(-1)
if err == nil {
t.Fatalf("should be error for %v but not:", -1)
}
got, err := FizzBuzz(1)
if err != nil {
t.Fatalf("should not be error for %v but: %v", 1, err)
}
if got != "1" {
t.Fatalf("want %q, but %q:", "1", got)
}
got, err := FizzBuzz(3)
if err != nil {
t.Fatalf("should not be error for %v but: %v", 1, err)
}
if got != "Fizz" {
t.Fatalf("want %q, but %q:", "Fizz", got)
}
}
この様に Fatalf のコピーになりかねません。まだ Buzz や FizzBuzz のテストも出来ていませんから、これからコピペが大量に作られる訳です。確かに Ruby の DSL は強力で、この様な退屈なテストを短い構文で記述する事が出来ます。しかし例外のない Go においては if 文が頻発し得ます。そこで Go ではテーブルドリブンテスト(Table Driven Tests)が推奨されています。
TableDrivenTests · golang/go Wiki · GitHub
Home Articles Blogs Books BoundingResourceUse cgo ChromeOS CodeReview CodeReviewComments CodeTools C...
https://github.com/golang/go/wiki/TableDrivenTests
以上のテストを Table Driven Tests に置き換えると以下の様になります。
func TestFizzBuzz(t *testing.T) {
tests := []struct {
input int
want string
err bool
}{
{input: -100, want: "", err: true},
{input: -1, want: "", err: true},
{input: 0, want: "", err: true},
{input: 1, want: "1", err: false},
{input: 2, want: "2", err: false},
{input: 3, want: "Fizz", err: false},
{input: 4, want: "4", err: false},
{input: 5, want: "Buzz", err: false},
{input: 6, want: "Fizz", err: false},
{input: 14, want: "14", err: false},
{input: 15, want: "FizzBuzz", err: false},
{input: 16, want: "16", err: false},
{input: 100, want: "Buzz", err: false},
{input: 101, want: "", err: true},
}
for _, test := range tests {
got, err := FizzBuzz(test.input)
if !test.err && err != nil {
t.Fatalf("should not be error for %v but: %v", test.input, err)
}
if test.err && err == nil {
t.Fatalf("should be error for %v but not:", test.input)
}
if got != test.want {
t.Fatalf("want %q, but %q:", test.want, got)
}
}
}
このテストでは今後テストケースを増やしても if が増える事はありません。つまり t.Fatal も増えません。一つのテストケースの中でテストされる結果はおおよそ同様の物となるはずです。そうでないならばそれはテストがユニットテストになっていないのだと思います。
その他
switchべんり ← おまえほんとうにそれでいいのか ** selectやswitchの中でbreakすると外側のforまで届かないのでbreak すればよいけど、結局goto使う
Golang に限らずですが、最近の言語では Labeled Break という物があります。
exit_loop:
for {
s := foo()
switch s {
case "exit":
break exit_loop
}
}
Goはnull安全ではない←構造体のポインタを扱い始めると気になってくる
重箱ぽくなりますが、構造体フィールドに直接アクセスしなければレシーバが nil かどうかで判定出来ます。
package main
import "fmt"
type Foo struct {
v int
}
func (f *Foo) doSomething() string {
if f == nil {
return "ぬるぽ"
}
return "のっとぬるぽ"
}
func main() {
var f *Foo
fmt.Println(f.doSomething())
f = new(Foo)
fmt.Println(f.doSomething())
}
まぁ、f が nil である事も条件を切り分ける為の一つの状態なので、これはあまり使わない手法ではあります。Go が null 安全だとは言ってないです。