Open16

Goを勉強する

UgoUgo

Goは構造体なので、継承は存在しない代わりに、構造体の埋め込みを行う事ができる。
また、メソッドの定義も、レシーバを書く事で構造体に紐づける関数を構造体の外で定義する必要があるようだ。

基本的な構造体
type Food struct {
	Name string
	Type string
}
メソッド定義
// ファンクション名の前にレシーバを書く
func (a Food) name() string {
	return a.Name
}
構造体の埋め込み
type Fruit struct {
	Food
}
上記を表示
func main() {
	f := Food{"beef", "meet"}
	fmt.Println("This is ", f.name())
}
UgoUgo

配列の書き方

1
// var 変数名[長さ] 型
var arr[2] string
2
// var 変数名[長さ] 型 = [大きさ]型 {初期値1, 初期値2}
var arr[2] string = [2]string {"hello", "world"}
3
// 変数名 := [...] 型{初期値1, 初期値2}
var arr[2] string = [2] string{"hello", "world"}
UgoUgo

インターフェースについて

インターフェースの定義の仕方

type 型名 interface {
  メソッド名1(引数の型, ...) (返り値の型, ...)
  ...
}

空のインターフェースが存在している。
全ての型と互換性を持っている。

型アサーション

value := <変数>.(<>)

変数 にインターフェースの変数が入る。
<型>の方にstringやintegerなどが入る。
また、二つ目の返り値も入る。

value, bool := <変数>.(<>)

ここでboolには、型チェックの結果が返ってくる。
ここで、型チェックがfalseだとしても、falseが返ると言うことでエラーにはならない。
boolがないと型チェックでfalseの場合、エラーとして吐き出される。

型 switch

型 switchというものがある。

func do(i interface{}) {
    switch variable := i.(type) {
    case int:
        fmt.Println(variable)
    case string:
        fmt.Println(variable)
    default:
        fmt.Println("Default")
    }
}

func main() {
    do(23) //=> 23
    do("hello") //=> hello
    do(true) //=> Default
}
UgoUgo

並行処理

GoRoutineとchannelという概念がある。
並行処理で使われる

GoRoutineだけだと、処理の完了を待たずに親ファンクションが完了してしまう。
そこで、channelというのを開き、並行処理(GoRoutine)が全て完了するまで待つことができる。

ch1 := make(chan bool)// この間に並行処理を複数書く<-ch1
UgoUgo

GOROOT:
実行されるGoがあるpathを入れる。

// brew経由で1.17.6を入れた場合
export GOROOT=/usr/local/Cellar/go/1.17.6/libexec/

GOPATH:
workspacesのpathを入れる。

// GolandなどのIDEを使ってる場合
export GOPATH=$HOME/GolandProjects/

// ghqなどでcloneしてきてる場合
export GOPATH=$HOME/ghq/{github account}/... 
UgoUgo

Goで.envの値を取得する

github.com/joho/godotenv を利用する。

envファイルを読み込む

err := godotenv.Load(".env")
if err != nil {
  // err hadnle
}

envから値を読み出す

os.Getenv("VALUE")
UgoUgo

cobraを使う

cobra-cli コマンドをインストール

go install github.com/spf13/cobra-cli@latest

cobraをプロジェクトにダウンロード

go get -u github.com/spf13/cobra@latest

プロジェクトでcliを使ってプロジェクトでcobraをinitする

cobra init --license MIT --viper=false

初期動作確認

go run main.go

以下が表示される

A longer description that spans multiple lines and likely contains 
examples and usage of using your application. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.

サブコマンドを追加

cobra-cli add version

以下のファイルが作られる(コメントは削除してます)

version.go
package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "A brief description of your command",
	Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("version called")
	},
}

func init() {
	rootCmd.AddCommand(versionCmd)
}

サブコマンドを呼び出す。

go run main.go version

Runの中が実行される

version called

参考

https://zenn.dev/tama8021/articles/22_0627_go_cobra_cli

https://qiita.com/nirasan/items/cc2ab5bc2889401fe596

UgoUgo

go mod edit -replace github.com/XXXX/YYYY=..

とすると、ディレクトリを分けたgo.modの中でも、親のgo.modのパッケージを参照するようになる。

UgoUgo

OpenFileを行う時の第二引数の意味

os.O_RDONLY: 読み取り専用で開く
os.O_WRONLY: 書き込み専用で開く
os.O_RDWR: 読み取りと書き込みの両方が可能なモードで開く
os.O_APPEND: 書き込み時にファイルの終端に追加する
os.O_CREATE: ファイルが存在しない場合に新規作成する
os.O_TRUNC: ファイルが既に存在する場合はサイズを0に切り詰める(内容を全て削除する)

OpenFileを行う時の第三引数の意味

0644: オーナーは読み書きが可能、他のユーザーは読み取りのみ可能
0755: オーナーは読み書き・実行が可能、他のユーザーは読み取りと実行のみ可能
0666: オーナーと他のユーザー両方が読み書きが可能

UgoUgo

init関数

Goの特殊な関数
そのパッケージに含まれるグローバル変数が初期化されたタイミングで自動的に呼ばれる。

constでenumを表現する

stringerを使う

go install golang.org/x/tools/cmd/stringer@latest
stringer -type Fruit fruit.go

go generateを使う

go:generate stringer -type Fruit fruit.go
を書いておくと

go generate

を実行した時に自動で実行される

UgoUgo

Table Driven Test

コードは以下に記載
https://github.com/ibuki-org/table-driven-test

前後処理

TestMain という関数を用意する

func TestMain(m *testing.M) {
	log.Println("before")
	ret := m.Run()
	log.Println("after")
	os.Exit(ret)
}

Short

以下を埋め込むことで
go test -short を実行したときにスキップすることができる。

if testing.Short() {
	t.SkipNow()
}

Parallel

for _, test := range tests {
	test := test // ここをしないとタイミングによって、最後の項目のみ参照されてしまう。
	t.Run(test.name, func(t *testing.T) {
		t.Parallel()
		got := Add(test.lhs, test.rhs)
		if got != test.want {
			t.Errorf("Add(%d, %d) = %d, want %d", test.lhs, test.rhs, got, test.want)
		}
	})
}

差分

google/go-cmp を使うと差分を取るのに楽

{
+ "value": 20
- "value": 21
} 

テンポラリーディレクトリ

テスト中に一時ファイルを作成したい時がある。
そういった時testing には、一時的にファイルを作る機能が備わっている。
例:

func TestCreateProfile(t *testing.T)  {
	dir := t.TempDir()
	filename := filepath.Join(dir, "profile.json")
	got, err := CreateProfile(filename)
	if err != nil {
		t.Fatalf("CreateProfile(%s) = %v", filename, err)
	}
	want := true
	if got != want {
		t.Errorf("CreateProfile(%s) = %v, want %v", filename, got, want)
	}
}

環境変数

t.SetEnv("DATABASE_URL")

などのように環境変数を設定することができる。

Fuzzingテスト

GoにはFuzzingtテストのためのツールが用意されている。
予想ができていないテスト項目をテストを行うまで気づけないことに対して、Fuzzingテストを行うことで未然にどういった挙動を行うか知ることができる。
例:

func FuzzDoSomething(f *testing.F) {
	f.Add("test&&&")
	f.Fuzz(func(t *testing.T, input string) {
		DoSomething(input)
	})
}

上記がある状態で以下のコマンドを実行
go test -fuzz FuzzDoSomething -fuzztime 10s