最近はgolangがアツいらしい。ちょうどRebuild: 15: After Google Reader, DIY Blogging, The Go language (typester)でtypesterさんがgolangについて触れていたのを聞いて、試しに触っていたがなかなか便利であることがわかった。
golangの印象としては
- go get、go runにgo build、そしてgo testが便利
- go fmtのようなコード整形ツールがついてくるのは嬉しい
- (ただ、インデントがハードタブなのはちょっと時代遅れな気がする)
- 標準packageが充実しているのが頼もしい
- golangのマスコットキャラクターであるGopherがかわいい
- (Gopher人形欲しい)
といった感じ。
←かわいい
golangの入門ということで、skkservを実装してみる。skkservというのは、ほとんどの方が使っているであろうSKK(日本語入力システム)の辞書サーバのことである。シンプルなプロトコルなので実装は容易であるため、題材として良さそうだ。
以下メモ書き。
GitHub - akiym/go-skkserv: Lightweight skkserv implementation for golang
packageを作成
How to Write Go Code - The Go Programming Languageを参考にして、packageを作成してみる。ここで注意したいのは、$GOPATH以下にpackageを作成するということ。
% mkdir -p $GOPATH/src/github.com/akiym/go-skkserv
とりあえずざっくりとskkserv.goに処理を書いていく。
package skkserv import ( "bufio" "log" "net" ) const SkkServVersion = "0.0.1" type Handler interface { Request(text string) ([]string, error) } type SKKServ struct { Port string Handler Handler } func NewServer(port string, handler Handler) *SKKServ { server := &SKKServ{ Port: port, Handler: handler, } return server } func (s *SKKServ) Run() { ln, err := net.Listen("tcp", s.Port) if err != nil { log.Fatal(err) } for { conn, err := ln.Accept() if err != nil { continue } go s.handleConnection(conn) } } func (s *SKKServ) handleConnection(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { c, err := reader.ReadByte() if err != nil { return } switch c { case '0': return // end of connection case '1': buf, err := reader.ReadBytes(' ') if err != nil { return } s.serverRequest(conn, buf) case '2': s.serverVersion(conn) case '3': s.serverHost(conn) } } }
型、メソッド
golangはどのような言語なのかを知るために、まずは型とメソッドについて調べてみる。golangは型に対してメソッドが生やせる。そしてメソッド名を大文字で始めることで、packageの外から呼び出せるようになる(エクスポートされる)。基本は以上。
基本的な形:
type SKKServ struct { Port string Handler Handler } func NewServer(port string, handler Handler) *SKKServ { server := &SKKServ{ Port: port, Handler: handler, } return server }
これだけでNewServerというメソッドを定義することができる。
別にメソッドを生やすことができるのはstructだけではなく、「型に対して」である。したがってstringに対しても同じようなことができる。
package main import ( "fmt" ) type MyString string func (m MyString) Censor() string { s := make([]byte, len(m)) for i := 0; i < len(m); i++ { switch c := m[i]; c { case 'u': s[i] = '*' default: s[i] = c } } return string(s) } func main() { var foo MyString = "fuck" fmt.Println(foo.Censor()) }
ここで注意したいのはstringそのものに対して、メソッドを生やすことはできないということ。実際にやってみても
cannot define new methods on non-local type string
と言われてしまう。
エラーハンドリング、defer
golangには一般的なプログラミング言語でおなじみの「例外」がないらしい。代わりに、戻り値としてerrorを返すことでエラーハンドリングさせる形式になっている。
(本質はpanic、recoverという仕組みだが、ここでは触れない)
例えば、このようにエラーハンドリングができる。
c, err := reader.ReadByte() if err != nil { return }
しかし、ここで気にしたいのはファイルディスクリプタをクローズ必要があるということだ。errorが返ってきたときにいちいちクローズするのは大変だ。
c, err := reader.ReadByte() if err != nil { conn.Close() return } // ... if err != nil { conn.Close() return } // ...
なんて書いていたら日が暮れてしまう。try-catch-finallyであるところのfinallyの処理を書きたい。そこでdeferという仕組みが用意されている。deferを利用することで、returnする直前に特定の処理を実行することができる。
defer conn.Close()
あらかじめdeferでクローズする処理を書いておけば、あとは気にする必要はない。NO MORE 悩み無用だ。
golangのエラー処理はほかの言語に慣れている身としては扱いにくいように思えたが、もろもろの処理は割と書きやすい気がする(少し貧弱かもしれないけど)。
参考: Defer, Panic, and Recover - The Go Blog
interface
今回の場合、リクエストを処理するhandlerを定義できるような設計にしたい。そこでinterfaceという仕組みを利用する。
このように書いておくことで、Request()というメソッドが実装されていることを明示することができる。
type Handler interface { Request(text string) ([]string, error) }
これがまた面白くて、外部からどう利用するのかというと:
type GoogleIMESKK struct{} func (s *GoogleIMESKK) Request(text string) ([]string, error) { words, err := Transliterate(text) if err != nil { return nil, err } return words, nil } func main() { var server = skkserv.NewServer(":55100", &GoogleIMESKK{}) server.Run() }
skkserv.NewServer()はHandlerを受け取るようにしていたので、そこにRequest()を実装した型をつっこむだけ。
これはinterfaceのほんの一部でしかなく、詳しいことはGo の interface 設計 - Block Rockin’ Codesに書いてある。
interface型
interface型はどんな型でも受け取れる。interface型を利用する具体例としては、JSONのパースがある:
dec := json.NewDecoder(resp.Body) var w [][]interface{} if err := dec.Decode(&w); err != nil { return nil, err } for _, v := range w[0][1].([]interface{}) { word := v.(string) result, ok := enc.ConvertStringOK(word) if ok { words = append(words, result) } }
interface型でアサーションして、stringに当て直すあたりがよくできている。
テストを書く
これまでskkserv.goを書いていたが、テストはskkserv_test.goに書いていく。見ればわかると思うが*_test.go
という名前のファイルがテストコードになる。あとはimport "testing"
して、"Test"で始まるメソッドを定義しておけば、go testでテストを走らせることができる。
func TestRequest(t *testing.T) { server := NewServer(":55100", &TestHandler{}) go server.Run() // ...