emahiro/b.log

Drastically Repeat Yourself !!!!

os.Openするときの相対パスの書き方

goで os.Open(filename string) を実行するときに読み取りファイルを特定のディレクトリにまとめておいたときに os.Open で指定する相対パスがどう決まるかのメモです。

答えはmain.goからの相対パスになります。

例えば以下のようなディレクトリ構成のとき

- src
  - main.go
  - conf.json

conf.jsonを読み込むためには

f, err := os.Open("./conf.json")

となります。

次に以下のようなディレクトリ構成を考えます。

- Project
  - conf
    - conf.json
  - src
    - project
      - main.go

この場合は

f, err := os.Open("../../conf/conf.json")

となります。

main.goから見た相対パスだということを忘れていたので備忘録のために書きました。

[追記]

答えはmain.goからの相対パスになります。

ここが少し違っていてこれは実行ディレクトリから見たときの相対パスになります。

- Project
  - conf
    - conf.json
  - src
    - project
      - main.go

のようなディレクトリ構造の時

# ProjectRootで実行する
$ go run src/project/main.go

とする場合はconf以下のjsonを読み込もうと思ったら os.Open(./conf/conf.json) になります。
一方で

# projectディレクトリで実行する
$ go run main.go

と実行する場合は本エントリで書いたような相対パスになります。

どこで実行するかで書き方を変更する必要があります。

選んだ理由よりも「選ばなかった」理由を知りたいなという話

現在働いているチームの目指すべき姿の一つに、答えではなく「観点」を理解する ことで次回以降一人でその答えに辿り着けるようにする。というものがあります。 自分はこの言葉がすごく好きではあるのですが、答えにたどり着く観点の他に、その答えの他に考えていたいくつかの「答え」候補を選ばなかった理由も合わせて知ることが、考える力と判断する力を養う一つのアプローチになるのではないかと考えています。

選ばなかった理由を知ることの例えとして、技術選定のプロセスを考えます。
このプロセスでは 特定の技術を採用する というのが最終的なゴールになります。 特定の技術を採用するということは、候補として上がっていた他の技術を採用しなかった ということになるわけで、その技術たちを どうして採用しなかったのか ということの方が、採用した経緯を知るより学べる事が多いと感じています。

もう一つの例えとしてコードを書くことを考えて見ます。

「コードを書く瞬間の思考」にアドバイスを貰える

at-grandpa.hatenablog.jp

書かなかったコードや、なぜそれを残さなかったのかにも学びがある

コーディングにおいてはもっとわかりやすいですが、「書かなかった理由 = 選ばなかった理由」であり、できる人のコードの書く瞬間が一番勉強になります。
現に自分であれば書いたであろう1行を書かないわけですから、その意図や理由は非常に勉強になります。

技術選定にしろ、コードを書くことにろ、結局は「課題解決」の手段として捉えればできる人の意思決定を真似るには できる人が選ばないことを自分も選ばなければいい ということに落ち着くのではないかと考えています。

ある特定の問題解決を使用する際に、いくつかソリューションの候補はあげますが、いざ決める時にその人の「思考」が詰まっています。
その思考のことをノウハウというのではないかと考えてるようになりました。

何かの意思決定をすることは することを決める ことですが、これは しないことを決める と同義です。
しかしながら、することを決める ノウハウは様々な場で知見が共有される一方で、しないことを決める ノウハウは語られることは少なく、そもそも語られなかったりして、「なぜ選ばなかったのか?」「なぜしなかったのか?」ということはあまり知見として広まっていないように感じています。

意思決定の瞬間の思考を深掘りするときに、どうして選ばなかったのかをしっかり説明できるようになりたいし、意思決定する際には「しない」理由にも目を向けると、自身の価値判断の手数が増えていき、武器が増えていくように感じます。

たくさんの選ばない理由に触れ、吸収し続けることの大事さを最近感じる出来事があったので、備忘録としてまとめて見ました。

おわり

depでginを入れる

やったこと

depをつかってginをinstallして動かすまで。

depのinstall方法

以下を見て下さい。

ema-hiro.hatenablog.com

depでginを入れる

depでginをinstallします。

# projectのROOTにいるとする。
$ cd ./src/{project_name}
$ dep init
$ dep ensure -add github.com/gin-gonic/gin

