20191008のGoに関する記事は9件です。

[Go]構造体の超初歩的なはなし

はじめに

Golangの構造体の超初歩的なおはなしです。

なぜ書いたのか?

Golangは僕にとってはじめての静的言語、知らない用語、意味不明な挙動。
正直、メモリとかポインタとか構造とか「なんじゃこりゃあああ?」ってなって
無視してたんだけど、そうは行かなかったようで・・。
1つずつ解剖していこうと思いペンを持ちました。
Go言語初心者of初心者の方にはいいかも。

構造体とは?

構造体(struct)は合成データ型であり、0個以上の任意の型の名前付き値を単一エンティティにまとめているものである。

などなど書いているけれど、
要するに色んな型の値をまとめて保持できるものと考えれば良い。

例えとして、音楽ならば、音楽という構造体の中に「グループ名」「ジャンル」「ヒット曲」「年代」など音楽を構成するものの中身と考えると良い。
この場合は「グループ名」と「ジャンル」と「私が好きな曲」はstring型、「年代」はint型となる。
※他言語のClassと呼ばれるものと似ている。

使い方

〜基本〜

構造名はなんでもいいんだけど、ビートルズとします。
※Theは割愛

// 構造体をBeatles宣言
type Beatles struct {
    Name string
    Bone int
    Part string
}

これでビートルズという構造体ができた。
構造体の中身はそれぞれの情報が書かれたフィールドを定義。

本当はメンバーがいて誰かがバンド名を決めるのが普通なんだけど、
構造体の宣言の方法は逆である。

続いて、メンバーを定義していきたい、まずJohn。
以下の感じでそれぞれの値を変数化することが可能。

// 構造体Beatlesからjというインスタンス変数を初期化して宣言
j := Beatles{"John",1940,"Vo/Gt"} //Output->{John 1940 Vo.Gt}
//何も入れない場合
//n := Beatles{} //Output->{ 0 } {string, int, string}の初期値

//jのそれぞれの値は.表記を使ってここでアクセス可能、また変数にしてもOK
john_name := j.Name //Output->John
john_bone := j.Bone //Output->1940
john_part := j.Part //Output->Vo/Gt

//変数なので値の書き換えもできる
john_name = j.Name + " Lennon" //Output->John Lennon

これだと1個ずつ入れなきゃいけないので面倒だって時に関数を使って初期化するのがいいかも

〜初期化関数〜


//変数gerogeの値をポインタ型で受け取り処理する関数
func createMember(Name string, Bone int, Part string) *Beatles{
    member := new(Beatles)
    member.Name = Name
    member.Bone = Bone
    member.Part = Part
    return member
  }

func main() {
   //ポイント型の構造体Beatles宣言、
   var geroge *Beatles = createMember("Geroge",1943,"Gt")
   //var ringo *Beatles = selectMember("Ringo",1940,"Gt")
   fmt.Println(geroge) // Output->&{Geroge 1943 Dr}
   fmt.Println(geroge.Name, geroge.Bone, geroge.Part) //Output->Geroge 1943 Gt
}

何行もある場合は上記使った方が楽かな。

さいごに

初Qiitaなのでお手柔らかにお願いしたいのでが、間違ってたら教えてください。
僕は今SNS系のwebアプリを作っている途中なので、
今度かくときはもう少し踏み込んだお話をしたいです。

参考

http://go.shibu.jp/effective_go.html#id11
https://www.amazon.co.jp/dp/4621300253/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goの型宣言とか、メソッドとか

go lang に置ける注意点

A Tour of Goをやってるので、その中の必要な型宣言とかメソッド周りで必要なところをまとめた記事です。golang初心者なので、突っ込んでください!

変数型宣言

いつものvariableのvarを使った宣言をしていきます。

var number int
var letters string

ってするのが基本形

  • 初期化子を利用した簡単な型宣言もできる
