Go Conference in Fukuoka に参加してきました!
はじめに
2019/7/13 の Go Conference'19 Summer in Fukuoka - Go Conference'19 Summer in Fukuokaに沖縄から学生スカラシップで参加してきました。このブログを書いているのがGoConから一夜明けた7/14で、空港のラウンジでぐったりしながらやってます(した)。イベントのクロージングでは「福岡はレベルが高い」との話もありましたが、個人的にはかなーりレベル高かったんじゃないかと思いたいところ。知らないけどまあなんとか聞いたことあるというレベルからはじまり、さっぱり聞いたこともないというレベルまで、「あー、わかるわかる」という瞬間がほとんどなかった(ありがたいことです)。
記事は最終的に僕が後から今回のGoConを振り返る資料みたいな出来上がりになってしまいました。それぐらい濃くて正直僕が一回ではほとんどろ過できなかったということです。スピーカー様の資料とメモったキーワードから実際に試しながら、少しずつろ過して情報をきれいに整理していければいいかなと思っています。
学生スカラシップスポンサー様のオフィスツアー
前日は、学生スカラシップスポンサーのアイキュードシステムズ様でオフィスツアー実施していただきました。
ビルを移転したのが4月ということで、とってもきれいなオフィスでした。そして何より立地が素晴らしいです。
Ruby をメインに採用されている企業様でした。最近は必要に応じて(gRPC等) Go を取り入れ始めているそうです。オフィスツアーの後はGoConFukuokaのあれが見たいとかこっちも気になるなどのお話をしました。また、当日は裏側も見れるようにと、発表予定のトークを事前に聞かせていただきました。ありがとうございます。
また、懇親会を企画してくださり、モツ鍋をごちそうになりました!
実はモツ鍋は初めてだったのですが、初体験を本場福岡でできたことを非常に幸運に感じております。とっても美味しかったです。
イベント
僕が見てきたトークを時系列に載せました。1つ1つについてきちんとまとめたい気持ちはありましたが、僕がこうギュッとまとめられるほどわかってはいないため資料を貼りました。
「Backlog」のGitを支えるサービス達をGoで刷新した技術的プラクティス
福岡市長様トーク
Image processing with Go has great potential!!
Deep "delve" into Delve:the debugger for the Go
Golang BFF with GraphQL and gRPC
感想
ブログ書きながら思いましたが、一日で結構タイトに詰め込まれていますね。そりゃ疲れるはずだ(めちゃくちゃ勉強になりました)!!
個人的に面白かったのは(というより身近な話題だったのは)「Goで作る進化計算パッケージ」のお話で、僕も進化論のアルゴリズムを調べてGoで実装してみようかなという野望の芽を抱いたり。あとは、gRPCは使ったことがなかったので実際に触ってみたいですし(今回のGoConではホットだった印象)、Observabilityに関しても山口さんが実演してくださったため真似してやってみようかなと思っています。実況ツイートの中でもGoのpprofがいいというツイートも見かけましたね、僕は全く知りませんでしたが。
また、僕が今回のGoConに来る前に1つ絶対やると決めていたことがあります。それは、渋川よしきさんとお話することです。Goならわかるシステムプログラミング(紙書籍+PDF版) – 技術書出版と販売のラムダノートにはWeb連載の時からめちゃくちゃお世話になりましたし、翻訳を担当されたエキスパートPythonプログラミング改訂2版 Michal Jaworski:生活・実用書 | KADOKAWAも僕にとって1つ先に進むために非常に勉強させていただいた本だったので、GoConにいらっしゃるということで勝手にテンション上がってました。LTの直後に少しお話できたので無事目標達成。くいなし。
あとは、近いうちに読もうと思っていたO'Reilly Japan - Go言語による並行処理の翻訳を担当された山口さん(当日発表を聞いて知った)や、他にも本を執筆されていたり雑誌に記事を掲載している人が多かった。
つまり、結論はやっぱりレベル高かったということですね。それでも、自分が注力していることで知らないことをいっぱい知れる機会ってそんなにないので、メモったことをきちんと試して身につけていければと思います。
Goで bufio.Scanner.Scan からfalseが返ってくる問題
はじめに
最近Goで競技プログラミングに挑戦し、ものの見事に叩きのめされました...。気持ちを一新してアルゴリズムの勉強をはじめたのですが、Goでbufio.Scanner.Scanからfalseが返ってきてるらしく、サイズの大きい一行を読み込むことができないという問題に直面したため、調査をしました。
false が返ってくるパターン
var sc = bufio.NewScanner(os.Stdin) func readLine() { sc.Scan() return sc.Text() }
基本的には、上のコードをベースに読み込み部分を書いていました。
このコードで今まで入力を取得し、いくつか問題を説いてきたのですが、ある問題で突然止まってしまい...。エラーメッセージもないため、原因不明でお手上げ状態。
原因の調査
ググって見ると簡単にヒットしました。
1行の長さがScannerのBuffer量をオーバするとスキャンをやめてしまうみたい。
バッファサイズはデフォルトでbufio.MaxScanTokenSize(65536)バイトとあるので実際にどうなのか試してみる.
package main import ( "bufio" "fmt" ) func main() { fmt.Println(bufio.MaxScanTokenSize) }
65536
そのとおりですね。
go doc で調べた結果。
$ go doc bufio.MaxScanTokenSize
const ( // MaxScanTokenSize is the maximum size used to buffer a token // unless the user provides an explicit buffer with Scan.Buffer. // The actual maximum token size may be smaller as the buffer // may need to include, for instance, a newline. MaxScanTokenSize = 64 * 1024 )
そして、ちゃんとエラーメッセージはあったみたいです。
if err := sc.Err(); err != nil { fmt.Println("Scanner error: %q\n",err) }
参考にしたブログの中で、
実際Scan()後にErr()を確認しないコードを何度か見たことがある
と述べられており、まさに私でした。
Goは error 型が返ってくると思っていたので、この確認の仕方は知りませんでした。
素晴らしい記事をありがとうございます。
エラーメッセージがわかりさえすれば、失敗している原因がわかります。
Scanner error: "bufio.Scanner: token too long"
ニャルほど。
最初は sc.Scan をみても false だけで、何で失敗しているのか検討もつかなかったけれど、エラーメッセージを見るとトークンが長すぎたのが原因であったということがわかりました。
でも、この問題をC言語で解いたときは、特につまらなかったはず...。
と、いうことでC言語で書いた入力部分のソースを見直してみた。
scanf("%d", &n); for ( i = 0; i < n; i++) scanf("%d", &A[i]);
1数字ずつ読み込んでいたわけか...理解。
ひとまずは、なんとか読み込みさせるためにC言語のソースっぽく実装してみる。
var A []int var n int fmt.Scan(&n) for i := 0; i < n; i++ { var a int fmt.Scan(&a) A = append(A, a) }
取り替えず、読み込めました。(パチパチ)
これでエラーの原因と、回避策がわかりました。
結局どうすればいいのか
ググっているなかで以下のサイトを見つけました。
競技プログラミングに関してで、私が躓いたことが全部掲載されている。
以下引用
- 簡単に書きたいとき
- fmt.Scanを使う
- たくさん(10 ^ 5)読み込みたいとき
- bufioのScannerを使う
- 長い行( > 64 * 10 ^ 3)読み込みたいとき
- bufioのReadLineを使う
ちなみに、
sc.Split(bufio.ScanWords)
で、スペース区切りにできるみたい。デフォルトは bufio.ScanLines で一行取得。
僕は bufio.Scanner を使った時は、1行を取得した後、以下のようにスペース区切りで分割していたので手間が減りそうです。
for _, v := range strings.Split(sc.Text(), " ") { num, _ := strconv.Atoi(v) list = append(list, num) }
コードを書き換えてみました。
package main import ( "bufio" "fmt" "os" "strconv" ) var sc = bufio.NewScanner(os.Stdin) func nextInt() int { sc.Scan() i, _ := strconv.Atoi(sc.Text()) return i } func main() { var n int var A []int fmt.Scan(&n) sc.Split(bufio.ScanWords) for i := 0; i < n; i++ { A = append(A, nextInt()) } fmt.Println(A) }
できた!
競技プログラミングでは、入力の個数が事前に与えられるのでそれは fmt.Scan で取得します。今回は自作関数の nextInt() で取得しても大丈夫そうです。
さて、残る問題は fmt.Scan と bufio.Scanner のどっちが早いかです。
比べてみた
測定用に、以下のコードを作成した。
package main import ( "bufio" "fmt" "os" "strconv" "strings" ) var sc = bufio.NewScanner(os.Stdin) // fmt.Scanを用いて入力を読み込む func try_scan() []int { var n int var A []int fmt.Scan(&n) for i := 0; i < n; i++ { var a int fmt.Scan(&a) A = append(A, a) } return A } func nextInt() int { sc.Scan() i, _ := strconv.Atoi(sc.Text()) return i } // bufio.Scannerでbufio.ScanWordsを指定して入力を読み込み func try_scanwords() []int { var n int var A []int fmt.Scan(&n) sc.Split(bufio.ScanWords) for i := 0; i < n; i++ { A = append(A, nextInt()) } return A } // 僕がこれまでやっていた手法 func splitElements() []int { var n int var A []int fmt.Scan(&n) sc.Split(bufio.ScanLines) sc.Scan() //if err := sc.Err(); err != nil { // fmt.Printf("Scanner error: %q\n", err) //} for _, v := range strings.Split(sc.Text(), " ") { num, _ := strconv.Atoi(v) A = append(A, num) } return A } func main() { // try_scan() // try_scanwords() // splitElements() // fmt.Println(a) }
測定には、AOJのPartition | Aizu Online Judgeの入力ファイルを用い、手元にダウンロードした後、以下のようにして測定した。
$ cat in | /usr/bin/time go run main.go
測定には以下のコードを使用し、main 関数の関数呼び出しのコメントアウトを変えて、手法の切り替えを行った。
timeコマンドを用いて測定したところ、私の環境では問題の入力に対しては bufio.Scanner は fmt.Scanよりも明らかに高速に動きました。
個人的に面白かったのは、私が今まで使っていた入力取得用関数 splitElements の挙動で、エラー処理をしていないと、そのままプログラムは進みスライスAを返してくる。ただし、Aは空のスライスで、その配列の要素にアクセスしようとして...バーンという感じになってしまうことがわかったことです。
まとめ
これまでは bufio.Scanner で1行取得後スペース区切りに分割という方法で、不自由なく問題を解いて来ましたが、1行が大きくなりすぎる場合には注意が必要だということがわかりました。
これを回避する方法としては、fmt.Scan を使って取得する方法もありますが、競技プログラミングはスペース区切りで値が与えられることが多いため、bufio.ScanWords を設定して取得すると、より高速に取得できます。
Swiftでcsvファイルの連続カンマを置換で消してみた
お久しぶりになりました。自分用のブログを作ろうとしばらく運用してみたのですが、私にはまだ早かったらしくこちらに戻ってまいりました。
さて、今回はSwiftのお話になります。
タイトルの通り、csvファイルのから連続するカラムを正規表現の置換によって取り除いたという内容です。
Google スプレッドシートに書かれた内容を読み取り、その内容にそって処理を走らせるというプログラムを書きました。
ただ、少し困ったことに、スプレッドシートのから指定されるパラメータは固定数ではなく、そのあたりをどう対処するか悩みどころでした
name | key | value | key | value |
---|---|---|---|---|
login | ||||
share | item_id | id | content_type | ctn |
level_up | level | 3 | character | shogo |
↑こんなやつです。
csv形式に治すと
login,,,, share,item_id,id,content_type,ctn level_up,level,3,character,shogo
今回処理の中で、邪魔になってくるのがカンマが連続する行。
let result = (String(data: data, encoding: String.Encoding.utf8) ?? "").components(separatedBy: "\r\n") for v in result let c = v.components(separatedBy: ",") }
↑のような感じで(動くかは知らない)、カンマ区切りのリストを作って処理していたのですが、カンマが続く行は空のリストができてしまう。
そこで、カンマ区切りのリストにする前に、文字列を置換することで解決しました。
let result = (String(data: data, encoding: String.Encoding.utf8) ?? "").components(separatedBy: "\r\n") for v in result { let repStr = v.replacingOccurrences(of: "(^.*?)(,,*)$", with: "$1", options: .regularExpression) let c = repStr.components(separatedBy: ",") }
解決。
とはいっても、今回はスプレッドシートの書き方に依存するので、スプレッドシート側でミスされるとプログラムがしにます。
正規表現の達人なら、そのあたりも含めていい感じにやれるようにできるのでしょう。
ああ、正規表現を操りたい。
TFUG Okinawa vol.1 に参加してきました。
参加した感想
全体的にためになるお話を聞くことができました。
僕はTourchというライブラリを使ってCNNなどをやっていましたが、TensorFlowに乗り換える時かもしれないですね。
最初のyabooさんのセッションでTensorFlowの事例紹介でそう感じました。
次のfujiwaraさんのセッションでは、Wifiが繋がらなくなるというトラブルに遭遇。
その対応のため、セッションを集中して聞くことができなかった...。
みんなの感想では、すごくわかりやすかったとのことなので、悔しい。
続いて再度yabooさんのセッション。
個人的にはすごく難しかったです。
それでもコード自体は全然追っていくことができて、あとは本当にTenforFlowの使い方を理解しないとダメかなっていう印象。
EstimatorとLow APIについて掘り下げて学習する必要があるかな。
研究の分野的にも「んー、わかんない」では済まされないので、復習して自分のものにします。
最後にLTセッション。
みなさん、ハキハキプレゼンして、会場には笑いが起こりいい雰囲気だなーって思いました。
いずれ僕もLTを...する...ぞ。
懇親会で「Kaggleは健康を悪くする」という興味深いお話を聞けたので、この辺りを次のLTにしたいところ。
そのためにはとりあえず手を動かせってことですね。
懇親会では、登壇者の方と近い席に座ることができて貴重なお話を聞けました。
正直、ちょっと独占しすぎたかなとも思ったりしたり。
印象的なのは、最近は機械学習のコンポーネントを学習するネットワークとかが出てきているという話。
GCPのAutoML visionとかは、画像にラベルつけてブッコムだけてかなりの精度で分別してくれるらしい。
そういえば、学部の時に友人が「AI系も多分将来的にAI自身が自動でやるから、職なくなると思ってる」と言ってたのを思い出しました。
んむむ。僕は将来職を失うのか?と不安を感じたりしましたが、fujiwaraさん曰く、それは「最終的なゴール」とのこと。
僕も技術の発展に貢献できるよう、今は勉強するのみです。
運営感想
今回は縁があって運営の方もさせていただきました。
4人ともこういったイベントの運営経験がなく、ドタバタしたと思います。
運営的にはいくつか反省する部分があって、Wifiが繋がらなくなったというトラブルであったり、ターゲットのミスマッチであったりタイムテーブルの過密さであったり。今まで参加させてもらう側だったので運営の方のありがたさが身にしみて理解できたような気がします。
今回の反省を生かして、次はより良い勉強会になるよう頑張っていければなと思いました。
ほんとい良い経験をさせていただきました!!ありがとうございます。
Goでファイルサーバ!!
背景
困ったことがおこりました。
Unityで作成したゲームを遠く離れた我が弟にやらせてみようと思った時です。
ファイルサイズが大きすぎて、弟に渡す手段がない!!
メールなどには容量の関係で添付できないし、Slackとかなら送れるかもしれないけど弟はPCに疎いので無理。
ということで、弟に渡すために配布サーバ(ファイルサーバ)を作りました。
※ 僕もPCに疎いので、1GBを超えるようなファイルをうまく送れる方法があったら教えて欲しいです。
実装
どうやってつくればいいのか...とググっているとhttp.FileServerなるものがあることを知りました。
package main import ( "flag" "log" "net/http" "path/filepath" "sync" "text/template" ) type templateHandler struct { once sync.Once filename string templ *template.Template } func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t.once.Do(func() { t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename))) }) t.templ.Execute(w, nil) } func main() { var addr = flag.String("addr", ":8000", "addres of Application") flag.Parse() // メインページ http.Handle("/", &templateHandler{filename: "main.html"}) // ファイルサーバ http.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir("./uploaded")))) log.Println("Start Web Server. Port", *addr) if err := http.ListenAndServe(*addr, nil); err != nil { log.Fatal("ListenAndServe:", err) } }
main.html
<!DOCTYPE html> <html> <head> <title>配布用ページ</title> </head> <body> <p>好きに落としていいよ</p> <a href="/files/games.zip">作ったゲーム</a> </body> </html>
構造
. ├── main.go ├── templates │ └── main.html └── uploaded └── games.zip <- 渡したいファイル
スッキリわかるJava入門を読みました。
前置き
気がつけば前回の記事投稿から20日も経ってしまった...。まあ、GWだったんで言い訳の余地なくサボってましたけどね。時間が空いて改めて感じるのはブログにまとめると頭の片隅に残るけど、サクッと読んだ本は何も覚えてないということです。ちょうど20日までにWebの本を読んだはずですが綺麗にさっぱり。サボらないでブログにまとめようと決意しました。この理由だと、完全に自分専用のまとめノート的な役割です。
背景
最近アプリ制作のアルバイトを始めました。アプリ開発の途中から入ったため、すでにある膨大なコードを読み解いて機能を実装していく必要があります。単に実装をするだけならできなくもないのですが、そこにはキチンとルールがあるわけで理解していない僕は結構注意を受けたり呆れられたりしてます。僕自身もコードの構造になぜ?などの疑問を感じることも多く、その原因を考えるとおそらくオブジェクト指向プログラミングの知識が不十分というのが大きいでしょう。今まで1人で書くことが多くコードも大規模になることがなかったため目をそらしてきた部分でもありますが、もう逃げられません。そこでオブジェクト指向の復習としてこの本のいくつかの章を読むことにしました。
この本は2年くらい前に購入して、おそらく今回で3周(満遍なくではない)したことになるはずです。もともとは大学の講義でJavaを学習するためその参考書として購入したのですが、オブジェクト指向についてわかりやすく解説されている本なので、該当部分だけ読むことにしました。ちなみにアプリの開発言語はC#です。
目的
実際に開発はC#なので文法的な面は違っていたりすると思います。今回はこの本の内容を理解することで、C#で似たような場面に出くわした時に、「多分こういうことだろう」という仮説を立てることができ、その仮説に沿って調べることができるということを目指します。
つまり、オブジェクト指向の基礎を抑えます。
内容
読んだ章は
- 11章 継承
- 12章 高度な継承
- 13章 多態性
です。
今更がっつり全部を読む時間もないですし。自分があんまりよくわかってないと思う部部分を読んだつもりです。
11章 継承
数えるほとですが、コード量を抑える目的で継承を使ったことはあります。本の内容も概ねその通りで、規模が大きくなると似通ったクラスがたくさんできてしまうため、追加・修正の手間と把握や管理をキチンとするために継承を使うと書かれていました。
文法的には以下のようになり、extendsで継承します。
class クラス名 extends 継承するクラス名{ 親クラスとの差分メンバ; }
差分に関しては、親クラスに同じメンバがない場合はそのメンバは新しく追加され、同じメンバがある場合はそのメンバを上書き(override)します。
C#書いてたときは override を記述してオーバーライドしてた気がする...。
あとは、クラスの宣言時にfinalをつければ継承できなくなる(メソッドは子クラスでオーバーライドできない)。
superで親インスタンスにアクセスできる。
上記のsuperとは別にsuper();で親のコンストラクタを呼び出す。
最後は「is - a」の関係を守って継承をしようという話でした。
まあこの辺りの内容は多分自分でググればなんとかなる内容でした。はい、次!
12章 高度な継承
ここからです。おそらく、1,2回目のときはこの辺りからわからなくなったんだと思います。
最初にこの章に関しては「あいまいなクラスたち」の定義を学習すると述べられていました。
この章を学習するにあたって開発の立場を2つに分け、片方の立場の人が使うと考えると理解しやすいと書かれています。
立場1.ゴリゴリ開発してる人(既存のクラスを継承して子クラスを作る)
立場2.最初に別の開発者のためにクラスを準備する人(親クラスを作る)
アルバイトの僕の立場は1です。完全には理解していませんが、あるクラスを継承しないとコンパイルが通らないことが多いので継承してどんどん機能を付け足しています。やっぱり継承先はある程度把握しておこうと思って、とりあえず親クラスをたどっていくと、ほとんど何も書かれていないクラスにたどり着きます。つまりこのほとんど何も書かれていないクラスを作る立場2の人目線で考えるとわかりやすいとのことです。
高度な継承に関する不都合として、
- クラスを作る段階で詳細が未定なメソッドがある。
- そのクラスを継承して使うのかインスタンス化して使うのか。
そこで抽象クラスというもの出てきます。抽象クラスはnewによるインスタンス化が禁止され、(Javaでは)抽象メソッドを含むクラスは必ず抽象クラスにしなければならないという決まりがあります。
抽象メソッド例
アクセス修飾子 abstract 戻り値 メソッド名(引数);
詳細未定なメソッドを抽象メソッドとして宣言すると、将来の開発者にオーバーライドを強制できる...ん?なんかめちゃくちゃ覚えがある(笑)
バイトであるメソッド呼び出したいのにエラーで呼び出せず、親クラスまで辿るとただの定義しかなくて、先輩に確認したらオーバーライドしてって言われたやんけ!!ちなみに先輩はそのあとに「...いかんなぁ」とこぼしてます。これもう口癖になったんじゃないかねぇ。
な・る・ほ・ど!
次の話はインターフェース。
インターフェースとして扱うには2つの条件を満たす必要あり。
- 全てのメソッドは抽象メソッド
- 基本的にフィールドを持たない
すると、
アクセス修飾子 interface インターフェース名 {
}
としてインターフェースを定義できる。
クラス名ではなくインターフェース名となってるところが地味にポイント。インターフェースを継承して子クラスを定義する場合は、extendsではなくimplementsを使う。僕が最近勉強しているGoでも出てきましたが、インターフェースくんはすごい。
- 同じインタフェースをimplementsする子クラスたちに共通のメソッド群の実装を強制できる(うぅ!)
- あるクラスがインターフェースをimplimentsしていればそのインターフェースが定めたメソッドを持っていることが保証されちゃう。(ちゃんとしないとエラーはかれちゃうからね)
Javaでは多重継承は禁止されているが、インターフェースには許されているのだ!!
はい、この章の内容を事前に抑えておけばアルバイトで苦労しなかったであろうことがわかりましたね!次が最後!
13章 多態性
これは特別は文法があるわけではなくて、考え方の話。
書籍中では『「あるものを、あえてザックリ捉えること」でさまざまなメリットを享受しようというもの』と記述されています。
これまでで、抽象クラスやインタフェースはインスタンス化することができないと述べましたが、型として利用することは可能なんです。
僕の理解が不十分かもしれませんが、ここではその型がポイントになりまして、型を決定することでどのメソッドが「呼び出せるか」を決定することができ、型の中身(実際の型)でメソッドオン動作を決定することができるみたいです。
何が嬉しいのか。
確かに型、型、型を厳密に決めてしまうと色々不便になりますね。
この辺はGoのインターフェースと照らし合わせて理解ができました。
インタフェースの決まりさえ満たせば、同じものとして扱えちゃいますからね。
まとめ
今回読んだ内容は、今の僕にとってかなりの収穫になったんじゃないかと思います。良い具合にピースが当てはまったという感触でした。ただ、さすがに1回読んだだけでこの内容を理解することは相当難しいと思います。
最近はバイト先で「Singletonって何ですか?」って聞いたら「...いかんなぁ」っていわれたので次はデザインパターンを抑えていくべきでしょうかね。やっぱりみんなで開発するときは共通のルールを理解しておかないと足引っ張るってことがわかりました。
突貫工事で英単語学習ツールを作る
はじめに...
ちょー適当な記事です。そして、筆者はWeb開発の経験がないです!!
背景
最近英語とのエンカウント率がぐっと上昇。もはや彼を避けるすべなし。
僕は彼との正面からの戦いを決意した…。
と、いうことで、英語学習の中で英単語学習のための単語保存ツールを作ることにしました。ただ、今回の趣旨としては「英単語学習ツールを作るぞ!!」ではなくて、「もうなんでもいいから英単語を保存できて見返せるやつはよ」状態なので動けばいいやの精神でいろんなことをすっ飛ばしてやります。
ほんとに現在進行形で困ってる。
概要
英語論文とかを読むとき知らない単語それはもういっぱい出てくる。その時々で調べるけれど、頭に残らず次にであった時は「あ、この単語前もみた!」で意味を覚えていない。そこで、わからなかった単語を調べて、例文とともに登録すれば覚えるための材料になるのではと考えた。
僕の思い浮かべるフローは以下の通り。
単語の保存
- フォームに単語と例文を入れて登録ボタンを押す。
- DBに書き込まれる。
登録した単語の表示
- URLを叩く。
- DBからデータを取ってくる。
- 登録した単語たちを表示。
これで、僕がどんな単語にであって、どんな文で出会ったのがわかる(はず)。
作成開始
技術力がないので、パクリ実装をします。
ディレクトリの構成はこんな感じ。
. ├── db.go ├── main.go └── templates ├── index.html └── register.gtpl
main.go
package main import ( "fmt" "log" "net/http" "path/filepath" "sync" "text/template" _ "github.com/lib/pq" ) type templateHandler struct { once sync.Once filename string templ *template.Template } func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t.once.Do(func() { t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename))) }) t.templ.Execute(w, r) } func register(w http.ResponseWriter, r *http.Request) { fmt.Println("method:", r.Method) //リクエストを取得するメソッド if r.Method == "POST" { r.ParseForm() InsertData(r.Form["word"][0], r.Form["example"][0]) } http.Redirect(w, r, "/", 301) } type Data struct { Words []string Examples []string } func show(w http.ResponseWriter, r *http.Request) { words, examples := SelectData() p := Data{ Words: words, Examples: examples, } tmpl := template.Must(template.ParseFiles(filepath.Join("templates", "index.html"))) tmpl.Execute(w, p) } func main() { http.Handle("/", &templateHandler{filename: "register.gtpl"}) http.HandleFunc("/register", register) http.HandleFunc("/show", show) log.Println("Webサーバを開始します") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal("ListenAndServe:", err) } }
まず、大元は前回のブログ記事で扱った本のコードをパクりました。
nishisi.hatenablog.com
付加要素として、フォームから入力を取ってくる必要があったのでその部分の処理は以下のドキュメントを参考にさせていただきました。
https://astaxie.gitbooks.io/build-web-application-with-golang/ja/04.1.html
今回は突貫なのでこのページしか読んでいませんが、時間を作ってしっかり読んでおきたいところ。コードをみてちょこちょこいじっただけだからほとんどわかってない...。
register.html
<html> <head> <title></title> </head> <body> <form action="/register" method="post"> 英単語:<input type="text" name="word"> 例文:<input type="text" name="example"> <input type="submit" value="登録"> </form> </body> </html>
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登録リスト</title> </head> <body> <h3>登録英単語リスト</h3> <ul> {{range $i, $v := .Words}} <li>{{$v}} {{index $.Examples $i}} </li> {{end}} </ul> </body> </html>
Goのテンプレートもわからず、どうやってスライスを渡して表示すればいいのか...という悩みに下の記事が解決してくれました。ありがとうございます。
golang : template の range で index を使う - i++
最後はデータベース。初めてPostgreSQLを使って見ました。セットアップは以下の通り。
$ go get github.com/lib/pq # goのパッケージをダウンロード $ brew install postgresql # brewを使ってインストール $ brew services start postgresql # postgersqlのサービス開始 $ createdb eng-words # データベースを作成 $ psql eng-words # データベースにアクセス # データベースの作成 eng-words=# CREATE TABLE wordlist (uid integer, word character varying(20) ,example character varying(200)) WITH (OIDS=FALSE);
データベースも2年ほど前授業で触ったきりだから、よく覚えていない。あとはGoでデータを出し入れできればOK。以下参考にしたドキュメント。
https://astaxie.gitbooks.io/build-web-application-with-golang/ja/05.4.html
db.go
package main import ( "database/sql" _ "github.com/lib/pq" ) func InsertData(word string, example string) { db, err := sql.Open("postgres", "dbname=eng-words sslmode=disable") checkErr(err) stmt, err := db.Prepare("INSERT INTO wordlist(word,example) VALUES($1,$2) RETURNING uid") checkErr(err) _, err = stmt.Exec(word, example) checkErr(err) } func SelectData() ([]string, []string) { var words []string var examples []string db, err := sql.Open("postgres", "dbname=eng-words sslmode=disable") rows, err := db.Query("SELECT word,example FROM wordlist;") checkErr(err) for rows.Next() { var word string var example string err = rows.Scan(&word, &example) checkErr(err) words = append(words, word) examples = append(examples, example) } return words, examples } func checkErr(err error) { if err != nil { panic(err) } }
あとは、コンパイルして実行するだけ。
突貫結果
http://localhost:8080 にアクセスして単語と例文を入力し登録ボタンを押す。
http://localhost:8080/showにアクセスするとDBに登録された内容が表示される。
まとめ
急ピッチで動くことだけを考えて作ってつもりが、ここまでくるのにエラーなどにも捕まって5時間かかりました。そして!作ったのはいいけど使う気しないーー。まあ自分でも色々ツッコミどころはあります。見た目がダサいとかログが全く出力されないとか入力を検査してないとか etc。まだまだGo自体もよくわかっていないので、今後の学習とともにアップグレードをしていけばいいかなと思います。登録すればDBにとりあえず書き込まれるので、我慢して使ってみます...。