20200327のGoに関する記事は6件です。

Golang 入門する

個人的なメモとして用いる。

GOの特徴

  • コンパイラ言語
  • 静的型付け言語
  • 文法がシンプル
  • クロスコンパイルが可能
  • 標準パッケージが豊富

ソースコードの構成

main.go
// パッケージの定義
package main

// 標準パッケージや外部ライブラリのインポート
import "fmt"

// main関数の定義
func main {
  fmt.Println("Hello World")
}

プログラムはどこから実行されるのか

  • main関数
    • mainパッケージに存在する
    • ソースコードを追う時はここから
    • プログラムのエントリポイントで、初期化後はここから実行される
    • webサーバの場合は常に実行されているので代わりにhttpハンドラを読む

Goプログラムの構成要素

  • パッケージ
  • 関数
  • 変数
  • 定数

パッケージ

  • 関数や定数・変数・型を意味のある単位でまとめたもの
  • Goプログラムはパッケージの組み合わせでできあがる
    • mainパッケージから別のパッケージをimportする
      • importすることで様々な機能が使えるようになる

標準パッケージ:https://golang.org/pkg/

  • fmt
    • 書式に関する処理など
  • net/http
    • webサーバなど
  • archive, comress
    • zipなどの圧縮形式
  • encording
    • JSON, XML, CSV, TSVなどのファイル形式
  • html/template
    • web用のHTMLを生成する機能
  • os, path/filepath
    • osへのアクセスや操作、ファイル操作など

組み込み型(最初から使える)

種類 ゼロ値
整数 int, int8, int16, int32, int64
uint, uint8, uint16, uint32, uint64
uintptr, byte, rune
0
浮動小数点数 float32, float64 0
複素数 complex64, complex128 0
文字列 string ""
真偽値 bool false
エラー error nil

型のキャスト

下のsample.goはコンパイルエラーになる。
原因はif文で用いる変数と浮動小数点数の不一致。

sample.go
var sum int
sum = 5 + 6 + 3
avg := sum / 3

// float64(avg)とキャストすればエラーではなくなる
if avg < 4.5 {
    fmt.Println(avg)
}

キャストできない場合には、エラーとなる
例)int("hello world") など

コンポジット型

複数のデータ型が集まって1つの型となっている

型の種類 説明 ゼロ値
構造体 型の異なるデータ型を集めたデータ型 全フィールドがゼロ値
配列 同じ型のデータを集めて並べたデータ型 要素が全てゼロ値
スライス 配列の一部を切り出したデータ型 nil
マップ キーと値をマッピングさせたデータ型 nil

構造体

  • 型のデータ型の変数を集めたデータ構造
    • 各変数はフィールドと呼ばれる
    • フィールドの方は自由
    • フィールドの方にはコンポジット型やユーザ定義型も使用可能
sample.go
type Person struct {
    Name string
    age  int
}

bob := Person{"Bob", 30}
fmt.Println(bob.Name, bob.Age) // Bob 30

また以下のように初期化関数を作成することで初期化をすることも一般的に行われます。

sample.go
func newPerson(name string, age int) *Person {
     person := new(Person)
     person.Name = name
     person.Age = age
     return person
}

func main(){
  var jen *Person = newPerson("Jennifer", 40)
  fmt.Println(jen.Name, jen.Age) //=>Jennifer 40
}

Goではクラスの概念がないが、構造体に対してメソッドを定義することができる

sample.go
func (<レシーバ引数>) <関数名>([引数]) [戻り値の型] {
    [関数の本体]
}
sample.go
func (p Person) intro(greetings string) string{
    return greetings + " I am " + p.Name
}

func main(){
  bob := Person{"Bob", 30}
  fmt.Println(bob.intro("Hello")) //=> Hello I am Bob
}

配列

  • 同じ型のデータを集めて並べたデータ構造
    • 要素の型は全て同じ
    • 要素数が違えば違う型
    • 要素数は変更できない
    • 型は型リテラルで記述することが多い
sample.go
// 初期化
var n [5]int
var n [5]int{1, 2, 3, 4, 5}
n := [...]{1, 2, 3, 4} //lengthを値から推論
n := [...]{3: 20, 4: 11} //要素数: 5, n[3] = 20, n[4] = 11

// アクセス
n[1] // 添字は変数可
n[2:3] //スライス演算

スライス

  • 配列の一部を切り出したデータ構造
    • 要素の型は全て同じ
    • 要素数は型情報に含まない
    • 背後に配列が存在する
sample.go
// 初期化 // アクセスは配列と同様
var n []int // 変数にゼロ値を代入
n := make([]int, 3, 10) // 長さと容量を指定して初期化, 各要素䛿ゼロ値で初期化される
var n = []int{1, 2, 3, 4, 5} // スライスリテラルで初期化, 要素数は指定しない, 自動で配列が作られる

n = append(n, 10, 30) // 要素の追加 // 容量が足りない場合は背後の配列が再確保される
len(n) // 長さ
cap(n) // 容量

マップ

  • キーと値をマッピングさせるデータ構造
    • キーと値の型を指定できる
    • キーには「==」で比較できる型しかNG

型リテラル
var n map[string]int

sample.go
// 初期化
var n map[string]int
var m = make(map[string]int)
n := make(map[string]int, 10) // 容量の指定
m = map([string]int){"a": 2, "b": 4}

// 操作
n["a"] // アクセス
n["a"] = 20 // キーの値を上書き

