Effective Go

Commentary

全てのパッケージには、パッケージコメント(package 句の前にあるブロックコメント)を持つべき。マルチファイルパッケージの場合、パッケージコメントは1つのファイルにのみ存在する必要がある。

/*
Package regexp implements a simple library for regular expressions.

The syntax of the regular expressions accepted is:

    regexp:
        concatenation { '|' concatenation }
    concatenation:
        { closure }
    closure:
        term [ '*' | '+' | '?' ]
    term:
        '^'
        '$'
        '.'
        character
        '[' [ '^' ] character-ranges ']'
        '(' regexp ')'
*/
package regexp

パッケージが単純な場合、パッケージのコメントは簡潔にすることができる。

// Package path implements utility routines for
// manipulating slash-separated filename paths.

生成された出力は、固定幅フォントで表示されない場合もあるため、配置のために space を用いないでください。
gofmtgodocでフォーマット化して、期待通りの表示をされているか確認する必要がある。

エクスポートされ r た名前にはすべてドキュメントコメントを残すべき。最初の文は、宣言している名前で始まる一文の要約であるべき。

// Compile parses a regular expression and returns, if successful,
// a Regexp that can be used to match against text.
func Compile(str string) (*Regexp, error) {

なぜなら、「Compile」という関数の名前を思い出せなくても、正規表現の解析関数を探しているので以下のように同時に関数名が表示され簡単に見つけ出せる。

$ go doc -all regexp | grep -i parse
    Compile parses a regular expression and returns, if successful, a Regexp
    MustCompile is like Compile but panics if the expression cannot be parsed.
    parsed. It simplifies safe initialization of global variables holding

宣言をグループ化したものに、ドキュメントコメントを残すとその関連する定数または変数のグループを紹介できる。

// Error codes returned by failures to parse an expression.
var(
    ErrInternal = errors.New( "regexp:internal error")
    ErrUnmatchedLpar = errors.New( "regexp:unmatched'('")
    ErrUnmatchedRpar = errors.New( "regexp:unmatched')'"...

Names

Package names

パッケージ名には、小文字の一語の名前をつけるべき。underscore や mixedCaps を付けるべきではない。なぜなら、パッケージを使用するすべての人がその名前を入力するため、簡潔である必要がある。衝突しても別の名前を使用することができるので問題ない。

また、パッケージ名はそのソースディレクトリの basename であるべき。src/encoding/base64パッケージはencoding/base64として import される。

また、パッケージ名を使用することでエクスポートされる名前の重複を防ぐことができる。例えば、ring.Ringというコンストラクタの名前をring.NewRingとせず、ring.Newと簡潔に書くことができる。

Getter

ゲッターの名前に Get を入れるのは慣例でも必要でもない。セッター関数が必要な場合は、 Setをつけるのが慣例。

owner:= obj.Owner()
if owner!= user {
    obj.SetOwner(user)
}

Interface names

慣習として、1 メソッドのインターフェースは、メソッド名に er サフィックスを付ける。Reader、Writer、Formatter、CloseNotifier など。

MixedCaps

Go では複数単語の名前を書く場合、アンダースコアではなく MixedCaps か mixedCaps を使うのが慣例。

Semicolons

C 言語と同様、Go はステートメントの最後にセミコロンを使用しますが、C 言語とは異なり、ソースには記述しない。レキサが自動的にセミコロンを挿入する。

Control Structure

Switch

break 文で switch を抜けることができる。ラベル移動も可能。s

Loop:
    for n := 0; n < len(src); n += size {
        switch {
        case src[n] < sizeOne:
            if validateOnly {
                break
            }
            size = 1
            update(src[n])

        case src[n] < sizeTwo:
            if n+1 >= len(src) {
                err = errShortInput
                break Loop
            }
            if validateOnly {
                break
            }
            size = 2
            update(src[n] + src[n+1]<<shift)
        }
    }

Type Switch

var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
    fmt.Printf("unexpected type %T\n", t)     // %T prints whatever type t has
case bool:
    fmt.Printf("boolean %t\n", t)             // t has type bool
case int:
    fmt.Printf("integer %d\n", t)             // t has type int
case *bool:
    fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
    fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}

Functions

Named result parameters

関数の戻り値には名前付きパラメータを用いることができる。名前をつけると、関数が始まる時にその型のゼロ値で初期化される。

これはコードをより短く、より明確化するためのもので、いわばドキュメントである。

func ReadFull(r Reader, buf []byte) (n int, err error) {
    for len(buf) > 0 && err == nil {
        var nr int
        nr, err = r.Read(buf)
        n += nr
        buf = buf[nr:]
    }
    return
}

Defer

defer 文は、defer を実行する関数が戻る直前に、実行される関数呼び出しをスケジュールする。これは、関数がどの経路で戻るかに関係なくリソースを解放しなければならないような状況に対処するためな方法に用いられる。
例としては、Mutex の Unlock や File の Close などがある。

Close のような関数への呼び出しを延期することには、2つの利点がある。

  • ファイルを Close するのを保証する。後で関数を編集する時に、return 文を追加して Close し忘れることは多々ある
  • Close が Open 文の近くにあるため、明確になる

defer 文に渡される関数の引数は、defer 文を通る時に評価される(関数を抜けた時に評価されない)。つまり、defer 文の関数の引数などが後で値が変わる心配がないという利点がある。また、一つの関数内で複数の defer 文を呼び出せることを意味する。

func trace(s stringstring {
    fmt.Println( "entering:"、s)
    return s
}

func un(s string{
    fmt.Println( "leaving:"、s)
}

func a(){
    defer un(trace ("a"))
    fmt.Println( "in a"}

func b(){
    defer un(trace( "b"))
    fmt.Println( "in b")
    a()
}

func main(){
    b ()
}
entering: b
in b
entering: a
in a
leaving: a
leaving: b

Data

Allocation with new

newmakeという allocation primitive がある。
newはメモリを確保する関数ですが、メモリを初期化するのではなく、ゼロ値にする。つまり、new(T)はゼロ値で初期化された T 型を確保し、そのアドレスを(*T 型)を返す。
&T{}と同じこと。

var a *Testはメモリを初期化するだけなので、a はnilになる。

Arrays

配列は(アドレスではなく)値である。そのため、関数の引数に配列を渡すとその関数は配列へのポインタではなく、配列を丸々コピーされたものを受け取る。
効率化するために配列のポインタを渡せば良い。

func Sum(a *[3]float64) (sum float64) {
    for _, v := range *a {
        sum += v
    }
    return
}

array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array)  // Note the explicit address-of operator

しかし、Go ではこのような方法は慣例的ではない。代わりにスライスを用いるべき。

Two-dimensional slices

2次元スライスを初期化する方法は二つある

  1. 各スライスを独立して割り当てる方法
// Allocate the top-level slice.
picture := make([][]uint8, YSize) // One row per unit of y.
// Loop over the rows, allocating the slice for each row.
for i := range picture {
    picture[i] = make([]uint8, XSize)
}
  1. 一つの配列を割り当てて、そこに個々のスライスを指定する方法
// Allocate the top-level slice, the same as before.
picture := make([][]uint8, YSize) // One row per unit of y.
// Allocate one large slice to hold all the pixels.
pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8.
// Loop over the rows, slicing each row from the front of the remaining pixels slice.
for i := range picture {
    picture[i], pixels = pixels[:XSize], pixels[XSize:]
}