20211205のGoに関する記事は9件です。

[Go言語] Airを使って最短でホットリロードを実現する

環境 以下の実行環境で動作を確認しています。 $ go version go version go1.15.4 darwin/amd64 $ docker -v Docker version 20.10.10, build b485636 $ docker-compose -v docker-compose version 1.29.2, build 5becea4c ホットリロードのライブラリといえば色々ありますが・・・ 最近ではAirが1番流行っているようです。サクッと試してみます。 Star History 必要なファイルを作成する 以下のファイルが必要になります。 $ tree . ├── Dockerfile ├── air.toml ├── docker-compose.yml ├── go.mod └── server.go シンプルなAPIサーバーを作成する リクエストを行うと「Hello, World」とレスポンスをするシンプルなAPIサーバーを作成します。 server.go package main import ( "fmt" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World") } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } Dockerfileを作成する Dockerfileを作成します。Golang - Official Image | Docker Hub に記述されているものに少し手を加えています。 Dockerfile FROM golang:1.17 WORKDIR /go/src/app COPY . . RUN go get -d -v ./... RUN go install -v ./... RUN go get -u github.com/cosmtrek/air CMD ["air"] docker-compose.yml docker-compose.ymlを作成します。 docker-compose.yml version: "3" services: go: build: . volumes: - ./:/go/src/app ports: - "8080:8080" air.tomlを作成する Airのリポジトリ に公開されている設定ファイルを使わせてもらいます。 air.toml # Config file for [Air](https://github.com/cosmtrek/air) in TOML format # Working directory # . or absolute path, please note that the directories following must be under root. root = "." tmp_dir = "tmp" [build] # Just plain old shell command. You could use `make` as well. cmd = "go build -o ./tmp/main ." # Binary file yields from `cmd`. bin = "tmp/main" # Customize binary, can setup environment variables when run your app. full_bin = "APP_ENV=dev APP_USER=air ./tmp/main" # Watch these filename extensions. include_ext = ["go", "tpl", "tmpl", "html"] # Ignore these filename extensions or directories. exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # Watch these directories if you specified. include_dir = [] # Exclude files. exclude_file = [] # Exclude specific regular expressions. exclude_regex = ["_test.go"] # Exclude unchanged files. exclude_unchanged = true # Follow symlink for directories follow_symlink = true # This log file places in your tmp_dir. log = "air.log" # It's not necessary to trigger build each time file changes if it's too frequent. delay = 1000 # ms # Stop running old binary when build errors occur. stop_on_error = true # Send Interrupt signal before killing process (windows does not support this feature) send_interrupt = false # Delay after sending Interrupt signal kill_delay = 500 # ms [log] # Show log time time = false [color] # Customize each part's color. If no color found, use the raw app log. main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # Delete tmp directory on exit clean_on_exit = true go.modを作成する ターミナルで以下のコマンドを実行します。 $ go mod init air-sample 実行する 以下のコマンドを実行します。 running...と表示され動作していることを確認しました。 $ docker-compose up --build ... go_1 | go_1 | __ _ ___ go_1 | / /\ | | | |_) go_1 | /_/--\ |_| |_| \_ , built with Go go_1 | go_1 | watching . go_1 | !exclude tmp go_1 | building... go_1 | running... 別のターミナルをからAPIリクエストを実行してみて、レスポンスが返ってくることを確認します。 $ curl http://localhost:8080 Hello, World 次に、server.goの内容を変更します。 出力を「Hello, Gopher」に変更し、保存します。 server.go func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, Gopher") } ファイルの変更を検知してビルド走ります。 go_1 | server.go has changed go_1 | building... go_1 | running... もう一度、APIリクエストを行うとレスポンスが変化していることがわかります。 $ curl http://localhost:8080 Hello, Gopher 以上です。 参考 cosmtrek/air: ☁️ Live reload for Go apps 【Golang】 Airを用いてWebアプリケーション開発中に変更を即座に反映する - zuckey blog Golang - Official Image | Docker Hub
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go の log.Fatal は気軽に使ったあかん!

log.Fatalはアプリケーションの実行が終了されるので、気軽に使うのはやめましょう! log pkgの中身は以下 // Fatal is equivalent to Print() followed by a call to os.Exit(1). func Fatal(v ...interface{}) { std.Output(2, fmt.Sprint(v...)) os.Exit(1) } os.Exit(1) されてるので、アプリケーションの実行が終了される。 なのでとりあえずログに吐きたい時は log.Println などにしましょう。 log.Println だとexitされないです。 log.Println のsrcは以下 // Println calls Output to print to the standard logger. // Arguments are handled in the manner of fmt.Println. func Println(v ...interface{}) { std.Output(2, fmt.Sprintln(v...)) } 参照 https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/log/log.go;l=316 https://pkg.go.dev/log
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TDD+DDD実践:ゆめみさんの模試

