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

初見でGo言語Webアプリケーションをやってみる(Hello World編)

はじめに

Go初心者が、初見でWebアプリケーションを作るまでの過程をつづっていきます。

環境

macOS:Catalina
goバージョン:go1.12.9

Go言語のWebフレームワークは何か?

私は普段Kotlinを使っているので、WebフレームワークはSpringFrameworkを使用しています。

SpringFramework的な、Go言語におけるWebフレームワークのスタンダードは何なのでしょうか?

https://qiita.com/loftkun/items/1a9951d1864bebdc51e1
上記の記事を発見し、ginがよく使用されているようなので、そちらを使っていきたいと思います。

ginフレームワークをいれる

前提としてGoがインストールされている必要があります。
そのうえで、ginのREADMEの通りに、インストールをします。

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

JSONを返すサンプルコードを動かす

ginのREADMEのサンプルコードを実行してみます。

example.go
package main

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

func main() {
    //LoggerとRecoveryがセットになったEngineインスタンスを返してくれる
    r := gin.Default()
    //SpringにおけるGetMapping
    r.GET("/ping", func(c *gin.Context) {
        //JSON形式のbodyを返す
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    //http.Serverを起動してリクエストを受け付ける
    r.Run()
}

そして、サンプルコードを実行します。

$ go run example.go

実行コマンドを打つと、「ネットワーク接続への許可」をきいてくるので、「許可」します。
image.png

ブラウザで localhost:8080/pingにアクセスすると
{"message":"pong"}とかえってきました。

HTMLを返してみる

JSONだと見た目が楽しくないですね。
先ほどのサンプルを改良して、HTMLを返すサンプルを作ってみます。

html_example.go
package main

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

func main() {
    r := gin.Default()
    r.LoadHTMLGlob("templates/*.html")

    r.GET("/hello", func(c *gin.Context) {
        c.HTML(200,"hello.html", none)
    })

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

    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

templates/hello.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HelloGo</title>
</head>
<body>
<h1>Hello Go</h1>
<img src="https://golang.org/lib/godoc/images/home-gopher.png">
</body>
</html>

コードを書いたら、以下のようにターミナルで実行コマンドをうちます。

$ go run html_example.go

ブラウザで localhost:8080/helloにアクセスすると、
image.png

HTMLが表示されましたね。

次回

HTMLに変数を渡してみる。

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

Go Compilerのインライン化についてまとめた

はじめに

Go は末尾再帰が最適化されない。というかあえて最適化しない、という選択を採用しているようです。

Goで再帰使うと遅くなりますがそれが何だ

そもそも私はあまりコンパイラの気持ちになったことがなく、関数呼び出しのときにインライン展開されるということがどういうことかわかりませんでした。

本記事では以下の 2 点についてまとめてみます。

  • 関数のインライン展開とはどういうことか
  • インライン展開されることでどのような効果があるのか

関数のインライン展開とはどういうことか

インライン展開とは

インライン展開(インラインてんかい、英: inline expansion または 英: inlining)とは、コンパイラによる最適化手法の1つで、関数を呼び出す側に呼び出される関数のコードを展開し、関数への制御転送をしないようにする手法。これにより関数呼び出しに伴うオーバーヘッドを削減する。特に小さくて頻繁に呼ばれる関数では効果的であり、呼び出し側にそのコードを展開することで定数畳み込みなどのさらなる最適化を施せる可能性が生じる。問題点はバイナリコードが一般に肥大化する結果を招く点であり、参照の局所性を損なうほどだったり、リソースの限界を超えると性能がかえって悪化することになる。

インライン展開」より引用

端的に言って、関数呼び出しのコードを呼び出し元に展開することで、関数呼び出しのオーバヘッドを削減し高速化する、という最適化手法のようです。

Go で関数がインライン化されたときとそうでないときで、コンパイラ結果にどのような違いがあるのか確認してみます。

Go では go:noinline という pragma を用いることができ、この pragma を用いることで関数をインライン化しないようにコンパイルします。1 go:noinline はテストを目的とした pragma です。2

pragma を用いて、インライン化するコードとそうでないコードで、それぞれコンパイル後の実行ファイルのダンプを取得して比較してみます。

サンプル実装

  • インライン化する場合
hello.go
package main

func main() {
    Hello()
}

func Hello() interface{} {
    return struct{}{}
}
go build main.go
go tool objdump main.exe > dump.txt
  • インライン化しない場合
main.go
package main

func main() {
    HelloNoInline()
}

//go:noinline
func HelloNoInline() interface{} {
    return struct{}{}
}
go build main.go
go tool objdump main.exe > dump_noinline.txt

出力結果の比較

ダンプした結果の main 関数のテキストセグメントを抜粋します。

  • インライン化する場合
...

TEXT main.main(SB) C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/opt/hello/cmd/main.go
  main.go:4     0x44f5c0        90          NOPL            
  main.go:4     0x44f5c1        c3          RET         
  :-1           0x44f5c2        cc          INT $0x3        
  :-1           0x44f5c3        cc          INT $0x3        
  :-1           0x44f5c4        cc          INT $0x3        
  :-1           0x44f5c5        cc          INT $0x3        
  :-1           0x44f5c6        cc          INT $0x3        
  :-1           0x44f5c7        cc          INT $0x3        
  :-1           0x44f5c8        cc          INT $0x3        
  :-1           0x44f5c9        cc          INT $0x3        
  :-1           0x44f5ca        cc          INT $0x3        
  :-1           0x44f5cb        cc          INT $0x3        
  :-1           0x44f5cc        cc          INT $0x3        
  :-1           0x44f5cd        cc          INT $0x3        
  :-1           0x44f5ce        cc          INT $0x3        
  :-1           0x44f5cf        cc          INT $0x3        

...
  • インライン化しない場合
...

TEXT main.main(SB) C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/opt/hello/cmd/main.go
  main.go:3     0x44f5c0        65488b0c2528000000  MOVQ GS:0x28, CX            
  main.go:3     0x44f5c9        488b8900000000      MOVQ 0(CX), CX              
  main.go:3     0x44f5d0        483b6110        CMPQ 0x10(CX), SP           
  main.go:3     0x44f5d4        761d            JBE 0x44f5f3                
  main.go:3     0x44f5d6        4883ec18        SUBQ $0x18, SP              
  main.go:3     0x44f5da        48896c2410      MOVQ BP, 0x10(SP)           
  main.go:3     0x44f5df        488d6c2410      LEAQ 0x10(SP), BP           
  main.go:4     0x44f5e4        e817000000      CALL main.HelloNoInline(SB)     
  main.go:5     0x44f5e9        488b6c2410      MOVQ 0x10(SP), BP           
  main.go:5     0x44f5ee        4883c418        ADDQ $0x18, SP              
  main.go:5     0x44f5f2        c3          RET                 
  main.go:3     0x44f5f3        e81882ffff      CALL runtime.morestack_noctxt(SB)   
  main.go:3     0x44f5f8        ebc6            JMP main.main(SB)           
  :-1           0x44f5fa        cc          INT $0x3                
  :-1           0x44f5fb        cc          INT $0x3                
  :-1           0x44f5fc        cc          INT $0x3                
  :-1           0x44f5fd        cc          INT $0x3                
  :-1           0x44f5fe        cc          INT $0x3                
  :-1           0x44f5ff        cc          INT $0x3                

TEXT main.HelloNoInline(SB) C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/opt/hello/cmd/main.go
  main.go:9     0x44f600        488d05f9ec0000      LEAQ runtime.rodata+58112(SB), AX   
  main.go:9     0x44f607        4889442408      MOVQ AX, 0x8(SP)            
  main.go:9     0x44f60c        488d05cd730800      LEAQ runtime.zerobase(SB), AX       
  main.go:9     0x44f613        4889442410      MOVQ AX, 0x10(SP)           
  main.go:9     0x44f618        c3          RET                 
  :-1           0x44f619        cc          INT $0x3                
  :-1           0x44f61a        cc          INT $0x3                
  :-1           0x44f61b        cc          INT $0x3                
  :-1           0x44f61c        cc          INT $0x3                
  :-1           0x44f61d        cc          INT $0x3                
  :-1           0x44f61e        cc          INT $0x3                
  :-1           0x44f61f        cc          INT $0x3

...

インライン化した場合は、実行ファイルから Hello 関数の呼び出しが消えています。インライン化された結果です。一方でインライン化しない場合は、実行ファイルに HelloNoInline 関数が存在し、main 関数から HelloNoInline 関数を CALL していることが分かります。
サンプルの実装例は、何もしない関数を定義しているため極端ですが、インライン化される場合とそうでない場合でコンパイルされた結果がどのように異なるか確認できました。

インライン展開されることでどのような効果があるのか

実行時間にどの程度影響があるのか、ベンチマークをとってみます。サンプルの関数の実装は先ほどの例の実装と同様です。

hello.go
package hello

//go:noinline
func HelloNoInline() interface{} {
    return struct{}{}
}

func Hello() interface{} {
    return struct{}{}
}
hello_test.go
package hello

import "testing"

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Hello()
    }
}

func BenchmarkHelloNoInline(b *testing.B) {
    for i := 0; i < b.N; i++ {
        HelloNoInline()
    }
}
> go test -bench . -benchmem
goos: windows
goarch: amd64
pkg: github.com/d-tsuji/go-sandbox/opt/hello
BenchmarkHello-8                2000000000               0.34 ns/op            0 B/op          0 allocs/op
BenchmarkHelloNoInline-8        2000000000               1.51 ns/op            0 B/op          0 allocs/op
PASS
ok      github.com/d-tsuji/go-sandbox/opt/hello 4.295s

関数は空構造体を返しているだけで、メモリはアロケートしておらず、どちらも 0 B/op で想定通りです。一方で実行時間はインライン化している場合は、そうでない場合と比較して約 1/5 倍の実行時間になっている結果が得られました3

まとめ

インライン化する場合とそうでない場合で Go のコンパイラがどのようなコードを取得するか確認しました。またインライン化の高速化の効果を確認しました。

Go のコンパイラがどのような最適化を実施するかは Compiler And Runtime Optimizations のページが参考になります。
上記のよると、関数のインライン化の要件は結構厳しくて、以下を満たす必要があります。

  • 関数に含まれる式が 40 個未満
  • 関数呼出し・ループ・クロージャー・panicrecoverselectswitch といった複雑なものを含まない

Only short and simple functions are inlined. To be inlined a function must contain less than ~40 expressions and does not contain complex things like function calls, loops, labels, closures, panic's, recover's, select's, switch'es, etc


  1. lex.go 

  2. cmd/compile: add a go:noinline directive 

  3. 関数の中身が小さい(今回の場合は、ない)ために、より効果的な結果が得られていると考えられます。 

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

Go Compilerのインライン展開についてまとめた

はじめに

Go は末尾再帰が最適化されない。というかあえて最適化しない、という選択を採用しているようです。

Goで再帰使うと遅くなりますがそれが何だ

そもそも私はあまりコンパイラの気持ちになったことがなく、関数呼び出しのときにインライン展開されるということがどういうことかわかりませんでした。

本記事では以下の 2 点についてまとめてみます。

  • 関数のインライン展開とはどういうことか
  • インライン展開されることでどのような効果があるのか

関数のインライン展開とはどういうことか

インライン展開とは

インライン展開(インラインてんかい、英: inline expansion または 英: inlining)とは、コンパイラによる最適化手法の1つで、関数を呼び出す側に呼び出される関数のコードを展開し、関数への制御転送をしないようにする手法。これにより関数呼び出しに伴うオーバーヘッドを削減する。特に小さくて頻繁に呼ばれる関数では効果的であり、呼び出し側にそのコードを展開することで定数畳み込みなどのさらなる最適化を施せる可能性が生じる。問題点はバイナリコードが一般に肥大化する結果を招く点であり、参照の局所性を損なうほどだったり、リソースの限界を超えると性能がかえって悪化することになる。

インライン展開」より引用

端的に言って、関数呼び出しのコードを呼び出し元に展開することで、関数呼び出しのオーバヘッドを削減し高速化する、という最適化手法のようです。

Go で関数がインライン化されたときとそうでないときで、コンパイラ結果にどのような違いがあるのか確認してみます。

Go では go:noinline という pragma を用いることができ、この pragma を用いることで関数をインライン化しないようにコンパイルします。1 go:noinline はテストを目的とした pragma です。2

pragma を用いて、インライン化するコードとそうでないコードで、それぞれコンパイル後の実行ファイルを逆アセンブリして確認します。Go に標準で備わっている objdump を用いて実行ファイルを逆アセンブリすることができます。

サンプル実装

  • インライン化する場合
hello.go
package main

func main() {
    Hello()
}

func Hello() interface{} {
    return struct{}{}
}
go build main.go
go tool objdump main.exe > dump.txt
  • インライン化しない場合
main.go
package main

func main() {
    HelloNoInline()
}

//go:noinline
func HelloNoInline() interface{} {
    return struct{}{}
}
go build main.go
go tool objdump main.exe > dump_noinline.txt

出力結果の比較

ダンプした結果の main 関数のテキストセグメントを抜粋します。

  • インライン化する場合
...

TEXT main.main(SB) C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/opt/hello/cmd/main.go
  main.go:4     0x44f5c0        90          NOPL            
  main.go:4     0x44f5c1        c3          RET         
  :-1           0x44f5c2        cc          INT $0x3        
  :-1           0x44f5c3        cc          INT $0x3        
  :-1           0x44f5c4        cc          INT $0x3        
  :-1           0x44f5c5        cc          INT $0x3        
  :-1           0x44f5c6        cc          INT $0x3        
  :-1           0x44f5c7        cc          INT $0x3        
  :-1           0x44f5c8        cc          INT $0x3        
  :-1           0x44f5c9        cc          INT $0x3        
  :-1           0x44f5ca        cc          INT $0x3        
  :-1           0x44f5cb        cc          INT $0x3        
  :-1           0x44f5cc        cc          INT $0x3        
  :-1           0x44f5cd        cc          INT $0x3        
  :-1           0x44f5ce        cc          INT $0x3        
  :-1           0x44f5cf        cc          INT $0x3        

...
  • インライン化しない場合
...

TEXT main.main(SB) C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/opt/hello/cmd/main.go
  main.go:3     0x44f5c0        65488b0c2528000000  MOVQ GS:0x28, CX            
  main.go:3     0x44f5c9        488b8900000000      MOVQ 0(CX), CX              
  main.go:3     0x44f5d0        483b6110        CMPQ 0x10(CX), SP           
  main.go:3     0x44f5d4        761d            JBE 0x44f5f3                
  main.go:3     0x44f5d6        4883ec18        SUBQ $0x18, SP              
  main.go:3     0x44f5da        48896c2410      MOVQ BP, 0x10(SP)           
  main.go:3     0x44f5df        488d6c2410      LEAQ 0x10(SP), BP           
  main.go:4     0x44f5e4        e817000000      CALL main.HelloNoInline(SB)     
  main.go:5     0x44f5e9        488b6c2410      MOVQ 0x10(SP), BP           
  main.go:5     0x44f5ee        4883c418        ADDQ $0x18, SP              
  main.go:5     0x44f5f2        c3          RET                 
  main.go:3     0x44f5f3        e81882ffff      CALL runtime.morestack_noctxt(SB)   
  main.go:3     0x44f5f8        ebc6            JMP main.main(SB)           
  :-1           0x44f5fa        cc          INT $0x3                
  :-1           0x44f5fb        cc          INT $0x3                
  :-1           0x44f5fc        cc          INT $0x3                
  :-1           0x44f5fd        cc          INT $0x3                
  :-1           0x44f5fe        cc          INT $0x3                
  :-1           0x44f5ff        cc          INT $0x3                

TEXT main.HelloNoInline(SB) C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/opt/hello/cmd/main.go
  main.go:9     0x44f600        488d05f9ec0000      LEAQ runtime.rodata+58112(SB), AX   
  main.go:9     0x44f607        4889442408      MOVQ AX, 0x8(SP)            
  main.go:9     0x44f60c        488d05cd730800      LEAQ runtime.zerobase(SB), AX       
  main.go:9     0x44f613        4889442410      MOVQ AX, 0x10(SP)           
  main.go:9     0x44f618        c3          RET                 
  :-1           0x44f619        cc          INT $0x3                
  :-1           0x44f61a        cc          INT $0x3                
  :-1           0x44f61b        cc          INT $0x3                
  :-1           0x44f61c        cc          INT $0x3                
  :-1           0x44f61d        cc          INT $0x3                
  :-1           0x44f61e        cc          INT $0x3                
  :-1           0x44f61f        cc          INT $0x3

...

インライン化した場合は、実行ファイルから Hello 関数の呼び出しが消えています。インライン化された結果です。一方でインライン化しない場合は、実行ファイルに HelloNoInline 関数が存在し、main 関数から HelloNoInline 関数を CALL していることが分かります。
サンプルの実装例は、何もしない関数を定義しているため極端ですが、インライン化される場合とそうでない場合でコンパイルされた結果がどのように異なるか確認できました。

インライン展開されることでどのような効果があるのか

実行時間にどの程度影響があるのか、ベンチマークをとってみます。サンプルの関数の実装は先ほどの例の実装と同様です。

hello.go
package hello

//go:noinline
func HelloNoInline() interface{} {
    return struct{}{}
}

func Hello() interface{} {
    return struct{}{}
}
hello_test.go
package hello

import "testing"

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Hello()
    }
}

