20201206のGoに関する記事は19件です。

Go/EchoでProtocolBuffersを使ったE2Eテストを書く

この記事はGo 4 Advent Calendar 2020 6日目の記事です。

業務でGo * ProtocolBuffersなプロダクトのE2Eテストを書くことがあっため、簡単に書かせていただきます。

はじめに

テストで利用した構成は以下の通りです。

・プログラミング言語:Golang 1.13.5
・Webフレームワーク:echo 
・DB:MySQL:

サンプルコード全体はこちらにご用意しましたのでをこちらをご参照ください。

今回、Webフレームワークとして、echoを利用していますが、
おそらく他のWebフレームワークを利用した場合でも同様にできるかと思います。

テスト対象コード

今回はごく簡単な構成としまして、echoでサーバーを起動して、アクセスするとレスポンスを返すようにしました。

main.go
package main

import (
    "net/http"

    pb "github.com/DAdDY0055/go_protocol_buffers_e2e_test/proto"
    "github.com/labstack/echo/v4"

    "google.golang.org/protobuf/proto"
)

func main() {
    e := echo.New()
    e.GET("/hello", func(c echo.Context) error {
        hello := &pb.Hello{Hello: "Hello, World!"}
        data, _ := proto.Marshal(hello)
        return c.Blob(http.StatusOK, "application/protobuf", data)
    })
    e.Logger.Fatal(e.Start(":8080"))
}

ProtocolBuffersを使っておりますので、変換に使うProtoのファイルは以下です。

test.proto
syntax = "proto3";

package protofiles;

message Hello {
  string hello = 1;
}

上記ファイルを作成した後、protocコマンドを使ってgoのシリアライズ・デシリアライズ用ファイルを生成します。

% protoc --go_out=./proto/ ./test.proto

※protocコマンドをインストールしていない場合は以下で可能です。

% brew install protobuf

テストコード

テストの実行には、
- テスト用サーバーの起動:net/http/http/test
- 値の検証:github.com/stretchr/testify/assert

を利用しました。

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
    "testing"

    pb "github.com/DAdDY0055/go_protocol_buffers_e2e_test/proto"
    "github.com/stretchr/testify/assert"
    "google.golang.org/protobuf/proto"
)

func TestHelloHandler(t *testing.T) {
    // 想定する値を定義
    want := &pb.Hello{
        Hello: "Hello, World!",
    }

    router := NewRouter()

    req := httptest.NewRequest("GET", "/hello", nil)
    rec := httptest.NewRecorder()

    router.ServeHTTP(rec, req)

    assert.Equal(t, http.StatusOK, rec.Code)

    res := &pb.Hello{}
    proto.Unmarshal(rec.Body.Bytes(), res)
    // assert.Equal(t, want, res) そのまま比較すると一致しない
    assert.Equal(t, want.Hello, res.Hello)

    fmt.Println("res", res) // 味気ないので出力
}

ここで鍵となるのは、ProtocolBuffersはバイナリで通信をしているため、assertでチェックをする前に値を変換する必要があるという点です。

proto.Unmarshalでデシリアライズ変換した値を使うことで、assertで検証できます。

実際にテスト実行

テストファイルがあるディレクトリにて、以下で実行します。

go test -v

実行結果

-> % go test -v
=== RUN   TestHelloHandler
res hello:"Hello, World!"
--- PASS: TestHelloHandler (0.00s)
PASS
ok      github.com/DAdDY0055/go_protocol_buffers_e2e_test   0.019s

この通り、値が合っていた場合OKとなります。

ちなみにですが、assert.Equal(t, want, res)と直接比較したい気がしますが、その場合はNot equalとなりFAILとなります。

-> % go test -v
=== RUN   TestHelloHandler
res hello:"Hello, World!"
--- FAIL: TestHelloHandler (0.00s)
    main_test.go:30:
            Error Trace:    main_test.go:30
            Error:          Not equal:
                            expected: &protofiles.Hello{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(0xc0000da640)}, sizeCache:0, unknownFields:[]uint8(nil), Hello:"Hello, World!"}
                            actual  : &protofiles.Hello{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(0xc0000da640)}, sizeCache:0, unknownFields:[]uint8(nil), Hello:"Hello, World!"}

                            Diff:
            Test:           TestHelloHandler
FAIL
exit status 1
FAIL    github.com/DAdDY0055/go_protocol_buffers_e2e_test   0.019s

expectedactualを比較すると同じに見えるのですが。。
なぜこれがequalにならないのかわからよくわかっていないため、もしご存知な方いらしたら教えていただけると幸いです。

最後に

ProtocolBuffersのE2Eテストは変換する手間が発生しますが、
わかってしまえば特に苦戦することもなかったです。
(assert.Equal(t, want, res)で値が一致しないのは)

これはnet/http/httptestが使いやすく、簡単にテストサーバーを立てることができることが大きいかと感じました。

"net/http/httptest""testing"を使って色々テストを書いたので本当はそのノウハウも書きたかったのですが、今回時間がなく書けなかったのでまた次回書きたいと思います。

今回参考にさせていただいた記事

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

続やさしめのGo Quiz

問題

つぎのコードを実行するとどうなるでしょう。

package main

func hello() {
    hello := func() {
        hello := func() {
            print("hello")
        }
        hello()
    }
    hello()
}

func main() {
    hello := hello
    hello()
}

A. hello と出力される
B. hellohello と出力される
C. コンパイルエラー
D. 実行時エラー(panic)

解答

ここを開いてください

正解はAです。

3行目から定義が始まる関数hello(以下hello3)が単純に呼び出されて場合については、昨日の問題と同様です。
くせものは14行目の変数hello(以下hello14)にhello3を代入しているところですが、これは許されます。つまり15行目で実行されているのはhello14です。

この代入が許されるのは、関数名(FunctionName) は identifier であり、identifier:= の右辺にとれるものの一つだからです。
詳しくは仕様の Short variable declarationsFunction declarationsからたどっていくとよいでしょう。

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

語り尽くされている go の testing をまたサイコーと言う

この記事は BrainPad Advent Calendar 2020 9日目の記事になります

会社ではピ●チュウで通っております。
よろしくお願いします。

筆者視点(APIサーバー視点)な成分が多くありますが、testing framework の比較記事となります。

結論と結論までの流れ

結論: go testing サイコー!!:dash:

公式FAQより:Where is my favorite helper function for testing?

A related point is that testing frameworks tend to develop into mini-languages of their own, with conditionals and controls and printing mechanisms, but Go already has all those capabilities; why recreate them? We'd rather write tests in Go; it's one fewer language to learn and the approach keeps the tests straightforward and easy to understand.

(意訳)testing framework のトレンドは、"条件、制御、出力の機構をいかに楽にするか?"
って話なら golang ならすでに内包してあるよ。
↪︎ 確かに!!testing frameworks 使う必要なかったです。と帰結したお話になります

流れ: 以下の記事内容になります

  1. test framework をみる観点を説明する
  2. 各観点で評価してみる
  3. まとめて、結論につなげる

余談:

fmt_test.go を読むと勉強になるらしい

The standard Go library is full of illustrative examples, such as in the formatting tests for the fmt package.

?‍? testing framework対象と観点の説明

各testing framework について、以下の観点で評価していきます。

  • testing package/framework
  • 観点
    • (1) 単体テスト ... testing framework に依存しない事を話す
    • (2) BDD
    • BDDの重要ポイント ... wikipedia ベースの観点
    • Test のX性観点と記述面
      • Testability(テスト容易性)/Understandability(理解容易性)/Modifiability(変更容易性)
      • 記述面
      • assertion ... assert 標準以外
      • CIでのReporting ... レポートする
    • (3) 受け入れテスト(AcceptanceTest) ... ポエムが書いてあります

(1) unittest

  • TableDrivenTests で unittest はおおよそカバーできる。(testing framework不要)
  • 対象となる testing framework は unittest の効率化・品質向上に繋がりにくいと判断して評価してません。
    • testify/assert があると便利だと思います
    • ※ いろんなところで記事があるので、testing でのtest内容は割愛します

ginkgo/goconvey/testing sample code

? ginkgo

Context("logic test ===> unit test", func() {
        It("Hello unittest", func() {
                Expect(app.Hello(testContext)).To(Equal("Hello"))
        })
})

? goconvey

Convey("logic test ===> unit test", func() {
        Convey("Hello unittest", func() {
                So(app.Hello(ctx), ShouldEqual, "Hello")
        })
})

? testing

t.Run("logic test ===> unittest", func(t *testing.T) {
        if app.Hello(ctx) != "Hello" {
                t.Errorf("Hello not match")
        }
})

unitest 小ネタ

・RDB のテスト

弊社では、go-sqlite3を利用してRDBの簡易テスト可能にしています、必要なテーブルとモデルとデータをセットにできナレッジの共有面で良いと思っています。

擬似コード

import "memdb"  // 自作パッケージ

func TestUseRDB(t *testing.T) {
  cfg := memdb.Config{
    // model ベースでTableを作成
    Tables:  map[string]interface{}{"test_table": model.TestTable{}},
    // テストに必要なデータを用意
    ExecSQL: `INSERT INTO test_table (id, text) values (1, 'hello');`,
    // SQLの標準出力
    SQLTrace:   true,
  }

  dberr := memdb.Run(context.Background(), cfg, func(c context.Context, tx sql.Transaction) error {
     // do something    
  })
}

