20191221のGoに関する記事は8件です。

【爆速】Go入門2

この記事は2019新卒 エンジニア Advent Calendar 2019の21日目の記事です。

今回は、爆速でGoに入門していくシリーズのPart2です(Part1はこちら【爆速】Go入門)。

Methods

Goには、クラスのしくみはありませんが、型にメソッドを定義できます。

メソッドは、レシーバと呼ばれる特別な引数を伴う関数です。

レシーバはfunc キーワードとメソッド名の間に自身の引数リストとして書きます。

この例では、 Abs メソッドは v という名前の Vertex 型のレシーバを持つことを意味しています。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
}
$ go run main.go
5

以下の Abs は、先ほどの例から機能を変えずに通常の関数として記述しています。上の例との違いを確認しておきましょう。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

// func (v Vertex) Abs() float64 {
//  return math.Sqrt(v.X*v.X + v.Y*v.Y)
// }

func Abs(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    // fmt.Println(v.Abs())
    fmt.Println(Abs(v))
}
$ go run main.go
5

例で挙げたstructの型だけではなく、任意の型(type)にもメソッドを宣言できます。

また、レシーバを伴うメソッドの宣言は、レシーバ型が同じパッケージにある必要があります。 他のパッケージに定義している型に対して、レシーバを伴うメソッドを宣言できません。

package main

import (
    "fmt"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    f := MyFloat(-46)
    fmt.Println(f.Abs())
}
$ go run main.go
46

ポインタレシーバでメソッドを宣言できます。

例では *Vertex に Scale メソッドが定義されています。

ポインタレシーバを持つメソッド(ここでは Scale )は、レシーバが指す変数を変更できます。 レシーバ自身を更新することが多いため、変数レシーバよりもポインタレシーバの方が一般的です。

変数レシーバでは、 Scale メソッドの操作は元の Vertex 変数のコピーを操作します。 (これは関数の引数としての振るまいと同じです)。 つまり main 関数で宣言した Vertex 変数を変更するためには、Scale メソッドはポインタレシーバにする必要があるのです。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    v.Scale(10)
    fmt.Println(v.Abs())
}
$ go run main.go
50

ポインタレシーバを変数レシーバに変えて、違いを確認しておきましょう。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// func (v *Vertex) Scale(f float64) {
//  v.X = v.X * f
//  v.Y = v.Y * f
// }

func (v Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    v.Scale(10)
    fmt.Println(v.Abs())
}
$ go run main.go
5

次に、先ほどのScaleメソッドを関数として書き直し(ScaleFunc)、呼び出し時の違いを見ていきましょう。

package main

import "fmt"

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    // ScaleFunc(v, 10) エラーになる
    ScaleFunc(&v, 10)
    v.Scale(2) // (&v).Scale(2)として解釈される

    p := &Vertex{4, 3}
    p.Scale(3)
    ScaleFunc(p, 8)

    fmt.Println(v, p)
}

ポインタを引数に取る ScaleFunc 関数は、ポインタを渡す必要があります。

v := Vertex{3, 4}
ScaleFunc(&v, 10) // OK
ScaleFunc(v, 10)  // ERROR

メソッドがポインタレシーバである場合、呼び出し時に、変数、または、ポインタのいずれかのレシーバとして取ることができます。

v := Vertex{3, 4}
v.Scale(2) //OK
p := &Vertex{4, 3}
p.Scale(10) //OK

v.Scale(5) のステートメントでは、 v は変数であり、ポインタではありません。 メソッドでポインタレシーバが自動的に呼びだされます。 Scale メソッドはポインタレシーバを持つ場合、Goは利便性のため、 v.Scale(5) のステートメントを (&v).Scale(5) として解釈します。

$ go run main.go
{60 80} &{96 72}

ポインタレシーバを使う2つの理由があります。

ひとつは、メソッドがレシーバが指す先の変数を変更するためです。

ふたつに、メソッドの呼び出し毎に変数のコピーを避けるためです。 例えば、レシーバが大きな構造体である場合に効率的です。

下の例では、次の Abs メソッドはレシーバ自身を変更する必要はありませんが、 Scale と Abs は両方とも *Vertex 型のレシーバです。

一般的には、変数レシーバ、または、ポインタレシーバのどちらかですべてのメソッドを与え、混在させるべきではありません。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    v.Scale(10)
    fmt.Println(v.Abs())
}
$ go run main.go
50

Interfaces

型にメソッドを実装していくことによって、インタフェースを実装(満た)します。

インタフェースを実装することを明示的に宣言する必要はありません( "implements" キーワードは必要ありません)。

package main

import (
    "fmt"
)

type I interface {
    M()
}

type T struct {
    S string
}

// このメソッドは暗黙的にTがインターフェースIを実装していることを意味する
func (t T) M() {
    fmt.Println(t.S)
}

func main() {
    var i I = T{"hello gunsoo"}
    i.M()
}
$ go run main.go
hello gunsoo

インターフェースの値は、値と具体的な型のタプルのように考えることができます : (value, type)

インターフェースの値は、特定の基底になる具体的な型の値を保持します。

インターフェースの値のメソッドを呼び出すと、その基底型の同じ名前のメソッドが実行されます。

関数describeを定義して確認してみましょう。

package main

import (
    "fmt"
)

type I interface {
    M()
}

type T struct {
    S string
}

func (t T) M() {
    fmt.Println(t.S)
}

func main() {
    var i I = T{"hello gunsoo"}
    describe(i)
    i.M()
}

func describe(i I) {
    fmt.Printf("(%v, %T)\n", i, i)
}
$ go run main.go
({hello gunsoo}, main.T)
hello gunsoo