func BenchmarkHelloNoInline(b *testing.B) {
    for i := 0; i < b.N; i++ {
        HelloNoInline()
    }
}
> go test -bench . -benchmem
goos: windows
goarch: amd64
pkg: github.com/d-tsuji/go-sandbox/opt/hello
BenchmarkHello-8                2000000000               0.34 ns/op            0 B/op          0 allocs/op
BenchmarkHelloNoInline-8        2000000000               1.51 ns/op            0 B/op          0 allocs/op
PASS
ok      github.com/d-tsuji/go-sandbox/opt/hello 4.295s

関数は空構造体を返しているだけで、メモリはアロケートしておらず、どちらも 0 B/op で想定通りです。一方で実行時間はインライン化している場合は、そうでない場合と比較して約 1/5 倍の実行時間になっている結果が得られました3

まとめ

インライン化する場合とそうでない場合で Go のコンパイラがどのようなコードを取得するか確認しました。またインライン化の高速化の効果を確認しました。

Go のコンパイラがどのような最適化を実施するかは Compiler And Runtime Optimizations のページが参考になります。
上記のよると、関数のインライン化の要件は結構厳しくて、以下を満たす必要があります。

  • 関数に含まれる式が 40 個未満
  • 関数呼出し・ループ・クロージャー・panicrecoverselectswitch といった複雑なものを含まない

