LoginSignup
9
7

More than 5 years have passed since last update.

Go言語:私的備忘録

Last updated at Posted at 2017-01-26

Golangにて遭遇したTipsを私的にメモする場所。
ちょいちょい加筆していきます。

構造体のタグを参照する

Json生成時に利用する構造体のタグ。
このタグを自由に使うための方法です(CSV等のカラム名等に便利)。

import (
  "reflect"
)

// タグ付きで構造体を定義
type Record struct {
  Name  string `column:"name"`
  Age   int    `column:"age"`
}

var csv Record = Record{
  Name: "Hoge",
  Age: 20,
}

// タグを1つずつ取得
t := reflect.TypeOf(csv)
fmt.Println(t.Field(0).Tag.Get("column")) //=> name
fmt.Println(t.Field(1).Tag.Get("column")) //=> age

// ループで参照
for i := 0; i < t.NumField(); i++ {
  fmt.Println(t.Field(i).Tag.Get("column")) //=> name -> age
}

// 元のフィールド名はこちら
for i := 0; i < t.NumField(); i++ {
  fmt.Println(t.Field(i).Name) //=> NAME -> Age
}

構造体のフィールド名に変数の値を用いて代入する

構造体のフィールド名に対し、変数の値をフィールド名として用い代入する方法。

import (
  "reflect"
)

type Record struct {
  Name string
}

var record Record

// フィールド名
var fieldName string = "Name"

// ポインタへの変換必須
p := reflect.ValueOf(&record).Elem()

// reflectでフィールド名を指定して代入
// 但しSetStringは型が異なるとpanicを起こすので注意(よしなに処理するSetやSetInt等もある)
p.FieldByName(fieldName).SetString("hoge")

Mapのキーによるソートは一旦キーのみ配列化してソートしたほうが手軽

▼ Go: sort a Map by key #golang
https://gist.github.com/flaviocopes/bed52cc2ac407137b3931bc864b4e31b

なおsort.Slice()関数を使えばsortインタフェースを定義しなくてもソート可能。
こちらの記事が大変参考になりました。

▼ 今回はソート(sort)のお話。
http://text.baldanders.info/golang/sort/

JSONをパースして手っ取り早く値を参照する

例えば以下のJSONの場合。

{
  "form1":{
    "subject": "hoge"
    "mailto": [
      "hoge_1@example.com",
      "hoge_2@example.com"
    ]
  },
  "form2":{
    "subject": "fuga"
    "mailto": [
      "fuga_1@example.com",
      "fuga_2@example.com"
    ]
  },
}

普通は専用の構造体を用意して代入する。
でもいちいち構造体を用意するのは面倒だし、手っ取り早く値を参照したい場合がある。
そのような場合は一旦interface{}型でまるっと読み込んでしまうと便利。

import (
  "encoding/json"
  "io/ioutil"
)

bytes, err := ioutil.ReadFile(path_to_json)
var jsonData interface{}
err := json.Unmarshal(bytes, &jsonData)

これで全データがjsonDataにinterface{}型で格納されるが、
この方法で読み込むと全てmap[string]interface{}型の階層構造になるのが難点。
そのままでは参照できないので、例えばform1subjectを参照するのであれば、

jsonData.(map[string]interface{})["f1"].(map[string]interface{})["subject"]

のようにinterface{}型への型キャストを繰り返せば可能となる。
長くなるので良いとは言えないが、さくっと参照したい場合に便利です。

returnで構造体のリファレンスを返してメソッドチェーン

dbrでのサンプルを例に。

dbh.UpdateBySql("UPDATE〜").Exec()
  • dbhはdbr内のあらゆるメソッドにアクセス可能な状態
  • dbhを通じてupdate_builder.goに定義されているUpdateBySql()を実行
  • update_builder.goにはUpdateBuilder型が定義されている
  • UpdateBuilder型を用いたオブジェクトがreturn &UpdateBuilder{}で返る
  • UpdateBuilder型には構造体メソッドExec()も定義されている
  • つまりupdate_builder.go内のExec()が実行される