終わり...

のはずだった。

しかし、goファイルがないよ!!! というエラーが出る事が分かったので、src/{project_name} 直下に main.go を置いてpackage mainを書いて再度 dep ensure -add をすることでginをDLする事ができます。

go ✕ ajaxを書いてみた

サマリ

goで簡易的なajax通信するアプリを作ったのでそのメモ

構成

環境

ディレクトリ構成

- src
  - app
    - main.go
    - handler
      - handler.go
    - render
      - render.go
- templates
  - index.tmpl

sampleコード

main.go

var port = "8080"
func main() {
    router := mux.NewRouter()
    router.HandleFunc("/", handler.Top).Methods("GET")
    {
        router.HandleFunc("/sample", handler.SamplePost).Methods("POST")
    }
    if err := http.ListenAndServe(fmt.Sprintf(":%s", port), router); err != nil {
        log.Fatalf("err: %v", err)
    }
}

handler/handler.go

func SamplePost(w http.ResponseWriter, r *http.Request) {
    // postのリクエストを処理する
  client := http.Client{}
  req, err := http.NewRequest("POST", "http://sample.com/create", nil)
    if err != nil {
        log.Fatalf("build request error. err: %v", err)
    }
    // form をparseする
    if err := r.ParseForm(); err != nil {
        log.Fatalf("parse form error. err: %v", err)
    }

    // 何かしらrequestのbodyにparseしたURLのパラメータを入れ込む処理か何かが入る
  /*
    何かしらの処理
  */
  
    resp, err := client.Do(req)
    if resp.Status != "200 OK" {
        log.Fatalf("http request failed. code: %v", resp.Status)
    }
  json.NewEncoder(w).Encode(&resp)
}

index.tmpl のjsの部分

var url = $("form").attr("action");
var p = $("form").serialize()
$.ajax({
  url: url,
  type: "post",
  data: p,
}).done(function (data) {
  res = JSON.parse(data);
  // responseで返ってきたjsonを◯◯する
}).fail(function (err) {
  console.log(err)
});

ハマったところ

requestはそのままparseする

簡易的なajaxなので今回はjqueryを使ってさらっと書いてますが、goでPOSTリクエストを受けて、serializedされたパラメータを url.Values 型として扱うためには、ポインタとして受け渡される http.Request をそのままparseします。

// form をparseする
    if err := r.ParseForm(); err != nil {
        log.Fatalf("parse form error. err: %v", err)
    }

http.Request の持つ関数の中に PostFrom というのがありますが、これは新しく空の url.Values を作るだけで、フロントからリクエストされたrequestをparseしてくれるわけではないです。

直感的な名前がついていたために、間違って使っていて、どうしてもrequestを一度でparseできないと悩んでしまっていました。

まとめ

validationとか一切考えない超簡易的なajaxを書いてみましたが、標準のライブラリしか使っていないのに、案外簡単に書けました。

GoLandの設定をremoteで管理する

Golandの設定をremoteで管理したかったので、その設定方法をメモとして書いておきます。

背景

PC変えたりすると使っていたPCの設定が全て初期化されて1から作り直すのめんどくさいです。
PCやeditorくらいであればもしかしたら、設定ファイルをgithub等にあげて clone してくれば設定完了みたいなことはしていましたが、jetbrains系のIDEの設定までremoteで管理していなかったので、この機会にGoLandを使ってIDEのremoteめsettingをsyncの方法を記載します。

ツール

  • Settings Repository (Browse Jetbrains Plugin)
  • GithubのAccessToken (Settings -> Developer Applications -> Personal access token)

の2点を利用します。

手順

Settings Repository をDLしてきます。
Plugin -> Browse Jetbrains Plugin から Settings Repository をinstallします。

f:id:ema_hiro:20171125031928p:plain

次にGithubのAccessTokenを取得します。
個人のGithubのアカウントを作成しておき
* Settings -> Developer applications -> Personal access token に遷移してGoLand用にアクセストークンを取得します。

GoLandに戻ってきたら Settings Repository を起動します。
GithubリポジトリのURLとaccess token の入力を求められるので、上記手順で取得したgithubaccess tokenを入力します。

適当なプロジェクト開いて
「File」 -> 「Settings Repository」 に遷移します。

