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

具体例で理解するGo言語のデバッグツールDelveの利用方法

こちらに移動しました。 https://nishinatoshiharu.com/go-delve-usage/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

具体例で理解するGo言語のデバッグツールdelveの利用方法

Go言語のデバッガはDelveとGDBが人気です。 GDBのドキュメントには以下のような記載があり、Delveの利用を推奨しています。 Note that Delve is a better alternative to GDB when debugging Go programs built with the standard toolchain. (Delve は、標準ツールチェーンでビルドされた Go プログラムをデバッグする際に、GDB よりも優れた代替手段であることに注意してください。) 今回はDelveを利用したGo言語のデバッグ方法について紹介します。 Delveの主なコマンド一覧 デバッグをする際によく利用するDelveのコマンドをピックアップしました。 内容 コマンド ショートコード 実行 dlv debug [ファイル名] N/A 再実行 restart r 終了 exitquit q ブレークポイントの作成 break [ファイル名]:[行数] b [ファイル名]:[行数] ブレークポイントの確認 breakpoints bp 次のブレークポイントへ移動 continue c 次の行へ移動 next n ステップイン step s ステップアウト stepout N/A コードの表示 ls [関数名]ls [パッケージ].[関数名] N/A ローカル変数の表示 locals N/A 変数の表示 print [変数] p [変数] 変数の値の変更 set [変数] = [値] N/A スタックトレースの表示 stack bt スタックトレース上でデバッグを実行 frame [番号] [コマンド] N/A ヘルプの表示 help h Delveにはそのほかにも多数のコマンドが用意されています。 詳細はdelve/Documentation/cli/README.mdをご参考ください。 Delveのインストール方法 Delveのインストール手順はdelve/Documentation/installation/README.mdに記載されています。 $ go install github.com/go-delve/delve/cmd/dlv@latest ### dlvコマンドが存在していればOK $ which dlv /go/bin/dlv Delveを利用した具体的なデバッグ方法 以下のコードを例に、Delveを利用した具体的なデバッグ方法について紹介します。 main.go package main import "fmt" func greeting(name string) string { beginning := "Hello! " message := beginning + name return message } func main() { name := "Bob" message := greeting(name) fmt.Println(message) } Delveの起動と終了 dlv debug [ファイル名]でデバッグモードになります。Delveを終了する場合はqを入力します。 $ dlv debug main.go (dlv) (dlv)q ブレークポイントを追加し、移動する bでブレークポイントを作成し、cでブレークポイントへ移動できます。 rでブレークポイントを残したままデバッグの再実行ができます。 $ dlv debug main.go (dlv) b main.go:14 Breakpoint 1 set at 0x4b6964 for main.main() ./main.go:14 (dlv) c 9: return message 10: } 11: 12: func main() { 13: name := "Bob" => 14: message := greeting(name) 15: 16: fmt.Println(message) 17: } (dlv) r Process restarted with PID 40673 (dlv) c > main.main() ./main.go:14 (hits goroutine(1):1 total:1) (PC: 0x10cb964) 9: return message 10: } 11: 12: func main() { 13: name := "Bob" => 14: message := greeting(name) 15: 16: fmt.Println(message) 17: } デバッグ中にソースコードを確認する lsでデバッグ中にソースコードの確認ができます。 $ dlv debug main.go (dlv) b main.go:14 (dlv) c 9: return message 10: } 11: 12: func main() { 13: name := "Bob" => 14: message := greeting(name) 15: 16: fmt.Println(message) 17: } (dlv) ls main.greeting # greetingメソッドの表示 Showing /Users/301184/work/delve/main.go:5 (PC: 0x10cb873) 1: package main 2: 3: import "fmt" 4: 5: func greeting(name string) string { 6: beginning := "Hello! " 7: message := beginning + name 8: 9: return message 10: } (dlv) ls greeting # greetingメソッドの表示 Showing /Users/301184/work/delve/main.go:5 (PC: 0x10cb873) 1: package main 2: 3: import "fmt" 4: 5: func greeting(name string) string { 6: beginning := "Hello! " 7: message := beginning + name 8: 9: return message 10: } (dlv) ls # 現時点(ブレークポイント)のコードを表示 > main.main() ./main.go:16 (PC: 0x10cb98a) 11: 12: func main() { 13: name := "Bob" 14: message := greeting(name) 15: => 16: fmt.Println(message) 17: } ブレークポイント時点での変数の中身を確認する localsで値の確認ができます。 たとえば、14行目ではnameの値が取得できます。 $ dlv debug main.go (dlv) b main.go:14 (dlv) c 9: return message 10: } 11: 12: func main() { 13: name := "Bob" => 14: message := greeting(name) 15: 16: fmt.Println(message) 17: } (dlv) locals name = "Bob" nで次の行へ移動すると、nameと、14行目でセットされたmessageが表示されます。 (dlv) n > main.main() ./main.go:16 (PC: 0x10cb98a) 11: 12: func main() { 13: name := "Bob" 14: message := greeting(name) 15: => 16: fmt.Println(message) 17: } (dlv) locals name = "Bob" message = "Hello! Bob" (div) p name "Bob" ステップインとステップアウト sでステップインできます。 たとえば14行目でsを実行すると、14行目で実行されているgreetingメソッドのデバッグができます。 $ dlv debug main.go (dlv) b main.go:14 (dlv) c 9: return message 10: } 11: 12: func main() { 13: name := "Bob" => 14: message := greeting(name) 15: 16: fmt.Println(message) 17: } (dlv) s > main.greeting() ./main.go:5 (PC: 0x10cb873) 1: package main 2: 3: import "fmt" 4: => 5: func greeting(name string) string { 6: beginning := "Hello! " 7: message := beginning + name 8: 9: return message 10: } stepoutでステップアウトします。 (dlv) ls > main.greeting() ./main.go:5 (PC: 0x10cb873) 1: package main 2: 3: import "fmt" 4: => 5: func greeting(name string) string { 6: beginning := "Hello! " 7: message := beginning + name 8: 9: return message 10: } (dlv) stepout > main.main() ./main.go:14 (PC: 0x10cb976) Values returned: ~r1: "Hello! Bob" 9: return message 10: } 11: 12: func main() { 13: name := "Bob" => 14: message := greeting(name) 15: 16: fmt.Println(message) 17: } ブレークポイントから次のブレークポイントへ移動する cでブレークポイントを移動できます。 (dlv) b main.go:14 (dlv) b main.go:16 (dlv) c > main.main() ./main.go:14 (hits goroutine(1):1 total:1) (PC: 0x10cb964) 9: return message 10: } 11: 12: func main() { 13: name := "Bob" => 14: message := greeting(name) 15: 16: fmt.Println(message) 17: } (dlv) c > main.main() ./main.go:16 (hits goroutine(1):1 total:1) (PC: 0x10cb98a) 11: 12: func main() { 13: name := "Bob" 14: message := greeting(name) 15: => 16: fmt.Println(message) 17: } スタックトレースを利用してデバッグする btでスタックトレースを表示し、frameで実行したいコマンドと位置を決定します。 $ dlv debug main.go (dlv) b main.go:9 (dlv) c > main.greeting() ./main.go:9 (hits goroutine(1):1 total:1) (PC: 0x10cb8cd) 4: 5: func greeting(name string) string { 6: beginning := "Hello! " 7: message := beginning + name 8: => 9: return message 10: } 11: 12: func main() { 13: name := "Bob" 14: message := greeting(name) (dlv) bt 0 0x00000000010cb8cd in main.greeting at ./main.go:9 1 0x00000000010cb976 in main.main at ./main.go:14 2 0x000000000103b6e3 in runtime.main at /usr/local/Cellar/go/1.16.3/libexec/src/runtime/proc.go:225 3 0x000000000106eb01 in runtime.goexit at /usr/local/Cellar/go/1.16.3/libexec/src/runtime/asm_amd64.s:1371 (dlv) frame 1 locals # ./main.go:14 で localsを実行 name = "Bob" さいごに 認識違いや補足があればコメントいただけると助かります。 参考 delveでGolangのデバッグ Golangのデバッガdelveの使い方
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】Search algorithms (探索アルゴリズム)

