20200927のGoに関する記事は9件です。

【Go】基本文法② ステートメント

udemy講座の「ステートメント」編を見終わったので、まとめとふりかえり。

今回もコンソール上に出力しながら基本的な動作を学習しました。

前提

・udemyで現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発)を視聴してGoを学習中
 ※「ブログ等で数行のコードの紹介は可能」とのことなので、講義で扱ったコードを引用しています。

・開発環境はVSCode + プラグイン(Goのバージョンは1.15)
・OSはWindows
・普段はJavaを使用しています。

if文・switch文

・書き方はjavaと似ている。
・if文で条件文に括弧をつけるとエラーになる。
 (癖で括弧を記述しても、VSCodeが自動で括弧を取り除いてくれます。)

for文

・for文もjavaと同じ書き方でOK。
・"range"を使用するとより簡単に記述できる。
・"range"はmapでも使用可能。

講座で出題された演習問題の自分の回答と解答例を比べると、javaのような記述とrangeを使用した記述の違いが分かりました。どちらも同じ結果が得られますが、rangeを使用する方がすっきり記述できます。rangeはmapも扱えるので、応用も効きそうです。


問題:以下のスライスから一番小さい数を探して出力するコードを書いてください。

l := []int{100, 300, 23, 11, 23, 2, 4, 6, 4}

私の回答
 l := []int{100, 300, 23, 11, 23, 2, 4, 6, 4}
    min := l[0]
    for i := 0; i < len(l); i++ {
        if min >= l[i] {
            min = l[i]
        }
    }
 fmt.Println(min)

解答例
 l := []int{100, 300, 23, 11, 23, 2, 4, 6, 4}

    var min int

    for i, num := range l {
        if i == 0 {
            min = num
            continue
        }

        if min >= num {
            min = num
        }
    }

 fmt.Println(min)

defer

・deferで記述した処理は、関数の処理が終わった後に実行される。
・ファイルのクローズに使用すると便利。
・スタック式で実行される。(はじめに記述した処理が最後に実行される。)

例:ファイルの内容をコンソールへ出力する
package main

import (
    "fmt"
    "os"
)

func main() {
    file, _ := os.Open("./lesson.go")
    // ファイルは必ずクローズする必要がある。最初にdeferで書いておくとクローズし忘れることがない。
    defer file.Close()

    // ファイルを読み込む際は、データとしてバイト配列を用意する必要がある。
    data := make([]byte, 100)
    file.Read(data)
    // stringにキャスト
    fmt.Println(string(data))

}

ログ

・標準ライブラリ(log)では最低限のログ出力しかできない。
・詳細なログが必要な場合は、サードパーティのライブラリを利用する。
・Fatalln()では、その時点で処理が強制終了されるので、後続処理が行われない。

代表的なログ出力関数
    log.Println("logging!")
    log.Printf("%T %v", "test", "test")

    log.Fatalln("error!") // この時点でコートが終了する。
    log.Fatalf("%T %v", "test", "test") // 実行されない

エラーハンドリング

・try-catchではなく、ifでハンドリングする。
・panic(例外)をプログラム中に記述することは非推奨。受け取ったエラーをif文で処理することが推奨されている。

所感

・短い記述でよくある処理(配列操作・ファイルクローズ・ログ出力)ができるので便利。
・関数の挙動や推奨される記述を正確に理解しておかなければならないので、難易度が高い。
・複雑な処理を行うには、標準ライブラリの関数だけでは限界がありそう。


<前回の投稿>

基本文法① 定義

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

【Go】基本文法まとめ②

udemy講座の「ステートメント」編を見終わったので、まとめとふりかえり。

今回も基本的な動作をコンソール上に出力しながら学習しました。

前提

・udemyで現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発)を視聴してGoを学習中
 ※「ブログ等で数行のコードの紹介は可能」とのことなので、講義で扱ったコードを引用しています。

・開発環境はVSCode + プラグイン(Goのバージョンは1.15)
OSはWindows
・普段はJavaを使用しています。

if文・switch文

・書き方はjavaと似ている。
・if文で条件文に括弧をつけるとエラーになる。
 (癖で括弧を記述しても、VSCodeが自動で括弧を取り除いてくれます。)

for文

・for文もjavaと同じ書き方でOK。
・"range"を使用するとより簡単に記述できる。
・"range"はmapでも使用可能。

講座で出題された演習問題の自分の回答と解答例を比べると、javaのような記述とrangeを使用した記述の違いが分かりました。どちらも同じ結果が得られますが、rangeを使用する方がすっきり記述できます。rangeはmapも扱えるので、応用も効きそうです。


問題:以下のスライスから一番小さい数を探して出力するコードを書いてください。