こんにちは、HCB Advent Calendar 2021の5日目を担当する、take-2405です。 HCB Advent Calendar 2021 ハッカソンの記事を書く予定でしたが、急遽変更してしまいました。ごめんなさい? 今回は、TDD、DDDを書籍で学んだのちに、実際にコードを書きながら理解を深めた過程を書きたいと思います。 この記事について この記事は、「TDDとDDDについて学んだから実践してみたい」ということでゆめみさんが公開している模試問題に取り組んでいます。 TDDの開発サイクルとオニオンアーキテクチャを意識して問題に解答しました。 取り組んだ問題 今回取り組んだ問題は、ゆめみさんが公開している模試問題です。 内容は、とあるゲームのスコアログをソートし、上位10位のスコアに概要するプレイヤー一覧を表示するという問題です。 詳細は下記ページをご確認ください。 https://www.yumemi.co.jp/serverside_recruit 本題 TDDのマントラに従うならば、最初にすべきことは必要なテストを洗い出し、失敗する自動テストを用意することです。一方で、DDDはドメインへの理解を深めモデルを作成する(改善し続ける)ことが最初に必要です。 TDDのマントラ 1. レッド 2. グリーン 3. リファクタリング これら(テストの用意、モデル作成)の優先順位はケースバイケースだと思いますが、今回はテスト項目を洗い出し、失敗する自動テストを用意することで必要なモデルへの理解を深め、モデル作成しました。 問題を呼んで洗い出したテスト項目 - 引数をきちんと受け取れている - ログファイル(csv)をきちんと開けている - ログファイル(csv)のヘッダー、列数を確認する(対象のログかどうか) - ログファイル(csv)の中身をパースする - ログファイル(csv)のスコアなどの型を適したものにする - プレイヤーごとの合計得点、プレイ回数を計算する - プレイヤーごとの平均得点を計算する - 平均点ごとにプレイヤーをソートする - 平均点を高い順に1-10位まで出力する(同じ得点の場合は同じ順位として出力し、10位まで出す) 最初に作成したモデル type playData struct{ Count int `csv:"player_id"` Score int `csv:"score"` } type PlayDatas struct{ Datas map[string] playData } type RankingDatas struct{ Datas map[int] string } もちろん上記のテスト項目から増えたもの、減ったものがありました。(初めてということもあり、項目の洗い出しは悩みました。) 最初に作成したモデルも、後々無駄な処理が必要になると感じる部分が度々あり、変更しました。 テスト項目を洗い出し、モデルを作成した後はのサイクルは至ってシンプルでした。 1. TDDのマントラに従い、テスト項目の中から1つを選択し、テストを作成、機能を実装、テストを通す。 2. 必要があればモデルを修正する。 の繰り返しです。 以下が最終的なアウトプットになります。 この記事ではアーキテクチャに関してほとんど触れられていないので、リポジトリを見ていただけると幸いです。 感想 TDD、DDDともに学ぶ前の敷居は高く感じられましたが、実践するうちにそれぞれのメリットが感じられました。 テストの粒度などを意識しすると、メソッドの分け方が洗練られた気がします。また、オニオンアーキテクチャを採用したことで普段よりも依存性に意識してコードを書くことができたと思います。 参考記事 https://zenn.dev/foxtail88/articles/a9243dd25acd98 https://zenn.dev/foolishell/articles/589536ec741ddd おわりに 明日のAddvent calenderは @QT21-004-6_HCB が記事を書くので、是非そちらも見てください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go 1.18 で追加される `any` は `interface{}` のエイリアス

先日、次の Issue を教えてもらって「any って type parameters 専用じゃなかったの?」と驚いたので調べてみました。 結論から書くと Go 1.18 で追加される any は interface{} のエイリアスとして事前定義された識別子なのでどこでも使えます。 最新の仕様 1 にも次のように記載されています。 For convenience, the predeclared type any is an alias for the empty interface. 型制約のための any (2021 年 1 月 12 日~) Go でジェネリクスをサポートするための仕様変更は 2021 年 1 月 12 日に提案されました。 (実際にはそれ以前に Go2 としての提案やデザインドラフトが公開されています) この時点での any はすべての型を受け付ける型制約 (type constraint) です。 The new predeclared name any is a type constraint that permits any type. interface{} は結果的にすべての型を表しているだけなので、より明確な意味を持つ識別子として any が定義されたようです。 同時に型制約において interface{} と any は交換可能 (どちらを使っても同じ) であるとされています。 また、 proposal ドキュメント 2 では事前定義された識別子としての any を型制約以外では使えないものとして定義しています。 It will not be valid to use any as anything other than a type constraint. この proposal はジェネリクスサポートに焦点を絞っているため any を他の場所でも使えるようにするかどうかについては別に議論する必要があるというのがその理由です。 型制約以外でも使えるように (2021 年 9 月 23 日~) any を型制約以外の場所でも使えるようにする変更は Go2 としての提案時点で行われていました。 賛成意見も反対意見もあったようですがジェネリクスとともに導入されることが 2021 年 9 月 23 日に決まりました。 3 導入の理由については次のコメントで説明されています。 関連する修正 any を型制約以外の場所でも使えるようにする修正です。 Go 仕様に any についての説明が追加されました。 ジェネリクスが有効な場合は runtime のコンパイル時に事前定義された識別子として any を出力する修正です。 事前定義された識別子である any を interface{} のエイリアスとして処理するための修正です。 builtin の godoc 4 に any と comparable を追加する修正です。 ドキュメントには any について次のように記載されています。 any is an alias for interface{} and is equivalent to interface{} in all ways. なお comparable は any と異なり型制約でしか使用できません。 comparable is an interface that is implemented by all comparable types (booleans, numbers, strings, pointers, channels, interfaces, arrays of comparable types, structs whose fields are all comparable types). The comparable interface may only be used as a type parameter constraint, not as the type of a variable. 既存コードへの影響はないのか? あくまでも事前定義された識別子であり、予約語ではないため既存コードへの影響はないようです。 例えば type any = interface{} という定義を自分で行っていたり、 any という変数を使用していてもそれは事前定義された any をシャドウイングするだけとのことです。 5 6 とはいえ Linter に怒られて CI が失敗するとかはあるかもしれません。 https://tip.golang.org/ref/spec#Interface_types ↩ https://github.com/golang/proposal/blob/135750a71111d228b1b5c552b20eacb227a663be/design/43651-type-parameters.md#the-any-constraint ↩ https://github.com/golang/go/issues/33232#issuecomment-925303192 ↩ https://pkg.go.dev/builtin@master ↩ https://github.com/golang/go/issues/33232#issuecomment-925202264 ↩ https://github.com/golang/go/issues/33232#issuecomment-925205036 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語でMentaの売上をfreeeに登録するCLI開発