f:id:ema_hiro:20171125032249p:plain

この時に以下のようなwindowが表示されるので Override local でローカルを上書きします。

f:id:ema_hiro:20171125032420p:plain

※ どうも一度目は Override local をしなければならないみたいです。

あとはaccess tokenによってremoteで接続すべきGithubのレポジトリも繋がっているので、IDEを終了したタイミングでgithub上に作成したremoteのリポジトリと設定がsyncされます。
これで他端末でも同じ設定が使えます。

refs

Sharing Your IDE Settings - Help | IntelliJ IDEA

qiita.com

QiitaのAPIで遊ぶ

サマリ

APIで遊びながらgoの学習をするシリーズ第二弾で、Qiitaで記事を検索するクライアント goota をgoで書きました。

コードはこちら

github.com

demo

f:id:ema_hiro:20171124023348g:plain

ざっくり仕様

Requirement

  • tagを指定できる。
  • tagはカンマ区切りでOR条件で検索出来る。
  • ストック数が100以上の記事にする。
  • 簡易的なAjaxを使ったSPAとする

はまったところ

Qiitaの仕様が変わって、従来のStocksがQIitaでは「いいね」を指していたので、最初クエリを組み立てる時に likes_count を設定してやろうとしても一件も記事が返ってこなくて困ってた。

refs

qiita API Document

qiita.com

「Qiita APIで投稿一覧を取得するときに、検索クエリをORでつなぐ時の注意点」

qiita.com

githubのSearchAPIで遊んだ話

githubのsearchAPIを簡単にラップしたGUI作りました。

f:id:ema_hiro:20171119031801g:plain

久しぶりにjqueryとか触ったらすごい懐かしい匂いがして色々つまりました。
request処理とかしててハマったところがあるので別でエントリでまとめようと思います。

コードは以下 github.com

refs: はまったところは以下でまとめてみた

ema-hiro.hatenablog.com

Goでファイル読み込みを調べた話

サマリ

  • io.Readerの読み込みついて調べた
  • io.ReaderをWrapして文字列置換
  • io.Readerが一括読み込みでなくstream的な動作で順次読み込みされている

io.Readerについて

refs: io - The Go Programming Language

io.ReaderはデフォルトのReadのWrap。
ioパッケージのReaderはinterfaceとしてReadが設定されている。

type Reader interface {
    Read(p []byte) (n int, err error)
}

文字列置換を実装する

文字列置換を実装する前にgoにおけるinterface埋め込みを使った抽象化で文字列置換を実装します。

interface埋め込みによる抽象化

Readerが標準のReadをinterfaceとして持っているので、このReaderを埋め込むことでReadメソッドに独自の処理を加えてWrapします。

※ goは同一interfaceを定義したメソッドをstructに定義することで実装の詳細をstructに生やしたメソッドに移譲することができる。

cf. io.Reader

type Reader interface {
    Read(p []byte) (n int, err error)
}

goによるinterfaceの埋め込み

type WrapRead interface {
  Read([]byte)(int, error)
}

type WrapReader struct {
   wrapper WrapRead
}

func (w *WrapReader) Read(p []byte) (int, error){
  // WrapReaderでのReadの実装の詳細を記載
} 

以下と書いても同義
※ io.Readerはデフォルトの Read([]byte) (int, error) interfaceのラップなのでわざわざinterfaceで定義し直すのは冗長。

type WrapReader struct {
  reader io.Reader
}

func (w *WrapReader) Read(p []byte) (int, error){
  // WrapReaderでのReadの実装の詳細を記載
}

文字列置換メソッドを実装する

type RewriteWriter struct {
    reader io.Reader
}

func (r *RewriteWriter) Read(p []byte) (int, error) {
    buf := make([]byte, len(p))
    n, err := r.reader.Read(buf) 
    if err != nil && err != io.EOF {
        return n, err
    }

    return copy(p, bytes.Replace(buf, []byte("0"), []byte("1"), -1)), nil
}

デフォルトのReaderを RewriteWriter のstructにreaderという変数で扱えるように埋め込んで置くことで、 RewriteWriter に生やしたReadメソッドでデフォルトのReadに実装の詳細を追加することができる。
置換するので、replaceした内容をもともとのbyteにコピーしています。

io.Readerの動作を確認する。