Only short and simple functions are inlined. To be inlined a function must contain less than ~40 expressions and does not contain complex things like function calls, loops, labels, closures, panic's, recover's, select's, switch'es, etc


  1. lex.go 

  2. cmd/compile: add a go:noinline directive 

  3. 関数の中身が小さい(今回の場合は、ない)ために、より効果的な結果が得られていると考えられます。 

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

Goで自分のqiita投稿記事一覧を見れるコマンドラインツールを作ってみた。

Goで自分のqiita投稿記事一覧を見れるコマンドラインツールを作ってみた。

Go言語の勉強をしたので、Cliアプリケーションを作成してみます。
何番煎じ目か知れませんが、QiitaのAPIを叩いて自分のqiitaの投稿記事一覧を見れるコマンドラインツールを作ってみます。
これが出来た暁には、qiitaを書く意欲がもりもりと湧いてくる筈である。

作ったもの

$ qi myqi

と打つと、
スクリーンショット 2019-11-12 17.21.51.png
と出力されるというコマンドラインツール。
githubはこちらです。:https://github.com/yujiteshima/qi

使用するフレームワーク

urfave/cliというCLIアプリケーションフレームワークを使用してみる。
urfaveの使い方は公式のDocumentが一番分かりやすかったです。https://github.com/urfave/cli/blob/master/docs/v1/manual.md