var number = 1
var letters = "This is a String"
  • 連続して同じ型を宣言する場合はまとめることもできる。
var number, numberTwo int
  • 複数型を一括でも宣言できる。初期化子を利用した方法で
var number, letters, isPython = 1 , "This is a String", false

えらい便利。すごい便利。素直にすごい。

  • メソッド内限定だが(僕はそう理解した)、以下のような暗黙の型宣言もできるらしい。
func someMethod(){
     i := 3
     // ゴリラやんけ
}

これ便利そう。varでわざわざ宣言する必要がないのは覚えとくと可読性の面でも楽そうだけど、型処理とかでめんどくさいことならないのかな。。。

型の種類

bool

string

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 の別名

rune // int32 の別名

float32 float64

complex64 complex128 //複素数とか虚数を許すすごいやつ

ほうほう。runeってこれまた古代魔法が使えそうな。。。
int, uint, uintptrの三種類は完全にそのシステムが64bitか32bitかによってその型特性?が変わると。
まぁ当たり前か。

定数宣言

これが結構特殊な感じがする。constantのconstを使っての宣言をする。
- constを使う場合ではメソッド内での := をさせてくれない。
- varよりも数値の精度が高い。
- 使える型はCharacter,String,Numeric,Booleanの四つのみ

この3点は覚えとく必要があるですね。

型宣言の特性

Goで最初にあげたように初期値を入れずに宣言をしたものは基本的に、No Value = nilにはならず、
Zero Value = 0もしくは空のStringになる。

var letters string    //letters = ""
var number int        //number = 0
var isPython bool     //isPython = false
var decimals float32  //decimals = 0

型変換(Casting)

わりかし簡単っていうのが第一印象

var number = 1
var decimals = float(number) //decimals = 1

っていう形になる。

じゃあintをstringにキャストできるかって言われると、できない。

var number = 1
var letters = string(number) //letters = ""

メソッド

Swiftが長いので正直めちゃくちゃわかりやすかった。

func swap(x,y string) (fullScript string){
    fullScript = y + x
    return fullScript
}

こう言う形で戻り値、引数宣言ができる。

もちろん暗黙的にReturnさせれる

func swap(x,y string) (fullScript string){
    fullScript = y + x
    return
}

引数がない場合に関しては、

func swap(){
    total := x + y
}

ってする。まぁわかる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サーバサイド初心者がGoの型宣言とメソッドを個人的に理解しようとした話

go lang に置ける注意点

A Tour of Goをやってるので、その中の必要な型宣言とかメソッド周りで必要なところをまとめた記事です。golang初心者なので、突っ込んでください!

変数型宣言

いつものvariableのvarを使った宣言をしていきます。

var number int
var letters string

ってするのが基本形

  • 初期化子を利用した簡単な型宣言もできる
var number = 1
var letters = "This is a String"
  • 連続して同じ型を宣言する場合はまとめることもできる。
var number, numberTwo int
  • 複数型を一括でも宣言できる。初期化子を利用した方法で
var number, letters, isPython = 1 , "This is a String", false

えらい便利。すごい便利。素直にすごい。

  • メソッド内限定だが(僕はそう理解した)、以下のような暗黙の型宣言もできるらしい。
func someMethod(){
     i := 3
     // ゴリラやんけ
}

これ便利そう。varでわざわざ宣言する必要がないのは覚えとくと可読性の面でも楽そうだけど、型処理とかでめんどくさいことならないのかな。。。

型の種類

bool

string

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 の別名

rune // int32 の別名

float32 float64

complex64 complex128 //複素数とか虚数を許すすごいやつ

ほうほう。runeってこれまた古代魔法が使えそうな。。。
int, uint, uintptrの三種類は完全にそのシステムが64bitか32bitかによってその型特性?が変わると。
まぁ当たり前か。

定数宣言

