読者です 読者をやめる 読者になる 読者になる

phperがgolang書いてて調べたこと

golang php

配列のループ

<?php

foreach ($items as $index => $item) {

}
for index, item := range items {

}

まぁ違和感はない。

package

Goji使ってサーバー書いてると、コントローラーやモデルをpackage分割したくなる。例えば以下のようなディレクトリ構造にしたい。

app.go
controllers/
    helpers/
        helper.go
    user_controller.go
    item_controller.go
models/
    user.go
    item.go

この時、packageをPHPでいうところのclassと捉えるとうまくいかない。 例えば、

package user

type User struct {
    Id   string,
    Name string,
}

と定義した場合、user.goファイルは以下に設置しないといけなくて冗長になる。

models/
    user/
        user.go

さらに、import時も以下のように冗長になる。

import(
    "github.com/hatajoe/json-server/models/user"
)

なので、packageはnamespaceと捉えると良いかもしれない。

package models

type User struct {
    Id   string,
    Name string,
}
models/
    user.go

models/item.goは、

package models

type Item struct {
    Id   string
    Name string
}

となる。importは

import (
    "github.com/hatajoe/json-server/models"
)

だけでmodels以下のstructを参照出来るようになる。

尚、同じパッケージ内ならファイルが分割されていようとも相互に参照出来る。全て1つのファイルに宣言されていると思って構わない。 これを利用して、同パッケージ間で共通して利用するルーチンはmodels/common.goみたいに分けて使うと便利。 ちなみに、同パッケージ内で同じ名前の関数やグローバル変数は宣言出来ない。(型が違えば出来ても良いと思うんだけどなー。)

func Register(uuid string) error {

}

// 以下は引数が違うけど名前が同じなので宣言出来ない。
//func Register(uuid string, name string) error {
//}

また、ネストしたディレクトリの場合は、そのファイルが所属するディレクトリの名前をpackage名とするみたい。

app/
    controllers/
        helpers/
            helper.go
package helpers

import(
    "net/http"
)

func GetUUID(r *http.Request) string {
    return r.Header.Get("UUID")
}

importする場合は、

package controllers

import(
    "github.com/hatajoe/json-server/controllers/helpers"
)

気がついたかわからないけれど、自作packageをimportする時に、github.com/hatajoe/json-server/...としてる。 実は、以下のように相対パスでもimport出来る。

package controllers

import(
    "./helpers"
)

しかしこうすると、go test時にパッケージ依存が解決出来ずテストが出来なかった。(参考 http://qiita.com/shiwork/items/a213b1af2da7f7f8ea22#整理)

$GOPATH配下に存在するプロジェクトの場合は相対指定だとダメらしい。 プロジェクトを$GOPATH以下に置くかどうかをチームで共有しておかないと$GOPATH以下にシンボリックリンクを貼るハメになる人が現れる。(逆にシンボリックリンクで解決出来るっちゃ出来る)

エラーハンドリング

1つの関数内でエラー処理をたくさんする場合、

func ComplexFunc() error {
    err := hoge()
    if err != nil { // 型推論してる
        // error
    }
 
    err = fuga()
    if err != nil { // 上で型が決定してるので型推論できない
        // error
    }
 
    v, err := foo() // vの型推論をしてる。が、errをしてるようにも見えちゃう。ややこしい。
    if err != nil {
        // error
    }
 
    log.Println(v)
 
    return nil
}

同じ変数名err型推論は同じスコープ内で一度しか出来ない。んだけど、複数の戻り値を受け取る際、片方の型が決定していなければ型推論出来る。ように見える。これは、以下のようにif文のスコープを利用するとうまく出来る。

func ComplexFunc() error {
    if err := hoge(); err != nil { // errはこのif文ブロック内がスコープとなる
        // error
    }
 
    if err := fuga(); err != nil { // 綺麗に書ける
        // error
    }
 
    v, err := foo() // 逆にここはvをif文スコープ内に入れるわけにはいかないのでこのままとなる
    if err != nil {
        // error
    }
 
    log.Println(v)
 
    return nil
}

if文のスコープ。面白い。 じゃあ複数の値を返す関数を2つ以上使ってる場合はどうするの、ってなるけど、その場合は予めvar err errorを宣言しとくとかなのかな。

抽象化

モデルには大抵プライマリキーでデータを取得する以下のような関数がある。

<?php

class User extends BaseModel
{
    protected $table = "user";
    
    public static function findById($id)
    {
        return DB::where("id", $id)->get()->first();
    }
}

これは、BaseModelに宣言すれば良いじゃない。

<?php

class BaseModel extends Eloquent
{
    protected $table;
    
    public static function findById($id)
    {
        return DB::where("id", $id)->get()->first();
    }
}

こんな感じに書けたと思う。 goだと型の都合上これが出来ない。そもそも継承もない。

僕はシンプルにテーブル構造からモデルクラスを自動生成するツールを書いて対応してる。 長らくPHPとか書いてるとなんとか共通化したくなる部分だと思う。多分方法はいくつかあるんだろうけど、あまり複雑なことはしたくなかった。 簡単に出来る方法があるなら教えて欲しいです。

困ったとき

  • godoc
    • 関数リファレンス頼れる
  • github
    • READMEを読む
    • examplesがあれば読む
    • testを読む

とにかく英語多い。それが辛い・・・!