Goでプログラミングの基礎を学ぶシリーズ スクールでは教えてくれないプログラミングの基礎、データ構造とアルゴリズムをGoで学んでいくシリーズです。 そのデータ構造がどのようなものであるかは、理解を助けてくれるサイトを紹介しつつ、簡単に説明に留めさせていただきます。(ご自身でも調べてみてください!) 筆者自身、Exerciseに取り組みながら理解を深めていったので、コードの解説を中心に記事を書いていきたいと思います。 タイトル #0 はじめに (環境構築と勉強方法) #1 Pointer, Array, String (ポインタと配列と文字列と) #2 File operations (ファイル操作) #3 Linked List (連結リスト) #4 Stack & Queue (スタックとキュー) #5 Search algorithms (探索アルゴリズム) ☜ here #6 Tree (木構造) #7 Sorting algorithms (ソートアルゴリズム) #8 String pattern matching algorithms (文字列探索アルゴリズム) 今回は#5 Search algorithms (探索アルゴリズム)です。 Search algorithms google.comやyahoo.comを使って調べ物をしたり、データベースからデータを抽出したりする場合に、探索(検索)をすると思います。 その探索をするためのアルゴリズムは一つではありません。 今回は代表的な探索アルゴリズムを扱っていきます。 Liner Search (線型探索) Sequential Searchとも呼ばれる探索アルゴリズムです。 先頭から順番にデータを比較していき、探索したい値が見つかったら探索を終了するというアルゴリズムです。 一番シンプルで直感的ですね。 package main import ( "fmt" ) func linerSearch(numbers []int, target int) int { for i, n := range numbers { if n == target { fmt.Printf("%d is found: %d\n", target, i) return i } } return -1 } func main() { numbers := []int{6, 86, 24, 56, 1, 33, 68, 9, 77} i := linerSearch(numbers, 86) if i < 0 { fmt.Println("target not found") } i = linerSearch(numbers, 9) if i < 0 { fmt.Println("target not found") } i = linerSearch(numbers, 10) if i < 0 { fmt.Println("target not found") } } ☟ 出力結果 86 is found: 1 9 is found: 7 target not found Liner Search の問題は、探索の効率があまり良くないことです。 時間計算量が $O(n)$ となります。 効率を良くする手法として、 Self Organized List があります。 Self Organized List 探索自体は、 Liner Search と変わりないですが、探索したい値が見つかった場合に、その要素をデータの先頭に移動させます。 そうすると、次回以降、同じ値を探索しようとしたときには、以前よりも先に比較が行われるため、探索にかかる時間が短くなります。 これは、一度探索された値は、再度探索される可能性が高い場合に有効です。 反対に、一度探索された値は、再度探索される可能性が低い場合には、探索された要素をデータの最後尾に移動させるということも考えられます。 ※ データ構造によっては、要素を移動させるという動作自体が非常に遅い場合もあるので、注意が必要です。 参考: Self Organizing List | Set 1 (Introduction) Binary Search (二分探索) すでにソートされている配列に対する探索アルゴリズムの一つです。 同一の値がないソート済みの配列の中央にある値と、探索したい値との大小関係を用いて、探索したい値が中央の値の右にあるか、左にあるかを判断して、探索したい値が含まれないほうは比較をしません。 Liner Searchでは先頭から順に比較をしていくため、 31 を探すためには 8 回比較をしなくてはなりませんが、 Binary Search では 4 回で済んでいます。 時間計算量で表すと $O(logn)$ となり、 Liner Search よりも効率が良いことが期待できます。 ? Exercise 0~99が昇順にソートされている配列から、入力した値を探索するアルゴリズムを、Binary Search で実装してみましょう。 何回の比較で数値を見つけ出すことができたかも出力しましょう。 これまで扱っていない、 Recursive function (再帰関数) を使う必要がありますので、わからないよ!という方は、以下をご参考ください。 ☟ 解答例 package main import "fmt" func binarySearch(slice []int, low int, high int, target int, count int) (int, int) { if len(slice) < 1 || target < slice[0] || slice[high] < target { return -1, count } mid := (low + high) / 2 // 中央の index を定義 count++ // 比較回数をプラス if slice[mid] == target { return mid, count } if low >= high { return -1, count // これ以上探索できるデータがないので return } if slice[mid] > target { return binarySearch(slice, low, mid-1, target, count) } if slice[mid] < target { return binarySearch(slice, mid+1, high, target, count) } return -1, count } func main() { // 0~99 の slice を定義する slice := []int{} for i := 0; i < 100; i++ { slice = append(slice, i) } // 探索する数字を入力 var target int fmt.Printf("input target number ▶︎ ") fmt.Scanf("%d", &target) count := 0 // 探索する数字が見つかるまでに、何回比較が行われたかをカウントする変数を定義 index, count := binarySearch(slice, 0, len(slice)-1, target, count) if index < 0 { fmt.Printf("target not found") } else { fmt.Printf("index: %d, count: %d", index, count) } } ☟ 出力結果 input target number ▶︎ 19 index: 19, count: 7 input target number ▶︎ 100 target not found slice[mid] > target と slice[mid] < target で Recursive function (再帰関数) を使っています。 19 を探索する例でいくと、以下のようになります。 まず、slice: 0~99の配列, low: 0, high: 99, target: 19, count: 0が binarySearchに渡る mid := (0 + 99)/2 で 49 となる slice[49] == 49 なので、 49 > 19 となり、 slice, low: 0, high: 48, target, count: 1 が binarySearch の引数となる (以下、sliceとtargetは変わらないため省略) mid == 24, slice[mid] > target となり、 low: 0, high: 23, count: 2 が binarySearch の引数となる mid == 11, slice[mid] < target となり、 low: 12, high: 23, count: 3 が binarySearch の引数となる mid == 17, slice[mid] < target となり、 low: 18, high: 23, count: 4 が binarySearch の引数となる mid == 20, slice[mid] > target となり、 low: 18, high: 19, count: 5 が binarySearch の引数となる mid == 18, slice[mid] < target となり、 low: 19, high: 19, count: 6 が binarySearch の引数となる mid == 19, slice[mid] == target となり、 index: 19 と count: 7 が return される Recursive function は次のデータ構造とアルゴリズムである Tree でよく使いますので、理解しておく必要がありますね。 おわりに Exerciseの解答例はあくまで例なので、もっといい書き方あるよ!という方、ぜひコメントをお寄せください! 説明についても、筆者自身が初心者であるため、ご指摘や補足は大歓迎でございます。 株式会社Link Sportsでは、あらゆるスポーツを楽しむ人たちに送る、チームマネジメントアプリを開発しています。 未経験でも経験豊富なエンジニアの方でも、スポーツ好きなら活躍できる環境があります! 絶賛エンジニア募集中です! Wantedly ▶︎ https://www.wantedly.com/projects/324177 Green ▶︎ https://www.green-japan.com/job/82003 次回は、データ構造とアルゴリズム#6 Tree (木構造)です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