これが結構特殊な感じがする。constantのconstを使っての宣言をする。
- constを使う場合ではメソッド内での := をさせてくれない。
- varよりも数値の精度が高い。
- 使える型はCharacter,String,Numeric,Booleanの四つのみ

この3点は覚えとく必要があるですね。

型宣言の特性

Goで最初にあげたように初期値を入れずに宣言をしたものは基本的に、No Value = nilにはならず、
Zero Value = 0もしくは空のStringになる。

var letters string    //letters = ""
var number int        //number = 0
var isPython bool     //isPython = false
var decimals float32  //decimals = 0

型変換(Casting)

わりかし簡単っていうのが第一印象

var number = 1
var decimals = float(number) //decimals = 1

っていう形になる。

じゃあintをstringにキャストできるかって言われると、できない。

var number = 1
var letters = string(number) //letters = ""

メソッド

Swiftが長いので正直めちゃくちゃわかりやすかった。

func swap(x,y string) (fullScript string){
    fullScript = y + x
    return fullScript
}

こう言う形で戻り値、引数宣言ができる。

もちろん暗黙的にReturnさせれる

func swap(x,y string) (fullScript string){
    fullScript = y + x
    return
}

引数がない場合に関しては、

func swap(){
    total := x + y
}

ってする。まぁわかる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サーバサイド初心者がGoのPackageとimportを個人的に理解しようとした話

Packageってなに???

The Tour of Goでいきなり出てくるねんけどなにこれ、てか、Cみてーだなこれ。

ってなったので、メモがわりに。。。

golangってなに?!

@soichiro331 さんの記事によると

Golangが作られた背景は

・動的型付け言語のプログラミング容易性、静的型付け言語の安全性・効率的なコンパイルを一つの言語で享受したい。
・マルチコアプロセッサを生かした優れた非同期処理による実行効率を目指した。
・本当に開発者が必要だと思う機能のみを提供するように、言語仕様をリッチにしないことにより、開発者の迷いを無くし、生産性の高い開発をサポートしたい。

っていう三つらしい。ほう。なるほど。わからん。

ようするにCとかC++は今のマルチコア環境に適応しきった言語ではなく、動的な型付けの便利さを残しつつ、静的言語の安全性、効率性を残したスーパーC言語を作ろうぜ的な?

そんな感じか。ほうほう。

もともとSwift書いてる身からすると、めちゃくちゃ読みやすいなってなった。

TourofGo
package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello, World")
}

ふむふむ。Packageからなんかしてるのかなー。 Importでfmtのライブラリだったりを呼び出してるのかなー

func()ふむふむいつも通りのやつね。

fmtはなんかちゃうなこれがライブラリみたいな感じで書いてるんかな。おう?

ってなったからPackageとfmtについて調べてみた。

Packageとは

Packageは割とクラスと同じと思ってる。インスタンス化が常にされてるクラス的なサムシング。

要するに

package main

import (
     "fmt"
)

func numberPrint() {
}

var number int

とかっていうこう言うプロパティやらインスタンスを他のimport(継承)したところでも使えますよっていう認識をしてる。詳しいところだとちょっと違うんだろなぁって思いつつもここからどう展開したらいいかわからんから一旦こういう認識をしとく。

importとは

他のパッケージを読み込む時に使う。継承の認識をしてる。

さっき作ったnumberPrint()メソッドとかを他の画面で読み込む時に

package otherPkg

import (
    "fmt"
    "main"
)

func otherFunc(){
     main.numberPrint()
}

ってできるようになるよっていうことだと言うことがわかった。

細かいimportの挙動

実際にimportするときは、

import (
     "fmt"
     "main"
)

ってまとめれる。これを、Factored Import Statementって呼ぶらしく、Swiftみたく

import "fmt"
import "main"

ってできるらしい。けど、理想的な書き方は上のほうらしい。

あと疑問なのが、import時にファイルパスの末端を参照するって言う規約(?)なのに、なぜ
import"math"ではrandは参照されず、'import"math/rand"'っていうパス指定をしないといけないのかがわからない。。。