n, ok := m["a"] // キー存在の確認, ok << true, false
if _, ok := m["no"]; !ok {
    fmt.Println("そのキーはありません")
}

delete(m, "z") // 要素の削除
sample.go
// マップのバリューがスライス
var a map[string][]int
a = map[string][]int{
    "a": []int{1, 2, 3},
    "b": []int{4, 5, 6},
    "c": []int{7, 8, 9},
}
fmt.Println(a["a"][0])

ユーザ定義型

  • typeで名前をつけて新しい型を定義する type 型名 基底型
sample.go
// 組み込み型を基にする
type MyInt int

// 他のパッケージを基にする
type MyWriter io.Writer

// 型リテラルを基にする
type Person struct {
    Name string
}

型リテラル

  • 型リテラルとは
    • 型の具体的な定義を書き崩した型の表現方法
    • コンポジット型などを表現するために使う
    • 変数定義やユーザ定義型などで使用する
sample.go
// intのスライスを用いた変数定義
var n []int

// mapの型リテラルを用いた変数定義
var m map[string]int

リテラル = 識別子(名前)が付与されていないもの

関数

  • 一連の処理をまとめたもの
    • 引数で受け取った値を基に処理を行い戻り値として結果を返す機能
    • 引数:関数䛾入力となるものf(x)の場合x
    • 戻り値(返り値): 関数の出力となるもの
  • 戻り値がある場合䛿変数に代入したり式中で使う
    • x := f(12, 1+1, y)

組み込み関数

関数名 機能
print, println 表示を行う
make コンポジット型の初期化
new 指定した型のメモリ
len/cap スライスの長さ/容量を返す
copy スライスのコピー
delete マップから指定したキーのエントリ
complex 複素数型を作成
imag/real 複素数の虚部/実数部を取得
panic/revocer パニックを起こす/回復する

関数の定義

sample.go
// 関数の定義方法
func add(x, y int) int {
}

// 複数の戻り値を返す
func swap(x int, y int) (int, int) {
    return x, y
}

// 名前付き戻り値
func swap(x y int) (x2, y2 int) {
    x2, y2 := x, y
    return
}

func main() {
    x, y := swap(1, 2) // 返り値はカンマで区切って取得する
}

クロージャ

  • 無名関数とも呼ばれる
  • 定義と実行のタイミングを気をつける
    • 関数外の変数を参照している場合
    • 実行のタイミングでは値が変わっている可能性がある
sample.go
// 「引数に文字列を取り、戻り値に文字列を返す関数」を返す関数
func later() func(string) string {
    var store string

    // 前の関数の呼び出し時に格納されている変数の値を返す
    return func(next string) string {
        s := store
        store = next
        return s
    }
}

func main() {
    f := later()
    fmt.Println("a: " + f("Golang"))
    fmt.Println("b: " + f("is"))
    fmt.Println("c: " + f("awesome!"))
    fmt.Println("d: " + f(""))
}

変数

  • メモリに名前をつけて値を格納する
  • 変数は型を持つ
  • 変数に違う型の値を代入することはできない
  • 暗黙の型演算がない
  • 型推論がある

変数定義

sample.go
// 変数定義と代入を同時に
var n int = 100
// 変数代入時に型推論
var n = 100
// 変数に後から代入
var n int
n = 100
// varを省略, 型推論
n := 100
// まとめて定義
var (
    n = 100
    b = 1000
)

定数

  • 値の変わらないもの
  • リテラルで記述される場合が多い
  • 型推論ではデフォルトの型を使用
  • 桁区切りには_を用いる
    • 例)100_000_000

定数定義

sample.go
const n int = 100
const n = 100
const n = "hello" + "world" // 定数式, コンパイル時に計算される

const (
    n = 100
    m = 200
)

制御構文

条件分岐: if

sample.go
// 通常のif文
if x == 0 {
    fmt.Println("xは0です")
} else if x == 1 {
    fmt.Println("xは1です")
} else {
    fmt.Println("xは2以上です")
}

// 代入if文
if a := f(); a < 1 {
    fmt.Println(a)
} else {
    fmt.Println(a*2)
}

条件分岐: switch

sample.go
switch a {
case 1, 2:
    fmt.Println("a is 1 or 2")
case a == 3:
    fmt.Println("a is 3")
default
    fmt.Println("default")
}

繰り返し: for

Golangの繰り返しはfor文のみ

sample.go
// 初期化; 継続条件; 更新
for i := 0; i <= 10; i++ {
    fmt.Println(i)
}

// 継続のみ
for i <= 100 {
}

// rangeを使った繰り返し
for i, v := range []int{1, 2, 4, 8} {
    fmt.Println(i, v)
}

ひたすら簡単なおみくじプログラム

sample.go
package main

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

func main() {
    rand.Seed(time.Now().Unix())

    results := map[int]string{
        0: "凶",
        1: "小吉",
        2: "小吉",
        3: "吉",
        4: "吉",
        5: "中吉",
        6: "大吉",
    }

    for i := 0; i <= 10; i++ {
        num := rand.Intn(8)
        if a := results[num]; a == "" {
            fmt.Printf("サイコロに%vの目はありません\n", num)
        }
        fmt.Printf("あなたの今年の運命は%vです\n", results[num])
    }
}

ポインタ

  • ポインタ = 0xc000016068 的なもの
  • 変数の格納先を表す値
    • 値で渡される型の値に対して破壊的な操作を加える際に利用する
      • 破壊的な操作 = 関数を出てもその影響が残る