大雑多な解釈だが、

  • UpdateBySql()の実行により「Update処理である」事を確定し
  • Update処理に必要なデータをUpdateBuilder型に定義した上で
  • update_builder.go内のExec()を実行させる事ができる

という事。
なるほどメソッドチェーンという感じでした。

インターフェース型を引数に渡す、返り値で受ける

▼ 引数に渡す

sortパッケージではLen()/Less()/Swap()の3メソッドを満たす型(構造体メソッドとして持つ型)を引数に渡すことで、内部でこのメソッドを呼んで結果を返してくれる。なので3つのメソッドを目的に応じて定義すれば、様々なsortパターンを実現することが可能になっている。

  • sortパッケージ内にインターフェース型が定義されており
  • インターフェースを満たす型を用意し(3つのメソッドさえ持っていれば独自に定義した型で良い)
  • それをsortに渡す事で独自メソッドで処理された結果を得ることができる
▼ 返り値で受ける

dbrパッケージではSQLをExec()した場合、LastInsertId()/RowsAffected()メソッドを持つsql. Resultインターフェース型で結果が帰ってくる。なので返り値を受け取る変数をresultとした場合、result.RowsAffected()で更新された行数を得ることが可能になっている。

  • dbrパッケージ内でsql.Resultインターフェース型を満たす型(2つのメソッドを持つ型)が用意され
  • それをExec()メソッドの返り値として渡されて
  • 実行元で実行可能にしている

つまりこれは、sortパッケージは内部処理を呼び出し元に委譲することで利用の幅(バリエーション)をもたせ、dbrパッケージでは実行されたメソッド(Select/Update/Delete...)別に結果をインターフェースのメソッドとして定義し、返すことで、一つの共通型でそれぞれの結果を得られる仕組みを提供している。

  • sortは一つのメソッドの結果をユーザーが自由にカスタマイズ可能にするため
  • dbrは異なるメソッドの結果を共通のメソッドで得るため

インターフェースも使い方で色々とできるんだな、と思ったのでメモ。

r.URLをstringとして処理したい

r.URLはtype *url.URL型なので、そのままではstring型としての処理ができない。
その場合String()で参照できる。

strings.Replace(r.URL.String(), "www", "", 1)

実行時間の計測

start := time.Now()
end := time.Now()
result := end.Sub(start).Seconds()

空のmapを代入可能な状態で生成する

var var1 map[string]string = map[string]string{}
var1["hoge"] = "fuga" // OK

var var2 map[string]string
var2["hoge"] = "fuga" // NG(からデータがない状態でのこの方法はNG)
var2 = map[string]string{"hoge": "huga"} // OK

関数内では型推論による定義を行う。

hoge := map[string]string{}
hoge["fuga"] = "hogefuga" // OK

URL関連や環境変数の操作メモ

fmt.Println(os.Environ()) // 環境変数
fmt.Println(r.URL.Host) // hoge.fuga.com
fmt.Println(r.URL.Path) // /index.html

os.Setenv("HOGE", r.URL.Host) // 環境変数を設定
fmt.Println(os.Getenv("HOGE")) // 環境変数を参照

map[string]interface{}で定義された値の参照

例えば以下の定義からidを参照しようとするとエラーが発生する。

hoge := map[string]interface{}{
    "id": 1,
}
fmt.Println(hoge["id"]) // type interface {} does not support indexing

以下のエラーが発生する。

type interface {} does not support indexing

slice同士の連結

連結する側のslice末尾に「...」を忘れないこと。
Split等の関数実行時も「...」を付ければ連結できる。

result = append(result, source...)
result = append(result, strings.Split("1,2,3", ",")...)

strconv の Atoi と Itoa

返り値の個数が異るため無駄にはまる。
Atoiはerrorも受け取る必要があるので注意!

result, err := strconv.Atoi(target)
result      := strconv.Itoa(target)

1文字単位に変換

string型は内部はsliceなのでruneで1=1文字単位で参照できる。
例えば先頭から10文字を取得する場合はこんな感じ。

text := string([]rune(text)[:10])