標準搭載のpackage

golang.jp参照
使い勝手の良さそうなpkgとか一応頭入れとこうと思ったpkgとか

package名(ファイルパス) package用途
ローカル系
zip zip形式のファイルの読み書きを行うためのパッケージ
bufio I/Oのバッファリング機能を提供します。
hex 16bitエンコードとデコード
base64 base64エンコードあるやんけ!
exec 外部コマンドの実行
fmt formatのfmtかwwww I/Oを使うやつか。printfとか
サーバ系
http これめっちゃ使うんやろなー
html これもめちゃ使うんやろなー
image/jpeg jpeg型のimageライブラリを一時的に生成する
image/png png型のimageライブラリを一時的に生成する
データ系
json jsonへのエンコード、デコード
net TCP/IP、UDP、ドメイン名解決、UNIXドメインソケットを含むUnixネットワークソケットへのポータブルインタフェースを提供します。(わけわかめ)
sort 配列やユーザ定義のコレクションをソートするためのあれやこれ
strconv String to Data cast的な?!
strings 文字列操作のための
sync 相互排他ロックってことは標準搭載のreactive的なサムシング?
utf16 utf16シーケンスのエンコード
websocket websocketプロトコルのサーバ実装
xml XML1.0のパーサー

参考

初めてのGo言語 Packageについて

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Convert UTC to JST with Carbon

https://play.golang.org/p/LMaAy1RaIFQ

package main

import (
    "fmt"
    "time"

    "github.com/uniplaces/carbon"
)

func main() {
    tokyo := "Asia/Tokyo"
    // Parse date time from string
    startAt, _ := time.Parse("2006-01-02T15:04:05Z", "2019-10-07T06:54:08Z")
    endAt, _ := time.Parse("2006-01-02T15:04:05Z", "2019-10-07T06:54:08Z")
    // UTC to JST
    location, _ := time.LoadLocation("Asia/Tokyo")
    startAt = startAt.In(location)
    endAt = endAt.In(location)
    fmt.Println(startAt, endAt)
    // Convert to date range
    start, _ := carbon.CreateFromDate(startAt.Year(), startAt.Month(), startAt.Day(), tokyo)
    end, _ := carbon.CreateFromDate(endAt.Year(), endAt.Month(), endAt.Day(), tokyo)
    fmt.Println(start.StartOfDay(), end.AddDay().StartOfDay())
    // Convert to UTC date range
    fmt.Println(start.StartOfDay().In(time.UTC).String(), end.EndOfDay().In(time.UTC).String())

}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分のためにまとめるGoの基礎

array

要素数固定のいつもの配列
a := [3]int{} // [0 0 0]
b := [3]int{1} // [1 0 0]
c := [3]int{1, 2, 3} // [1 2 3]
d := [2]int{1, 2, 3} // array index 2 out of bounds [0:2]

// 要素数を自動で数えてくれる書き方。スライスではない。
e := [...]string{1, 2, 3} // [1 2 3]
arrayを引数に使うときは要素数も一致させなきゃダメ
func main(){
    a := [1]string{"a"}
    echo_(slice|array1|array2)(a)//各メソッドを呼び出してみる
}

func echo_slice(strs []string) {
    fmt.Println(strs)
}
// cannot use a (type [1]string) as type []string in argument to echo_slice

func echo_array1(strs [1]string) {
    fmt.Println(strs)
}
// [a]

func echo_array2(strs [2]string) {
    fmt.Println(strs)
}
// cannot use a (type [1]string) as type [2]string in argument to echo_array2

slice

a := []int{} // []
b := []int{1, 2, 3} // [1 2 3]

// 初期状態で要素を持たせたい場合はmakeを使う
c := make([]string, 2) // [0 0] 
// 第3引数はcapacity
d := make([]string, 2, 5) // [0 0] 
要素の追加とcapacityの増加
a := []int{}
fmt.Println(len(a), cap(a), a)

