2023/12/24

golang の取りあえず context

居酒屋に入って「取りあえずビール(中ジョッキ)」とやってしまうような感覚で、golang の引数に context があったら、上位から渡されたのがあればそれを、なければ context.Background() で与えていることが多い。

もう少し調べておこう。

 

まず、context.Background() はこれだ。

https://cs.opensource.google/go/go/+/release-branch.go1.21:src/context/context.go;l=211-213

backgroundCtx{} を返すだけで、NewBackgroundCtx() という名前にしたい気もするが、これはアドレスではなくインスタンスを返すから new が付かないのだろうか。
その下に context.TODO() もある。同じようにを todoCtx{} 返すだけである。

標準の context.Context はいくつあるのだろう?

目視なのでとりこぼしがあるかもしれない。

サンプルでよく見る context.Background()context.TODO() だが、どちらも emptyCtx を組み込んだだけになっている。

context.Background() は「これで contextを始める!」という top-level の場合に使い、context.TODO() は「まだよくわからんけどこれでやっておこう」という unclear なときに使う。

cancelCtx は、名前からするとキャンセルを通知できるようになっているのだろう。
じゃあ withoutCancelCtx は? emptyCtx との違いは Value() があるくらいだろうか。cancelCtx を使っていたけどキャンセル機構をなくしたくなった場合とか?
いや、cancelCtx を直接作る関数は用意されていない(引数にcontextを取る)から扱いが違うんだろうか。

 

だいたい、なんで Background() などは type Context の下にあるのだろう? レシーバーがあれば Types の下になるのは分かるのだが、戻り値が1つでそれが type だったら Types の下にぶら下がるんだろうか。

 こんなはっきりしない終わり方は嫌なので、ちゃんと context について説明しているサイトを読もう(この記事台無し......)!

よくわかるcontextの使い方
https://zenn.dev/hsaki/books/golang-context

 

2023/12/16

golang の test で before each は(たぶん)ない

golang でテストを書く場合、 TestMain(*testing.M) があるとそのファイルの中に *testing.T の関数は呼ばずに TestMain() だけ呼び出し、その中で Run() を呼び出すことで *testing.T の関数が呼ばれるらしい。
それを利用して、テスト全体の事前処理と事後処理を書くと良いそうだ。

ならば、各*testing.T の関数を呼び出す前後にも単独の事前処理と事後処理を呼び出すしくみがありそうなものだが、どうも無いようだ。

しくみを作っている人もいそうではあったが、標準にないならいいかな、と私は思った。テスト関数に直接埋め込むだけだ。
埋め込み忘れるという心配はあるのだが、だいたい TableDrivenTests とか言っているんだし、for文の中でテスト対象の関数を何度も呼び出すという書き方をすることが多いのだとすると、テスト関数単独の事前/事後処理を呼び出してもなぁ、という気がする。

 

 

golangのtestは自由度が高すぎる

golang は標準で testing というパッケージを持っていて、テストコードを書いたり、テストコードは実行ファイルに含めないようになっていたりとテスト用のしくみがある。

それはよいのだが、自由度が高すぎると思う。ファイルや関数の決まり事以外は t.Errorf()を通ればエラー、というだけのルールだけなのでどのようにでも書けてしまう。私はテストする関数と、あとは結果が正常系だったりエラーのxxだったりみたいな感じでテスト関数を分けて書いたりしていたが、境界値テストみたいな場合は構造体で INPUT と期待する OUTPUT の組を作った配列を for でぐるぐる回したりして、今ひとつ統一性がなかった。

 

go.dev の wiki には TableDrivenTests という項目があった。さっき最後に書いた構造体でテストデータと結果を作って流すような書き方がこれのようだ。

TableDrivenTests - The Go Programming Language
https://go.dev/wiki/TableDrivenTests

この方式だけでテストが書けるならば、テスト対象の関数とテスト関数が同じ数だけ並ぶので、わかりやすいと思う。
ただねー、INPUT と OUTPUT 以外に「前提条件」もあることが多いではないか。あれを構造体だけで表すのが難しいこともあると思うのだよ。
と思ったが、関数分けして書いたとしても前提条件を実装しないといけないことに変わりは無いので、それを「前提条件1」「前提条件2」みたいな INPUT にして、それぞれを if 文で分岐してしまえば同じことなのか。あるいは構造体に func なメンバーを持ってしまえば良いのか。

 