インターフェース自体の中にある具体的な値が nil の場合、メソッドは nil をレシーバーとして呼び出されます。

いくつかの言語ではこれは null ポインター例外を引き起こしますが、Go では nil をレシーバーとして呼び出されても適切に処理するメソッドを記述するのが一般的です(この例では M メソッドのように)。

具体的な値として nil を保持するインターフェイスの値それ自体は非 nil であることに注意してください。

package main

import (
    "fmt"
)

type I interface {
    M()
}

type T struct {
    S string
}

func (t *T) M() {
    if t == nil {
        fmt.Println("<nil>")
        return
    }
    fmt.Println(t.S)
}

func main() {
    var i I

    var t *T
    i = t
    describe(i)
    i.M()
}

func describe(i I) {
    fmt.Printf("(%v, %T)\n", i, i)
}
$ go run main.go
(<nil>, *main.T)
<nil>

ゼロ個のメソッドを指定されたインターフェース型は、 空のインターフェース と呼ばれます : interface{}

空のインターフェースは、任意の型の値を保持できます。 (全ての型は、少なくともゼロ個のメソッドを実装しています。)

空のインターフェースは、未知の型の値を扱うコードで使用されます。 例えば、 fmt.Print は interface{} 型の任意の数の引数を受け取ります。

package main

import (
    "fmt"
)

func main() {
    var i interface{}
    describe(i)

    i = 46
    describe(i)

    i = "gunsoo"
    describe(i)
}

func describe(i interface{}) {
    fmt.Printf("(%v, %T)\n", i, i)
}
$ go run main.go
(<nil>, <nil>)
(46, int)
(gunsoo, string)

Type assertions

型アサーション は、インターフェースの値の基になる具体的な値を利用する手段を提供します。

t := i.(T)
この文は、インターフェースの値 i が具体的な型 T を保持し、基になる T の値を変数 t に代入することを主張します。

i が T を保持していない場合、この文は panic を引き起こします。

インターフェースの値が特定の型を保持しているかどうかを テスト するために、型アサーションは2つの値(基になる値とアサーションが成功したかどうかを報告するブール値)を返すことができます。

t, ok := i.(T)
i が T を保持していれば、 t は基になる値になり、 ok は真(true)になります。

そうでなければ、 ok は偽(false)になり、 t は型 T のゼロ値になり panic は起きません。

package main

import "fmt"

func main() {
    var i interface{} = "hello"

    s := i.(string)
    fmt.Println(s)

    s, ok := i.(string)
    fmt.Println(s, ok)

    f, ok := i.(float64)
    fmt.Println(f, ok)

    // f := i.(float64) // panic
    // fmt.Println(f)
}
$ go run main.go
hello
hello true
0 false

型switch はいくつかの型アサーションを直列に使用できる構造です。

package main

import "fmt"

func do(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Twice %v is %v\n", v, v*2)
    case string:
        fmt.Printf("%q is %v bytes long\n", v, len(v))
    default:
        fmt.Printf("I don't know about type %T!\n", v)
    }
}

func main() {
    do(46)
    do("hello")
    do(true)
}
$ go run main.go
Twice 46 is 92
"hello" is 5 bytes long
I don't know about type bool!

Errors

Goのプログラムは、エラーの状態を error 値で表現します。

error 型は fmt.Stringer に似た組み込みのインタフェースです:

type error interface {
    Error() string
}

よく、関数は error 変数を返します。そして、呼び出し元はエラーが nil かどうかを確認することでエラーをハンドル(取り扱い)します。

nil の error は成功したことを示し、 nilではない error は失敗したことを示します。

package main

import (
    "fmt"
    "strconv"
)

func main() {
    i, err := strconv.Atoi("46")
    if err != nil {
        fmt.Printf("couldn't convert number: %v\n", err)
        return
    }
    fmt.Println("Converted integer:", i)
}
$ go run main.go
Converted integer: 46

まとめ

主にメソッドとインターフェースを学んだGo入門第2回目でした。

次回へと続きます。

(参考)
https://go-tour-jp.appspot.com/list

(Go入門1)
【爆速】Go入門

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

[golang] graceful shutdownは難しい

メリークリスマス!

概要

非同期にログのようなものをサービスから送信するようなclientを作るとき、「サーバーをどう落とすか」というのはとても難しい問題だと思います。
特にお金がかかわるようなログだと送り漏れがないようにする必要がありますし「もう同期でいいんじゃね」と頭を抱えてしまいます。

非同期なのでバッファのような場所にデータがたまっているので、これを掃除してから落とす必要があるのが厄介の源です。

要件

clientは、あるjson型のログを別のRESTのシステムに投げるようなユースケースを想定していますが、ここではそこは端折ってio.Writerに書いています。
clientは、Sendで送られたメッセージをキャッシュし、

  • キャッシュがあるサイズに達したら
  • 定期的に
  • 最後にサーバーをShutdownするときに

の3つの条件でflushします。

解1:同期的に送信する

まずシンプルな解として同期的なものを用意しておきます。非同期処理はこれと同じ事を実現する必要があります。

type V1Client struct {
    w io.Writer
}

func NewV1Clienet(w io.Writer) *V1Client {
    return &V1Client{w}
}

func (c *V1Client) Send(message string) error {
    _, err := fmt.Fprintln(c.w, message)
    return err
}

これでなぜダメだったかといいますと、メッセージを束ねる必要があったためです。

解2:戦いはここから始まった

type V2Client struct {
    w    io.Writer
    q    chan string
    logs []string
    max  int
    mu   *sync.Mutex
    itvl time.Duration
}