この記事では教育プラットフォームであるMentaの売上情報をダウンロードし、売上情報として会計サービスfreeeのAPIを利用し登録する流れを解説したいと思います。まだ連携先として追加されていない他のECサービス・収益化が行える配信サイト等を利用している方の参考にもなるかと思います。 Mentaとはプログラミング技術などを教える人・学習する人をマッチングするサイトです。私も1年以上メンターを行っており、累計50人近くの方に学習のサポートを行っています。現在調べてみたところ、このサイトの収益情報を自動登録できる会計ソフトは存在しないようなので、APIを通じてfreeeへ取引を登録するための簡単なCLIを作成しました。 成果物はこちらに掲載しています。 freeeでの収支の入力方法を理解する Mentaは入金から30日以上経過した売上について出金の申請が行なえます。契約者毎に支払タイミングが異なり、毎月支払いが行われるので手動で入力するのはかなり手間です。売上が発生したタイミングでは会計ソフト上で売上を登録します。売上金については一旦Mentaへ銀行口座のように貯められるものと扱い、出金を行ったタイミングでMentaの口座から自分の口座に振替を行うという風に扱います。 売上に対しての手数料は20%に対して消費税を加算した22%で、出金の手数料は1回あたり300円必要です。 参考までにMenta上でダウンロードできる売上情報はこのようなCSVフォーマットでダウンロード可能です。 契約No,日付,ユーザー名,プラン名,招待割引,キャンセル,金額,手数料,売上額,出金日 11111,2021-09-24,Example User,"プラン名",,,1000,220,780,"2021-11-10 12:34:56" 22222,2021-10-24,Example User,"プラン名",,,1000,220,780, 取引入力の前に、Menta上の残高を登録するための口座を作成する必要が有ります。「決済サービス・電子マネーを登録する」→「利用しているPOSレジ・ESサイト(出店)・決済サービスが見つからない場合」と遷移することで、手動で入力が可能な空の口座が作成できます。 freeeでの収支入力の例です。 今回の例は1000円の契約に対しての売上があり、それに対して220円の手数料が運営側に差し引かれています。収入で売上高として1000円を入力し、マイナス収入として手数料の220円を差し引いています。そして、その合計金額780円と同額をMentaの口座で受け取りします。また、備考欄にてMentaでの注文IDを記述しています。 これから取引情報をAPI経由で投げ込んでいきます。一旦Web画面から入力しておくと、Developer Toolsでの通信内容などを覗くことで、APIで投げるJSONペイロードの形がわかりやすくなるのでオススメです。 freeeでOauth2認証を行う APIを呼び出すまでの手順はこちらで解説されています。開発者アカウントは無料で登録することができ、一定期間毎にデータが消去されるようです。登録が完了すると事業者IDとアクセストークンが画面に表示されます。 freee API スタートガイド https://developer.freee.co.jp/getting-started しかしながら、今回は開発者アカウントではなく個人事業主として作成した事業所でのAPI呼び出しを行いたいです。この場合はスタートガイドに記述されていない方法を利用する必要がありました。アカウント作成時に表示されたアクセストークンは一定時間で失効し、テスト環境の事業所でしか使えません。実際に本番利用するためには、Oauth2認証を利用し、ClientID,ClientSecretからアクセストークンを取得する必要があります。具体的には以下の方法があります。 サーバサイドでの標準的な方法。HTTPエンドポイントを立てて、そこにコールバックを受けてアクセストークンを取得。 組み込みブラウザ等で認証を行う際の方法。認証後の画面に直接アクセストークンが表示され、それをコピペ/自動読み取り等で認識を行う。リダイレクトURLに urn:ietf:wg:oauth:2.0:oob を指定する。 https://app.secure.freee.co.jp/developers/tutorials/3 今回は手元で動くCLIなのでどちらの方法でも構いませんが、より利便性が高くなるように1番の方法で、ローカルサーバを立ててアクセストークンを取得する方法を採用しました。以下のような流れでアクセストークンを取得することができます。’ http://localhost:9999/ でサーバを起動 ブラウザで https://accounts.secure.freee.co.jp/public_api/authorize へアクセス その際GETパラメータの redirect_uri=http://localhost:9999/ をつける freeeのサイト上でログイン等を行う http://localhost:9999/ にリダイレクトされる その際GETパラメータに code が付いている CLIが https://accounts.secure.freee.co.jp/public_api/token へGETリクエストを投げる その際GETパラメータに code をつけるとレスポンスでアクセストークンが受け取れる freeeのOauthアプリケーションの作成方法はこちらで紹介されています。 https://app.secure.freee.co.jp/developers/tutorials/2 Oauth2の設定 oauth2.Config とサーバの待受ポートを渡すことで、HTTPサーバの起動からアクセストークンの取得までを行うコードはこちらです。再利用性が良さそうなので外から使えそうな形にしました。(実際のコードでは攻撃を防ぐためのstateの検証・エラーハンドリング等を行っていますが簡略化したものを掲載しています。) oauth2.go func Oauth2AuthorizeLocalServer(config *oauth2.Config, port int) (*oauth2.Token, error) { ctx := context.Background() // redirect to authorized page url := config.AuthCodeURL(state) open.Start(url) // ブラウザが起動する // ローカルサーバの起動・認証コードの受け取り code, _ := func() (string, error) { l, _ := net.Listen("tcp", fmt.Sprintf("localhost:%d", port)) defer l.Close() quit := make(chan string) go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { query := req.URL.Query() w.Write([]byte(`auth success`)) w.(http.Flusher).Flush() quit <- query.Get("code") })) return <-quit, nil }() oauth2Token, err := config.Exchange(ctx, code) return oauth2Token, err } 利用例 func main () { oauth2Config := &oauth2.Config{ ClientID: os.Getenv("FREEE_CLIENT_ID"), ClientSecret: os.Getenv("FREEE_CLIENT_SECRET"), Endpoint: oauth2.Endpoint{ AuthURL: "https://accounts.secure.freee.co.jp/public_api/authorize", TokenURL: "https://accounts.secure.freee.co.jp/public_api/token", }, RedirectURL: "http://localhost:9999/callback", }} token, _ := Oauth2AuthorizeLocalServer(oauth2Config, 9999) log.Println(token) } 受け取った oauth2.Token 構造体にはAccess TokenやRefresh Tokenが入っているので、これを永続化して再利用すればログインを省略することができます。今回は json.Encoder を利用して secret.json に保存しました。 Goからfreee APIの呼び出し 対応APIは一部だけですが、Go言語向けのクライアントライブラリがサードパーティによって提供されています。今回はこれを使います。対応していないエンドポイントを利用したい場合は、 https://qiita.com/kamijin_fanta/items/67dfa90675f0d66c9670 で紹介したようにOpenAPI定義を利用したクライアント生成を行うのがオススメです。 READMEにも記載がありますが、Oauth2のClientID,ClientSecret,AccessToken等が有れば呼び出しが可能です。クライアント生成後には以下のような記述で取引をfreeeに登録することが出来ます。 details := []freee.DealCreateParamsDetails{ // 取引の詳細 } payments := []freee.DealCreateParamsPayments{ // 関連する支払情報 } client.CreateDeal(ctx, oauth2Token, freee.DealCreateParams{ IssueDate: "yyyy-mm-dd", Type: "expense", // 収支区分 (収入: income, 支出: expense) CompanyID: companyId, Details: details, Payments: &payments, }) Mentaの売上情報CSVをパース 先程お見せしましたが、Mentaはダッシュボードから以下のようなフォーマットのCSVを出力できます。 契約No,日付,ユーザー名,プラン名,招待割引,キャンセル,金額,手数料,売上額,出金日 11111,2021-09-24,Example User,"プラン名",,,1000,220,780,"2021-11-10 12:34:56" 22222,2021-10-24,Example User,"プラン名",,,1000,220,780, Go標準ライブラリの csv.NewReader を利用することで簡単にパースを行うことが出来ます。難しいことは行っていないのですがコードは長いのでGitHubのリンクを貼り付けておきます。 パーサ: https://github.com/kamijin-fanta/freee-menta-importer/blob/master/parser.go パーサのテスト: https://github.com/kamijin-fanta/freee-menta-importer/blob/master/parser_test.go パースにより以下のような構造体のスライスが得られます。 model.go type Sale struct { ContractNo string // 契約No Date string // 日付 UserName string // ユーザ名 PlanName string // プラン名 Price int // 金額 Fee int // 手数料 Sales int // 売上額 // ...省略 } Mentaへ取引登録する MentaのCSVから注文情報が取得できたので、あとは取引の登録をするだけです。先程作成したAPIクライアントで各種パラメータを埋めた構造体を投げます。descriptionに文字列を指定することで備考として確認が可能となるので、契約IDを指定しました。 cmd_sales.go description := fmt.Sprintf("menta-contract=%s", sale.ContractNo) details := []freee.DealCreateParamsDetails{ // 売上の収入登録 { TaxCode: 129, // sales_with_tax_10: 課税売上10% AccountItemID: 429799547, // 売上高 Amount: int32(sale.Price), Description: &description, }, // 利用手数料の支出登録 { TaxCode: 136, // purchase_with_tax_10: 課対仕入10% AccountItemID: 429799593, // 販売手数料 Amount: int32(sale.Fee) * -1, }, } payments := []freee.DealCreateParamsPayments{ { Amount: int32(sale.Sales), FromWalletableID: walletableId, FromWalletableType: "wallet", Date: sale.Date, }, } client.CreateDeal(ctx, oauth2Token, freee.DealCreateParams{ IssueDate: date, Type: "income", // 収支区分 (収入: income, 支出: expense) CompanyID: companyId, Details: details, Payments: &payments, }) リクエスト後にfreeeのWeb UIを確認すると正常に登録されていることが確認できます。 今回利用した全体のコードはこちらに掲載していますので、興味があれば御覧ください。また、MITライセンスとしましたのでコードの再利用等も歓迎します。 https://github.com/kamijin-fanta/freee-menta-importer 所感 前回のアドベントカレンダーを試した際に便利だと感じたので、今年からは契約を行い個人事業主で実際に利用するユーザとして記事を書かせていただきました。(去年の記事: https://qiita.com/kamijin_fanta/items/67dfa90675f0d66c9670 ) 数十件・数百件の売上情報を手入力するのは現実的では無いので、こういった個人でも気軽に利用できる形でAPIが提供されているのは非常に有り難いです。今後も自動入力したいデータが増えたら自作しようと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go の気になったコミット (2021 年 11 月)

