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

【errorgroup】Golangの並行処理内でerrorハンドリングをする実装で詰まった

概要

Golangの並行処理をしつつerrorハンドリングをしたい場合は、errorgroupパッケージを使うことができます。
GoDocはこちら
https://godoc.org/golang.org/x/sync/errgroup

GoDocに書いてあることが全てです。
この記事で言いたきことは、とにかくドキュメントを読め、の1点です。

とりあえず実装してみる(期待していない値が返ってくる例)

import "golang.org/x/sync/errgroup"

var g errgroup.Group
for _, url := range urls {
    g.Go(func() error {
        // Fetch the URL.
        resp, err := http.Get(url)
        if err == nil {
            resp.Body.Close()
        }
        return err
    })
}

こんな感じで書くと、urlsの末尾の要素のみ参照してしまい、期待通りに動作しません。なぜなのか。
GoDocを読むと以下のようにサンプルコードが書いてあります。

期待通りに動作する例

import "golang.org/x/sync/errgroup"

var g errgroup.Group

for _, url := range urls {
    // Launch a goroutine to fetch the URL.
    url := url // https://golang.org/doc/faq#closures_and_goroutines   <- この代入の意味なんぞ :thinking_face: 
    g.Go(func() error {
        // Fetch the URL.
        resp, err := http.Get(url)
        if err == nil {
            resp.Body.Close()
        }
        return err
    })
}