sample.go
func f(xp *int) {           // intのポインタ型
    fmt.Println("xp", xp)   // xp 0xc000016068
    fmt.Println("*xp", *xp) // *xp 0
    fmt.Println("&xp", &xp) // &xp 0xc00000e028

    *xp = 100               // *でポインタの指す先に値を入れる
}

func main() {
    var x int      // ゼロ値 = 0
    f(&x)          // &でポインタを取得する
    fmt.Println(x) // x == 100
}
  • 以下の型では内部でポインタが用いられているためポインタを使用する必要がない
    • スライス
    • マップ
    • チャネル
sample.go
ns := []int{10, 20, 30}
ns2 := ns
ns[1] = 200
fmt.Println(ns[0], ns[1], ns[2])    // 10 200 30
fmt.Println(ns2[0], ns2[1], ns2[2]) // 10 200 30
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golang 入門

個人的なメモとして用いる。

GOの特徴

  • コンパイラ言語
  • 静的型付け言語
  • 文法がシンプル
  • 並行処理プログラミング
  • クロスコンパイルが可能
  • 標準パッケージが豊富

ソースコードの構成

main.go
// パッケージの定義
package main

// 標準パッケージや外部ライブラリのインポート
import "fmt"

// main関数の定義
func main {
  fmt.Println("Hello World")
}

プログラムはどこから実行されるのか

  • main関数
    • mainパッケージに存在する
    • ソースコードを追う時はここから
    • プログラムのエントリポイントで、初期化後はここから実行される
    • webサーバの場合は常に実行されているので代わりにhttpハンドラを読む

GOPATH

  • GOPATHとは?
    • Goのソースコードやビルドされたファイルが入るパスが設定されている
    • インポートされるパッケージもここから検索される
    • 環境変数として設定される
    • デフォルトが決まっている
      • Unix系:$HOME/go
      • Windows:%USERPROFILE%\go
    • 複数設定できる
    • go env GOPATH コマンドで取得可能

実際のGOPATH

  • $GOPATH
    • bin
      • fuga ビルドされた実行可能ファイルが入る
    • pkg
      • darwin_amd64 ビルドされたパッケージが入る
        • hoge.a
    • src
      • github.com
        • Kanai-Yuki
          • sample project
            • main.go 実行ファイル
          • sample2 project
            • main.go 実行ファイル

Goプログラムの構成要素

  • 関数
  • 変数
  • 定数
  • パッケージ

組み込み型(最初から使える)

種類 ゼロ値
整数 int, int8, int16, int32, int64
uint, uint8, uint16, uint32, uint64
uintptr, byte, rune
0
浮動小数点数 float32, float64 0
複素数 complex64, complex128 0
文字列 string ""
真偽値 bool false
エラー error nil

型のキャスト

下のsample.goはコンパイルエラーになる。
原因はif文で用いる変数と浮動小数点数の不一致。

sample.go
var sum int
sum = 5 + 6 + 3
avg := sum / 3

// float64(avg)とキャストすればエラーではなくなる
if avg < 4.5 {
    fmt.Println(avg)
}

キャストできない場合には、エラーとなる
例)int("hello world") など

コンポジット型

複数のデータ型が集まって1つの型となっている

型の種類 説明 ゼロ値
構造体 型の異なるデータ型を集めたデータ型 全フィールドがゼロ値
配列 同じ型のデータを集めて並べたデータ型 要素が全てゼロ値
スライス 配列の一部を切り出したデータ型 nil
マップ キーと値をマッピングさせたデータ型 nil

構造体

  • 型のデータ型の変数を集めたデータ構造
    • 各変数はフィールドと呼ばれる
    • フィールドの方は自由
    • フィールドの方にはコンポジット型やユーザ定義型も使用可能
sample.go
type Person struct {
    Name string
    age  int
}

bob := Person{"Bob", 30}
fmt.Println(bob.Name, bob.Age) // Bob 30

また以下のように初期化関数を作成することで初期化をすることも一般的に行われます。

sample.go
func newPerson(name string, age int) *Person {
     person := new(Person)
     person.Name = name
     person.Age = age
     return person
}

func main(){
  var jen *Person = newPerson("Jennifer", 40)
  fmt.Println(jen.Name, jen.Age) //=>Jennifer 40
}

配列

  • 同じ型のデータを集めて並べたデータ構造
    • 要素の型は全て同じ
    • 要素数が違えば違う型
    • 要素数は変更できない
    • 型は型リテラルで記述することが多い
sample.go
// 初期化
var n [5]int
var n [5]int{1, 2, 3, 4, 5}
n := [...]{1, 2, 3, 4} //lengthを値から推論
n := [...]{3: 20, 4: 11} //要素数: 5, n[3] = 20, n[4] = 11

// アクセス
n[1] // 添字は変数可
n[2:3] //スライス演算

スライス

  • 配列の一部を切り出したデータ構造
    • 要素の型は全て同じ
    • 要素数は型情報に含まない
    • 背後に配列が存在する
sample.go
// 初期化 // アクセスは配列と同様
var n []int // 変数にゼロ値を代入
n := make([]int, 3, 10) // 長さと容量を指定して初期化, 各要素䛿ゼロ値で初期化される
var n = []int{1, 2, 3, 4, 5} // スライスリテラルで初期化, 要素数は指定しない, 自動で配列が作られる

n = append(n, 10, 30) // 要素の追加 // 容量が足りない場合は背後の配列が再確保される
len(n) // 長さ
cap(n) // 容量