for _, i := range [10]int{} {
    a = append(a, i)
    fmt.Println(len(a), cap(a),  a)
}
// --------------
0 0 []
1 1 [0]
2 2 [0 0]
3 4 [0 0 0]
4 4 [0 0 0 0]
5 8 [0 0 0 0 0]
6 8 [0 0 0 0 0 0]
7 8 [0 0 0 0 0 0 0]
8 8 [0 0 0 0 0 0 0 0]
9 16 [0 0 0 0 0 0 0 0 0]
10 16 [0 0 0 0 0 0 0 0 0 0]
要素の追加とcapacityの増加_makeの場合
a := make([]int,1,3)
fmt.Println(len(a), cap(a), a)

for _, i := range [10]int{} {
    a = append(a, i)
    fmt.Println(len(a), cap(a),  a)
}
//---------
0 3 []
1 3 [0]
2 3 [0 0]
3 3 [0 0 0]
4 6 [0 0 0 0]
5 6 [0 0 0 0 0]
6 6 [0 0 0 0 0 0]
7 12 [0 0 0 0 0 0 0]
8 12 [0 0 0 0 0 0 0 0]
9 12 [0 0 0 0 0 0 0 0 0]
10 12 [0 0 0 0 0 0 0 0 0 0]

if文

丸カッコがいらない
if a == 1 {
  println(1)
} else if a == 2 {
  println(2)
} else {
  println("else")
}

for文

配列のfor
for index, value in range list {
  println(index, value)
}