ディレクトリきったり、go mod init したりする。

Go 1.11から採用されているmodulesを使用する。

gopathの外でも開発が可能であるので、home直下にworkspace_golangというディレクトリを作って、

その中に今回のプロジェクトのディレクリを作成しました。

golangはbuildした時の実行ファイル名がデフォルトではディレクトリ名になるので、

コマンドの名前を付けると思って考えます。

「qiitaの記事を取得するコマンドだから········qiだ。」
というような感じで考えました。悩みますが、勉強が全然進まなくなりますので、さっと考えて進めていきます。

$ mkdir workspace_golang
$ cd workspace_golang
$ mkdir qi <- 好きな名前を付ける、オプションを付けずに`build`するとフォルダ名がコマンド名になる

次にモジュールの初期化を行います。

$ cd qi
$ go mod init github.com/username/qi

これでプロジェクトフォルダにgo.modファイルが作成されます。
このファイルがnpmpackage.jsonみたいなもののようだ。

buildすると新たに、go.sumファイルが作成される。
このファイルはbuildした実行ファイルの依存パッケージのlockする為にバージョンが記載されている。
npmpackage-lock.jsonみたいなもののようだ。

次にCLIフレームワークurfave/cligo getしておきます。

$ go get github.com/urfave/cli

ディレクトリ構成

今回はmyqiコマンドだけしか実装しないが、他にも好きなキーワードで最新記事や、人気記事を取ってきたり、様々なコマンド実装をしていきたいので、cmdパッケージへ分けていきたいと思います。

qi
├── cmd
│   └── myqi.go
├── go.mod
├── go.sum
└── main.go

全体のコード

まず全体のコードを載せ、続いてコードの説明をしていきたいと思います。

main.go
package main

import (
    "os"

    "github.com/username/qi/cmd" 

    "github.com/urfave/cli"
)

func main() {
    app := cli.NewApp()
    app.Name = "qiitasearch"
    app.Usage = "search qiita articles"
    app.Version = "0.1.0"

    app.Commands = []cli.Command{
        {
            Name:  "myqi",
            Usage: "qiita + mine : you get yours articles",
            Action: func(c *cli.Context) error {
                qiitaToken := os.Getenv("QIITA_TOKEN")
                datas := cmd.FetchQiitaData(qiitaToken)
                cmd.OutputQiitaData(datas)
                return nil
            },
        },
    }

    app.Run(os.Args)
}
myqi.go
package cmd

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
)

// jsonをパースする為の構造体を定義する

type Data struct {
    ID             string `json:id`
    Url            string `json:"url"`
    Title          string `json:"title"`
    LikesCount     int    `json:"likes_count"`
    PageViewsCount int    `json:"page_views_count"`
}

func FetchQiitaData(accessToken string) []Data {

    baseUrl := "https://qiita.com/api/v2/items?query=user:yujiteshima"
    // 様々な検索条件をかけるときはbaseUrlをv2/までにして他を変数で定義してurl.Parseで合体させる
    endpointURL, err := url.Parse(baseUrl)
    if err != nil {
        panic(err)
    }

    b, err := json.Marshal(Data{})
    if err != nil {
        panic(err)
    }

    var resp = &http.Response{}
    // qiitaのアクセストークンがない場合はAuthorizationを付与しない
    // 2パターン作っておく。
    // accessトークンは環境変数に入れておく。自分の場合は.bash_profileにexport文を書いている。

    if len(accessToken) > 0 {
        // QiitaAPIにリクエストを送ってレスポンスをrespに格納する。
        resp, err = http.DefaultClient.Do(&http.Request{
            URL:    endpointURL,
            Method: "GET",
            Header: http.Header{
                "Content-Type":  {"application/json"},
                "Authorization": {"Bearer " + accessToken},
            },
        })
    } else {
        fmt.Println("***** Access Token 無しでQiitaAPIを叩いています アクセス制限に注意して下さい*****")

        resp, err = http.DefaultClient.Do(&http.Request{
            URL:    endpointURL,
            Method: "GET",
            Header: http.Header{
                "Content-Type": {"application/json"},
            },
        })
    }
    defer resp.Body.Close()

    if err != nil {
        panic(err)
    }

    b, err = ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    var datas []Data

    if err := json.Unmarshal(b, &datas); err != nil {
        fmt.Println("JSON Unmarshal error:", err)
        return nil
    }

    /*********一覧取得では、ページビューがnilになるので個別で取りに行ってデータを得る*****************/
    for i, val := range datas {

        article_id := val.ID
        baseUrl := "https://qiita.com/api/v2/items/"
        endpointURL2, err := url.Parse(baseUrl + article_id)
        if err != nil {
            panic(err)
        }

        b, err := json.Marshal(Data{})
        if err != nil {
            panic(err)
        }

        resp, err = http.DefaultClient.Do(&http.Request{
            URL:    endpointURL2,
            Method: "GET",
            Header: http.Header{
                "Content-Type":  {"application/json"},
                "Authorization": {"Bearer " + accessToken},
            },
        })

        if err != nil {
            panic(err)
        }

        b, err = ioutil.ReadAll(resp.Body)
        if err != nil {
            panic(err)
        }

        var m map[string]interface{}

        if err := json.Unmarshal(b, &m); err != nil {
            fmt.Println("JSON Unmarshal error:", err)
            return nil
        }

        datas[i].PageViewsCount = int(m["page_views_count"].(float64))
    }
    return datas
}