goimportsをインストールし、Go言語のコードをフォーマットする手順

goimportsとはGo言語の準標準パッケージ(サブレポジトリ)であるGo Toolsに内包されている静的解析ツールです。 goimportsの機能は以下の通りです。 不要なパッケージのimportを削除 必要なパッケージのimportを追加 gofmtのルールに沿ったコードのフォーマット goimportsはパッケージのインポートを修正するだけでなく、Go言語の標準パッケージであるgofmtのルールでコードを修正します。1 そのため、goimportsはgofmtの上位互換ツールといえます。 今回はgoimportsの利用方法について紹介します。利用するGoのバージョンは1.16.3です。 下準備: 『$GOPATH/bin』をパスに追加する goimportsコマンドを実行するためには$GOPATH/binをパスに追加する必要があるので、シェルの設定ファイルを修正しておきます。 ### GOPATHの確認 $ go env | grep GOPATH GOPATH="~/go" .zshrc export GOPATH=$HOME/go export PATH=$GOPATH/bin:$PATH # 重複パスを登録しない typeset -U path PATH goimportsのインストール goimportsのバイナリファイル(実行ファイル)をインストールします。 $ go install golang.org/x/tools/cmd/goimports@latest $GOPATH/binにgoimportsがインストールされていればOKです。 $ ls $GOPATH/bin goimports 動作確認 以下のようなサンプルコードに対してgoimportsを実行してみます。 main.go package main import ( "fmt" "os" ) // osはimport不要 func main() { fmt.Println("goimports") // 本来はインデントが必要 } ### goimportsでmain.goのコードをフォーマットする $ goimports -w main.go # -w: 修正した内容で上書き goimportsを実行すると、以下のようにimport文とコードのフォーマットが行われます。 import ( "fmt" - "os" ) // osはimport不要 func main() { - fmt.Println("goimports") // 本来はインデントが必要 + fmt.Println("goimports") // 本来はインデントが必要 } さいごに 認識違いや補足があればコメントいただけると助かります。 https://pkg.go.dev/golang.org/x/tools/cmd/goimports ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go Modulesを利用してローカル環境でGo言語を実行する手順

