printf デバッグは便利だ。技術の後退と言われようと printf でないと解決できない事はまだまだたくさんあります。
今日は net/http でクライアントが得たレスポンスの JSON を確認したいといった場合に、どうデバッグしたらいいかを書いてみたいと思う。
Go のインタフェースは大よそ io.Reader もしくは io.Writer を使う様に設計されている。こうする事でプログラムがメモリを一度に沢山確保してしまわない様にしています。
package main
import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)
type Foo struct {
    ID  string `json:"id"`
    Content string `json:"content"`
}
func main() {
    resp, err := http.Get("http://example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    var foo Foo
    err = json.NewDecoder(resp.Body).Decode(&foo)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(foo.Content)
}
例えばこういうコードの、resp.Body に何が流れているのか確認したい場合、デバッグ出力する為に一旦 ioutil.ReadAll で全て読み取ったりしていないでしょうか。
package main
import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)
type Foo struct {
    ID  string `json:"id"`
    Content string `json:"content"`
}
func main() {
    resp, err := http.Get("http://example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(b))
    var foo Foo
    err = json.NewDecoder(bytes.NewReader(b)).Decode(&foo)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(foo.Content)
}
デバッグ表示したいだけなのに、ちょっとコードが増えてしまった感じがしますよね。デバッグを無効にしたいときに消すコードも多い。しかも json.NewDecoder の部分にも手を入れてしまわないといけなくてなんだか嫌な感じもします。元のコードは json.NewDecoder の箇所に手を入れられるから良いですが、時には io.Reader を引数に持つ関数に渡す必要があったり、ioutil.ReadAll で全て読み取る事が出来ないストリームデータの場合には使えません。こういった場合は io.TeeReader を使います。
package main
import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
)
type Foo struct {
    ID  string `json:"id"`
    Content string `json:"content"`
}
func main() {
    resp, err := http.Get("http://example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    var r io.Reader = resp.Body
    r = io.TeeReader(r, os.Stderr)
    var foo Foo
    err = json.NewDecoder(r).Decode(&foo)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(foo.Content)
}
こうしておき、必要に応じて r = io.TeeReader(r, os.Stderr) の行をコメントアウトすれば良いのです。コメントアウトを外せばデバッグ表示になります。メモリも節約出来てお得感ありますね。
 