マップ

  • キーと値をマッピングさせるデータ構造
    • キーと値の型を指定できる
    • キーには「==」で比較できる型しかNG

型リテラル
var n map[string]int

sample.go
// 初期化
var n map[string]int
var m = make(map[string]int)
n := make(map[string]int, 10) // 容量の指定
m = map([string]int){"a": 2, "b": 4}

// 操作
n["a"] // アクセス
n["a"] = 20 // キーの値を上書き

n, ok := m["a"] // キー存在の確認, ok << true, false
if _, ok := m["no"]; !ok {
    fmt.Println("そのキーはありません")
}

delete(m, "z") // 要素の削除
sample.go
// マップのバリューがスライス
var a map[string][]int
a = map[string][]int{
    "a": []int{1, 2, 3},
    "b": []int{4, 5, 6},
    "c": []int{7, 8, 9},
}
fmt.Println(a["a"][0])

ユーザ定義型

  • typeで名前をつけて新しい型を定義する type 型名 基底型
sample.go
// 組み込み型を基にする
type MyInt int

// 他のパッケージを基にする
type MyWriter io.Writer

// 型リテラルを基にする
type Person struct {
    Name string
}

型リテラル

  • 型リテラルとは
    • 型の具体的な定義を書き崩した型の表現方法
    • コンポジット型などを表現するために使う
    • 変数定義やユーザ定義型などで使用する
sample.go
// intのスライスを用いた変数定義
var n []int

// mapの型リテラルを用いた変数定義
var m map[string]int

リテラル = 識別子(名前)が付与されていないもの

関数

  • 一連の処理をまとめたもの
    • 引数で受け取った値を基に処理を行い戻り値として結果を返す機能
    • 引数:関数䛾入力となるものf(x)の場合x
    • 戻り値(返り値): 関数の出力となるもの
  • 戻り値がある場合䛿変数に代入したり式中で使う
    • x := f(12, 1+1, y)

組み込み関数

関数名 機能
print, println 表示を行う
make コンポジット型の初期化
new 指定した型のメモリ
len/cap スライスの長さ/容量を返す
copy スライスのコピー
delete マップから指定したキーのエントリ
complex 複素数型を作成
imag/real 複素数の虚部/実数部を取得
panic/revocer パニックを起こす/回復する

関数の定義

sample.go
// 関数の定義方法
func add(x, y int) int {
}

// 複数の戻り値を返す
func swap(x int, y int) (int, int) {
    return x, y
}

// 名前付き戻り値
func swap(x y int) (x2, y2 int) {
    x2, y2 := x, y
    return
}

func main() {
    x, y := swap(1, 2) // 返り値はカンマで区切って取得する
}

クロージャ

  • 無名関数とも呼ばれる
  • 定義と実行のタイミングを気をつける
    • 関数外の変数を参照している場合
    • 実行のタイミングでは値が変わっている可能性がある
sample.go
// 「引数に文字列を取り、戻り値に文字列を返す関数」を返す関数
func later() func(string) string {
    var store string

    // 前の関数の呼び出し時に格納されている変数の値を返す
    return func(next string) string {
        s := store
        store = next
        return s
    }
}

func main() {
    f := later()
    fmt.Println("a: " + f("Golang"))
    fmt.Println("b: " + f("is"))
    fmt.Println("c: " + f("awesome!"))
    fmt.Println("d: " + f(""))
}

メソッドとレシーバ

  • Goではクラスの概念がないが、ユーザ定義型に対してメソッドを定義することができる
    • typeで型を定義し、レシーバ・ユーザ定義型を指定することでメソッドを定義することができる
  • レシーバにできる型
    • typeで定義した型
      • ユーザ定義型をレシーバにできる
    • ポインタ型
      • レシーバに変更を与えたい的
    • 内部にポインタを持つ型
      • マップやスライスなどもレシーバにできる
sample.go
func (<レシーバ引数>) <関数名>([引数]) [戻り値の型] {
    [関数の本体]
}
sample.go
func (p Person) intro(greetings string) string{
    return greetings + " I am " + p.Name
}

type T int

func (t *T) M() {
    fmt.Println("method")
}

func main(){
    bob := Person{"Bob", 30}
    fmt.Println(bob.intro("Hello")) // Hello I am Bob

    var t T
    p := &t
    p.M() // method
}

変数

  • メモリに名前をつけて値を格納する
  • 変数は型を持つ
  • 変数に違う型の値を代入することはできない
  • 暗黙の型演算がない
  • 型推論がある

変数定義

sample.go
// 変数定義と代入を同時に
var n int = 100
// 変数代入時に型推論
var n = 100
// 変数に後から代入
var n int
n = 100
// varを省略, 型推論
n := 100
// まとめて定義
var (
    n = 100
    b = 1000
)

定数

  • 値の変わらないもの
  • リテラルで記述される場合が多い
  • 型推論ではデフォルトの型を使用
  • 桁区切りには_を用いる
    • 例)100_000_000

定数定義

sample.go
const n int = 100
const n = 100
const n = "hello" + "world" // 定数式, コンパイル時に計算される

const (
    n = 100
    m = 200
)

制御構文

条件分岐: if

sample.go
// 通常のif文
if x == 0 {
    fmt.Println("xは0です")
} else if x == 1 {
    fmt.Println("xは1です")
} else {
    fmt.Println("xは2以上です")
}

// 代入if文
if a := f(); a < 1 {
    fmt.Println(a)
} else {
    fmt.Println(a*2)
}

