oinume journal

Scratchpad of what I learned

golintの-min_confidenceでチェックするレベルを変更する

これはGo的にはあまりやらない方がいいと思うやつなんだけど、こういうこともできるよ、という紹介。

type Hoge struct {
    Url    string
}

みたいに書いてgolintを実行すると "struct field Url should be URL" と怒られる。しかし、golintには -min_confidence という引数を指定できる。デフォルトは0.8なので、試しにこれを1.0に引き上げてみると"struct field Url should be URL"というエラーは出なくなる。https://github.com/golang/lint/blob/master/lint.go のソースを見てみると、いろいろなチェック処理で

f.errorf(vs, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment%s or be unexported", kind, name, block)

のようにf.errorfの第2引数にConfidenceを指定しているので、該当のエラーを消したい場合は -min_confidence でそのチェック処理のConfidenceより大きい物を指定すれば良さそう。

package main

type Session struct {
    Url string
}

func (s *Session) Open() bool {
    return true
}

func main() {
    s := Session{"https://github.com/"}
    s.Open()
}

ためしにこんな感じのソースをgolintにかけるとデフォルトでは

$ golint golint.go
golint/golint.go:3:6: exported type Session should have comment or be unexported
golint/golint.go:7:1: exported method Session.Open should have comment or be unexported
golint/golint.go:4:2: struct field Url should be URL

というエラーが出るけど、-min_confidence=1.1にすると全部出なくなる。あんまりやらない方がいいことだとは思うけど、エラー出まくるからという理由でgolintをかけなくなるよりかは -min_confidence を指定してgolintする方がいいのではないかなぁと思った次第です。

The Way to Go: A Thorough Introduction to the Go Programming Language

The Way to Go: A Thorough Introduction to the Go Programming Language

Revel Frameworkでのテスト

今使っているRevel Frameworkのテストについてまとめてみた。

デモ

サンプル https://github.com/oinume/revel-sample

テストの実行(ブラウザー経由)

revel run revel-sample

http://localhost:9000/@tests にアクセス

テストの実行(CUI)

revel test revel-sample

TestSuite

type AppTest struct {
    revel.TestSuite
}

revel.TestSuiteを埋め込んで使うと、コイツが持っている各種関数が使えるようになる。

  • t.Get(PATH)
  • t.AssertOk()
  • t.AssertContentType
  • etc...
type AppTest struct {
    revel.TestSuite
}

func (t *AppTest) Before() {
    //println("Set up")
}

// Test function must start with 'Test'
func (t AppTest) TestThatIndexPageWorks() {
    t.Get("/")
    t.AssertOk() // Check HTTP status is 200
    t.AssertContentType("text/plain; charset=utf-8")
    t.AssertContains("Hello world")
}

func (t *AppTest) After() {
    //println("Tear down")
}

Before/After

  • テスト関数を実行する前/後に呼び出される。
  • テスト関数が呼び出されるたびに呼ばれるので、テストで使うデータのリセットなどを行う用途に使う。

どういうモデルで動作しているか?

TestSuite(HTTP Client)  --> Revel app (Server)

revel test を実行すると裏でサーバーが立ち上がって、TestSuiteがクライアントとしてHTTPリクエストを投げてる。

厳密には...

となっている。

よくある質問

特定のテストだけ実行したい

$ revel test revel-sample AppTest.TestThatIndexPageWorks

引数に <TestSuite名>.<Test関数名> という形式で渡せばそのテスト関数だけ実行される

テスト時のサーバー側のログをあとで確認したい

test-results/app.log にログファイルあるよ!

Mastering Concurrency in Go

Mastering Concurrency in Go

Atomで突然の死ジェネレータを作ってみた

https://atom.io/packages/sudden-death-generator

Atomのpackageを作る練習として。

実践Node.jsプログラミング (Programmer's SELECTION)

実践Node.jsプログラミング (Programmer's SELECTION)

Nginxのserver_nameに正規表現で量指定子を使うとエラー

server_name ~^(?<subdomain>[\w]{3,})\.example\.com$;

Nginxの設定ファイルで↑のような感じのserver_nameを書いて起動しようとすると

2014/08/21 23:07:02 [emerg] 17233#0: directive "server_name" is not terminated by ";" in /etc/nginx/conf.d/vhost.conf:10

というエラーが出てしまった。「えっ、server_nameの正規表現で量指定子使えないの?マジで?」とか思って調べたら単にダブルクォートで括ればいいだけだった。

server_name "~^(?<subdomain>[\w]{3,})\.example\.com$";

ドキュメント

A regular expression containing the characters “{” and “}” should be quoted

って書いてあったのでドキュメント嫁ということです。

マスタリングNginx

マスタリングNginx

Goのstructとinterface

社内でGo勉強会をやったのでその時に自分が発表した時の内容。オブジェクト指向的なことをGoでどう実現するのか、どの辺を諦めなくてはいけないのかをまとめてみた。

Goでのオブジェクト指向

Goでは基本的にオブジェクト指向プログラミングはできないと思ったほうが良い。できるのはstructを定義して関数を追加していくことだけである。

http://play.golang.org/p/7w9nbHDec1

package main

import "fmt"

type User struct {
    Id   int
    Name string
}

// 文字列化する関数
func (u *User) String() string {
    return fmt.Sprintf("%d:%s", u.Id, u.Name)
}

func main() {
    var user *User = &User{1, "oinume"}
    fmt.Println(user.String())
}

継承っぽいことを実現するには

埋め込み構造体を使えばクラスの継承っぽいことはできる。ただし、継承という仕様はないため型の互換性はない。Mixinに近い感じ。

http://play.golang.org/p/5wHW90Zvs6

package main

import (
    "fmt"
)

type User struct {
    Id int
    Name string
}

func (u *User) String() string {
    return fmt.Sprintf("%d:%s", u.Id, u.Name)
}

// 通常の埋め込み構造体
type BannedUser struct {
    User
    BannedReason string
}

func (u *BannedUser) GetBannedReason() string {
    return u.BannedReason
}

// ポインタ型の埋め込み構造体
type PBannedUser struct {
    *User
    BannedReason string
}

func (u *PBannedUser) GetBannedReason() string {
    return u.BannedReason
}

func main() {
    user := BannedUser{User{1, "oinume"}, "Too many spam"}
    fmt.Println(user.Name)

    puser := PBannedUser{&User{1, "oinume"}, "Too many spam"}
    // User.Nameが参照できる
    fmt.Println(puser.Name)
    // puser.User.Name でも参照可能
    fmt.Println(puser.User.Name)

    // Mixin: User.String()を呼び出す
    fmt.Println(puser.String())

    //var u *User = &PBannedUser{&User{1, "oinume"}, "Too many spam"}  型の互換性はない
    //--> compile error: cannot use PBannedUser literal (type *PBannedUser) as type *User in assignment
}

型の定義とキャスト

以下のようにキャストしても、呼び出されるのはReadOnlyDatabaseの関数ではなく、Databaseのものであることに注意。

http://play.golang.org/p/2U5NVaYKb_

package main

import "fmt"

// Database
type Database struct {
}
func (d *Database) NewSession() *Session {
    return &Session{}
}

// Session
type Session struct {
}
func (s *Session) Connect() string {
    return "Session.Connect()"
}

// ReadOnlyDatabase
type ReadOnlyDatabase Database
func (d *ReadOnlyDatabase) NewSession() *ReadOnlySession {
    return &ReadOnlySession{}
}

// ReadOnlySession
type ReadOnlySession Session
func (s *ReadOnlySession) Connect() string {
    return "ReadOnlySession.Connect()"
}

func main() {
    var database *Database = &Database{}
    var session *Session = database.NewSession()
    fmt.Println(session.Connect())

    var roDataBase *ReadOnlyDatabase = &ReadOnlyDatabase{}
    // ReadOnlySession -> Session型にキャスト
    var roSession *Session = (*Session)(roDataBase.NewSession())
    // ★ReadOnlySession.Connect()ではなくSession.Connect()が呼ばれる
    fmt.Println(roSession.Connect())

    // ReadOnlySession型
    var ros *ReadOnlySession = roDataBase.NewSession()
    // ★この場合はReadOnlySession.Connect()が呼ばれる
    fmt.Println(ros.Connect())
}

Javaだと下記はReadOnlySession.Connect()のメソッドが呼ばれる。

var roSession *Session = (*Session)(roDataBase.NewSession())
roSession.Connect()

http://golang.org/doc/faq#How_do_I_get_dynamic_dispatch_of_methods

The only way to have dynamically dispatched methods is through an interface. Methods on a struct or any other concrete type are always resolved statically.

interfaceとduck typing

Goでは型の継承という仕様はないが、interfaceというものがある。interfaceは単にメソッドを記述したものである。そのメソッドを実装しているstructはそのinterfaceを満たしていると言える。

package main

import "fmt"

// 「JSON化できる」インターフェース
type JSONable interface {
    JSON() string
}

type User struct {
    Id   int
    Name string
}

// JSON()メソッドを実装
func (s *User) JSON() string {
    return fmt.Sprintf(`{ "Id": %d, "Name": "%s" }`, s.Id, s.Name)
}

type AdminUser struct {
    User
    Admin bool
}

// User.JSON()をオーバーライド
func (s *AdminUser) JSON() string {
    return fmt.Sprintf(`{ "Id": %d, "Name": "%s", "Admin": %v }`, s.Id, s.Name, s.Admin)
}


func main() {
    // JSONable を実装しているのでJSONable型に代入できる
    var user JSONable = &User{1, "oinume"}
    fmt.Println(user.JSON())

    // AdminUserもJSONableを実装している
    var adminUser JSONable = &AdminUser{User{0, "admin"}, true}
    fmt.Println(adminUser.JSON())

    // ★型アサーションでadminUserがJSONableを実装しているかチェックする
    jsonable, ok := adminUser.(JSONable)
    if ok {
        // 実装してる場合
        fmt.Printf("JSON(): %s\n", jsonable.JSON())
    } else {
        // 実装してない場合
        fmt.Printf("Not JSONable\n")
    }
}

Duck Typingとは

「アヒルのように鳴くならアヒル」

Rubyとかの動的言語でよく使われる。「そのメソッドが呼び出せるのならインタフェースを満たしている」 上の例だと、UserやAdminUserはJSON()メソッドを実装しているのでJSONableを実装していると言える。

所感

Goでは継承という概念がないので、structの型の互換性を考えるよりもinterfaceベースで型を考えないとダメ。

参考資料

http://jxck.hatenablog.com/entry/20130325/1364251563 が非常に参考になりました。

An Introduction to Programming in Go

An Introduction to Programming in Go