2021年5月現在、Go言語のパッケージ管理はGo Modulesが標準となっています。 今回はGo Modulesを利用してGo言語を実行する手順について紹介します。 ローカル環境で初めてGo言語を実行する人向けの記事となっています。 今回利用するGoのバージョンは1.16.3です。 作業ディレクトリの作成・移動 まずは作業ディレクトリの作成と移動をします。 $ mkdir go-example && cd $_ Go Modulesの初期化 go mod init [モジュール名]でGo Modulesの初期化をします。 モジュール名にはモジュールの公開パスを指定するのが正確ですが、公開前提でなければパスでなくても問題ありません。 ### サンプルコードなどであれば以下のような感じでOK $ go mod init go-example ### 公開前提の場合はモジュールのパスを指定する $ go mod init github.com/nishina555/go-example 以下のようなgo.modが作業ディレクトリに作成されればOKです。 go.mod module go-example go 1.16 必要なパッケージをインストールする たとえば以下のようなmain.goを作成したとします。このファイルを実行するにはrsc.io/quoteが必要です。 main.go package main import ( "fmt" "rsc.io/quote" ) func main() { fmt.Println(quote.Hello()) } importに記載されたパッケージはgo getもしくはgo mod tidyでインストールします。 コマンドによって実行される内容は以下の通りです。 コマンド 実行内容 go get [パッケージ名] 指定したパッケージのインストール go get importに記載されたパッケージのインストール go mod tidy importに記載されたパッケージのインストール。 不要なパッケージの削除 パッケージをインストールするとgo.modおよびgo.sumに情報が反映されます。 ### パッケージのインストール $ go mod tidy ### パッケージがインストールされると go.sumが作成される $ ls go.mod go.sum main.go ### go.modに rsc.io/quote の情報が追加されている $ cat go.mod module go-example go 1.16 require rsc.io/quote v1.5.2 ### go.sumに rsc.io/quote をインストールした際のチェックサム情報が追加されている $ cat go.sum golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y= rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0= rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= モジュールの依存するパッケージ一覧はgo list -m allやgo mod graphで確認できます。 ### 依存パッケージ一覧 $ go list -m all go-example golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c rsc.io/quote v1.5.2 rsc.io/sampler v1.3.0 ### 依存関係の表示 $ go mod graph go-example rsc.io/quote@v1.5.2 rsc.io/quote@v1.5.2 rsc.io/sampler@v1.3.0 rsc.io/sampler@v1.3.0 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c go getやgo mod tidyによってインストールされたパッケージは$GOPATH/pkg/modに保存されます。 ### golang.org や rsc.io が作成されている $ ls $GOPATH/pkg/mod/ cache golang.org rsc.io Goファイルのコンパイルと実行 go buildでGoファイルのコンパイルをし、作成されたバイナリファイルを実行します。 ### コンパイルしてバイナリファイル(main)を作成 $ go build main.go ### バイナリファイルを実行 $ ./main Hello, world. 以下のようにするとワンライナーでコンパイルと実行ができます。 $ go build main.go && ./main Hello, world. go runを利用すると、バイナリファイルを作成せずに直接Goファイルの実行ができます。 ただしgo runはgo buildとは異なり、importされたパッケージしか読み込まないので注意が必要です。 $ go run main.go Hello, world. 参考: go buildによるパッケージの自動インストールについて Go 1.15まではgo buildでビルドだけでなく、パッケージのインストールも行われました。 しかし、Go 1.16からはgo buildによるパッケージのインストールが廃止されました。 ですので、パッケージのインストールが必要なGoファイルを実行する場合、Go 1.16では『go mod tidy or go get』 → 『go build』という手順になります。 まとめ: GoModulesを利用したGo開発の手順 『go mod init』でGo Modulesの初期化 実装をする 『go get』もしくは『go mod tidy』で必要なパッケージをインストール パッケージが不要になったら『go mod tidy』で削除 『go build』でコンパイル後、バイナリファイルを実行。あるいは『go run』でコンパイル&実行 認識が間違っていたら指摘していただけると助かります。 参考 Go1.16からの go get と go install について Go言語の依存モジュール管理ツール Modules の使い方 go mod init <ここには何を書く?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

