20191009のGoに関する記事は10件です。

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-go

Goならわかるシステムプログラミング

@shibukawaさんの連載です。Goで低レイヤーを学んでいきます。根底の仕組みがガッツリ学べます。あまりに良すぎて僕は書籍で購入してます。書籍の方は色々加筆されているので皆さんも書籍で書いましょう。
https://ascii.jp/elem/000/001/235/1235262/

A Journey With Go

Goに関する面白いトピックが多いです。Goの内部の話などGo初心者が中級者になるための記事が多い印象です。
https://medium.com/a-journey-with-go

Tips

Go CodeReviewComments 日本語翻訳 #golang

Goを書く上で気をつけるべき&意識すべきポイントが網羅された「Go CodeReviewComments」の日本語訳記事
https://qiita.com/knsh14/items/8b73b31822c109d4c497

High Performance Go Workshop

Goアプリケーションのパフォーマンスに関するトピックほぼ全てが網羅された記事。ベンチマーク、プロファイリングはもちろん、コンパイラ最適化、メモリやガベージコレクタについてなどについて書かれている。はっきり言って最高。
https://dave.cheney.net/high-performance-go-workshop/gopherchina-2019.html

Profiling Go Programs

Goの公式ブログによるプロファイリングツールpprofの使い方です。使い方忘れたらよくここにきます。
https://blog.golang.org/profiling-go-programs

SliceTricks

Goの公式WikiのSlice操作の実装パターン集です。いい感じのSlice操作実装を忘れた時にめちゃ見返す。
https://github.com/golang/go/wiki/SliceTricks

インタフェースの実装パターン #golang

インターフェースの実装パターンがコード付きで丁寧に解説されています。
この記事を書いている @tenntenn さんの記事はどれもGo初心者にとって知っておいたほうがいい事ばかりなので他の記事も読んでみることをオススメします。
https://qiita.com/tenntenn/items/eac962a49c56b2b15ee8

Architecture

struct に依存しない処理は function に切り出すのか、method に切り出すのか

関数にするかメソッドにするかはGopherたちが悩む部分です。迷ったら読み返します。
https://www.pospome.work/entry/2017/01/16/233351

Golang 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/080000

Packages

Awesome Go : 素晴らしい Go のフレームワーク・ライブラリ・ソフトウェアの数々

こんなパッケージないかな??って時に開く記事。
https://qiita.com/hatai/items/f31914f37dc6c53b2bce

Playground

GoDoc Playground

GoDocのPlaygroundです。GoDocを書く際にコメントアウトで記載していきますがそれをリアルタイムで確認できます。
https://bradleyjkemp.dev/godoc-playground/

Editor

vim-go チュートリアル

vim-goのチュートリアルです。GoをVimで操りたくなったらまずここ。
https://github.com/hnakamur/vim-go-tutorial-ja

vim-goを使うなら使用したいコマンド集と設定

VimでGoを触るためのノウハウが体系的にまとまっています。
https://qiita.com/gorilla0513/items/a027885d03af0d6d5863

Detects Issue

Go Report Card

Go Report Card

GoのリポジトリのURLを入れるだけでgolintやgofmtでコードをチェックしたりmisspellを見つけたりしてくれます。最終的にREADME.mdに貼れる上記のようなバッジがすぐに作れます。

https://goreportcard.com/

GolangCI

GolangCI

こちらもGoのリポジトリに対して様々な問題を指摘してくれます。structcheck、errcheck、gosimpleなどのツールで細かい問題点も指摘してくれます。こちらもバッジが作れます。

https://golangci.com/

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

GolangでHTMLのtemplateに値を渡す方法

はじめに

Goではhttp/templateを使用してhtmlのテンプレートに値を渡すことができます。

具体的には

  • 変数
  • スライス
  • マップ
  • 構造体

などの値です。

今回は、基本となる変数と、使用機会が一番多いであろう構造体について説明していきます。

Web開発に使用する技術ですが、検索しても情報があまり出てこなかったので書きました。

環境

  • macOS Catalina 10.15
  • go 1.12.9

0.何も渡さず実行

メインのGoとhtmlファイルです。

これらを基本に説明していきます。

ディレクトリ構造
.
├── main.go
└── tpls.html
main.go
package 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.go
package 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.go
package 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)
    }
}

今回はNameAgeの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.go
package 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.go
package 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内でパイプ|を使用する方法など、他にも様々な手法があります。

反響があれば記事を書こうと思います。

最後まで読んでいただきありがとうございました。

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

Go の Web フレームワーク Echo で IT 用語クイズアプリを作ってみた

はじめに

近年、世間ではクイズが流行っています(?)が、IT 用語に特化したクイズのアプリってあんまりないような気がしたので作ってみました。
Go の勉強を最近始めたので、 echo でクイズのデータの JSON を返すだけの Web サーバをたてることにします。

作ったもの

https://go-itquiz.herokuapp.com/
Github: https://github.com/chgzm/go-itquiz

  • 動作イメージ itquiz-screenshot.png

サーバ側