l := []int{100, 300, 23, 11, 23, 2, 4, 6, 4}

私の回答
 l := []int{100, 300, 23, 11, 23, 2, 4, 6, 4}
    min := l[0]
    for i := 0; i < len(l); i++ {
        if min >= l[i] {
            min = l[i]
        }
    }
 fmt.Println(min)

解答例
 l := []int{100, 300, 23, 11, 23, 2, 4, 6, 4}

    var min int

    for i, num := range l {
        if i == 0 {
            min = num
            continue
        }

        if min >= num {
            min = num
        }
    }

 fmt.Println(min)

defer

・deferで記述した処理は、関数の処理が終わった後に実行される。
・ファイルのクローズに使用すると便利。
・スタック式で実行される。(はじめに記述した処理が最後に実行される。)

例:ファイルの内容をコンソールへ出力する
package main

import (
    "fmt"
    "os"
)

func main() {
    file, _ := os.Open("./lesson.go")
    // ファイルは必ずクローズする必要がある。最初にdeferで書いておくとクローズし忘れることがない。
    defer file.Close()

    // ファイルを読み込む際は、データとしてバイト配列を用意する必要がある。
    data := make([]byte, 100)
    file.Read(data)
    // stringにキャスト
    fmt.Println(string(data))

}

ログ

・標準ライブラリ(log)では最低限のログ出力しかできない。
・詳細なログが必要な場合は、サードパーティのライブラリを利用する。
・Fatalln()では、その時点で処理が強制終了されるので、後続処理が行われない。

代表的なログ出力関数
    log.Println("logging!")
    log.Printf("%T %v", "test", "test")

    log.Fatalln("error!") // この時点でコートが終了する。
    log.Fatalf("%T %v", "test", "test") // 実行されない

エラーハンドリング

・try-catchではなく、ifでハンドリングする。
・panic(例外)をプログラム中に記述することは非推奨。受け取ったエラーをif文で処理することが推奨されている。

所感

・短い記述でよくある処理(配列操作・ファイルクローズ・ログ出力)ができるので便利。
・関数の挙動や推奨される記述を正確に理解しておかなければならないので、難易度が高い。
・複雑な処理を行うには、標準ライブラリの関数だけでは限界がありそう。

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

gRPCで使う.protoファイルの書き方まとめ

  • gRPCでは、Protocol Buffersのフォーマットでシリアライズしてデータのやり取りを行う。
  • .protoファイルにてスキーマ定義を行い、ツールを使ってコード生成ができる。そのため、クライアント・サーバーそれぞれでこの.protoファイルを共有できれば、仕様のズレなく開発をすすめることが可能。
  • この記事では、.protoファイルの文法をざっとまとめてみる

サンプル全体

// バージョン定義
syntax = "proto3";

// パッケージ定義
package sample;

// import
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";

// サービスとRPCメソッド定義
service SampleService {
    rpc Sample (SampleRequest) returns (SampleResponse);

    // サーバーストリーミングRPC
    rpc SampleServerStreamMethod (SampleRequest) returns (stream SampleResponse);

    // クライアントストリーミングRPC
    rpc SampleClientStreamMethod (stream SampleRequest) returns (SampleResponse)

    // 双方向ストリーミングRPC
    rpc SampleBidirectionalMethod (stream SampleRequest) returns (stream SampleResponse)
}

message SampleRequest {
    string name = 1;
}

message SampleResponse {
    Sample sample = 1;
}


// メッセージ型
// 右の数字は「タグナンバー」
message Sample {
    // スカラー型
    // 数値、文字列、真偽値、バイト配列がある
    int32 id = 1;
    string name = 2;
    bool isBool = 3;

    // deprecated 廃止予定かつ非推奨であるフィールドを明示する
    string duplicated_field = 4 [deprecated = true]

    // reserved識別子 廃盤にしたタグナンバー
    reserved 7, 8, 10 to 12;

    // リスト(配列)
    // 多次元配列は定義できない
    repeated SampleList sample_list = 5;

    // マップ(連想配列)
    map<string, string> sample_map = 6;

    // 複数の中からどれかひとつ
    oneof message {
        string one = 1;
        string other = 2;
    }

    // Well Known Types
    google.protobuf.Duration sample_duration = 9;
    google.protobuf.Timestamp create_time = 13;

    // 列挙型
    enum SampleEnum {
        UNKNOWN = 0;
        TEST1 = 1;
        TEST2 = 2;
        TEST3 = 3;
    }

}

バージョンの定義

syntax = "proto3";

パッケージの定義

  • 他の.protoファイルで定義したメッセージを使うときなどに、名前の衝突を避けるためにパッケージ名を設定できる
package sample;