・Http Server を起動しないTest

サーバーの起動は環境整備コストが高いため、このようなアプローチもとっています。

import (
  "testing"
  "net/http/httptest"
)

func TestEndpoint(t *testing.T) {
  req := httptest.NewRequest(http.MethodGet, serverURL + "/", nil)
  w := httptest.NewRecorder()
  testHandler.ServeHTTP(w, req)

  res := w.Result()
  if res.StatusCode != http.Statusxxx {
    t.Errorf("xxxx")
  }
  body, err := ioutil.ReadAll(res.Body)
  if "Expect Value" != string(body) {
    t.Errorf("xxxxx")
  }
}

(2) BDD

BDDの重要ポイントと参考資料(P.26,27)での3観点のX性を支える4観点+コード記述感で比較します。

  • BDD重要ポイント (wikiより
    • 何処から始まるか?理解可能にする(Where to start in the process)
    • テストが必要・不必要の判断を可能にする(What to test and what not to test)
    • 一つにどれだけテストが必要か理解可能にする(How much to test in one go)
    • テストと呼ぶものは何かを明確にする(What to call the tests)
    • テストを失敗した理由を理解可能にする(How to understand why a test fails)
  • Testability(テスト容易性)/Understandability(理解容易性)/Modifiability(変更容易性)
    • Operability ... 実行円滑性(容易かつ高速な実行が良い)
    • Observability ... 観測容易性(結果がわかり易く、その後工程に移りやすいと良い)
    • Controllability ... 制御容易性(準備、対象操作、後処理などし易いと良い)
    • Decomposability ... 分解容易性(分割、結合、置換が容易だと良い)

BDDの重要ポイントの比較

対象のtesting framework では以下のような構造で記述が出来ます。

この形式を用いて、BDDの重要ポイントの表現や実現性などが評価点となりますが、

BDD重要ポイントを満たすためには、Test設計/記述者の責務になり、testing framework の選択に依存した効果は無いと判断しています。

func Testxxx() {
  Hoge("Test description", func() {
    Fuga("Test description", func() {
       // do something
    })
  })
}

httpexpect は除きます

Testの X 性と記述面の比較

goconvey が良さそうに見えますが、httpに通信結果のAssertionでは httpexpect の圧勝です。

観点 ginkgo,gomega goconvey httpexpect testing
Operability
ginkoの作法がある
Observability △(簡素すぎる) ◎(web ui有)
Controllability
Decomposability
Assertion △(書き心地は良) x
Reporting △(実装が必要)

(補足)assertion

ginko, convey ともに http の Body のassert するには不便な所がある

httpexpect を利用したい

assertのコード

ginkgo

Context("api test ===> e2e test", func() {
  It("GET", func() {
    res, err := http.Get(serverURL + "/")
    Expect(err).ShouldNot(HaveOccurred())
    Expect(res.StatusCode).Should(Equal(http.StatusOK))

    body, err := ioutil.ReadAll(res.Body)
    Expect(err).ShouldNot(HaveOccurred())
    _ = res.Body.Close()
    Expect(string(body)).To(Equal("Hello"))
  })
  It("POST", func() {
    res, err := http.Post(serverURL+"/", "application/json", strings.NewReader("{}"))
    Expect(err).ShouldNot(HaveOccurred())
    Expect(res.StatusCode).Should(Equal(http.StatusOK))

    body, err := ioutil.ReadAll(res.Body)
    Expect(err).ShouldNot(HaveOccurred())
    _ = res.Body.Close()

    Expect(string(body)).To(Equal(`{"value":"Hello"}`))
  })
})

convey

Convey("API Test", func() {

  Convey("GET", func() {
    res, err := http.Get(serverURL + "/")
    defer func() { _ = res.Body.Close() }()
    So(err, ShouldBeNil)
    So(res.StatusCode, ShouldEqual, http.StatusOK)

    text, err := readBody(res)
    So(err, ShouldBeNil)
    So(text, ShouldEqual, "Hello")
  })

  Convey("POST", func() {
    res, err := http.Post(serverURL+"/", "appliaction/json", strings.NewReader("{}"))
    defer func() { _ = res.Body.Close() }()
    So(err, ShouldBeNil)
    So(res.StatusCode, ShouldEqual, http.StatusOK)

    text, err := readBody(res)
    So(err, ShouldBeNil)
    So(text, ShouldEqual, `{"value":"Hello"}`)
  })
})

testing

t.Run("api test ===> e2e test", func(t *testing.T) {
  t.Run("GET", func(t *testing.T) {
    e := httpexpect.New(t, serverURL)
    e.GET("/").
      Expect().
      Status(http.StatusOK).
      Text().Equal("Hello")
  })
  t.Run("POST", func(t *testing.T) {
    e := httpexpect.New(t, serverURL+"/")
    e.POST("/").
      Expect().
      Status(http.StatusOK).
      JSON().
      Object().ContainsKey("value").ValueEqual("value", "Hello")
  })
})

(補足)CIでのReporting

Brainpad では CircleCI を利用しています。その場合にテストレポートの形式を junit 形式に変換が必要です。(ginkgo は出力が独自)

コードとコマンド

ginkgo はコードに手を入れないといけないため、癖がある。

func TestFoo(t *testing.T) {
    RegisterFailHandler(Fail)
    junitReporter := reporters.NewJUnitReporter("junit.xml")
    RunSpecsWithDefaultAndCustomReporters(t, "Foo Suite", []Reporter{junitReporter})
}

convey, testing は go-junit-report を利用すればよく、コードに変更は不要

go test -v 2>&1 | go-junit-report > report.xml

(3) AcceptanceTest(end-to-end Test)

golang の testing framework でどうであるかの検証は、以下の点から不毛だと思われるため評価しない。
(※ 適切なテスト設計と自動化の配備について考える事の方が重要なためtesting frameworkを評価の重要度低めと判断)

  • ginkgo,gomega は agouti で利用すること出来ます。 しかしブラウザ/スマートフォンアプリの e2e として利用する場合 agouti が良いとは言いにくいと思っています。
    • そもそも golang でe2eしたい人はいるのだろうか?(懐疑的な気持ち)
  • Fixing a Test Hourglass から、BDDやIntegration Testの自動化Testの時間 > AT,e2eTest時間 が望ましい。

(現実)⇨(理想)

まとめ

  • testing でBDDまでは十分に行える
    • assertion が弱いので httpexpect を利用するといいと思う
  • BDDでは testing framework の利用と言うよりは、テスト設計/記述者の力が大事
    • 言語は問わずに、やった方が良い
  • AcceptanceTest,e2eTest,ManualTest の時間は短いことは大正義

< golang testing を利用すれば良い

Run Result

比較コード(anonymous化してます)

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

やさしめのGo Quiz

問題

つぎのコードを実行するとどうなるでしょう。

package main

func main() {
    hello := func() {
        hello := func() {
            print("hello")
        }
        hello()
    }
    hello()
}

A. hello と出力される
B. hellohello と出力される
C. コンパイルエラー
D. 実行時エラー(panic)

解答と解説

ここを開いてください

正解はAです。

4行目の変数hello(以下hello4)と、5行目の変数hello(以下hello5)は異なるスコープで定義されており、つまりは別の変数です。
8行目で呼び出されているのはhello5で、10行目で呼び出されているのはhello4です。

hello4のやっていることを整理すると、以下のようになります。

  1. print("hello") を呼び出す関数を変数hello(hello5)に代入する
  2. 変数hello(hello5)を呼び出す

これを10行目で呼び出しているため、print("hello") は一度だけ呼び出されるため、 hello と出力されます。

同名の変数を再度宣言して代入することをシャドーイング(shadowing)と呼ぶそうです(Go固有ではなくプログラミング言語一般の用語)。
Effective GoのChannelsで少しだけ触れられています。
Goは変数名が短いことが多いためか、シャドーイングをよく見かける気がします。

明日へ続く

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

Goで標準入力がターミナルかどうかを判定する(Goで標準入力がファイルかどうかを判定する)

概要

Vim を起動する際に以下のように標準入力をファイルにすると警告が出ます。

$ vim < test.txt
Vim: Warning: Input is not from a terminal
...
...

テキストエディタやコマンドラインツールを作っていると標準入力がファイルかターミナルか知りたい場合があります。
今回はGoにおいてそれをどうやって実現するか書きます。

実践

まずは完成形から。
コードの仕様は以下の通りです。
1. 標準入力がターミナルの場合、固定文と引数を出力する。
2. 標準入力がファイルの場合、固定文とファイルの中身を出力する。

例)
$ go run main.go hoge fuga
from terminal
[hoge fuga]
$ go run main.go < test.txt
from not terminal
111
222
333
package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "syscall"

    "golang.org/x/crypto/ssh/terminal"
)

func main() {
    if terminal.IsTerminal(syscall.Stdin) {
        fmt.Println("from terminal")
        fmt.Println(os.Args[1:])
    } else {
        fmt.Println("from not terminal")
        b, err := ioutil.ReadAll(os.Stdin)
        if err != nil {
            fmt.Println(err)
            os.Exit(0)
        }
        fmt.Println(string(b))
    }
}

今回、標準入力がターミナルかどうかを判定する肝となるのはterminal.IsTerminal(syscall.Stdin)の部分です(GoDoc)。
引数に対象となるファイルディスクリプタの番号をしていることによりファイルディスクリプタがターミナルかどうか判定してくれます。
標準入力のファイルディスクリプタの数値は固定で0なのでそれを指定します。