コメントに記載したリンク( https://golang.org/doc/faq#closures_and_goroutines )に飛ぶと以下の文とサンプルコード発見

Even easier is just to create a new variable, using a declaration style that may seem odd but works fine in Go:

このように記述するとより簡単に書くことができます。

This behavior of the language, not defining a new variable for each iteration, may have been a mistake in retrospect. It may be addressed in a later version but, for compatibility, cannot change in Go version 1.

新しい値をイテレーションの中で定義するのは、振り返ると言語仕様として間違っていたが、Goの1系では直せない。

まとめ

errorgroupではこのイテレーションの方法を取るしかなさそう。

ということで、イテレーションの中でerrorgroupを利用する場合はご注意下さい。

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

【errorgroup】Golangの並行処理中にerrorハンドリングをする実装で詰まった

概要

Golangの並行処理をしつつerrorハンドリングをしたい場合は、errorgroupパッケージを使うことができます。
GoDocはこちら
https://godoc.org/golang.org/x/sync/errgroup

GoDocに書いてあることが全てです。
この記事で言いたきことは、とにかくドキュメントを読め、の1点です。

とりあえず実装してみる(期待していない値が返ってくる例)

import "golang.org/x/sync/errgroup"

var g errgroup.Group
for _, url := range urls {
    g.Go(func() error {
        // Fetch the URL.
        resp, err := http.Get(url)
        if err == nil {
            resp.Body.Close()
        }
        return err
    })
}

こんな感じで書くと、urlsの末尾の要素のみ参照してしまい、期待通りに動作しません。なぜなのか。
GoDocを読むと以下のようにサンプルコードが書いてあります。

期待通りに動作する例

import "golang.org/x/sync/errgroup"

var g errgroup.Group
for _, url := range urls {
    // Launch a goroutine to fetch the URL.
    url := url // https://golang.org/doc/faq#closures_and_goroutines   <- この代入の意味はなんだろう :thinking_face: 
    g.Go(func() error {
        // Fetch the URL.
        resp, err := http.Get(url)
        if err == nil {
            resp.Body.Close()
        }
        return err
    })
}

コメントに記載したリンク( https://golang.org/doc/faq#closures_and_goroutines )に飛ぶと以下の文とサンプルコード発見

Even easier is just to create a new variable, using a declaration style that may seem odd but works fine in Go:

このように記述するとより簡単に書くことができます。

This behavior of the language, not defining a new variable for each iteration, may have been a mistake in retrospect. It may be addressed in a later version but, for compatibility, cannot change in Go version 1.

新しい値をイテレーションの中で定義するのは、振り返ると言語仕様として間違っていたが、Goの1系では直せない。

まとめ

errorgroupではこのイテレーションの方法を取るしかなさそう。

ということで、イテレーションの中でerrorgroupを利用する場合はご注意下さい。

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

GoとGinをプロジェクトで使ってみて思ったこと

はじめに

k.s.ロジャースの西谷です。

今回はGo言語とGinを使ってプロジェクトの開発を行いました。
実務利用は初めてで、このときに感じた便利な機能や躓いた箇所について書いてみようと思います。

もし、間違いやより良い実装があればコメントにて教えて頂けたらと思います。

開発環境

  • 言語: Go 1.13.3
  • フレームワーク: Gin

便利だと思った所

リクエストのバリデーション

Ginではリクエストオブジェクトの定義にbindingを書くことでバリデーションを実施できます。
特殊パターン以外はリクエストに直接書くことができるので、直感的だと思います。

以下のように書くと、reqIDの値は必ず存在し、Ageは0以上となります。

request.go
type CreateUser struct {
    ID   string `json:"id" binding:"required"`
    Name string `json:"name"`
    Age  int    `json:"age" binding:"min=0"`
}
controller.go
func (h *HogeController) Create(c *gin.Context) {
    var req request.CreateUser

    // このタイミングでバリデーションが実行される
    err := c.ShouldBindJSON(&req)
    if err != nil {
        // リクエストが間違っている時の処理
        return
    }
}

バリデーションをカスタマイズしたい場合は以下のようにします。

// 日付がyyyy-mm-ddのフォーマットになっているか
func DatetimeValidator(field validator.FieldLevel) bool {
    value := field.Field().String()

    _, err := time.Parse("2006-01-02", value)
    if err != nil {
        return false
    }

    return true
}

func main() {
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        // is_dateのタグでDatetimeValidatorを登録
        v.RegisterValidation("is_date", DatetimeValidator)
    }
}

request.go
type CreateUser struct {
    ID   string    `json:"id" binding:"required"`
    Name string    `json:"name"`
    Age  int       `json:"age" binding:"min=0"`
    Date time.Time `json:"date" binding:"is_date"`
}

詳細はこちらを参照ください。

エラー処理周り

Goでは必ず処理直後にエラー処理を行います。
こちらはエラー処理が煩雑など賛否両論ではありますが、私の意見としては複雑なtry, catchが乱立するよりはいいかなと思いました。

err := HogeFunc()
if err != nil {
    log.Fatal(err)
}

コード品質を気にしているチームであればtry, catchが乱立することはないかなと思います。
しかし、プロジェクトによってはコードを一切管理しておらず、例外が理解できないほど複雑になることもあるので、Goを利用していれば最悪ケースを防げるかなと思います。

躓いたところ

APIリクエストでのnullの扱い

REST APIのリクエストで未入力と初期値(0や空文字など)を区別したい場合があります。
これを実装する場合は変数をポインタにしてnilを持たせる必要があります。

実際に起きたバグとしては、PATCH関連のAPIでリクエストオブジェクトを通常の型で定義したところ、未入力フィールドがnullで上書きされる問題でした。

例えば次の定義に対して、ユーザ名だけを変更しようとしてリクエストを行うとAgeが0で上書きされます。

request.go
type UpdateUser struct {
    ID   string `json:"id"`
    Name string `json:"name"`
    Age  int    `json:"age"`
}
PATCH_USER
{
  "id": "xxxxxxxxx",
  "name": "hogehoge"
}

これに対して、nullが投げられる可能性のあるフィールドをポインタで定義することで解決できます。

request.go
type UpdateUser struct {
    ID   string  `json:"id"`
    Name *string `json:"name"`
    Age  *int    `json:"age"`
}

これで未入力の場合はnilとなりますので、サーバ側で保存対象を区別できます。
しかし、リクエストでnullで上書きしたい場合は、nullをリクエストで投げても更新されなくなります。
(定義済みのデータを未定義に更新出来ることが特殊だと思うので、一部の言語が特殊だと考えています)

ジェネリクスがない

型は違うが同じような処理をしたい場合はたまにあります。
現状ですと、interface{}で頑張るしかないようです。 1

func AddOne(v interface{}) interface{} {
    switch v.(type) {
    case int:
        return v.(int) + 1
    case float64:
        return v.(float64) + 1.0
    default:
        return nil
    }
}

func main() {
    a := AddOne(1.0)
    fmt.Printf("%f", a.(float64))
}

おわりに

今回はGoを実際にプロダクトで使ってみて感じたことについて説明しました。
設計や工夫次第で解消できる箇所もあるかなと思うので、これからも色々試していきたいと思います。

間違い等あればご指摘頂けたらと思います。

Wantedlyでもブログ投稿してます

Techブログに加えて会社ブログなどもやっているので、気になった方はぜひ覗いてみてください。
https://www.wantedly.com/companies/ks-rogers


  1. Go2.0でGenericsの実装される可能性があります。 

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

geth(go-ethereum)のdifficultyを固定にする

記事の内容

ethereumをプライベートで使用する際にdifficultyを固定にする方法の紹介
※運用ベースでのテストはしてません

環境

OS:CentOS7.4
golang:1.13.3
geth:1.9.11-unstable

goやgit、g++などコンパイルに必要となるツールはインストールされている前提で進めます。

difficultyの固定と動作検証(簡易)

gethの修正&ビルド

まずはgitからコードを落として、早速対象のコードを修正します。
ディレクトリは任意の場所で作業をします。

$ cd /home
$ git clone https://github.com/ethereum/go-ethereum.git
$ vi consensus/ethash/consensus.go

454行目から

consensus/ethash/consensus.go
// calcDifficultyFrontier is the difficulty adjustment algorithm. It returns the
// difficulty that a new block should have when created at time given the parent
// block's time and difficulty. The calculation uses the Frontier rules.
func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int {
       diff := new(big.Int)
       adjust := new(big.Int).Div(parent.Difficulty, params.DifficultyBoundDivisor)
       bigTime := new(big.Int)
       bigParentTime := new(big.Int)

       bigTime.SetUint64(time)
       bigParentTime.SetUint64(parent.Time)

       if bigTime.Sub(bigTime, bigParentTime).Cmp(params.DurationLimit) < 0 {
               diff.Add(parent.Difficulty, adjust)
       } else {
               diff.Sub(parent.Difficulty, adjust)
       }
       if diff.Cmp(params.MinimumDifficulty) < 0 {
               diff.Set(params.MinimumDifficulty)
       }

       periodCount := new(big.Int).Add(parent.Number, big1)
       periodCount.Div(periodCount, expDiffPeriod)
       if periodCount.Cmp(big1) > 0 {
               // diff = diff + 2^(periodCount - 2)
               expDiff := periodCount.Sub(periodCount, big2)
               expDiff.Exp(big2, expDiff, nil)
               diff.Add(diff, expDiff)
               diff = math.BigMax(diff, params.MinimumDifficulty)
       }
       // return diff
       return big1
}

修正箇所はreturnで返却する値を計算結果からbig1(定数 = 1)に変更する。
コードを見る限り、この関数内を「return big1」だけにしても問題無いかなと思います。

ビルドします。

$ make geth
env GO111MODULE=on go run build/ci.go install ./cmd/geth
>>> /usr/lib/golang/bin/go install -ldflags -X main.gitCommit=976a0f5558e20ed7cb7ba2cd68d7429d1ef01db9 -X main.gitDate=20200205 -v ./cmd/geth
Done building.
Run "./build/bin/geth" to launch geth.

この様な結果になればビルド成功です。

gethの起動準備

ここからはほぼ、以下のサイトの内容になります。

https://book.ethereum-jp.net/first_use/connect_to_private_net.html

まずは、ジェネシスファイル(JSON)を作成します。

$ mkdir /home/eth_private_net 
$ cd /home/eth_private_net
$ vi myGenesis.json
myGenesis.json
{
  "config": {
    "chainId": 15
  },
  "nonce": "0x0000000000000042",
  "timestamp": "0x0",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "extraData": "",
  "gasLimit": "0x8000000",
  "difficulty": "0x0000",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "coinbase": "0x3333333333333333333333333333333333333333",
  "alloc": {}
}

ここではdifficultyを0にしています。

gethを動かしてみる

ジェネシスファイルの準備が出来たら、gethを動かしてみます。

$ cd /home/go-ethereum/build/bin
$ ./geth --datadir /home/eth_private_net init /home/eth_private_net/myGenesis.json
(略)問題なければ以下のログが出力されるはず
INFO [02-07|00:26:16.907] Successfully wrote genesis state         database=lightchaindata                       hash=9f02b9…c34631
$ ./geth --networkid "15" --nodiscover --datadir "/home/eth_private_net" console 2>> /home/eth_private_net/geth_err.log
Welcome to the Geth JavaScript console!

instance: Geth/v1.9.11-unstable-976a0f55-20200205/linux-amd64/go1.13.3
at block: 0 (Thu Jan 01 1970 00:00:00 GMT+0000 (UTC))
 datadir: /home/eth_private_net
 modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

マイニングを開始する

これからは「Geth Java Script console」の操作になります。

アカウントの作成、マイニング開始→マイニング終了までを操作してみます。

> eth.account
undefined
> eth.accounts
[]
> personal.newAccount("hogehoge01")
"0x6918ef5c82e837fed798af3d61f62fda54151729"
> miner.start()
null
> eth.blockNumber
15
> miner.stop()
null
>

マイニング開始後、最新のブロック番号を取得すると「15」となっており、ブロックが作成されていることを確認できました。

※私は「miner.start()」をやってもマイニングが最初出来なかったのですが、仮想マシンに割り当てているメモリが不足していることが原因でした。
1GBだとメモリ不足となりマイニングされず、4GBに増やすとマイニングされました。
調べてみるとマイニングには2GB以上必要というような情報を見つけました。

ブロック情報を確認する

ジェネシスブロックからブロックの情報をいくつか確認してみます。

> eth.getBlock(0)
{
  difficulty: 0,
  extraData: "0x",
  gasLimit: 134217728,
  gasUsed: 0,
  hash: "0x9f02b99a864192b04587a44b20a9776cabf49bc664829cc7886c297155c34631",
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0x3333333333333333333333333333333333333333",
  mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  nonce: "0x0000000000000042",
  number: 0,
  parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 505,
  stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  timestamp: 0,
  totalDifficulty: 0,
  transactions: [],
  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  uncles: []
}
> eth.getBlock(1)
{
  difficulty: 1,
  extraData: "0xd88301090b846765746888676f312e31332e33856c696e7578",
  gasLimit: 134086657,
  gasUsed: 0,
  hash: "0xcde3272999885b223cfe03555098ded3695b8f40f305bcd571340b938478feec",
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0x6918ef5c82e837fed798af3d61f62fda54151729",
  mixHash: "0x8ee76bebc7e8831a892bc5bfc624b5b4c94078b6a47c7f4870faf19ba3be2b0b",
  nonce: "0x25778c99f3377e77",
  number: 1,
  parentHash: "0x9f02b99a864192b04587a44b20a9776cabf49bc664829cc7886c297155c34631",
  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 534,
  stateRoot: "0x4b9572efef79e46c6779da6432a2fda287e9ed8557c9ecc6c417eba7b925c039",
  timestamp: 1581035813,
  totalDifficulty: 1,
  transactions: [],
  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  uncles: []
}
> eth.getBlock(2)
{
  difficulty: 1,
  extraData: "0xd88301090b846765746888676f312e31332e33856c696e7578",
  gasLimit: 133955714,
  gasUsed: 0,
  hash: "0x163e0ddd27e8c84465ea4012db0677737493cacee9871ff4db97c6ce3c3a8dff",
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0x6918ef5c82e837fed798af3d61f62fda54151729",
  mixHash: "0xdf576b908fff00f745e87bb756aa8951d4edc841b55233087e484ccf78b8f4f2",
  nonce: "0x1b7726eebe60e5b1",
  number: 2,
  parentHash: "0xcde3272999885b223cfe03555098ded3695b8f40f305bcd571340b938478feec",
  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 534,
  stateRoot: "0xe1d55911fc5cc66e79e899d76a12f85907d4d2d3096076764c1008231b08681a",
  timestamp: 1581035815,
  totalDifficulty: 2,
  transactions: [],
  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  uncles: []
}

ジェネシスブロックはdifficultyを「0」に指定していましたが、マイニングよって作られたブロックはdifficultyの値が「1」固定になっています。

参考

gethの古いバージョンだと修正するソースコードの場所が異なるようです。

geth の difficulty をあまり上げない(or 一定にする)方法

感想

思いの外、簡単に出来ました。
次回は処理待ちトランザクションがないとブロックを作らないなどの改造にチャレンジしようと思います。

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

gRPC の使い方 (Go)

参考ページ
Go Quick Start

設定ファイル、サーバープログラム、クライアントプログラムの3つが必要です。

.
├── greeter_client
│   └── main.go
├── greeter_server
│   └── main.go
└── helloworld
    └── helloworld.proto

設定ファイル

helloworld/helloworld.proto
こちらと同じ
gRPC の使い方 (python)

サーバープログラム

greeter_server/main.go
package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "../helloworld"
)