import

  • 他の.protoファイルで定義したメッセージ型を使いたい場合に使う
  • ここではGoogleがつくった.protoファイルをimportしている
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";

サービスとRPCメソッド

  • APIの定義本体
  • SampleServiceというサービスを定義
  • SampleRequestを引数に取り、SampleResponseを返すSampleというメソッドを定義している
  • ストリーミングにしたい場合は引数、戻り値にstreamをつける
  • 単方向ストリーミング
    • サーバーストリーミングRPC(戻り値にstreamを設定)
      • サーバー側が複数のレスポンスを非同期で返す
    • クライアントストリーミングRPC(引数にstreamを設定)
      • クライアント側から複数のリクエストを非同期で送り、サーバー側は1つのレスポンスを返す
  • 双方向ストリーミング
    • 引数、戻り値の両方にstreamをつける
service SampleService {
    rpc Sample (SampleRequest) returns (SampleResponse);

    // サーバーストリーミングRPC
    rpc SampleServerStreamMethod (SampleRequest) returns (stream SampleResponse);

    // クライアントストリーミングRPC
    rpc SampleClientStreamMethod (stream SampleRequest) returns (SampleResponse);

    // 双方向ストリーミングRPC
    rpc SampleBidirectionalMethod (stream SampleRequest) returns (stream SampleResponse);
}

スカラー型

データ型 デフォルト値
string 空文字
bytes 空配列
bool false
数値 0
enum 最初に定義された値。必ず0にする
メッセージ型 実装依存
repeated 空配列

メッセージ型

  • 複数のフィールドを持った型
  • タグナンバー
    • フィールドの右側の数字。
    • 同一メッセージ内で一意にしなければならない
    • reserved識別子
      • フィールド削除などで廃盤にしたタグナンバーを記載する
    • deprecatedオプション
      • 廃止予定かつ非推奨のフィールドを明示する
message Sample {
    // スカラー型
    // 数値、文字列、真偽値、バイト配列がある
    int32 id = 1;
    string name = 2;
    bool isBool = 3;

    // deprecated 廃止予定かつ非推奨であるフィールドを明示する
    string duplicated_field = 4 [deprecated = true]

    // reserved識別子 廃盤にしたタグナンバー
    reserved 7, 8, 10 to 12;

  // 〜 中略 〜
}

リスト(配列)

  • 型の前にrepeatedをつけることで配列を定義できる
  • スカラー型・メッセージ型ともに使用可能
  • 多次元配列は定義できない
    // リスト(配列)
    // 多次元配列は定義できない
    repeated SampleList sample_list = 5;

マップ(連想配列)

  • キーには整数値、文字列、真偽値のみ使える
  • mapは配列にできない
    map<string, string> sample_map = 6;

列挙型

  • 型の前にenumをつけることで列挙型を定義できる
    // 列挙型
    enum SampleEnum {
        UNKNOWN = 0;
        TEST1 = 1;
        TEST2 = 2;
        TEST3 = 3;
    }

oneof

  • oneofをつけることで、複数のものから1つという定義ができる
  • ここではoneotherを返すメッセージ型になる
    oneof message {
        string one = 1;
        string other = 2;
    }

Well Known Types

  • Googleが定義したメッセージ型
  • 日時・期間や、戻り値を返さないことを表すものなどがある
    google.protobuf.Duration sample_duration = 9;
    google.protobuf.Timestamp create_time = 13;

コードの生成

  • 上記で定義した.protoファイルをもとに、クライアント・サーバーの両サイドで各言語用のprotocコマンドを使ってボイラープレートコードを生成する

参考

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

【Golang】Go言語基礎 Goroutineとは?

本日はgoroutineについて勉強しましたのでここでアウトプットさせて頂きます!

Goroutineとは?

Goroutineとはgoステートメントで関数を指定することで、並行実行されるものです!

まずは普通の関数を書いてみます。

package main

import (
    "fmt"
    "time"
)

