- 投稿日:2019-04-17T23:09:24+09:00
GO1.11.9のコンテナでgo testしようとするとexec: "gcc": executable file not found in $PATH と怒られる
Goの公式DockerImageでgo testしたいのに
exec: "gcc": executable file not found in $PATH
と怒られる問題。ググって下記にたどり着く
https://github.com/golang/go/issues/27303
goenv
で環境変数を確かめてみるとCGO_ENABLED="1"
になっていた。
export CGO_ENABLED=0
でこいつを無効にしてやるとgo test
が通るようになった。
- 投稿日:2019-04-17T20:46:05+09:00
Windows10 GO言語環境構築してみた
はじめに
今回はWindows用インストーラーからダウンロードしようとしたが.msiが何故か動かなかったため、zipファイルから環境構築することにした。
対象者
Windows10でGo言語環境構築したい人
設定環境
Windows10 64-bit
インストールするバージョン:go1.12.41.ダウンロード
C:に(C:\Go)となるようにGoフォルダを作る。
「公式サイト」からgo1.12.4.windows-amd64.zipをダウンロード。ダウンロードしたzipを展開し、中のデータを(C:\GO)のフォルダに移動させる。
例えば、binフォルダだと、(C:\Go\bin)となるはずである。2. PATHを通す
「システム」→「システムの詳細設定」→「環境変数」から以下のようにユーザー環境変数にGOPATHを新規」ボタンから追加。
次に、ユーザー環境変数のPathの欄を選択し「編集」ボタンから編集する。「新規」ボタンから「%GOPATH%bin」を追加。4.確認
PowerShellで「go version」で以下のように表示できれば正常にインストールできている。
参考サイト
「婿入りエンジニア、ブログ書くーGolang開発環境構築 – Windows10 / VisualStudioCode」
「Windows10でGO言語環境の構築」最後に
最後まで閲覧いただきありがとうございました。まだ、知らないことが多いので不備等ありましたらご教授のほどよろしくお願いいたします。
- 投稿日:2019-04-17T16:10:55+09:00
Excelの全角数値を半角数値に変換する
経緯
前職SIerの自称・技術力高い上司(50代)が、顧客から「全角数値を半角数値に変換できないか?」という要望に対して、「技術的に難しい」と回答したらしい。
自称・技術力高い上司が「全角から半角に変換できない」というからには、おそらく難しいのだろうと思ったのであるが、(不慣れな)Go言語を用いて対応可能かを検証してみた。
やること
同じフォルダにエクセル(.xlsxのみ)を保存して実行すると、copy_***.xlsxというファイルが作成され、全角数値だった箇所のみが半角数値に変換される
ソース
main.gopackage main import ( "fmt" "io/ioutil" "os" "path" "regexp" "strings" "time" "github.com/tealeg/xlsx" ) func main() { dt1 := time.Now() var files []string files = GetFiles() // カレントディレクトリのエクセルを取得 for _, file := range files { fmt.Println("ファイル名", file) copyfile := "copy_" + file err := os.Link(file, copyfile) // ファイルのコピー if err != nil { fmt.Printf(err.Error()) } excel, err1 := xlsx.OpenFile(copyfile) // コピーしたファイルを開く if err1 != nil { fmt.Printf(err1.Error()) } for _, sheet := range excel.Sheets { fmt.Println("シート名", sheet.Name) for _, row := range sheet.Rows { for _, cell := range row.Cells { v := cell.String() val := ZenkakuToHnakaku(v) // 変換不要なセルもあるが、とりあえず全て書き換え // fmt.Println(val) cell.Value = val //cell.Value = fmt.Sprintf(val) } } } // ファイルを保存 err = excel.Save(copyfile) if err != nil { fmt.Printf(err.Error()) } } dt2 := time.Now() fmt.Println(dt2.Sub(dt1)) //Excelファイル変換時間 } func GetFiles() []string { dir, _ := os.Getwd() //カレントディレクトリ //dir := "/home/.../" //フォルダを直接指定する場合 files, err := ioutil.ReadDir(dir) if err != nil { panic(err) } var paths []string for _, file := range files { if !file.IsDir() && path.Ext(file.Name()) == ".xlsx" && !regexp.MustCompile(`^~$*`).Match([]byte(file.Name())) { paths = append(paths, file.Name()) } } return paths } func ZenkakuToHnakaku(str string) string { // Replacerを使って置換する r := strings.NewReplacer("0", "0", "1", "1", "2", "2", "3", "3", "4", "4", "5", "5", "6", "6", "7", "7", "8", "8", "9", "9") resStr := r.Replace(str) return resStr }結論
簡単にできた。
- 投稿日:2019-04-17T15:23:41+09:00
goumlでgoプロジェクトのUML図を出力する
goプロジェクトの依存をグラフとして可視化できるツールとして kisielk/godepgraph や paulbuis/golistdepgraph がありますが、その粒度は少し荒いものでした。(パッケージ間の依存を解析)
そこに颯爽と登場したのが kazukousen/gouml です。
Goのコードを静的解析してDDDパターンライクなPlantUMLを生成するツールを作り始めました。
— にったマン (@one_meets_seven) 2019年4月11日
ぽつぽつ開発していきたいと思いますhttps://t.co/eyydVrOCoVgoプロジェクトを解析してumlファイルを出力してくれる素晴らしいツールで、実際に出力してみた図はこのような感じになります。UMLですね〜
以下でコマンドの実行し、画像として保存するところまでを紹介していきます。
インストール
go get -u github.com/kazukousen/gouml/cmd/gouml
goumlを実行しumlファイルを出力する
gouml init対象のディレクトリを指定する場合は
-d
or--dir
を指定します。gouml init -d ./src/
出力するumlファイル名を指定する場合は
-o
or--out
を指定します。gouml init -o xxxx
umlファイルをpngファイルに変換する
いくつか方法がありそうですが、今回はplantumlを使います。
brew install plantuml brew install graphviz brew install gnu-sedcat xxxx.uml | gsed '1i @startuml' | gsed '$a @enduml' > xxxx.pu plantuml -tpng xxxx.puここで
xxxx.png
という画像が生成されているはずです。ご確認ください。注1: 出力されるpngファイルについて、デフォルトでは縦横ともに4096に制限されています。
画像にuml図の全体が収まっていないときは以下の環境変数のことを思い出してください。
echo 'export PLANTUML_LIMIT_SIZE=32768' >> ~/.bash_profile
注2: 現状では特定の条件下でグラフがちょっと悲しい感じになることがあります。
ものすごい勢いで開発されており、以下の問題も爆速で解消されました。
僕が遭遇したのは、string型のグローバル変数が宣言されている場合で、これに対処するパッチを一応投げて置きました
- 投稿日:2019-04-17T14:04:57+09:00
Go 言語 context パッケージ誕生の背景と使用方法
この記事は 私のホームページ とのマルチポストです
Go 言語を使って何を書くかといえば、なんだかんだでサーバプログラムを書くことが多いかと思います。
大抵の場合、ハンドラは goroutine で動かすことになります。また、ハンドラの中で goroutine 動かすケースも多々あります。
さて、例えばハンドラ内で外部 API を呼び出したり、マイクロサービスな関連サーバへリクエストをかけたりする際に、
それぞれのリクエストで AccessToken やタイムアウトの規定時間など、共通の値を用いるケースは多々あるかと思います。
また、 goroutine で平行で動かしているリクエストがある時、1つが失敗したら残りのリクエストも全部失敗させたいときなどもあり得るでしょう。
context
パッケージはそのような、リクエスト毎のデータを取り扱うために作られました。この文書は
context
パッケージの使い方とやりたかったことを
より初心者向けに、バカバカしく、何もかも忘却した2ヶ月後の私自身でもわかるようにcontext
の使い方を説明した記事です。準備編: HTTP サーバとハンドラを書いてみよう。
Go を使うと速くて軽い http サーバが簡単に書けるので、ナウでヤングなマイクロサービスを
やりたいときとかに気軽にサーバプログラムを書いたりします。一番簡単な HTTP サーバと、HTTPサーバの動作検証方法
というわけで、 "簡単なサーバ" を書いて実行してみましょう。まずは
main.go
を用意します。package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World.") }) http.ListenAndServe(":3000", nil) }で、コンパイルして実行します。
$ go run main.go
実行できたらアクセスしてみましょう。
$ curl http://localhost:3000/ Hello, World.ほらね。簡単に http サーバが書けたでしょう?
この記事では、このプログラムをベースとして様々な改造をしていきます。
その際、毎回curl
で動作検証をするのは面倒です。
ですので、go run main.go
で HTTP レスポンス自体を検証できるように、
httptest を使ってリクエスト・レスポンスの検証をしやすくしておきます。main.gopackage main import ( "fmt" "io/ioutil" "net/http" "net/http/httptest" ) func handleRoot(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World.") } func main() { server := httptest.NewServer(http.HandlerFunc(handleRoot)) defer server.Close() res, _ := http.Get(server.URL) body, _ := ioutil.ReadAll(res.Body) fmt.Printf("Response: %s\n", body) }こうしておけば、以下のコマンドですぐに実行できますし...
$ go run main.go Response: Hello, World.手元に Go 言語の開発環境がなくても、Go Playground から 実行可能 です。
外部 API 呼び出しなどの非同期処理がある場合
さて、Go 言語の特徴の一つとして非同期処理を比較的簡単に書けるというものがあります。
ですので、例えばハンドラ内で外部APIを呼び出したりする場合には、 goroutine を使った非同期かがよく行われます。具体的なイメージとしては、例えば、下記みたいに ntp 使って時刻を表示するサーバとか
main.goimport "github.com/beevik/ntp" func handleRoot(w http.ResponseWriter, r *http.Request) { time, _ := ntp.Time("ntp.nict.jp") fmt.Fprintln(w, time) }後は、なんか重めな処理を goroutine 使ってバックエンドに流したりするすサーバとかもあるわけです。
main.gofunc request(w io.Writer, text string, count int) { for i := 0; i < count; i++ { time.Sleep(500 * time.Millisecond) fmt.Fprintln(w, text) } } func handleRoot(w http.ResponseWriter, r *http.Request) { go request(w, "foo", 4) go request(w, "bar", 4) request(w, "baz", 5) go request(w, "qux", 4) }他にも、MySQL などのデータベースの読み書きや、それをラップした DAO, ORM などの処理。
ファイルの読み書きなどディスクIOが発生する何かなどは気軽に goroutine 化して行くことが数多くあるでしょう。直接 goroutine 化しないとしても、ライブラリ内などでは積極的にされているかもしれません。
このようなとき、上記のような素直すぎるプログラムを書いたとき、真っ先に困るのが タイムアウト処理の実装 でしょう。何をどう困るのかを実感するために、サンプルコードとして、ランダムで 0 - 2000 ミリ秒で String を返す
request
メソッドと、それを呼び出すハンドラを用意します。main.go// request は 0-2000ミリ秒ランダムで待機した上で、`kind` を返す func request(kind string) string { time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) return fmt.Sprintf("%s, ", kind) } // handleRoot は HTTP リクエストのハンドラで、foo, bar, baz, qux をランダムに返す func handleRoot(w http.ResponseWriter, r *http.Request) { c := make(chan string) go func() { c <- request("foo") }() go func() { c <- request("bar") }() go func() { c <- request("baz") }() go func() { c <- request("qux") }() for i := 0; i < 4; i++ { res := <-c fmt.Fprint(w, res) } } func main() { server := httptest.NewServer(http.HandlerFunc(handleRoot)) defer server.Close() rand.Seed(time.Now().UnixNano()) // 一応、所要時間も記録しておく start := time.Now() res, _ := http.Get(server.URL) elapsed := time.Since(start) body, _ := ioutil.ReadAll(res.Body) fmt.Printf("Response: %s (%s)\n", body, elapsed) // main が即終了しないようにしておく time.Sleep(2000 * time.Millisecond) }
console $ go run main.go
Response: bar, baz, foo, qux, (1.860987136s)
タイムアウト処理を実装しようとして、失敗してみる
シンプルにタイムアウトを実装するなら、
c
以外にもう一つタイムアウト用のチャンネルを用意し、
どちらかが返ってきたらhandleRoot
を打ち切るように実装すれば ok なはずです。
より、処理を簡単にするため、下のコードではさらにall
チャンネルと 4 つのリクエストを待つ goroutine を用意していきます。
- タイムアウトして、 "timeout" を返す
- 4 つの
request
を全て処理しきった結果を返すのどちらかの挙動となるはずです。
main.gofunc handleRoot(w http.ResponseWriter, r *http.Request) { c := make(chan string) go func() { c <- request("foo") }() go func() { c <- request("bar") }() go func() { c <- request("baz") }() go func() { c <- request("qux") }() // 全てのレスポンスを待機するチャンネルを作る all := make(chan string) go func() { var res string for i := 0; i < 4; i++ { res += <-c } all <- res }() // タイムアウトを1500ミリ秒後に設定する timeout := time.After(1500 * time.Millisecond) select { case response := <-all: fmt.Fprint(w, response) case <-timeout: fmt.Fprint(w, "timeout") } return } // request, main は略さて、これでタイムアウトができた。
・・・と、思うでしょう?例えば、タイムアウト時間を 100 ミリ秒とかに設定すると、期待通りに動かないことがすぐにわかるはずです。
func handleRoot(w http.ResponseWriter, r *http.Request) { c := make(chan string) go func() { c <- request("foo") }() go func() { c <- request("bar") }() go func() { c <- request("baz") }() go func() { c <- request("qux") }() all := make(chan string) go func() { var res string for i := 0; i < 4; i++ { res += <-c fmt.Printf("all: %s\n", res) } all <- res }() timeout := time.After(100 * time.Millisecond) select { case response := <-all: fmt.Fprint(w, response) case <-timeout: fmt.Fprint(w, "timeout") } return } // request, main は略$ go run main.go Response: timeout (103.853504ms) all: bar, all: bar, foo, all: bar, foo, qux,見ての通り、タイムアウトしたにも関わらず、
all
チャンネルを使う goroutine や、
request
メソッドを呼び出す goroutine は生きていたままになっていることがわかるかと思います。この問題は、 リクエスト全体を通して、処理が完了しているかを示すフラグやチャンネル があればなんとかなりそうです。
リクエスト毎の変数的な何か。または context が何を解決するか。
上記に出てきたような リクエスト全体を通して、処理が完了しているかを示すフラグやチャンネル 、
リクエスト毎に区切られた認証情報や処理の経過時間みたいな変数、リクエストを中止させる共通のインターフェースなどを用意したのが
context パッケージです。
- golang でも気軽に
CurrentUserId()
的メソッドで現在アクセス中のユーザIDが欲しい- とりあえずエラーを発生させて全ての処理を中止させたい
みたいな要求に気軽に応えられるようになります。
使い方はシンプルで、context を作って、下部のメソッドに渡して行くだけです! 簡単!
(ただし、下部のメソッドは goroutine を作る際に、ctx.Done()
で処理が終了していないかチェックかけてね!)func handleRoot(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(context.Background(), 2000*time.Millisecond) defer cancel() c := make(chan string) go func() { c <- request("foo") }() go func() { c <- request("bar") }() go func() { c <- request("baz") }() go func() { c <- request("qux") }() all := make(chan string) go func() { var res string for i := 0; i < 4; i++ { select { case r := <-c: res += r fmt.Printf("all: %s\n", res) case <-ctx.Done(): // リクエスト全体が完了しているなら、この goroutine を中止する。 return } } all <- res }() select { case response := <-all: fmt.Fprint(w, response) case <-ctx.Done(): fmt.Fprint(w, "timeout") } return } // request, main は略Context パッケージはこう使うんだよ
さて、ここまでで context が生まれた背景を説明していきました。
context を使うことで、リクエストを処理する全体で、タイムアウトやキャンセルなどの処理や、リクエスト全体をまたがる変数を取り扱うことができるようになります。
ここからは具体的な使い方を見ていきましょう。
Context
型Go Concurrency Patterns: Context - The Go Blog にも記載がありますが、Context のコアな構造は次の通りです。
// Context はキャンセルフラグやリクエスト毎の変数、リクエストのデッドラインなどを // API 境界をまたいでアクセスするために使います。各メソッドは複数の goroutine から // 同時にアクセス可能です。 type Context interface { // Context がキャンセルされたりタイムアウトしたりした時に close されるチャンネルを返す Done() <-chan struct{} // なんでこのコンテキストが中止されたのかを示す error オブジェクト。Done チャンネルが // close した後にセットされる Err() error // Deadline はこのコンテキストがキャンセルされる予定の time.Time を返す Deadline() (deadline time.Time, ok bool) // このコンテキストに関連づけられた変数 Value(key interface{}) interface{} }
Done
関数はこのリクエスト全体が完了またはキャンセルされた時にシグナルが渡されるチャンネルを返します。
このチャンネルが閉じるか、シグナルが来た場合は、リクエスト全体が終了したため、各 goroutine は直ちに終了する必要があります。
また、Err
関数は、このリクエスト然たがキャンセルまたはエラーで終了した場合、そのエラーを返します。子 goroutine をキャンセルさせる
Context
にはキャンセルを実行するメソッドは定義されていませんでした。
これは、通常の場合、キャンセルシグナルを受け取る goroutine とキャンセルを実施する goroutine は異なるためです。例えば、ある goroutine A があり、 A が新しく goroutine B を呼び出す場合で考えますと
- A は B をキャンセルできます
- B は A をキャンセルできません
- A は A 自身をキャンセルできません。(return で抜けることはできるでしょう)
- B は B 自身をキャンセルできません。(return で抜けることはできるでしょう)
と整理できます。
この場合、A は B にキャンセル可能なコンテキストを渡す必要があり、 context.WithCancel で作成可能です。例を見せましょう。
まず、コンテキストを受け取り、キャンセル可能な無限ループを作ります。
// infLoop は無限ループを行います。渡された context が終了した際にはこの関数を抜けます。 func infLoop(ctx context.Context) { fmt.Println("start infLoop") for { select { case <-ctx.Done(): fmt.Println("exit infLoop") return } } }main では context.Background で新しいコンテキストを作った上で、
この親コンテキストをもとにキャンセル可能な子コンテキストを作り、infLoop
に渡します。func main() { rand.Seed(time.Now().UnixNano()) ctx := context.Background() ctx, cancel := context.WithCancel(ctx) go infLoop(ctx)後は、好きなタイミングで
cancel()
を呼び出せば、infLoop
は終了します。go infLoop(ctx) time.Sllep(1000 * time.Millisecond) cancel() }実行可能なプログラムの全体像は以下の通りです。
package main import ( "context" "fmt" "math/rand" "time" ) // infLoop は無限ループを行います。渡された context が終了した際にはこの関数を抜けます。 func infLoop(ctx context.Context) { fmt.Println("start infLoop") for { select { case <-ctx.Done(): fmt.Println("exit infLoop") return } } } func main() { rand.Seed(time.Now().UnixNano()) ctx := context.Background() ctx, cancel := context.WithCancel(ctx) go infLoop(ctx) time.Sleep(1000 * time.Millisecond) fmt.Println("do cancel") cancel() // main が終了しないように sleep を挟んでおく time.Sleep(1000 * time.Millisecond) }実行結果は以下のようになります。
$ go run main.go start infLoop do cancel exit infLoop子 goroutine をタイムアウトさせる
ほぼ、上記のキャンセルと同様ではありますが、タイムアウト処理には context.WithDeadline と
context.WithTimeout の二つが使えます。WithDeadline は指定時刻に、WithTimeout は指定時間経過後にそれぞれキャンセルされます。
cancel() の呼び出しはどこに書くか?
ここまでのコード例では、
time.Sleep
を多用したため、cancel()
を手動で呼び出していました。
しかし、大抵の場合、foo
関数で子 goroutine を作りっぱなしにすることはなく、foo
が終了するタイミングで子 goroutine も後始末することが多いでしょう。
ですので、defer
を使って下記のように書くことが多いと思います。func foo(ctx context.Context) { ctx, cancel := context.WithCancel(ctx) defer cancel() go func(ctx) { // ... }(ctx) }コンテキストで値を渡す
context.WithValue を使い、値を渡します。
重要なのは、ここで渡す値は、例えばリクエストの処理の間、ずっと受け渡したい値であり、gorouitne や関数のオプションに渡すべきではないということです。
(この関数を使うと便利に値が渡せてしまいますので、濫用に注意しましょう)
context.WithValue
では、key
とval
のそれぞれにinterface
が使えます。コンテキストの木構造
Context
は親子関係を持っています。WithCancel
やWithTimeout
,WithValue
などを呼び出すことで、どんどん子Context
ができていきます。
親のContext
がキャンセルされた場合、子供や子孫のコンテキストにDone
が送信されます。では、最上位の根
Context
はどうやって作れば良いのでしょうか??context.Background がそれになります。
このコンテキストはキャンセルもできませんし、タイムアウト設定などもされていません。
まとめと Context を使う際のルールについて
context
パッケージを使うと、リクエストの処理中の値を取り扱いや、リクエスト自体がキャンセルされた際の、子 goroutine の適切なキャンセルなどをとてもスッキリと記述できます。
・・・が、それにはいくつかルールが必要です。Go Concurrency Patterns: Context - The Go Blog の最後に、Google 社内でのルールについて記されています。
まず、context
を用いる全ての関数で、最初の引数としてcontext
を受け取れるようにしています。
この時、 必ず第1引数 にしています(社内規約です)。
また、context
を用いた関数が goroutine を呼び出すなら、適切なキャンセル処理をする必要があります。
このルールに従うことで、謎の goroutin がサーバ上で残るなど変な不具合がぐっと減らせるはずでしょう。サンプルコードは https://github.com/ayasuda/sandbox/tree/master/go_context においてあります。
参照
- 投稿日:2019-04-17T14:01:57+09:00
go doc の使い方・コメントを書いて、ちゃんと読む
この記事は 自前のホームページ とのマルチポストです
Go 言語ではソースコード中にコメントを書くことで自動的に API ドキュメントを生成する方法が最初からサポートされています。
Godoc: documenting Go code でも、
The Go project takes documentation seriously.
と書かれているほど、しっかりと練られた文書化ツールが揃っています。
とはいえ、良い書き方や読み方がわからないと、書く気が起きませんよね?
そんなわけで、本文書では文書生成用のドキュメントの書き方と、それぞれのドキュメントの読み方について記していきます。godoc の動きの確認
まず、サンプルコードを用意しましょう。
$ mkdir -p $GOPATH/src/path/to/your/library $ cd $GOPATH/src/path/to/youre/library $ vim sample.gosample.gopackage sample import "fmt" func Foo(s string) string { return "Foo " + s + " ooF" } func PrintFoo(s string) { fmt.Println(Foo(s)) }この段階で、すでに
godoc
コマンドからドキュメントを参照可能ですまずはブラウザから見てみましょう。
-http
オプションで、ドキュメントをみるための web サーバが起動できます。$ godoc -http=:8080コマンドを実行したら
http://localhost:8080
にアクセスしてみましょう。まるで公式サイトのような Web ページが見られると思います。ヘッダの「Packages」リンクから、標準ライブラリおよび、GOPATH に持ってきている全てのパッケージのドキュメント が確認できます。
もちろん、たった今作ったsample.Foo
のドキュメントもあります!なお、この時の URL は
http://localhost:600/pkg/path/to/your/library/
になります。わかりやすいですね!簡単なコメントを書く
Godoc で読むためのコメントはとてもシンプルで、
godoc
がなくても読みたくなるようなコメントが書けるように意識して書くのが良いでしょう。Go Blog でも以下のように言及されています。Godoc comments are just good comments, the sort you would want to read even if godoc didn't exist.
コメントは
type
,variable
,constant
,function
,package
に書くことができます。また、コメントには一つルールがあり、その要素名で始める必要があります。sample.go// sample パッケージはコメントの書き方と読み方の例を提供します。 package sample import "fmt" // Bar はとある変数です var Bar = "some variable" // Baz はとある定数です const Baz = "some constant" // Qux はとある構造体です type Qux struct { // A には適当な文字列をどうぞ A string // A にも適当な文字列をどうぞ B string } // Foo はとりあえず前後に変な文字をくっけます func Foo(s string) string { return "Foo " + s + " ooF" } // PrintFoo はとりあえず前後に変な文字をくっけます func PrintFoo(s string) { fmt.Println(Foo(s)) }こいつをブラウザで表示すると下記のようにいい感じに表示されます。
ブラウザ以外からドキュメントを読む
godoc
のヘルプを見ればすぐにわかりますが、godoc package [name]
でそのパッケージおよび各種項目のドキュメントがコマンドラインから読めます。$ godoc fmt Println func Println(a ...interface{}) (n int, err error) Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.自作のライブラリ含めたサードパーティのライブラリのドキュメントを読む場合は、パッケージをフルパスで書きましょう。先に作ったサンプルの場合なら次のようになります。
godoc path/to/your/library PrintFoo func PrintFoo(s string) PrintFoo はとりあえず前後に変な文字をくっけますQ: パッケージ名の
sample
はどこに行ったのでしょうか?Answer: 僕がライブラリの作り方ミスって、sample が消失しただけだよ! golang のライブラリの作り方はこの文書の範疇じゃないから仕方ないね!
(具体的には、パッケージ名とディレクトリ名は通常一致させるべき)もっと詳しくドキュメントを書く
ここまでで日常使いには十分ですが、さらに詳しく、いろいろ書きたい人向けの機能にもふれておきましょう。
パッケージドキュメントを盛り盛りにする
専門知識が必要なパッケージを作りたいときなどに、プログラム本体のコメントとしてドキュメントを書くとちょっと長くなるケースがあると思います。
そういう時はパッケージドキュメント専用のファイルを作ることができます。具体的には、
doc.go
というファイルを用意し、そこにパッケージのドキュメントを記述します。gob パッケージ では、この機能が使われており、ドキュメントは src/encoding/gob/doc.go に書かれています。
-notes
オプションで追加項目をリストアップする基本的にコード中や指定箇所以外のコメントは無視されますが、
godoc
のノート機能を使うと、より多くのメタ情報を知るせます。デフォルトでは
BUG(who)
が定義されているので、例えば、下記のようなコメントを書くと、godoc で見たときにひとまとまりに表示されます。// Foo はとりあえず前後に変な文字をくっけます // // BUG(ayasuda) ごめん、なんかバグある気がする func Foo(s string) string { return "Foo " + s + " ooF" }ブラウザで見ると下記のようになります。
繰り返しになりますが、デフォルトでは
BUG
が定義されているので、コメント中でBUG(who)
を記述可能です。-notes
オプションでこの定義は自由に増やせます。簡単なフォーマット
コメントを書く際に、以下のルールに従うとことで HTML 化した際のフォーマットを変更することができます。
- 連続した行は一つの段落と見なされます
- 段落を区切りたい場合は空白行を間に入れる必要があります
- 英大文字で始まり、直前が句読点ではない単一行の段落は見出しになります
- 字下げすると整形済みテキストになります
- URL はリンクになります
そんなわけで、例えば、下記のようなコメントを書くと
/* ここはパッケージコメントの最初になるから見出しではないよー Hで始まり単一行かつ句読点なしかつ前が見出しではないのでこれは見出し 段落段落 段落段落 段落段落 次の段落 次の段落 整形済みテキスt 次のやつはリンクになるはず。 https://golang.org/ */ package sample以下のようにHTML化されます。
これ以上にいろいろ装飾したい場合は、 最悪、ソースが ここ にあるので・・・
Example をつけるには
公式ドキュメントのいくつかの関数では、 Example が付いてます。
これは Testable Examples を利用して作られています。上記の
sample
パッケージに対して Testable Example を作るなら、まずは/path/to/your/library/example_test.go
を用意します。
パッケージ名はsample_test
としてください。中には次のルールでサンプルがかけます。
func ExampleFoo() // Foo 関数のサンプルコードになります func ExampleBar_Qux() // Bar 構造体の Qux 関数のサンプルコードになります func Example() // パッケージ全体のサンプルコードになります func ExampleFoo_foo // Foo 関数のサンプルコード(fooの場合)また、テストコード中に
Output:
から始まるコメントを記載することで、標準出力に書き込まれた値をチェックすることができます。そんなわけで、こんなコードを書きます。
/path/to/your/library/example_test.gopackage sample_test import ( "fmt" "path/to/your/library" ) func ExampleFoo() { fmt.Println(sample.Foo("foo")) // Output: Foo foo ooF }テストはいつも通り、
go test
コマンドから実行可能です。$ go test PASS ok path/to/your/library 0.004s後は、ドキュメントを生成すると、Example が記載されているはずです。
参照
- 投稿日:2019-04-17T12:22:33+09:00
Golangでメソッド呼び出しによる部分適用
出来ないかなーと思いつつ遊んでいたら出来たのでメモ。
// 関数に名前付き型を定義する type Add func(int, int) int // 上で定義したAdd型に部分適用するメソッドを定義 func (f Add) apply(applyValue int) func(int) int { return func(value int) int { return f(value, applyValue) } } // Add型と同じ型を持つ関数を定義 func add(a, b int) int { return a + b } func main() { // 関数addがAdd型として扱えるように変数に格納する var f Add = add // Add型であるfからはapplyメソッドが呼び出せる f2 := f.apply(2) // 部分適用した関数f2は引数が一つで呼び出せるようになる fmt.Print(f2(1)) // 3 }このような方法を取らなくても、以下のような部分適用する関数を定義すればいいだけなので無性にメソッド呼び出ししたくなった時に利用するといいでしょう。
func add(a, b int) int { return a + b } func apply(f func(int, int) int, applyValue int) f(int) int { return func(value int) int { return f(value, applyValue) } } func main() { add2 := apply(add, 2) fmt.Print(add2(1)) // 3 }