終わりに

過去に以下のような記事も投稿しています。一つ一つは簡単なことですが、これらの要素を組み合わせることにより自らの望むツールの実現に繋がるのでごましお程度に覚えといてください。
Goでエスケープシーケンスを扱う(文字色、カーソル移動)
Goでターミナルのサイズに合わせて枠を表示し続ける

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

Go言語についてと開発環境

はじめに

Go言語は、Googleが開発したオープンソースのプログラミング言語であり、シンプルで信頼性の高い効率的なソフトウェアを簡単に構築できます。Go言語は、コンパイル言語の中でも特にエラーを検出しやすく、多人数での開発で起こるような重大なミスが起きにくくなっています。その為、現在では多くのサービスや企業で取り入れられています。

その為、技術レベルで遅れないように今からGo言語を始めると良いと思います。
Go言語についてと開発環境の方法を学んだので、備忘録としてまとめます。

Go言語とは

go.jpg

Go言語は、静的型付け、コンパイル言語、メモリ安全性、ガベージコレクション、構造的型付け、CSPスタイルの並行性などの特徴を持ちます。Go言語のコンパイラ、ツール、およびソースコードは、すべてフリーかつオープンソースです。

Github
https://github.com/golang

マスコット・キャラクターはGopher(ホリネズミ)です。

Go言語でできること

WEBサーバーの構築

標準パッケージを使うことで、WEBサービスを動作させるサーバー構築をスムーズに行えうことも可能です。

モバイルアプリケーション開発

モバイル開発向けのGo Mobileを使用する事で、Go言語でもiPhoneやAndroidのスマホアプリケーション開発に対応出来ます。

コマンドラインインターフェース(CLI)で動作するツール作成

CLIで動作するアプリケーションもGo言語での開発に向いています。

Go言語のマルチプラットフォームに対応したクロスコンパイル機能があるので、どんな環境でも問題なく開発できる点が非常に優秀です。

Go言語参考サイト

A Tour of Go
https://tour.golang.org/welcome/1
Go言語基礎文法を学ぶ事が出来ます。

GoDoc
https://godoc.org/
Go言語のパッケージを検索する事が出来ます。

Go言語学習本

環境構築

簡単な Hello world を実行してみる

Go言語で書いた簡単なコードを以下のサイトで確認する事が出来ます。
試しに Hello world を実行する事が出来ます。
https://play.golang.org/

Hello world

package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}

開発環境をPCに準備する

Go言語ツールのインストール

以下のリンクからインストールします。
https://golang.org/doc/install

Go言語ツールのディレクトリを環境変数GOROOTに設定する必要があります。

export GOPATH=$HOME/.go
export PATH=$GOPATH/bin:$PATH

まとめ

Go言語を学び、Go言語の良さを感じ、次世代の開発でGoエンジニアが必ず重宝される時代がくると強く感じております。

プログラミング言語別年収

go2.jpg

引用:プログラミング言語別年収中央値を発表、求人検索エンジン「スタンバイ」調べ
https://www.bizreach.co.jp/pressroom/pressrelease/2018/0807.html

Go言語は、2009年にリリースされたばかりの若い言語でありながら、すでに様々な企業が取り入れようと動いていることで注目度は年々高くなっています。また、Googleが開発している言語であることも注目されている理由になってきます。

今後、様々な取り入れ方でシステムの効率化がGo言語によって行われると考えられます。
今からGo言語を始めると良いと感じています。

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

Go言語についてと開発環境の準備

はじめに

Go言語は、Googleが開発したオープンソースのプログラミング言語であり、シンプルで信頼性の高い効率的なソフトウェアを簡単に構築できます。Go言語は、コンパイル言語の中でも特にエラーを検出しやすく、多人数での開発で起こるような重大なミスが起きにくくなっています。その為、現在では多くのサービスや企業で取り入れられています。

その為、技術レベルで遅れないように今からGo言語を始めると良いと思います。
Go言語についてと開発環境の方法を学んだので、備忘録としてまとめます。

Go言語とは

go.jpg

Go言語は、静的型付け、コンパイル言語、メモリ安全性、ガベージコレクション、構造的型付け、CSPスタイルの並行性などの特徴を持ちます。Go言語のコンパイラ、ツール、およびソースコードは、すべてフリーかつオープンソースです。

Github
https://github.com/golang

マスコット・キャラクターはGopher(ホリネズミ)です。

Go言語でできること

WEBサーバーの構築

標準パッケージを使うことで、WEBサービスを動作させるサーバー構築をスムーズに行えうことも可能です。

モバイルアプリケーション開発

モバイル開発向けのGo Mobileを使用する事で、Go言語でもiPhoneやAndroidのスマホアプリケーション開発に対応出来ます。

コマンドラインインターフェース(CLI)で動作するツール作成

CLIで動作するアプリケーションもGo言語での開発に向いています。

Go言語のマルチプラットフォームに対応したクロスコンパイル機能があるので、どんな環境でも問題なく開発できる点が非常に優秀です。

Go言語参考サイト

A Tour of Go
https://tour.golang.org/welcome/1
Go言語基礎文法を学ぶ事が出来ます。

GoDoc
https://godoc.org/
Go言語のパッケージを検索する事が出来ます。

Go言語学習本

環境構築

簡単な Hello world を実行してみる

Go言語で書いた簡単なコードを以下のサイトで確認する事が出来ます。
試しに Hello world を実行する事が出来ます。
https://play.golang.org/

Hello world

package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}

開発環境をPCに準備する

Go言語ツールのインストール

以下のリンクからインストールします。
https://golang.org/doc/install

Go言語ツールのディレクトリを環境変数GOROOTに設定する必要があります。

export GOPATH=$HOME/.go
export PATH=$GOPATH/bin:$PATH

まとめ

Go言語を学び、Go言語の良さを感じ、次世代の開発でGoエンジニアが必ず重宝される時代がくると強く感じております。

プログラミング言語別年収

go2.jpg

引用:プログラミング言語別年収中央値を発表、求人検索エンジン「スタンバイ」調べ
https://www.bizreach.co.jp/pressroom/pressrelease/2018/0807.html

Go言語は、2009年にリリースされたばかりの若い言語でありながら、すでに様々な企業が取り入れようと動いていることで注目度は年々高くなっています。また、Googleが開発している言語であることも注目されている理由になってきます。

今後、様々な取り入れ方でシステムの効率化がGo言語によって行われると考えられます。
今からGo言語を始めると良いと感じています。

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

VScodeのGo: Generate Interface Stubsでエラーが出る場合の対処法メモ

事象

同一ファイルでstructとinterfaceを定義して、structに対してinterfaceのメソッドを実装するために、Go Generete Interface Stubsしたところエラーになる。

コマンドパレットからGo Generate Interface Stubsを実行。

image.png

image.png

この時、VSCodeでエラーが出てしまう

Cannot stub interface: unrecognized interface go generate interface

対処法

自分の環境ではこれで直った。

go get -u github.com/josharian/impl

インストール後、再度Go Generate Interface Stubsを試す。

image.png

今度はstructにインタフェースのメソッドが追加された。

image.png

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

【Go】構造体について

構造体の定義

さまざまな型をデータ構造のひとつの型として扱うことができます。Goはオブジェクト指向とは違う、メソッドの機能によって型を便利に扱えるような仕組みに設計されています。

一般的に構造体は予約語typeで型名を与え、任意のフィールドを定義します。

type 型名 struct { フィールド定義 }

エイリアスの定義

先であげた予約語typeは、すでに定義されている型をもとに新しい型を定義するための機能です。そのため、下記のようにエイリアスを定義することができます。

main.go
type (
   Ints    [2]int
   Strings []string
)

func main() {
   i := Ints{5, 10}
   s := Strings{"test1", "test2"}

   fmt.Println(i)  //[5 10]
   fmt.Println(s)  //[test1 test2]
}

エイリアスの定義では、同じ型の二つのエイリアスには互換性がないので注意が必要です。

フィールド定義と複合リテラル

フィールドは下記のように定義します。

main.go
type Test struct {
   A int 
   B int 
}

func main() {
   t := Test{A: 5, B: 10}

   fmt.Println(t.A)  //5
   fmt.Println(t.B)  //10
}

構造体型Testでint型のフィールドA, Bに初期値を指定して定義してます。このようにGoには、初期値を指定し構造体を生成する複合リテラルが用意されています。

ちなみに、フィールド定義ではメタ情報を付与するタグという機能があります。

type Test struct {
   A int "test1"
   B int "test2"
}

プログラムの実行には影響しませんが、jsonパッケージを使用するプログラムのなどで有効に活用されます。

構造体の埋め込み

構造体に構造体型を定義するパターンがあります。

main.go
type Test1 struct {
   A int
   B int
}

type Test2 struct {
   A int
   Test1 Test1
}

func main() {
   t := Test2{
      A: 5,
      Test1: Test1{
         A: 6,
         B: 12,
      },
   }
   fmt.Println(t.Test1.A)  //6
   fmt.Println(t.Test1.B)  //12
}

このように埋め込まれた構造体を変数に指定することで、埋め込んだ構造体型のフィールドに干渉することができます。

上記のプログラムでは、埋め込む構造体型を明示的にフィールド名を与えていましたが、フィールド名を省略しても同じ結果が得られます。しかし、暗黙的なフィールドの場合プリフィックスが無視されてしまうことに注意が必要です。

