LoginSignup
3
1

More than 3 years have passed since last update.

超訳Effective Goその1

Last updated at Posted at 2019-08-16

重要だからEffective Go読めよ。
でもダラダラ書いてあるから超訳してみた。

とりあえずControl structures(制御構造)まで。

イントロダクション

Goで実装するなら他の言語のことは一旦忘れろ。既存コードはそのまま移植しようとしても上手くいかない。
Goに入ってはGoに従え

サンプル

Goのパッケージのソースは良サンプルだからそのまま手本にしろ。

フォーマッティング

gofmtしろ

コメント

C++スタイルだ。/* comment */ // comment
トップレベルの宣言の直前行に書いたコメントはそのままgodocでドキュメントになるぞ。
最初の行は宣言名で始まる1行サマリーを書くんだ。
英語のプレーンテキストで書くのがベストだ。余計な書式タグとかはいらないぞ。

名前

Go命名規則は重要な意味がある(例えば頭大文字でパッケージ外に公開)から手間を惜しむなよ。
ちなみに名前は短いほうがいい。超絶長い名前付けるくらいならドキュメントを書くんだ。

パッケージ名

パッケージ名はそのソースファイルのディレクトリ名だ。
名前は小文字の1単語にしろ。
万が一インポートするパッケージ名が衝突しても使うときに選択可能だし、実際混乱することなんて稀だ。

src/encoding/base64ににあるソースのパッケージ名はbase64

import (
  "encoding/base64"
  "fmt"
)

func main() {
    str := base64.StdEncoding.EncodeToString([]byte("data"))
    fmt.Println(str)
}

ってな感じで使うぞ。

ゲッター

Goにゲッター、セッター的な規約はないが実装しても構わない。
ゲッターはエクスポートされていないownerフィールドに対して大文字で始まるOwner()って書いてエクスポートされたフィールドっぽくアクセスできるようにするんだ。
セッターのほうはSetOwner()でいいぞ。

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

インターフェース名

one-method interfaces(メソッドが1個しないインターフェース)は、メソッド名に接尾辞「er」つけてインターフェース名にするんだ。
ReaderWriterFormatterCloseNotifierこういうやつ。

ReadWriteCloseFlushStringみたいなメソッドは上記のような既知のone-method interfaceがあるから、そのインターフェースを使えるように命名することも大事だ。例えば文字列として出力するメソッド名はtoString()じゃなくてString()にしてStringerとして扱えるようにするんだ。
既知のインターフェースと別の用途で使うなら被らない別の名前にしてくれ。

大文字混合

Goではスネークケースsnake_casing的なやつじゃなくて、いわゆるキャメルケースcamelCasingとかパスカルケースPascalCasingってやつを使うんだ。

セミコロン

いらん。

正確には改行直前のトークン後にコンパイラが自動的にセミコロンを入れているんだ。
だから、{の前とかに改行入れると不要なセミコロン扱いでコンパイルエラーになるぞ。

if i < f()  // wrong! ;
{           // wrong!
    g()
}

制御構造

Cに似てるがいろいろ違うぞ。

  • dowhileはない
  • ifswitchはいろいろ初期化できる
  • breakcontinueはラベルを使って抜ける場所を決められる
  • selectっていうのが増えた

if

カッコつけんじゃない

if x > 0 {
    return y
}

もちろんreturnbreakで制御ブロックから抜けられる。

forみたいにローカル変数として初期化できる

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

breakcontinuegotoreturnで終了するんだったらelseは省略して次の処理を書いていい。

f, err := os.Open(name) // err宣言
if err != nil {
    return err
}
d, err := f.Stat()  // d宣言、err再割当て(代入)
if err != nil {
    f.Close()
    return err
}
codeUsing(f, d)

再宣言と再割り当て

余談だが、前項の例でerrが2回宣言されているように見えるが問題ない。2回目はスコープそのままに普通の代入だ。
:=で複数宣言するときは、新しく宣言する変数が最低1つあればいい。

for

Goのfor ループはCと似て非なるものだ。Cのwhileとかもforに統合されていて、3つの形式があるぞ。

// ふつうのCっぽいfor
for 初期化; 条件; ループ後 { }

// whileっぽいやつ 
for 条件 { }

// for(;;)っぽいやつ 無条件ループ
for { }

インデックス変数はループ初期化で簡単に宣言できるぞ。

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

arrayslicestringmap、またはchanから読取る場合はrangeを使ってループするぞ。

for key, value := range oldMap {
    newMap[key] = value
}

rangeでキーだけが必要なら代入する変数1個だけにしたらいいぞ。

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

値の方だけ必要なら、1番目の変数を_(blank identifier)にしてキーを無視すればいい。
_(blank identifier)はよく使うので後で説明する。

sum := 0
for _, value := range array {
    sum += value
}

文字列の場合はrange使うといろいろ(UTF-8を解析して個々のUnicodeコードポイントに分割、誤ったエンコードはU+FFFDに置換)やってruneを生成してくれるぞ。
(runeの詳細は言語仕様をみてくれ)

for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
    fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
prints

character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7

forで複数の式は使えないから、複数の変数を実行する場合は1つの式で複数割当るんだ。

// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}

switch

switchの式はCのと違って定数か整数じゃなくてもいいぞ。
caseは上から順にswitchの式(省略したらtrue)と一致するまで評価される。
これはif-else-if-elseチェーンをswitchで書けるってことだ。

func unhex(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
        return c - '0'
    case 'a' <= c && c <= 'f':
        return c - 'a' + 10
    case 'A' <= c && c <= 'F':
        return c - 'A' + 10
    }
    return 0
}

複数caseをフォールスルーはしないんだけど、1つのcaseに複数の条件を書けるぞ。

func shouldEscape(c byte) bool {
    switch c {
    case ' ', '?', '&', '=', '#', '+', '%':
        return true
    }
    return false
}

breakステートメントを使ってswitchを抜けることができるけど、スイッチじゃなくて外のforループから抜け出したいときは、forループにラベル付けをしておいて、そのラベルをbreakしてやればいい。
もちろんcontinueステートメントもラベルが使えるぞ。

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

        case src[n] < sizeTwo:
            if n+1 >= len(src) {
                err = errShortInput
                break Loop // forを抜ける
            }
            if validateOnly {
                break // switchを抜ける
            }
            size = 2
            update(src[n] + src[n+1]<<shift)
        }
    }

最後に2つのswitchステートメントを使用するbytesliceの比較ルーチンを書いておく。

// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
    for i := 0; i < len(a) && i < len(b); i++ {
        switch {
        case a[i] > b[i]:
            return 1
        case a[i] < b[i]:
            return -1
        }
    }
    switch {
    case len(a) > len(b):
        return 1
    case len(a) < len(b):
        return -1
    }
    return 0
}

type switch

switchをつかうことでinterface{}の動的型を検出できるぞ。
switchの式で、type asertion構文を使って変数を宣言すると、caseでその型が評価できるんだ。
このとき宣言する変数名は、元のinterface{}型の変数と同じ名前にするといい。その名前の変数が評価された型で使えるぞ。

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
}
3
1
0

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
  3. You can use dark theme
What you can do with signing up
3
1