timeパッケージによる日時の取扱について

日時処理を行うtimeパッケージですが、独自の仕様満載で慣れるまで戸惑います。
とはいえ便利な機能も多々ありますので、備忘録としてまとめておきます。
ほんと色々な関数が用意されているので、一度ドキュメントに目を通すのがオススメです。
https://golang.org/pkg/time/

なおこちらの記事をとても参考にさせていただきました。
- Goで時刻を取り扱う
http://qiita.com/taizo/items/acbee530bd33c803dab4
ありがとうございます。

// 現在時刻から色々と参照(type Timeに色々と定義されている)
now := time.Now()
fmt.Println(now.Unix())                  // 1486544245
fmt.Println(now.Month())                 // 2
fmt.Println(reflect.TypeOf(now.Month())) // time.Month型(time.Dateに使える!)

// 任意の時刻を定義する
location, _ := time.LoadLocation("Asia/Tokyo")
var month time.Month = 2                                                                                                     // 定数の代わりに任意の数値で指定する場合はtime.Month型で定義
current := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), location) // 現在日時をセット
future := time.Date(2017, month, 9, 17, 0, 0, 0, location)                                                                   // 未来の日時をセット
past := time.Date(2017, month, 7, 17, 0, 0, 0, location)                                                                     // 過去の日時をセット
fmt.Println(current)                                                                                                         // 現在日時(2017-02-08 17:59:11.942314121 +0900 JST)

// 自由なフォーマットに整形して出力
fmt.Println(now.Format("2006年01月02日 15時04分05秒"))     // 2017年02月08日 21時20分21秒
fmt.Println(current.Format("2006年01月02日 15時04分05秒")) // 2017年02月08日 21時20分21秒
fmt.Println(future.Format("2006年01月02日 15時04分05秒"))  // 2017年02月09日 17時00分00秒
fmt.Println(past.Format("2006年01月02日 15時04分05秒"))    // 2017年02月07日 17時00分00秒

// 2つの時刻を比較する
fmt.Println(time.Since(past))                 // pastから現在時刻までの経過時間(25h4m16.724052151s)
fmt.Println(reflect.TypeOf(time.Since(past))) // time.Duration型(const定義と同じ型!)
fmt.Println(future.After(past))               // futureはpastよりも未来か(true)
fmt.Println(past.After(future))               // pastはfutureよりも未来か(false)

// 定数に基準となる時刻単位がセットされている
fmt.Println(time.Hour)                 // 1h0m0s
fmt.Println(time.Minute)               // 1m0s
fmt.Println(time.Second)               // 1s
fmt.Println(reflect.TypeOf(time.Hour)) // time.Duration型(time.Sinceで取得する型と同じ!)

// 現在時刻に指定期間を加味した時刻を得る
fmt.Println(now.Add(time.Hour))          // 2017-02-08 19:24:39.432408145 +0900 JST(一時間後)
fmt.Println(now.Add(-time.Hour))         // 2017-02-08 19:24:39.432408145 +0900 JST(一時間前)
fmt.Println(now.Add((time.Since(past)))) // 2017-02-09 19:52:25.842717409 +0900 JST(time.Sinceで取得した時間経過分)

// 入力フォーマットを定義し「2014-12-31 00:00:00 +0000 UTC」(ISO-8601形式)で出力させる
var timeformat = "2006-01-02T15:04:05 MST"                        // 入力される日時のフォーマット定義(各値は固定値から選ぶ)
formatted, _ := time.Parse(timeformat, "2017-02-08T20:28:06 JST") // 右辺に変換対象となる日時パラメータ
fmt.Println(formatted)                                            // 2017-02-08 20:28:06 +0900 JST

// 曜日名を得る
fmt.Println(time.Weekday(2)) // Tuesday

サンプル

指定日時が現在日時から7日以内か確認。
New!マーク等の表示に使えます。