// データの出力
func OutputQiitaData(datas []Data) {
    fmt.Println("************************自分のQiita投稿一覧******************************")
    for _, val := range datas {
        fmt.Printf("%-15v%v%v\n", "ID", ": ", val.ID)
        fmt.Printf("%-15v%v%v\n", "Title", ": ", val.Title)
        fmt.Printf("%-12v%v%v\n", "いいね", ": ", val.LikesCount)
        fmt.Printf("%-9v%v%v\n", "ページビュー", ": ", val.PageViewsCount)
        fmt.Printf("%-15v%v%v\n", "URL", ": ", val.Url)
        fmt.Println("-------------------------------------------------------------------------")
    }
}

main.goでしている事

urfave/cliを使用して、コマンドラインツールのインターフェイスを定義する。
urfave/cliでは、複雑なインターフェイスも定義できるが、今回は、

$ qi myqi

とうてば自身のqiita記事が出力されるようにしたいと思います。
他のコマンドもゆくゆくは作りたいので最低限、コマンドを1つとるツールとしていきます。

Qiitaのトークンの取得

qiitaのトークンを取得しておき、環境変数に入れておきます。qiitaのトークンの取得の仕方は、この記事を書いている2019/11時点では、Qiitaのページ右上の自分のアカウントのアイコンから、

設定 -> 左側のメニューのアプリケーション -> 新しくトークンを発行する

となっています。
自分の環境の場合は、.bash_profileに取得したトークンを以下のように記述しています。

.bash_profile
# qiita_api
export QIITA_TOKEN='取得したqiitaのアクセストークンを入れてください'

別パッケージのimportは絶対パス指定で取得する

別のcmdディレクトリに作成した、cmdパッケージのimportは絶対パス指定にしないとならない。
modulesを使用した時と,していない時で、記述方法が違い、古い情報のまま相対パスで記述するとパスを見つけられずエラーがでる。https://qiita.com/yujiteshima/items/8dc2f782f27f147a1e3e

main.go·抜粋
import (
    "os"

    "github.com/yujiteshima/qiita/cmd" // 絶対パスで指定する。

    "github.com/urfave/cli"
)

初期化する

goには、オブジェクト指向プログラミングに見られる「コンストラクタ」機能は無いですが、
「型のコンストラクタ」というパターンの利用が多く使われている。
型のコンストラクタはNew + 型名とする事が多い。また、型のコンストラクタは対象の型のポインタ型を返すように定義するのが望ましく、urfave/cliにおいてもそのようになっている。

main.go·抜粋
func main(){

app := cli.NewApp()

}

app.Commandsにコマンドmyqiを登録する。

main.go·抜粋
app.Commands = []cli.Command{
        {
            Name:  "myqi", 
            Usage: "qiita + mine : you get yours articles",
            Action: func(c *cli.Context) error {
                qiitaToken := os.Getenv("QIITA_TOKEN")
                datas := cmd.FetchQiitaData(qiitaToken)
                cmd.OutputQiitaData(datas)
                return nil
            },
        },
    }

Nameに登録するコマンドを文字列で指定する。
Usageには使い方を登録する。helpで使われる。
Actionに実際の処理を定義する。
Action内で、
1. FectchMyQiitaData()を実行して、データを取得する。
2. OutputQiitaData()で出力する。

以上がmain.goでの処理です。続いて、cmdパッケージの説明をします。

cmdパッケージ

cmdパッケージには、構造体Dataの定義とFetchMyQiitaData関数とOutputQiitaData関数をを定義している。

importしているパッケージ

まず各種パッケージをimportする。

  • "encoding/json" : QiitaAPIから取得したjsonをgoのデータにパースする時に使用する。
  • "fmt" : 標準出力に使用する。
  • "io.ioutil" : QiitaAPIから取得したデータを読み込む時に使用する。
  • "net/http" : QiitaAPIを叩きに行く時に使用する。
  • "net/url" : 文字列からurlへパースする時に使用する。
cmd.go·抜粋
package cmd

import (
    "encoding/json" 
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
)

jsonをパースする為に構造体を定義する

encoding/jsonを用いて構造体のデータにパースする。
encoding/jsonは構造体にTagでjson:キー名を定義しておくと、キー名を出力するjsonのキー名として使ってくれます。
書籍のスターティングGOのChapter5の構造体とインターフェイスが分かりやすかったです。

cmd.go·抜粋
type Data struct {
    ID             string `json:id`
    Url            string `json:"url"`
    Title          string `json:"title"`
    LikesCount     int    `json:"likes_count"`
    PageViewsCount int    `json:"page_views_count"`
}

FetchMyQiitaData()を定義する。

FetchMyQiitaDataは引数としてaccessTokenを受け取ってHeaderにセットしている。

この他にもサブコマンドに引数を受け取ってここで使用することも出来る。キーワードを引数で受け取って検索するコマンドを作るときに使える。

accessToken無しでリクエストを送った時には、accessToken無しでアクセスしているとメッセージを出すようにしておきました。
jsonで受け取ったデータをパースする為に、標準パッケージのencoding/jsonを使いました。Unmarshalメソッドを使って、jsonをGoの構造体データのスライスにパースする。とても簡単に出来ます。

OutputQiitaData()を定義する

あとは出力する為のOutputQiitaDataを定義するだけです。

cmd.go·抜粋
func OutputQiitaData(datas []Data) {
    fmt.Println("************************自分のQiita投稿一覧******************************")
    for _, val := range datas {
        fmt.Printf("%-15v%v%v\n", "ID", ": ", val.ID)
        fmt.Printf("%-15v%v%v\n", "Title", ": ", val.Title)
        fmt.Printf("%-12v%v%v\n", "いいね", ": ", val.LikesCount)
        fmt.Printf("%-9v%v%v\n", "ページビュー", ": ", val.PageViewsCount)
        fmt.Printf("%-15v%v%v\n", "URL", ": ", val.Url)
        fmt.Println("-------------------------------------------------------------------------")
    }
}

forループで出力する。
見やすくフォーマットしておく。

ページビューが取れない問題発生

しかし、ここで一度出力して確認すると、ページビューが0となってしまっている。
アクセストークンが外れてしまっているのか等様々調べた結果、