func NewV2Client(w io.Writer, bufsize int, itvl time.Duration) *V2Client {
    return &V2Client{
        w:    w,
        logs: make([]string, bufsize)[:0],
        mu:   &sync.Mutex{},
        max:  bufsize,
        itvl: itvl,
    }
}

func (c *V2Client) Send(ctx context.Context, message string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.logs = append(c.logs, message)
    if len(c.logs) >= c.max {
        c.flush(ctx)
    }
}

func (c *V2Client) Start(ctx context.Context) {
    go func() {
        for {
            time.Sleep(c.itvl)
            c.mu.Lock()
            if err := c.flush(ctx); err != nil {
                fmt.Printf("error: %+v\n", err)
            }
            c.mu.Unlock()
            select {
            case <-ctx.Done():
                return
            }
        }
    }()
}

func (c *V2Client) flush(ctx context.Context) error {
    defer func() {
        c.logs = c.logs[:0]
    }()
    for _, m := range c.logs {
        if _, err := fmt.Fprintln(c.w, m); err != nil {
            return err
        }

    }
    return nil
}

非同期バージョンの最初の実装はこんな感じでした。ほかの言語のイメージで実装するとこんな感じになるのではないかと思います。
この実装には様々な問題点があります。

  • context cancelすることでプログラムを落とす、その時にlogsの中に残ったログの掃除をしていない
  • logsという配列を共有メモリのようにgoroutine間のデータのやり取りに使っている
  • logsがオーバーフローしたとき、同期的にログを送信してしまっている

主にこの3点でしょうか。ほかにもいろいろあるとは思いますが。

解3:修正版

type V3Client struct {
    logs []*string    // log buffer
    q    chan string // channel to send log
    max  int           // max size of logs
    itvl time.Duration // log send interval
    w    io.Writer     // l2pc backup logger

    exit chan struct{}
    once *sync.Once
}


func NewV3Client(writer io.Writer, buffsize int, interval time.Duration) *V3Client {
    client := &V3Client{
        logs:         make([]string, buffsize)[:0],
        q:            make(chan string),
        max:          buffsize,
        itvl:         interval,
        w:            writer,
        once:         &sync.Once{},
    }
    return client
}

func (c *V3Client) Send(message string) {
    c.q <- message
}

func (c *V3Client) Start(ctx context.Context) {

    if c.exit != nil {
        fmt.Println("client is already started.")
        return
    }
    c.exit = make(chan struct{})

    go func() {

        t := time.NewTicker(c.itvl)
        defer t.Stop()

        for {
            select {
            case m := <-c.q:
                if err := c.tryFlush(ctx, m); err != nil {
                    fmt.Println("log send error: " + err.Error())
                }
            case <-t.C:
                if err := c.flush(ctx); err != nil {
                    fmt.Println("log send error: " + err.Error())
                }
            case <-c.exit:
                return
            }
        }
    }()
}

func (c *V3Client) Close(ctx context.Context) {
    c.once.Do(func() {
        c.exit <- struct{}{}
        if err := c.flushAll(ctx); err != nil {
            fmt.Println("log send error" + err.Error())
        }
    })

}

func (c *V3Client) tryFlush(ctx context.Context, msg string) error {
    c.logs = append(c.logs, msg)
    if len(c.logs) >= c.max {
        return c.flush(ctx)
    }
    return nil
}

func (c *V3Client) flushAll(ctx context.Context) error {
    close(c.q)
    all := c.logs
    for l := range c.q {
        all = append(all, l)
    }
    for {
        if len(all) < c.max {
            c.logs = all
            return c.flush(ctx)
        }
        c.logs, all = all[:c.max], all[c.max:]
        if err := c.flush(ctx); err != nil {
            return err
        }
    }
}

func (c *V3Client) flush(ctx context.Context) error {

    defer func() {
        c.logs = c.logs[:0]
    }()

    for _, l := range c.logs {
        if _, err := fmt.Fprintln(c.w, l); err != nil {
            return err
        }
    }
    return nil
}}

各メソッドはそれぞれ

client 説明
client#Start clientの非同期送信を開始する。goroutine部。
client#Send メッセージを送る(入力側)。業務ロジックが呼び出す。
client$Close 終了処理開始および、終了処理がおわるまで待機
client#flush 実送信部
client#tryFlush メッセージを受け取り必要であればflushする
client#flushAll 終了処理

です。#2と比較して特に違うのは「ノンブロッキング」だという事だと思います。
ほかの言語だと、こういったプログラムには必ずといっていいほどLockが顔を出します。ですが上にはありません。

  • SleepではなくTickerを使っている
  • logsをgoroutineで共有せず、goroutine間の受け渡しにqというchanを使っている

ためです。
また、終了処理のために専用のexitというchを導入しました。なのでselect/caseで3つのchannelを待つコードになっています。

学んだこと

  1. goroutine間を渡すデータはlockして共有メモリで渡すのではなくchannelで渡す
  2. 一定時間置きに起動する処理はSleepを使わずTickerを使う
  3. defer cl.Close()で終了処理を開始しつつかつ終了処理の終了まで待たせる
  4. select/caseで複数のchannelを待つとき、同時に複数のchannelにデータが入ったときどのchannelが起動するかはrandom
func main(){
   ctx := context.Background()
   cl := NewV3Client(...)
   cl.Start(ctx)
   defer cl.Close(ctx)

   // start server with client
}

最終的なmainはこのようになりました。
割と普通なインターフェースに収まったように思います。