条件分岐: switch

sample.go
switch a {
case 1, 2:
    fmt.Println("a is 1 or 2")
case a == 3:
    fmt.Println("a is 3")
default
    fmt.Println("default")
}

繰り返し: for

Golangの繰り返しはfor文のみ

sample.go
// 初期化; 継続条件; 更新
for i := 0; i <= 10; i++ {
    fmt.Println(i)
}

// 継続のみ
for i <= 100 {
}

// rangeを使った繰り返し
for i, v := range []int{1, 2, 4, 8} {
    fmt.Println(i, v)
}

ひたすら簡単なおみくじプログラム

sample.go
package main

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

func main() {
    rand.Seed(time.Now().Unix())

    results := map[int]string{
        0: "凶",
        1: "小吉",
        2: "小吉",
        3: "吉",
        4: "吉",
        5: "中吉",
        6: "大吉",
    }

    for i := 0; i <= 10; i++ {
        num := rand.Intn(8)
        if a := results[num]; a == "" {
            fmt.Printf("サイコロに%vの目はありません\n", num)
        }
        fmt.Printf("あなたの今年の運命は%vです\n", results[num])
    }
}

ポインタ

  • ポインタ = 0xc000016068 的なもの
  • 変数の格納先を表す値
    • 値で渡される型の値に対して破壊的な操作を加える際に利用する
      • 破壊的な操作 = 関数を出てもその影響が残る
sample.go
func f(xp *int) {           // intのポインタ型
    fmt.Println("xp", xp)   // xp 0xc000016068
    fmt.Println("*xp", *xp) // *xp 0
    fmt.Println("&xp", &xp) // &xp 0xc00000e028

    *xp = 100               // *でポインタの指す先に値を入れる
}

func main() {
    var x int      // ゼロ値 = 0
    f(&x)          // &でポインタを取得する
    fmt.Println(x) // x == 100
}
  • 以下の型では内部でポインタが用いられているためポインタを使用する必要がない
    • スライス
    • マップ
    • チャネル
sample.go
ns := []int{10, 20, 30}
ns2 := ns
ns[1] = 200
fmt.Println(ns[0], ns[1], ns[2])    // 10 200 30
fmt.Println(ns2[0], ns2[1], ns2[2]) // 10 200 30

パッケージ

  • 関数や定数・変数・型を意味のある単位でまとめたもの
  • Goプログラムはパッケージの組み合わせでできあがる
    • mainパッケージから別のパッケージをimportする
      • importすることで様々な機能が使えるようになる

標準パッケージ:https://golang.org/pkg/

package名 用途
fmt 書式に関する処理など
net/http webサーバ
archive, comress zipなどの圧縮形式
encording JSON, XML, CSV, TSVなどのファイル形式
html/template web用のHTMLを生成する機能
os, path/filepath osへのアクセスや操作、ファイル操作など

パッケージ外へのエクスポート

  • 先頭を大文字にした識別子がエクスポートされる
    • var Abc string // exported
    • var abc string // unexported

go get でライブラリの取得をする

  • goのライブラリを取得するコマンド
  • 依存するライブラリも一緒に取得してくれる
  • 指定した場所からダウンロード&インストールしてくれる
$ go get github.com/tenntenn/greeting
$ ls $GOPATH/src/github.com/tenntenn/greeting
README.md greeting.go

go modコマンド

  • go modコマンドを有効にする
    • 環境変数のGO111MODULEonにする
  • go modコマンドのサブコマンド
コマンド 処理
go mod init
go mod init モジュール名
指定したモジュール名でgo.modファイルを作成する
モジュール名を省略するとGOPATHから推測する
go mod tidy 使用していないパッケージのgo.modからの削除
必要なパッケージのダウンロードとgo.modへの追加
go mod why 指定したパッケージがなぜ必要になったか表示
go mod vendor 依存するパッケージをvendor以下にコピーする
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gin+Realize+docker-compose+Nginxで環境を整えてみる

Gin + Nginxで仕事でも使えるかな、と思いつつ開発環境を作成する

ソースはこちら

環境

Go 1.14
Gin 1.5.0
Docker 19.03.8
docker-compose 1.25.4

やること

  1. GinでREST APIを作成
  2. realizeでホットリロードできるようする
  3. docker-composeでNginxを立ててリバプロする

GinでREST APIを作成

GETでリクエストがきたら、 {"key":"value"}を返すだけのコードを作成

main.go
package main

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

func main() {
    router := gin.Default()

    router.GET("/api", func(context *gin.Context) {
        context.JSON(200,  gin.H{
            "key": "value",
        })
    })

    router.Run()
}

realizeでホットリロードできるようする

変更後にgo run main.goをするのが面倒なので、ホットリロードをできるようにする
realizeをInstallする

go get github.com/oxequa/realize

main.goがあるディレクトリで

realize start

をすると、そのディレクトリに.realize.yamlが作成される

.realize.yaml
settings:
  legacy:
    force: false
    interval: 0s
schema:
- name: src
  path: .
  commands: {}
  watcher:
    extensions:
    - go
    paths:
    - /
    ignored_paths:
    - .git
    - .realize
    - vendor

このままでは、watch状態にならないようなので、以下に変更する

.realize.yaml
settings:
  legacy:
    force: false
    interval: 0s
schema:
- name: src
  path: .
  commands: 
    run:
      status: true
  watcher:
    extensions:
    - go
    paths:
    - /
    ignored_paths:
    - .git
    - .realize
    - vendor