"https://qiita.com/api/v2/items?query=user:yujiteshima"

このように一覧を取得する際には、ページビューはnullになって返ってくる。つまり、個別の記事検索でしか、ページビュ0は取得できないという事でした。

個別のページからページビューを取得する

ページビューの表示を諦めそうになりますが、ページビュー欲しいです。個別のページから全て取得していきます。

for i, val := range datas {
        //fmt.Println("id:", val.ID)
        article_id := val.ID
        baseUrl := "https://qiita.com/api/v2/items/"
        endpointURL2, err := url.Parse(baseUrl + article_id)
        if err != nil {
            panic(err)
        }

        b, err := json.Marshal(Data{})
        if err != nil {
            panic(err)
        }

        resp, err = http.DefaultClient.Do(&http.Request{
            URL:    endpointURL2,
            Method: "GET",
            Header: http.Header{
                "Content-Type":  {"application/json"},
                "Authorization": {"Bearer " + accessToken},
            },
        })

        if err != nil {
            panic(err)
        }

        b, err = ioutil.ReadAll(resp.Body)
        if err != nil {
            panic(err)
        }

        var m map[string]interface{}

        if err := json.Unmarshal(b, &m); err != nil {
            fmt.Println("JSON Unmarshal error:", err)
            return nil
        }
        datas[i].PageViewsCount = int(m["page_views_count"].(float64))
    }

取得した一覧で得たidを使ってループで回してpage_view_countを取得していき、0が入ってしまっているpage_view_countを更新していきます。

これで、出力してみて完成です。
qiディレクトリで、go installします。

$ go install

これで、qiディレクリで無くても、コマンドqiが使えるようになった。

まとめ

やはり、何か作って見ると、理解が深まると感じた。

今回のアプリケーションの今後の拡張としては、

  1. さらに検索コマンドを増やしすこと。
  2. テストを書く。
  3. ページビューを取得する為に何回もAPIにアクセスする事になる事の改良。

3のページビューを取得する為に何回もAPIにアクセスする事になる事の改良については、FaaSCloudStorage等のサービスを使って定期実行してページビューも取得済のデータをキャッシュさせたものを叩きに行くか、コマンドラインツールから、コマンドでデータをリフレッシュさせれて、そのデータを叩きにいったり出来るようにしてみたい。

たくさんGoを使って何か作って理解を深めていきたいと思います。

リファクタリングした方が良い所や、まずい書き方あれば指摘お願いします。

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

mongoDB公式のGoのDriverを使ってみた

概要

mongoDB公式のGo用のDriver触ったので、そのまとめ

環境

go 1.12.5
go.mongodb.org/mongo-driver v1.1.2
mongoDB 3.1

用語

わかりやすくまとめてくれてる方がいた

  • table → collection
  • row → document
  • column → field

接続

公式ドキュメントのUsage見た方が早い気がしやすが一応

import (
    "context"
    "log"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://username:password@localhost:27017"))
    if err != nil {
        log.Fatalln(err)
        return
    }
}

ApplyURI("mongodb://username:password@localhost:27017")ここで接続先指定してる感じ

FindOne

1ドキュメントの取得
検索条件を構造体で作成した場合

import (
    "go.mongodb.org/mongo-driver/bson/primitive"
    // NOTE: 他に必要なパッケージは省略
)

type FindOneRequest struct {
    // NOTE: 検索したいfieldとvalueを適宜定義する
    TargetField string `json:"targetField" bson:"targetField"`
}

type FindOneResponse struct {
    ID              primitive.ObjectID `json:"id" bson:"_id"`
    Hoge            string             `json:"hoge" bson:"hoge"`
    Fuga            string             `json:"fuga" bson:"fuga"`
}

func main() {
    // NOTE: 接続処理は省きやす 上の見てくだされ

    collection := client.Database("db_name").Collection("collection_name")

    request := FindOneRequest{
        TargetField: "検索内容",
    }

    var response FindOneResponse
    err = collection.FindOne(context.Background(), request).Decode(&response)
    if err == mongo.ErrNoDocuments {
        log.Println("Documents not found")
    } else if err != nil {
        log.Fatalln(err)
    }

    log.Println(res)
}

構造体使う場合はbsonタグ使ってドキュメントで使用している項目名を指定すればいい感じにマッピングしてくれる
ドキュメント作ると勝手に生成してくれるObjectIDはprimitive.ObjectID型を使えばよき

Find

複数ドキュメントの取得
今回は検索条件をbson.Dで定義し、レスポンスにはbson.Mを使用

import (
    "go.mongodb.org/mongo-driver/bson"
    // NOTE: 他に必要なパッケージは省略
)

func main() {
     // NOTE: 接続処理は省きやす

    collection := client.Database("db_name").Collection("collection_name")

    // NOTE: 検索したいfield名とvalueを定義
    cur, err := collection.Find(ctx, bson.D{{Key: "field_name", Value: "value"}})
    if err != nil {
        log.Fatal(err)
    }
    defer cur.Close(ctx)
    for cur.Next(ctx) {

        // NOTE: 1ドキュメントずつdecode
        //       mapが返ってくる
        var result bson.M
        err := cur.Decode(&result)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(result)
    }
    if err := cur.Err(); err != nil {
        log.Fatal(err)
    }
}

Find使った時に返り値には単純な配列とかが返ってくるわけでなく、
*Cursor型が返ってくるのでここらへんの扱いが独特だと感じた次第

InsertOne

1ドキュメントの追加

import (
    "go.mongodb.org/mongo-driver/bson/primitive"
    // NOTE: 他に必要なパッケージは省略
)

type InsertOneRequest struct {
    ID              primitive.ObjectID `json:"id" bson:"_id"`
    Hoge            string             `json:"hoge" bson:"hoge"`
    Fuga            string             `json:"fuga" bson:"fuga"`
}

func main() {
    // NOTE: 接続処理は省きやす

    collection := client.Database("db_name").Collection("collection_name")

    request := InsertOneRequest{
        ID: primitive.NewObjectID(),
        Hoge: "hoge",
        Fuga: "fuga",
    }

    // NOTE: InsertOneの返り値には作成したドキュメントのObjectIDが返ってくる
    response, err := collection.InsertOne(context.Background(), request)
    if err != nil {
        log.Fatalln(err)
    }
}

作成したドキュメント使って何かしたいときはrequest.ID使ってFindOneした構造体を使う感じになりますかね