読み込みを確認するために以下のようなコードを書きました。

handler/main.go

func Top(w http.ResponseWriter, r *http.Request) {
    res, err := http.Get(fmt.Sprintf("http://%v:%v/data", host, port))
    if err != nil {
        fmt.Printf("request get error. err: %v", err)
    }
    body := res.Body
    defer body.Close()
  io.Copy(w, &RewriteWriter{body})
}

func Data(w http.ResponseWriter, r *http.Request) {
    var str string
    for i := 0; i < 10000; i++ {
        str = str + fmt.Sprintf("%v\n", "000")
    }

    w.Write([]byte(str))
}

10000行の文字列を置換するというものです。

動かしてみます。

$ go run main.go
# serverが起動

$ curl http://localhost:8080
111
111
111
111
# 以下同様

10000行程度ならすぐに完了してしまいますが、Readメソッドは読み込むデータを一時的に保存しておくbufferの頭から順々に読み込んでいくような動作をしているようです。
全てのデータを終端記号まで読み込んでから全データを処理するわけではないみたいです。
※ 全てのデータを終端記号まで一括で読み込む場合でから使う場合には ioutil.ReadAllメソッドを使います。

このあたりは 「Goならわかるシステムプログラミング」 の io.Readerの章を確認しながら理解しました。

今回書いたコードは以下

github.com

GAE/GOのversionを上げたらContextが違ってコードが動かなくなってた話

有名な話です。が、いざ自分が体験したので備忘録としてまとめます。

github.com

上記で上げられている netcontextとcontext周りで死ぬ というのに引っかかりました。

課題

go1.6上で以下のようなリクエスト比較するコードを書いてました。 ※ コードはあくまでサンプルです。

url := "https://www.gooogle.com"
values := url.Values{}
req_1, _ := http.NewRequest("POST", url, strings.NewReader(values.Encode()))
req_2, _ := http.NewRequest("POST", url, strings.NewReader(values.Encode()))

if reflect.DeepEqual(req_1, req_2) {
  // requestが同値である。
} else {
  // requestは同値でない
}

req_1req_2 をdeepEqualで比較して、同値性を判別したいという意図でしたが、このコードはgo1.6だと同値だと判定されますが、go1.8だと同値だと判定されません。

理由は最初に書いた netcontextとcontext周りで死ぬ が原因だと思われます。

goのx/net/context パッケージが標準の context に入ったというのは有名です。
refs: Go 1.7 Release Notes - The Go Programming Language

net/context も標準のcontextとして扱われるようになったので、req_1req_2 は別々のリクエスト、すなわち異なるcontextを持っていると判定され、それを reflect.DeepEqual にかけた場合、標準の context 違いがあるので、同値判定されません。

go1.7のcontextについては以下のブログがすごく勉強になりました。

Go1.7のcontextパッケージ | SOTA

同値性の比較

では、requestの比較をしたい場合はどうすればいいかというと、 httputil.DumpRequest を使います。 refs: httputil - The Go Programming Language

url := "https://www.gooogle.com"
values := url.Values{}
req_1, _ := http.NewRequest("POST", url, strings.NewReader(values.Encode()))
req_2, _ := http.NewRequest("POST", url, strings.NewReader(values.Encode()))

// dumpは[]byte型
dump_1, _ := httputil.DumpRequest(req_1, true)
dump_2, _ := httputil.DumpRequest(req_2, true) 

// DeepEqual
if reflect.DeepEqual(dump_1, dump_2){
  // requestの同値判定
}

// bytes.Equal
if bytes.Equal(dump_1, dump_2) {
  // requestの同値判定
}

// stringに変換して文字列比較
if string(dump_1) == string(dump_2) {
  // requestの同値判定
}

DumpRequest することで context ではなくリクエストそのものを比較出来ます。
比較方法は、以前同様 DeepEqual を使ってもいいですし、 []byte 型に変換されるので、それに合わせて bytes packageを使ってもいいですし、文字列に変換して文字列一致をしても同値性を取ることが出来ると思います。

time.IsZero()の挙動でハマった話

サマリ

  • goのtimeパッケージの IsZero() はUnixTime = 0ではない
  • GAEのdatasotreのdefaultの時刻で IsZero() を使ってもtrueを返さない

IsZero()メソッドについて