そして再度実行すると、起動ログが出力される

realize start
[21:15:11][SRC] : Watching 1 file/s 1 folder/s
[21:15:11][SRC] : Install started
[21:15:12][SRC] : Install completed in 0.946 s
[21:15:12][SRC] : Running..
[21:15:12][SRC] : [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[21:15:12][SRC] : [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
[21:15:12][SRC] :  - using env: export GIN_MODE=release
[21:15:12][SRC] :  - using code:        gin.SetMode(gin.ReleaseMode)
[21:15:12][SRC] : [GIN-debug] GET    /api                      --> main.main.func1 (3 handlers)
[21:15:12][SRC] : [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[21:15:12][SRC] : [GIN-debug] Listening and serving HTTP on localhost:8080

この状態で、コードの変更をすると変更を検知してリロードが走る

Dockerリバプロの設定

localhost:80でリクエストがあると、Nginxが3000番ポートへ流す構成にする

Gin環境のDockerfileとコードを少し改変

FROM golang:latest

RUN go get -u github.com/gin-gonic/gin && \
    go get -u github.com/oxequa/realize

WORKDIR /go/src/github.com/gin-nginx-sample/src

CMD [ "realize", "start"]

3000番ポートで起動するように変更
(一応ログファイルも出るようにして、リクエストIDを表示するだけのmiddlewareも書いてみた)

main.go
package main

import (
    "github.com/gin-gonic/gin"
    "github.com/labstack/gommon/log"
    "io"
    "os"
)

func main() {

    file, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(file)
    log.SetOutput(gin.DefaultWriter)

    router := gin.Default()

    router.GET("/api", myMiddleware(), func(context *gin.Context) {
        context.JSON(200, gin.H{
            "key": "value",
        })
    })

    router.Run(":3000")
}

func myMiddleware() gin.HandlerFunc {
    return func(context *gin.Context) {
        header := context.GetHeader("X-REQUEST-ID")
        log.Print("RequestId=" + header)
        context.Next()
    }

Nginx環境のDockerfile

自分で書いたnginx.confをnginx.confに上書きする

FROM nginx:latest

COPY ./nginx-docker/nginx.conf /etc/nginx/nginx.conf
nginx.conf
worker_processes auto;

events {
    worker_connections  1024;
}

http {
    server {
         listen       80;
         location / {
             proxy_http_version 1.1;
             proxy_set_header Upgrade $http_upgrade;
             proxy_set_header Connection "upgrade";
             proxy_set_header Host $host;

             proxy_pass http://golang:3000;
         }
    }
}

docker-composeをかく

2つのDockerfileを読み込んでdocker-composeを設定する

docker-compose.yml
version: '3'
services:
  golang:
    container_name: "gin-nginx-sample"
    tty: true
    build:
      context: .
      dockerfile: ./go-docker/Dockerfile
    volumes:
      - ./src:/go/src/github.com/gin-nginx-sample/src
    ports:
      - "3000"

  nginx:
    container_name: "nginx"
    build:
      context: .
      dockerfile: ./nginx-docker/Dockerfile
    ports:
      - "80:80"
    depends_on:
      - "golang"

確認

  1. locahost:80/apiでアクセスしたら、{"key":"value"}が返ってる
  2. main.goを変更して、ホットリロードが動く これらができたら、成功!

まとめ

普段意識して触ることのない技術スタックだったため、これだけの半日かかりました
もっと良い方法があったらぜひコメントください

参考

https://qiita.com/yoship1639/items/92405ab31779c8527c08

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

Goの門をくぐってみる(GoとGinのQuickStart)

なんとなく触ってみたいなと前々から思ってたので、チャレンジ。普段はJavaを書いています。なので、型が欲しいという思いでGoに触れてみます。僕はGoについての知識0なので、どこまで簡単に作れるのかもみていきたいです。

いろんな規則や、わからないことがあれば、以下の2つのサイトを探りながら書いていきたいと思います。

事前準備

homebrew

このサイトからinstallできます

go

コマンド打つだけ、簡単

brew install go

versionは1.14

ri2kku~:$: 
->> go version
go version go1.14 darwin/amd64

何がともあれGoのQuickStart

Go公式のQuickStartを参考に世界に挨拶してみます
https://golang.org/

ファイル作成 -> コード書く -> 動かす、まで一気に

ri2kku~/sandbox/go/go-sample:$: 
->> vi hello_world.go
hello_world.go
package main

import "fmt"

func main() {
  fmt.Println("Hello, World")
}
ri2kku~/sandbox/go/go-sample:$: 
->> go run hello_world.go 
Hello, World

雑感

10分ほどで世界に挨拶できました!こんなに簡単なら早く触っておけばよかったです。シンタックス等はJavaと結構違っているのかな?セミコロンがないことにすごく違和感を感じていました。

Gin

次はFWを使って簡単なアプリを作成してみます
一番使われてそうなFWを選びました
https://github.com/gin-gonic/gin

インストール

Goのversionは1.1以降が必要らしいです

go get -u github.com/gin-gonic/gin

GinのQuickStart

書いて動かすところまで

exmaple.go
package main

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

func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "pong",})
  })
  r.Run()
}

localhost:8080でlistenしてる

ri2kku~/sandbox/go/gin-sample:$: 
->> go run example.go
GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on localhost:8080

http://localhost:8080/pingにアクセスすると、pongが返ってくるようになっているのを確認できました

ri2kku~/sandbox/go/gin-sample:$: 
->> curl http://localhost:8080/ping
{"message":"pong"}

