- 投稿日:2020-08-01T19:23:57+09:00
[Go言語] 初心者必見シリーズ: 構造体(Structs)
構造体
構造体は、フィールド(field)の集まりです。
定義方法
構造体は下記のように
type
とstruct
を使用して定義します。
- 構文
type 構造体の名前 struct { フィールド名 型名 フィールド名 型名 フィールド名 型名 ... }同じ型のフィールド名は、カンマ区切りで書くことができます。
type 構造体の名前 struct { フィールド名, フィールド名, フィールド名 型名 フィールド名 型名 }
- 例
人のデータをまとめた構造体です。
type People struct { Name string Sex string Age int Height int Weight int }初期化
方法1:Key:Valueで初期化します。
people := People{Name: "太郎"}設定しない要素は ゼロ値 で初期化される。
方法2:要素の定義順に初期化します。
people := People{"太郎", "男", ...}この方法では、全部のfieldを設定する必要があります。
方法3:定義後にドット(.)を用いてfieldを初期化します。
people := People{} people.Sex = "男" people3.Age = 8構造体へのポインター(Pointer to structs)
方法1:
&
(アドレス演算子)で構造体へのPointerを取得します。people := People{Name: "太郎"} p := &people (*p).Name = "花子"
(*p)
はp
に省略できます。p.Name = "花子"方法2:
new(構造体名)
で構造体へのポインターを生成します。people := new(People) // Peopleのポインタ型 (*people).Name = "花子" people.Name = "花子" // (*people) は peopleに省略できます。
new(T)
はT型の無名変数(unnamed variable)を作成し、それをT
のゼロ値へ初期化し、*T
型の値であるそのアドレスを返します。式
new(People)
と&People{}
は等価です。タグ(Tag)
Go構造体タグは、Go構造体宣言のフィールドの後に表示される注釈です。
実行時に
reflect
パッケージでその情報を取得し利用したりします。良くある構造体をJSONに変換する例として、
下記のようにフィールドの後ろに
json
タグをつけると、type People struct { Name string `json:"name"` Sex string `json:"sex"` Age int `json:"age"` Height int `json:"height"` Weight int `json:"weight"` }Go言語の
json
標準パッケージで構造体をJSONに変換すると、フィールド名がTagで定義された名前に置換されます。people := People{Name: "太郎", Sex: "男"} jsonBytes, _ := json.Marshal(people) fmt.Println(string(jsonBytes)) // JSON: {"name": "太郎", "sex": "男"}その仕組み
json
パッケージの内部で下記のようにタグ情報を取得してJSONデータに変換しています。t := reflect.TypeOf(people) for i := 0; i < t.NumField(); i++ { field := t.Field(i) tag := field.Tag.Get("json") // `json`タグ情報を取得 }下記のように、複数のタグはスペースで区切って記述できます。
type People struct { Name string `json:"name" validate:"required"` Sex string `json:"sex" validate:"required"` Age int `json:"age" validate:"required"` Height int `json:"height" validate:"required"` Weight int `json:"weight" validate:"required"` }埋め込みフィールド(Embedded fields)
型だけで宣言されていて、明示的なフィールド名がないフィールドを埋め込みフィールドと呼びます。
Go言語は埋め込みで他言語のクラスの継承ぽいことができます。
埋め込み子構造体(
Address
)のフィールドが昇格し、構造体の外(Person
)からダイレクトにアクセスすることが可能です。person.Adress.city → person.city person.Adress.state → person.state // PersonがAdressを継承しているように、Adressのフィールドをダイレクトにアクセスすることが可能となります。
Person
に同名のフィールドやメソッドがあるときは、Address
にあるそのフィールドは昇格されないです。構造体の比較
reflect.DeepEqual
はポインターか否かにかかわらず、フィールドの値が同じであれば
true
を返します。import ( "reflect" ) ... people1 := People{Name: "太郎"} people2 := People{Name: "太郎"} println(reflect.DeepEqual(people1, people2)) // true println(reflect.DeepEqual(&people1, &people2)) // true println(people1 == people2) // true println(&people1 == &people2) // false動画(YouTube)
https://www.youtube.com/watch?v=91_7hWkByv4
元記事
- 投稿日:2020-08-01T16:41:13+09:00
VS Code の Remote Container で複数のコンテナを起動して作業を切り替える
はじめに
VSCode の Remote Container って便利ですよね。
ローカルPCの環境を汚さずにいろいろな言語を試すことのできるのが最高。ただ、サクッと試したいだけなのに、いちいち言語別に設定するのが面倒だったりしますよね。
この投稿では、1回起動するだけで複数のコンテナに接続できる設定を紹介します。
公式ドキュメントにも書かれているので、詳しく知りたい方はこちらを。
https://code.visualstudio.com/docs/remote/containers-advanced#_connecting-to-multiple-containers-at-onceデモコードの概要
ここでは、golang と python のコンテナを扱うこととします。
.
├── golang
│ ├── .devcontainer.json
│ └── Dockerfile
├── python
│ ├── .devcontainer.json
│ └── Dockerfile
└── docker-compose.yml
設定ファイルの詳細
1. docker-compose.yml
remote-containerで管理するコードの親元を docker-compose.yml に記載します。
build.context でディレクトリを指定し、そこから Dockerfile までの相対パスを dockerfile に書いておけば大丈夫です。
ディレクトリ構成が気に入らなければ、自由に変えることもできます。
docker-compose.ymlversion: "3" services: golang-container: build: context: golang dockerfile: Dockerfile volumes: - .:/workspace:cached environment: TZ: "Asia/Tokyo" command: sleep infinity python-container: build: context: python dockerfile: Dockerfile volumes: - .:/workspace:cached environment: TZ: "Asia/Tokyo" command: sleep infinity2. .devcontainer.json
コンテナごとの設定を .devcontainer.json に記載します。
親子関係をつなぐために、 dockerComposeFile と service の名前は docker-compose.yml で定義したものと一致させています。
workspaceFolder で言語ごとに区分けたディレクトリを指定してやることにより、VS Code のウィンドウ上に他言語が表示されないようにできます。(表示していると警告が出てきたりしてうるさい)
extensions は調べたらいろいろあるので、好きにカスタマイズしたらいいです。
.devcontainer.json{ "name": "Dev Container Golang Env", "dockerComposeFile": "../docker-compose.yml", "service": "golang-container", "workspaceFolder": "/workspace/golang", "extensions": [ "golang.go", "donjayamanne.githistory", "eamodio.gitlens", "codezombiech.gitignore", "mhutchie.git-graph", "esbenp.prettier-vscode", "coenraads.bracket-pair-colorizer", "ionutvmi.path-autocomplete" ], "terminal.integrated.shellArgs.linux": [ "-l" ], "shutdownAction": "stopCompose" }3. Dockerfile
コンテナごとに使いたい設定を記載します。
普通のDockerfileと同じようにかけるので、ライブラリのインストールもしておくと便利です。
ここでは、みんな大好き git の補完ツールをインストールさせています。
DockerfileFROM golang:buster WORKDIR / RUN apt-get update \ && apt-get install -y --no-install-recommends \ git \ openssh-client \ procps \ ca-certificates \ curl \ unzip \ gnupg \ vim \ wget \ && rm -rf /var/lib/apt/lists/* # git RUN wget https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash -O ~/.git-completion.bash RUN chmod a+x ~/.git-completion.bash RUN echo "source ~/.git-completion.bash" >> ~/.bashrc RUN wget https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh -O ~/.git-prompt.sh RUN chmod a+x ~/.git-prompt.sh RUN echo "source ~/.git-prompt.sh" >> ~/.bashrc複数コンテナの起動方法
- VS Code のコマンドパレットを開いて、
Remote-Containers: Open Folder in Container...
を選択する- 開きたいディレクトリを選択する(今回は golang か python のディレクトリ)
上記のように普通にRemoteContainerを起動する要領で操作したら、選択したコンテナだけでなく、設定に記載されている他のコンテナも作成してくれます。
切り替えるには、1. 2. の手順と同様に、開きたいディレクトリを選択するだけです。
1回目の起動時にコンテナは立ち上がっているので、ウィンドウが切り替えられてすぐに操作できます。もし別ウィンドウにしたければ、VS Code で新規ウィンドウを作成して、そこから 1. 2. の手順を実行したらいいです。
まとめ
VS Code の Remote Container で複数のコンテナを起動する方法を紹介しました。
ここでは golang と python の2つのコンテナでしたが、自由に追加できます。複数言語を git のプライベートレポジトリで管理するときなんかに役立つのではないでしょうか。
言語のバージョン違いの動作検証なんかにも使えそうですね。ソースコードは github で公開しています。
https://github.com/fumiyakk/demo-multiple-remote-containerspython は poetry を使うために手のこんだ作りになってます。
そのうち poetry と black を組み合わせた python の開発環境について紹介します。
- 投稿日:2020-08-01T12:01:39+09:00
Goでソースコードを画像化するCLIを作った
こんにちわ
ゴリラです普段、Twitterでたまにこういうふうにソースコードの画像を貼り付けることがあります。
画像を作るのにcarbonというサービスを使っています。
このサービスできれいな画像を生成できますが、インターネットとブラウザなしでは使えないためちょっと不便と感じています。
また、sliconというrust製のツールもありますが、これもcarbonと同様の制限があります。
そこで、ブラウザやネットを使用せずCLIでソースコードを画像化できたら良いなと思いCLIを作りました。どんな感じ
こんな感じでpngファイルを出力できます。外部ツール依存無しでGoだけで動くのでインターネットもブラウザも必要ないです。
使い方
2通りあります。ソースコードを標準入力で渡すか、ファイルを渡すかです。
code2img - convert source code to image Version: 1.0.0 Usage: $ code2img -t monokai main.go main.png $ echo 'fmt.Println("Hello World")' | code2img -ext go -t native -o sample.pngファイルの場合はオプションなくても使えますが、カラースキーマを指定したい場合は
-t
を使用します。
使用可能なカラースキーマはこちら、サポートしている言語一覧はこちらを参照ください。実装
処理の流れは大まかと次になります。
- コードをトークナイズして、トークンごとに色を情報をつける
- トークンを1文字ずつpngに書き込む
ソースコードをトークナイズしてトークンごとに色(RGBA)をつけますが、ここをスクラッチで多言語に対応するのは骨が折れるので、素直にgithub.com/alecthomas/chromaというライブラリを使用しました。
このライブラリを使えば、2の処理だけを書けば済みます。
2の処理は大まかに次になります。
- 文字を描画するfontを読み込み
- 座標を計算しつつ、1文字ずつ描画する
fontに関してはマルチバイト対応のCicaをgithub.com/jessevdk/go-assetsで埋め込んでいます。
そのためCLIのサイズが倍くらい増えましたが、致し方ない…文字描画の処理は次になっています。この
iterator.Tokens()
がtoken情報を返してくるので、
tokenの種類からstyle.Get()
で色情報を取得しています。その後、1文字ずつpngに書き込んで、座標を計算して…を繰り返します。ここでポイントですが、マルチバイトの場合は座標は +2 しないと文字が重なって読めなくなります。なので1文字の長さを確認して必要あればx座標を +2 しています。これでマルチバイトでも問題なく描画されます。
for _, t := range iterator.Tokens() { c := style.Get(t.Type).Colour dr.Src = image.NewUniform(color.RGBA{R: c.Red(), G: c.Green(), B: c.Blue(), A: 255}) for _, c := range t.String() { if c == '\n' { x = fixed.Int26_6(padding) y++ continue } else if c == '\t' { x += fixed.Int26_6(padding) continue } dr.Dot.X = fixed.I(10) * x dr.Dot.Y = fixed.I(20) * y s := fmt.Sprintf("%c", c) dr.DrawString(s) // if mutibyte if len(s) > 2 { x = x + 2 } else { x++ } } }エディタと連携
標準入力に対応しているので、Vimなどのエディタと連携してサクッと画像化出来ます。
さいごに
依存なしでソースコードを画像に変換できるので、ぜひ試してみてください。エディタとも連携できて便利です。
- 投稿日:2020-08-01T10:22:15+09:00
GoでMonth型をint型に変換
英語で表されるtime.Month型をintで型キャストしたら普通に変換できたのでメモ。
main.gopackage main import ( "fmt" "time" ) func main() { now := time.Now() year := now.Year() month := now.Month() monthToInt := int(month) date := now.Day() hour := now.Hour() minute := now.Minute() second := now.Second() fmt.Printf("年を表示:%v\n", year) fmt.Printf("月をtime.Month型で表示:%v\n", month) fmt.Printf("月をint型に変換して表示:%v\n", monthToInt) fmt.Printf("日にちを表示:%v\n", date) fmt.Printf("時間を表示:%v\n", hour) fmt.Printf("分を表示:%v\n", minute) fmt.Printf("秒を表示:%v\n", second) }結果年を表示:2020 月をtime.Month型で表示:August 月をint型に変換して表示:8 日にちを表示:1 時間を表示:10 分を表示:31 秒を表示:17
- 投稿日:2020-08-01T04:12:36+09:00
Golangを唐突に始めたい衝動に駆られた話
金曜日の深夜に急にGoを始めたい衝動に駆られた
思い立った経緯
金曜日の夜にふと人気プログラミング言語について閲覧していた。そこでPythonやJavascript不動の人気に、「はい、そうですか。」気分でみていた。そこでみたGoLangをみて、「あ、せっかくだし触りだけでも今日やろうかな」と思ったので、自分への議事録のため記事を投稿。
やったこと
自分のPCでの動作環境構築と公式ドキュメントの「Packages, variables, and functions.
Learn the basic components of any Go progr」をやってみた動作環境
OS:macOS Catalina 10.15.6
Editor:Visual Studio Code 1.47.3
pkg管理:Homebrew 2.4.9環境構築
まずはGoLangのインストールから
hoge@hoge-MacBook-Air ~ % brew install go ...省略... hoge@hoge-MacBook-Air ~ % go version go version go1.14.5 darwin/amd64
インストール完了だね。
動作確認
Visual Studio Code(以下VSCode)を起動して、必要な拡張機能をインストールする。
だけをインストールしました。
そして、(Cmd+Shift+P)で全てのコマンドを表示し、以下を選択する。
- Go:Install/Update Tools
項目を全て選択し、インストールする。
インストール完了したら環境構築は完了となるので、実際にコードを試しに書いていく。サンプルコード実装と内容の理解
公式のページ(https://golang.org/ )にならって、以下のファイルを任意のフォルダで作成。
hello.go// You can edit this code! // Click here and start typing. package main import "fmt" func main() { fmt.Println("Hello, 世界") }そしてコンパイル実行を行う。
hoge@hoge-MacBook-Air sampleGo % go run hello.go Hello, 世界
問題なく実行確認が完了しました。
これでとりあえずの環境構築が完了としています。公式ドキュメントを読んでいく
A Tour of Go(https://golang.org/doc/ )でドキュメントを読んでいく。
Package & import
Goのプログラムはパッケージで構成されています。
Packageはmainから開始される。
下記のプログラムでは、"fmt"と"math/rand"をインポートしている。package main import ( "fmt" "math/rand" )Exported names
最初の文字が大文字で始まる名前は、外部パッケージから参照できるエクポートされた名前となっている。
パッケージをインポートしているとエクスポートしている名前は参照することができる。exported-names.go// Piはmathパッケージでエクスポートされている package main import ( "fmt" "math" ) func main() { fmt.Println(math.Pi) }function
関数は0個以上の引数を受け取れる。
Named return values
Goでの戻り値となる変数に名前をつけることができ、ただし、名前をつけた戻り値の変数(以下のxとy)についてはreturn ステートメントに何も書かずに戻すことができ、このことをnaked returnという。推奨は使わない。
named-results.gopackage main import "fmt" func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return } func main() { fmt.Println(split(17)) }variables
基本的には「var XXX = X」の形で宣言を行うが、関数ないだけ「XXX := X」と暗黙的な型宣言ができるが、関数の外ではエラーとなる。
基本型と初期値については以下の通り。bool デフォルト初期値:false string デフォルト初期値:"" int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // uint8 の別名 rune // int32 の別名 // Unicode のコードポイントを表す float32 float64 complex64 complex128 デフォルト初期値:0Type conversions
Go言語では明示的な変換が必要で、以下のような形でおこなう。
もし、明示的な型を指定せずに変数を宣言する場合は、変数の右側から型推論がされる。// 通常 var i int = 42 var f float64 = float64(i) var u uint = uint(f) // シンプルに i := 42 f := float64(i) u := uint(f)Constants
定数はconstキーワードを使って変数と同じように宣言を行う。
定数は、文字(character)、文字列(string)、boolean、数値(numeric)のみで使えます。
なお、定数は := を使って宣言できません。最後に
今日はここまでやったので、また衝動に駆られた際に続きをやっていこうかと。
よい気晴らしになりました。新しい言語を学び始めるとどっぷりハマってしまうので、
また忘れた頃に続きを別記事で更新します。
- 投稿日:2020-08-01T00:46:01+09:00
GoMockで、構造体オブジェクトのポインタを受け取る関数の引数をテストする
概要
モック化させたインターフェースの関数に、期待した値が入って呼ばれるかどうかをテストしたいシーンのお話です。
引数に構造体のポインタを受け取る関数をテストする時、
- テストスイート内でアサーションのために宣言したオブジェクト
- 実際のビジネスロジックの中で生成されたオブジェクト
はポインタが異なるため、オブジェクトのチェックが難しくなります。
gomockを使っている際に、この問題を解決する方法をまとめます。
GoMock
GitHub: https://github.com/golang/mock
今回使用したバージョンはv1.4.3です。
ユースケース
概要だけだとイメージが湧きにくいかと思いますので、ここからは例を交えて説明します。
ユースケースは下記のようにシンプルなものを用意します。
- タイトルと著者名を入力して、Bookオブジェクトを作成する
- タイトルに空文字を入れた時には、Bookオブジェクトのタイトルが「no title」になる
テストケース
テストでは、
- When: ユースケースの関数に空文字のタイトルを渡した時
- Then: DBに保存されるエンティティのタイトルに「no title」がセットされている
ということをチェックします。
ビジネスロジック実装
Entity
ID、 タイトル、 著者名を持ちます。
book.gopackage entity type Book struct { ID uint64 Title string Author string } func NewBook(title, author string) Book { return Book{ Title: title, Author: author, } }Repository
entityを永続化させるインターフェースです。
book_repository.gopackage repository import "github.com/.../entity" type BookRepository interface { // Bookオブジェクトのポインタを受け取り、DBに保存する Save(book *entity.Book) error }RepositoryのSave関数がポインタを受け取っています。
このインターフェースを、GoMockでモック化させます。Interactor
そしてユースケースに該当する、Interactorの関数を今回はテストします。
book_interactor.gopackage service import ( "fmt" "github.com/.../entity" "github.com/.../repository" ) type BookInteractor struct { bookRepository repository.BookRepository } func (bi BookInteractor) CreateBook(title, author string) error { // titleが空の場合はno titleを入れる if title == "" { title = "no title" } // entity作成 book := entity.NewBook(title, author) // entityをDBに保存 err := bi.bookRepository.Save(&book) if err != nil { return fmt.Errorf("failed to save book: %w", err) } return nil }テストコード
失敗するケース
book_interactor_test.gopackage service import ( "testing" "github.com/golang/mock/gomock" "github.com/.../entity" mock_repository "github.com/.../mock/repository" ) func TestBookInteractor_CreateBook(t *testing.T) { controller := gomock.NewController(t) defer controller.Finish() t.Run("succeed when name is empty", func(t *testing.T) { // テストする関数の引数に入れる値 argTitle := "" // When: タイトルに空を指定する argAuthor := "taro" // bookRepositoryをモック化 bookRepository := mock_repository.NewMockBookRepository(controller) // bookRepository.Save関数の引数に期待するオブジェクト expectedBook := entity.Book{ ID: 0, Title: "no title", // Then: no titleがセットされている Author: "taro", } // bookRepository.Save関数に期待した引数が入って呼ばれるかチェック bookRepository.EXPECT().Save(&expectedBook).Return(nil) interactor := BookInteractor{ bookRepository: bookRepository, } // action: テスト対象関数呼び出し err := interactor.CreateBook(argTitle, argAuthor) if err != nil { t.Fatal(err) } }) }
Save
関数が呼ばれる時の引数の構造体の中身は、想定するexpectedBook
と同じはずですが、このケースでは失敗します。なぜなら、
Save
関数が呼ばれる時には引数がポインタで渡されています。
そして、このexpectedBook
と、interactorの中でNewBook
で生成されたオブジェクトの中身は同じですが、オブジェクト自体のポインタが異なるためにエラーが吐かれます。この場合にどうすれば良いかというと、2つやり方があります。
- 構造体の中身をアサーションするカスタムMatcherを作る
- mockに用意された
Do
メソッドを使って、構造体の中身を1つずつアサーションする前者は少々面倒なので、後者で回避します。
成功するケース
book_interactor_test.gopackage interactor import ( "testing" "github.com/golang/mock/gomock" "github.com/.../entity" mock_repository "github.com/.../mock/repository" "github.com/stretchr/testify/assert" ) func TestBookInteractor_CreateBook(t *testing.T) { controller := gomock.NewController(t) defer controller.Finish() t.Run("succeed when name is empty", func(t *testing.T) { // テストする関数の引数に入れる値 argTitle := "" // When: タイトルに空を指定する argAuthor := "taro" // bookRepositoryをモック化 bookRepository := mock_repository.NewMockBookRepository(controller) // bookRepository.Save関数に期待した引数が入って呼ばれるかチェック bookRepository.EXPECT().Save(gomock.Any()).Return(nil).Do(func(actualBook *entity.Book) { // 変更箇所 assert.Equal(t, "no title", actualBook.Title) // Then: no titleがセットされている assert.Equal(t, "taro", actualBook.Author) }) interactor := BookInteractor{ bookRepository: bookRepository, } // action: テスト対象関数呼び出し err := interactor.CreateBook(argTitle, argAuthor) if err != nil { t.Fatal(err) } }) }GoMockには、モックオブジェクトの関数の処理後にフックされる
Do
メソッドがあり、それを使用します。
Do
に渡すコールバック関数の中に、Save
メソッドが呼ばれた時の実際の引数が入ってきます。
それを対象にコールバック関数内でアサーションを行います。ポインタで引数が渡されるなら、渡ってきたオブジェクトの中身を1つずつアサーションしてしまおう という戦法です。
なお、
Save
関数の引数に、gomock.Any()
という「どんな引数でも期待する」というMatcherを入れないと、モック関数の呼び出しでコケます。最後に
GoMockで、構造体オブジェクトのポインタを受け取る関数の引数をテストする方法をまとめました。
もしもっとスマートにテストできる方法があれば教えていただけますと嬉しいです!