refs: time package - time - pkg.go.dev

IsZero reports whether t represents the zero time instant, January 1, year 1, 00:00:00 UTC. 

IsZero()は 01-01 00:00:00 UTC の時にtrueを返します。
ここで注意するべきはtrueを返す時刻はunixtimeのスタート時刻 1970-01-01 00:00:00 +0000 UTC を指し示すわけではないということでです。

実際の挙動を見てみます。

def := time.Time{}
fmt.Printf("%v\n", def)
fmt.Printf("%v\n", def.IsZero())
// output
// 0001-01-01 00:00:00 +0000 UTC
// true

time.Time{} は何もない時刻をinstance化する、すなわちtimeパッケージにおける標準時刻をinstance化することですが、これの結果は 0001-01-01 00:00:00 +0000 UTC という時刻がinstance化され、IsZero() はこの時刻のときのみtrueを返します。

udef, _ := time.Parse("2006-01-02 15:04:05 -0700", "1970-01-01 00:00:00 +0000")
fmt.Printf("%v\n", udef)
fmt.Printf("%v\n", udef.IsZero())
// output
// 1970-01-01 00:00:00 +0000 +0000
// false

一方でコンピューターにおける時刻ゼロとはunixtimeの1番最初だと想起できるので、unixtimeのスタートした時刻に対して IsZero() をcallすると、unixtimeのstartの時刻にもかかわらず false を返します。

timeパッケージの中身を見てみると

type Time struct {
    // sec gives the number of seconds elapsed since
    // January 1, year 1 00:00:00 UTC.
    sec int64

    // nsec specifies a non-negative nanosecond
    // offset within the second named by Seconds.
    // It must be in the range [0, 999999999].
    nsec int32

    // loc specifies the Location that should be used to
    // determine the minute, hour, month, day, and year
    // that correspond to this Time.
    // Only the zero Time has a nil Location.
    // In that case it is interpreted to mean UTC.
    loc *Location
}

とあり、そもそもの sec = 0 の時には January 1, year 1 00:00:00 UTC. が初期値設定されています。
IsZero() については

// IsZero reports whether t represents the zero time instant,
// January 1, year 1, 00:00:00 UTC.
func (t Time) IsZero() bool {
    return t.sec == 0 && t.nsec == 0
}

とあるので、そもそもunixtime=0を返さないのはgoのtimeパッケージの仕様のようです。

GAE上での挙動について

さて、ここで困ったのがGAEでDatastore上に time.Time 型で標準時刻をinstance化した時のことです。

以下のようなstructを考えてみます。

type App struct {
  ID         int       `datastore: "ID"         json: "id"`
  CreatedAt  time.Time `datastore: "createdAt"  json: "created_at"`
  UpdatedAt  time.Time `datastore: "updatedAt"  json: "updated_at"`
  ReleasedAt time.Time `datastore: "ReleasedAt" json: "released_at"`
}

この App Entityがcreateされた時に CreatedAtUpdatedAt はそれぞれcreateされた時刻が入りますが リリースされたわけではないので、 ReleasedAt には何も入りません。
つまり、 ReleasedAt のfieldには time.Time{} が入ってくることを期待してました。
しかし実際には 1970-01-01 00:00:00 +0900 JST という日本標準時のでのunixtime = 0の状態が入っていました。

つまり、 ReleasedAt に一度しか値を入れたくない、みたいな要件があったときに

if !app.ReleasedAt.UTC().IsZero() {
    // ReleasedAtにすでに値が入っている時
} else {
    // ReleasedAtに初回に値が入る    
}

上記のような条件分岐を考慮した場合、どんなときでも else 以下に入ってしまいます。
理由は上記で述べた通り、 unixtime のスタートはtimeパッケージで IsZero 判別するときには false を返してしまうからです。

ではどうすればいいかというと、実は unixtimeの最初の状態を作り出した time オブジェクトのunixtimeを取ると 0 になります。

udef, _ := time.Parse("2006-01-02 15:04:05 -0700", "1970-01-01 00:00:00 +0000")
fmt.Printf("%v\n", udef)
fmt.Printf("%v\n", udef.UTC().Unix())
// output
// 1970-01-01 00:00:00 +0000 +0000
// 0

これを利用して上記の条件分岐を以下のように書き換えます。