// indexいらない場合
for _, value in range list {
  println(value)
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Debug timezone in Docker

  • Code
package main

import (
        "fmt"
        "github.com/uniplaces/carbon"
)

func main() {
        loc := "Asia/Tokyo"
        now, _ := carbon.NowInLocation(loc)
        today := now.StartOfDay()
        tomorrow := today.AddDay()
        fmt.Println(now, today, tomorrow)
}
$ docker run --rm -it --privileged -v $(pwd):/app golang bash

$ cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

$ date +%T -s "07:00:00"

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

セコクラウドの立ち上げからいつの間にか見た事のない実装を社内ローンチしちゃった件

アウトプット強化週間!_2

AWS Firecrackerをガチ気で使おうとしている人って調べててAmazon当人以外にあんまいないようなのでノウハウをアウトプット。コンテナ起動させた以上の記事があまり無いような気がする。

Amazonさん、OSS使って利益吸い上げだけで貢献が云々言われてますけど、僕自身はこの記事にあるようにfirecrackerガンガン使ってますので凄く助かってます!ありがとうございます!

はじまり

「スキルアップ目的で何でも試せる壊してOKな、お気軽検証環境あったら手を動かす動機になるんじゃないすかね?」
「だね」

という会話から検証環境を作ってみようかとなった。が、その際に貰ったPCがスペック不足でどうにもなんなかった。

  • アサインされたパソコンは5,6年前のSATAが載っているようなお古。ミニクラウドとはいえ外部ストレージ無いと複数人からディスクアクセスに耐えられない
  • 各人でアカウントを払い出したりを考えるとvCenterが要るがライセンス料がまかなえない

色々選定してみてOpenStackすら重くてだめそうだったのでLXD+Golangの自作APIでラップしてみることにした。Ubuntuプレーン、docker、kubernetesイメージこさえたりして。

でもだめだった、辛うじてk8sは動くもののディスクアクセスが重すぎてpod、特にhelmががんがんタイムアウト死

あとネットワークの偏り問題があった。主系からは両系にアクセスできるけど、もう方系からは主系にはアクセスできん問題。Stackoverflowに情報あったけど元記事失念

どうしてもアクセスしやすい主系側にコンテナが偏ってしまう。
(これ今はどうにかできるんですかね?そういうものだからネットワーク観点でVXLAN使ってどうにかせよと書いてあったような)

さらに致命的なのがdockerとかで大量のプロセスが動いたノードをshutdown -h nowみたいにgracefulな停止してしまうとコンテナ上でプロセスがゾンビ?化してしまうようで発生するとコンテナが停止できなくなる。うえに消せない。かつ、cluster化していると全ノード再起動しないと消せない。定期的な全停止運用が発生

AWS Firecracker発表と作り直し

ドン詰まった辺りで発表されたのはAWS Firecracker。ためしにちょっと触ると起動早くて良さそう。環境ファイルも凄く少ない。コピーとか削除とかファイル単位だしでsnapshot機能作るの凄く簡単そうにみえた

だけど、基本はFirecrackerって単一のコンテナを動かすくらいの機能しか無いんですよ。Clusterとか全然できない。なので先のLXD用APIを改良してこんな構成を作ってみた。

vm1.png

vm2.png

vm3.png

vm4.png

これによって自立型分散仮想基盤とでも言えるようなものが完成。他のサーバーに呼びかけてダメそうなら俺が責任もってコンテナ動かすぜ!てなかんじ。この構成だとダッシュボードとかAPIサーバーみたいに止まると運用できなくなるようなシングルポイントがなくなるんですね。あとスケールも同じ構成のをくっつければ良いだけなので強烈にスケーリングしやすい。

k8sを動かしたい

AWS公式だとkubernetes未対応なんですが、

https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1748671

これをヒントにカーネル4.4.0-116+CONFIG_VXLAN=yでVXLANを有効にしたら動いた。あとCONFIG_DEVPTS_MULTIPLE_INSTANCES=yも有効にしないとpodに入れない

# ./k3s kubectl get pod -o wide
NAME        READY   STATUS    RESTARTS   AGE     IP           NODE     NOMINATED NODE   READINESS GATES
nginx-pod   1/1     Running   0          4m16s   10.42.0.37   master   <none>           <none>

本k8sじゃないけど学習用ならk3sでも十分っすよね。

その後Prometheus+Grafanaで監視したり色々追加遊んでたら片方マシンがHDD Media Errorで再起不能になった・・ので一台でがんばって動いております。

ユーザーにコンソールを開放したい

firecrackerは標準入出力がコンソールになるのでそのままだと利用者サイドからはコンソールは使えない。なので

https://github.com/gravitational/console-demo

この実装をforkさせてもらって標準入出力をラップしてWebからアクセスする。クラウドでよくあるコンソールのWebアクセスの実装を作ってみた。元の実装だとfirecrackerが起動する前にHTTPのプロセスがあがったりでうまく動かない。順番変えたりクラウドから使うためにトークン認証実装したり少々手を加えた。

https://github.com/yasutakatou/console-demo

git clone https://github.com/yasutakatou/console-demo
cd console-demo/
make

これでビルド

# ./demo -port=12345 -debug -html=./www

みたいに起動させて(./wwwはcloneしたhtmlがあるフォルダ)

http://127.0.0.1:12345/?token=passwd

みたいにトークン指定してアクセスする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoのSQL周りをちょっとだけいい感じにしてみた

最近サイバーエージェント主催のヒダッカソンというインターンで優勝をいただきました。インターンはGoで参加したのですが、コードを書く上で特にORM周りが実装の速度低下を引き起こしていたので自前で作ってみることにしました。

GoのSQLあたりって書きづらいんですよね.. Columnの分だけPointerを渡さないといけないし… そこでjson.Unmarshalみたいに、StructのTagでいい感じにしてくれたらいいんじゃねということでQueryRow, Query, Exec関数を非常に薄くラップしてみることにしました。

できたもの: https://github.com/K-jun1221/gosqlper

参考にしたもの

意気込んで作り始めたのはいいのですが、冒頭からつまづきました 笑 引数としてinterface{}で受け取らなければならず、Tagは愚か型情報が一切関数に渡って来ないんですねこれが… StackOverFlow上でも同じようなことで迷っている人がいました。 笑

じゃあ、JsonPackageのUnmarshalどうやって実装しとるんやということで、Unmarshalのソースコードを見にいきました。

Unmarshal.go
func Unmarshal(data []byte, v interface{}) error {
    // Check for well-formedness.
    // Avoids filling out half a data structure
    // before discovering a JSON syntax error.
    var d decodeState
    err := checkValid(data, &d.scan)
    if err != nil {
        return err
    }

    d.init(data)
    return d.unmarshal(v)
}

ほーん。と適当に眺めたところで、d.unmarshal(v)を見にいきます。

d_unmarshal.go
func (d *decodeState) unmarshal(v interface{}) error {
    rv := reflect.ValueOf(v)
    if rv.Kind() != reflect.Ptr || rv.IsNil() {
        return &InvalidUnmarshalError{reflect.TypeOf(v)}
    }

    d.scan.reset()
    d.scanWhile(scanSkipSpace)
    // We decode rv not rv.Elem because the Unmarshaler interface
    // test must be applied at the top level of the value.
    err := d.value(rv)
    if err != nil {
        return d.addErrorContext(err)
    }
    return d.savedError
}

こいつも適当にほーんと眺めて最後にd.value(rv)を見にいきます。

d_value.go
func (d *decodeState) value(v reflect.Value) error {

    fmt.Println("[value]")
    switch d.opcode {
    default:
        panic(phasePanicMsg)

    case scanBeginArray:

        fmt.Println("scanBeginArray")
        if v.IsValid() {
            if err := d.array(v); err != nil {
                return err
            }
        } else {
            d.skip()
        }
        d.scanNext()

    case scanBeginObject:
        if v.IsValid() {
            if err := d.object(v); err != nil {
                return err
            }
        } else {
            d.skip()
        }
        d.scanNext()

    case scanBeginLiteral:
        // All bytes inside literal return scanContinue op code.
        start := d.readIndex()
        d.scanWhile(scanContinue)
        fmt.Println(d.data[start:d.readIndex()])

        if v.IsValid() {
            if err := d.literalStore(d.data[start:d.readIndex()], v, false); err != nil {
                return err
            }
        }
    }

    return nil
}

なんか出てきた... 苦戦しながらも色々と読んでみると、思いの外単純であることがわかりました。JsonをDecode + 型判定しないといけないからこんなに複雑になっているんですね...

めちゃくちゃシンプルにしてみると以下のようなコードになります。

var obj interface{}
v := reflect.ValueOf(obj)

subv := v.Field("fieldName")
subv.SetString("fieldValue")

え、めっちゃ簡単やん。 ってことでこいつを参考にコードを書いていきます。タグはdbという名前でつけることにしました。型はめんどくさいのでStringで統一することにしました。自分用なので型の判定とかは甘々です... 許してくださいなんでもします...

自分で書いてみる

func QueryRow(db *sql.DB, sql SelectSQL, obj interface{}) error {

    // create reflect.Value
    v := reflect.Indirect(reflect.ValueOf(obj))

    // get tag mapping list
    tm, err := tagCheck(sql.Select, v)
    if err != nil {
        return err
    }

    // get raw sql statement
    rawSQL, err := sql.MakeSQL()
    if err != nil {
        return err
    }

    // call scan
    columns := make([]interface{}, len(sql.Select))
    for i := 0; i < len(sql.Select); i++ {
        var str string
        columns[i] = &str
    }
    err = db.QueryRow(rawSQL).Scan(columns...)
    if err != nil {
        return err
    }

    for i, column := range columns {
        subv := v.Field(tm[i])
        str, ok := column.(*string)
        if !ok {
            return errors.New("could not cast interface{} to *string type")
        }
        subv.SetString(*str)
    }

    return nil
}

追加でSQLの可読性向上+必要なTagがセットされているかのチェックのためにSQLをStruct化して定義しておきます。ついでにExec()とかQuery()とかもUnmarshalを参考にして実装しておきます。

models.go
type SQLStatement interface {
    MakeSQL() (string, error)
}

// SelectSQL @required: Select, From @optional: Join, Where
type SelectSQL struct {
    Select []string
    From   string
    Join   string
    Where  string
    Others string
}

// MakeSQL @required: Select, From @optional: Join, Where
func (s *SelectSQL) MakeSQL() (string, error) {
    if len(s.Select) == 0 || s.From == "" {
        return "", errors.New("lack required args")
    }

    sql := "SELECT " + strings.Join(s.Select, ", ") + " FROM " + s.From

    if s.Join != "" {
        sql += " JOIN " + s.Join
    }

    if s.Where != "" {
        sql += " WHERE " + s.Where
    }

    if s.Others != "" {
        sql += s.Others
    }

    return sql, nil
}

Havingとか他にも色々ありますが、めんどくさいので結構適当に作っています。とりあえず、お手製のORMっぽいもの作ってみたかったんです... 許してください。

書き換えてみる

QueryRow

old.go
type User struct {
    UserID      string `json:"user_id"`
    UserName    string `json:"user_name"`
    Password    string `json:"password"`
    IsAdmin     string   `json:"is_admin"`
    UserComment string `json:"user_comment"`
}

var row User
_ := db.QueryRow("SELECT id, name, pass, comment, is_admin FROM users WHERE user_id = ?", id).Scan(&row.UserID, &row.UserName, &row.Password, &row.IsAdmin, &row.UserComment)
new.go
type User struct {
    UserID      string `json:"user_id" db:"user_id"`
    UserName    string `json:"user_name" db:"user_name"`
    Password    string `json:"password" db:"password"`
    IsAdmin     string   `json:"is_admin" db:"is_admin"`
    UserComment string `json:"user_comment" db:"user_comment"`
}

var row User
sql := gosqlper.SelectSQL{
    Select: []string{"id", "name", "pass", "comment", "is_admin"},
    From:   "users",
    Where:  "user_id = \"" + id + "\"",
}

_ := gosqlper.QueryRow(db, sql, &row)

Query

old.go
type User struct {
    UserID      string `json:"user_id"`
    UserName    string `json:"user_name"`
    Password    string `json:"password"`
    IsAdmin     string   `json:"is_admin"`
    UserComment string `json:"user_comment"`
}

var users []User
rows, _ = db.Query("SELECT id, name, pass, comment, is_admin FROM users")

for rows.Next() {
    var row User
    _ := rows.Scan(&row.UserID, &row.UserName, &row.Password, &row.IsAdmin, &row.UserComment)
    users = append(users, row)
}
new.go
type User struct {
    UserID      string `json:"user_id" db:"user_id"`
    UserName    string `json:"user_name" db:"user_name"`
    Password    string `json:"password" db:"password"`
    IsAdmin     string   `json:"is_admin" db:"is_admin"`
    UserComment string `json:"user_comment" db:"user_comment"`
}

var users []User
sql := gosqlper.SelectSQL{
    Select: []string{"id", "name", "pass", "comment", "is_admin"},
    From:   "users",
}

rows, _ = gosqlper.Query(db, sql, &users)


QueryRowの箇所はあんまり改善しませんね.. (むしろ冗長...?) ですが、複数行のQueryMethodは結構いい感じになったのではないでしょうか? インターンでも結構SQLを書く場所が多くて時間が取られたので、こういうものを作っておけばもう少し時間短縮ができて楽に優勝ができたのかなと思います。

感想

ライブラリとかを作るとか初めてだったのですごく楽しかったです。次はMigration機構や、Version管理ツールとかに挑戦してみたいですね。すでにいい感じのものが作られているのですが、やっぱり自分で作ってみたいじゃないですか。あ、もちろんインターンも楽しかったです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む