func goroutine(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func hoge(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    goroutine("world")
    hoge("hello")
}
実行結果
world
world
world
world
world
hello
hello
hello
hello
hello

これは、普通に上から関数が0.1秒ごとに実行されているだけですね。

これを並行実行するためには、関数名の前にgoを付けるだけで並行実行できます。

(同じなので省略)

func main() {
    go goroutine("world")
    hoge("hello")
}
実行結果
hello
world
world
hello
world
hello
hello
world
world
hello

並行実行しているので出力が混じった状態になります。
このように goステートメントを指定するだけで並行実行が簡単に実現できます。

ここで、timeの部分をコメントアウトしてみるとどうなるでしょうか?

package main

import (
    "fmt"
)

func goroutine(s string) {
    for i := 0; i < 5; i++ {
        //time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func hoge(s string) {
    for i := 0; i < 5; i++ {
        //time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go goroutine("world")
    hoge("hello")
}
実行結果
hello
hello
hello
hello
hello

timeの部分をコメントアウトすると、helloしか出力されなくなりました。
これは、並列処理でgoroutineスレッドが生成されても、先にmain関数の処理が終わってしまったため、
goroutine関数が実行されずに終わってしまったからです。

このようにgorutineの処理が終わらなくてもプログラムのコードは終了してしまうということを覚えておきましょう。

ではこれを避けるためにはどうすればよいか?

sync.WaitGroupというのを使います。

sync.WaitGroup

package main

import (
    "fmt"
    "sync"
)

func goroutine(s string, wg *sync.WaitGroup) {
    for i := 0; i < 5; i++ {
        fmt.Println(s)
    }
    wg.Done()
}

func hoge(s string) {
    for i := 0; i < 5; i++ {
        fmt.Println(s)
    }
}

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go goroutine("world", &wg)
    hoge("hello")
    wg.Wait()
}

このようにwgという変数を宣言してwg.Add(1)で一個の並列処理があるということを記述し、
goroutine関数へwgのアドレスを引数で渡してあげます。

そして、wg.Wait()と書いてあげることで、gorutine関数のwg.Done()が呼ばれるまで処理が終わるのを待ってくれます。
このように書くことでgoroutine関数が実行されずに処理が終わってしまうことを回避することができます。

ちなみに、wg.Add(1)でgoroutineの処理が終わるのを待っているので、 wg.Done()をコメントアウトするとエラーになります。
wg.Addしたらwg.Done()を呼び出して処理が完了したことを示す必要があります。

またgoroutine関数は以下のように書くこともできます。

func goroutine(s string, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 5; i++ {
        fmt.Println(s)
    }
}

deferステートメントを使うとその関数の処理が終わった後にwg.Done()が実行されるので、このように書くこともできるのです。

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

間違いや感想などありましたらコメントいただけると嬉しいです!!

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

Golang + Gin + Dockerのホットリロード、VSCodeデバッグ環境の構築

概要

Go言語のフレームワークGinの開発環境の構築。(ホットリロード、VSCodeデバッグ対応)

今回のコードはGitHubに公開してあります。
https://github.com/yolo-lin/go-demo

環境

  • MacOS 10.15.6
  • go version go1.15.2 darwin/amd64

導入するパッケージ

Gin:Goのフレームワーク
cosmtrek/air:ホットリロード
delve:デバッグ

事前準備

  • Goのインストール:公式からインストール
  • Go Modulesで依存関係を管理するため、環境変数GO111MODULEをonにする
.zshrc
export GO111MODULE=on
  • Go Modulesの初期化
/src
$ go mod init パッケージ名

ディレクトリ構成

Ginのディレクトリ構成は特に決まってないので、以下は自己流です。
メインのソースコードは全部srcの下に配置する。今後はmodelscontrollersもsrcに追加する予定です。

.
├── .vscode
│   └── launch.json
├── docker
│   ├── go
│   │   └── Dockerfile
│   └── mysql
│       └── Dockerfile
├── src
│   ├── go.mod
│   ├── go.sum
│   ├── .air.toml
│   └── main.go
│
└── docker-compose.yml

Docker

Go

/docker/go/Dockerfile
FROM golang:1.15.2

ENV GO111MODULE=on

COPY src/ /go/src/app/

WORKDIR /go/src/app

# cosmtrek/airのインストール
RUN go get -u github.com/cosmtrek/air
# delveのインストール
RUN go get -u github.com/go-delve/delve/cmd/dlv

# airの起動
CMD air -c .air.toml

MySQL

/docker/go/Dockerfile
FROM mysql:8.0

RUN chown -R mysql /var/lib/mysql && \
    chgrp -R mysql /var/lib/mysql

docker-compose

./docker-compose.yml
version: '3'

networks:
    backend:
      driver: bridge

services:
    go:
        container_name: go
        build:
            context: .
            dockerfile: ./docker/go/Dockerfile
        ports:
            - 8080:8080
            - 2345:2345
        links:
            - mysql
        tty: true
        volumes:
            - ./src:/go/src/app
        depends_on:
            - mysql
        security_opt:
            - seccomp:unconfined
        cap_add:
            - SYS_PTRACE
        networks:
            - backend

    mysql:
        container_name: mysql
        build:
            context: .
            dockerfile: ./docker/mysql/Dockerfile
        environment:
            MYSQL_USER: root
            MYSQL_ROOT_PASSWORD: password
            MYSQL_DATABASE: demo
        hostname: mysql
        ports:
            - "3306:3306"
        volumes:
            - ./docker/mysql/data:/var/lib/mysql
        security_opt:
            - seccomp:unconfined
        networks:
            - backend

    phpmyadmin:
        image: phpmyadmin/phpmyadmin
        environment:
            - PMA_ARBITRARY=1
            - PMA_HOST=mysql
            - PMA_USER=root
            - PMA_PASSWORD=password
        ports:
            - "8081:80"
        depends_on:
            - mysql
        networks:
            - backend

cosmtrek/airの設定

公式のair_example.tomlを流用

以下デバッグ用の記述を追記

/src/.air.toml
# Debug
#full_bin = "APP_ENV=dev APP_USER=air /go/bin/dlv exec ./tmp/main --headless=true --listen=:2345 --api-version=2 --accept-multiclient"
/src/.air.toml
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format

# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"

[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o ./tmp/main ."
# Binary file yields from `cmd`.
bin = "tmp/main"

# Customize binary.
full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"

# Debug
#full_bin = "APP_ENV=dev APP_USER=air /go/bin/dlv exec ./tmp/main --headless=true --listen=:2345 --api-version=2 --accept-multiclient"

# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# Watch these directories if you specified.
include_dir = []
# Exclude files.
exclude_file = []
# This log file places in your tmp_dir.
log = "air.log"
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 1000 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # ms

[log]
# Show log time
time = false

[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"

[misc]
# Delete tmp directory on exit
clean_on_exit = true

これで、docker-compose up -dしたら、自動的に設定ファイルを適応し、ホットリロード化される。

デバッグ

vscode

Goの拡張機能をインストール、以下デバッグ設定ファイルを追加する。

/.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Remote",
            "type": "go",
            "request": "launch",
            "mode": "remote",
            "program": "${workspaceFolder}",
            "port": 2345,
            "host": "127.0.0.1",
            "showLog": true,
        }
    ]
}

デバッグが必要な場合、cosmtrek/airの設定を変更する。
開発用とデバッグ用のfull_binを交換し、docker-compose再起動したら、vscodeでデバッグ可能になります。

/src/.air.toml
# Customize binary.
# full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"

# Debug
full_bin = "APP_ENV=dev APP_USER=air /go/bin/dlv exec ./tmp/main --headless=true --listen=:2345 --api-version=2 --accept-multiclient"

ただし、コード更新した場合、cosmtrek/airより自動ビルドされるが、vscode側が切断され、手動で再接続が必要です。
CleanShot 2020-09-27 at 16.22.58.png

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

【Go】基本文法① 定義

udemy講座の「定義」編を見終わったので、まとめとふりかえり。

基本的な動作をコンソール上に出力しながら学習しました。

前提

 ※「ブログ等で数行のコードの紹介は可能」とのことなので、講義で扱ったコードを引用します。

  • 開発環境はVSCode + プラグイン(Goのバージョンは1.15)
  • OSはWindows

ライブラリ

・goサイトの"document"か、コマンドプロンプトで説明が読める。
・関数外でimportして使用する。

変数

・宣言した変数が使用されていないとコンパイルエラーになる。
・まとめて宣言することも可能 ↓

    var (
        a int
        b float64
        c string
        d bool
    )

・関数の中では、型を指定しなくても宣言できる。(自動で型を判定してくれる。)
・変数の型はfmtライブラリの関数を使用して調べられる。

   x := 1

     // Printfで型を調べられる
     fmt.Printf("%T", x)

・不変な値はconstで宣言する。ふつう関数外に書く。

const (
    // constでは型が指定されていない(コンパイラに解釈はされているが、実行はされていない)
    Username = "test_user"
    Password = "test_pass"
)

・他のgoのファイルからも呼び出される場合、変数名は大文字ではじめる。

配列

・要素数を指定して生成した配列は、サイズを変更できない。
・スライスで要素数を指定せずに配列を生成すると、appendで要素を追加できる。
・要素数0の配列の宣言方法は2通り↓

    // パターン1 初期値での配列をメモリに生成
    array1 := make([]int, 0)
    // パターン2 nil(null的な扱い)
    var array2 []int

Map

・Map型で保存されている。
・キーから値を取り出せる。

    m := map[string]int{"apple": 100, "banana": 200}

    // キーから値を取り出せる
    fmt.Println(m["apple"])

    // キーを指定しvalueを更新できる
    m["banana"] = 300
    fmt.Println(m)

    // 新しい要素の追加も可能
    m["new"] = 500
    fmt.Println(m)

クロージャー

・関数ごと戻り値として設定できる。
・関数内の値は保持される。
・値の動きが分かりにくいので、デバッグツールで動きを追うと良い。

func circleArea(pi float64) func(radius float64) float64 {
    return func(radius float64) float64 {
        return pi * radius * radius
    }
}

func main(){
    // はじめに円周率を設定
    c1 := circleArea(3.14)
    c2 := circleArea(3)

    // 設定した円周率から計算を実行
    fmt.Println(c1(2))
    fmt.Println(c2(2))
}

クロージャーの説明は、こちらのサイトも参考にしました。

Javaとの違い

普段はJavaを使っているので、気になった点がいくつかありました。

・行の最後に「;」が無い。
・クラスが存在しない。
・public/privateを明記しない。
・「変数名 <型>」の順に記述する。(javaと逆)
・未使用の変数が一つでもあると、エラーになる。
・オブジェクト指向ではなく、ライブラリで用意された関数を使用して値を扱う。
 (オブジェクトクラスの継承がない。)

※Goがオブジェクト指向かどうかについては、「Yes and no」だそうです。(FAQの回答より)

Is Go an object-oriented language?
Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).
Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.

