Qiitaに書いてみた。
Go言語のポリモーフィズムの実現方法です。
golang.tokyo#21「Goエンジニアの採用」というイベントがあったそうです。
行ってみたかった。
golangtokyo.connpass.com
そのイベントでレポートをアップした方がいましたが、内容がとても良かった。
その中で身を引き締まる内容がありました。
mom0tomo.github.io
「lsしたときに受けた印象が変わることはほとんどない」
言わんとすることは、評価者の立場になってくれよってことだと思います。
他にも、go fmt や golint などソースをすぐに整理するコマンドなんて手軽にできるはずなんだからやっておくべきだなと再確認しました。
下記のリンクはちゃんと読むべきだなと思いました。
しっかりGo言語勉強しようっと
追記
これもおまけ
ukai-go-talks.appspot.com
こんにちは suganoo です。
Pythonでまた似たようなエラーが出たので備忘として書いておきます。
このエラーなんだっけなと基本的なものなのにたまにど忘れしてしまいました。
TypeError: string indices must be integers, not str
stringのインデックス型でintで指定するところstringで指定してるよ。
っといった意味です。
わかりやすいサンプルコードを書いてみます。
str1 = "Hello World!" print str1 print str1[:2] print str1["hoge"]
Hello World! He Traceback (most recent call last): File "stringindices.py", line 6, in <module> print str1["hoge"] TypeError: string indices must be integers, not str
インデックスは数値でアクセスしなければならないのに、文字列を指定してみるとこんなエラーがでます。
参考にしてみてください。
blog.suganoo.net
【PR】SESで働いているあなた!自分の月額単価とマージン率を知ってますか?
blog.suganoo.net
こんにちは suganoo です。
Go言語からのDB操作を復習したいと思います。「Goプログラミング実践入門」でDBの操作が出ていたので、おさらいをしておきたいなと思ってました。
今回はORMは使いません。それはそれでいつかまた調べて記事にしてみようと思います。
本記事ではPostgreSQLを使っています。インストール作業は他のサイトなどを見て設定してみてください。
www.dbonline.jp
プログラムの中でCREATE DATABASE/TABLE からやってみようかなと思いましたが、考えてみてもそんな場面はほとんどないので事前にDB設定とCREATE TABLEはしておきます。
-- データベースは postgresを使います create table members ( id serial primary key, first_name varchar(255), last_name varchar(255), email varchar(255), accessprev boolean ); INSERT INTO members VALUES (1, 'minoru', 'tanaka', 'tanaka@gmail.com', TRUE); INSERT INTO members VALUES (2, 'tadashi', 'sato', 'sato@gmail.com', FALSE); INSERT INTO members VALUES (3, 'sachiko', 'suzuki', 'suzuki@yahoo.co.jp', FALSE);
テーブルの中身を確認するとこうなってると思います。
id | first_name | last_name | accessprev | |
---|---|---|---|---|
1 | minoru | tanaka | tanaka@gmail.com | t |
2 | tadashi | sato | sato@gmail.com | f |
3 | sachiko | suzuki | suzuki@yahoo.co.jp | f |
今回はPostgreSQLを使うので、それ用のdriverをインポートします。
import ( "database/sql" _ "github.com/lib/pq" )
データ取得用にMember構造体を定義しておきます。
type Member struct { Id int FirstName string LastName string Email string AccessPrev bool }
先に以降で説明するサンプルコードを掲載しておきます。
Go言語でDB操作
単純にクエリ実行したい場合に使えます。
sql - The Go Programming Language
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
サンプルコード
rows, err := Db.Query("SELECT * FROM members ORDER BY id") if err != nil { return } for rows.Next() { m := Member{} rows.Scan(&m.Id, &m.FirstName, &m.LastName, &m.Email, &m.AccessPrev) fmt.Println(m) }
Query()はRows型を返します。
sql - The Go Programming Language
SQLの結果が複数行を想定しているからです。
そんでそのメソッドのNext()で行があるか判断して、Scan()で値を取得しています。
このQuery()関数はプレイスホルダーも使えます。SQL分に$1, $2, $3 といった書き方をして、引数で実際の値を設定するというものです。
rows, err = Db.Query("SELECT * FROM members WHERE accessprev = $1 ORDER BY id", "false")
値はたいてい別々に取得するものなのでSQL文に入れる必要がないので見やすくなりますね。SQL文の中では$1, $2, $3...の順番はありません、引数に指定する実際の値の順番が$1, $2, $3...になるので順番を間違えないようにしましょう。
そんで次にQueryRow()です。
https://golang.org/pkg/database/sql/#DB.QueryRow
func (db *DB) QueryRow(query string, args ...interface{}) *Row
QueryRow()はRow型を返します。一行のSQL結果が返るを想定してるのでScan()しかないですね。
サンプルコードです
m := Member{} err = Db.QueryRow("SELECT * FROM members WHERE accessprev = $1 ORDER BY id", "true").Scan(&m.Id, &m.FirstName, &m.LastName, &m.Email, &m.AccessPrev) if err != nil { return } fmt.Println(m)
SQL文だけでStmtオブジェクトを作り、クエリを実行するメソッドと分けることができます。
sql - The Go Programming Language
func (db *DB) Prepare(query string) (*Stmt, error)
サンプルコードです
statement := "SELECT * FROM members WHERE accessprev = $1 ORDER BY id" stmt, err := Db.Prepare(statement) if err != nil { return } defer stmt.Close() rows, err = stmt.Query("false") for rows.Next() { m := Member{} rows.Scan(&m.Id, &m.FirstName, &m.LastName, &m.Email, &m.AccessPrev) fmt.Println(m) }
抜粋ですが実行メソッドが分けられているのがわかるかと思います。
statement := "SELECT * FROM members WHERE accessprev = $1 ORDER BY id" stmt, err := Db.Prepare(statement) defer stmt.Close() // Close()が必要!! rows, err = stmt.Query("false")
単純にクエリを実行し、結果行を戻さないメソッドです。たいていは delete, insert で使うと思います。
sql - The Go Programming Language
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
返すResult型を見てもどこの行に入ったかとかを返すようですね。データベース依存みたいです。
sql - The Go Programming Language
サンプルコードです
_, err = Db.Exec("INSERT INTO members VALUES ($1, $2, $3, $4, $5)", 4, "takashi", "yamamoto", "yamamoto@yahoo.co.jp", "FALSE")
ここまで基本的なメソッドを見てきましたが、ドキュメントを見てみるとcontex.Contextを渡せるメソッドも用意されているんですね。
例えば QueryContext()とか
sql - The Go Programming Language
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
確かにSQLでクエリに時間がかかることもありますから、タイムアウト時間も設定したいものです。けっこう便利ですな。
context.Context()の使い方については下記の記事が参考になります。
blog.suganoo.net
そんでまたドキュメントを見てみるとNamed()メソッドなるものも見つかりました。
sql - The Go Programming Language
func Named(name string, value interface{}) NamedArg
Exampleを見てみると、@で任意のプレイスホルダーを指定できるように見えます。
db.ExecContext(ctx, ` delete from Invoice where TimeCreated < @end and TimeCreated >= @start;`, sql.Named("start", startTime), sql.Named("end", endTime), )
なんだsql.Named() 優秀やーん!!
.....っと思ったのですが、実はこれまだ使えるDBは多くないそうです。
mattn.kaoriya.net
Named()を使えるのはSQLiteだけみたいですね。
私はこれに気づかずPostgreSQLで試行錯誤してみましたが、ぜんぜん使えず半日以上を無駄にしました。
issueにはあるみたいですが、まだ使えないようですね。
github.com
github.com
あとdatabase/sqlのドキュメントを調べて気づきましたが、Exampleがことごとくすべてエラーになってます。これいいんですかねえ.....。
golang.org
まあDBがないとできないようなExampleだから、仕方ないといえばそうなのかもだけど。。。
他にもいろいろ関数はありましてColumnType型やカラム一覧が取得できる関数などもありますが、上記の基本的な使い方がわかればあとは何とかなるかと思います。
次回はgormでもやってみたいと思います。
<こんな記事もあります>
blog.suganoo.net
blog.suganoo.net
達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ
こんにちは suganoo です。
今月号のSoftware Design (2019年2月号)に「”速い”コードの書き方」という特集がありました。Go言語についても書かれていたので、おお!っとさっそく読み込んでしまいました。
内容としてはGo言語の特徴であるgoroutineを使った非同期処理やタイムアウトがまとめられています。goroutineを使ってchanで非同期処理ってけっこうやってみないと理解できなかったりするので、あのページ数にまとめていたのはけっこう大変だったのではと推測してしまいます。
たまたま自分はGo言語で非同期処理のツールを作ってたので復習として読めてラッキーでした。
またタイミングが良かったことに、WebSokcketでチャットアプリを作ってみたいなと思っていまして、Go言語でWebSocketっというとgorilla/websocketがよく使われているのでそれをよく読んでいました。
https://github.com/gorilla/websocket
そこのexamples/echoでお勉強していたのですが、定期実行を下記のようにtimeパッケージのticker.Cでやっていたんですね。最初はこれが何やってるのかわかりませんでした。
ticker := time.NewTicker(time.Second) defer ticker.Stop() for { select { case <-done: return case t := <-ticker.C: err := c.WriteMessage(websocket.TextMessage, []byte(t.String())) ..........
そこでSoftware Designにも書かれていたことだし自分の復習もかねて定期実行とタイムアウトを調べてみました。
※以降のコードはSoftware Design (2019年2月号)も参考にしています。
その前にプログラムを実行中にプログラムを止めたい場合があります。
そのやり方をざっくり書いておきます。
import "os" ....... sc := make(chan os.Signal, 1) // シグナル用のチャンネル作って signal.Notify(sc, os.Interrupt) // シグナルを登録する loop: for { select { case <- sc: // <---- ctrl + c を押すとここでキャッチされる。 fmt.Println("interrupt") break loop } }
いくつかやり方はあるそうですがここではtime.After を使う方法と、NewTickerを使う方法、time.Tickを使う方法を紹介します。
https://golang.org/pkg/time/#After
func After(d Duration) <-chan Time
例えば2秒ごとに何かを実行したいなら、こんな感じに書けます。
Software Design (2019年2月号) ではこのように書いてますね。
case <-time.After(2 * time.Second):
サンプルコードです
package main import ( "fmt" "os" "os/signal" "time" ) func doSomething() { fmt.Println(time.Now()) } func main() { sc := make(chan os.Signal, 1) signal.Notify(sc, os.Interrupt) loop: for { select { case <- sc: fmt.Println("interrupt") break loop //os.Exit(0) //case t := <-time.After(2 * time.Second): // fmt.Println(t) case <-time.After(2 * time.Second): doSomething() } } }
実行結果です。以降実行結果はどれも同じなので省略します。
2019-01-22 13:48:58.5850586 +0900 JST m=+2.025493201 2019-01-22 13:49:00.5845793 +0900 JST m=+4.025013901 2019-01-22 13:49:02.5862735 +0900 JST m=+6.026708101 interrupt // ctrl + c を押す
またループの抜け方ですが、Software Design (2019年2月号)に書いてありましたが上記のようにbreak loopで抜けられます。Go言語ってラベルでループを抜けるとかダメじゃないんですよね。もしくは os.Exit()でプログラムを終わらすこともできます。
またちなみにですが、下記のようにチャネルの結果を取得することも可能で、表示させてみるとtime型のオブジェクトが取得できます。以降の例も同様です。
case t := <--time.After(...): fmt.Println(t) // --> ex. "2019-01-22 13:49:00.5845793 +0900 JST m=+4.025013901"
time.NewTickerを使うとこんな感じです。
https://golang.org/pkg/time/#Ticker
func NewTicker(d Duration) *Ticker type Ticker struct { C <-chan Time // The channel on which the ticks are delivered. // contains filtered or unexported fields } ..... tk := time.NewTicker(2 * time.Second) defer tk.Stop() ..... case <-tk.C: ...
サンプルコードです
package main import ( "fmt" "os" "os/signal" "time" ) func doSomething() { fmt.Println(time.Now()) } func main() { sc := make(chan os.Signal, 1) signal.Notify(sc, os.Interrupt) tk := time.NewTicker(2 * time.Second) defer tk.Stop() loop: for { select { case <- sc: fmt.Println("interrupt") break loop case <-tk.C: doSomething() } } }
https://golang.org/pkg/time/#Tick
func Tick(d Duration) <-chan Time
time.Tick()を使うとこんな感じです。
for _ = range time.Tick(2 * time.Second) { doSomething() }
サンプルコードです。
package main import ( "fmt" "os" "os/signal" "time" ) func doSomething() { fmt.Println(time.Now()) } func main() { sc := make(chan os.Signal, 1) signal.Notify(sc, os.Interrupt) go func() { for _ = range time.Tick(2 * time.Second) { doSomething() } }() loop: for { select { case <- sc: fmt.Println("interrupt") break loop } } }
どれも <-chan 型を返してきてますね。
WithCancel, WithTimeout, WithDeadlineを使うと別プロセスから特定の関数をキャンセルすることができます。
ちょっとここでWithDeadlineについては特定の時間が来たらキャンセルになる機能でして、時間を指定すればいいだけなので説明は省きます。WithDeadlineの引数に特定の時間を入れればいいです。
https://golang.org/pkg/context/#WithDeadline
WithCancelは(というかWithTimeoutもWithDeadlineもですが)第二パラメーターにCancelFuncを返します。そのCancelFuncを実行するとContextを受け継いだ関数がキャンセルされます。
https://golang.org/pkg/context/#WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
サンプルコードです
package main import ( "context" "fmt" "time" ) func doSomething(ctx context.Context) { fmt.Println("5s sleep start from doSomething.") // (4) time.Sleep(5 * time.Second) // (5) fmt.Println("End doSomething.") // (2)で2秒待って(3)でキャンセルされるので表示されない } func main() { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) go doSomething(ctx) fmt.Println("2s sleep start") // (1) time.Sleep(2 * time.Second) // (2) cancel() // (3) select { case <-ctx.Done(): // (6) fmt.Println("done:", ctx.Err()) } }
実行結果
2s sleep start // (1)が実行 5s sleep start from doSomething. // (4)が実行 // (2)の2秒待機 done: context canceled // 5秒待たず(3)でキャンセルされdoSomething()が終わり(6)に来る
WithCancelを使わない場合はわざわざキャンセル用のチャンネルを作ってdoSomethingに持たせ、forでループさせて待機させる必要があります。その点WithCancelのおかげでプログラムが簡素になりました。
WithCancelを連鎖させることもできます。連鎖というとぷよぷよを思い出してしまいますね。ですがそんなイメージです。
サンプルコードを見てみましょう。
package main import ( "context" "fmt" "time" ) func doSomethingParent(ctx context.Context) { fmt.Println("5s sleep start from doSomethingParent.") time.Sleep(5 * time.Second) fmt.Println("End doSomethingParent.") } func doSomethingChild(ctx context.Context) { fmt.Println("3s sleep start from doSomethingChild.") time.Sleep(3 * time.Second) fmt.Println("End doSomethingChild.") } func main() { ctx := context.Background() pCtx, pCancel := context.WithCancel(ctx) cCtx, cCancel := context.WithCancel(pCtx) defer cCancel() //defer pCancel() go doSomethingParent(pCtx) go doSomethingChild(cCtx) fmt.Println("2s sleep start") time.Sleep(2 * time.Second) pCancel() // doSomethingParent 関数が終わりきる前に親関数をキャンセルする。 //cCancel() select { case <-pCtx.Done(): fmt.Println("done Parent Context :", pCtx.Err()) fmt.Println("done Child Context :", cCtx.Err()) case <-cCtx.Done(): fmt.Println("done Parent Context :", pCtx.Err()) fmt.Println("done Child Context :", cCtx.Err()) } }
実行結果
2s sleep start 3s sleep start from doSomethingChild. 5s sleep start from doSomethingParent. done Parent Context : context canceled // <--- (pCancel)親関数がキャンセルされたから done Child Context : context canceled // <--- 子関数もキャンセルされてるよ!!
そこで今度は子関数を先にキャンセルしてみましょう。main()だけ記載します。
func main() { ctx := context.Background() pCtx, pCancel := context.WithCancel(ctx) cCtx, cCancel := context.WithCancel(pCtx) //defer cCancel() defer pCancel() go doSomethingParent(pCtx) go doSomethingChild(cCtx) fmt.Println("2s sleep start") time.Sleep(2 * time.Second) //pCancel() cCancel() // <--- 子関数をキャンセルする。 select { case <-pCtx.Done(): fmt.Println("done Parent Context :", pCtx.Err()) fmt.Println("done Child Context :", cCtx.Err()) case <-cCtx.Done(): // <-- こっちが実行されるよ! fmt.Println("done Parent Context :", pCtx.Err()) fmt.Println("done Child Context :", cCtx.Err()) } }
実行結果
2s sleep start 3s sleep start from doSomethingChild. 5s sleep start from doSomethingParent. done Parent Context : <nil> // <--- 親関数のcontextはキャンセルされてない! done Child Context : context canceled
想像通り子関数だけキャンセルされますね。
ちょっと有効方法が思いつきませんが、何かと使えそうですね。
WithTimeoutはSoftware Design (2019年2月号)Go言語のところの最後に書いてありました。
指定時間が経過したら関数をキャンセルします。
(2019/01/23 ctrl + c で止める必要はなさそうなのでその部分は削除しました。)
package main import ( "context" "fmt" "time" ) func doSomething(ctx context.Context) { fmt.Println("before in doSomething") time.Sleep(5 * time.Second) // (2) fmt.Println("after in doSomething") } func main() { ctx := context.Background() ctx, cancel := context.WithTimeout(ctx, 3*time.Second) // (1) defer cancel() go doSomething(ctx) select { case <-ctx.Done(): // (3) fmt.Println("done:", ctx.Err()) } }
実行結果
before in doSomething // (2)doSomething で5秒待機が始まる // (1)しかしWtithTimeoutの3秒待機後のキャンセルが早い done: context deadline exceeded // そのため(3)のチャネルが返る
WithTimeoutで指定時間が過ぎるとキャンセルすることができます。
ここまでのサンプルコードを自分で書いててふと疑問に思ったのですが、タイムアウト予定の関数(ここではdoSomething)が思いのほか早く終わってタイムアウト以内で終わってしまったらどうなるのか?どうしたらいいのか?を考えてみました。
っとなったら、すぐに次の処理に行ってほしいですよね。
サンプルコードでタイムアウト時間を単純に延ばしてやってみます。
(2019/01/23 ctrl + c で止める必要はなさそうなのでその部分は削除しました。)
package main import ( "context" "fmt" "time" ) func doSomething(ctx context.Context) { fmt.Println("before in doSomething") time.Sleep(5 * time.Second) fmt.Println("after in doSomething") } func main() { ctx := context.Background() // 10秒にしました。 ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() go doSomething(ctx) select { case <-ctx.Done(): fmt.Println("done:", ctx.Err()) } }
実行結果
before in doSomething after in doSomething // doSomethingの5秒待機が終わる // でもWithTimeoutのタイムアウト時間が10秒だから残り5(=10 - 5)秒間待たされる! // タイムアウト以内で処理が終わるんならすぐに終わってほしい。。。 done: context deadline exceeded
ちょっと場合によってはよろしくない動作になりました。
おそらくWithTimeoutを設定した場合は一定時間内に処理が終わってほしいものの、すぐに終わるんであればすぐ次の動作に行ってほしいことが多いと思われます。例えばどこかのサーバーにデータを取りにって30秒以内にリクエストが返ってこなければタイムアウトしてしかたないけど、数秒でリクエストが返ってくるのであればタイムアウト時間まで待つ必要はありません。
これどうしたもんかな?っと考えてみました。
ググってみたところあまり見つからなかったので、こんなコーディングにしてみました。CancelFuncを関数に渡しています。
package main import ( "context" "fmt" "time" ) // 第二引数にcancel context.CancelFunc func doSomething(ctx context.Context, cancel context.CancelFunc) { fmt.Println("before in doSomething") time.Sleep(5 * time.Second) fmt.Println("after in doSomething") cancel() // 終わったら自分でcancelする。 } func main() { ctx := context.Background() ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() go doSomething(ctx, cancel) // cancel関数も一緒に渡す select { case <-ctx.Done(): fmt.Println("done:", ctx.Err()) } }
実行結果
before in doSomething after in doSomething done: context canceled // doSomethingが終わったらすぐにcancel()するのでctx.Done()のチャンネルをキャッチ
defer cancel()がプログラム終了後に実行されてしまうんで、なんかモヤっとしてしまいます。けどもcontextをタイムアウトを無効化する方法がなさげなので、うーんまあこれでいいかなーと・・・・。
Go言語のgorotine,非同期処理はなかなか難しいですが知ってると柔軟な実装ができていいですね。
そんでふと以前読んだ「Go言語による並行処理」を見てみると今回の内容がほとんど書いてありますね!かなり飛ばし読みして途中でやめてしまったのが悔やまれます。必読の本であることを再確認しました。
(こんな記事もあります)
blog.suganoo.net
Goでちゃんとテストコードを書こうとようやく重い腰をあげてみました。
コーディングしてるとテストコードは後回しにしてしまうんですよね。ちゃんとやってみます。後回しにするの良くないっすね(汗)。。。
go testについて調べたことを記事にしてみようと思います。
こんなコードを用意してみます。
hoge.go hoge_test.go fuga_test.go
hoge.go
package main import ( "fmt" ) func hogefunc() int { return 1 } func fugafunc() int { return 2 } func main() { fmt.Println(hogefunc()) fmt.Println(fugafunc()) }
hoge_test.go
package main import ( "testing" ) func TestHogefunc(t *testing.T){ n := hogefunc() if n != 1 { t.Fatal("Error return num") } }
fuga_test.go
package main import ( "testing" ) func TestFugafunc(t *testing.T){ n := fugafunc() if n != 2 { t.Fatal("Error return num") } }
気をつけたいところとして下記があります。
2番目の方は一度、「Testgethoge...」とか書いてしまいなんでテストが実行されないんだ?っと悩んでしまいました。正確に書きましょう「TestGethoge...」ですね。
実行コマンドです。
go test
ですが、下記パラメータをオススメします。
go test -v -cover
っというのも出力結果が変わってくるからです。
なにもオプションをつけないとこうなります。
go test PASS ok _/xxxxxxxxxxxx 0.293s
が、オプションつけるとこうなります。
go test -v -cover === RUN TestFugafunc --- PASS: TestFugafunc (0.00s) === RUN TestHogefunc --- PASS: TestHogefunc (0.00s) <--- -v のおかげ PASS coverage: 50.0% of statements <---- -coverのおかげ ok /xxxxxxxxxxxxxxx 0.296s
特定のテスト関数だけを実行することも可能です
go test -v -cover -run TestHogefunc
ここなんですけど、じゃあ特定の2つのテスト関数やりたいときどうすればいいのか?と考えたんですが、あまりスマートにできそうにありませんでした。
どうも -run 以降は正規表現で指定するようで、特定の複数のテスト関数を指定したいときは正規表現で指定するしかないようです。
例
go test -v -cover -run Test.*func
とか。
特定のテストファイルだけを実行したいときは下記のようにします。
go test -v -cover hoge_test.go hoge.go
この時 hoge.go ファイルを抜かしてしまうと下記のようなエラーになります。
go test -v -cover hoge_test.go # command-line-arguments [command-line-arguments.test] .\hoge_test.go:8:7: undefined: hogefunc FAIL command-line-arguments [build failed]
hogefunc関数がねーよと。
上記のオプションはごく一部でして、まだたくさんあります。
下記のコマンドを実行すればいろいろ出てきます。
go help testflag
上記のcoverageの結果なんですが、ちょっと気になったところがありました。
coverage: 50.0% of statements
50%ってどういうことなんだ?どこからこういう計算をしてるんでしょう?
下記のコマンドを実行するとcoverrageの結果をブラウザで確認することができます。
go test -v -coverprofile output go tool cover -html=output -o cover.html
このcover.htmlをブラウザで確認してみます。
なるほど、実行してる関数の内50%しか確認してないことがわかりますね。
スクリプトを実行する際に引数を必要とすることもあります。
例えば、flagを使ってその引数にconfファイルを設定してて、そのファイルの中の値を取得する関数があったとします。テスト実行時に引数としてファイルが必要になりますよね。
そんなときには -args が使えます。
引数が必要なサンプルコード 引数無くても動いちゃうんだけど、そこは勘弁。。。
package main import ( "flag" "fmt" "os" ) var ( argC = flag.String("c", "default", "config file path") ) func hogefunc() int { return 1 } func fugafunc() int { return 2 } func ini() { // read config file flag.Parse() conf := *argC fmt.Println(conf) if conf == "default" { os.Exit(1) } } func main() { fmt.Println(hogefunc()) fmt.Println(fugafunc()) }
実行はこんな感じ
go run hoge.go -c conf.txt
テスト実行コマンドはこうなります。
go test -v -cover -args -c confighoge
(SEOのために試しに書いてみる)「いかがでしたか?」
ここでも「Goプログラミング実践入門」が役に立ちました。
あの本の中にはUnitテストやHttpのテスト、ベンチマークテストも書かれており、ざっくりGoのテストを理解するためにかなり役立ちました。
けっこう優秀だな!
Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る impress top gearシリーズ
で、こんな記事を書いてて「みんなのGo言語」を立ち読みしたら、俺の知りたいことほとんど書いてあった。。。
ある程度知識や経験が溜まってくると「みんなのGo言語」はかなりいい本だなと再認識してきます。
オススメです。
GoでWebプログラミングを学んでます。
今回はtemplateの使い方の紹介です。
まずはソースコードから。
main.go
package main import ( "html/template" "net/http" ) func process(w http.ResponseWriter, r *http.Request){ t := template.Must(template.ParseFiles("tmpl.html")) m := "Helooooooo!!!" // <--- tmpl.html の {{ . }} に表示されるデータ t.Execute(w, m) } func main(){ server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", process) server.ListenAndServe() }
tmpl.html
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Go template work</title> </head> <body> {{ . }} <!-- <--- ここにデータが表示される--> </body> </html>
これを実行して localhost:8080/process をブラウザで確認すると
Helooooooo!!!
っと表示されます。
でこれは、文字列以外に数値でも構造体でも配列でもハッシュでも m:= ... のところでデータを渡せば表示することができます。
余談ですがtemplate.ParseFilesの結果をtemplate.Mustでラップしてます。
これ知らなかったのですが、エラー処理を省略するためです。
template - The Go Programming Language
ParseFilesの結果はエラー結果も返してしまいますが、そのエラー処理を省略させてしまうのがMustです。
template - The Go Programming Language
そんでこのデータを渡すときは構造体を渡してやると何かと使いやすいので構造体を使うことが多いです。
以降修正箇所は部分的に提示します。
type Member struct { Id int Name string } func process(w http.ResponseWriter, r *http.Request){ t := template.Must(template.ParseFiles("tmpl.html")) m := Member{ // <--- 構造体で渡す Id: 123, Name: "bob", } t.Execute(w, m) }
<body> {{ .Id }} <!-- 構造体MemberのIdを表示 --> </body>
例えば下記のように構造体の中の配列を表示させるには、rangeを使うと表示できます。
type Member struct { Items []Item } type Item struct { Id int Name string } func process(w http.ResponseWriter, r *http.Request){ t := template.Must(template.ParseFiles("tmpl.html")) m := Member{ Items: []Item{ Item{Id: 123, Name: "bob"}, Item{Id: 456, Name: "tom"}, Item{Id: 789, Name: "mike"}, }, } t.Execute(w, m) }
<body> <ul> {{ range .Items }} <li>{{ .Id }} - {{ .Name }}</li> {{ end }} </ul> </body>
・123 - bob ・456 - tom ・789 - mike
そしてこのrangeはindexも使えます。
main.goは変えずにtmpl.htmlを修正します。
<body> <ul> {{ range .Items }} <li>{{ .Id }} - {{ .Name }}</li> {{ end }} </ul> <ul> {{ range $i, $v := .Items }} <li>{{ index $.Items $i }} : {{ $v }}</li> {{ end }} </ul> </body>
・123 - bob ・456 - tom ・789 - mike ・{123 bob} : {123 bob} ・{456 tom} : {456 tom} ・{789 mike} : {789 mike}
なかなか使えますね。
HadoopへファイルをGo言語でインポートしたくて、webhdfs経由で入れようとしていました。
そこでGoのクライアントライブラリーを使ってやってみたんだけど、少し苦労したって話です。
Go言語でアプリを作ってて、webhdfsのクライアントになんかいいのないかなーと探してたら下記を見つけたのでこれでやってみることにした。
github.com
なんかアフリカ系のすごい笑顔がでてきたな。。。
これを探すまでにちょっと驚いたんだけどGo言語でHadoopのクライアントライブラリーってほとんど無いみたいね。
ここで探してもたった2つしかない。。。
webhdfs - Go libraries and applications
まあそうだろうね、webhdfsのREST apiがあるから別に誰かのクライアント使わなくても自分でURLとかちゃんと書いてnet/http使えばなんとかなりそうだからな。
でも面倒なことしたくなかったのでクライアント使うことにしました。
READMEをもとにして書いてやってみたんですが、なんかエラーがでる。
import os import github.com/vladimirvivien/gowfs fs, err := gowfs.NewFileSystem(gowfs.Configuration{Addr: "hoge.com:14000", User: "hdfs"}) if err != nil{ log.Fatal(err) } fp, _ := os.Open("hoge.txt.gz") ok, err := fs.Create( fp, gowfs.Path{Name:"/hoge/hoge.txt.gz"}, false, 0, 0, 0700, 0, )
invalid character '<' looking for beginning of value
ソースのエラーのところをfmt.Printlnでいろいろと表示させて調べたところ、content-typeが適切に入ってないことがわかってきた。
Content-Type:[text/html;charset=utf-8]
<u>Data upload requests must have content-type set to 'application/octet-stream'</u><
なので仕方がないので、forkして修正してPRしてみた。
github.com
fs.Create()の末尾にcontent-type 入れたらそれを設定できるというもの。
ok, err := fs.Create( fp, gowfs.Path{Name:"/hoge/hoge.txt.gz"}, false, 0, 0, 0700, 0, "application/octet-stream" // <--- これな! )
本家のコミットを見てみると、3,4年更新されてないからPRも取り込まれることもないでしょう。
なので動作確認はしたけどテストコードは試してない。。。
テストコード書いた方がいいんだけどなあ....
お疲れ様でした。
git clone しようとしたらタイトルのようなエラーが出ました。
詳しく言うと、go get でgitのソースコードを落としてこようとしたら、なぜかSSL connect error が出ました。
その解消法です。
go get やったところこんなエラーが出ました。
go get github.com/vladimirvivien/gowfs # cd .; git clone https://github.com/vladimirvivien/gowfs /home/apdev/go/src/github.com/vladimirvivien/gowfs Cloning into '/xxxxxxx/go/src/github.com/vladimirvivien/gowfs'... fatal: unable to access 'https://github.com/vladimirvivien/gowfs/': SSL connect error package github.com/vladimirvivien/gowfs: exit status 128
はて、SSL connect error とは.....。
じゃあsrc 配下のところで、git clone もしてみました。
git clone https://github.com/vladimirvivien/gowfs.git Cloning into 'gowfs'... fatal: unable to access 'https://github.com/vladimirvivien/gowfs.git/': SSL connect error
同じようなエラー。。。
export GIT_CURL_VERBOSE=1
を設定しておくと、git で操作するときに詳細なログがでてきます。
もう一回 git cloneしてみる。
git clone https://github.com/vladimirvivien/gowfs.git ....... * Connected to xxx.xxxx.xxxx (11.22.33.44) port 8080 (#1) * NSS error -12190 * Expire cleared * Closing connection #1 fatal: unable to access 'https://github.com/vladimirvivien/gowfs.git/': SSL connect error
NSS error -12190 .... !?
ググってみると、nss というものが古いのかもとかでてきます。
yum update したほうがいいそうです。
sudo yum update -y nss
そんでgit clone してみるとうまくいきました!
ついでにファイル消してgo get もやってみるとうまくいった!!
git clone https://github.com/vladimirvivien/gowfs.git go get github.com/vladimirvivien/gowfs
GIT_CURL_VERBOSE これは使えるなあ。
ファイル名にタイムスタンプを入れたいなと思ってtime パッケージを調べてたんですが、便利な機能が意外とあるなと気づいたのでまとめてみました。
ちょくちょく調べることがあるので、この際いっきにまとめてみることにした。
自分でも知らない機能があったりして、面白かったです。
まずはドキュメントを読んでみましょう。
time - The Go Programming Language
あ部分的にDocumentのExampleを参考にしてるところはあります。
package main import ( "fmt" "time" ) func main() { fmt.Println("----- Basic usage -----") now := time.Now() fmt.Println("Now() : " + now.String()) time1 := time.Date(2018, 10, 15, 16, 48, 32, 12345, time.Local) fmt.Println("Date() : " + time1.String()) fmt.Println() }
----- Basic usage ----- Now() : 2018-12-21 18:02:48.2384152 +0900 JST m=+0.024933301 Date() : 2018-10-15 16:48:32.000012345 +0900 JST
Now()で現在時刻、Date()で特定の時刻が取れます。
ここ以降は出力の関係上 "strconv"もimportしておいてください。
また説明が重複するため、package, import 部分は省略します。
日時などの値を単純に取得する関数です。
fmt.Println("----- Functions -----") fmt.Println("Year() : " + strconv.Itoa(now.Year())) fmt.Println("Month() : " + now.Month().String()) fmt.Println("int(now.Month()) : " + strconv.Itoa(int(now.Month()))) fmt.Println("Day() : " + strconv.Itoa(now.Day())) fmt.Println("Hour() : " + strconv.Itoa(now.Hour())) fmt.Println("Minute() : " + strconv.Itoa(now.Minute())) fmt.Println("Second() : " + strconv.Itoa(now.Second())) fmt.Println("Nanosecond() : " + strconv.Itoa(now.Nanosecond())) fmt.Println("Weekday() : " + now.Weekday().String()) fmt.Println("int(Weekday()) : " + strconv.Itoa(int(now.Weekday()))) fmt.Println("Unix() : " + strconv.FormatInt(now.Unix(), 10)) fmt.Println("UnixNano() : " + strconv.FormatInt(now.UnixNano(), 10)) iso_year, iso_week := now.ISOWeek() fmt.Println("ISOWeek() year : " + strconv.Itoa(iso_year)) fmt.Println("ISOWeek() week : " + strconv.Itoa(iso_week)) fmt.Println()
----- Functions ----- Year() : 2018 Month() : December int(now.Month()) : 12 Day() : 21 Hour() : 18 Minute() : 2 Second() : 48 Nanosecond() : 238415200 Weekday() : Friday int(Weekday()) : 5 Unix() : 1545382968 UnixNano() : 1545382968238415200 ISOWeek() year : 2018 ISOWeek() week : 51
Month()だと英文字の月名になるのが要注意ですね。なんで数字じゃないんだ。
intでキャストする必要があります。Weekday()、お前は許す。。
またIOSWeek()は年の53週間中何週間目がわかるから便利ですね。
じゃあ年月日を取るために Year(), Month(), Day() を使うか~っと思ったら、ちょっと待って。
下記のDate(), Clock()が便利ですよ。
fmt.Println("----- Simple Functions -----") fmt.Println("Date() Clock()") year, month, day := now.Date() hour, min, sec := now.Clock() fmt.Println(year) fmt.Println(int(month)) fmt.Println(day) fmt.Println(hour) fmt.Println(min) fmt.Println(sec) fmt.Println() fmt.Println("YearDay() : " + strconv.Itoa(now.YearDay())) fmt.Println("UTC() : " + now.UTC().String()) fmt.Println("Local() : " + now.Local().String()) zoneName, zoneOffset := now.Zone() fmt.Println("Zone() (name,offset) : " + "( " + zoneName + " , " + strconv.Itoa(zoneOffset) + " )") fmt.Println()
----- Simple Functions ----- Date() Clock() 2018 12 21 18 2 48 YearDay() : 355 UTC() : 2018-12-21 09:02:48.2384152 +0000 UTC Local() : 2018-12-21 18:02:48.2384152 +0900 JST Zone() (name,offset) : ( JST , 32400 )
Date()、Clock()はいっきに取れるから便利ですね。
また年間で何日目なのか?がわかるYearDay()も便利ですね。
UTC←→JST変換も楽です。zone情報もわかりますね。
時間の加算減算です。
fmt.Println("----- Add -----") fmt.Println("Now() : " + now.String()) now = now.Add(time.Duration(-1) * time.Hour) fmt.Println("time 1 hour ago : " + now.String()) fmt.Println()
----- Add ----- Now() : 2018-12-21 18:02:48.2384152 +0900 JST m=+0.024933301 time 1 hour ago : 2018-12-21 17:02:48.2384152 +0900 JST m=-3599.975066699
もちろんHour以外にもDayでもMinuteでも可能です。
指定のフォーマットから文字列時刻を読み取ります。
fmt.Println("----- Parse() Format()-----") var timeParse01 = time.Time{} timeParse01, _ = time.Parse("2006/01/02 15:04:05 (MST)", "2018/12/21 13:45:38 (JST)") fmt.Println("Parse() : " + timeParse01.String()) fmt.Println("Format(ANSIC) : " + timeParse01.Format(time.ANSIC)) fmt.Println("Format(RFC1123Z) : " + timeParse01.Format(time.RFC1123Z)) fmt.Println()
----- Parse() Format()----- Parse() : 2018-12-21 13:45:38 +0900 JST Format(ANSIC) : Fri Dec 21 13:45:38 2018 Format(RFC1123Z) : Fri, 21 Dec 2018 13:45:38 +0900
パースするところで、2006とか01とか02ってなんなんだ?と思ってましたが、そういうフォーマットの指定方法なんですね。
ここを見るとわかります。
src/time/format.go - The Go Programming Language
const ( ANSIC = "Mon Jan _2 15:04:05 2006" UnixDate = "Mon Jan _2 15:04:05 MST 2006" RubyDate = "Mon Jan 02 15:04:05 -0700 2006" RFC822 = "02 Jan 06 15:04 MST" RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone RFC850 = "Monday, 02-Jan-06 15:04:05 MST" RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone RFC3339 = "2006-01-02T15:04:05Z07:00" RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" Kitchen = "3:04PM" // Handy time stamps. Stamp = "Jan _2 15:04:05" StampMilli = "Jan _2 15:04:05.000" StampMicro = "Jan _2 15:04:05.000000" StampNano = "Jan _2 15:04:05.000000000" ) const ( _ = iota stdLongMonth = iota + stdNeedDate // "January" stdMonth // "Jan" stdNumMonth // "1" stdZeroMonth // "01" stdLongWeekDay // "Monday" stdWeekDay // "Mon" stdDay // "2" stdUnderDay // "_2" stdZeroDay // "02" stdHour = iota + stdNeedClock // "15" stdHour12 // "3" stdZeroHour12 // "03" stdMinute // "4" stdZeroMinute // "04" stdSecond // "5" stdZeroSecond // "05" stdLongYear = iota + stdNeedDate // "2006" stdYear // "06" stdPM = iota + stdNeedClock // "PM" stdpm // "pm" stdTZ = iota // "MST" stdISO8601TZ // "Z0700" // prints Z for UTC stdISO8601SecondsTZ // "Z070000" stdISO8601ShortTZ // "Z07" stdISO8601ColonTZ // "Z07:00" // prints Z for UTC stdISO8601ColonSecondsTZ // "Z07:00:00" stdNumTZ // "-0700" // always numeric stdNumSecondsTz // "-070000" stdNumShortTZ // "-07" // always numeric stdNumColonTZ // "-07:00" // always numeric stdNumColonSecondsTZ // "-07:00:00" stdFracSecond0 // ".0", ".00", ... , trailing zeros included stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted stdNeedDate = 1 << 8 // need month, day, year stdNeedClock = 2 << 8 // need hour, minute, second stdArgShift = 16 // extra argument in high bits, above low stdArgShift stdMask = 1<<stdArgShift - 1 // mask out argument )
fmt.Println("----- ParseDuration() -----") duration, _ := time.ParseDuration("2h35m10s") fmt.Println("Duration String : " + duration.String()) fmt.Println("Duration in minutes : " + strconv.FormatFloat(duration.Minutes(), 'f', 0, 64)) fmt.Println("Duration in seconds : " + strconv.FormatFloat(duration.Seconds(), 'f', 0, 64)) fmt.Println()
----- ParseDuration() ----- Duration String : 2h35m10s Duration in minutes : 155 Duration in seconds : 9310
X時間は何分/何秒なのか?とかの単位変換計算が楽ですね。
fmt.Println("----- Sub() Since() Until() -----") time01 := time.Date(2018, 10, 10, 15, 0, 0, 0, time.Local) time02 := time.Date(2018, 10, 25, 15, 30, 10, 0, time.Local) fmt.Println("time01 : " + time01.String()) fmt.Println("time02 : " + time02.String()) time01Subtime02 := time01.Sub(time02) time02Subtime01 := time02.Sub(time01) fmt.Println("time01.Sub(time02) : " + time01Subtime02.String()) fmt.Println("time02.Sub(time01) : " + time02Subtime01.String()) fmt.Println() time03 := time.Date(2018, 12, 10, 0, 0, 0, 0, time.Local) time04 := time.Date(2018, 12, 25, 0, 0, 0, 0, time.Local) fmt.Println("Now() : " + now.String()) fmt.Println("time03 : " + time03.String()) fmt.Println("time04 : " + time04.String()) fmt.Println("time.Since(time03) : " + time.Since(time03).String()) fmt.Println("time.Until(time04) : " + time.Until(time04).String()) fmt.Println()
----- Sub() Since() Until() ----- time01 : 2018-10-10 15:00:00 +0900 JST time02 : 2018-10-25 15:30:10 +0900 JST time01.Sub(time02) : -360h30m10s time02.Sub(time01) : 360h30m10s Now() : 2018-12-21 17:02:48.2384152 +0900 JST m=-3599.975066699 time03 : 2018-12-10 00:00:00 +0900 JST time04 : 2018-12-25 00:00:00 +0900 JST time.Since(time03) : 282h2m48.3541336s time.Until(time04) : 77h57m11.6458664s
二つの時間の差分がSub()、その時間から今までがSince()、今から未来の時間までがUntil()。
このへんも便利ですね。
自分はUnixタイムで比較してましたが、こんな関数もあるんですね。
fmt.Println("----- After() Before() -----") date11m := time.Date(2018, 11, 1, 0, 0, 0, 0, time.Local) date12m := time.Date(2018, 12, 1, 0, 0, 0, 0, time.Local) fmt.Println(date11m) fmt.Println(date12m) isDate11mAfterDate12m := date11m.After(date12m) isDate12mAfterDate11m := date12m.After(date11m) fmt.Printf("date11m.After(date12m) = %v\n", isDate11mAfterDate12m) fmt.Printf("date12m.After(date11m) = %v\n", isDate12mAfterDate11m) isDate11mBeforeDate12m := date11m.Before(date12m) isDate12mBeforeDate11m := date12m.Before(date11m) fmt.Printf("date11m.Before(date12m) = %v\n", isDate11mBeforeDate12m) fmt.Printf("date12m.Before(date11m) = %v\n", isDate12mBeforeDate11m) fmt.Println()
----- After() Before() ----- 2018-11-01 00:00:00 +0900 JST 2018-12-01 00:00:00 +0900 JST date11m.After(date12m) = false date12m.After(date11m) = true date11m.Before(date12m) = true date12m.Before(date11m) = false
After()、Before() の方がわかりやすい!可読性がよくなります。これまで時間の比較はエポックタイムで比較してたんですが、これならぱっと見わかりやすくなりますね。
時差があっても同じ時刻か比較してくれます。
fmt.Println("----- Equal() -----") //時差が8時間の "Beijing Time" を作ります。 secondsEastOfUTC := int((8 * time.Hour).Seconds()) beijing := time.FixedZone("Beijing Time", secondsEastOfUTC) timeBeijin := time.Date(2018, 2, 1, 20, 15, 45, 0, beijing) timeUtc := time.Date(2018, 2, 1, 12, 15, 45, 0, time.UTC) fmt.Println(timeBeijin) fmt.Println(timeUtc) datesEqualUsingEqualOperator := timeBeijin == timeUtc datesEqualUsingFunction := timeBeijin.Equal(timeUtc) fmt.Printf("datesEqualUsingEqualOperator = %v\n", datesEqualUsingEqualOperator) fmt.Printf("datesEqualUsingFunction = %v\n", datesEqualUsingFunction) fmt.Println()
----- Equal() ----- 2018-02-01 20:15:45 +0800 Beijing Time 2018-02-01 12:15:45 +0000 UTC datesEqualUsingEqualOperator = false datesEqualUsingFunction = true
timeBeijinとtimeUtcは時刻は違いますが、地球上の上では時差を換算すると同じ時刻ですからTrueになりますね。
(UTC)12:15 + (Beijin時差)8hr = (Beijin Time)20:15
それにしても上記のように書けば時差ロケーションが作れるんですね。
時間の四捨五入ってめんどくさいですよね。でもこれなら簡単です。
fmt.Println("----- Round() Truncate() -----") time10 := time.Date(2018, 12, 10, 14, 15, 45, 987654321, time.Local) fmt.Println("time10 : " + time10.String()) fmt.Println("time10.Round(time.Hour) : " + time10.Round(time.Hour).String()) fmt.Println("time10.Round(time.Minute) : " + time10.Round(time.Minute).String()) fmt.Println("time10.Round(time.Second) : " + time10.Round(time.Second).String()) fmt.Println("time10.Truncate(time.Hour) : " + time10.Truncate(time.Hour).String()) fmt.Println("time10.Truncate(time.Minute): " + time10.Truncate(time.Minute).String()) fmt.Println("time10.Truncate(time.Second): " + time10.Truncate(time.Second).String()) fmt.Println()
----- Round() Truncate() ----- time10 : 2018-12-10 14:15:45.987654321 +0900 JST time10.Round(time.Hour) : 2018-12-10 14:00:00 +0900 JST time10.Round(time.Minute) : 2018-12-10 14:16:00 +0900 JST time10.Round(time.Second) : 2018-12-10 14:15:46 +0900 JST time10.Truncate(time.Hour) : 2018-12-10 14:00:00 +0900 JST time10.Truncate(time.Minute): 2018-12-10 14:15:00 +0900 JST time10.Truncate(time.Second): 2018-12-10 14:15:45 +0900 JST
Round()は四捨五入みたいな感じで、Truncate()は切り捨てですね。
IsZero() ってなんのためにあるのかな?と思ってましたが、確認のためのようですね。
fmt.Println("----- IsZero() -----") timeRight, _ := time.Parse("2006/01/02 15:04", "2018/12/21 13:45") // Parse処理で不適切な形式をしてして失敗させます。 timeFail, _ := time.Parse("2006/01/02 15:04", "2018/12/21 hoge 13:45") fmt.Println("timeRight : " + timeRight.String()) fmt.Println("timeFail : " + timeFail.String()) fmt.Println("timeRight.IsZero() : " + strconv.FormatBool(timeRight.IsZero())) fmt.Println("timeFail.IsZero() : " + strconv.FormatBool(timeFail.IsZero())) fmt.Println()
----- IsZero() ----- timeRight : 2018-12-21 13:45:00 +0000 UTC timeFail : 0001-01-01 00:00:00 +0000 UTC timeRight.IsZero() : false timeFail.IsZero() : true
なるほど。パースに失敗すると0001-01-01....になってしまうんだな。
こちらです
Tips of Tim pkg
qiitaにも書いてみました。
qiita.com
たまたまgzファイルを一つにまとめる(=コンカチする)必要があったのでやってみました。
読み込んだ時にバイナリーで扱ってるんで、そのまま書いてしまえばいいから簡単ですね。
こんな感じの適当なファイルを用意しました。
cat aaaaa.txt aaaaaaaaaa AAAAAAAAAA 1111111111
cat bbbbb.txt bbbbbbbbbb BBBBBBBBBB 2222222222
cat ccccc.txt cccccccccc CCCCCCCCCC 3333333333
それをそれぞれgzipに固めておきます。
gzip aaaaa.txt gzip bbbbb.txt gzip ccccc.txt
ls aaaaa.txt.gz bbbbb.txt.gz ccccc.txt.gz
このgzファイルをtest_dirに入れておきます。
実行する前にフォルダ構成を提示しておきます。
pack_gz_files.go test_dir aaaaa.txt.gz bbbbb.txt.gz ccccc.txt.gz
pack_gz_files.go
concatenate gz files
https://golang.org/pkg/os/#OpenFile
func OpenFile(name string, flag int, perm FileMode) (*File, error)
ファイルのポインタが取得できます。
https://golang.org/pkg/io/ioutil/#ReadFile
func ReadFile(filename string) ([]byte, error)
読み込んだファイルはバイト型で返されます。
https://golang.org/pkg/os/#File.Write
func (f *File) Write(b []byte) (n int, err error)
読み込んだバイトデータを引数にそのままバイト型を与えればいいわけですね。
go run pack_gz_files.go test_dir
実行すると output.txt.gzができます。
それを解凍してみると...。
gunzip output.txt.gz cat output.txt aaaaaaaaaa AAAAAAAAAA 1111111111 bbbbbbbbbb BBBBBBBBBB 2222222222 cccccccccc CCCCCCCCCC 3333333333
うまくコンカチされてますね。
たまにこんなエラーが出て戸惑いました。
あんまり調べなかったせいか、ググってもよくわからなくて引数かな?とか考えてたら全然違ってました。
結論から言うと、返り値が2つなのに1変数しか受取ろうとしてないぞ!
っていうエラーでした。
アホ過ぎて、よく調べろよ!っと自分に言いたくなったので書いておきます....。
ちなみにですが、自分のエラーが出た状況は下記です。
ファイルを読み込みたかったんですね。
gzFile := ioutil.ReadFile(file)
こんなエラーが出ます
multiple-value ioutil.ReadFile() in single-value context
「一つの値の中で複数の値になってるよ!」って感じでしょうかね。
ReadFile
ioutil - The Go Programming Language
をよく見るとちゃんと書いてありますね。
func ReadFile(filename string) ([]byte, error)
返り値は 「[]byte, error」の2つです。
ちゃんとerror も受け取るように修正しました。
gzFile, err := ioutil.ReadFile(file)
ドキュメントよく読めよ(自分)。。。
まあでももうちょっとエラー出力の内容がわかりやすくなってたらなあ。。。
サンプルソースを写経していて、どーしても気になるところがあったのでメモしておきます。
blog.suganoo.net
(※2018/12/06更新 全然ちがうところ修正していたので更新)
ここの部分の .User.Name が無いせいで、スレッドを表示させる時に開始ユーザーが表示されません。
gowebprog/index.html at 46faa1da9ae329950ec5f94d903a3165646e9085 · mushahiroyuki/gowebprog · GitHub
開始ユーザー {{ .User.Name }} - 開始日 {{ .CreatedAtDate }} - 投稿数 {{ .NumReplies }}。
上のリンクは翻訳者のかたのリンクなのですが著者の方にもありません。
というのもUserの結果ってバインディングされてないように見えます。
gowebprog/user.go at 46faa1da9ae329950ec5f94d903a3165646e9085 · mushahiroyuki/gowebprog · GitHub
type User struct { Id int Uuid string Name string Email string Password string CreatedAt time.Time } type Thread struct { Id int Uuid string Topic string UserId int // <--- 単純に UserId だけでUserとはバインディングされていない CreatedAt time.Time }
なのでサンプルをそのまま使っても、そりゃ表示されないです。
自分なりにFolkして修正してみました。今日は疲れてしまったのでしっかり動作確認してないし、眠いので動かなかったらご勘弁。
なのでプルリクも出すつもりもありません。参考までにしてください。
全体修正箇所github.com
まずThreadについては下記のようにUser構造体を持つようにしました。
update UserofThread · suganoo/gowebprog@09b5348 · GitHub
type Thread struct { Id int Uuid string Topic string UserOfThread User //<--- これ CreatedAt time.Time }
そんでThreads()でThreadを取ってくる時に、ちゃんとidでUserのデータも取ってくるように修正。
ついでに UserById関数も作っておきました。
gowebprog/thread.go at 09b53482accc3750b04017ab4b6c0b33fcc62249 · suganoo/gowebprog · GitHub
func UserById(id string) (user User, err error) { user = User{} err = Db.QueryRow("SELECT id, uuid, name, email, password, created_at FROM users WHERE id = $1", id). Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.Password, &user.CreatedAt) return }
スレッドを取ってくるところ
for rows.Next() { conv := Thread{} userId := "" if err = rows.Scan(&conv.Id, &conv.Uuid, &conv.Topic, &userId, &conv.CreatedAt); err != nil { return } user := User{} user, err := UserById(userId) if err != nil { return } conv.UserOfThread = user threads = append(threads, conv) }
表示側のindex.htmlも .UserOfThread.Nameで表示できるようになります。たぶん。
update UserofThread · suganoo/gowebprog@09b5348 · GitHub
開始ユーザー {{ .UserOfThread.Name }} - 開始日 {{ .CreatedAtDate }} - 投稿数 {{ .NumReplies }}。
こんな風にユーザー名が取れました。
テストコードまで直す気力はも無いので寝ます。。。。
サンプルコードは参考までに読むものですね。
著者の方も結構issue出てるのに、全然答える気配がないようです。もう興味が薄れてしまったんでしょうかね。
あと運用上気になった所として下記の2つのアクセスが頻繁になると思われます。
おそらく運用上はkey value的なredisを使うとかした方がDBのアクセスを減らせるでしょう。
先日読んだこの本について内容がよかったので写経に挑戦してます。
blog.suganoo.net
やっと9割がた終わったのですが、とてもよかったです。
写経するの実は今回初めてだったのですが、かなり学びになりますね。
ところどころ端折ってしまったところも多々あるのですが、8割?おおすじの内容は写経して、ローカル環境でchatサービスの一応動くところまで写経できたので良しとしてブログにここまでの感想を書いておこうと思います。
本の中ではhtmlに値を渡すところは、一つの値を渡すところしか書いてなかったんですね。
「これ複数の値渡したいときどーしたらいいんだろ?」っと思ってましたが、第二章のchitchatのところをやることでわかりました。
DBデータ取得→構造体にバインディング→構造体を渡すことでhtmlに値を渡すことができます。
書いといてよ。。。
細かいところですが、サンプルソースでUser.Nameを使ってるがどこから渡してるのか???となるところがありました。
詳しくはissueを見てみてください。
https://github.com/mushahiroyuki/gowebprog/issues/4
著者のgithubにも書いたけど、回答してくれそうにないね。。。
https://github.com/sausheong/gwp/issues/14
DBはこれまでMySQLくらいしか使ったことなかったので、初めてPostgreSQL使ってみました。
DB一覧やテーブル一覧を表示させたいときコマンドが全然違うので戸惑いますね。
勉強になりました。
そうサンプルのログアウト処理が、これでいいのかな?と思いました。
ログアウトするごとにwarningメッセージだしてたり、cookieのリミット何もしてなかったり。
同じ事考えてる人がいたようで、こっちのissueを参考にしました。
https://github.com/sausheong/gwp/issues/4
これもせめて本の中で説明しておいてくれよと思ったところでした。
chitchatの章で書いてあるけど、1ページくらいで書いておいてほしかったなあと。
https://github.com/mushahiroyuki/gowebprog/blob/master/ch02/chitchat/main.go#L14
今回は簡単なWebプログラミングを学んで動かしてみたかったのでほぼテストコードを書いてませんでした。
よくないですねー。。。
まあこれは自分でも動かしたときに「あれ?なんでこれちゃんと表示されないんだー?」っと思ったら、たいていがSQLのスペルミスでした。
"insert int "とか "created_at, FROM"とか。。。。
テストコードかかないですすめると、めちゃめちゃデバッグに時間がかかった。。
いつもはちゃんとテストコード書いてるからな。。。
ちゃんとプラットフォームにデプロイまでしないと、できたって言わないからな。
うん、はしょりました。あとでやります。。。。
テキストにあるように html/template モジュールつかって、テンプレートの使い方が学べたのが良かった。
そう思ったのはRailsをやったとき、いきなりerbとか使ってたりして、そもそもerbテンプレートはどう使うものなのか?とか解説がないままにすすめられることが多くて違和感を感じてしまったからです。
まあフレームワークを学んでから、内部がどうなってるかを学ぶのもありだとは思うけどもねえ。。。
とりあえず今後Goのフレームワークを使うとき、中身の実装が想像しやすくなっていいだろうと思います。
上述したんですが、サンプルソースわからないところgithubのissueで質問したんですが出版からしばらくたってもこんな質問をうけるなんて、すごい大変だなと思います。
どんな内容だったなんて覚えてられないよ。
その点著者のgithubでは、issueにまーったく答えてないのはまあそうだよなと思いましたw。
ということでだいたいのGoによるWebプログラミングがわかったので次はGinに挑戦してみようかなと思います。
その前にテストコードしっかり書いてみたりしますが。
見返してみても、並行処理やjsonの扱い、テストコード、ベンチマークテストなども書かれてるのでかなり学びになった本でした。
かなりおすすめです。
Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る impress top gearシリーズ
サンプルソースを修正してみました。
blog.suganoo.net