if app.ReleasedAt.UTC().Unix() != 0 {
    // ReleasedAtにすでに値が入っている時
} else {
    // ReleasedAtに初回に値が入る    
}

app.ReleasedAt.UTC().Unix() とすることで、すでに ReleasedAt に値が入ってきている場合は、 Unix() でunixtimeに変換した時に 0以外 が入ってくる事になります。

まとめ

timeパッケージにおける IsZero() の挙動とGAEのDatastoreでデフォルトの時刻を unixtime = 0 判定を同様に考えてきて、かなりハマりました。
IsZero() がunixtimeのstart時刻を示さないのはどうにも納得が行きませんが、timeパッケージ的にはどうしようもなさそうなので、注意しようと思いました。

追記

このエントリを書いてから 4年以上経って言及されるとは思ってなかったですが、 いい感じに答えたが書いてあって参考にしたいなと思いました。

【続】FWに頼らないオレオレroutingを実装する

前回書いた記事の中でオレオレroutingを実装する際に標準の net/http パッケージだけだと足りないと書いてましたがこれ、間違いでした。

ema-hiro.hatenablog.com

標準の net/http パッケージだけでオレオレroutingを実装する方法は以下

main.go

package main

import (
    "gothub/handler"

    "fmt"
    "net/http"

    "github.com/labstack/gommon/log"
)

const port = "8080"

func main() {
    router := http.NewServeMux()
    router.HandleFunc("/", handler.Top)
    if err := http.ListenAndServe(fmt.Sprintf(":%s", port), router); err != nil {
        log.Fatal("err: %v", err)
    }
}

handler/handler.go

pakage handler

import (
    "net/http"
)

func Top(w http.ResponseWriter, r *httpRequest){
    // serverの処理
}

router := http.NewServeMux()HTTP Request multiplexerインスタンス化し、routerとして扱う。
http requestをhandleしたいroutingのメソッドには http.ResponseWriterhttp.Request を引数に与える。

routingのライブラリを使うことなく、標準のHTTPパッケージだけでもやりたかった、超薄いAPIを作るということは可能でした。

(golangのhttpパッケージすげぇ強力だなぁ(小並感))

GogLandでtmplファイルをhtmlのシンタックス対象に加える

いつからはわからないですが、GogLand EAPをアップデートしたらtmplファイルがhtmlのシンタックス対象から外れてて、htmlを開いてもxmlと判別されてエラーがうるさくなってしまったので、カスタムファイルとしてtmplファイルのときは、htmlのシンタックスを追加する方法を記載します。

手順

Preferences -> Editor -> FileType でhtmlを選択し、シンタックス適用の登録ファイルにtmplを追加する。

画像は以下の場所 (+) マークを押下して、 tmpl ファイルを追加します。

f:id:ema_hiro:20171107012953p:plain

【go】FWに頼らないオレオレroutingを実装する

goでFWに頼らず、net/http だけで簡単なWeb Serverを立てたいと思ったので作ってみた。

routing設定

まず躓いたのはroutingをどうするかということ。
net/httpパッケージを使用する場合、全てmain.goにroutingを書いてしまうことになりますが、FWの構造に則ってここはhttpリクエストをやりとりする箇所はちゃんとhandlerとしてディレクトリを用意したいと考えました。

(ginやechoを使っていたときはFW側で用意されたroutingをよしなに使ってましたが、いざ自分でroutingをかんがえると???ってなるものだなーと感じるなど(笑))

routingを設定する場合 www.gorillatoolkit.org github.com

上記のパッケージを使うのが一般的らしいということはわかっので、今回は gorilla/mux を使用。

gorilla/muxのinstall

$ dep ensure -add github.com/gorilla/mux
$ dep ensure

ディレクトリ構成

$GOPATH
  - src
    - project_root
      - main.go
      - handler/
        - main.go // ここに各種handlerを書いていく

実装

main.go

package main

import (
    "gothub/handler"

    "fmt"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/labstack/gommon/log"
)

const port = "8080"

func main() {
    router := mux.NewRouter()
    router.HandleFunc("/", handler.Top)
    router.Handle("/", router)
    if err := http.ListenAndServe(fmt.Sprintf(":%s", port), router); err != nil {
        log.Fatal("err: %v", err)
    }
}