雑感

すごく簡単に作れる模様?インストールがすごく楽ですぐに何かを試したい!という時にはさらっとかけそうな印象を持ちました、この時点では。まだ海面に触れたようなものなので、次はもう少し深く潜って簡単なWebアプリを作成したいと思います。

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

Golangで、デザインパターン「Flyweight」を学ぶ

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Flyweight」を学ぶ"

今回は、Pythonで実装した”Flyweight”のサンプルアプリをGolangで実装し直してみました。

■ Flyweight(フライウェイト・パターン)

Flyweightパターン(フライウェイト・パターン)とは、GoFによって定義されたデザインパターンの1つである。 等価なインスタンスを別々の箇所で使用する際に、一つのインスタンスを再利用することによってプログラムを省リソース化することを目的とする。

UML class and sequence diagram

W3sDesign_Flyweight_Design_Pattern_UML.jpg

UML class diagram

flyweight.png
(以上、ウィキペディア(Wikipedia)より引用)

□ 備忘録

flyweightというのは、「フライ級」のことで、ボクシングで最も体重が軽い階級を示すものであり、このデザインパターンでは、オブジェクトを「軽く」するためのものだそうです。
Flyweightパターンで使っている技法は、「インスタンスをできるだけ共有させて、無駄にnewしない」というもので、インスタンスが必要なときに、いつもnewするのではなく、すでに作ってあるインスタンスを利用できるなら、それを共有して使うものだそうです。
直感的に、デザインパターン「Singleton」の応用だと感じますね。

■ "Flyweight"のサンプルプログラム

(1) 事前準備

アクキーアートっぽく、数字を表示するテキストファイルを準備しておきます。

big0.txt
....######......
..##......##....
..##......##....
..##......##....
..##......##....
..##......##....
....######......
................
big1.txt
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
big2.txt
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................

(以下、略)

(2) 動作確認

実際に、Flyweightパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。

  • 引数の数字の順番に、指定された数字をアスキーアートで表示する
$ go run Main.go 012123
....######......
..##......##....
..##......##....
..##......##....
..##......##....
..##......##....
....######......
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
....######......
..##......##....
..........##....
......####......
..........##....
..##......##....
....######......
................

■ サンプルプログラムの詳細

Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Flyweight

  • ディレクトリ構成
.
├── Main.go
├── big0.txt
├── big1.txt
├── big2.txt
├── big3.txt
├── big4.txt
├── big5.txt
├── big6.txt
├── big7.txt
├── big8.txt
├── big9.txt
└── flyweight
    └── big_char_factory.go

(1) Flyweight(フライ級)の役

普通に扱うとプログラムが重くなるので共有した方が良いものを表す役です。
サンプルプログラムでは、bigChar構造体が、この役を努めます。

flyweight/big_char_factory.go
package flyweight

import (
    "fmt"
    "os"
)

... (snip)

type bigChar struct {
    fontdata string
}

func newBigChar(charname string) *bigChar {
    char := &bigChar{}
    data := make([]byte, 256)
    f, err := os.Open(fmt.Sprintf("big%s.txt", charname))
    if err == nil {
        f.Read(data)
        char.fontdata = string(data)
    } else {
        char.fontdata = charname + "?"
    }
    return char
}

func (b *bigChar) print() {
    fmt.Println(b.fontdata)
}

(2) FlyweightFactory(フライ級の工場)の役

Flyweightを作る工場の役です。この工場を使ってFlyweight役を作ると、インスタンスが共有されます。
サンプルプログラムでは、bigCharFactory構造体が、この役を努めます。

flyweight/big_char_factory.go
type bigCharFactory struct {
    pool map[string]*bigChar
}

func newBigCharFactory() *bigCharFactory {
    bigChrFct := &bigCharFactory{
        pool: make(map[string]*bigChar),
    }
    return bigChrFct
}

var instance *bigCharFactory

func getInstance() *bigCharFactory {
    if instance == nil {
        instance = newBigCharFactory()
    }
    return instance
}

func (b *bigCharFactory) getBigChar(charname string) *bigChar {
    var bc *bigChar
    if _, ok := b.pool[charname]; !ok {
        bc = newBigChar(charname)
        b.pool[charname] = bc
    } else {
        bc = b.pool[charname]
    }
    return bc
}

(3) Client(依頼人)の役

FlyweightFactory役を使ってFlyweightを作り出し、それを利用する役です。
サンプルプログラムでは、BigString構造体とstartMain関数が、この役を努めます。

flyweight/big_char_factory.go
// BigString is struct
type BigString struct {
    bigchars []*bigChar
}

// NewBigString func for initalizing BigString
func NewBigString(str string) *BigString {
    bigStr := &BigString{}
    factory := getInstance()
    for _, s := range str {
        bigStr.bigchars = append(bigStr.bigchars, factory.getBigChar(string(s)))
    }
    return bigStr
}

// Print func for print something
func (b *BigString) Print() {
    for _, bc := range b.bigchars {
        bc.print()
    }
}
Main.go
package main

import (
    "flag"

    "./flyweight"
)

func startMain(str string) {
    bs := flyweight.NewBigString(str)
    bs.Print()
}

func main() {
    flag.Parse()
    startMain(flag.Arg(0))
}

■ 参考URL

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

Golangで、デザインパターン「Facade」を学ぶ

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Facade」を学ぶ"

今回は、Pythonで実装した”Facade”のサンプルアプリをGolangで実装し直してみました。