golangのdeferでのエラー処理方法

はじめに golangci-lintで、こんなエラーでた。deferでエラーを返す関数をよんで、エラーを無視してたからみたい。 Error return value of `t.Disconnect` is not checked (errcheck) 修正前 こんな感じでやってた。 ローカルではtest通ってたけど、githubのCIでこけた。 もう、エラー無視してくれたらいいからって気分ではある。 func (t *hoge) hogeFunc(arg []byte) (hogeError error) { ごにょごにょ if err := t.Connect(); err != nil { return err } defer t.Disconnect() ごにょごにょ } func (t *hoge) Disconnect() error{ ごにょごにょ } 修正後 調べると、無名関数にして、名前付き戻り値にしたエラーに代入したらよいみたい。main()内なら、log.Fatal()とかよんだらいいのかも。 deferのあとで、return errしてるところもあったので、そのまま代入しちゃうとdeferのあとでエラーがでても握りつぶしちゃうから、t.Disconnect()がエラーのときだけ、代入した。deferのあとでエラーがあると、上書きしちゃうから、ほんとはappend的なことをしたほうがいいんやろうけど、サボった。 func (t *hoge) hogeFunc(arg []byte) (hogeError error) { ごにょごにょ if err := t.Connect(); err != nil { return err } defer func() { err := t.Disconnect() if err != nil { hogeError = err } }() ごにょごにょ } おわりに deferで、エラーあったときどうなるんやろうと思ってたけど、CI通らなかったので真面目に調べて、多少すっきりした。 ただ、自信はないので、変なところあったら、指摘お願いしたいです。 参考 【Go】 deferに渡した関数内のエラーを呼び出し元に返す - Qiita Deferred Cleanup, Checking Errors, and Potential Problems -- Bleve
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む