Go Conference 2014 Autumnレポート

鵜飼文敏氏「Goに入ってはGoに従え」可読性のあるコードにするために~Go Conference 2014 Autumn基調講演2人目

この記事を読むのに必要な時間:およそ 10 分

素直なコードフロー

インデントは最小にする

同氏は,基本のコードパスのインデントは最小にすべきだと述べていました。たとえば,以下のコードはifの条件を入れ替えることでもっと簡潔に書けるそうです。

if _, ok := f.dirs[dir]; !ok {
    f.dirs[dir] = new(feedDir)
} else {
    f.addErr(fmt.Errorf("..."))
    return
}
// some code

また,マップのキーが存在するかどうかを表す変数はいつでも変数名をokにする必要はなく,否定的な意味で使う場合はokだと不自然であると指摘していました。 この場合であれば,okの代わりにfoundという名前を使い,条件式を逆にすることで,!okfoundにすることができるとのことでした。

if _, found := f.dirs[dir]; found {
    f.addErr(fmt.Errorf("..."))
    return
}
f.dirs[dir] = new(feedDir)
// some code

上記のコードのように,うまく条件式を書くことでelseを使う必要がなく,インデントが少なくなります。

関数の分割

以下のコードでは,条件によってHTTPステータスを変え,結果をJSONで返すという処理をしています。しかし,この書き方では基本のコードパスがわかりづらく,どういうときにどういうHTTPステータスが返されるのかわかりにくくなっていると,同氏は指摘していました。

func (h *RESTHandler) finishReq(op *Operation, req *http.Request, w http.ResponseWriter) {
    result, complete := op.StatusOrResult()
    obj := result.Object
    if complete {
        status := http.StatusOK
        if result.Created {
            status = http.StatusCreated
        }
        switch stat := obj.(type) {
        case *api.Status:
            if stat.Code != 0 {
                status = stat.Code
            }
        }
        writeJSON(status, h.codec, obj, w)
    } else {
        writeJSON(http.StatusAccepted, h.codec, obj, w)
    }
}

そこで,ステータスを決める関数finishStatusを別に作ることで,HTTPステータスを決めて,JSONで返すという基本のコードパスをわかりやすくすることができると同氏は主張していました。また,ステータスを決める関数も条件が判定できれば,すぐにリターンすることができるため,非常にわかりやすくなるそうです。

func finishStatus(r Result, complete bool) int {
    if !complete {
        return http.StatusAccepted
    }
    if stat, ok := r.Object.(*api.Status); ok && stat.Code != 0 {
        return stat.Code
    }
    if r.Created {
        return http.StatusCreated
    }
    return http.StatusOK
}

func (h *RESTHandler) finishReq(op *Operation, w http.ResponseWriter, req *http.Request) {
    result, complete := op.StatusOrResult()
    status := finishStatus(result, complete)
    writeJSON(status, h.codec, result.Object, w)
}

switchを使う

以下のコードでは,Webブラウザのサイズによって対応する文字列"small""medium""large""null"を返す処理をしています。

func BrowserHeightBucket(s *session.Event) string {
    browserSize := sizeFromSession(s)
    if h := browserSize.GetHeight(); h > 0 {
        browserHeight := int(h)
        if browserHeight <= 480 {
            return "small"
        } else if browserHeight <= 640 {
            return "medium"
        } else {
            return "large"
        }
    } else {
        return "null"
    }
}

このコードでは,ifが入れ子になっていて,さらにif elseによって長くなっています。 同氏は,このような場合はifではなくswitchを使うと以下のように簡潔に書けると述べていました。

func BrowserHeightBucket(s *session.Event) string {
    size := sizeFromSession(s)
    h := size.GetHeight()
    switch {
    case h <= 0:
        return "null"
    case h <= 480:
        return "small"
    case h <= 640:
        return "medium"
    default:
        return "large"
    }
}

著者プロフィール

上田拓也(うえだたくや)

KLab(株)所属。Go Conference 2014 Autumnスタッフ。

業務ではJavaScript,Luaなどを扱っている。

大学時代にGo言語に出会い,それ以来Go言語にのめり込む。

時間があると,Go言語の勉強会に参加している。

Go言語のマスコットのGopherの絵を描くのも好き。

Twitter:@tenntenn