所感

・処理が速い。
・関数の記述方法はjavaScriptに近いように感じた。
 (javaScriptの知識も浅いので確かではないです。。。JSも勉強します。)
・Mapが扱いやすい。for文で検索しなくても要素を抽出できる。
・udemyでの学習は説明を聞きながら手を動かせるので、効率が良い。

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

【Go】基本文法まとめ①

udemy講座の「定義」編を見終わったので、まとめとふりかえり。

基本的な動作をコンソール上に出力しながら学習しました。

前提

 ※「ブログ等で数行のコードの紹介は可能」とのことなので、講義で扱ったコードを引用します。

  • 開発環境はVSCode + プラグイン(Goのバージョンは1.15)
  • OSはWindows

ライブラリ

・goサイトの"document"か、コマンドプロンプトで説明が読める。
・関数外でimportして使用する。

変数

・宣言した変数が使用されていないとコンパイルエラーになる。
・まとめて宣言することも可能 ↓

    var (
        a int
        b float64
        c string
        d bool
    )

・関数の中では、型を指定しなくても宣言できる。(自動で型を判定してくれる。)
・変数の型はfmtライブラリの関数を使用して調べられる。

   x := 1

     // Printfで型を調べられる
     fmt.Printf("%T", x)

・不変な値はconstで宣言する。ふつう関数外に書く。