他の言語に慣れていると、「ノンブロッキングに無限forループを回す」というのはなかなか違和感があるのですが、確かにPythonのGILのように、過剰な範囲で足止めをしてしまい、パフォーマンスの足を引っ張る要因になりやすいのはわかる気がします。

一旦ここまでで。

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

Golandをつかいましょう 2019冬

はじめに

この記事は、アドベントカレンダー Go4 の21日目の記事です。

みなさん、Goのエディタ何を使っていますか?
Vim? Vscode?

そうですねGolandを使いましょう。

image.png

Golandの利点

  • インストールしたらデフォルトでいい感じの環境
  • JetBrainsのエディタはUI同じなので複数言語使う人におすすめ

Golandはじめ方

それでは、Golandを始めていきましょう

筆者がMacしか持ってないのでここではMacでやる手順を記載してしまいました。
ただし心配することはありません。JetBrains製品はWindows,Linuxもサポートしています。導入手順も大体同じです。多分。

Golandを買いましょう

Golandは有償です。初年度は1万円ですが、2年目以降はディスカウントされます。さらに使用していくうちにだんだんと多幸感に包まれるため、相殺され実質無料になります。

国内代理店のサムライズムさん経由なら若干安く購入することが出来ます。
また、日本語でのサポートも行っていただけるのでそちら経由で購入することをおすすめします。
https://samuraism.com/jetbrains/goland

実質無料なのですが、どうしても使ってから購入したい人は30日間のトライアルがあるのでそちらを利用してもいいかもしれません。

ここまできてまだお悩みなら一旦 All Products Pack
を購入された方がいいと思います。

こちらも初年度は3万円弱のお値段なのですが、継続3年目で2万円を切る値段にディスカウントされます。

もしあなたが、複数の言語を操るプログラマーなら迷わず購入するべきです。

以降はサムライズムさんのサイトで購入した体で話を進めていきます。

インストールしましょう

購入してアクティベーションされるまで若干のタイムラグがあります。その間にGolandをインストールしておきましょう

ちょっと待ってください。ChromeでGolandと検索してダウンロードページに進もうとしませんでしたか?
JetBrains製品をインストールするために直接アプリをインストールするのは愚かです。
ToolBox Appをインストールしましょう
https://www.jetbrains.com/ja-jp/toolbox-app/

ToolBox Appを使うことで、ライセンスの自動適用、バージョン自動管理などを簡単に行うことが出来ます。

ToolBox Appを起動すると、なんか右上にメ●カ●のようなアイコンが表示されていませんか?
クリックしてみましょう。

image.png

そこにGolandがありますね。
Installボタンをポチりましょう。

そしてインストールされるのを待ちましょう。

....

アクティベーションしましょう

ちょうど、アクティベーションメールが届く頃だと思います。
Gmailで確認しましょう。
こんな感じのメールにPDFが添付されているかと思います。

image.png

アクティベーション用URLをクリックして、流れでJetBrainsアカウントを作りましょう。

image.png

image.png

そしてそのアカウントでToolBox Appにログインしましょう。
これでエディタ個別のアクティベーションが不要になり、以降は期限が切れるまでつかいたい放題です。

Goをインストールしましょう

「は?ここでGo?Golandで全部揃うって言ったじゃん?嘘なの?」

と思われるかもしれませんが、Golandと同じくらい便利なツールがあります。

anyenvです。

「は?goenv入れておけばいいやん。なんで?」
と思われるかもしれませんが、anyenvを入れておけば、他言語も使えるからとりあえず入れとこ?な?

brewで入れて環境変数をゴニョゴニョするだけです。入れておきましょう。
https://github.com/anyenv/anyenv#homebrew-for-macos-user

GOENV_DISABLE_GOPATH=1

悪いこと言わないので~/.bash_profileとかに
export GOENV_DISABLE_GOPATH=1を追加しておいてください。
何も考えずにとりあえず追加しておいてください。
理由を知りたい人はこちらにありがたい記事があるので見ておいてください。
https://qiita.com/gimKondo/items/add08298e24ae400505e
(自分もハマってた)

goの最新版をインストールしてください。

goenv install -l # インストール可能なバージョン確認
goenv install 1.13.4 # 最新バージョンインストールしてね
goenv global 1.13.4

goimportsをインストールしておきましょう

go get golang.org/x/tools/cmd/goimports

Xcodeをインストールしましょう

何も考えずにとりあえず入れておきましょう。
インストール超長いんですが、業務時間に入れて構いません。

入れたら、下記のコマンドをやってください。

xcode-select

そんで環境変数を追加してください。
これも~/.bash_profileとかに突っ込んでおけばいいです。

export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"

理由はOSXのアップデートのたびに、SDKROOTのパスがコロコロ変わるからなんですが、
ありがたい記事で解説されているので、参照されるといいと思います。
https://qiita.com/imbsky/items/da709a514dded95b8575

Golandを立ち上げましょう

あなたは今使っているエディタに不満を持っていて、Golandに乗り換えようとしていると仮定しましょう。
ただし、手元には他エディタで作業しているプロジェクトフォルダがあります。

ToolBox AppからGolandを立ち上げてください

そして、あなたのいつも作業しているプロジェクトフォルダを開いてください。

image.png

開きましたね。

image.png

後少しで開発出来ます。

設定をしましょう

GOROOT

Command + , でPreferences(設定画面)が開きます。
Go > GOROOTで設定しましょう。
image.png

Go Module

GolandはGo Moduleに対応しています。
Go > Go Modules(vgo)Enable Go Modules(vgo) integrationにチェックしましょう
Proxydirectにしておいてください。

image.png