handler/main.go

package handler

import (
    "net/http"

    "fmt"

    "github.com/labstack/gommon/log"
)

func Top(w http.ResponseWriter, r *http.Request) {
    log.Debugf("request url: %s", r.URL)
    w.WriteHeader(http.StatusOK)
    fmt.Printf("%v", r.Body)
}

handlerに設定するメソッドには http.Resposewriterhttp.Request を引数に持たせることでhandlerとして登録してrequestを処理できるようになる。

動作させる

$ go run main.go

// 別shellで
$ curl -i http://localhost:8080
HTTP/1.1 200 OK
Date: Sat, 04 Nov 2017 09:27:35 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8

ちゃんとroutingが通っていることを確認

まとめ

goを使って簡易的なapiクライアントを作りたかったのですが、わざわざFWを使うほどでもないので自分で超薄いFWを作れたらなくらいのノリで書いてみましたら、結構簡単に作ることが出来ました。
FWを使っててのツラミとかはこのあたりのスライドにも書いてあるので標準の net/http パッケージを基準にapiサーバーとか構築するのに便利だと思いました。

www.slideshare.net

depを使ってみる

golangのオフィシャル謹製パッケージマネージャ「dep

github.com

個人プロジェクトではglideから乗り換えました。

install

公式の手順に則っておけば問題ないです。

Macでは brew でインストール出来ます。

$ brew install dep

ディレクトリ構成

- $GOPATH
  - src/
    - project/

dep$GOPATH/src 以下にプロジェクトのソースを管理するディレクトリを作成しておかないと動作しません。

使い方

$ cd $GOPATH/src/project
$ dep init
// Gopkg.tomlとGopkg.lockが作成されます。

// add
$ dep ensure -add package-src-URL
$ dep ensure

help

$  dep help
dep is a tool for managing dependencies for Go projects

Usage: dep <command>

Commands:

  init     Initialize a new project with manifest and lock files
  status   Report the status of the project's dependencies
  ensure   Ensure a dependency is safely vendored in the project
  prune    Prune the vendor tree of unused packages
  version  Show the dep version information

Examples:
  dep init                               set up a new project
  dep ensure                             install the project's dependencies
  dep ensure -update                     update the locked versions of all dependencies
  dep ensure -add github.com/pkg/errors  add a dependency to the project

Use "dep help [command]" for more information about a command.

//ensureのhelpの場合
$ dep help ensure
Usage: dep ensure [-update | -add] [-no-vendor | -vendor-only] [-dry-run] [<spec>...]

Project spec:

  <import path>[:alt source URL][@<constraint>]


Ensure gets a project into a complete, reproducible, and likely compilable state:

  * All non-stdlib imports are fulfilled
  * All rules in Gopkg.toml are respected
  * Gopkg.lock records precise versions for all dependencies
  * vendor/ is populated according to Gopkg.lock

Ensure has fast techniques to determine that some of these steps may be
unnecessary. If that determination is made, ensure may skip some steps. Flags
may be passed to bypass these checks; -vendor-only will allow an out-of-date
Gopkg.lock to populate vendor/, and -no-vendor will update Gopkg.lock (if
needed), but never touch vendor/.

The effect of passing project spec arguments varies slightly depending on the
combination of flags that are passed.


Examples:

  dep ensure                                 Populate vendor from existing Gopkg.toml and Gopkg.lock
  dep ensure -add github.com/pkg/foo         Introduce a named dependency at its newest version
  dep ensure -add github.com/pkg/foo@^1.0.1  Introduce a named dependency with a particular constraint

For more detailed usage examples, see dep ensure -examples.

Flags:

  -add          add new dependencies, or populate Gopkg.toml with constraints for existing dependencies (default: false)
  -dry-run      only report the changes that would be made (default: false)
  -examples     print detailed usage examples (default: false)
  -no-vendor    update Gopkg.lock (if needed), but do not update vendor/ (default: false)
  -update       update the named dependencies (or all, if none are named) in Gopkg.lock to the latest allowed by Gopkg.toml (default: false)
  -v            enable verbose logging (default: false)
  -vendor-only  populate vendor/ from Gopkg.lock without updating it first (default: false)

こんな感じ。オフィシャル謹製だし、今後はこちらを使っていこう。 refs: mattn.kaoriya.net