などなど、go.dev に書いてある方式を使うことにしたとしてもまだ自由度が高いと思う。もうちょっと縛ってくれないだろうか。
こういうときはフレームワークだと思う。例えば JavaScript だとほぼ jest がデファクトスタンダードになっていると思うが、そういうやつ。

自分で探す気力が無かったので、記事を探した。

 testify がよく出てくるようだけど、デファクトスタンダードとまではいかないというところだろうか。

assert があるとテストの比較する部分の書き方が統一される。比較しているどちらが期待値でどちらが実際の値かということに頭を使いたくないのだ。mock はどのくらい使いやすいのかによるが、自分でモックのしくみを考えたくないのであるなら利用したい。

と、今回は紹介だけになった。気が向いたら次回試してみようと思うが、普通に使うなら本家のサンプルを見た方がわかりやすいだろう。

2023/12/02

最近の go work

"go work" は比較的最近使えるようになった。
というと使いこなしているように見えてしまうが、実は初めて使う。。。

Tutorial: Getting started with multi-module workspaces
https://go.dev/doc/tutorial/workspaces

"go work" の "work" は workspace の work だろう。

このチュートリアルを最後まで行うと、workspace/ の中はこうなっている。赤枠がチュートリアルで作成した部分、それ以外は git clone で持ってきた部分だ。

 

hello.go で "fmt.Println(reverse.String("Hello"), reverse.Int(24601))

の reverse.Int(24601) はオリジナルの "golang.org/x/example/hello/reverse" にはなく、今回追加した int.go で実装されている。従来、こういう場合は go.mod の require にある "golang.org/x/example/hello/reverse" を replace で相対フォルダ指定して「実はこっち使ってます」宣言するのだが、"go work" のしくみを使うとそれが不要になっているというのがポイントだと思っている。

同じような使い方をするなら、誰かのリポジトリを GitHub fork して自前でちょっと変更したバージョンを使いたいとかだろうか。ただ fork すると別リポジトリとして扱いたいので git submodule で "go work" の中に配置するとかがよいのかな?

あるいは、単純に同じリポジトリの中に go.mod を複数持って相手を参照するような場合でも良いのか。gRPCサーバのアプリを作ったとき、protobuf の定義ファイルも同じリポジトリにおいたのだが、proto定義は変わらないのにサーバの更新だけ進むのでなんだかなー、という気持ちになっていた。
そういう場合にうまいこと使えないだろうかと考えたが、クライアントアプリも同じリポジトリで運用するならよさそうな気がする。気がするが、それは単に replace を書かなくていいのが便利なだけで gRPC の件とは何の関係もないな。

サーバのリポジトリの中に proto があると、クライアントアプリを作りたいだけなのに go.mod にサーバのパッケージを書くのでいらないものまでダウンロードすることになるというのがなんか嫌でね。どうせサーバもクライアントも同じPCで開発するだろうからいいやん、と言われればそれまでなのだが。

 

"workspace" という名前の通り、同じワークスペースにある go.mod はネットワーク参照ではなくローカル参照してくれるのが便利、という考え方で良いのかな。試作している間、repalce でローカルを読むように変更する手間が面倒だったので、workspace 自体は git 管理せずに作業中だけ使うというやり方でよいかもしれん。


最近の go get

"go get" の仕様が変わったという話は聞いていたし、実際に違いも感じるのだが、「なんとなく」で go get したり go mod tidy したりしている。
そのせいだと思うが、git clone してビルドするだけなのに go.mod が更新されていたり go.sum が更新されたりして落ち着かない。
それに過去の仕様を覚えているというわけでもない。

あきらめて、いまの go v1.21 くらいだとどう使うのが普通なのか調べておこう。


なるべく go.dev から参照していきたい。解説記事を読んだとしても、私が過去の仕様を知らないので「これは古い内容だ」という判断ができないのだ。

まず、go v1.17 から "go get" で実行ファイルのインストールが行われなくなった。
その代わりに "go install" を使いなさいということだ。

Deprecation of 'go get' for installing executables - The Go Programming Language
https://go.dev/doc/go-get-install-deprecation

その代わり "go get" がどうなったかというと、go v1.18 から go.mod への追加、更新、削除を行うようになったということだ。


今の "go get" の説明はここにあった。

