search
LoginSignup
90

More than 5 years have passed since last update.

posted at

updated at

Goだからこそ許される3つの作法

この記事はGo その3 Advent Calendar 2015の18日目です。


仕事の関係でGoを使い始めて気付けば9ヶ月。今ではすっかり慣れましたが、始めた当初はそれまでC言語・Java中心で仕事をしてきた自分にとってGoの文化はかなり異質に映ったのを覚えています。

せっかくなので、他言語経験者でこれからGoを始めますよという方向けに、「Goではこんなことしても許されるよ」みたいなポイントを挙げてみたいと思います。

1文字の変数名

昔のプログラミングの入門書のコード例なんかだと、

int a, b, c;

というように1文字の変数名が多用されてたりして、それを引きずったまま職業プログラマになってコードレビューで可読性警察に逮捕される、なんてことは良くあったのではないでしょうか。

最近は「リーダブルコード」などの良い書籍が普及したのもあり、変数名は多少長くとも意味が判るものが良い、という文化が一般的なのではないかな、と思います。例えばこんな感じ。

int theAnswerToTheUltimateQuestionOfLife = 42;

ところがGoのソースコードを読んでみると1文字の変数名がバリバリ使われてます。

// strings.Join関数のコードを抜粋
func Join(a []string, sep string) string {
    if len(a) == 0 {
        return ""
    }
    if len(a) == 1 {
        return a[0]
    }
    n := len(sep) * (len(a) - 1)
    for i := 0; i < len(a); i++ {
        n += len(a[i])
    }

    b := make([]byte, n)
    bp := copy(b, a[0])
    for _, s := range a[1:] {
        bp += copy(b[bp:], sep)
        bp += copy(b[bp:], s)
    }
    return string(b)
}

この辺はGoの公式ドキュメントの一端でもきちんと触れられていて

Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCount. Prefer i to sliceIndex.

GoCodeReviewCommentsのVariable Namesより抜粋)

と、「スコープが限られたローカル変数に限り」短い変数名を推奨しているんですね。確かに、先述のコード内のnやbといった変数も数行しか使われていないので、あまり読みにくいと感じることはありません。

シンプルさを尊重するGoの文化が良く表れてますね。

グローバル変数

グローバル変数って今では親の仇のように嫌われてますよね。自分から使う人はもうあまりいないと思いますが、JavaScriptでうっかりvarつけ忘れてコードレビューでけちょんけちょんにされるなんてことは未だにあると思います。少なくとも僕が見つけたらそうします。

function add(left, right) {
    result = left + right;
    return result;
}

でもGoだと気にせずガンガン使います。ガンガンは言い過ぎだけど割と使っても平気です。パッケージ名で綺麗に名前空間が区切られますものね。

クラスの概念がないGoだとパッケージ全体を静的クラスと同等のものとみなして、グローバル変数を静的フィールドのように使うこともあります。例えばシングルトンパターンを実現しようとすると以下のようなものになります。

Singleton Pattern in Goより抜粋)

package singleton

import (
    "sync"
)

type singleton struct {
}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}

うん、これグローバル変数というよりただのパッケージプライベートな変数だ!
許してください、なんでもしますから・・・

パブリックな例はosパッケージとか見れば結構見つかるんじゃないですかね(投げやり)

1テストメソッド複数アサーション

多くの言語のテストメソッドで良く言われるアンチパターンとして、1メソッドに複数のassertを書いてしまう、というものがあります。

@Test
public void testXXX() {
    hoge = new Hoge();
    assertThat(hoge.getName(), is("ほげ"));
    assertThat(hoge.getWife(), nullValue());
    //・・・
}

これが嫌われる理由は以下のような点にあります。

  • 途中のassertで失敗すると、以降のassertが行われない
  • テストメソッドが単一責務の原則に違反する可能性がある

まあ後者に該当するようなケースは素直にメソッド分けした方が良いと思いますが、少なくとも先述のコードはオブジェクトのプロパティの初期値のassertをしているに過ぎず、複数メソッドに分ける価値は薄いように見えます。かといって一々特化したassertメソッドを作るのはめんどくさい。

でも、Goなら気にせず以下のように書いてしまえばいいんですね。

func TestXXX(t *testing.T) {
    hoge := hoge.New()
    if hoge == nil {
        t.Fatal("Failed to create hoge.")
    }
    if hoge.Name != "ほげ" {
        t.Errorf("hoge.Name => %s, wants %s", hoge.Name, "ほげ")
    }
    if hoge.Wife != nil {
        t.Errorf("hoge.Wife => %v, must be nil", hoge.Wife)
    }
}

テストメソッドを中断しないError系のメソッドと、中断するFatal系のメソッドを使い分けることで実に気楽にテストが書けます。testingパッケージ最強や!

JavaならHogeクラスのequalsメソッドをオーバーライドしとけ?
C言語ならGoogle C++ Testing Frameworkでも出来る?
はて・・・


もっとあったと思いますが、いざ考えるとなかなか挙げられないものです・・・。
思い出したらまた増やします。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
90