goのsortで複数条件でのsortを実現する上で考えたこと

サマリ

特定のModelに対して複数条件でsortしたい場合の実装方法についての考察

実装方法

  • sortしたいfield毎にモデルにsortのinterfaceを実装をする - ➀
  • embedで親のsortパッケージを継承する(sort条件を上書きする) - ➁

➀のとき

sortしたいStructのfield毎にLen,Swap,Lessのinterfaceを実装する。
古典的な実装方法で基本Structのfieldごとにsortで必要となるinterface3つを実装すればよい。

type Person struct{
  Name
  Age
}

type PeopleByName []Person

func (p PeopleByName)Len int{
  return len(p)
}

func (p PeopleByName)Swap(i, j int) {
  p[i], p[j] = p[j], p[i]
}

// Nameでsortする場合
func (p PeopleByName)Less (i, j int) {
  return p[i].Name < p[j].Name
}

func (p People)SortByName(){
  sort.Sort(PeopleByName(p))
}

type PeopleByAge []Person

func (p PeopleByAge)Len int{
  return len(p)
}

func (p PeopleByAge)Swap(i, j int) {
  p[i], p[j] = p[j], p[i]
}

// Ageでsortする場合
func (p PeopleByAge)Less (i, j int)  bool {
  return p[i].Age < p[j].Age
}

func (p People) SortByAge (){
  sort.Sort(PeopleByAge(p))
}

func main (){
  p := []Person{
  Person{
    Name: "Taro",
    Age: 1,
  },
  Person{
    Name: "Jiro",
    Age: 2,
  },
  Person{
    Name: "Saburo",
    Age: 3
  }
  
  // 名前でSort
  p.SortByName() // Jiro, Saburo, Taro
  // 年齢でsort
  p.SortByAge() // 1, 2, 3

}

古典的でわかりやすい反面、使わないinterfaceも定義してしまい、コード全体の見通しが悪い。

②の場合

ベースとなるStructにつき一度sortのinterface3つを実装する。
複数条件でsortする場合は、ベースとなるStructを埋め込んだ別のStructを作って、その新しく条件を追加するために作成したStructに必要なinterfaceのみを追加する。

ベースとなるStructを埋め込んだ、新しいStructではsortのinterfaceを全て実装しなくても、大本のStrcutでsortが実装されていれば、全て実装する必要はない。
必要となるinterfaceのみ実装すれば、sortがcallされる時にsort条件が上書きされる。
※ 継承みたいなイメージ

type Person struct{
  Name
  Age
}

type People []Person

func (p People)Len int{
  return len(p)
}

func (p People)Swap(i, j int) {
  p[i], p[j] = p[j], p[i]
}

// Nameでsortする場合
func (p People)Less (i, j int) bool {
  return p[i].Name < p[j].Name
}

最初にベースとなるPeople StructにNameでsortするための基準となるsortのinterfaceを定義しておきます。
次にAgeでsortするために、Ageでsortする場合はNameでsortするために定義したLessを上書きします。

type PeopleByAge struct{
  People 
}

func (b PeopleByAge) Less (i, j int) bool {
  return b.People[i].Age < b.People[j].Age // ここでPeopleのLessを上書きしている。
}

func main (){
  p := []Person{
  Person{
    Name: "Taro",
    Age: 1,
  },
  Person{
    Name: "Jiro",
    Age: 2,
  },
  Person{
    Name: "Saburo",
    Age: 3
  }
  
  // 名前でSort
  sort.Sort(p)
  // AgeでSort
  sort.Sort(PeopleByAge{p})
}
type PeopleByAge struct{
  People 
}

の箇所でPeople Structを埋め込んでいるので、PeopleByAgeに新しく3つ全てのinterfaceを実装しなくても良くなります。

考察

古典的な方法はわかりやすい、かつ実装しやすいため基本➀で実装するのをベースに置きつつ、埋め込み型の方がよりsort条件を明確にでき、かつコードの記述量もすくないと思うので、使えるならStructに埋め込むパターンの方がいい気がする。

一方でsortについては➁の実装方法はベースのStructに依存することになるので、もしかしたら改修のときなどに予期せぬ影響が出る可能性がある。

refs

https://qiita.com/Jxck_/items/fb829b818aac5b5f54f7