go command - cmd/go - Go Packages
https://pkg.go.dev/cmd/go#hdr-Add_dependencies_to_current_module_and_install_them

"Add dependencies to current module and install them" というタイトルだったが、実際は更新や削除までやるようだ。
コマンド単体の説明があったので、こちらの方がよいか。

go get
https://go.dev/ref/mod#go-get

  • Upgrade a specific module.
  • Upgrade modules that provide packages imported by packages in the main module.
  • Upgrade or downgrade to a specific version of a module.
  • Update to the commit on the module's master branch.
  • Remove a dependency on a module and downgrade modules that require it to versions that don't require it.
  • Upgrade the minimum required Go version for the main module.
  • Upgrade the suggested Go toolchain, leaving the minimum Go version alone.
  • Upgrade to the latest patch release of the suggested Go toolchain.

多い、多いよ。。。

勝手に分類するなら、個別パッケージ向けかメインモジュール向けかというところか。

個別パッケージ向け

# Upgrade a specific module.
$ go get golang.org/x/net

# Upgrade or downgrade to a specific version of a module.
$ go get golang.org/x/text@v0.3.2

# Update to the commit on the module's master branch.
$ go get golang.org/x/text@master

# Remove a dependency on a module and downgrade modules that require it
# to versions that don't require it.
$ go get golang.org/x/text@none

# Upgrade to the latest patch release of the suggested Go toolchain.
$ go get toolchain@patch

メインモジュール向け

# Upgrade modules that provide packages imported by packages in the main module.
$ go get -u ./...

# Upgrade the minimum required Go version for the main module.
$ go get go

# Upgrade the suggested Go toolchain, leaving the minimum Go version alone.
$ go get toolchain


メインモジュールというのはカレントディレクトリか親ディレクトリの go.mod を指すそうだ。go.dev のドキュメントは "go get" や "go.mod" のフォントが若干他とは違っているようだけど、そのくらいだと見分けが付けづらいのよねぇ。

昔の "go get -d" が go v1.18 からはデフォルトの挙動になっている。"-d" がない場合はそのパッケージのビルドとインストールまでやっていた挙動がなくなったというのが仕様変更でよく言われている内容ということになる。

ここの用例ではオプションがあるけれども "go get" だけでも動作する。パッケージを指定していないのでメインモジュールに向けての操作になるはず。"-u" がないからアップグレードでもないし indirect な require だけ更新するのだろうか?


ここまで "go get" を見てきたが、パッケージの取得だけなら "go mod tidy" を使うイメージを持っている。が、 "go mod tidy" で go.sum が更新されたことがあるような気がするのだ。
なんとなく "go get" だけ実行したときと同じような感じがする。

go mod tidy
https://go.dev/ref/mod#go-mod-tidy

  • adds any missing module requirements necessary to build the current module’s packages and dependencies
  • removes requirements on modules that don’t provide any relevant packages.
  • It also adds any missing entries to go.sum and removes unnecessary entries.

こちらは go.modとソースコードの関係性を重視しているのか? "go get" は "-u" 指定しない限りは indirect くらいしか更新しないようだが "go mod tidy" は不要なパッケージがあれば indirect でなくても削除していた。ただ新しいバージョンがあっても更新はしないので、ビルドできるかどうかだけ判断しているのかな?

2023/11/25

protoc-gen-go-grpcの-Mオプションがうまくいかない

gRPC の勉強というか仕事というか。

適当に proto ファイルを作って、protoc-gen-go と protoc-gen-go-grpc をインストールした状態で protoc を実行する。
が、エラーになった。

protoc-gen-go-grpc: unable to determine Go import path for "rpc.proto"

Please specify either:
       • a "go_package" option in the .proto source file, or
       • a "M" argument on the command line.

See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.

--go-grpc_out: protoc-gen-go-grpc: Plugin failed with status code 1.

今回は自作した proto ファイルなので好きに編集できるが、別のところが提供している proto ファイルを使う場合は加工したくない。

なので --go_opt=M を付けて protoc を実行すると成功した。

2023/11/19

モダンスタンバイ?

ThinkPad T14s を購入した。
OSは Windows11 Home。