func getNew() bool {

    // 指定日時を分割
    r := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z`)
    date := r.FindStringSubmatch(varDate)

    // int型へキャスト(この辺が融通きかない…)
    yer, _ := strconv.Atoi(date[1])
    num, _ := strconv.Atoi(date[2])
    var mon time.Month = time.Month(num) // 月はtime.Month型なので注意!
    day, _ := strconv.Atoi(date[3])
    hur, _ := strconv.Atoi(date[4])
    min, _ := strconv.Atoi(date[5])
    sec, _ := strconv.Atoi(date[6])

    // 7日前の日時をセット(実際はconstとか切ってね)
    loc, _ := time.LoadLocation("Asia/Tokyo")
    target := time.Date(yer, mon, day, hur, min, sec, 0, loc)
    target = target.Add(24 *  * time.Hour)

    // Before()でbool判定結果を返す
    return time.Now().Before(target)
}

Regexで後方参照

\1や$1では簡単に取り出せない。

import "regexp"
...
r := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z`)
resultSlice := r.FindStringSubmatch(target)
fmt.Println(resultSlice) // [2016-02-07T10:36:25Z 2016 02 07 10 36 25]

連番付きフィールドからループで値を取得する

例えばこんな構造体。

type Hoge strunc {
    Field1 string
    Field2 string
    ...
}

通常フィールドへの参照は.で行い、フィールド名に変数は利用できません。
JSのようにハッシュによる参照Hoge["Field" + i]ができないため、golangでは困ってしまいます。
そんな時はreflectパッケージで値を操作します。

package main

import (
    "fmt"
    "reflect"
    "strconv"
)

type Hoge struct {
    Field1 string
    Field2 string
}

func main() {
    result := []string{}
    rv := reflect.ValueOf(Hoge{Field1: "value1", Field2: "value2"})

    for i := 1; i <= 2; i++ {
        label := "Field" + strconv.Itoa(i)               // iをstring型に変換
        st := rv.FieldByName(label)                      // reflect.Value型
        result = append(result, st.Interface().(string)) // 一旦interface{}型に変換してからstring型へキャスト
    }

    fmt.Println(reflect.TypeOf(result[0])) // string
    fmt.Println(result)                    // [value1 value2]
}

reflectで返される値はreflect.value型になります。
reflect.value型はそのままstring型へキャストできないため、一旦interface{}型に変換してから型アサーションでstring型に変換します。

元データがgolang標準型であれば問題ないのですが、例えばDBから取得した値でsql.NullString等を利用している場合、値が{{String:"Fuga", Valid: true}}のようにネスト構造のため困ってしまいます。
そんな時はメソッドチェーンで解決できます。

st := rv.FieldByName(label).FieldByName("String")

Interfaceの理解に困ったら

sort.goのソースを読む。
https://golang.org/src/sort/sort.go

Len()Less()Swap()の扱い方が非常に参考になりました。
処理タイプごとに構造体作って、構造体メソッドとしてLen()Less()Swap()を定義しておく。つまりは独自構造体にも同様にメソッド追加しておけば独自のsort処理が作れるよーという感じ。

同一パッケージ内で同じメソッド名を同居させたい

基本ですが具体例で解説。

user.go
    package models
    type User struct { ... } // DB用の型
    func (d *User) SelectById(id int) {
        ...
    }
book.go
    package models
    type Book struct { ... } // DB用の型
    func (d *Book) SelectById(id int) {
        ...
    }

上記の例では同じパッケージ名package modelsを利用している。
通常同一パッケージ内での同名メソッドは許可されないが、構造体メソッド化することで可能になる。これはDB操作など、一意のメソッド名で統一したい場合に便利。また構造体がベースなのでu.User.Hogeのようにフィールドにもシームレスにアクセス可能。

つまりgolangの構造体はclass的振る舞いが可能ということ。
golangの柔軟性を垣間見た気がしました。

ちなみに同一パッケージ名利用のさらなる利点は、配置場所を一つのディレクトリでまかなえる点。
上記ではmodelsディレクトリにuser.gobook.goを直接配置可能であり、importも***/modelsの一回でまかなえます。これが異なるパッケージで独立していたらuser/user.gobook/book.goという感じになり、importも個別に行う必要があります。

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7