const (
    // constでは型が指定されていない(コンパイラに解釈はされているが、実行はされていない)
    Username = "test_user"
    Password = "test_pass"
)

・他のgoのファイルからも呼び出される場合、変数名は大文字ではじめる。

配列

・要素数を指定して生成した配列は、サイズを変更できない。
・スライスで要素数を指定せずに配列を生成すると、appendで要素を追加できる。
・要素数0の配列の宣言方法は2通り↓

    // パターン1 初期値での配列をメモリに生成
    array1 := make([]int, 0)
    // パターン2 nil(null的な扱い)
    var array2 []int

Map

・Map型で保存されている。
・キーから値を取り出せる。

    m := map[string]int{"apple": 100, "banana": 200}

    // キーから値を取り出せる
    fmt.Println(m["apple"])

    // キーを指定しvalueを更新できる
    m["banana"] = 300
    fmt.Println(m)

    // 新しい要素の追加も可能
    m["new"] = 500
    fmt.Println(m)

クロージャー

・関数ごと戻り値として設定できる。
・関数内の値は保持される。
・値の動きが分かりにくいので、デバッグツールで動きを追うと良い。

func circleArea(pi float64) func(radius float64) float64 {
    return func(radius float64) float64 {
        return pi * radius * radius
    }
}

func main(){
    // はじめに円周率を設定
    c1 := circleArea(3.14)
    c2 := circleArea(3)

    // 設定した円周率から計算を実行
    fmt.Println(c1(2))
    fmt.Println(c2(2))
}

クロージャーの説明は、こちらのサイトも参考にしました。

Javaとの違い

普段はJavaを使っているので、気になった点がいくつかありました。

・行の最後に「;」が無い。
・public/privateを明記しない。
・「変数名 <型>」の順に記述する。(javaと逆)
・未使用の変数が一つでもあると、エラーになる。
・オブジェクト指向ではなく、ライブラリで用意された関数を使用して値を扱う。
 (オブジェクトクラスの継承がない。)

※Goがオブジェクト指向かどうかについては、「Yes and no」だそうです。(FAQの回答より)

Is Go an object-oriented language?
Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).
Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.

所感

・処理が速い。
・関数の記述方法はjavaScriptに近いように感じた。
 (javaScriptの知識も浅いので確かではないです。。。JSも勉強します。)
・Mapが扱いやすい。for文で検索しなくても要素を抽出できる。
・udemyでの学習は説明を聞きながら手を動かせるので、効率が良い。

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