組み込み関数new

組み込み関数newは、任意の型のポインタ型を生成するときに使用します。

main.go
type Test struct {
   A int
   B string
}

func main() {
   a := new(Test)

   fmt.Println(a.A)  //0
   fmt.Println(a.B)  //
}

メソッド

メソッドは下記のように定義します。

main.go
type Test struct {
   A int
   B string
}

func (a *Test) Example() {
   fmt.Printf("%d, %s\n", a.A, a.B)  //5, example
}

func main() {
   b := &Test{A: 5, B: "example"}
   b.Example()
}

このプログラムはa *Testはレシーバーを表しており、*Test型のメソッドExampleを定義し、変数bにアドレス演算子を使用した複合リテラルで初期化した値を呼び出して、出力しています。
これは、構造体型のポインタを使ったメソッドの定義でしたが、他の型や、エイリアスに対しても定義できる機能を持っています。

型のコンストラクタ

Goでは、オブジェクト指向プログラミング言語でのコンストラクタ機能に代わって、New型名と定義することで型のコンストラクタを表すことができます。

参考文献

著者 松尾愛賀
翔泳社 スターティングGo言語

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

A Tour of Go メモ ~Packages, variables, and functions~

はじめに

Go の勉強のため、A Tour of Goに取り組んでいる
今回はPackages, variables, and functions章について、学んだことを記していく

使えそうなスニペット

※筆者は VSCode を使用

  • スニペットは最初の一文字を入力するだけで予測変換に出てくるので、全部を入力する必要はない
  • また、文字を飛ばしても良い
  • 以下の例でいうと、pmと入力してもpkgmが予測変換に出る

pkgm -> package main
fm -> func main(){ }
func -> func (){ }
fp -> fmt.Println("")
ff -> fmt.Printf("", var)
iferr -> if err != nil { return nil, err}

ページごとの補足

A Tour of Goを進めていく中で、疑問に思ったことをまとめた

Packages

Import する package 名は、Import Path の最後の要素になる

  • Ex: math/rand -> rand

同じ名前のパッケージをインポートしたい場合は、Package Name を指定する
また、Package Name にドット(.)を指定すると、修飾子なしにメソッドにアクセスできる

// Import declaration          Local name of Sin

import   "lib/math"         // math.Sin
import m "lib/math"         // m.Sin
import . "lib/math"         // Sin

Ref. The Go Programming Language Specification

math/randの使い方

math/randはシード値が同じであれば同じ疑似乱数を返すため、実行するたびに値を変更したければSeedメソッドを都度呼ぶ必要がある
また、Seedメソッドの引数にtime.Now().UnixNano()を使うことで、疑似乱数が作りやすくなる。

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    fmt.Println(rand.Intn(10)) // 1 固定
    rand.Seed(10)
    fmt.Println(rand.Intn(10)) // 4 固定
    rand.Seed(time.Now().UnixNano())
    fmt.Println(rand.Intn(10)) // 毎回変わる
}

ただ、より安全性の高い乱数を作りたい場合はcrypto/randを使用する

package main

import (
    "crypto/rand"
    "fmt"
    "math/big"
)

func main() {
    n, err := rand.Int(rand.Reader, big.NewInt(10))
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(n)
}

Ref. Go の math/rand と crypto/rand

Imports

複数importする際は Factored Import Statement で記述する
(Factored は直訳で因数分解、意訳でグループ化らしい)
importに限らず、var, constで複数定義する際も、これを用いる
ちなみに、1 つだけ定義した場合もこのスタイルで書ける模様
(フォーマッタに添削されないということは、割とスタンダードなのだろうか)

import (
    "fmt"
    "math"
)

func main() {
    const (
        x = 1
        y = 2
    )
    var (
        z = math.Pi
    )
    fmt.Println(z)
}

Exported names

大文字の名前はエクスポートされ、小文字はされない

foo.go
package foo

func Test() string {
    return "Success"
}
func test() string {
    return "Failure"
}

const (
    CONSTANT = 1
    constant = 2
)
main.go
package main

import (
    "./foo"
    "fmt"
)

func main() {
    fmt.Println(foo.Test())
    fmt.Println(foo.CONSTANT)

    // Error: cannot refer to unexported name foo.test
    // Error: cannot refer to unexported name foo.constant
    fmt.Println(foo.test())
    fmt.Println(foo.constant)
}

ちなみに、ファイル分割の方法は以下が参考になった
Ref. Go 言語の package の作り方: 長くなったコードを別ファイルに切り出す方法

Functions

C 言語や Java 等とは異なり、変数名 型名の順に記述する
理由はこちら
※和訳は、こちらがわかりやすかった

一言で言えば、「こっちのほうが可読性が良いから」

Multiple results

複数の戻り値を返すことができる
戻り値の一部を使わない場合は、受け取る変数名を_にすれば良い
こうしないとコンパイル時にdeclared but not usedとエラーが出る

package main

import (
    "fmt"
)

func swap(x, y string) (string, string) {
    return y, x
}

func main() {
    a, _ := swap("Hello", "World")
    fmt.Println(a)
}

Basic Types

rune(ルーン) とは

'で定義される文字列であり、Unicode のコードポイントを指す。中身は int32 の alias である
コードポイントについてはこちらが分かりやすい

rune と string の違い

※以下の記事を参考にした

実は、string 型は、値を Byte 配列で格納している
そのため、単純に for 文で回すと以下のようになる

    x := "aあ"
    for i := 0; i < len(x); i++ {
        fmt.Printf("% x", x[i])
  }
  // 61 e3 81 82

    for i := 0; i < len(x); i++ {
        fmt.Printf("% v", x[i])
    }
  // 97 227 129 130

1文字を1文字として扱うには、for range構文を使う必要がある
for range 構文で string を回すと、rune が取れる

    x := "aあ"
    for _, y := range x {
        fmt.Printf("% x", y)
  }
  //  61 3042

    fmt.Println("")
    for _, y := range x {
        fmt.Printf(" %c", y)
    }
  // a あ

%cは整数(コードポイント)に対応する文字を返す
Ref. fmt.Printf なんかこわくない

Zero Values

変数に初期値が与えられないとゼロ値が与えられる
逆に、変数にゼロを与えた場合は、表記によって型が変わる

    fmt.Printf("%T", 0)   // int
    fmt.Printf("%T", 0.0) // float64

蛇足

ルーンって型名を使ってるだけで、Go 言語が大好きになった
以下を忘れないようにしよう
(キャスニキの呪文のコードポイントも調べようとしたけど、長すぎるので断念……)

    ansz := 'ᚨ' // アンサズ: 発火のルーン
    fmt.Printf("%d, %x", ansz, ansz) // 5800, 16a8
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCodeに毎日挑戦してみた 69. Sqrt(x)(Python、Go)

はじめに

無料英単語サイトE-tanを運営中の@ishishowです。

プログラマとしての能力を上げるために毎日leetcodeに取り組み、自分なりの解き方を挙げていきたいと思います。

Leetcodeとは

leetcode.com
ソフトウェア開発職のコーディング面接の練習といえばこれらしいです。
合計1500問以上のコーデイング問題が投稿されていて、実際の面接でも同じ問題が出されることは多いらしいとのことです。

golang入門+アルゴリズム脳の強化のためにgoとPythonで解いていこうと思います。(Pythonは弱弱だが経験あり)

17問目(問題69)

69. Sqrt(x)

問題内容

Given a non-negative integer x, compute and return the square root of x.

Since the return type is an integer, the decimal digits are truncated, and only the integer part of the result is returned.

(日本語訳)

2つのバイナリ文字列aとが与えられた場合bそれらの合計をバイナリ文字列として返します

負でない整数が与えられた場合x、の平方根を 計算して返しxます。

戻り値の型は整数であるため、10進数は切り捨てられ、結果の整数部分のみが返されます。

Example 1:

Input: x = 4
Output: 2

Example 2:

Input: x = 8
Output: 2
Explanation: The square root of 8 is 2.82842..., and since the decimal part is truncated, 2 is returned.

Constraints:

  • 0 <= x <= 231 - 1

考え方

  1. バイナリーサーチという探索方法を使用します。

  2. 左端と右端(l,r)を定義し、検索する間隔を半分にしながらループで回していきます。

  3. midの二乗とmid+1の二乗に値が入った時midを返します

  • 解答コード
class Solution(object):
    def mySqrt(self, x):
        l, r = 0, x
        while l <= r:
            mid = l + (r-l)//2
            if mid * mid <= x < (mid+1)*(mid+1):
                return mid
            elif x < mid * mid:
                r = mid - 1
            else:
                l = mid + 1
  • Goでも書いてみます!(goではバイナリーサーチしていません)