充電をしていない場合、蓋を閉じるなり、電源ボタンを押すなり、Fn+4 を押すなりするとスリープしている。
どうやってスリープしていると判断しているかというと、ThinkPad LED(表紙とパームレストにあるLED)が点滅しているかどうかである。
この LED の説明を探しても見つからないので、たぶんそうだろうという判断である。


充電中の場合、上に書いた操作のどれを行っても ThinkPad LED が変化しない。ファンも回っている。そうなるとスリープしたように見えない。

ただ検索すると、スリープ中もファンが回っているという記事がいくつも見つかるので、実はスリープしているのかもしれない。
そうやって調べていくと、モダンスタンバイという機能が見つかった。

モダン スタンバイ | Microsoft Learn
https://learn.microsoft.com/ja-jp/windows-hardware/design/device-experiences/modern-standby

スマートフォンのように画面が消えてスリープになっているときでもネットワークにつながったままにできるらしい。
充電中だけモダンスタンバイになっている? LEDの点滅はスリープとはあまり関係が無い?いろいろ謎はあるのだが、とりあえず無効にしてみると分かるだろう。

 powercfg /a で S0 低電力アイドルになっていることを確認した。

How to Disable Modern Standby in Windows 11 and Windows 10
https://winaero.com/how-to-disable-modern-standby-in-windows-11-and-windows-10/

レジストリ操作がいるのか。。。
再起動して確認。

あれ? 普通のスリープってのにならないのか?
実際に Fn+4 などで試してみたが休止状態になった。充電中でもそうなったのでモダンスタンバイは無効になったようだ。
設定画面でも電源を切る時間しか項目が出なくなった。

そういえば BIOS ... UEFI の設定にスリープ関係があったような・・・なかった。

 

モダンスタンバイか休止状態の二択しかないのか。

ただ、一度設定を変更したのが良かったのか、充電中でも LED が点滅するスリープになるようになった。


2023/11/18

ThinkPad T14s Gen4

比較的最近、ThinkPad T14sを買いました。Gen4 です。


トラックポイントを使っていますが、メーカーが変わっていました。
Synaptics から ELAN になりました。
だからかどうかわかりませんが、操作感が結構違うように感じます。

以前はしばらく使っていると反動?で自動的にカーソルが動くことがありました。
最初は「なんじゃこりゃ!」と驚いたのですが、慣れてしまうと不思議なことに「元気だねー」くらいの気持ちでいられるようになりました。

今回のトラックポイントですが、そういう動作はしなくなったように思います。
ですが、カーソルが動かなくなることがあるような気がします。「気がします」と弱気なのは、押している力がそのとき弱くなったのかもしれないとちょっとだけ思うからです。指を離して同じくらいの力で(と私は思っている)操作するとまた動きます。
まだ慣れてないですが、これもいつか慣れるのでしょうか。

もう1つ違和感があるのは、同じ方向に力を加えた後に指を離すとカーソルが逆方向にちょっと動くことがあるところです。
カーソルなら気にならないのですが、中央ボタンを押したままでスクロール動作をさせたときは指を離すと逆方向にスクロールしてしまうので気になります。
これはソフトウェアで処理できそうなので、そういう調整ができるとうれしいです。急に逆方向に力が加わったときは多少の移動量は捨てるとか。

トラックポイント関連の設定項目ですが、たぶんこれらになります。
パッドもありますが、私が使っていないので省略します。

  • Windowsの設定画面からできる「マウスポインターの速度」

  •  マウスのプロパティの速度とポインターの精度チェックボックス(上と同じもの?)


  • ELAN TrackPoint for ThinkPadでの感度設定


 

感度の設定でスティックを押す力とカーソルの動き始めが調整できそうです。これを大きくすると弱い力でカーソルが動いてくれそうでした。
ただ、同時に動く移動量も多くなってしまうようで、そちらは速度で調整するようです。難しい・・・。


中央ボタンを押してスクロールさせた場合ですが、どうも Windowsの標準スクロールとは違う経路を通っているような感じがします。
別のソフトウェアでスクロールを調整するアプリがあったのですが、その影響を受けなかったからです。まあ、スクロールはするので違う経路があるだけなのでしょうが。

うまく言えないのですが、Excel などで横スクロールさせると間がすっ飛びます。まあ、縦スクロールもそうなのですが。
別のアプリでは縦スクロールだけ重たかったです。
まあ、これはアプリのせいかもしれませんが、ともかくアプリによって違いが発生することがあるということのようでした。