TinyGoの開発環境をVSCode向けにセットアップする

TinyGoのVSCode向け開発環境セットアップがちょっと面倒だったので書き残し。
公式の記事はここ
TinyGoのインストール・セットアップは他記事をご参照ください。
筆者の環境 : MacBook Air (Retina, 13-inch, 2020) macOS Catalina 10.15.5

image.png

2020/09/27 追記1

.vscode/settings.json は下記TinyGo拡張機能のインストール・セットアップを行えば自動的に生成されるとのご指摘をいただきました。
なので以下settings.jsonの作成 パートはスキップしてしまって構いません。

TinyGoの設定確認

TinyGoでは本家Goとは別パスにあるGOROOT(?)を使用するようです。1
@sago35 さんよりご指摘頂き、TinyGoはmachineパッケージなどのTinyGo専用パッケージはTinyGoのROOTより解決し、それ以外のimportパスは通常のGo言語の物を使っている(GOPATHも本家Goと同じ)とのことでした。sago35さんありがとうございます!
tinygo info でTinyGo用のGOROOTのパスを確認して下さい。

$ tinygo info microbit
// 出力結果...

// LLVM triple:       armv6m-none-eabi
// GOOS:              linux
// GOARCH:            arm
// build tags:        cortexm baremetal linux arm nrf51822 nrf51 nrf microbit tinygo gc.conservative scheduler.tasks
// garbage collector: conservative
// scheduler:         tasks
// cached GOROOT:     /home/user/.cache/tinygo/goroot-go1.14-f930d5b5f36579e8cbd1c139012b3d702281417fb6bdf67303c4697195b9ef1f-syscall

cached GOROOT:... で始まる行のパスがTinyGo用GOROOTです。
build tags:... で始まる行がビルド時に使用するオプションです。

settings.jsonの作成

VSCodeの設定を直接いじっても良いのですが、通常のGoを用いた開発に影響を出したくないため、プロジェクトフォルダに.vscodeフォルダを作成しsettings.json ファイルを作成します。
その際GOROOT はそのまま貼り付けて問題ないのですが、GOFLAGSは-tags=を先頭につけ、スペースに代わりにカンマ(,) を使って区切ります。
例えば筆者の環境ではbuild tags: darwin amd64 tinygo gc.extalloc scheduler.coroutines という結果だったため、
-tags=darwin,amd64,tinygo,gc.extalloc,scheduler.coroutines となります。

settings.json
{
    "go.toolsEnvVars": {
        "GOROOT": "さっき取得したGOROOT...",
        "GOFLAGS": "-tags=avr,baremetal,linux,arm,atmega328p,atmega,avr5,arduino,tinygo,gc.conservative,scheduler.none"
    }
}

TinyGo 拡張機能のインストール

ExtensionsよりTinyGo をインストールします。
image.png

インストール後、コマンドパレットよりTinyGo targetを選択し、使っているマイコンを選択します。

image.png

筆者の場合Arduino互換機のMaruduinoのため、arduinoを選択します。2

image.png

選択後、VSCodeをリロードし"machine"パッケージの自動補完が正しく効けば完了です。
image.png


  1. ビルド時にGOROOTとかGOPATHを独自のものに置き換えてビルドしている(?)ようですが詳しくはわからず... どなたかコメントでご教授いただけると幸いです。 

  2. マイコン初心者の自分にも優しくArduino選びに付き合っていただけました。marutsuさんありがとうございました...! 

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

GO/Gin gin-swagger使用してAPI仕様書を生成します

前書

この記事は gin-swaggerライブラリ使用してGinプロダクトにAPI仕様書生成するための知見をまとめるものになります。
gin-swaggerリポジトリリンク
swagger-banner.png

テスト用のプロダクトを作成

フォルダを作成

// 任意のディレクトリで
$ mkdir use_swagger && cd use_swagger

go modを初期化

$ go mod init use_swagger

ライブラリインストール

$ go get -u github.com/gin-gonic/gin
$ go get -u github.com/swaggo/swag/cmd/swag
$ go get -u github.com/swaggo/gin-swagger
$ go get -u github.com/swaggo/files

インストール完了後のバージョン確認。

$ swag -v
swag version v1.6.7

現在のディレクトリ構成

|-- use_swagger
|-- |-- go.mod

プロダクトにAPI仕様書追加

use_swaggerディレクトリ配下にmain.goファイル作成

$ touch main.go

追加前

main.go
package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.New()  
    r.GET("/test", test)    
    r.Run()
}

func test(c *gin.Context){
    c.JSON(http.StatusOK, gin.H{ "msg": "ok"})
}

