Go言語初心者がハマった2つのポイント

こちらはeureka Advent Calendar 2016 21日目の記事です。 
前回は臼井さんの「regexpとの付き合い方 〜 Go言語標準の正規表現ライブラリのパフォーマンスとアルゴリズム〜」でした! 
 

こんにちは。エウレカでPairsを担当しています @takochuu と申します。 
今回の記事では、今年の7月まではインタプリタ型の言語でずっと開発していた僕が 
どのようにGo言語のキャッチアップに取り組んだかを紹介します。 
 

こちらの二川さんの記事にあるように 
エウレカではPairsとCouplesという2つのプロダクトを展開しており、Pairsではサーバーサイドは全てGo言語で記述されています。 
 

前職ではPerlを使って業務を行ってきた僕には、コンパイル型の言語であるGo言語での開発をキャッチアップする過程で様々なハードルがありました。 
今回は実際に僕がハマった一例についてご紹介します。 
 

インターフェースを用いたプログラミングについて

Go言語にはクラス・継承という概念が存在しないのでインターフェースを用いて実装を行います。 
http://golang.jp/go_faq#inheritance  
 

インターフェースを用いた実装のサンプルとして、 
下記は標準出力へ”hogehoge” という文字列を出力するためのプログラムです。

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Fprintf(os.Stdout, "hogehoge\n")
}

 
 
Fprintf メソッドは、第一引数に io.Writer 型を取り、 w.Write(p.buf) を呼んでいます。

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrintf(format, a)
    n, err = w.Write(p.buf)
    p.free()
    return
}

 
 
次にio.Writer のソースコードを確認すると Write というメソッドを定義したインターフェースであることがわかります。

type Writer interface {
    Write(p []byte) (n int, err error)
}

 
 
os.Stdoutを渡しているのに Fprintf が問題なく動作するのはなぜでしょうか。 ここで os.Stdout をのぞいてみます。

var (
    Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
    Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
    Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

 
 
os.Stdoutos パッケージ内の公開されている定数であり、右辺を見ると NewFile を呼んでいます。
NewFile の返り値は *File 型であるため os.Stdout には *File 型の構造体が入っていることになります。

func (f *File) Write(b []byte) (n int, err error) {
    if f == nil {
        return 0, ErrInvalid
    }
    n, e := f.write(b)
    if n < 0 {
        n = 0
    }
    if n != len(b) {
        err = io.ErrShortWrite
    }

    epipecheck(f, e)

    if e != nil {
        err = &PathError{"write", f.name, e}
    }
    return n, err
}

上記のように File 構造体はWriteメソッドを実装しているため、io.Writerのインターフェースを満たすと判定され、引数として扱うことができます。 
 

パッケージレイアウトの学び方について

書き方については、上記の例を挙げましたが、Go言語を学ぶなかでもう一つハマったのがパッケージレイアウトでした。
僕がパッケージレイアウトを学んだオススメの方法は、標準パッケージ群を読むことです。

image

上記はtimeパッケージのソースコードになりますが、ディレクトリ階層がなくフラットになっていることがわかります。
Go言語においてはこのようにパッケージ内のディレクトリを深くせず、フラットに実装するのが1つのパターンではあります。 
 

ただ、Go言語においてのパッケージレイアウトについては語られることが多くなく、書かれている記事はこちらぐらいしか見つけることができませんでした。
View story at Medium.com

こちらの記事では以下のように書かれています。

Vendoring. Generics. These are seen as big issues in the Go community but there’s another issue that’s rarely mentioned — application package layout.

ベンダリングとジェネリクスはgoコミュニティの大きな問題ですが、もう一つの大きな問題であるapplication package layoutについてはほとんど言及されることがないようです。
僕は、パッケージレイアウトについては標準パッケージを参考にするという考え方をしています。 
 

記事内で筆者はパッケージレイアウトについて提言をしてくれているのですが、
上記記事で紹介されているレイアウトが一般的になっているとは言い難い現状なのではないかと感じています。 
 

少し抽象的になってしまいますが、import cycleを避ける目的と依存が深くなるのを避けるために、フラットに実装するのが現時点では良いと考えています。
良いアプローチをお持ちの方がいれば教えていただきたいです。 
 

#これからgoを学ぶ方へ
結論が抽象的な話を書いてしまいましたが、Go言語は薄く書けるし、書いていてとても楽しい言語です!
最後に、Go言語初学者が読んでおくと良い記事を紹介します。 
 

https://talks.godoc.org/github.com/davecheney/introduction-to-go/introduction-to-go.slide#1
※Go言語の作成者であるDave cheneyさんがGo言語の構文やインストール方法について解説しています。 
 

http://talks.godoc.org/github.com/davecheney/high-performance-go-workshop/high-performance-go-workshop.slide#1
※Dave Cheneyさんがベンチマークのとり方やプロファイリングについて解説している資料です 
 

上記記事は日本語ではありませんが、サクっと探したWebの記事には書いていない内容が多くオススメです。 
 

今年も残り短くなりましたが、皆様良いGolifeを!
 
そして、明日は森川さんです。お楽しみに!

  • このエントリーをはてなブックマークに追加

エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!

Recommend

【Go言語】append使い分けのススメ 〜スライスの先頭へ要素を追加するとき、中身の型は固定長?可変長?〜

Kotlinの気持ちよさ