golang/go の master ブランチに行われたコミットから個人的に気になったものをリストにしたものです。 前回作成したリストはこちら。 軽く説明を追加していますが勘違いや誤りがあるかもしれません。 Topic 既存の標準パッケージよりも改善された IP アドレス表現を提供する net/netip が追加されました。 新しい GC Pacer (GC をいつ動かすか決定するアルゴリズム) が実装され、デフォルトで有効になりました。 安全ではないとされている証明書の署名や TLS バージョンに対する扱いが変更されています。 Commits 2021-11-02 既存の標準パッケージよりも改善された (小さく比較可能でメモリ割り当てが発生しない) IP アドレス表現を提供する net/netip が追加されました。 1 2 既存とは異なる関数への移行が必要ですが特に UDP の送受信で恩恵が大きいようです。 記載していませんが UDP 送受信周りを最適化する変更もいくつかマージされています。 2021-11-03 ビルドされたバイナリが必要とするアーキテクチャレベルを起動時にチェックするようになりました。 3 Go 1.18 では同じ amd64 アーキテクチャでもより新しい世代で追加された命令を利用するようになりました 4 が、起動時にチェックしないと未サポートの命令を実行したタイミングで SIGILL が発生するので地味に助かる処理だと思います。 2021-11-05 新しい GC Pacer (GC をいつ動かすか決定するアルゴリズム) が実装され、デフォルトで有効になりました。 5 これにより最小ヒープサイズが 4 MiB から 512 KiB に削減されているのですが性能上の問題が見つかっておりヒープサイズは元に戻されています。 6 7 2021-11-06 netip.Addr が提供する関数のメモリ割り当てが改善されています。 この PR をきっかけに知ったのですが名前付き戻り値を使うことでメモリコピーを減らせるようです。 8 とはいえ最適化目的での利用は推奨されていません 9 し、コンパイラに最適化を頑張ってほしいところです。 SHA-1 形式で署名された証明書の検証が無効化されました。 10 TLS クライアントが要求するデフォルトの最小バージョンが TLS 1.2 になりました。 11 段階的に適用範囲を拡大していき、将来的に TLS 1.0 と TLS 1.1 のサポートを削除する予定みたいです。 2021-11-09 少し前の変更で Windows 環境でクラッシュダンプが作成できるようになった 12 のですが、条件次第で Microsoft にクラッシュダンプ (メモリの内容を含む) が自動アップロードされる 13 ため revert されました。 2021-11-13 HTTP/2 にてレスポンスボディを Close()した場合にエラーが返ってくることがある不具合が修正されています。 14 Go 1.17.3 のリグレッションとのことです。 2021-11-17 any (Go 1.18 で追加された事前定義された識別子) を使ったソースを go run ではなく go build すると失敗する問題の修正です。 15 ジェネリクスが有効な場合は runtime のコンパイル時に事前定義された識別子として any が出力されるようになりました。 2021-11-20 事前定義された識別子である any を interface{} のエイリアスとして処理する修正です。 16 これは Go の仕様にも記載されている 17 そうですが、実際には Alias ではなく Defined type として処理されていたそうです。 For convenience, the predeclared type any is an alias for the empty interface. 雑感 リストに記載していないのですが相変わらずジェネリクス関連の修正が多いです。 また、 Go 1.18 リリースに向けてドキュメント周りの修正も多数行われていました。 ジェネリクスに限らず Go 1.18 では新しく追加された便利なパッケージや関数が多いため古い Go では動かないパッケージが増えそうだなと感じています。 https://github.com/golang/go/issues/46518 ↩ https://tailscale.com/blog/netaddr-new-ip-type-for-go/ ↩ https://github.com/golang/go/issues/48506 ↩ https://github.com/golang/go/issues/45453 ↩ https://github.com/golang/go/issues/44167 ↩ https://github.com/golang/go/issues/49744 ↩ https://github.com/golang/go/commit/c5c1955077cb94736b0f311b3a02419d166f45ac ↩ https://github.com/golang/go/issues/20859 ↩ https://github.com/golang/go/wiki/CodeReviewComments#named-result-parameters ↩ https://github.com/golang/go/issues/41682 ↩ https://github.com/golang/go/issues/45428 ↩ https://github.com/golang/go/issues/20498 ↩ https://github.com/golang/go/issues/49471 ↩ https://github.com/golang/go/issues/49366 ↩ https://github.com/golang/go/issues/49619 ↩ https://github.com/golang/go/issues/49665 ↩ https://tip.golang.org/ref/spec#Interface_types ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyやってた人間がGo始めて思ったこと