■ Facade(ファサード・パターン)

Facadeパターンあるいは Façadeパターン(ファサード・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義された、コンピュータソフトウェアのデザインパターンの1つである。Facade(ファサード)とは「建物の正面」を意味する。異なるサブシステムを単純な操作だけを持ったFacadeクラスで結び、サブシステム間の独立性を高める事を目的とする。

UML class and sequence diagram

W3sDesign_Facade_Design_Pattern_UML.jpg

UML class diagram

Example_of_Facade_design_pattern_in_UML.png
(以上、ウィキペディア(Wikipedia)より引用)

■ "Facade"のサンプルプログラム

実際に、Facadeパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。

$ go run Main.go 
welcome1.html is created for hyuki@hyuki.com (Hiroshi Yuki)
welcome2.html is created for mamoru@hyuki.com (Mamoru Takahashi)

サンプルプログラムを起動すると、2つのhtmlファイルが生成されます。
おのおのを、Webブラウザを確認してみるとこんな感じになりました。

  • welcome1.html
welcome1.png
  • welcome2.html
welcome2.png

■ サンプルプログラムの詳細

Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Facade

  • ディレクトリ構成
.
├── Main.go
├── facade
│   ├── database.go
│   ├── html_writer.go
│   └── page_maker.go
├── maildata.json
├── welcome1.html
└── welcome2.html

(1) Facade(正面)の役

Facade役は、システムを構成しているその他大勢の役の「シンプルな窓口」となります。Facade役は、高レベルでシンプルなインタフェースをシステム外部に提供します。
サンプルプログラムでは、PageMaker構造体が、この役を努めます。

facade/page_maker.go
package facade

import (
    "fmt"
    "os"
)

// PageMaker is struct
type PageMaker struct {
}

// Pagemaker is variable
var Pagemaker = &PageMaker{}

// MakeWelcomePage func for making welcome page
func (p *PageMaker) MakeWelcomePage(mailaddr, filename string) {
    username := ""
    database := &Database{}
    prop := database.getProperties("maildata")
    for _, person := range prop {
        if person.Mail == mailaddr {
            username = person.Name
        }
    }
    file, _ := os.Create(filename)
    writer := &HTMLWriter{file}
    writer.title(fmt.Sprintf("Welcom to %s's page!", username))
    writer.paragraph("We'll wait for your sending")
    writer.mailto(mailaddr, username)
    writer.close()
    fmt.Printf("%s is created for %s (%s)\n", filename, mailaddr, username)
}

(2) システムを構成しているその他大勢の役

その他大勢の役は、それぞれの仕事を行いますが、Facade役のことは意識しません。Facade役から呼び出されて仕事を行いますが、その他大勢の役の方からFacade役を呼び出すことはありません。
サンプルプログラムでは、Database構造体と、HTMLWriter構造体が、この役を努めます。

facade/database.go
package facade

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "os"
)

// Database is struct
type Database struct {
}

// Person is struct
type Person struct {
    Name string `json:"name"`
    Mail string `json:"mail"`
}

var maildata []Person

func (d *Database) getProperties(dbname string) []Person {
    filename := dbname + ".json"
    jsonString, err := ioutil.ReadFile(filename)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    if err = json.Unmarshal(jsonString, &maildata); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    return maildata
}
maildata.json
[
  {
    "mail": "hyuki@hyuki.com", 
    "name": "Hiroshi Yuki"
  }, 
  {
    "mail": "hanako@hyuki.com", 
    "name": "Hananko Sato"
  }, 
  {
    "mail": "tomura@hyuki.com", 
    "name": "Tomura"
  }, 
  {
    "mail": "mamoru@hyuki.com", 
    "name": "Mamoru Takahashi"
  }
]
facade/html_writer.go
package facade

import (
    "fmt"
    "os"
)

// HTMLWriter is struct
type HTMLWriter struct {
    writer *os.File
}

func (h *HTMLWriter) title(title string) {
    h.writer.Write([]byte("<html>\n"))
    h.writer.Write([]byte("<head>"))
    h.writer.Write([]byte(fmt.Sprintf("<title>%s</title>", title)))
    h.writer.Write([]byte("</head>\n"))
    h.writer.Write([]byte("<body>\n"))
    h.writer.Write([]byte(fmt.Sprintf("<h1>%s</h1>\n", title)))
}

func (h *HTMLWriter) paragraph(msg string) {
    h.writer.Write([]byte(fmt.Sprintf("<p>%s</p>\n", msg)))
}

func (h *HTMLWriter) link(href, caption string) {
    h.writer.Write([]byte(fmt.Sprintf("<a href=\"%s\">%s</a>", href, caption)))
}

func (h *HTMLWriter) mailto(mailaddr, username string) {
    h.link(fmt.Sprintf("mailto:%s", mailaddr), username)
}

func (h *HTMLWriter) close() {
    h.writer.Write([]byte("</body>\n"))
    h.writer.Write([]byte("</html>\n"))
    h.writer.Close()
}

(3) Client(依頼人)の役

Facadeパターンを利用する役です。
サンプルプログラムでは、startMain関数が、この役を努めます。

Main.go
package main

import (
    "./facade"
)

func startMain() {
    facade.Pagemaker.MakeWelcomePage("hyuki@hyuki.com", "welcome1.html")
    facade.Pagemaker.MakeWelcomePage("mamoru@hyuki.com", "welcome2.html")
}

func main() {
    startMain()
}

■ 参考URL

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