サーバー立ち上げ後 http://localhost:8080/testにアクセスすると、簡単なJsonデータが戻ってきます。
キャプチャ.PNG

追加後

main.go
package main

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    "github.com/swaggo/gin-swagger"
    "net/http"

    _ "use_swagger/docs"
)

// @title APIドキュメントのタイトル
// @version バージョン(1.0)
// @description 仕様書に関する内容説明
// @termsOfService 仕様書使用する際の注意事項

// @contact.name APIサポーター
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io

// @license.name ライセンス(必須)
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host localhost:8080
// @BasePath /
func main() {
    r := gin.New()

    url := ginSwagger.URL("http://localhost:8080/swagger/doc.json")
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))

    r.GET("/test", test)
    r.Run()
}

// @description テスト用APIの詳細
// @version 1.0
// @accept application/x-json-stream
// @param none query string false "必須ではありません。"
// @Success 200 {object} gin.H {"code":200,"msg":"ok"}
// @router /test/ [get]
func test(c *gin.Context){
    c.JSON(http.StatusOK, gin.H{ "msg": "ok"})
}

仕様書を初期化します。

$ swag init

初期化完了後、ディレクトリ構成は以下のようになりました。

|-- use_swagger
|-- |-- docs
|-- |-- |-- docs.go
|-- |-- |-- swagger.json
|-- |-- |-- swagger.yaml
|-- |-- main.go
|-- |-- go.mod

サーバーを立ち上げ、http://localhost:8080/swagger/index.htmlへアクセスすれば、仕様書が表示されます。
キャプチャ.PNG
実際使ってみます、適当にパラメータを渡して、実行します。
キャプチャ.PNG
期待通りのレスポンスが返ってきました。
キャプチャ.PNG

よく使用されるオプション

API オプション

オプション 説明
description 操作動作の詳細な説明。
id 操作を識別するために使用される一意の文字列。すべてのAPI操作の中で一意である必要があります。(使用されてる所あんまり見ない)
tags コンマで区切られた各API操作へのタグのリスト、APIの関連性を示します。
summary 操作の実行内容の簡単な要約。
accept APIが使用できるMIMEヘッダタイプのリスト。値は、MIMEヘッダタイプで説明されているとおりでなければなりません。
produce APIが生成できるMIMEタイプのリスト。値は、MIMEヘッダタイプで説明されているとおりでなければなりません。
param スペースで区切られたパラメーター。パラメータ名、パラメータタイプ、データタイプ、必須ですか?、コメント属性(オプション)
security 各API操作のセキュリティ。
success スペースで区切られた成功レスポンス。レスポンスコード、{param type}、データ型、コメント
failure スペースで区切られた障害レスポンス。レスポンスコード、{param type}、データ型、コメント
router パス、[httpMethod]

使用凡例

// @Summary Auth admin
// @Description get admin info
// @Tags accounts,admin
// @Accept  application/x-json-stream
// @Produce  application/x-json-stream
// @Success 200 {object} model.Admin
// @Failure 400 {object} httputil.HTTPError
// @Failure 401 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Security ApiKeyAuth
// @Router /admin/auth [post]
func (c *Controller) Auth(ctx *gin.Context) {
    authHeader := ctx.GetHeader("Authorization")
    if len(authHeader) == 0 {
        httputil.NewError(ctx, http.StatusBadRequest, errors.New("please set Header Authorization"))
        return
    }
    if authHeader != "admin" {
        httputil.NewError(ctx, http.StatusUnauthorized, fmt.Errorf("this user isn't authorized to operation key=%s expected=admin", authHeader))
        return
    }
    admin := model.Admin{
        ID:   1,
        Name: "admin",
    }
    ctx.JSON(http.StatusOK, admin)
}
// ShowBottle godoc
// @Summary Show a bottle
// @Description get string by ID
// @ID get-string-by-int
// @Tags bottles
// @Accept  json
// @Produce  json
// @Param  id path int true "Bottle ID"
// @Success 200 {object} model.Bottle
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /bottles/{id} [get]
func (c *Controller) ShowBottle(ctx *gin.Context) {
    id := ctx.Param("id")
    bid, err := strconv.Atoi(id)
    if err != nil {
        httputil.NewError(ctx, http.StatusBadRequest, err)
        return
    }
    bottle, err := model.BottleOne(bid)
    if err != nil {
        httputil.NewError(ctx, http.StatusNotFound, err)
        return
    }
    ctx.JSON(http.StatusOK, bottle)
}

その他API情報は公式ドキュメントこちらのリポジトリを参考にしてください。

使用感想

gin-swaggerライブラリは非常に便利とは言えない、コメントから生成されるAPI仕様書であるために、
記述する際には間違えがないかよく確認したほうがいいと思います。:point_up_tone1:

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