Go そのものについては、始めて 3 日くらいなのでよくわかってませんが、echo の Guite を読めば大体問題なくできました。

  • main 部分. heroku にデプロイするために、環境変数 PORT からポート番号を取得するようにする
main.go
package 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.go
package 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.go
package 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 のフレームワークは使わず実装しました(使うほどの規模でもないので)。作ってる途中に気づきましたが、ドットインストール にクイズアプリ作るレッスンがあったので大いに参考にしました。

おわりに

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

Go の Web フレームワーク echo で IT 用語クイズアプリを作ってみた

はじめに

近年、世間ではクイズが流行っています(?)が、IT 用語に特化したクイズのアプリってあんまりないような気がしたので作ってみました。
Go の勉強を最近始めたので、 echo でクイズのデータの JSON を返すだけの Web サーバをたてることにします。

作ったもの

https://go-itquiz.herokuapp.com/
Github: https://github.com/chgzm/go-itquiz

  • 動作イメージ itquiz-screenshot.png

サーバ側

Go そのものについては、始めて 3 日くらいなのでよくわかってませんが、echo の Guite を読めば大体問題なくできました。

  • main 部分. heroku にデプロイするために、環境変数 PORT からポート番号を取得するようにする
main.go
package 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.go
package 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.go
package 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 のフレームワークは使わず実装しました(使うほどの規模でもないので)。作ってる途中に気づきましたが、ドットインストール にクイズアプリ作るレッスンがあったので大いに参考にしました。

おわりに

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

TSV,CSVファイルを読込んでみる

Shift_JISで書かれたtsvファイルの読込み処理をGOで書いてみました。

iconv-go を利用してみる[失敗編]

UTF-8で記述されたtsv/csvファイルを読込む場合、encoding/csvのみで対応できるのであるが、Shift_JISで記述されている場合、そのままだと文字化けを起こしてしまう。
そこで、パッケージを利用したのですが、うまくいかなかったた。

main.go
package 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.go
package 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ファイルを読込めた。

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

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

なぜGoがWebアプリ開発に適しているのか

なぜGoがWebアプリ開発に適しているのか

このエントリはGoプログラミング実践入門を読んで、5分でもわかるように序章を軽くまとめたものです

大規模なWebアプリケーション開発用の言語が必要とする要素

  • スケーラビリティ
  • モジュール性
  • 保守性
  • 高速性

Goが上記をカバーしている

スケーラビリティ

アプリケーションの処理能力を素早く簡単に増強して処理できるリクエストの数を増やすことのできる能力

スケーラビリティの側面

  • 垂直方向=単一のマシンでCPU数を増やして処理能力を向上
    • Goのゴルーチンで効率的に高速に実行
  • 水平方向=マシンの台数を増やして処理能力を向上
    • Go Webアプリケーションの多数のインスタンスの上にプロキシを置く
    • 動的依存性のない静的なバイナリとしてコンパイルされることを利用し、Goが組み込まれていないシステムへ分散

モジュール性

交換可能なコンポーネントを使って設計することで、機能の追加・削除・変更が容易になり、アプリケーションに対する要求変化に応えやすい

  • 関数でインターフェースを引数としてとれるため、新規コード追加でも既存の関数に対してそのインターフェースが要求するメソッドを実装することで、その関数をそのまま使える
  • 関数プログラミングのような機能(関数型、値としての関数、クロージャ)が数多く実装されているため、関数から別の関数を生成させることができる

近年話題の「マイクロサービス」中心のアーキテクチャとも親和性が高い

保守性

保守が容易なことは、機能改善・修正の際に時間がかからない・デグレを生みにくい

  • シンプルな構文
  • パッケージシスsテムが柔軟で曖昧さがなく、開発作業効率を上げ、可読性の高いコードを書くためのツールが多い
  • ドキュメント作成も楽 (godoc)
  • テスティング (go test)

高速性

C言語の実行速度に近づける思想

  • コンパイル型のため、インタプリタ型言語よりは一般的に速い
  • ゴルーチンによる並列実行のサポート
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サーバサイド初心者がニュートン法の例題を個人的に解こうとした話

いや、待ってめっちゃ数学やん。うふぇぇ苦手なんだよなぁ。。。

ってことでニュートン法についてまずウィキ様から引用します。

ニュートン法

数値解析の分野において、ニュートン法(ニュートンほう、英: 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ケタくらいだし。

おもしろいなぁ

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

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(キリ番ゲット機能付き)みたいなサービスが出てきてくれればいいんですけどね。

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

サーバサイド初心者が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
//1

deferはその呼び出し元のメソッドがリターンすると呼び出されるもの。参照はトップダウンで終わるが、呼び出しが実行後にくるって、なるほど。参照が先にされるのが少し自分的にははてながつくが、まぁそういうもんだろうと一旦流す。

  • Last-in-First-Out
func main() {
    fmt.Println("counting")

    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("done")
}

この場合はLiFOの原理によって9から逆順になる。最後に入れたものが最初に出てきて、最初に準備してた人が最後に出てくる。逆リハ。的な。その。的な?

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