重要だから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」**つけてインターフェース名にするんだ。
Reader
、Writer
、Formatter
、CloseNotifier
こういうやつ。
Read
、 Write
、 Close
、 Flush
、 String
みたいなメソッドは上記のような既知のone-method interfaceがあるから、そのインターフェースを使えるように命名することも大事だ。例えば文字列として出力するメソッド名はtoString()
じゃなくてString()
にしてStringer
として扱えるようにするんだ。
既知のインターフェースと別の用途で使うなら被らない別の名前にしてくれ。
大文字混合
Goではスネークケースsnake_casing
的なやつじゃなくて、いわゆるキャメルケースcamelCasing
とかパスカルケースPascalCasing
ってやつを使うんだ。
セミコロン
いらん。
正確には改行直前のトークン後にコンパイラが自動的にセミコロンを入れているんだ。
だから、{
の前とかに改行入れると不要なセミコロン扱いでコンパイルエラーになるぞ。
if i < f() // wrong! ;
{ // wrong!
g()
}
制御構造
Cに似てるがいろいろ違うぞ。
do
やwhile
はないif
とswitch
はいろいろ初期化できるbreak
とcontinue
はラベルを使って抜ける場所を決められる- **
select
**っていうのが増えた
if
カッコつけんじゃない。
if x > 0 {
return y
}
もちろんreturn
やbreak
で制御ブロックから抜けられる。
for
みたいにローカル変数として初期化できる。
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
break
、continue
、goto
、return
で終了するんだったら**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
}
array
、slice
、string
、map
、または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ステートメントを使用するbyte
sliceの比較ルーチンを書いておく。
// 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
}