func mySqrt(x int) int {
    for i := 1; i <= x; i++ {
        if i*i == x { return i }
        if i*i > x { return i-1 }
    }
    return 0
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Lambdaをローカル環境に用意する(AWS-SAMのインストール)

前書き

lambdaを実装する必要があり、ローカルでプログラムのテストができないか調べてたところ、AWS SAMを用いて検証するようなので設定方法を書いていきます。

前提条件

Dockerがインストールが必要です。
+今回lamdaでgo言語を試すのでgoのインストール必要です。

検証環境

OS:Windows 10 Pro 64bit ver 1909
言語環境:go version go1.14.1

SAMで構築する検証環境について

AWS-SAMをインストールすることで下記のような環境をローカルに作成できます。

SAM.jpg

ローカルに仮想のAPI GaweWayを作りそこからLambda関数を実行する形です。
実行時にjsonを渡すことでevent発火時の動作も検証できます。

lambda関数の実行時にdockerコンテナが起動し関数が実行されます。そのため、dockerのインストールが必要になります。

SAMのインストール

AWS SAM CLI のインストール
上記リンクからAWS-SAM CLIをダウンロードしインストールします。

SAMをAPI GwateWay経由での実行する

チュートリアル
公式のチュートリアルを参考にHello-wouldを実行します。

1.テンプレートからアプリケーションの作成
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1
What package type would you like to use?
        1 - Zip (artifact is a zip uploaded to S3)
        2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 1
Which runtime would you like to use?
        4 - go1.x
Runtime: 4
Project name [sam-app]: Hello-Would
AWS quick start application templates:
        1 - Hello World Example
        2 - Step Functions Sample App (Stock Trader)
Template selection: 1
2.アプリケーションのビルド
cd .\Hello-Would\
sam build       //アプリケーションのビルド
3.アプリケーションの実行

まずローカル上のAPI Gatewayを起動しAPI叩くことでlamdaが実行されます。

sam local start-api   //Gatewayの起動
> C:\Windows\System32\curl.exe http://127.0.0.1:3000/hello
Hello, (IPアドレス)
(参考)Windows 10上でcurlの実行

windows10 ver 1803以降は標準のcurlツールがあるのでそちらを使ってテストします。

ディレクトリ
C:\Windows\System32
C:\Windows\System32\curl.exe http://127.0.0.1:3000/hello

上記で実行ができました。
lambdaが呼び出されたときにdocker imageが起動するので初回や環境によって時間がかかります。
GawteWayを起動せず直接実行する際は、プロジェクトフォルダで

sam local invoke

で実行できます。

感想

ローカルの別のコンテナに接続する際は、docker networkを指定する必要だったり、
event時の環境変数をjsonで設定できます。
そちらも別記事にしようと思います。

lambda関数を作成するうえでローカルに実行環境があるほうが便利ですが、意外と記事がなかったので作成しました。

誰かの役に立てば幸いです。
ありがとうございました。

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

golangci-lintを使いこなしたい。

初めに

この記事はWanoグループ Advent Calendar 2020 の6日目の記事になります。

golangci-lintって?

https://github.com/golangci/golangci-lint

Go言語には非常に多くのコード解析ツールがあります。例えば gofmt や goimports, govet などはgolangを書いている人なら知ってる人も多いと思います。
golangci-lintはGo言語のコード解析ツールを一つに集約したようなすごいやつです。

コードレビューをする時にまずこれを実行すると、機械的にまず直しておいたほうが良さそうな部分がわかって治安がよくなるかもしれないということですね。
そして解析結果はXMLだったりで出力することが出来るのでそこからさらにプログラムで使うのも簡単です。

インストールの方法などは上記リンクに記述してあるのでカットさせていただきます。

configファイルを晒してみる

golangci-lintではconfigファイルを用いて
* どの解析ツールを使うか
* 解析ツールの条件(変数の出現回数など)

などを設定することが出来ます。

自分が実際に使っているものを晒してみたいと思います(結構適当に設定してますが)。

run :
  issues-exit-code: 0

issues:
  max-issues-per-linter: 1000
  max-same-issues: 1000

output :
  format : checkstyle

linters:
  disable-all: true
  enable:
    - deadcode
    - errcheck
    - gosimple
    - govet
    - ineffassign
    - staticcheck
    - structcheck
    - typecheck
    - unused
    - varcheck
    - bodyclose
    - depguard
    - dogsled
    - gochecknoglobals
    - gochecknoinits
    # - goconst
    - gocritic
    - gofmt
    - goimports
    # - golint
    - gosec
    - interfacer
    - misspell
    - nakedret
    - prealloc
    - scopelint
    # - stylecheck
    - unconvert
    - unparam
    # - whitespace
    # - wsl

linters-settings:
  errcheck:
    ignore: '[rR]ead|[w|W]rite|[c|C]lose|[c|C]ommit|[r|R]ollback|[p|P]rintln'

  goconst:
    min-len : 5

  golint:
    min-confidence : 1.0

使い方としては

  1. まずgolangci-lint自体の設定を記述。
  2. 使うlinterを選ぶ。
  3. 各linterの設定を追加。

configファイルを呼んで解析する場合は

golangci-lint run -c .golang_review_config.yml <適当なpath>

とすれば良い感じに。なんでもかんでもぶち込むと「厳しすぎない?」となるので使うlinter類は各プロジェクトや開発者で選ぶと良いと思います。ちなみにymlファイルなのでコメントアウトも出来ます。

おすすめ

個人的に入れとくと良いやつを紹介してみます。
各linterの細かいconfigはカットです。

govet

これに関しては言うこともない気がします...が。
簡単に言うと「バグる可能性が高い部分を検知してくれる」やつです。なのでgovetでエラーが出る場合は「アウト」の認識だと良いと思います。例えばですが

type A struct {
    Num  int
    Text string
}

a := A{
    1,
    "aaa",
}

では「構造体を使う時に変数だけしか書いてない」ということで怒られます。

type A struct {
    Num  int
    Text string
}

a:=A{
    Num : 1,
    Text : "aaa",
}

こうしろということですね。

errcheck

個人的にgovetと並ぶと思っている強いやつです。エラーハンドリングし忘れたやつを教えてくれるので、これに引っかかる場合もアウトにして良いかもしれません(ただしデフォルトで Close() などでも引っかかる)。

func (db DB)insert(<hogehoge>)error

func main() {
    DB.insert(<hogehoge>) // エラー
    _ = DB.insert(<hogehoge>) // 明示するとlinterがスルーしてくれる
}

例えば「エラーを返す関数だけど確認してないよ!」というときに教えてくれます。もし確認する必要がなければ明示的に _ で受けるとlinterが確認しなくなります(こういうことは開発ルールとして明記したほうが良いのかも)。

他にも...

  • unconvert : 不要なキャストを知らせる
  • unparam : 不要な関数の引数を知らせる
  • unused : 使っていない変数を教えてくれる(自分の中でunparamと混じってるかも)

他にも不要な○○を教えてくれる、書き方が変または簡単に書ける(省略できる)、など色々なことを教えてくれます。

まとめ

色々なlinterを使って開発者ごとのブレを少なく出来るといいですね()。
書き方などはともかく、バグになる可能性がある解析ツールをONにしてコミットやpushするときに解析すると事前に確認が出来て良いと思います。

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

golangでテーブル駆動テスト(テーブルドリブンテスト)を並列実行で書く際の注意点

なぜかテストが正しく動作しないことがあり、調べたら以下が原因でした

https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721

なぜ上記のようなことが起こるかというと、
https://play.golang.org/p/QIjYezj9RzR
を見たら分かる通り、for loopでloop毎に新しく変数を作っているのではなく、同じ変数を使いまわしているからですね

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

[初めてのGoアプリ]簡単なタイピングゲームアプリ作ってみた!

概要

Goの基礎練習に、初めてアプリ作ってみました!
なお、今までRails中心のアプリしか作ったことない、完全Go初心者です。
Go言語書いたことないけど興味ある駆け出しエンジニアさんに見ていただきたいです!

※内容はProgateのGoコースを参考にしています。

成果物(コンソールにて)

% go run typing.go
[質問1] 次の単語を入力してください: html
html
正解!
[質問2] 次の単語を入力してください: css
ccs
不正解!
[質問3] 次の単語を入力してください: ruby
ruby
不正解!
5.291065秒かかりました
スコア 20

ほんと、このレベルですのでご了承ください...

なお、以下はGoがインストールされている前提です。
インストールされていない方は、私の以前の記事をご覧ください!
10分もあればインストールできますよ!
↓↓
▼Railsしか使ったことない駆け出しエンジニアが、Goの環境構築してみた!▼
https://qiita.com/matsufuji_tech/items/523b1520c1bb71c63f78

手順

1. 雛形を記述

typing.go
package main

import "fmt"

func main() {

}

2. 質問と正解or不正解を表示させるためのask関数を追加

typing.go
package main

import "fmt"

func main() {

}

//以下を追記
func ask() {

}

3. 質問のみをコンソールに表示

typing.go
package main

import "fmt"

func main() {
  //引数をask関数内に渡してask関数を実行
  ask("html")
  ask("css")
  ask("ruby")
}

//引数のデータ型を指定して変数questionとして受け取る
func ask(question string) {
  //以下を追記
  fmt.Printf("次の単語を入力してください: %s\n", question)
}

4. 変数inputのデータ型を設定し、fmt.Scanを用い入力待ちにする

typing.go
package main

import "fmt"

func main() {
  ask("html")
  ask("css")
  ask("ruby")
}

func ask(question string) {
  //変数inputを宣言し、データ型を指定
  var input string

  fmt.Printf("次の単語を入力してください: %s\n", question)

  //fmtパッケージ内の機能、Scanを用いquestion内に入力されたデータを格納させる
  fmt.Scan(&input)

}

5.正解or不正解のif分岐

typing.go
package main

import "fmt"

func main() {
  ask("html")
  ask("css")
  ask("ruby")
}

func ask(question string) {
  var input string

  fmt.Printf("次の単語を入力してください: %s\n", question)

  fmt.Scan(&input)

  //同じ文字列になるかどうかで分岐
  if question == input {
    fmt.Println("正解!")
  } else {
    fmt.Println("不正解!")
  }

}

6.int型のデータをnumberに引数として受け取らせ、[質問1]...を表示

typing.go
package main

import "fmt"

func main() {
  //引数として1, 2, 3をそれぞれ追加
  ask(1, "html")
  ask(2, "css")
  ask(3, "ruby")
}

//numberのデータ型を指定し、呼び出し時に引数として受け取れるようにする
func ask(number int, question string) {
  var input string

  //[質問1]...を追加し、numberを受け取る値として追加。(※numberはquestionより先に文字列に表示されるので、先に指定しないとエラーになります)
  fmt.Printf("[質問%d] 次の単語を入力してください: %s\n", number, question)

  fmt.Scan(&input)

  if question == input {
    fmt.Println("正解!")
  } else {
    fmt.Println("不正解!")
  }

}

7. 戻り値(ゲーム結果)を変数totalScoreで受け取るようにし、表示させる

typing.go
package main

import "fmt"

func main() {
  //戻り値を格納する変数totalScoreを定義。"+="はさらに足し合わせるという意
  totalScore := ask(1, "html")
  totalScore += ask(2, "css")
  totalScore += ask(3, "ruby")

  //結果を表示
  fmt.Println("スコア", totalScore)
}

//戻り値のデータ型を指定
func ask(number int, question string) int {
  var input string

  fmt.Printf("[質問%d] 次の単語を入力してください: %s\n", number, question)

  fmt.Scan(&input)

  if question == input {
    fmt.Println("正解!")
   //正解したら10を戻り値として返す
    return 10

  } else {
    fmt.Println("不正解!")
    //不正解なら0を戻り値として返す
    return 0

  }

}

8. ゲームにかかった時間を表示させる

typing.go
package main

import "fmt"
//tumeパッケージをインポート
import "time"

func main() {
  //ask関数実行前に現在の時刻を取得
  start := time.Now();

  totalScore := ask(1, "html")
  totalScore += ask(2, "css")
  totalScore += ask(3, "ruby")

  //ask関数実行後に現在の時刻を取得
  start := time.Now();

  //何秒かかったか表示
  fmt.Printf("%f秒かかりました\n", (end.Sub(start)).Seconds())

  fmt.Println("スコア", totalScore)
}

func ask(number int, question string) int {
  var input string
  fmt.Printf("[質問%d] 次の単語を入力してください: %s\n", number, question)
  fmt.Scan(&input)

  if question == input {
    fmt.Println("正解!")
    return 10

  } else {
    fmt.Println("不正解!")
    return 0

  }

}

終わりに

質問の文字列を色々変えて、
"go run typing.go"で遊んでみてください!

まだまだ、こんなレベルなのでこれからも精進いたします!

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

goでデータの循環参照をチェックする(gonumパッケージ グラフ入門)

Applibot Advent Calendar 2020」 6日目の記事になります。
前日は @ref3000 さんのCloud Firestore でさくっとオンライン対戦ゲームを作ろう という記事でした!

目次

1.はじめに
2.基礎知識
3.要件定義
4.実装
5.まとめと感想
6.おわりに

1. はじめに

管理者が入力するマスターデータに入れ子構造のデータが必要になり、循環参照が発生するデータを入力されるとロジック側が無限ループになってしまうため、それを回避するために検証機能を追加した話になります。
gonumパッケージのグラフとライブラリを利用することで簡単に実装することができます。

2. 基礎知識

本稿を読むにあたってグラフに関する基礎知識があると理解が深まるため、以下に簡単にまとめました。

2.1 グラフ

ノードと呼ばれる頂点と、エッジと呼ばれる辺で構成されるデータ構造です。
グラフには下記のような種類があり、丸の部分がノード(数字はノードを識別するid)、線の部分がエッジを表しています。

2.1.1 無向グラフ

各ノードをつなぐエッジに、方向がないグラフを無向グラフと呼びます。
「1 - 2 - 3」の間は相互につながっており、それぞれのノードから参照可能です。
simple_undirected_sample.png

2.1.2 有向グラフ

各ノードをつなぐエッジに方向があり、相互につながっているグラフを有向グラフと呼びます。
「1 -> 2」,「1 -> 3」で1は2と3を参照できることを、「2 -> 3」で2は3を参照できることを表します。
3は1,2の両方ともに矢印を持っていないため、1,2を参照することができません。
simple_directed_sample.png

2.1.3 単純グラフ

無効グラフ、有効グラフで示した例のように、ノード間のエッジが1本のみのものを単純グラフと呼びます。
simple_sample1.png

2.1.4 多重グラフ

「1 -> 2」, 「2 -> 1」のように、同一ノード間に2つ以上のエッジ(多重辺)を持つものが含まれているグラフを多重グラフと呼びます。
また、「3 -> 3」のように、自分自身へのエッジを持つものが含まれているグラフも多重グラフと呼びます。
multi_directed_sample2.png

2.2 循環参照

multi_directed1_c.png

2.2.1 閉路

1 -> 2 -> 4 -> 1」のように、始点と終点が同じノードになる経路のことを閉路と呼びます。

2.2.2 ループ

3 -> 3」のように自分自身へのエッジをもち、自身のノードを参照できるものを、ループと呼びます。

3. 要件定義

今回検証したいテーブルは、下記のようなrelationのテーブルのデータを二次元配列(スライス)で持っており、こちらに入力されたデータに対して循環参照が発生しないようなチェックを行いたいと思います。

parent_id child_id
1 2
1 3
2 1
2 3
2 4
4 1
4 5

上記のテーブルをグラフで表すと下記のようになっており、オレンジ色の「1 -> 2 -> 1」、「1 -> 2 -> 4 -> 1」で閉路を持っており、青色の「3 -> 3」でループをもっています。
relation_sample2.png

4. 実装

4.1 ソースコード

The Go Playgroundで動作確認ができます。

package main

import (
    "fmt"

    "gonum.org/v1/gonum/graph"
    "gonum.org/v1/gonum/graph/multi"
    "gonum.org/v1/gonum/graph/topo"
)

func main() {
    relations := [][]int64{
        {1, 2},
        {1, 3},
        {2, 1},
        {2, 4},
        {3, 3},
        {4, 1},
        {4, 5},
    }
    refs := getCircularReferences(relations)
    fmt.Println(refs)
}

func getCircularReferences(relations [][]int64) [][]graph.Node {
    // 参照関係からグラフの生成
    g := multi.NewDirectedGraph()
    for _, rel := range relations {
        // グラフのノードとエッジ生成&登録
        line := g.NewLine(multi.Node(rel[0]), multi.Node(rel[1]))
        g.SetLine(line)
    }

    // 循環参照チェック
    refs := make([][]graph.Node, 0)
    // ループチェック(topo.DirectedCyclesInではループを取得できないため)
    nodes := g.Nodes()
    for nodes.Next() {
        node := nodes.Node()
        // 自身のID間が参照可能であればループしているためrefsに追加
        if g.HasEdgeBetween(node.ID(), node.ID()) {
            refs = append(refs, []graph.Node{node})
        }
    }
    // 閉路チェック
    cycles := topo.DirectedCyclesIn(g)
    refs = append(refs, cycles...)
    return refs
}

4.2 説明

今回は要件的には単純有向グラフですが、データ構造は多重有向グラフであるため、multi.NewDirectedGraphを用います。
g.SetLine実行時にグラフに存在しないノードはグラフに追加され、グラフの管理下に置かれます。
topo.DirectedCyclesInでは閉路を取得可能ですがループは取得できないため、ループチェックの方で自分でグラフの持つ全ノードに対してループが存在するかチェックを行い、存在する場合refsに追加しています。

4.3 実行結果

下記のようになり、循環参照しているID群を全て取得することができました。

[[3] [1 2 1] [1 2 4 1]]

5. まとめと感想

gonumのグラフパッケージを使うことで簡単に循環参照の検証メソッドを追加することができました。

今回多重有向グラフを用いたように、たとえ要件的には単純有向グラフや木構造で成立するものでも、データ構造が表現可能なグラフで実装しないと異常データの検出が難しくなる場合もあるため、注意が必要だなと感じました。

gonumでは今回作成したグラフ以外にも重み付きグラフなども表現可能で、pathパッケージなどを使うことで「ダイクストラ」や「A*」などのアルゴリズムも利用でき、簡単に最短経路探索なども実装可能です。
アルゴリズム関連のメソッドは自分で実装するとブラックボックスになりがちで、バグなどにも気づきにくいことから、ライブラリを利用しても問題ない状況では今後も積極的に使っていきたいと思いました。

6. おわりに

Applibot Advent Calendar 2020」 6日目の記事でした!
明日は @fu11mated さんです!

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

GKEでのDBマイグレーション自動化手法

QualiArts Advent Calendar 2020、6日目担当の8kkaです。
今回はGKE環境でのDBマイグレーション自動化手法について書こうと思います。

1. 前提の設計など

今回は以下の構成で組まれているシステムに対して、自動化手法を組み込んでいきます。

  • アプリケーションはGoで記述されている
  • GKE上で稼働させている
  • CDはArgoCDを利用している
  • DBはCloudSQLを利用している

紹介しているコードやアーキテクチャは、実際に構築してみたものを記事用に編集して掲載しています。

ライブラリやツールの紹介をしつつ記述していますが、アーキテクチャの全体像だけ知りたい方は4. アーキテクチャと処理フローの画像だけ参照すると何となくイメージは掴めると思います。

2. マイグレーション実行用のコンテナイメージ作成

マイグレーションの処理には、golang-migrateというライブラリを利用します。
まずはこのライブラリを使ってマイグレーションを実行するコンテナイメージを作成します。

2.1 golang-migrate

golang-migrateは、Go言語で記述されたDBマイグレーションのライブラリです。
Goのライブラリとしても使えるし、CLIから実行する事も出来ます。
マイグレーションファイルはローカルからだけではなく、GitHubやS3、GCSから取得する事も出来ます。
また、利用可能なDBが多い事も特徴で、2020/12/06現在では以下のDBが対応されています。

  • PostgreSQL
  • Redshift
  • Ql
  • Cassandra
  • SQLite
  • SQLCipher
  • MySQL/ MariaDB
  • Neo4j
  • MongoDB
  • CrateDB
  • Shell
  • Google Cloud Spanner
  • CockroachDB
  • ClickHouse
  • Firebird
  • MS SQL Server

2.2 マイグレーションファイルの構成

公式が推奨するマイグレーションファイルの構成(ファイル名)は以下になります。

{version}_{title}.up.{extension}
{version}_{title}.down.{extension}

upはバージョンを1つ上げる際に使用されるファイル、downは1つ下げる際に使用されるファイルです。
空のファイルを作成した場合は、空のクエリを実行しようとするので注意が必要です。
{version} の命名については 1_master.up.sql, 2_master.up.sql のような連番にする方法や、タイムスタンプを用いる方法があります。
複数のリリースバージョンの開発が並行して進む場合があるため、リリースバージョンに合わせて {major}{minor}{patch}_schema.up.sql という規則で作成してみます。

v1.0.0 -> 001000000_schema.up.sql
v1.1.1 -> 001001001_schema.up.sql

例えば、v1.0.0でitemテーブルを作成してv1.1.1でvalueカラムを追加する際は、以下のようなファイル名とSQLを記述します。

001000000_schema.up.sql
CREATE TABLE IF NOT EXISTS `item` (
  `id` varchar(255) NOT NULL COMMENT 'アイテムID',
  `name` varchar(255) NOT NULL COMMENT '名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT='アイテム';
001001001_schema.up.sql
ALTER TABLE item ADD COLUMN value INT NOT NULL COMMENT '効果値' AFTER name;

v1.0.0のアプリがデプロイされている環境では001000000_schema.up.sqlで定義されているitemテーブルがマイグレートされ、v1.1.1のアプリがデプロイされている環境では、valueカラムが追加されたitemテーブルがマイグレートされます。

golang-migrateでDBのマイグレーションを実行すると、対象のDBにschema_migrationsというテーブルが作成され、そのテーブル内で現在のバージョンを管理することになります。
schema_migrationsはMySQLに対して実行した場合の名前で、CloudSpannerに実行した場合はSchemaMigrationsになるなど、DBによって命名は多少変わります。

2.3 実行用のコンテナイメージ

DBマイグレーションの実行は、Goで作ったアプリケーションをコンテナ化してArgoCD上のJobとして実行します。
コンテナ化するアプリケーションは以下のようなコードです。

package main

import (
    "context"
    "log"

    "github.com/golang-migrate/migrate/v4"
    _ "github.com/golang-migrate/migrate/v4/database/mysql"
    _ "github.com/golang-migrate/migrate/v4/source/file"
    "golang.org/x/sync/errgroup"
)

func main() {
    eg, _ := errgroup.WithContext(context.Background())
    eg.Go(func() error {
        if err := migrateMySQL(); err != nil {
            return err
        }
        return nil
    })
    if err := eg.Wait(); err != nil {
        log.Panic(err)
    }
}

func migrateMySQL() error {
    m, err := migrate.New(
        "file://./db/ddl/master",
        "mysql://{user}:{password}@tcp({port})/{db}",
    )
    if err != nil {
        return err
    }
    if err := m.Up(); err != nil && err != migrate.ErrNoChange {
        return err
    }
    return nil
}

Goroutineを使用しているのは、複数のデータベースに対してマイグレーションを実行する際に並列に処理したいためです。
(今回省略していますが、複数のDBを利用する要件もありました。)

migrate.ErrNoChangeのエラー判定を除外しているのは、「マイグレーション実行時にスキーマ変更がなかった場合エラーとして終了させないようにする」ための記述です。
例えばv1.0.0からv1.1.0に上げる場合、カラムが追加されるのはv1.1.1からなのでスキーマに変更が入らず、エラーとなってしまいます。
こちらを回避するため、migrate.ErrNoChangeの判定を実装しています。

上記アプリケーションをリリースバージョンのタグをつけてコンテナイメージ化します。
コンテナイメージはCloudBuildで作成し、ContainerRegistryに保存しておきます。
(ArtifactRegistryも使ってみたいですが、現状まだ触れていません。)

作成したマイグレーションファイルは./db/ddl/masterに保存しておき、コンテナ内に一緒に詰めておきます。
イメージにタグを付ける際、タグバージョンとコンテナ内に保存されているマイグレーションファイルの最新バージョンが同じものとなります。

例: v1.1.0のコンテナ内マイグレーションファイル
./db/ddl/master/001000000_schema.up.sql

例: v1.1.1のコンテナ内マイグレーションファイル
./db/ddl/master/001000000_schema.up.sql
./db/ddl/master/001001001_schema.up.sql

3. ArgoCDを使ったマイグレーションの自動実行

作成したコンテナイメージはGKE上のArgoCDを通して実行します。
ここでは、ArgoCDの ResourceHooks という仕組みを使って、GKEへのアプリケーションデプロイ前にマイグレーションを実行する方法を記述します。

3.1 ArgoCD Resource Hooks

ArgoCDにはResourceHooksという仕組みがあり、同期の操作前、操作中、操作後にスクリプトを実行する事ができます。
マニフェストの例はこちら。

apiVersion: batch/v1
kind: Job
metadata:
  generateName: db-schema-migrate
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded

argocd.argoproj.io/hookでHookのタイミングを指定し、argocd.argoproj.io/hook-delete-policyでHookリソースの削除を設定できます。
このマニフェストの場合は、「同期前にJobが実行され、Jobが成功したらHookリソースを削除する」という挙動になります。

3.2 Hook Policy

argocd.argoprj.io/hookで設定できる項目は以下になります。

hook ポリシー 動作
PreSync マニフェスト適用前に実行
Sync PreSync完了時に実行
Skip マニフェスト適用をスキップ
PostSync マニフェスト適用に成功したら実行
SyncFail マニフェスト適用が失敗したら実行

今回はDBマイグレーションをマニフェスト適用前に実行したいので、PreSyncのポリシーを利用します。
また、argocd.argoprj.io/hook-delete-policyで設定できる項目は以下になります。

hook-delete ポリシー 動作
HookSucceeded フックで実行した処理が成功したらフックリソース削除
HookFailed フックで実行した処理が失敗したらフックリソース削除
BeforeHookCreation 新しいフックリソースが作られる前に既存のフックリソース削除

DBマイグレーションで実行したJobが成功した場合はリソースを削除し、失敗した場合は調査のためPodを残したいので、HookSucceededを利用します。

3.3 反映させるマニフェスト

実際に反映させるマニフェストは以下のような記述になります。

apiVersion: batch/v1
kind: Job
metadata:
  generateName: db-migrate
  namespace: job
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      containers:
      - name: db-migrate
        image: "gcr.io/test-app/github.com/qualiarts/migrate:v1.0.0"
        imagePullPolicy: Always
        command:
        - "/migrate"
      restartPolicy: Never
  backoffLimit: 0

Jobのリソースとして作成し、ResourceHooksのアノテーションを追加します。
実行はContainerRegistryに保存してあるマイグレーション用のイメージを利用します。
このマニフェストをArgoCDのApplicationリソースに紐付ける事で、ResourceHooksの仕組みが動作します。

4. アーキテクチャと処理フロー

全体のアーキテクチャと処理フローはこちらになります。
スクリーンショット 2020-12-06 3.16.47.png

app-serverにマイグレーションファイルを作成し、タグが切られたタイミングでCloudBuildが起動し、タグバージョンが指定されたコンテナイメージを作成します。
コンテナイメージ作成後、helmに記載されているアプリのバージョンを上げてmasterブランチにプッシュすると、ArgoCDが変更を検知してResourceHooksの処理が走ります。
ResourceHooksのマイグレーション処理が正常に完了すると、DBスキーマが更新されてからhelmの差分同期が走ります。

まとめ

golang-migrateとArgoCDのResourceHooksを利用する事で、DBのスキーママイグレーションを自動化する事が出来ました。
今回CloudSQLを例に挙げて記述しましたが、golang-migrateで対応されているCloudSpannerなど他のDBでも同じように構築する事が出来るので、似たような環境で構築されている方はぜひお試しください。
(一応、筆者が実際に確認したのはCloudSQLとCloudSpannerだけです。)

ここまで閲覧頂き、ありがとうございました。
明日は hikaru-suzuki さんの記事です。

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

VSCode, DockerでHelloWorldを10言語やってみる

はじめに

  • VSCodeのremote-containersが開発環境構築時にすごく便利だと感じたので、10のプログラミング言語でHelloWorldやってみます
    • Rust, Go, Python, C++, C#, Ruby, TypeScript, Java, Dart, プロデル!
  • なるべくシンプルに、汎用的に
  • まだ全てにおいてはデバッグ実行できるところまで記述していません(手抜き。。)

準備

  • 前提:VSCode 1.51.1
  • 前提:Windows10 home(Insider Previewバージョン)の環境で試していますが、Macでもほぼ変わらないと思います
  • VSCodeをインストールする(方法は割愛)
    • Extension:Remote-Containerをインストールする
  • Docker環境を用意する
    • Windowsなら、Docker Desktop for Windowsを使えるようにします。
    • Macなら、Docker Desktop for Macのインストールは簡単です。

①Rust

  • 1.まず適当なworkディレクトリを作ってVSCodeで開きます
    • この時点でディレクトリ内は空でいいです
  • 2.「Command Palette」を開いて、Remote-Containers:Add Development Container Configuration Files...を実行
    • image.png
    • Show All Definition...を選択すると全て表示されます
    • Rustを選択します
    • .devcontainerディレクトリ、その配下にdevcontainer.jsonDockerfileが生成されます
  • 3.コンテナを生成して、VSCodeをコンテナ接続に切り替えます
    • ポップアップでも促されると思いますが、「Command Palette」を開いて、Remote-Containers:Add Development Container Configuration Files...を実行
      • image.png
    • Dockerデーモンが起動していることが前提です、初回はdocker imageのダウンロードやビルドもあるので少々時間がかかります。
    • コンテナが起動すると、VSCodeも新しく起動されます
  • 4.helloworld.rsを作成して、コーディングします。
fn main(){
    println!("hello world!");
}
  • 5.vscode内のterminalでビルドします、バイナリが生成されます
$ rustc helloworld.rs
  • 6.実行します
    • まずlaunch.jsonを作成
      • 「Command Pallete」を開いて、Open launch.jsonを選択、LLDBを選択
      • image.png
      • image.png
    • 中身を少し書き換えます、"program": "${workspaceFolde}/helloworld"とします。
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug",
            "program": "${workspaceFolder}/helloworld",
            "args": [],
            "cwd": "${workspaceFolder}"
        }
    ]
}
    • F5で実行します
    • image.png
  • 7.最終的な構成です