InsertMany

複数ドキュメントの登録

import (
    "go.mongodb.org/mongo-driver/bson/primitive"
    // NOTE: 他に必要なパッケージは省略
)

type Request struct {
    ID              primitive.ObjectID `json:"id" bson:"_id"`
    Hoge            string             `json:"hoge" bson:"hoge"`
    Fuga            string             `json:"fuga" bson:"fuga"`
}

func main() {
    // NOTE: 接続処理は省きやす

    collection := client.Database("db_name").Collection("collection_name")

    var insertManyRequest []interface{}

    for index := 0; index < 10; index++ {
        indexString := strconv.Itoa(index)

        request := Request{
            ID:   primitive.NewObjectID(),
            Hoge: "hoge",
            Fuga: "fuga",
        }

        insertManyRequest = append(insertManyRequest, question)
    }

    // NOTE: 今回の場合、一括で10件登録してくれる
    //       返り値はinsertしたドキュメントのObjectIDの配列
    response, err := collection.InsertMany(context.Background(), insertManyRequest)
    if err != nil {
        log.Fatalln(err)
    }
}

InsertManyメソッドの第二引数は[]interface{}

Aggregate

集計はAggregateってやつでできるらしい
mongoDBにはOperatorsってのがあってCollectionに対して、このOperatorsを使ってく感じですね

Operatorsの中でもAggregateで使うのがPipeline
公式ドキュメントに比較的Exampleが書いてるのでそれとにらめっこするといつの間にか実装できる

import (
    "go.mongodb.org/mongo-driver/bson"
    // NOTE: 他に必要なパッケージは省略
)

func main() {
    // NOTE: 接続処理は省きやす

    collection := client.Database("db_name").Collection("collection_name")

    pipeline := []bson.M{
        bson.M{
            "$match": bson.M{
                "targetField": "value",
            },
        },
        bson.M{
            "$group": bson.M{
                "_id": "$targetFieldID",
                "count": bson.M{
                    "$sum": 1,
                },
            },
        },
    }

    // NOTE: AggregateはFindと同様に`*Cursor`型を返すので、Cursor型用の扱い方が必要
    hogeAggre, err := collection.Aggregate(ctx, pipeline)
    if err != nil {
        log.Fatalln(err)
    }

    defer hogeAggre.Close(ctx)

    for hogeAggre.Next(ctx) {
        var result bson.M
        err := answerAggre.Decode(&result)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Println(result)
    }
    if err := answerAggre.Err(); err != nil {
        log.Fatal(err)
    }
}

出力結果は下記のような感じのを返してくれるはず
厳密には違うかもしれないので悪しからず

[
    {
        "_id": "hogehoge",
        "count": 15,
    },
    {
        "_id": "fugafuga",
        "count": 13,
    },
]

$がついてるのがOperatorですね
今回Pipelineで指定したのはcollection_nameってコレクションのtargetFieldフィールドの値がvalueのドキュメントたちをtargetFieldIDフィールドでGroupByしてtargetFieldIDごとのドキュメント数をcountフィールドに算出させた感じです
多分・・・

OperatorとかPiplineのStageとかはもっとドキュメント読み込む必要がありやす

所感

InsertやFindするときの引数や返り値は構造体でしっかり型定義してたけども、結局ドキュメントの構造は同一のコレクションでも異なる可能性が出てくるのでbson.Mとかみたくしっかり型定義しないほうがいい気もした

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

アドベントカレンダーに向けてMarkdown に埋め込まれた Go のソースコードに gofmt をかけてくれるツールを作った

こんにちは pon です。みなさんアドベントカレンダーは参加しますか?僕も今年は Go や Rust、Elasticsearch のアドベントカレンダーに参加する予定です。

僕はQiitaの記事を書く際には手元でMarkdownファイルを作ってそこに下書きするのですが、gofmtをかけるのが正直面倒でした。もちろん、既存コードからコピペする分には良いかもしれませんが、記事向けにコードを修正したり補足を入れた時にgofmtがかかっていない状態になることがありました。

そこで来たるアドベントカレンダーに向けて Markdown に埋め込まれた Go のソースコードに gofmt をかけてくれるツールを作ったので、ツールの紹介と作り方を記事にしました。

gofmtmd

作ったツールはこちら

Markdown に埋め込まれた Go のソースコードに gofmt をかけてくれるツールです。例えば下記のようにGoのコードブロックを検知してその中身だけにgofmtがかかります。

もちろんQiitaのようなMarkdownをサポートしているブログサービスだけでなく、README.md を書く時なんかも便利かもしれません。

インストールはこちら

$ go get github.com/po3rin/gofmtmd/cmd/gofmtmd

使い方としてはなるべく gofmt に寄せるようにしました。-rをつけると引数で指定したファイルをそのまま上書きフォーマット。-wを使うと保存するファイルを指定できます。何もつけないとformatした結果が標準出力に流れます。

# replace Go code with formated code
$ gofmtmd testdata/testdata.md -r

# write result to file instead of stdout
$ gofmtmd testdata/testdata.md -w formatted.md

Internal

Markdown のASTを取得する

内部の実装を紹介します。主に使ったのはGo製の Markdown Parser である gopkg.in/russross/blackfriday.v2 を使いました。

これを使ってMarkdownのバイト列をASTに変換しています。

func FmtGoCodeInMarkdown(md []byte) ([]byte, error) {
    // ...
    n := blackfriday.New(blackfriday.WithExtensions(blackfriday.FencedCode)).Parse(md)
    // ...
}

実は fenced block の記法は拡張機能としてのみサポートされているので引数にオプションとして blackfriday.WithExtensions(blackfriday.FencedCode)を渡しています。これで fenced block として Parse してくれるようになります。

ASTをWalkする

MarkdownのASTに対して何かしらの処理を帰納的に処理(トラバース)を実行するには。Walkという *blackfriday.Node から生えているメソッドを使います。Walkにノードごとに実行したい関数を渡してあげます。Walk に渡せる関数は下記の型を持っています。

type NodeVisitor func(node *Node, entering bool) WalkStatus

今回作ったツールではgenFmtWalkerという自作関数をWalkに渡しています。MarkdownのASTをトラバースしていき、Goのコードブロックを見つけたらGoが標準で用意してくれているフォーマット用関数のformat.Sourceに渡しています。

