YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

実践 ベストプラクティスを適用してみる

はじめに

こんにちは、Go界のジェフ・ベゾスです。シアトルのお父さんがGoでおもしろプログラムを書いているようですが、ちょっと気になったので勝手に添削しました。

元のコード

まず最初のコード。

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "log"
    "os"
)

func main() {
    if len(os.Args) < 1 {
        log.Fatal("You must input a hostname")
    }
    peerCertificates, err := GetCert(os.Args[1])
    if err == nil {
        for i, Cert := range peerCertificates {
            fmt.Printf("i=%d\\n", i)
            fmt.Println(Cert.Issuer.ToRDNSequence())
            fmt.Println(Cert.NotBefore)
            fmt.Println(Cert.NotAfter)
            fmt.Println(Cert.Subject.ToRDNSequence())
        }
    } else {
        log.Fatal(err)
    }
}

func GetCert(host string) ([]*x509.Certificate, error) {
    config := &tls.Config{InsecureSkipVerify: true}
    conn, err := tls.Dial("tcp", host+":443", config)
    var peerCertificates []*x509.Certificate
    if err == nil {
        connectionState := conn.ConnectionState()
        peerCertificates = connectionState.PeerCertificates
    }
    return peerCertificates, err
}

勝手に添削

エラー処理は先に書く

Goには例外処理はありません。通常関数の戻り値を正常値とエラーのタプルとして、異常がある場合にはエラーにError型の何かが入るという形になります。 さて、例外処理がない分、エラーの扱いはCと同様、if等で扱うことになります。ベストプラクティスとしては、 errnil でないパターンを先に処理するべきです。そうしないとif分がネストしやすくなる上にコードが見辛くなります。 この間のOSCON 2013でもエラー処理のベストプラクティスとして話されていました。

エラー処理を先にしてみるとコードもちょっと読みやすくなりました。

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "log"
    "os"
)

func main() {
    if len(os.Args) < 1 {
        log.Fatal("You must input a hostname")
    }
    peerCertificates, err := GetCert(os.Args[1])
    if err != nil {
        log.Fatal(err)
        return
    }

    for i, Cert := range peerCertificates {
        fmt.Printf("i=%d\\n", i)
        fmt.Println(Cert.Issuer.ToRDNSequence())
        fmt.Println(Cert.NotBefore)
        fmt.Println(Cert.NotAfter)
        fmt.Println(Cert.Subject.ToRDNSequence())
    }
}

func GetCert(host string) ([]*x509.Certificate, error) {
    config := &tls.Config{InsecureSkipVerify: true}
    conn, err := tls.Dial("tcp", host+":443", config)
    if err != nil {
        return nil, err
    }
    var peerCertificates []*x509.Certificate
    connectionState := conn.ConnectionState()
    peerCertificates = connectionState.PeerCertificates
    return peerCertificates, err
}

型推論を積極的に使う

GoはCとPythonの中間的な文法や記述をしますが、どちらかというとC寄りではあります。ただ、豊富な標準パッケージや型推論、初期化演算子などで、記述量を減らすことが可能になっています。 変数宣言も出来るだけ初期化と同時に行ったほうが読みやすいコードになります。

func GetCert(host string) ([]*x509.Certificate, error) {
    config := &tls.Config{InsecureSkipVerify: true}
    conn, err := tls.Dial("tcp", host+":443", config)
    if err != nil {
        return nil, err
    }

    connectionState := conn.ConnectionState()
    peerCertificates := connectionState.PeerCertificates // peerCertificatesは初期化もしちゃう
    return peerCertificates, nil
}

さらに言えば必要ない宣言も減らせるので次のように落ち着きます。

func GetCert(host string) ([]*x509.Certificate, error) {
    config := &tls.Config{InsecureSkipVerify: true}
    conn, err := tls.Dial("tcp", host+":443", config)
    if err != nil {
        return nil, err
    }

    connectionState := conn.ConnectionState()
    return connectionState.PeerCertificates, nil
}

添削後

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "log"
    "os"
)

func main() {
    if len(os.Args) < 1 {
        log.Fatal("You must input a hostname")
    }
    peerCertificates, err := GetCert(os.Args[1])
    if err != nil {
        log.Fatal(err)
        return
    }

    for i, Cert := range peerCertificates {
        fmt.Printf("i=%d\\n", i)
        fmt.Println(Cert.Issuer.ToRDNSequence())
        fmt.Println(Cert.NotBefore)
        fmt.Println(Cert.NotAfter)
        fmt.Println(Cert.Subject.ToRDNSequence())
    }
}

func GetCert(host string) ([]*x509.Certificate, error) {
    config := &tls.Config{InsecureSkipVerify: true}
    conn, err := tls.Dial("tcp", host+":443", config)
    if err != nil {
        return nil, err
    }

    connectionState := conn.ConnectionState()
    return connectionState.PeerCertificates, nil
}