│  helloworld
│  helloworld.rs
│
├─.devcontainer
│      devcontainer.json
│      Dockerfile
│
└─.vscode
        launch.json

②Go

  • 1.前項参照
  • 2.ほぼ前項と同じ
    • Go1.15、選択していく
  • 3.前項参照
  • 4.helloworld.goを作成して、コーディングします。
package main

import "fmt"

func main() {
    fmt.Printf("hello world!\n")
}

  • 5.vscode内のterminalで実行します
$ go run helloworld.go
  • 6.簡単ですね!

③Python

  • 1.前項参照
  • 2.ほぼ前項と同じ
    • Python 33.9、選択していく
  • 3.前項参照
  • 4.helloworld.pyを作成して、コーディングします。
print("hello world!")
  • 5.vscode内のterminalで実行します
$ python helloworld.py
  • 6.こちらも簡単ですね!

④C++

  • 1.前項参照
  • 2.ほぼ前項と同じ
    • C++Ubuntu20.04、選択していく
  • 3.前項参照
  • 4.helloworld.cppを作成して、コーディングします。
#include <stdio.h>
int main() {
    printf("hello world");
    return 0;
}
  • 5.ビルド&実行します

    • 最初だけ次のようにlaunch.json, tasks.jsonを作成
    • 「Command Pallete」を開いて、Open launch.json → C++(GDB/LLDB) → g++ Build and debug active file compiler:/usr/bin/g++を選択
    • image.png
    • あとはF5で実行できます
  • 6.ディレクトリ構造です

