- 投稿日:2019-10-09T18:56:46+09:00
Goを始めて1年間で最高にお世話になったGo関連ブックマークを晒します。
自分は普段はChromeのブックマークを使ってよく見返す記事を保存しています。Goを一年間書いてきてブックマークを整理したのですが、せっかくなのでお世話になったブックマーク記事を晒します。
Blog & Serial
The Go Blog
Goの公式ブログ。深いところまでしっかり書かれているので、調べたいトピックはまずはここで調べたい。
https://blog.golang.org/Practical Go
GoのcontributorであるDave Cheneyさんのブログです。Goで開発&運用する上でのアドバイスが書かれており、入門記事だけでは得られないノウハウがふんだんにまとめられています。
https://dave.cheney.net/practical-goGoならわかるシステムプログラミング
@shibukawaさんの連載です。Goで低レイヤーを学んでいきます。根底の仕組みがガッツリ学べます。あまりに良すぎて僕は書籍で購入してます。書籍の方は色々加筆されているので皆さんも書籍で書いましょう。
https://ascii.jp/elem/000/001/235/1235262/A Journey With Go
Goに関する面白いトピックが多いです。Goの内部の話などGo初心者が中級者になるための記事が多い印象です。
https://medium.com/a-journey-with-goTips
Go CodeReviewComments 日本語翻訳 #golang
Goを書く上で気をつけるべき&意識すべきポイントが網羅された「Go CodeReviewComments」の日本語訳記事
https://qiita.com/knsh14/items/8b73b31822c109d4c497High Performance Go Workshop
Goアプリケーションのパフォーマンスに関するトピックほぼ全てが網羅された記事。ベンチマーク、プロファイリングはもちろん、コンパイラ最適化、メモリやガベージコレクタについてなどについて書かれている。はっきり言って最高。
https://dave.cheney.net/high-performance-go-workshop/gopherchina-2019.htmlProfiling Go Programs
Goの公式ブログによるプロファイリングツールpprofの使い方です。使い方忘れたらよくここにきます。
https://blog.golang.org/profiling-go-programsSliceTricks
Goの公式WikiのSlice操作の実装パターン集です。いい感じのSlice操作実装を忘れた時にめちゃ見返す。
https://github.com/golang/go/wiki/SliceTricksインタフェースの実装パターン #golang
インターフェースの実装パターンがコード付きで丁寧に解説されています。
この記事を書いている @tenntenn さんの記事はどれもGo初心者にとって知っておいたほうがいい事ばかりなので他の記事も読んでみることをオススメします。
https://qiita.com/tenntenn/items/eac962a49c56b2b15ee8Architecture
struct に依存しない処理は function に切り出すのか、method に切り出すのか
関数にするかメソッドにするかはGopherたちが悩む部分です。迷ったら読み返します。
https://www.pospome.work/entry/2017/01/16/233351Golang Receiver vs Function Argument
こちらも処理を関数にするかメソッドにするか迷った時に読み返します。
https://grisha.org/blog/2016/09/22/golang-receiver-vs-function/Functional Option Pattern
構造体の初期化時にオプション引数を与えるためのデザインパターンである「Functional Option Pattern」の日本語解説です。
https://blog.web-apps.tech/go-functional-option-pattern/Analysis
GoのためのGo
Goで静的解析する際の基本が体系的にまとまっています。
https://motemen.github.io/go-for-go-book/Handson
Write a Kubernetes-ready service from zero step-by-step
Goで作ったAPIをKubernetesで動かす記事ですが、Kubernetes動かすまでにGoによるAPI開発の基本的なポイントがしっかり抑えられていてGoを触り始めた頃にすごく勉強になったハンズオンです。
https://blog.gopheracademy.com/advent-2017/kubernetes-ready-service/Test
Goのtestを理解する in 2018 #go
テストの書き方でここってどうやって書いたっけ?となったら必ず訪れるブログ。ほぼテストに関する全てが書いてある。
https://budougumi0617.github.io/2018/08/19/go-testing2018/Go Fridayこぼれ話:非公開(unexported)な機能を使ったテスト #golang
非公開の機能をテストしたい時のパターンが網羅されている。
https://tech.mercari.com/entry/2018/08/08/080000Packages
Awesome Go : 素晴らしい Go のフレームワーク・ライブラリ・ソフトウェアの数々
こんなパッケージないかな??って時に開く記事。
https://qiita.com/hatai/items/f31914f37dc6c53b2bcePlayground
GoDoc Playground
GoDocのPlaygroundです。GoDocを書く際にコメントアウトで記載していきますがそれをリアルタイムで確認できます。
https://bradleyjkemp.dev/godoc-playground/Editor
vim-go チュートリアル
vim-goのチュートリアルです。GoをVimで操りたくなったらまずここ。
https://github.com/hnakamur/vim-go-tutorial-javim-goを使うなら使用したいコマンド集と設定
VimでGoを触るためのノウハウが体系的にまとまっています。
https://qiita.com/gorilla0513/items/a027885d03af0d6d5863Detects Issue
Go Report Card
GoのリポジトリのURLを入れるだけでgolintやgofmtでコードをチェックしたりmisspellを見つけたりしてくれます。最終的にREADME.mdに貼れる上記のようなバッジがすぐに作れます。
GolangCI
こちらもGoのリポジトリに対して様々な問題を指摘してくれます。structcheck、errcheck、gosimpleなどのツールで細かい問題点も指摘してくれます。こちらもバッジが作れます。
- 投稿日:2019-10-09T17:30:28+09:00
GolangでHTMLのtemplateに値を渡す方法
はじめに
Goでは
http/template
を使用してhtmlのテンプレートに値を渡すことができます。具体的には
- 変数
- スライス
- マップ
- 構造体
などの値です。
今回は、基本となる変数と、使用機会が一番多いであろう構造体について説明していきます。
Web開発に使用する技術ですが、検索しても情報があまり出てこなかったので書きました。
環境
- macOS Catalina 10.15
- go 1.12.9
0.何も渡さず実行
メインのGoとhtmlファイルです。
これらを基本に説明していきます。
ディレクトリ構造. ├── main.go └── tpls.htmlmain.gopackage main import ( "html/template" "log" "os" ) var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("tpls.html")) } func main() { err := tpl.Execute(os.Stdout, nil) // dataは渡さない if err != nil { log.Fatalln(err) } }tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>hoge</h1> </body> </html>main.go を
go run main.go
で実行した結果がこちらです。実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>hoge</h1> </body> </html>⏎なんの変哲もありませんね。
今回はターミナルに標準出力しているので全文表示されますが、実際にはwebブラウザに読み込ませるので、hoge だけが表示されるはずです。
ではテンプレートの使い方を見ていきましょう。
1.変数の値を渡す
まずは変数を渡します。
tpl.Executeの第2引数に変数を指定することで渡せます。main.gopackage main import ( "html/template" "log" "os" ) var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("tpls.html")) } func main() { var Age = 235 err := tpl.Execute(os.Stdout, Age) // 変数を指定 if err != nil { log.Fatalln(err) } }次にhtmlです。
値を受け取るには{{.}}
の表記が必要になります。
独特の表記ですが基本となるので慣れてください。tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>{{.}}</h1> <!-- 値を受け取る --> </body> </html>実行結果がこちらです。
実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>235</h1> </body> </html>⏎値が渡されているのがわかります。
複数回値を受け取れる
なお、受け取った値は複数回表示したり、変数に代入することができます。
tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>{{.}}</h1> <!-- 同じ値を複数回受け取る事もできる --> <h1>{{.}}</h1> <h1>{{$Age := .}}</h1> <!-- 代入したときは表示されない --> <h1>{{$Age}}</h1> <!-- 値を表示 --> </body> </html>実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>235</h1> <h1>235</h1> <h1></h1> <h1>235</h1> </body> </html>⏎2.構造体の値を渡す
次に構造体の値を渡します。
実際の開発では構造体を使うことが一番多いでしょう。
具体的にはjsonから受け取った値を構造体に入れて、それを表示するといった感じでしょう。では、実行して行きます。
main.gopackage main import ( "html/template" "log" "os" ) type Person struct { Name string Age int } var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("tpls.html")) } func main() { p1 := Person{ Name: "hogefuga", Age: 28, } err := tpl.Execute(os.Stdout, p1) if err != nil { log.Fatalln(err) } }今回は
Name
とAge
の2つのフィールドを持った構造体を用意しました。
ベタですね。htmlはこちらです。
tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>{{.}}</h1> <h2>{{.Name}}</h2> <!-- フィールドにアクセスする --> <h2>{{.Age}}</h2> </body> </html>フィールドにアクセスするには
{{.Name}}
のようにドットに続けてフィールド名を書きます。一方で、
{{.}}
のみだと構造体がそのまま表示されます。
これは感覚そのままですね。では実行結果です。
実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>{hogefuga 28}</h1> <h2>hogefuga</h2> <h2>28</h2> </body> </html>⏎全く説明した通りの結果です。
3.構造体のスライスを渡す
実際の開発では、
[]hogestruct
として構造体を複数使用したい場合があるでしょう。こちらのケースも見ていきます。
main.gopackage main import ( "html/template" "log" "os" ) type Person struct { Name string Age int } var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("tpls.html")) } func main() { p1 := Person{ Name: "hogefuga", Age: 28, } p2 := Person{ Name: "hogeeeeeee", Age: 51, } p3 := Person{ Name: "fugagagaga", Age: 3, } var persons = []Person{p1, p2, p3} err := tpl.Execute(os.Stdout, persons) if err != nil { log.Fatalln(err) } }やっているのは構造体のスライスを渡しているだけです。
次にhtmlです。
スライスの全要素を順に表示するには{{range .}}
を使用します。
実際にコードを見るのが速いでしょう。tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <ul> {{range .}} <li>{{.Name}} - {{.Age}}</li> {{end}} </ul> </body> </html>
range
を使用するときは{{end}}
を忘れないようにしてください。では実行結果です。
実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <ul> <li>hogefuga - 28</li> <li>hogeeeeeee - 51</li> <li>fugagagaga - 3</li> </ul> </body> </html>⏎正しく表示されたと思います。
スライス要素に個別にアクセス
ちなみにスライスの要素に個別にアクセスしたいときは、html内でデフォルト関数の
index
を使用します。
これもコードを見たほうが速いでしょう。tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <ul> {{index . 1}} <!-- スライスの要素に個別にアクセス --> {{index . 2}} {{(index . 0).Name}} <!-- 要素のフィールドにアクセスすることも可能--> </ul> </body> </html>個別要素のフィールドにアクセスするには
{{(index . 0).Name}}
のように()
でくくる必要があります。それでは実行結果です。
実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <ul> {hogeeeeeee 51} {fugagagaga 3} hogefuga </ul> </body> </html>デフォルト関数は
index
の他にも多数存在します。
詳細はgolangのtemplateページのFunctionの部分を参照してください。
ページがtext/templateとなっていますが、同じように使えます。(html/templateのページで詳細が見当たらなかった)4.構造体のメソッドについて
templateでは構造体のメソッドを実行することも可能です。
ただしメソッドの実装は値渡しでないといけません。(ポインタ渡しはエラー)main.gopackage main import ( "html/template" "log" "os" ) type Person struct { Name string Age int } // func (p *Person) のようなポインタ渡しはエラーが出る func (p Person) DoubleAge() int { return p.Age * 2 } var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("tpls.html")) } func main() { p1 := Person{ Name: "hogefuga", Age: 28, } err := tpl.Execute(os.Stdout, p1) if err != nil { log.Fatalln(err) } }tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> {{.DoubleAge}} <!-- methodも書き方は同じ --> </body> </html>実行結果です。
実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <ul> 56 </ul> </body> </html>⏎Ageを2倍した値が表示されます。
おわりに
今回の記事は以上になります。
基本的な開発はこの記事の内容で十分行えると思います。
html/template
には関数を渡すfuncmap
や、template内でパイプ|
を使用する方法など、他にも様々な手法があります。
反響があれば記事を書こうと思います。最後まで読んでいただきありがとうございました。
- 投稿日:2019-10-09T16:51:10+09:00
Go の Web フレームワーク Echo で IT 用語クイズアプリを作ってみた
はじめに
近年、世間ではクイズが流行っています(?)が、IT 用語に特化したクイズのアプリってあんまりないような気がしたので作ってみました。
Go の勉強を最近始めたので、 echo でクイズのデータの JSON を返すだけの Web サーバをたてることにします。作ったもの
https://go-itquiz.herokuapp.com/
Github: https://github.com/chgzm/go-itquizサーバ側
Go そのものについては、始めて 3 日くらいなのでよくわかってませんが、echo の Guite を読めば大体問題なくできました。
- main 部分. heroku にデプロイするために、環境変数 PORT からポート番号を取得するようにする
main.gopackage main import ( "html/template" "io" "os" "github.com/chgzm/go-itquiz/data" "github.com/chgzm/go-itquiz/handler" "github.com/labstack/echo" ) type TemplateRenderer struct { templates *template.Template } func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { if viewContext, isMap := data.(map[string]interface{}); isMap { viewContext["reverse"] = c.Echo().Reverse } return t.templates.ExecuteTemplate(w, name, data) } func setRouting(e *echo.Echo) { e.GET("/", handler.HandleIndex) e.GET("/question", handler.HandleQuestion) e.POST("/data", handler.HandleData) } func setStatic(e *echo.Echo) { e.Static("/public/css", "./public/css/") e.Static("/public/js", "./public/js/") } func main() { e := echo.New() renderer := &TemplateRenderer{ templates: template.Must(template.ParseGlob("public/*.html")), } e.Renderer = renderer err := data.InitTable(e, "./data") if err != nil { e.Logger.Error(err) return } setRouting(e) setStatic(e) port := os.Getenv("PORT") if port == "" { port = "1323" } e.Logger.Fatal(e.Start(":" + port)) }
- ハンドラの処理. /data へのアクセスでランダムに 10 問選んで JSON 形式で返す
handler/handler.gopackage handler import ( "math/rand" "net/http" "time" "github.com/chgzm/go-itquiz/data" "github.com/labstack/echo" ) func HandleIndex(c echo.Context) error { return c.Render(http.StatusOK, "index.html", "") } func HandleQuestion(c echo.Context) error { return c.Render(http.StatusOK, "question.html", "") } func HandleData(c echo.Context) error { entries := data.GetAllEntries() extract := extractEntry(entries) return c.JSON(http.StatusOK, extract) } func extractEntry(entries []data.Entry) []data.Entry { extract := make([]data.Entry, 0, 10) selected := make(map[int]bool) rand.Seed(time.Now().UnixNano()) entLen := len(entries) i := 0 for { index := rand.Intn(entLen) if selected[index] == true { continue } selected[index] = true extract = append(extract, entries[index]) i++ if i == 10 { break } } return extract }
- テキストからクイズのデータを読み込んでメモリ上に保持する処理
data/table.gopackage data import ( "bufio" "io/ioutil" "os" "path/filepath" "strings" "github.com/labstack/echo" ) type Entry struct { ID int `json:"id"` Question string `json:"question"` Answer string `json:"answer"` Dummy1 string `json:"dummy1"` Dummy2 string `json:"dummy2"` Dummy3 string `json:"dummy3"` } type Tag struct { ID int Name string EntryIDs []int } var entryTable []Entry var tagTable []Tag func InitTable(e *echo.Echo, dir string) error { entryTable = make([]Entry, 0, 2048) tagTable = make([]Tag, 0, 64) // // init entry // ent := Entry{} tagIDMap := make(map[string]int) entryID := 0 tagID := 0 for _, path := range getFilePaths(dir) { f, err := os.Open(path) if err != nil { e.Logger.Error(err) return err } scanner := bufio.NewScanner(f) for scanner.Scan() { str := scanner.Text() if len(str) == 0 { continue } else if strings.HasPrefix(str, "##") { // add to TagTable tagNames := strings.Split(strings.TrimLeft(strings.TrimPrefix(str, "##"), " "), ",") for _, tagName := range tagNames { if id, ok := tagIDMap[tagName]; ok == false { tagIDMap[tagName] = tagID tag := Tag{} tag.ID = tagID tag.Name = tagName tag.EntryIDs = make([]int, 0, 256) tag.EntryIDs = append(tag.EntryIDs, entryID) tagTable = append(tagTable, tag) tagID++ } else { tag := tagTable[id] tag.EntryIDs = append(tag.EntryIDs, entryID) } } } else if str[0] == '#' { ent.Question = strings.TrimLeft(strings.TrimPrefix(str, "#"), " ") } else if str[0] == '1' { ent.Answer = strings.TrimLeft(strings.TrimPrefix(str, "1."), " ") } else if str[0] == '2' { ent.Dummy1 = strings.TrimLeft(strings.TrimPrefix(str, "2."), " ") } else if str[0] == '3' { ent.Dummy2 = strings.TrimLeft(strings.TrimPrefix(str, "3."), " ") } else if str[0] == '4' { ent.Dummy3 = strings.TrimLeft(strings.TrimPrefix(str, "4."), " ") // add to EntryTable ent.ID = entryID entryID++ entryTable = append(entryTable, ent) ent = Entry{} } } f.Close() } return nil } func GetAllEntries() []Entry { return entryTable } func GetEntries(tagID int) []Entry { entries := make([]Entry, 0, 1024) for _, entryID := range tagTable[tagID].EntryIDs { entries = append(entries, entryTable[entryID]) } return entries } func getFilePaths(dir string) []string { files, err := ioutil.ReadDir(dir) if err != nil { panic(err) } paths := make([]string, 0, 32) for _, file := range files { if file.IsDir() { paths = append(paths, getFilePaths(filepath.Join(dir, file.Name()))...) continue } if file.Name() == "question.md" { paths = append(paths, filepath.Join(dir, file.Name())) } } return paths }クライアント側
CSS や Javascript のフレームワークは使わず実装しました(使うほどの規模でもないので)。作ってる途中に気づきましたが、ドットインストール にクイズアプリ作るレッスンがあったので大いに参考にしました。
おわりに
- echo は簡単でよい。Go もよい感じ。
- 肝心のクイズを作るのが面倒なので誰か作って欲しい
- 投稿日:2019-10-09T16:51:10+09:00
Go の Web フレームワーク echo で IT 用語クイズアプリを作ってみた
はじめに
近年、世間ではクイズが流行っています(?)が、IT 用語に特化したクイズのアプリってあんまりないような気がしたので作ってみました。
Go の勉強を最近始めたので、 echo でクイズのデータの JSON を返すだけの Web サーバをたてることにします。作ったもの
https://go-itquiz.herokuapp.com/
Github: https://github.com/chgzm/go-itquizサーバ側
Go そのものについては、始めて 3 日くらいなのでよくわかってませんが、echo の Guite を読めば大体問題なくできました。
- main 部分. heroku にデプロイするために、環境変数 PORT からポート番号を取得するようにする
main.gopackage main import ( "html/template" "io" "os" "github.com/chgzm/go-itquiz/data" "github.com/chgzm/go-itquiz/handler" "github.com/labstack/echo" ) type TemplateRenderer struct { templates *template.Template } func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { if viewContext, isMap := data.(map[string]interface{}); isMap { viewContext["reverse"] = c.Echo().Reverse } return t.templates.ExecuteTemplate(w, name, data) } func setRouting(e *echo.Echo) { e.GET("/", handler.HandleIndex) e.GET("/question", handler.HandleQuestion) e.POST("/data", handler.HandleData) } func setStatic(e *echo.Echo) { e.Static("/public/css", "./public/css/") e.Static("/public/js", "./public/js/") } func main() { e := echo.New() renderer := &TemplateRenderer{ templates: template.Must(template.ParseGlob("public/*.html")), } e.Renderer = renderer err := data.InitTable(e, "./data") if err != nil { e.Logger.Error(err) return } setRouting(e) setStatic(e) port := os.Getenv("PORT") if port == "" { port = "1323" } e.Logger.Fatal(e.Start(":" + port)) }
- ハンドラの処理. /data へのアクセスでランダムに 10 問選んで JSON 形式で返す
handler/handler.gopackage handler import ( "math/rand" "net/http" "time" "github.com/chgzm/go-itquiz/data" "github.com/labstack/echo" ) func HandleIndex(c echo.Context) error { return c.Render(http.StatusOK, "index.html", "") } func HandleQuestion(c echo.Context) error { return c.Render(http.StatusOK, "question.html", "") } func HandleData(c echo.Context) error { entries := data.GetAllEntries() extract := extractEntry(entries) return c.JSON(http.StatusOK, extract) } func extractEntry(entries []data.Entry) []data.Entry { extract := make([]data.Entry, 0, 10) selected := make(map[int]bool) rand.Seed(time.Now().UnixNano()) entLen := len(entries) i := 0 for { index := rand.Intn(entLen) if selected[index] == true { continue } selected[index] = true extract = append(extract, entries[index]) i++ if i == 10 { break } } return extract }
- テキストからクイズのデータを読み込んでメモリ上に保持する処理
data/table.gopackage data import ( "bufio" "io/ioutil" "os" "path/filepath" "strings" "github.com/labstack/echo" ) type Entry struct { ID int `json:"id"` Question string `json:"question"` Answer string `json:"answer"` Dummy1 string `json:"dummy1"` Dummy2 string `json:"dummy2"` Dummy3 string `json:"dummy3"` } type Tag struct { ID int Name string EntryIDs []int } var entryTable []Entry var tagTable []Tag func InitTable(e *echo.Echo, dir string) error { entryTable = make([]Entry, 0, 2048) tagTable = make([]Tag, 0, 64) // // init entry // ent := Entry{} tagIDMap := make(map[string]int) entryID := 0 tagID := 0 for _, path := range getFilePaths(dir) { f, err := os.Open(path) if err != nil { e.Logger.Error(err) return err } scanner := bufio.NewScanner(f) for scanner.Scan() { str := scanner.Text() if len(str) == 0 { continue } else if strings.HasPrefix(str, "##") { // add to TagTable tagNames := strings.Split(strings.TrimLeft(strings.TrimPrefix(str, "##"), " "), ",") for _, tagName := range tagNames { if id, ok := tagIDMap[tagName]; ok == false { tagIDMap[tagName] = tagID tag := Tag{} tag.ID = tagID tag.Name = tagName tag.EntryIDs = make([]int, 0, 256) tag.EntryIDs = append(tag.EntryIDs, entryID) tagTable = append(tagTable, tag) tagID++ } else { tag := tagTable[id] tag.EntryIDs = append(tag.EntryIDs, entryID) } } } else if str[0] == '#' { ent.Question = strings.TrimLeft(strings.TrimPrefix(str, "#"), " ") } else if str[0] == '1' { ent.Answer = strings.TrimLeft(strings.TrimPrefix(str, "1."), " ") } else if str[0] == '2' { ent.Dummy1 = strings.TrimLeft(strings.TrimPrefix(str, "2."), " ") } else if str[0] == '3' { ent.Dummy2 = strings.TrimLeft(strings.TrimPrefix(str, "3."), " ") } else if str[0] == '4' { ent.Dummy3 = strings.TrimLeft(strings.TrimPrefix(str, "4."), " ") // add to EntryTable ent.ID = entryID entryID++ entryTable = append(entryTable, ent) ent = Entry{} } } f.Close() } return nil } func GetAllEntries() []Entry { return entryTable } func GetEntries(tagID int) []Entry { entries := make([]Entry, 0, 1024) for _, entryID := range tagTable[tagID].EntryIDs { entries = append(entries, entryTable[entryID]) } return entries } func getFilePaths(dir string) []string { files, err := ioutil.ReadDir(dir) if err != nil { panic(err) } paths := make([]string, 0, 32) for _, file := range files { if file.IsDir() { paths = append(paths, getFilePaths(filepath.Join(dir, file.Name()))...) continue } if file.Name() == "question.md" { paths = append(paths, filepath.Join(dir, file.Name())) } } return paths }クライアント側
CSS や Javascript のフレームワークは使わず実装しました(使うほどの規模でもないので)。作ってる途中に気づきましたが、ドットインストール にクイズアプリ作るレッスンがあったので大いに参考にしました。
おわりに
- echo は簡単でよい。Go もよい感じ。
- 肝心のクイズを作るのが面倒なので誰か作って欲しい
- 投稿日:2019-10-09T16:07:31+09:00
TSV,CSVファイルを読込んでみる
Shift_JISで書かれたtsvファイルの読込み処理をGOで書いてみました。
iconv-go を利用してみる[失敗編]
UTF-8で記述されたtsv/csvファイルを読込む場合、
encoding/csv
のみで対応できるのであるが、Shift_JISで記述されている場合、そのままだと文字化けを起こしてしまう。
そこで、パッケージを利用したのですが、うまくいかなかったた。main.gopackage main import ( "encoding/csv" "io" "os" iconv "github.com/djimenez/iconv-go" ) func main() { file, err := os.Open("XXXX.tsv") if err != nil { panic(err) } defer file.Close() // shif-jisの場合には以下の処理を追加 converter, err := iconv.NewReader(file, "sjis", "utf-8") if err != nil { panic(err) } //reader := csv.NewReader(file) reader := csv.NewReader(converter) // デリミタ設定(TSVなら\t, CSVなら,) reader.Comma = '\t' // コメント設定(コメント文字を指定) reader.Comment = '#' // ダブルクオートを厳密にチェックしない reader.LazyQuotes = true var line []string for { line, err = reader.Read() // 1行読み出し if err == io.EOF { break } else if err != nil { fmt.Println("読み込みエラー: ", line) panic(err) } // ここに処理を追記する } }エラー内容
なぜか、TSVファイルの15行目を読込むと、以下のエラーが発生する。
panic: The storage control blocks were destroyed.
15行目の内容を削除し、1行詰めても同じ場所でエラーが発生する。
原因が不明だったためパッケージを変更することに。text/transform を利用してみる[成功編]
main.gopackage main import ( "encoding/csv" "io" "os" "golang.org/x/text/encoding/japanese" "golang.org/x/text/transform" ) func main() { file, err := os.Open("XXXX.tsv") if err != nil { panic(err) } defer file.Close() // shif-jisの場合には以下の処理を追加 converter := transform.NewReader(file, japanese.ShiftJIS.NewDecoder()) //reader := csv.NewReader(file) // 取込ファイルがUTF8の場合、こっち reader := csv.NewReader(converter) // デリミタ設定(TSVなら\t, CSVなら,) reader.Comma = '\t' // コメント設定(コメント文字を指定) reader.Comment = '#' // ダブルクオートを厳密にチェックしない reader.LazyQuotes = true var line []string for { line, err = reader.Read() // 1行読み出し if err == io.EOF { break } else if err != nil { fmt.Println("読み込みエラー: ", line) panic(err) } // ここに処理を追記する } }こちらの方法だと問題なくtsvファイルを読込めた。
- 投稿日:2019-10-09T11:04:14+09:00
Goでその年の日数を数える
うるう年だったら366, それ以外だったら365になるやつ。
うるう年判定を実装してもいいのだけど、t.YearDay()というメソッドがあったので、12月31日がYearDayで何日目か調べるという技がありそう。package main import ( "fmt" "time" ) func main() { for y := 2010; y < 2021; y++ { fmt.Println(y, CountYearDay(y)) } } func CountYearDay(year int) int { lastday := time.Date(year, 12, 31, 0, 0, 0, 0, time.UTC) return lastday.YearDay() }実行結果
2010 365 2011 365 2012 366 2013 365 2014 365 2015 365 2016 366 2017 365 2018 365 2019 365 2020 366
- 投稿日:2019-10-09T01:13:38+09:00
なぜGoがWebアプリ開発に適しているのか
なぜGoがWebアプリ開発に適しているのか
このエントリはGoプログラミング実践入門を読んで、5分でもわかるように序章を軽くまとめたものです
大規模なWebアプリケーション開発用の言語が必要とする要素
- スケーラビリティ
- モジュール性
- 保守性
- 高速性
Goが上記をカバーしている
スケーラビリティ
アプリケーションの処理能力を素早く簡単に増強して処理できるリクエストの数を増やすことのできる能力
スケーラビリティの側面
- 垂直方向=単一のマシンでCPU数を増やして処理能力を向上
- Goのゴルーチンで効率的に高速に実行
- 水平方向=マシンの台数を増やして処理能力を向上
- Go Webアプリケーションの多数のインスタンスの上にプロキシを置く
- 動的依存性のない静的なバイナリとしてコンパイルされることを利用し、Goが組み込まれていないシステムへ分散
モジュール性
交換可能なコンポーネントを使って設計することで、機能の追加・削除・変更が容易になり、アプリケーションに対する要求変化に応えやすい
- 関数でインターフェースを引数としてとれるため、新規コード追加でも既存の関数に対してそのインターフェースが要求するメソッドを実装することで、その関数をそのまま使える
- 関数プログラミングのような機能(関数型、値としての関数、クロージャ)が数多く実装されているため、関数から別の関数を生成させることができる
近年話題の「マイクロサービス」中心のアーキテクチャとも親和性が高い
保守性
保守が容易なことは、機能改善・修正の際に時間がかからない・デグレを生みにくい
高速性
C言語の実行速度に近づける思想
- コンパイル型のため、インタプリタ型言語よりは一般的に速い
- ゴルーチンによる並列実行のサポート
- 投稿日:2019-10-09T01:01:00+09:00
サーバサイド初心者がニュートン法の例題を個人的に解こうとした話
いや、待ってめっちゃ数学やん。うふぇぇ苦手なんだよなぁ。。。
ってことでニュートン法についてまずウィキ様から引用します。
数値解析の分野において、ニュートン法(ニュートンほう、英: Newton's method)またはニュートン・ラフソン法(英: Newton-Raphson method)は、方程式系を数値計算によって解くための反復法による求根アルゴリズムの1つである。対象とする方程式系に対する条件は、領域における微分可能性と2次微分に関する符号だけであり、線型性などは特に要求しない。収束の速さも2次収束なので古くから数値計算で使用されていた。名称はアイザック・ニュートンとジョゼフ・ラフソンに由来する。
やっぱりわかんない。
結局やりたいことは、収束する点を探したいってことと、特定の桁数まで合致するようにif文とfor文を回せってことだと思う。
まず頭の中でどういうアルゴリズムであるべきかを想定する。
xの平方根を求める方法を考える。変数zを使う場合に、10回の試行でどれだけ近づけるかを考える。
一回書いてみっかーヒント見ながら。
package main import ( "fmt" "math" ) func Sqrt(x float64) float64 { z := float64(1) fmt.Println(math.Sqrt(x)) for i := 0; i < 9; i++{ z -= (z*z - x) / (2*z) } return z } func main() { fmt.Println(Sqrt(2)) }はい答え。。。
10回の試行で小数点以下16桁まで合うのすげぇ。
まあ、そうか偏差的に計算してるし、そらよっていくよね。1000からzを初めて10回試行で2ケタくらいだし。
おもしろいなぁ
- 投稿日:2019-10-09T00:59:31+09:00
AWS Lambda/GCP Cloud Functionsで、インスタンスID的なものが取れるか試してみた
結論から書いておくと、Lambdaはなんとか取れるが、Cloud Functionsは無理そう。
なぜこのようなことに挑戦しようかと思ったかというと、ソート可能だけどワーカー間の調整が不要なsnowflakeやらsonyflakeを使ってみたいな、と思ったからです。その場合、ワーカーID的なものが必要になります。LambdaもCloud Functionsも、適当な間隔でリクエストがあると、初期化された状態のインスタンス(AWSのドキュメントでは実行コンテキストと呼ばれる)を維持して、効率を上げる仕組みがあります。完全にリクエストが途絶えてしまうとインスタンスが落ちて、次の実行時に新たにインスタンスが起動されます。このインスタンス単位でIDが取得できたらなぁ、というのが最初の妄想です。
とりあえず、環境変数やらホスト名やら何やらをダンプしてみる。
Lambda
hostnameコマンドは使えず。プロセスIDやネットワークのIPアドレスなどは固定っぽかった。いろいろ見た感じだと、次のものがインスタンス起動ごとに変わりそう
- 環境変数: LAMBDA_RUNTIME_LOAD_TIME, LAMBDA_SERVER_PORT, AWSACCESS_KEY, AWSACCESS_KEY_ID, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
- ネットワーク: MACアドレス (net.Interfaces()で取得)
GCP Cloud Functions
hostnameはlocalhostしか帰ってこない。プロセスIDは1固定。ネットワーク関係はIPアドレスが固定。MACアドレスは空文字列で取得できず。環境変数などではかんたんに識別は難しそう。
時間を使ってみる?
Goの場合、どちらもinit()を使って共通初期化処理をインスタンス起動のたびに一度だけ実行する、というのが可能です。じゃあこれで起動時の時間を取ればインスタンスの識別は可能?
たしかにどちらも可能そうだし、ポータブルな方法ではありますが、64ビットの数値を適切な分解能を維持したまま16ビットにする(sonyflakeの場合)のはやや難しそう・・・
まとめ
という感じで、ぱっとフットワーク良く使える手法は今のところなさそうです。時間を使う手法は、格納するDBで情報種別によるビット数制限がなければ良さそうではありますが。もちろん、インスタンスを維持していることを想定したコードというのは本来のLambdaやCloud Functionsの趣旨からは外れるはずなので、できてしまったとしても、今後も同じ手法が継続して提供されるかというと難しい気がします。
サーバーレスでも使える、ElasticAccessCounter(キリ番ゲット機能付き)みたいなサービスが出てきてくれればいいんですけどね。
- 投稿日:2019-10-09T00:33:07+09:00
サーバサイド初心者がGoの基本構文を個人的に理解しようとした話
A Tour of Goも中盤に差し掛かり基本構文の章に差し掛かりました。
For文
- 普通のfor文
func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
- while文的な書き方
func main() { sum := 1 for ; sum < 1000; { sum += sum } fmt.Println(sum) }ということはもちろんセミコロンを排除できる
func main(){ sum := 1 for sum < 1000 { sum += sum } fmt.Println(sum) }スラっとした綺麗なコード、if文まだ触ってないからあれやけど、
for bool
もできるのでは?
- 無限ループ
func main(){ for { } }if文
- 典型的なif文構築
package main import ( "fmt" "math" ) func sqrt(x float64) string { if x < 0 { return sqrt(-x) + "i" } return fmt.Sprint(math.Sqrt(x)) } func main() { fmt.Println(sqrt(2), sqrt(-4)) }
- 前にステートメントを付けたとき
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { fmt.Println(v) return v } return lim }このコードを理解するのに時間がかかったくらいにはプログラミング初心者なのかもしれない()
最初の ` v := math.Pow(x,n); でステートメントを追加してvにxをn乗したものを代入して、
if v < limならこのメソッドは戻り値にvを返す。違うならlimをっていう形ですねー。マジでちょっと悩んだのが悔しい。
- else文
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } else { fmt.Printf("%g >= %g\n", v, lim) } return lim }elseを入れる場合もこのケースでのvなどのif文で生成した変数は、スコープ内として扱える。
ほう。。。なるほど。。。
割と簡単なケースが多くて割と安心したっていうのが正直なところっていう感じ。
Switch文
func main() { fmt.Println("When's Saturday?") today := time.Now().Weekday() fmt.Println(today) switch time.Saturday { case today + 0: fmt.Println("Today.") case today + 1: fmt.Println("Tomorrow.") case today + 2: fmt.Println("In two days.") default: fmt.Println("Too far away.") } }一般的な記法と同じでif else構文を短縮したもの。便利でいいなーってサンプルコードを読んでて思ったのは、time.Now().Weekday()って呼ぶだけで、当日の曜日がわかるのは正直激アツ。
- switch trueも可
func main() { t := time.Now() switch { case t.Hour() < 12: fmt.Println("Good morning!") case t.Hour() < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") } }なるほど。簡単
defer
これやばい、意味わかんね。やばい。
func main() { x := 1 defer fmt.Println(x) x = 2 fmt.Println("hello") } //出力結果 //hello //1deferはその呼び出し元のメソッドがリターンすると呼び出されるもの。参照はトップダウンで終わるが、呼び出しが実行後にくるって、なるほど。参照が先にされるのが少し自分的にははてながつくが、まぁそういうもんだろうと一旦流す。
- Last-in-First-Out
func main() { fmt.Println("counting") for i := 0; i < 10; i++ { defer fmt.Println(i) } fmt.Println("done") }この場合はLiFOの原理によって9から逆順になる。最後に入れたものが最初に出てきて、最初に準備してた人が最後に出てくる。逆リハ。的な。その。的な?