- 投稿日:2019-10-08T23:27:20+09:00
[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/
- 投稿日:2019-10-08T19:26:16+09:00
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 uintptrbyte // 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 }ってする。まぁわかる。
- 投稿日:2019-10-08T19:26:16+09:00
サーバサイド初心者が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 uintptrbyte // 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 }ってする。まぁわかる。
- 投稿日:2019-10-08T17:34:03+09:00
サーバサイド初心者がGoのPackageとimportを個人的に理解しようとした話
Packageってなに???
The Tour of Goでいきなり出てくるねんけどなにこれ、てか、Cみてーだなこれ。
ってなったので、メモがわりに。。。
golangってなに?!
@soichiro331 さんの記事によると
Golangが作られた背景は
・動的型付け言語のプログラミング容易性、静的型付け言語の安全性・効率的なコンパイルを一つの言語で享受したい。
・マルチコアプロセッサを生かした優れた非同期処理による実行効率を目指した。
・本当に開発者が必要だと思う機能のみを提供するように、言語仕様をリッチにしないことにより、開発者の迷いを無くし、生産性の高い開発をサポートしたい。っていう三つらしい。ほう。なるほど。わからん。
ようするにCとかC++は今のマルチコア環境に適応しきった言語ではなく、動的な型付けの便利さを残しつつ、静的言語の安全性、効率性を残したスーパーC言語を作ろうぜ的な?
そんな感じか。ほうほう。
もともとSwift書いてる身からすると、めちゃくちゃ読みやすいなってなった。
TourofGopackage 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のパーサー 参考
- 投稿日:2019-10-08T17:20:05+09:00
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()) }
- 投稿日:2019-10-08T14:44:10+09:00
自分のためにまとめる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_array2slice
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文
配列のforfor index, value in range list { println(index, value) } // indexいらない場合 for _, value in range list { println(value) }
- 投稿日:2019-10-08T10:46:25+09:00
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"
- 投稿日:2019-10-08T10:20:27+09:00
セコクラウドの立ち上げからいつの間にか見た事のない実装を社内ローンチしちゃった件
アウトプット強化週間!_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を改良してこんな構成を作ってみた。
これによって自立型分散仮想基盤とでも言えるようなものが完成。他のサーバーに呼びかけてダメそうなら俺が責任もってコンテナ動かすぜ!てなかんじ。この構成だとダッシュボードとか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みたいにトークン指定してアクセスする。
- 投稿日:2019-10-08T00:01:57+09:00
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.gofunc 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.gofunc (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.gofunc (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.gotype 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.gotype 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.gotype 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.gotype 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.gotype 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管理ツールとかに挑戦してみたいですね。すでにいい感じのものが作られているのですが、やっぱり自分で作ってみたいじゃないですか。あ、もちろんインターンも楽しかったです。