│  helloworld
│  helloworld.cpp
│
├─.devcontainer
│      devcontainer.json
│      Dockerfile
│
└─.vscode
        launch.json
        tasks.json

⑤C#'

  • 1.前項参照
  • 2.ほぼ前項と同じ
    • C#(.NET Core)3.1(default)、選択していく
  • 3.前項参照
  • 4.Terminalからプロジェクトを生成します。
$ dotnet new console
  • 5.ビルド&実行します

    • F5実行で下記のダウンロードが始まります(数分かかる) - image.png
    • .NET Coreを選択すると、launch.jsontask.jsonが生成されます
  • 6.ディレクトリ構造です

│  Program.cs
│  test-devcontainer-csharp-helloworld.csproj
│
├─.devcontainer
│  │  devcontainer.json
│  │  Dockerfile
│  │
│  └─library-scripts
│          azcli-debian.sh
│
├─.vscode
│      launch.json
│      tasks.json

⑥Ruby

  • 1.前項参照
  • 2.ほぼ前項と同じ
    • Ruby2.7、選択していく
  • 3.前項参照
  • 4.helloworld.rbを作成して、コーディングします。
print "hello world!\n"
  • 5.vscode内のterminalで実行します
$ ruby helloworld.rb
  • 6.ディレクトリ構造です