この記事は、フラー株式会社 Advent Calendar 2021の6日目の記事です。 5日目の記事は @m-coder さんによる DroidKaigi2021に登壇した話 でした。 私は元々Ruby on Railsで開発をしていたのですが、最近転職しまして、Goに入門しました。 Goやり始めて「へぇ〜」と思ったことを書いてみます。 型があるとうれしい 私は静的型付けの言語をこれまであまり触ったことがなかったのですが、型がちゃんと決まってるとありがたいですね。 エディタ(VSCodeを使っています)の補完機能がバリバリ効いて、 この変数にどんな値が入っているのか この変数はどんなメソッドが使えるか このメソッドにどんな値を入れればいいか などを教えてもらえるので、サクサクコードを書き進められます。 Rubyだと「最近入力した単語」くらいしかエディタの候補に出してもらえないので、ファイルを行き来してメソッドの中身とかを確認する必要があったりしたので、そこが地味に手間だったな〜と思います。 クラスは無い Goではクラスを定義する仕組みが無いんですね。 他の言語で言うクラス的なことをやるには、struct(構造体)を使って任意の型のフィールドを持つ型を定義して、その型に対するメソッド定義を外側で(型定義とは別に)やる、みたいな流れになります。 例えばfooというstring型のフィールドと、barというint型のフィールドを持ったFooBarという型を定義したいな〜というときはこういう風に書きます。 type FooBar struct { foo string bar int } で、FooBar型のインスタンスに紐づくメソッド(Rubyで言うところのインスタンスメソッド)を定義したいな〜というときは、型定義とは別に、以下のようなメソッド定義を書きます。 func (f FooBar) SayFoo() { fmt.Sprint(f.foo) } funcキーワードの後に(f FooBar)とあるのが、「この関数はFooBar型の変数fがレシーバーになるよ」という意味で、これによりFooBar型のインスタンスでSayFoo()というメソッドを使用できるようになります。 結果として、以下のようにSayFoo()メソッドを備えたFooBar型が使用できるようになります。 func main() { f := FooBar{foo: "Woooo!", bar: 1} // FooBar型のインスタンスを生成して、`f`に代入 f.SayFoo() // `f`のメソッド`SayFoo()`を実行 } 全体のコードとしてはこうなります。 package main import "fmt" type FooBar struct { foo string bar int } func (f FooBar) SayFoo() { fmt.Print(f.foo) } func main() { f := FooBar{foo: "Woooo!", bar: 1} f.SayFoo() } ちなみに、同じようなことをRubyでやるとしたらこんな感じでしょうか。 class FooBar def initialize(attr = {}) @foo = attr[:foo].to_s @bar = attr[:bar].to_i end def say_foo() puts @foo end end foo_bar = FooBar.new(foo: "Woooo!", bar: 1) foo_bar.say_foo Goの方が型定義とメソッド定義が分離されてスッキリしてて良いね〜ということなんでしょうかね。 変数名は短くする Go界隈だと、割と短い変数名をつける風潮があるように感じます。1文字だったり略語にしたり。 Rubyだと2単語をアンダーバーでつなげた変数名とかを良く書いていた気がします。 型が明確になっているし、説明的な変数名をつけないといけないような見通しの悪いメソッドは書くもんじゃねえぞ、というノリなんでしょうかね。 まあGoに入ればGoに従えということで。 略語は全部大文字にする Goでは複数単語をつなげた名称をつけるときはキャメルケースにする風習です。 その際にIDやURLといった、略語はすべて大文字のままにしておくのが良しとされているようです。 例えばuserIDとかbaseURLみたいな感じです。 JavaScriptなんかだとuserIdとかbaseUrlみたいにしちゃいそうな気がしますが、まあGoに入ればGoに従えということで。 構造体のタグというものがある Goの構造体のフィールド定義では、型に加えて「タグ」と呼ばれる情報を付与することができます。フィールドのオプションみたいなもののようです。 例えばGoの標準パッケージであるjsonパッケージを使うときに、読み込むJSONのキーと構造体のフィールド名を関連付けるのに使われます。 こんな感じで書き、json:"id"という部分がタグです。 type User struct { ID int `json:"id"` Name string `json:"name"` } こうすることで、「JSONの"id"というキーが、User型のIDフィールドに対応しているよ〜」ということがjsonパッケージに伝えられて良い感じにやってくれるようになります。へぇ〜。 ディレクトリごとにパッケージとしてまとめられる Goではプロジェクト内にディレクトリを作成し、その中に.goファイルを配置すると、別パッケージとして扱えるようになります。(同じディレクトリに複数パッケージのソースコードを配置することはできない) また、同じパッケージのソースコード同士では、importを書かなくても別ファイルの内容を参照できます。 例えばこんな感じのディレクトリ構成になっていたとして、 . ├── main.go └── nice ├── bar.go └── foo.go nice/foo.goとnice/bar.goの一番上にはpackage niceと書いておきます。 そうするとmain.go内では、niceパッケージをimportすることで、niceパッケージで定義した型や関数をnice.HogeHogeといった形で参照できるようになります。 また、nice/foo.goとnice/bar.goの中では、同じパッケージ内なので、内容を相互に参照可能です。 割と分かりやすいので、コードの整理をしやすそうだな〜という雰囲気を感じます。 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】VsCodeで環境構築をやってみた

はじめに ブロックチェーンの勉強を行っていると、結構Go言語の話が出たり求人とかもGo言語が求められているので、 今後必要になりそうだなーと思ったので早速Udemyで講座を購入し、勉強をはじめました!! が、収録されている時からGoのバージョンが上がっていてgo getが使えなかったりして地味に環境構築に時間が取られたので、他の方にも参考になればと思い投稿することにしました。 Udemy 講座 現役シリコンバレーエンジニアが教えるGo入門+応用でビットコインのシストレFintechアプリの開発 環境 OS : Mac エディタ : VScode Go : 1.17.4 注※本記事ではVscodeのインストール方法は記載しておりません。 Goのインストール Downloadsのサイトからパッケージをダウンロードして、インストールしました。 バージョン確認してGoが無事にインストールできたことを確認! go version go version go1.17.4 darwin/amd64 Homebrewのインストール (私は既にインストール済みだったので、実際にはやっていません) Homebrewのサイトから記載されているインストール用のコマンドをコピーして、ターミナルに貼り付けて実行することでインストールできます。 下記スクリプトをターミナルで実行 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 実行時にパスワード聞かれたりしますが、ログインする時のパスワードを入れればよかったはずです。 バージョン確認してHomebrewが無事にインストールできたことを確認! brew -v Homebrew 3.3.6-72-g5096d6e ... 外部パッケージを取得 プログラムを書くときには外部パッケージを利用していくことになります。 そのためには、Gitが必要らしいので、上記でインストールしたHomebrewを使用してGitをインストールします。 brew install git バージョン確認してGoが無事にインストールできたことを確認! git --version git version 2.24.3 (Apple Git-128) godocを取得 外部パッケージが取得できることの確認としてgodocを試しに取得します。 godocとは? Go言語は、godoc というコマンドがあります。 このコマンドを使用することでgolang.orgのパッケージのドキュメントをオフラインで閲覧することが出来ます。 講座ではgo get XXXで外部パッケージ取得していましたが、私がインストールしたGoではgo getが使用できなくなっており、なんだこれってなりました。。 調べてみると、go1.17からgo getは非推奨、代わりにgo installを使用してねーとのことでした。 上記の理由から以下のコマンドでgodocを取得しました。 go install golang.org/x/tools/cmd/godoc@latest 以下のコマンドでfmtのドキュメントを確認して、godocが無事に取得できたことを確認! したいのですが、ここでも講座ではgodoc fmtでgodocを参照できるとのことでしたが、 仕様が変更されたみたいでgodoc fmtのコマンドを実行しても、以下のメッセージが表示され、godocが読み込めませんでした。 godoc fmt -bash: godoc: command not found 調べてみると、godocのCLIモードはサポート終了されていることが原因らしく、 その代わりにgo docを使用してねーとのことみたいです。 なのでgo docを使用すると長文が表示されており、どうやら無事にgodocが取得できているみたいです。 go doc fmt package fmt // import "fmt" Package fmt implements formatted I/O with functions analogous to C's printf and scanf. The format 'verbs' are derived from C's but are simpler. ... 参考 godocの代わりに go doc を推奨された件について Vscodeをインストール Vscodeのインストールについては、私は既にインストール済みであること、 インストール方法は、たくさん記事があるので本記事では割愛させていただきます。 VscodeでGoの環境を設定 まずはMarketplaceで拡張機能をインストールします。 「Go」と検索すれば一番上に表示されると思います。 拡張機能「Go」がインストールできたら、開発に必要なライブラリ(デバッグ用のライブラリ等)は別途でインストールする必要があるらしいので、実施して行きます。 ・F1で、コマンドパレットを呼び出し、「>go Install」で検索 ・「Go: Install/Update Tools」が表示されるのでクリック ・必要なものを選択し、OKを押下し、インストール完了  とりあえず私は全部にチェックを入れてインストールしました ここまで終われば一旦、設定は完了です。 Goでhello world HelloWorld 用のファイルを作成。 どこでも良いと思いますが、私は以下に作成しました。 $GOPATH/src/hello_world.go GOPATHの場所はgo envで確認できます。 hello_world.goの内容はこんな感じ hello_world.go package main import "fmt" func main() { fmt.Println("Hello world!") } F5でデバッグ実行を行うと、以下のエラーが起きて実行できませんでした。 なにかする度につまづいている気がします。 go: go.mod file not found in current directory or any parent directory; see 'go help modules' (exit status 1) go.modがないんだなーというのはわかりました。 調べた結果modファイルを作ってあげればいいみたいです。 $\tiny{なんで必要とか細かいところまでは今後調べます!!}$ Vscodeでターミナルを開いてgo mod init [プロジェクト名]のコマンドを実行して作成します。 src $ go mod init hello_world go: creating new go.mod: module hello_world go: to add module requirements and sums: go mod tidy go.modというファイルができました。 中身はこんな感じですね。依存関係が追加されていくとかどうとか・・ go.mod module hello_world go 1.17 go.modファイルが無事作成されたことが確認できたので、再度 F5でデバッグ実行を行うと,無事にHello world!が出力されました! 参考 VSCode を使って Go のデバッガーが動くまでの設定をした VSCodeでGoの開発ができるようにする VSCodeでGo言語の開発環境を構築する おわりに Udemyの講座と違う部分もあり、少し時間を使いましたが無事にVsCodeで実行することが出来ました。 長くなってしまいましたが、誰かのお役に立てれば幸いです。 細かい部分の調査ができていなかったり、記載ミス等ありましたら優しく教えて欲しいです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Tour of Goのスライスの挙動で引っかかってたら、色々分かった話

目次 はじめに 対象の読者 この記事で伝えたいこと スライスとは スライスのスライシング どこで引っかかったか スライスの代入は元の配列のポインタを渡す スライシングの仕様 まとめ 参考 はじめに この記事は カオナビ Advent Calendar 2021 5日目です。 初めまして!カオナビに新卒で入って現在2年目の@summer_shineです。 最近、自分が新しく所属することになったチームで、初めてGoを触ることになりました。 その際に、A Tour of Go (Goのチュートリアルができる場所)のスライスの項目でそんな挙動するの!?ってなり、引っかかった箇所があったので、それについて調べてみました。 自分と同じように、つまづいた人がいれば参考にして頂けると嬉しいです。 というわけで、5日目はGoのスライスの話をしようと思います! 対象の読者 Goの初学者 スライスについて学び始めた人 Tour of GoのSlice length and capacityの挙動が良く分からなかった人 ←ピンポイント この記事で伝えたいこと 重要:スライスの代入について 重要;スライスのインデックスの仕様について スライスについて スライスのスライシングについて スライスとは スライスについての記事は調べると沢山出てきますが、自分が引っかかったコードについて理解してもらう為にも、最低限必要なスライスの知識については触れておきます。 スライスとはGoにおける可変長の配列のようなものです。 スライスは配列をラップ(配列の機能を含みつつ、別のものとして提供)してできています。 Goの配列は固定長です。 普段コードを書く際には利便性が高いという理由で、スライスの方が使われることが多いです。(後からスライスに要素を追加したりできるので) スライスの宣言の仕方は簡単で、配列宣言時の要素数を省略することで、使うことができます。(make関数を使うことでも作成可能) // 配列 favoritePokemon := [3]string{"カイオーガ", "ウルガモス", "シャンデラ"} // スライス favoritePokemon := []string{"カイオーガ", "ウルガモス", "シャンデラ"} // make([]T, len, cap)を使ったスライスの宣言。 T、len、capにはそれぞれ型、長さ、容量を指定 favoritePokemon := make([]string, 3, 3) スライスはポインタ(ptr)と長さと(len)容量(cap)で構成されています。 ptr : 元となる配列のアドレスを持つ len : スライスが参照している配列の要素の数 cap : 元になる配列内の要素の数。ptrによって参照される要素から数えた数。 スライスのスライシング スライスはスライシングをすることでスライスの値を切り出して渡すことができます。 スライスシングの指定の仕方はs[low:high]でスライスに対してインデックスを指定することで取得できます。 // スライス favoritePokemon := []string{"カイオーガ", "ウルガモス", "シャンデラ"} // 炎タイプだけスライシング   fireType := favoritePokemon[1:3]// ["ウルガモス", "シャンデラ"] どこで引っかかったか 次に、まず自分がTour of Goのスライスの項目の挙動で引っかかった箇所を見てみたいと思います。 上記の項目のスライスの宣言とスライシングが分かっていれば、該当の箇所は読めると思います。(コードはTour of Goのままですが、コメントを一部編集しています) package main import "fmt" func main() { // スライスを代入 s := []int{2, 3, 5, 7, 11, 13} printSlice(s) // [2 3 5 7 11 13] // スライスの[low:high]を指定してスライシング // この場合は要素数0から0、つまり空のスライスをsに再代入 s = s[:0] printSlice(s) // [] // 問題の箇所 // 先ほど、空を代入したスライスの0番目から4番目までを代入!? s = s[:4] printSlice(s) // [2 3 5 7] ← !?!?!?!? // 再代入したものをスライシング s = s[2:] printSlice(s) } func printSlice(s []int) { // len()とcap()はスライスを受け取り、len()はスライスの長さ、cap()は容量を返す fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } 実行結果 len=6 cap=6 [2 3 5 7 11 13] len=0 cap=6 [] len=4 cap=6 [2 3 5 7] len=2 cap=4 [5 7] 実行結果では確かに空が出力されていたのに、そのスライスに対して[:4]とスライシングすると最初に代入していた値が取れています。 自分は最初、何が起こったんだ・・・となってましたが、調べたら謎が解けたので次以降の項目で、この現象がなぜ起こったのかを順を追って話したいと思います。 スライスの代入は元の配列のポインタを渡す スライスを変数に代入するときに、その変数に渡されるスライスの値(例:[2, 3, 5, 7])は値のコピーを渡すのではなく、元の配列のポインタを渡しています。 その為、スライスを代入した状態でそれぞれの値が指し示すアドレスを表示してみると、同じものが出力されます。 同じポインタを持っていると言うことはs2のを値を変えるとs1の値も変わります。 // 配列へptr、len:6、cap:6をもつスライスを代入 s1 := []int{2, 3, 5, 7, 11, 13} // [2 3 5 7 11 13] // スライシングして再代入 s2 := s1[:1] // [2] // アドレスを表示すると同じ場所を指している fmt.Printf("%p \n", s1) // 例: 0xc0000b6030 fmt.Printf("%p \n", s2) // 例: 0xc0000b6030 つまり、自分が引っかかった場所である以下のGo tourのコードは、値としては空の配列であるが、ポインタが指す先は元の配列のアドレスを持っていた、という事になります。 s := []int{2, 3, 5, 7, 11, 13} // スライスを代入 printSlice(s) // [2 3 5 7 11 13] // スライシングしつつ、元の配列のポインタも渡している s = s[:0] printSlice(s) // [] 再代入されたsが、ポインタとしては元の配列を指していることは分かったと思うのですが、空のスライスに対してs = s[:4]とスライシングが出来てしまう事には、まだ疑問を持っている人もいるかもしれません。 (少なくとも自分は、空からなんで値取れるんだ・・・ってなってました) ちなみに、スライスの複製をしたい場合はcopy()を使って作ることがきます。 copy()だとそれぞれのスライスが別々のポインタを持つので、片方を変更しても、もう片方が変わるという事にはなりません。 // スライス s1 := []int{1, 2, 3, 4, 5, 6} // 複製したいスライスの代入先の初期化 s2 := make([]int, len(s1)) // copy(複製の代入先、複製したいスライス) copy(s2, s1) スライシングの仕様 スライシング(s[low:high])でスライスの値を切り出せると言う話をしたと思いますが、その際に指定できるインデックス(low:highの数値 )の範囲は、要素の数len(s)ではありません。 スライスの場合、指定できるインデックスの上限は容量cap(s) 、つまり元になる配列のptrによって参照される要素から数えた値によります。 For slices, the upper index bound is the slice capacity cap(a) rather than the length. (和訳)スライスの場合、インデックスの上限は、長さではなくスライスの容量cap(a)となります。 The Go Programming Language Specification - go.dev より スライスの代入は元の配列のポインタを渡すこと スライシングのインデックスの範囲は容量cap(s) によること 以上の2つを踏まえてもう一度go tourのコードを見ていきましょう。(一部省略) // スライスを代入 s := []int{2, 3, 5, 7, 11, 13} // len=6 cap=6 [2 3 5 7 11 13] // 空のスライスをsに再代入 s = s[:0]// len=0 cap=6 [] // 問題の箇所 s = s[:4] 5行目でs = s[:0]の再代入により、sは基の配列のptr(指し示す先は[])、len ([ ]なので0)、cap (ptrからみた元の配列の要素数なので6)を持っている状態です。 8行目のs = s[:4] は、再代入されたsのlen は0であるが、スライスのインデックスの範囲はlenではなくcapを見るので、ポインタが指し示す基の配列に対して[:4] というスライシングを行ったという事になります。 その為、空の配列から値を取っているように見えた。という事になります。 これでスライスの挙動で引っかかっていたものが解消されました(チョースッキリ!!) ビミョーに話がズレますが、スライシングで追加で気をつけて欲しいことは、インデックスを省略した場合です。 // スライスを代入 s := []int{2, 3, 5, 7, 11, 13} // len=6 cap=6 [2 3 5 7 11 13] // 空のスライスをsに再代入 s = s[:0] // len=0 cap=6 [] fmt.Println(s4[:cap(s)])// [2 3 5 7 11 13] fmt.Println(s4[:])//  []   [2 3 5 7 11 13]とはならない [:]は全要素を取り出したい時に使ったりしますが、この時に省略したlowとhighはそれぞれlow = 0、high = len(s) として扱われます。その為、5行目で空を再代入したsはlenが0な為、[:]で取り出しても空のままとなります。 まとめ スライスの代入は配列のポインタを渡す! スライシングのインデックスの上限はlenではなくcap! スライスの再代入の挙動には気を付けよう! 今回初めて記事を書く事になって色々と大変でしたが、学びがありました。 気になった点などあれば、コメントよろしくお願いします! 参考 https://go.dev/blog/slices-intro https://go.dev/ref/spec#
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む