Dep,Go Mdudle Vendoring modeにも対応していますが、絶対にやめておきましょう。
Go Moduleを使っている限り、プロジェクトフォルダにvendor/が存在することに何の意味もありません。(個人の感想です)

コーディングしましょう

おめでとうございます!ここまできたら、Golandでの開発環境は整いました!
あとはザクザクコーディングしていくだけです!Golandの世界へようこそ!

プラグインを入れておきましょう

どんなにデフォでいい感じと言いつつも、やれvimバインドしたいだの、github連携させたいだの
個人の趣味に合わせた使い方をしたい場合はプラグインをインストールすることが出来ます。

僕は本当はデフォでも大丈夫なんですが、下記のプラグインとか入れています。

  • ideaVim
  • .ignore
  • BashSupport
  • Github
  • Docker

コーディングサンプル

先ほど、あなたのプロジェクトフォルダを開いてもらったのですが、いったん閉じていただいて、
ここではサンプルを使って、Golandでのコーディング作業を見ていきましょう。

こちらのコードを下記のディレクトリ構成にして貼り付けてください

project/
  `- sample/
      |- mocks/
      `- sample.go
       |- sample.go
       `- sample.test.go
sample/sample.go
package sample

import (
    "fmt"
)

type HogeClient interface {
    Do() error
}

type Sample struct {
    HogeClient HogeClient
}

func (s *Sample) Do() error {
    err := s.HogeClient.Do()
    if err != nil {
      return fmt.Errorf("えらーだよ : %w", err)
    }
    return nil
}
sample/sample_test.go
package sample

import (
    "fmt"
    "testing"

    "github.com/golang/mock/gomock"
    "github.com/junpayment/sample/sample/mocks"
    "github.com/stretchr/testify/assert"
)

func TestSample_Do(t *testing.T) {
    t.Run("success", func(t *testing.T) {
        ctrl := gomock.NewController(t)
        mock := mocks.NewMockHogeClient(ctrl)
        mock.EXPECT().Do().Return(nil)
        s := &Sample{HogeClient: mock}
        err := s.Do()
        assert.Nil(t, err)
    })
    t.Run("failed", func(t *testing.T) {
        ctrl := gomock.NewController(t)
        mock := mocks.NewMockHogeClient(ctrl)
        mock.EXPECT().Do().Return(fmt.Errorf("test"))
        s := &Sample{HogeClient: mock}
        err := s.Do()
        assert.Nil(t, err)
    })
}
sample/mocks/sample.go
// Code generated by MockGen. DO NOT EDIT.
// Source: sample/sample.go

// Package mocks is a generated GoMock package.
package mocks

import (
    gomock "github.com/golang/mock/gomock"
    reflect "reflect"
)

// MockHogeClient is a mock of HogeClient interface
type MockHogeClient struct {
    ctrl     *gomock.Controller
    recorder *MockHogeClientMockRecorder
}

// MockHogeClientMockRecorder is the mock recorder for MockHogeClient
type MockHogeClientMockRecorder struct {
    mock *MockHogeClient
}

// NewMockHogeClient creates a new mock instance
func NewMockHogeClient(ctrl *gomock.Controller) *MockHogeClient {
    mock := &MockHogeClient{ctrl: ctrl}
    mock.recorder = &MockHogeClientMockRecorder{mock}
    return mock
}

// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockHogeClient) EXPECT() *MockHogeClientMockRecorder {
    return m.recorder
}

// Do mocks base method
func (m *MockHogeClient) Do() error {
    m.ctrl.T.Helper()
    ret := m.ctrl.Call(m, "Do")
    ret0, _ := ret[0].(error)
    return ret0
}

// Do indicates an expected call of Do
func (mr *MockHogeClientMockRecorder) Do() *gomock.Call {
    mr.mock.ctrl.T.Helper()
    return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockHogeClient)(nil).Do))
}

さらにいろいろgo getしてください

go get github.com/golang/mock
go get github.com/stretchr/testify

この状態で、ここの緑の三角をクリックしましょう。

image.png

そしてRunするとテスト実行されます
image.png

こんな感じでまじでほとんど何もしなくてもデフォでデバッグ出来ます。

image.png

おっとテスト失敗しましたね。
テストを書き直して失敗したテストだけやり直しましょう。

sample/sample_test.go
    t.Run("failed", func(t *testing.T) {
        ctrl := gomock.NewController(t)
        mock := mocks.NewMockHogeClient(ctrl)
        mock.EXPECT().Do().Return(fmt.Errorf("test"))
        s := &Sample{HogeClient: mock}
        err := s.Do()
        assert.NotNil(t, err) // ここ
    })

image.png

楽ぅ〜

デバッグモードでも実行出来ます
image.png

ブレークポイントも貼りましょう。
image.png

条件付きのブレークポイントも貼りましょう。

image.png

便利ぃ〜

最後に

ここまで来たら、もはや今までのエディタをアンインストールし、全てのJetBrains製品をインストールするのみです。

Goland,JetBrains人口は他のエディタに比べて少ないのですが、
image.png
[引用]https://job-draft.jp/articles/323

デフォルト状態でいい感じに開発スタートできる(当社比), すぐコーディング作業に没頭することのできる(当社比)という利点は
各プログラミング言語ごとに最適化された製品があるJetBrainsならでは無いでしょうか?だよね?

みなさんGolandつかいましょう。

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

Echoで簡単なwebサーバーを実装してみる

VOYAGE GROUPのサマーインターン「Treasure」のアドベントカレンダー17日目の記事.
遅れてごめんなさい.

目的

Goのwebフレームワークの一つであるEchoを使って簡単なwebサーバーをさくっと生やす

なんでEcho?

Goのwebフレームワークはたくさんある. ginとかbeegoとか...
そんな中Echoを選んだ理由は
1. 公式のドキュメントがすごい丁寧でわかりやすかった
2. 軽量で処理が高速 (らしい)
3. 書き方がわかりやすかった

という感じ. 他のフレームワークにももちろん良いところはあるのだろうが, とっつきやすさを感じたのでEchoを使うことにした.

echo公式ドキュメント

実践

サーバーの起動

Echoでwebサーバーを起動させるのは超簡単

main.go
package main

import "github.com/labstack/echo/v4"

func main() {
    e := echo.New()
    e.Start(":1991")
}

これでmain.goを実行すると以下のようにwebサーバーが立ち上がる.
スクリーンショット 2019-12-21 16.24.17.png

APIの実装

それではAPIを作っていく. 今回は/helloGETリクエストを送ったらHello Worldという文字列がレスポンスとして返ってくるような簡単なAPIを作る.
main.goに数行追加する.

main.go
package main

import (
    "net/http"

    "github.com/labstack/echo/v4"
)

func main() {
    e := echo.New()
    // 追加部分
    e.GET("/hello", func(c echo.Context) error {
        return c.String(http.StatusOK, "Hello World")
    })

    e.Start(":1991")
}

今回はリクエストに対してDBの操作などをしないので, ルーティングの部分でレスポンスを返しているが, もちろん関数に切り出せる.
それではリクエストを実際に飛ばしてレスポンスが返ってくるか確認しよう. まずはmain.goを実行し, 別のターミナル上で以下のコマンド叩く.

curl http//localhost:1991/hello

Hello Worldが返ってきた.
スクリーンショット 2019-12-21 16.48.45.png

まとめ

書いてから気づいたがechoの公式ドキュメントのQuick Startとやってることが99%一緒だった. 今回は簡単な事しかやらなかったがEchoは色々とカスタマイズができる. ログの表示の仕方とかも自分でいじれたりルーティングのグループ化ができたり... 時間ができたらそういったこともアウトプットしていきたい.

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

眺めて覚えるGo言語 その3 web server gateway

眺めて覚えるGo言語(Wsgi編)

-Wsgi Webアプリケーションフレームワーク)を接続するための、標準化されたインタフェース定義である
image.png

views/layout.tpl
<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <title>{{.}}</title>
  </head>
  <body>
  <main role="main">
  <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
  <a class="navbar-brand" href="#">Boostrap4</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>

  <div class="collapse navbar-collapse" id="navbarsExampleDefault">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item active">
        <a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="/about">About</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="/contact">Content</a>
      </li>
      <li class="nav-item dropdown">
        <a class="nav-link dropdown-toggle" href="http://example.com" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
        <div class="dropdown-menu" aria-labelledby="dropdown01">
          <a class="dropdown-item" href="#">Action</a>
          <a class="dropdown-item" href="#">Another action</a>
          <a class="dropdown-item" href="#">Something else here</a>
        </div>
      </li>
    </ul>
    <form class="form-inline my-2 my-lg-0">
      <input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
      <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
    </form>
  </div>
</nav>

  {{ template "content" . }}

</main>
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>

だだのhtmlです。

  • {{ template "content" . }}に注目
  • ここに実体を流し込みます。
content.tpl
{{ define "content" }}
  <!-- Main jumbotron for a primary marketing message or call to action -->
  <div class="jumbotron">
    <div class="container">
      <h1 class="display-3">Hello, world!</h1>
      <p>これは、単純なマーケティングWebサイトまたは情報Webサイトのテンプレートです。ジャンボトロンと呼ばれる大きな吹き出しと、3つのサポートコンテンツが含まれています。これを基本として、よりユニークなものを作成してください。</p>
      <p><a class="btn btn-primary btn-lg" href="https://getbootstrap.com/" role="button">Learn more &raquo;</a></p>
    </div>
  </div>

  <div class="container">
    <!-- Example row of columns -->
    <div class="row">
      <div class="col-md-4">
        <h2>Heading</h2>
        <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
        <p><a class="btn btn-secondary" href="#" role="button">View details &raquo;</a></p>
      </div>
      <div class="col-md-4">
        <h2>Heading</h2>
        <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
        <p><a class="btn btn-secondary" href="#" role="button">View details &raquo;</a></p>
      </div>
      <div class="col-md-4">
        <h2>Heading</h2>
        <p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
        <p><a class="btn btn-secondary" href="#" role="button">View details &raquo;</a></p>
      </div>
    </div>

    <hr>

  </div> <!-- /container -->
{{ end }}

コンテンツの中身です。

  • {{ define "content" }}.....{{ end }} に注目
  • {{ template "content" . }}に流し込ませる
about.tpl
{{ define "content" }}
<div class="container mt-5">
<h2>{{.title}}</h2>
<h3>{{.message }}</h3>
<p>この領域を使用して、追加情報を提供します。</p>
</div>
{{ end }}

about用コンテンツ

contact.tpl
{{ define "content" }}

<div class="container mt-5">
 <h2>{{.title}}.</h2>
<h3>{{.message}}</h3>

<address>
    〒100-0001<br/>
    東京都千代田区千代田1<br />
    <abbr title="Phone">P:03-3213-2050</abbr>
</address>
<address>
    <strong>サポート:</strong>   <a href="mailto:Support@example.com">Support@example.com</a><br />
    <strong>営業:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>   
  </div> <!-- /container -->
{{end}}

contact用コンテント

main.go
package main

import (
    "./mux"
    "html/template"
    "log"
    "net/http"
)

func Home(w http.ResponseWriter, r *http.Request) {
    // テンプレートをパース
    t := template.Must(template.ParseFiles("views/layout.tpl","views/content.tpl"))

    str := "Home"

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "layout.tpl", str); err != nil {
        log.Fatal(err)
    }
}
func About(w http.ResponseWriter, r *http.Request) {
    // テンプレートをパース
    t := template.Must(template.ParseFiles("views/layout.tpl","views/about.tpl"))
    data := map[string]string{
        "title":   "About",
        "message": "Your application description page.",
    }
    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "layout.tpl", data); err != nil {
        log.Fatal(err)
    }
}
func Contact(w http.ResponseWriter, r *http.Request) {
    // テンプレートをパース
    t := template.Must(template.ParseFiles("views/layout.tpl","views/contact.tpl"))
    data := map[string]string{
        "title":   "Contact",
        "message": "Your contact page.",
    }
    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "layout.tpl", data); err != nil {
        log.Fatal(err)
    }
}
func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", Home)
    r.HandleFunc("/about", About)
    r.HandleFunc("/contact", Contact)
    http.Handle("/", r)
    http.ListenAndServe("192.168.1.10:8080", r)
}

html描画用関数を

  • Home
  • About
  • Contact

テンプレートファイルと関数を用意する。

メイン関数でルータを用意しurlと関数を関係つけする。

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

ioutil.WriteFile のFileMode の挙動の意味がわからなかったので調べてみた

ファイルを一括で記述したかったので、ioutil.WriteFile を使おうと思いました。この第三引数の部分がいまいち理解できていないので調べていました。

定義

func WriteFile(filename string, data []byte, perm os.FileMode) error

よくここにos.ModePrem とか書くけどあれなによ?というやつです。FileModeの定義も読んでみます。様々なデバイスファイルやモードが定義されています。単にファイルを書きたいときだと、os.ModePrem でオッケーで、Unix の0777のパーミッションがファイルに設定されるように見えます。

const (
    // The single letters are the abbreviations
    // used by the String method's formatting.
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: is a directory
    ModeAppend                                     // a: append-only
    ModeExclusive                                  // l: exclusive use
    ModeTemporary                                  // T: temporary file; Plan 9 only
    ModeSymlink                                    // L: symbolic link
    ModeDevice                                     // D: device file
    ModeNamedPipe                                  // p: named pipe (FIFO)
    ModeSocket                                     // S: Unix domain socket
    ModeSetuid                                     // u: setuid
    ModeSetgid                                     // g: setgid
    ModeCharDevice                                 // c: Unix character device, when ModeDevice is set
    ModeSticky                                     // t: sticky
    ModeIrregular                                  // ?: non-regular file; nothing else is known about this file

    // Mask for the type bits. For regular files, none will be set.
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular

    ModePerm FileMode = 0777 // Unix permission bits
)

ファイルのパーミッションがうまく動作しない理由

それなら、普通に書きこむファイルのパーミッションを設定できるのでは?と思いしらべてみると、次のように書けます。

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "strconv"
)

func main() {
    ioutil.WriteFile("ModePerm.txt", []byte("ModePerm.txt"), os.ModePerm)
    fmt.Println(os.ModePerm.IsRegular())
    perm := "0777"
    perm32, _ := strconv.ParseUint(perm, 8, 32)
    ioutil.WriteFile("777.txt", []byte("some"), os.FileMode(perm32))
    fmt.Println(perm32)
    perm = "0644"
    perm32, _ = strconv.ParseUint(perm, 8, 32)
    ioutil.WriteFile("644.txt", []byte("some"), os.FileMode(perm32))
    fmt.Println(perm32)
    perm = "0666"
    perm32, _ = strconv.ParseUint(perm, 8, 32)
    ioutil.WriteFile("666.txt", []byte("some"), os.FileMode(perm32))
    // os.Chmod("./666.txt", os.FileMode(perm32))
}

これを実行すると、4つのファイルが生成されて、想定通りのパーミッションが設定されるといいのですが、そうなりません。

ls -l 
total 4
-rw-r--r-- 1 ushio ushio   4 Dec 21 08:45 644.txt
-rw-r--r-- 1 ushio ushio   4 Dec 21 08:45 666.txt
-rwxr-xr-x 1 ushio ushio   4 Dec 21 08:45 777.txt
-rwxr-xr-x 1 ushio ushio  12 Dec 21 08:45 ModePerm.txt
-rw-r--r-- 1 ushio ushio  47 Dec 21 08:11 go.mod
-rw-r--r-- 1 ushio ushio 809 Dec 21 08:44 main.go

なんでやねん!と思って私のGo師匠であるかえる師匠に聞いてみたのですが、こんなStackOverflowの記事を見つけてくれました。

これを読むと、これは、go の問題ではなく、OS で設定されている umaskの問題では?との答えがありました。

umask

umask() sets the calling process's file mode creation mask (umask) to mask & 0777 (i.e., only the file
permission bits of mask are used),and returns the previous value of the mask.

The umask is used by open(2), mkdir(2), and other system calls that create files to modify the permissions placed on newly created files or directories. Specifically, permissions in the umask are turned off from the mode argument to open(2) and mkdir(2).

Umaskは、openやmkdirのシステムコールで使われて、新しいファイルやディレクトリを作るときにパーミッションをモディファイするとあります。ここで、私が今使っているWSLのumaskを見てみましょう。

$ umask
022

022 ですね、これをマスクするということは、Unixのファイルパーミッション設定は、4(read) 2(write) 1(execute) なので、022は所有者以外の書き込み権限なので、それでマスクをすると、書き込み権限がなくなることになります。
実際の上の実行例を見てみると、644は正しく設定されているのに、666は、644に変更されています。ModePerm も元が777なのが、755に変更されています。

想定どおりのパーミッションを設定したい場合は?

では、想定通りのパーミッションを設定するにはどういう方法がよいでしょう?先ほどのStack Overflow からたどれるWikiで次のような記述があります。

In Unix-like systems, each file has a set of attributes that control who can read, write or execute it. When a program creates a file the file permissions are restricted by the mask. If the mask has a bit set to "1", then the corresponding initial file permission will be disabled. A bit set to "0" in the mask means that the corresponding permission will be determined by the program and the file system. In other words, the mask acts as a last-stage filter that strips away permissions as a file is created; each bit that is set to a "1" strips away its corresponding permission. Permissions may be changed later by users and programs using chmod.

Unixシステムでは、ファイルを作成した時にはマスクで、制限されてるとあります。パーミッションはchmodを使って後で変えられるかもしれないとあります。つまり、想定通りのパーミッション例えば、0666を設定したい場合は、

 os.Chmod("./666.txt", os.FileMode(perm32))

を追記すればいいことになります。そうすれば、666に無事設定されていることがわかると思います。

ls -l 
total 4
-rw-r--r-- 1 ushio ushio   4 Dec 21 08:49 644.txt
-rw-rw-rw- 1 ushio ushio   4 Dec 21 08:49 666.txt
-rwxr-xr-x 1 ushio ushio   4 Dec 21 08:49 777.txt
-rwxr-xr-x 1 ushio ushio  12 Dec 21 08:49 ModePerm.txt
-rw-r--r-- 1 ushio ushio  47 Dec 21 08:11 go.mod
-rw-r--r-- 1 ushio ushio 651 Dec 21 08:49 main.go
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語のGo Modulesを試す。フォルダ配置が柔軟に。

背景

GoにはGOPATHという概念があります。
GOPATHはフォルダ(Unixではディレクトリ)を示しており、自動で設定されます。
https://github.com/golang/go/wiki/SettingGOPATH

If no GOPATH is set, it is assumed to be $HOME/go on Unix systems and %USERPROFILE%\go on Windows.

(USERPROFILEはC:\Users\Windowsのユーザー名です)

なおGOPATHは変更可能です。

去年までは、Goに関連するファイルはGOPATH以下の置くことになっていました。
つまり、プロジェクトを好きなフォルダに置けませんでした。

僕は他の言語も含めて、
C:\Users\Windowsのユーザー名\code
にプロジェクトを置いてますが、ここに置けませんでした。
(GOPATHをこのフォルダに設定すれば置けますが、デフォルトが好きなので変えたくありませんでした)

しかしながら、今年リリースされたGo 1.12からGo Modulesというものが導入されました。
(Go 1.11でもオプションで使えます)
これにより、好きなフォルダにファイルを置いていいようになりました。

今回はこの機能を試してみます。

クイックスタート

公式を参考にします。
https://github.com/golang/go/wiki/Modules

Windowsで行います。
オプションですがGitHubを使います。

まずGitHubにリポジトリを作成します。
https://github.com/hideboh/qiita_go_modules

クローンします。

新しいモジュールを初期化します。
go mod init github.com/hideboh/qiita_go_modul

go.mod
module github.com/hideboh/qiita_go_modules

go 1.13

が作成されます。

コードを書きます。

hello.go
package main

import (
    "fmt"
    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())
}

ビルドして実行します。

go build -o hello.exe
.\hello.exe

こんにちは世界。
が表示されました!

go buildをすると、必要なライブラリを$GOPATH/pkg/mod/に自動でダウンロードしてくれます。

またgo installをすると、実行ファイルを$GOPATH/binにコピーします。
ファイル名は、モジュール名のqiita_go_modules.exeとなりました。
Windowsだとインストール時?に$GOPATH/binが環境変数のPathに設定されるので、コマンドプロンプト等でqiita_go_modules.exeを打つと実行されます。

簡単ですので、これで好きなフォルダで開発ができます。

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

Goで迷路生成ツールを作る

この記事はtomowarkar ひとりAdvent Calendar 2019の21日目の記事です。

はじめに

以前作成したツールに迷路生成機能を追加してみました。

Goで地形生成ツールを作る
迷路の最短経路探索を実装する
OreIs9NBAPeoy2ulD8Fs1576904354-1576904601.gif

このように自動迷路を生成する機能と、最短経路を求める機能になります。

迷路生成アルゴリズムは現状棒倒し法と穴掘り法の2種類だけなのでゆるりと拡張していけたらなという感じです。

使い方

ソースコード

go get

go get -u github.com/tomowarkar/biome

コード

package main

import (
    "github.com/tomowarkar/biome/maze"
)

func main() {
    // 迷路描画フィールドの生成
    m := maze.NewMaze(48, 60)
    var seed int64

    // 棒倒し法
    m.StickDown(seed)
    // 迷路を10倍スケールでpng変換
    m.ToPng("maze1", 10)
    // 迷路の最短経路を導出
    m.Solve()
    m.ToPng("route1", 10)

    // 穴掘り法
    m.Digging(seed)
    m.ToPng("maze2", 10)
    m.Solve()
    m.ToPng("route2", 10)
}

棒倒し法

maze1.png

maze1.png

route1.png

route1.png

穴掘り法

maze2.png

maze2.png

route2.png

route2.png

おわりに

明日はGIF画像生成について書いていけたらなと思います。

以上明日も頑張ります!!
tomowarkar ひとりAdvent Calendar Advent Calendar 2019

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