│  helloworld.rb
│
└─.devcontainer
        devcontainer.json
        Dockerfile

⑦TypeScript

  • 1.前項参照
  • 2.ほぼ前項と同じ
    • Node.js & TypeScript14、選択していく
  • 3.前項参照
  • 4.helloworld.rbを作成して、コーディングします。
console.log("hello world!");
  • 5.vscode内のterminalで実行します
$ tsc helloworld.cs
$ node helloworld.js
  • 6.ディレクトリ構造です
│  helloworld.js
│  helloworld.ts
│
└─.devcontainer
        devcontainer.json
        Dockerfile

⑧Java

  • 1.前項参照
  • 2.ほぼ前項と同じ
    • Java15、選択していく
  • 3.前項参照
  • 4.helloworld.javaを作成して、コーディングします。
public class helloworld{
   public static void main(String[] args){
     System.out.println("hello world!!");
   }
}
  • 5.vscode内のterminalで実行します
$ javac helloworld.java
$ java helloworld
  • 6.ディレクトリ構造です
│  helloworld.class
│  helloworld.java
│
└─.devcontainer
        devcontainer.json
        Dockerfile

⑨Dart

  • 1.前項参照
  • 2.ほぼ前項と同じ
    • Dartを選択していく
  • 3.前項参照
  • 4.helloworld.dartを作成して、コーディングします。
void main() {
  print('hello world!');
}
  • 5.vscode内のterminalで実行します
$ dart helloworld.dart
  • 6.ディレクトリ構造です
│  helloworld.dart
│
└─.devcontainer
    │  devcontainer.json
    │  Dockerfile
    │
    └─library-scripts
            common-debian.sh

⑩プロデル(笑)

  • 1.前項参照
  • 2.下記のディレクトリ、ファイルを手動で作成
└─.devcontainer
        devcontainer.json
        Dockerfile
devconainer.json
{
    "name": "Produire",
    "build": {
        "dockerfile": "Dockerfile",
        "context": "..",
    },

    "settings": { 
        "terminal.integrated.shell.linux": "/bin/bash",
    },

    "extensions": [
        "utopiat.produirelang"
    ],
}

Dockerfile
FROM mono

RUN \
apt-get update && \
apt-get install unzip wget -y && \
rm -rf /var/lib/apt/lists/*

RUN wget -O produire.zip https://rdr.utopiat.net/files/mono//produire-mono-1.6.965.zip \
&& unzip 'produire.zip' -d /usr/bin

  • 3.前項参照
  • 4.helloworld.rdrを作成して、コーディングします。
「hello world!」を表示する
  • 5.vscode内のterminalで実行します
$ mono /usr/bin/produire-mono/pconsole.exe  helloworld.rdr
  • 6.ディレクトリ構造です
│  helloworld.rdr
│
└─.devcontainer
        devcontainer.json
        Dockerfile
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Regular Contest 110のメモ

前置き

Atcoderをやってみたので、自分用のメモです。
あとから加筆・修正する予定です。

問題

https://atcoder.jp/contests/arc110

A

Q_A.go
package main

import (
    "fmt"
)

func gcd(a, b int64) int64 {
    if b == 0 {
      return a
    }
    return gcd(b, a % b)
}

func lcm(a int64, b int64) int64{
    return a * b / gcd(a,b)
}

func main() {
    var N int64
    fmt.Scanf("%d", &N)

    var ans int64 = 1
    var i int64 
    for i=2; i<=N; i++{
        ans = lcm(ans, i)
    }

    fmt.Printf("%d\n", ans+1)
}

B

Q_B.go
package main

import (
  "fmt"
)

func main() {
  var N int64
  fmt.Scanf("%d", &N)

  var T string
  fmt.Scanf("%s", &T)

  var ans int64

  str := "110"
  var n int64
  n = (N+1) / 3 + 1

  var count int64 = 0 
  var i int64

  var S string
  // 文字列候補の作成
  for i=0; i<n; i++{
    S = S + str
  }

  var flag int64
  for i=0; i<3; i++{
    if T == S[i:i+N]{
      count += 1
      flag = i
    }
  }

  if N != 1{
    ans = (10000000000 - (N+flag-1)/3) * count
  } else {
    ans = (10000000000 - (n-1)) * count
  }

  fmt.Printf("%d\n", ans)
}

C

覚えてたら後で書きます。

D

覚えてたら後で書きます。

E

覚えてたら後で書きます。

F

覚えてたら後で書きます。

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