const (
    port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("SayHello Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Test Hello " + in.GetName()}, nil
}

func (s *server) SayHello2(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("SayHello2 Received: %v", in.GetName())
        return &pb.HelloReply{Message: "Test2 Hello again " + in.GetName()}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

クライアントプログラム

greeter_client/main.go
package main

import (
    "context"
    "log"
    "os"
    "time"

    "google.golang.org/grpc"
    pb "../helloworld"
)

const (
    address     = "localhost:50051"
    defaultName = "Tom"
)

func main() {
    // Set up a connection to the server.
    conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // Contact the server and print out its response.
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    name = "John"
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())


    name = "Tom"
    r, err = c.SayHello2(ctx, &pb.HelloRequest{Name: name})
    if err != nil {
            log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
}

gRPC のコードを作成します。

スクリプト

protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld

実行後

$ tree
.
├── greeter_client
│   └── main.go
├── greeter_server
│   └── main.go
└── helloworld
    ├── helloworld.pb.go
    └── helloworld.proto

サーバープログラムの起動

go run greeter_server/main.go

クライアントプログラムの実行

$ go run greeter_client/main.go 
2020/02/07 11:21:59 Greeting: Test Hello John
2020/02/07 11:21:59 Greeting: Test2 Hello again Tom

サーバーのコンソールには次のようなメッセージが出ます。

$ go run greeter_server/main.go 
2020/02/07 11:21:59 SayHello Received: John
2020/02/07 11:21:59 SayHello2 Received: Tom

Arch Linux でのコマンドのインストール方法

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

最近ちょっとGoを触った感想とか振り返りとか1

仕事で使いそうだったので勉強してみた感想とか。振り返りとか。

学習履歴

触ってみて

コンパイルが早い

Scala を書いてるときのいわゆる 待ち みたいなのを感じなくなったかも(コンパイルとかユニットテストとか)。Mac も大きな声で泣き叫んで発熱することもなくなった感。

組み込みのAPIが充実

用意されているものが優秀なので、簡易なHTTPサーバーを作るのにフレームワークなどを使う必要はなさげ(個人差ありそう)。

実行環境を選ばない

go build で生成したバイナリはランタイムに依存しないので配布しやすい。コンテナを作るときはマルチステージビルドでバイナリだけ入れとくとか巷で良く見かける。

FROM golang:1.13-buster as builder

RUN mkdir /app
WORKDIR /app

COPY src ./src
RUN go build -o /main ./src/cmd/*.go

FROM debian:stretch-slim

COPY --from=builder /main .

ENTRYPOINT ["./main"]

軽量のコンテナは、CA証明書が入ってなかったりするから Image に固めるアプリケーションが HTTP Client 実装してたりしたときは注意。

構文に関して

迷いのなきエラーハンドリング

変数に入れて nil かどうかを愚直に評価していく。とにかくこれな印象。

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

いわゆるガード節を書いていくので else とかは書かないほうが良い。

f, err := os.Open(name)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    f.Close()
    return err
}
codeUsing(f, d)

たまにはモナドが恋しいときもある。

命名でパッケージ外からの可視性が決まる

先頭文字を大文字にすることで他パッケージからの参照が可能となる。
小文字だと参照不可。最初これ知らなくてハマった。

t_horikoshi.go
package model

type T_horikoshi struct {
    Name string
}

type t_horikoshi struct {
    name string
}
main.go
package main

func Ref() {
    t := domain.T_horikoshi{Name:"t_horikoshi"}
    //t := domain.t_horikoshi{name:"unexported..."}
    fmt.Print(t)
}

リソースのクローズとかやってくれる Defer が便利

リソースのクローズ処理のため Defer という仕組みが用意されている。
関数内に宣言しておくと最後まで処理を行った後に自動的に実行してくれるのでので便利。

type ExchangeRate struct {
    Quotes []Quote
}

type Quote struct {
    High             string
    Open             string
    Bid              string
    CurrencyPairCode string
    Ask              string
    Low              string
}

func GetExchangeRatePerYen() (rate *ExchangeRate, err error) {
    resp, err := http.Get("https://www.gaitameonline.com/rateaj/getrate")
    if err != nil {
        return nil, err
    }

    defer func() {
        if err := resp.Body.Close(); err != nil {
            log.Fatal("close resources", err)
        }
    }()

    bytes, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    rate = new(ExchangeRate)
    if err := json.Unmarshal(bytes, rate); err != nil {
        return nil, err
    }
    return rate, nil
}

resp.Body.Close()error を返すので無名関数で実装しなければならない。

interface を実装する

Scala脳に染まってたので extends どうやるんやとか思ってたら、interface に定義したメソッドを実装したらその時点で継承が完了するみたい。

世間でダックタイピングと呼ばれているもの。

↑これわたし勉強不足だったのですが、Structural typing と呼ばれるもので構造の宣言や定義によって型の互換性や透過性を保証し、型チェックを行わない Duck typing とは全くもって対をなすというものでした。

https://golang.org/doc/faq#implements_interface

@c-yan さんコメントありがとうございます。

main.go
package main

import "fmt"


type Human interface {
    Call() string
}

func VoiceChecker(h Human) {
    fmt.Print(h.Call())
}

type Animal struct {}

func (a Animal) Call() string {
    return "bau!bau!"
}

type Insect struct {}

func (i Insect) Call() string {
    return "paru!paru!"
}

func main() {
    VoiceChecker(Animal{})
    VoiceChecker(Insect{})
}

つづく...

そのうちに書きます。

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