func genFmtWalker(md *[]byte, fmterr *error) blackfriday.NodeVisitor {
    return func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
        if isGoCodeBlock(node) {
            fmted, err := format.Source(node.Literal)
            if err != nil {
                *fmterr = err
                return blackfriday.Terminate
            }
            *md = bytes.ReplaceAll(
                *md,
                bytes.TrimRight(node.Literal, "\n"),
                bytes.TrimRight(fmted, "\n"),
            )
        }
        return blackfriday.GoToNext
    }
}

func isGoCodeBlock(node *blackfriday.Node) bool {
    return node.Type == blackfriday.CodeBlock && string(node.CodeBlockData.Info) == "go"
}

genFmtWalker はクロージャになっており NodeVisitor の型を満たす関数を返しています。なぜこんな複雑な形になったかというと gopkg.in/russross/blackfriday.v2 は Markdown の AST から Markdown に戻す機能がないからです。genFmtWalker で Markdown のバイト列のポインタを受け取って、ポインタの指す先を Walk 内で Replace するようにしています。

Walk はエラーを返さないためエラーも同様に処理しています。error があった場合は blackfriday.Terminate というステータスを返してトラバースを中断します。もっといい実装があるかも。。

ここまで紹介した関数を使って下記のように Markdown内のGoのコードをフォーマットした結果を返す関数が完成しました。主な実装はこれだけです。

// FmtGoCodeInMarkdown formats go code in Markdown.
// returns error if code has syntax error.
func FmtGoCodeInMarkdown(md []byte) ([]byte, error) {
    var err error
    n := blackfriday.New(blackfriday.WithExtensions(blackfriday.FencedCode)).Parse(md)
    // Walk内でformatしていく為、元のMarkdwonのポインタを渡してあげる。
    n.Walk(genFmtWalker(&md, &err))
    if err != nil {
        return nil, err
    }
    return md, nil
}

これを main から読んであげれば Markdown に埋め込まれた Go のソースコードに gofmt をかけてくれる CLI の完成です。

まとめ

Go の AST をトラバースする感覚で Markdown を扱えました。この要領で Markdown 内の Dockerfile のフォーマットなんかも実装できるかもしれません。何かバグがあったら教えてください!!

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

aws-lambda-go/cmd/build-lambda-zipをgo get時のエラー

何をしていたか

此方の記事を参考に、sam cliにてgolang, API gateway を試用していたところ、エラーに遭遇しました。

環境

  • windows10 Pro
>sam --version
SAM CLI, version 0.31.0

詳細

ローカル実行は問題無し。API gatewayからRequestしたところ、502エラーが出ました。

もろもろの記事を見ると、build-lambda-zip をbuild時に実行し、zipファイルをS3へuploadすればよさそうです。

build:
        GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o hello-world/hello-world ./hello-world
        ${GOPATH}\bin\build-lambda-zip.exe -o hello-world/hello-world.zip hello-world/hello-world

こちらのbuild-lambda-zip.exe をinstall。

エラー。

>go.exe get -u github.com/aws/aws-lambda-go/cmd/build-lambda-zip
# github.com/aws/aws-lambda-go/cmd/build-lambda-zip
src\github.com\aws\aws-lambda-go\cmd\build-lambda-zip\main.go:19:17: cannot use cli.StringFlag literal (type cli.StringFlag) as type cli.Flag in array or slice literal:
        cli.StringFlag does not implement cli.Flag (Apply method has pointer receiver)

解決策

  1. go get gopkg.in/urfave/cli.v1
  2. src\github.com\aws\aws-lambda-go\cmd\build-lambda-zip\main.go を下記のように変更
  3. go.exe get -u github.com/aws/aws-lambda-go/cmd/build-lambda-zip
import (
    "archive/zip"
    "errors"
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"

    // "github.com/urfave/cli"
    "gopkg.in/urfave/cli.v1"
)

これで問題なくexeファイルが作成されました。

補遺

最後に、makeファイルを変更し、zipファイルをuploadしてあげます。

build:
    GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o hello-world/hello-world ./hello-world
    ${GOPATH}\bin\build-lambda-zip.exe -o hello-world/hello-world.zip hello-world/hello-world
package:
    sam package --template-file template.yaml --output-template-file output-template.yaml --s3-bucket sam-template-store-dev-kubosuke --profile *****

deploy:
    sam deploy --template-file output-template.yaml --stack-name joyfru-news-dev-kubosuke --capabilities CAPABILITY_IAM --profile *****
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】コーディングTips

はじめに

Goを書いていて便利だと思ったコーディング方法を溜めていきます。

構造体をフィールド名付きでPrintする

ただ fmt.Println するとフィールド名が付かないので見づらいときに。

The Go Playground

fmt.Printf("%+v\n", data)
{name:山田太郎 age:23}

長さがNの文字列を生成する

文字数制限のメソッドをユニットテストしたいときに。
「1000文字も手打ちできないよ〜」みたいなときに。
ちなみに長さは指定した分になるけど、値は空っぽ。

The Go Playground

str := string(make([]rune, 1000))

ある文字をN個生成したい

デバック中、見たいログを目立たせるときに。

The Go Playground

str := strings.Repeat("+", 100)

nilを代入しても元の型情報は保持される

型も値も同じなのに何故かユニットテストでFAILするときみたいなときはコレを疑って。
結論、 関数の返り値は型情報を持たせたままnilを返さない方が良い。

The Go Playground

    s := make([]string, 0)
    fmt.Printf("type = %T | value = %v\n", s, s)

    s = nil
    fmt.Printf("type = %T | value = %v\n", s, s) // type = []string | value = []

nilはキャストできる

わざわざnilをキャストしてnilかどうかなんで判定したくないはず。

関数の返り値は型情報を持たせたままnilを返さない方が良い。

The Go Playground

    i := new(int)
    i = nil
    fmt.Println(reflect.TypeOf(i) == nil) // false
    fmt.Println(i == (*int)(nil)) // true

明示的にnilを返す。

// bad
func do() something {
    // something
    something = nil
    return something
}

// good
func do() something {
    // something
    return nil
}

簡単にバイトを割り当て

有名なやつ。
いちいち手打ちしなくても簡単に値をいれてくれる。
iota の初期値は 0 なので +1

バイトじゃなくてもステータスを列挙して値に数字を入れたいときに。

The Go Playground

type byteSize uint64

const (
    KB byteSize = 1 << (10 * (iota + 1))
    MB
    GB
)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む