2022/05/15

[typescript] ちょっと触った感想

週末に typescript を少し触っていたので感想を残しておこう。

 

トランスパイル後のjsファイルもcommitしたくなる

ts ファイルがあってもトランスパイルしないと node.js で動かせない。
トランスパイル使用としたら tsc をインストールしないといかん。
大したことじゃないのだけど、全然大した作業じゃないのだけど、クラウドに VM 立てて、 node.js と npm が使えるようになって、 git clone してさあ npm start、ってやったら js がないってなったときにちょっとガックリするのだよね。

ts-node がよく紹介されるので使っていたけど、リポジトリ見たら結構大きい・・・。
それなら Makefile 作って、make install、make、npm start、の 3ステップくらいにしたらよいんだろうか。
でも make も何かインストールしないと使えないので、それはそれでって感じだ。

今回は gRPC のためにやっていたので proto ファイルもある。
proto ファイルからツールを使って d.ts ファイルと js ファイルを作ってもらい、それを使った ts ファイルを作り、トランスパイルして js ファイルを作ってもらう。

最後には proto も ts もいらなくなるのだから、なんとなく悲哀を感じてしまった。
node.js みたいに node.ts とかあるとよいのだけどね。

 

トランスパイルしたファイルのディレクトリ

tsconfig に "outDir" があったので "./out" にしてみた。
proto ファイルから d.ts ファイルと js ファイルを作るので、それは "./proto" に置いてみた。
そうすると tsc は成功するのだが node.js で実行しようとするとエラーになる。
import する場所が "./proto" なのだけど js ファイルがあるのは "./out" なので、実際には "../proto" にあることになるからだ(あとから js ファイルを ./out/proto に置けば良いことに気付いたがね)。

rootDirs に "./proto" を追加して import は相対パスで指定すればよいと思ったのだけどダメだった。
「This does not affect how TypeScript emits JavaScript」とあるからトランスパイル後のパスまでは考慮しないってことだと思うけど、tsc で効いてない理由が分からん。

私は挫折して d.ts ファイルをカレントディレクトリに持ってきて "./" で参照するようにし、js ファイルは "./out" に置くようにした。

TypeScript の paths はパスを解決してくれないので注意すべし! – 自主的20%るぅる
https://www.agent-grow.com/self20percent/2019/03/11/typescript-paths-work-careful/

これは paths なのでちょっと違うけど、import が解決できないという意味では同じだ。 ts-patch , typescript-transform-paths を使ってみたが、なんかもう一手間いる感じがした。

[typescript] gRPCはどうやるのがよいんだかわからん

前回、import したファイルを export することについて調べていた。

https://blog.hirokuma.work/2022/05/typescript-import.html


が、元々は gRPC というか proto ファイルだけある状態で呼び出すコードを typescript で書きたいけどどうすりゃいいんだ、というのが目的だった。

proto-loader-gen-types を使って自動生成された tsファイルが大量にあったのでまとめようとしたのだが、よく見るとまとめているようなファイルも生成されていることに気付いた(つまり export で調べたことは関係なくなった)。言い訳だが、ファイルが大量にあって気付かなかったのだ。

別のツールを使っているサイトを探してみた。

OK Google, Protocol Buffers から生成したコードを使って Node.js で gRPC 通信して | メルカリエンジニアリング
https://engineering.mercari.com/blog/entry/20201216-53796c2494/

ようやく気付いたのだが、typescript のコードを生成するということは proto ファイルは実行時にいらないんじゃないか? proto ファイルは情報が載っているだけで、そのファイルを使って gRPC を行うわけではない。proto ファイルが TypeScript でいう型情報みたいなものだと考えて良いと思う。

そう考えれば、typescript もそんなに身構えなくてよいのではなかろうか。構えてないけどね。

 

[typescript] 複数のimportするファイルを何とかしたい

タイトルだと全然わからんのでちゃんと説明をします......

 

発端は gRPC クライアントアプリを JavaScript で作っていたので TypeScript にしようと考えたことだった。
いままで TypeScript のことを調べはしたものの、 1ファイルで終わるような内容であれば JavaScript で済ませていたのだが、大きくなりそうな気配があったので TypeScript にしておこうとしたのだ。

--init で tsconfig.json を作り、ちょっとだけ変更して、拡張子を js から ts にリネームし、プリミティブ型以外のところは any で逃げてコンパイルというかトランスパイルというか、ともかく JavaScript にして node.js で実行するところまではできた。

次の段階は any を何かの型に置き換える作業だろうと思って進めていた。
作っていた JavaScript の中では特に Object を作るようなことはしていなかったのだが、 gRPC のリクエストとレスポンスは proto ファイルに由来する型を使っていた。

では proto ファイルから型を作る何かがあるだろうと調べて出てきたのがこれだった。

Generating TypeScript types
https://github.com/grpc/grpc-node/tree/master/packages/proto-loader#generating-typescript-types

npx proto-loader-gen-types を実行すると、proto ファイルを読み込んで型情報を持つ ts ファイルを作ってくれた。
そこまでは良かったのだが、proto ファイルに記述されている message ごとに ts ファイルを作るようだった。使う予定の proto ファイルにはたくさん message があり、大量の ts ファイルが生成された。

これを import すれば使えそうなのだが、その前に「この大量のファイルを import するということ自体を何とかできんだろうか、と考えたのだ。


まず考えたのがワイルドカード。
import もモジュールといいつつファイル名みたいなもんだから from のところに './proto/*' みたいな書き方をすればよいんじゃないの?と思うのは仕方あるまい。

結果としてダメだった。
まあ、同じディレクトリにあるだけで読み込まれるってのはさすがに危険すぎるな。

 

dynamic import というやり方も見つけたのだが、別に静的で良いのだ。
単に import 文をたくさん書きたくないだけなのだよ。
いや、書いても良いけど import だけで何十行もあるような状況が嫌なのだよ。

 

というわけで次に思いついたのが、別の ts ファイルに import だけ詰め込んで それを import するような手段が執れないだろうか、ということだった。

import - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import

JavaScript だろうと TypeScript だろうと import は import のはずだから MDN の説明でよいと思う。
ただね、読む前に想像すると、そんなことはできないんじゃないかと思うのだ。
まあ、やらんとわからん。

 

image

image

>node index.mjs
aaa=30
bbb=20
ccc=10

TypeScript ではないが、まあよかろう。
これが基本形だ。

 

ダメだったワイルドカード。

import * as aaa from './files/*.js';

console.log(`aaa=${aaa.A}`);
console.log(`bbb=${aaa.B}`);
console.log(`ccc=${aaa.C}`);

Cannot find module になる。
ワイルドカードと見ずに "*.js" というモジュール名として処理するからだろう。

 

a.js, b.js, c.js は変更しないとして files/index.mjs を追加してみた。
まあ、ダメなんだけどね。

image

>node index.mjs
aaa=undefined
bbb=undefined
ccc=undefined

import でモジュール名しか指定していない場合は import はしないのだね。。。

副作用のためだけにモジュールをインポートする
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import#import_a_module_for_its_side_effects_only

それに、もし import できていたとしても export していないから参照できないだろう。
これなら動いた。

image

>node index.mjs
30
20
10

export {a.A} みたいな書き方をすれば total.A で参照できるかと思ったがダメだった。 export {a.A as A} もダメだ。
再export すればいける。

image

export from という書き方もあるそうだ。

export
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/export

image

これくらいだったら bash で機械的に作れるんじゃないかな。

2022/05/08

[golang]go.modの入れ子を理解できなかった

go.mod をずいぶん理解できたんじゃないかと思っていたのだが、入れ子になった場合、つまり サブディレクトリの中にも go.mod がある場合にどうしたらよいのかがわからなかった。

 

なんで入れ子にしようとしたかというと、gRPC 用の proto ファイルを別管理しようとしていたからだ。過去の記事にも同じことを書いたような気がするが、

  • proto ファイルをサーバ側のアプリと同じリポジトリに置いた
  • でも proto ファイルの方はバージョンが変わらずサーバアプリだけ更新されることが多い
  • うーん、ならば go.mod を別にした方がよいのでは?

という思考からだ。

リポジトリごと分ける方が無難なのだろうけど、それはそれでやりすぎな気がしてそうしたのだ。
サーバもクライアントもアプリが小さいなら同じリポジトリに置いてしまってよかったかもしれないが、clone するだけで大ごとになるようなサイズなので「サーバ+proto」「クライアント」の2つに分けている。

と「仕方ない」という感を出そうと思ったが、初回のリリースまでは proto もサーバアプリと同じ go.mod にして、リリースしたらどうせ tag を付けるだろうからバージョン管理で逃げてしまえばよかったと思う。
開発中は tag を付けていないので、go.mod に書くときは commit-id を元にした pseudo-version を使っている。

 

入れ子にしたのはこれだけではなかった。
クライアント側も gRPC にアクセスする部分だけ go.mod を別にしてしまったのだ。
gRPC のテスト用クライアントアプリを別に作ったので、gRPC アクセス部分だけ使い回そうとしたのだ。

そんなこんなして、面倒な構成になってしまった。
実際はこんなにシンプルではなく、もっと大量に import している。

image

こうなったとき、go.mod をどう書いて良いのかが結局分からなかった。
go mod tidy してもエラーが出て、いろいろ書き換えても解消できなくなったのだ。 replace を使ってローカルディレクトリを参照したのも原因かもしれない。

結局あきらめて、クライアントアプリの grpcアクセス部分は go.mod を削除して単にクライアントアプリの 1パッケージとして使うことにした。

image


悔しいからいうわけではないが、この構成に変更してよかったと思う。
そもそも、テストアプリはテストアプリに過ぎないのだから、そのために苦労しすぎるのは馬鹿らしい。
それに、grpc の部分を本格的に別のところでも使いたいならクライアントアプリとは別リポジトリにすべきだろう。サーバが複数あることは考えにくいからセットにしたが、クライアントは種類が複数あっても変ではないのだ。

 

だから構成を変更したことに対しては文句がないのだが、その理由が「できなかった」というのは納得がいかないものだ。
できるけどやらない、くらいにしておきたい。

というわけで、入れ子になった go.mod 環境を作って試していこう(本題)。


その前に、用語の整理をしたい。
パッケージは goファイルの中に package として書くのでわかるのだが、モジュールとかバージョンになるとちゃんと理解できていない。今回だって「go.mod の入れ子」と書いているが、正式な名称じゃないと思う。

Modules, packages, and versions
https://go.dev/ref/mod#modules-overview

モジュール

これは package なんかの集合で、識別はモジュールパスで行われる。
モジュールパスは go.mod に依存関係などと一緒に書く。
モジュールルートディレクトリは go.mod があるディレクトリ。
メインモジュールはちょっと違って、go コマンドを実行したディレクトリに含まれているモジュールを指す。つまりルートディレクトリの下にあるサブディレクトリで go コマンドを実行しても、そこに go.mod が含まれていなければ上にたどって直近にある go.mod のモジュールがメインモジュールになるということだ。

git で管理していて普通に作るなら、モジュールパスは git のリポジトリと同じにするだろう。 git 以外でも同じようなものだろう。
ローカルファイルでも悪くはないと思うのだが、どうなんだろうね。


というわけで、ローカルディレクトリで試していこう。

$ go version
go version go1.18.1 linux/amd64

最近は GOPATH を使わなくてもよくなりつつあるようだが、よくわからんので GOPATH を使う。

$ export GOPATH=`pwd`
$ mkdir bin src
$ cd src
$ mkdir mod1 mod2
$ cd mod1
$ go mod init
$ cd ../mod2
$ go mod init
$ cd ..

これで mod1 と mod2 というモジュールができた。
モジュールパスも mod1 と mod2。

なにか import させて go.mod を賑やかにしたいので、昔作った gogo-test1 を持ってこよう。

$ cd mod1
$ go get github.com/hirokuma/gogo-test1
go: downloading github.com/hirokuma/gogo-test1 v0.0.0-20211121014212-382e4677bbfb
go: github.com/hirokuma/gogo-test1@v0.0.0-20211121014212-382e4677bbfb requires
        github.com/hirokuma/yoshio@v0.0.0: reading github.com/hirokuma/yoshio/go.mod at revision v0.0.0: git ls-remote -q origin in /home/xxxx/golang/pkg/mod/cache/vcs/301146d5f0494aad6678724d644f7711fa7ff362d0bc8683a33b7d297ca8e2c3: exit status 128:
        remote: Repository not found.
        fatal: repository 'https://github.com/hirokuma/yoshio/' not found

なんだこりゃ?と思ったが、 gogo-test1 の go.mod で require github.com/hirokuma/yoshio にしてて、それを replace で github.com/hirokuma/gogo-test2 にしているだけだ。

gogo-test1 を clone して go mod tidy してもエラーにならない。
もしかして replace って import すると使えなくなったりするんだろうか。

依存関係がない gogo-test4 はすんなり go get できた。

$ go get github.com/hirokuma/gogo-test4

go.mod

module mod1

go 1.18

require github.com/hirokuma/gogo-test4 v0.1.0

 

main.go

package main

import (
    "fmt"

    gogo "github.com/hirokuma/gogo-test4"
)

func main() {
    gogo.SetValue(3)
    fmt.Printf("%v\n", gogo.GetValue())
}

$ go run .
3


hirokuma/gogo-test1 がダメな理由だが、なんとなく tag で付けた v3 もよくない感じがする。
gogo-test1/go.mod にあった replace をこちらにも追加したのだが、これはダメだった。

$ go mod tidy
go: errors parsing go.mod:
/home/ueno/golang/src/mod1/go.mod:7: no matching versions for query "v3"

module mod1

go 1.18

replace github.com/hirokuma/yoshio v0.0.0 => github.com/hirokuma/gogo-test2 v0.0.0-20211121012830-b239fb1fd1ae

require github.com/hirokuma/gogo-test1 v3

 

こっちだと go mod tidy できた。

module mod1

go 1.18

replace github.com/hirokuma/yoshio v0.0.0 => github.com/hirokuma/gogo-test2 v0.0.0-20211121012830-b239fb1fd1ae

require github.com/hirokuma/gogo-test1 v0.0.0-20211121014212-382e4677bbfb

 

tag に v0.0.3 という名前で追加しても成功したので、 x.y.z 系の tag を付けておくのが無難ということか。
本題とは関係ないのに時間がかかってしまった。


というところで気付いたが、ローカルディレクトリだとバージョンも何もないので意味が無いのでは・・・?

とりあえずやっておこう。

src/
  mod1/
    go.mod
    main.go
  mod2/
    go.mod
    gogo.go

 

mod2/go.mod

module mod2

go 1.18

mod2/gogo.go

package mod2

import "fmt"

func Gogo() {
    fmt.Printf("mod2 GoGo!\n")
}

 

mod1/go.mod

module mod1

go 1.18

replace github.com/hirokuma/yoshio v0.0.0 => github.com/hirokuma/gogo-test2 v0.0.0-20211121012830-b239fb1fd1ae
replace mod2 => ../mod2

require github.com/hirokuma/gogo-test1 v0.0.0-20211121014212-382e4677bbfb
require mod2 v0.0.0

mod1/main.go

package main

import (
    "fmt"
    "mod2"

    "github.com/hirokuma/gogo-test1/gogo"
)

func main() {
    fmt.Printf("gogo-test1!\n")
    gogo.Gogo()
    mod2.Gogo()
}

$ cd mod1
$ go run .
gogo-test1!
gogo!
mod2 GoGo!

mod2 は replace するから require しなくてもよいかと思ったが、それはダメだった。


ここで、mod2 の下に modmod2 を追加する。

image

 

mod2/modmod2/gogogo.go

package modmod2

import "fmt"

func Gogogo() {
    fmt.Printf("modmod2 GoGo!\n")
}

そして mod1 で使う。

 

mod1/go.mod

module mod1

go 1.18

replace github.com/hirokuma/yoshio v0.0.0 => github.com/hirokuma/gogo-test2 v0.0.0-20211121012830-b239fb1fd1ae
replace mod2 => ../mod2
replace modmod2 => ../mod2/modmod2

require github.com/hirokuma/gogo-test1 v0.0.0-20211121014212-382e4677bbfb
require mod2 v0.0.0
require modmod2 v0.0.0

mod1/main.go

package main

import (
    "fmt"
    "mod2"
    "modmod2"

    "github.com/hirokuma/gogo-test1/gogo"
)

func main() {
    fmt.Printf("gogo-test1!\n")
    gogo.Gogo()
    mod2.Gogo()
    modmod2.Gogogo()
}

$ go run .
gogo-test1!
gogo!
mod2 GoGo!
modmod2 GoGo!

 

なんというか、これだと単にディレクトリの位置が違うというだけで、入れ子になっているのは関係ないことになる。

バージョンのことを除けば、go.mod が入れ子になろうと関係がないということかしら。
いつも親?のモジュール名のサブディレクトリっぽいモジュール名を付けていた(今回なら mod2/modmod2 みたいな)ので go.mod がないときと同じように扱っていたのだが、全然違うモジュール名を付けていてもなんとかなったのかもしれない(どうやってリポジトリと紐付けるのかは知らんが)。

mod2/gogo.go から modmod2.Gogogo() を呼ぶこともできた。

package mod2

import (
    "fmt"
    "modmod2"
)

func Gogo() {
    fmt.Printf("mod2 GoGo!\n")
    modmod2.Gogogo()
}

こちらは mod2/go.mod に追加せずにできた。

mod1/go.mod のように追加しなければならないかと思ったのだが、そこはサブディレクトリという特権なのか?
ディレクトリ名を mod2/modmod2 から mod2/modmodmod2 に変更だけしてみたら、mod1/go.mod の replace したパスが存在しないというエラーになった。

$ go run .
../mod2/gogo.go:5:2: modmod2@v0.0.0: replacement directory ../mod2/modmod2 does not exist

mod1/go.mod の replace を ../mod2/modmodmod2 に変更すると go run できた。
なんで mod2/gogo.go は変更しなくてよかったのだろう?
キャッシュかもしれんと考えて各 go.mod があるディレクトリで go clean -modcache と go clean -cache を実行したが変わらなかった。

まあ、やっぱりサブディレクトリの特権ということか。


そう考えると、あまりサブディレクトリに go.mod を持つ利点よりも、バージョン管理が難しくなる欠点が目立ちそうなので避けたい気もする。

最初に書いた proto のディレクトリに go.mod を付けたとしても、tag をディレクトリごとに付けられるわけでもないので、やるなら v0.0.1-proto みたいにサフィックスを付けるとかになるんだろうか。

バージョンの付け方は go.dev に説明がありそうだったが・・・読む気にならん。

Minimal version selection (MVS)
https://go.dev/ref/mod#minimal-version-selection

 

golang の言語仕様自体は結構シンプルな印象を受けているのだが、 go.mod のことで悩む時間が多いのがきついところだ。なんとなく、バージョン管理にまで口を出す傲慢な言語、っていうイメージになってしまった。まあ Java が出てきたときも「ディレクトリが言語に合わせないといけないなんて!」と思ったので今さらかもしれん。オープンソースとはいえ開発主体があるならその意向に沿ってしまうのは仕方ないだろう。嫌なら自分で開発すれば良いだけなのだから。

 

最後の方になって役に立ちそうな記事を見つけた。

Go のモジュール管理【バージョン 1.17 改訂版】
https://zenn.dev/spiegel/articles/20210223-go-module-aware-mode

ありがたや。

2022/04/20

[linux] bash の -u と -e

私が bash のスクリプトで bash のオプションとしてしばしば使うのは -u と -e だ。

https://linuxjm.osdn.jp/html/GNU_bash/man1/bash.1.html


-u は、設定されていない変数やパラメータをエラーとして扱うというもの。

私はこの「パラメータ」だけに反応していて、つまり bash の引数である $1 とか $2 を参照しようとして引数として与えられていなかったらエラーになるんだろう、と思い込んでいた。

さっきまで。

しかしよく読むと変数も対象ではないか。

#!/bin/bash -u

$GOPATH/bin/aaa

これで GOPATH に設定し忘れて実行してもエラーになってくれる。
私の開発スタイルだと GOPATH は未設定の状態がデフォルトなので、こうやってエラーになってくれると非常に助かるのだ。

ちなみに今までは、

if [ -z "$GOPATH" ]; then
  echo no GOPATH
  exit 1
fi

みたいなのを書いていた。。。


もう1つが -e で、これは戻り値が 0 以外だったら即時終了するというもの。

まあそのままなのだが、これを付けていると本当に即時終了するので、開発中のデバッグでぐるぐる回すスクリプトなんかで使っているといつの間にか終了してしまっているということがしばしばあり、使うのを敬遠していた。

先ほど -u で気付いたようにこちらも何かあるかもしれないと読んでいると「ERRのトラップ」という言葉が出てきた。

err1.sh というファイルを作ってみた。

#!/bin/bash -eu

function mya() {
    echo MYA-
}

# trap mya ERR

function err1() {
    echo \"err1\"
    return 1
}

err1
echo end.

$ ./err1.sh
"err1"
MYA-

おお!

「trap mya ERR」をコメントアウトすると「"err1"」だけ出力された。

 

全部捕捉されるかは分からんが、助けになりそうだ。

2022/04/17

[linux] logrotateがまだ動いていないのは場所が悪かった

logrotate のことを昨日書いた。

hiro99ma blog: [linux]思ったより logrotate が難しい
https://blog.hirokuma.work/2022/04/linux-logrotate.html

VirtualBox 環境では確認しづらかったので、AWS に立てている Ubuntu で設定して、眠たかったので明日のお楽しみ、だったのだが、今朝見てみるとまだ動いていなかった。

 

かなり長くなったので結論を書くと、

  • systemd の service で動かす logrotate は /tmp や /var/tmp に mount する PrivateTmp が trueになっている
  • /tmp にログファイルを置いていたのだが、PrivateTmp=true によって /tmp に mount されたことでファイルが見えなくなっていた

である。

 


/var/lib/logrotate/status を見ると、2022年4月17日の0時(UTC)に更新されているものがあった(これを書いているのは2022年4月17日 11時(JST))ので、少なくとも 1回は動作していることがわかる。

うーん、ありそうなのはこんなところか。

  • systemctrl の timer が hourly で動いていない
  • logrotate の設定が hourly で動いていない
  • logrotate は実行したけれどもローテートしなくて良いと判断された

 

まず、自分で追加した logrotate の設定をなかったことにするため、 /var/lib/logrotate/status の中から自分のアプリの行を削除した。
これで前回動作したという記憶がなくなったはずだ。

この状態で logrotate を実行してみよう。

$ cat /var/lib/logrotate/status | grep aaaa
$ sudo logrotate /etc/logrotate.d/aaaa
$ cat /var/lib/logrotate/status | grep aaaa
"/tmp/aaaa/aaaa.log" 2022-4-17-2:0:0

うむ、status ファイルに記録が残った。
日本時間11時・・・UTC 2時なので「2:0:0」でよかろう。
なお logrotate の設定ファイルはこんな感じだ。

/tmp/aaaa/aaaa.log {
    missingok
    hourly
    rotate 12
    copytruncate
    compress
}

aaaa.log はアプリがオープンしたまま書き込むので copytruncate でコピーしてオリジナルは ftruncate してサイズだけ切り詰めてもらう・・・という動作をしてほしい。

あと 1時間待って、systemctl の timer が自発的にやってくれるのを待とう。


放置している間に情報を探しておこう。

How To Manage Logfiles with Logrotate on Ubuntu 20.04 | DigitalOcean
https://www.digitalocean.com/community/tutorials/how-to-manage-logfiles-with-logrotate-on-ubuntu-20-04#step-3-setting-up-an-example-config

んん?
システムの logrotate は1日1回しか動かないから hourly は動かない・・・?
このサイトの人は、自分のアプリ用に logrotate の設定を用意して、ユーザレベルの cron に logrotate を実行するよう書き加えている。 

 

まあ、私が hourly にしているのもさっさと結果を確認したいからであって、時間ごとにログをローテートさせたいわけではないので、本当に daily 以上の間隔じゃないと動作しなかったとしても別に問題ではない。

ただこのアプリは最終的には systemctl で起動させようと思っているので、logrotate も systemctl の方でやってほしい。


さて、あれから1時間が経過した。

/var/lib/logrotate/status に変化はなかった・・・。
systemctl list-timers の方は LAST の時間が更新されていたので、タイマーは動いたのだと思う。
journalctl -u logrotate でも Succeeded になってるしね。

 

となると、やはりシステムでは logrotate が daily 以上の間隔という話が真実味を帯びてくる。
もしかすると /etc/logrotate.conf の設定がそうなっているのかと思ったのだが、そこは weekly だったので関係ないだろう。

では、/lib/systemd/system/logrotate.service を /etc/systemd/system/ にコピーして、logrotate を -v で実行させよう。
そうすれば journalctl でももう少しログが出てくれるんじゃなかろうか。


さらに1時間待った。
logrotate -v のおかげで journalctl で詳細が出てきた。

Apr 17 04:00:12 logrotate: rotating pattern: /tmp/aaaa/aaaa.log  hourly (12 rotations)
Apr 17 04:00:12 logrotate: empty log files are rotated, old logs are removed
Apr 17 04:00:12 logrotate: switching euid to 0 and egid to 4
Apr 17 04:00:12 logrotate: considering log /tmp/aaaa/aaaa.log
Apr 17 04:00:12 logrotate:   log /tmp/aaaa/aaaa.log does not exist -- skipping

does not exist ???
ls すると見えるんですけど ???
sudo logrotate -d で実行してもエラーになりませんし !!!

... すまん、動揺してしまった。
「empty log files are rotated」の方を気にしないといけない気がする。
前の方で logrotate の動作確認をしたので、そのときに truncate されてファイルサイズがゼロになったままだったのだ。
だからきっと「(ファイル自体はあるかもしれんが)ローテートすべきファイルがない」という意味の「does not exist」なのではなかろうか。
ファイルサイズがゼロにならないようにしてもう1時間待ってみよう。

 

なお、設定ファイルの方は root:root がよいそうだ。
これは私の方もそうしていた。

logrotateの設定ファイル、オーナーが重要 - Qiita
https://qiita.com/abetomo/items/59ee7d55e5458a33988c

ログを格納しているディレクトリのパーミッションが影響することもあるそうだ。
私のはこのエラーログではない。

logrotate ~ ログがローテートされないときに~ | GMOインターネットグループ 次世代システム研究室
https://recruit.gmo.jp/engineer/jisedai/blog/logrotate/

エラーログはそうではなかったのだが、ディレクトリが ubuntu:ubuntu だ!
そういえばアプリは AWS の EC2 に立てた Ubuntu そのままのユーザで動かしているのでそうなっている。

does not exist がサイズが原因だったとして、その次はアカウントの方でエラーになるんじゃなかろうか。
conf ファイルを変更しておきたいところだが、あと 10分くらいで timer の時間になるので待っておこう。

 

「empty log files are rotated」はここ。
LOG_FLAG_IFEMPTY がセットされているということは conf に "ifempty" を書いているということかと思ったのだが、私は書いてない。 "notifempty" もあるので、デフォルトは "ifempty" ということか? なら空ファイルでもローテーションするということではないのか??

https://github.com/logrotate/logrotate/blob/4c52ff5a1ddd0c34096824d786e6a68e20c18136/logrotate.c#L2350

そうならば、ログファイルのサイズを増やした次タイミングでも失敗するに違いない。


1時間経った。
「does not exist」は変わらず。。。
ログファイルはサイズそのままだし、ローテートもされていない。

 

「does not exist」はここ。

https://github.com/logrotate/logrotate/blob/4c52ff5a1ddd0c34096824d786e6a68e20c18136/logrotate.c#L1394

missingok だから return 0 になっているだけだ。

それよりも lstat() が ENOENT になっているせいだということがわかる。
まあ、ENOENT は「ファイルやディレクトリがありません」だろう。
しかし %s しているファイル名と lstat() しているファイルは同じものだから実物の有無以外で ENOENT になっているはずだ。

 

ログの「switching euid to 0 and egid to 4」は、ユーザID を 0、グループID を 4にするということだろう。

https://github.com/logrotate/logrotate/blob/4c52ff5a1ddd0c34096824d786e6a68e20c18136/logrotate.c#L145-L146

conf で su を設定するとそれを使うということだろう。
未設定の場合は

id コマンドで調べると、0 は root、 4 は sync だった。
ならば、ログのファイルやディレクトリが ubuntu:ubuntu なので、logrotate が root:sync に切り替えるとアクセスできないので ENOENT になった、だろうか。

そういうわけで、 conf に「su ubuntu ubuntu」を追加して次のタイミングを待とう。


1時間経過。

はい、変わらず。
su を指定したのに「switching euid to 0 and egid to 4」ってのも変な話よね。
どこかもっと違うところで間違えている気もする。

 

が、せめて単独でコマンドを実行したときと同じ現象であれば調べる気力が湧くのだけど、試せるのが1時間に1回となると非常に効率が悪い。
まずはコマンドラインなどで同じことができるようにしよう。

先ほど /etc/systemd/system にコピーした logrotate.service をリネームして自分の名前にする(aaaa.service とか)。
ExecStart で logrotate の設定ファイルを /etc/logrotate.d/ においた自分の設定ファイルにする。
そして、

sudo systemctl start aaaa

これで、

journalctl -u aaaa

を見てみると、ほら、does not exist に!
これで検証できる。


さて、does not exist にはなったのだが、。

rotating pattern: /tmp/aaaa/aaaa.log  hourly (12 rotations)
empty log files are rotated, old logs are removed
switching euid to 1000 and egid to 1000
considering log /tmp/aaaa/aaaa.log
  log /tmp/aaaa/aaaa.log does not exist -- skipping
switching euid to 0 and egid to 0

そう、su を書いたのだからこうなってほしかったのだよ、さっきも。
それはそれで納得できないし、ここでは切り替えたにもかかわらず does not exist だし。

 

直接 logrotate コマンドを実行すると成功するので、そのログの違いから探っていこう。

rotating pattern: /tmp/aaaa/aaaa.log  hourly (12 rotations)
empty log files are rotated, old logs are removed
switching euid to 1000 and egid to 1000
considering log /tmp/aaaa/aaaa.log
  Now: 2022-04-17 06:43
  Last rotated at 2022-04-17 02:00
  log needs rotating

色を付けたメッセージは skipping の少しあとにある。
やっぱり lstat() をなんとかしないといかんという結論になる。

 

ちなみにまだ status ファイルに記述がない場合はこういうログになる。

considering log /tmp/aaaa/aaaa.log
Creating new state

そして /var/lib/logrotate/status ファイルに追加されて、実際のログファイルには変更無し。
続けて実行しても、何もならなかった(1時間経っていないからか)。

 

そもそも、コマンドラインで実行すると成功して systemctl だと失敗するというなら、service に原因を求めるのが筋だろう。

GitHub のサンプルはこうだった。

https://github.com/logrotate/logrotate/blob/master/examples/logrotate.service

そして Ubuntu の方は下の方がこうなっていた。

PrivateDevices=true
PrivateTmp=true
ProtectControlGroups=true
ProtectKernelModules=true
ProtectSystem=full
RestrictRealtime=true

自分で書くときには指定したことがないオプションだ。
そしてこれらをコメントアウトして systemctl start すると実行するではないか! ローテートされるじゃないか!

もうちょっと前に気付いてもよかったんじゃないかと思わなくもないが、まあよかろう。
私は未来を愛しているのだ。


では、これらのオプションが何かを調べる。
hardening options らしい。

https://www.freedesktop.org/software/systemd/man/systemd.exec.html

PrivateTmp

If true, sets up a new file system namespace for the executed processes and mounts private /tmp/ and /var/tmp/ directories inside it that are not shared by processes outside of the namespace. This is useful to secure access to temporary files of the process, but makes sharing between processes via /tmp/ or /var/tmp/ impossible. If true, all temporary files created by a service in these directories will be removed after the service is stopped. Defaults to false.

ああ、そういうことね・・・。
/tmp に mount してから実行するので /tmp 以下に置いていたファイルは not exist になるということか。

 

/tmp にログをおいたのは、出来心だった。
ちょうどログを出力する実装をしているときに見たサイトが /tmp の下に置いていたのでまねしただけ。
それがこんな悲劇を生もうとは・・・。

いや、私も /var/log とかに格好良く(?)ログファイルを置きたいのだけど、権限が通常ユーザだと足りないので /tmp はちょうど良かったのだ。
仮にログファイルを置くなら、まだユーザディレクトリの中の方がましだったようだ。

2022/04/16

[linux]思ったより logrotate が難しい

現在進行形だ。

常時起動するアプリを作って、ログをファイルに保存するようにした。
アプリの起動時にログファイルを O_APPEND で開いて、あとはそこに書き込むだけである。

そうなると、起動している時間が長いとログファイルが肥大してしまって困ったことになるだろう。
直近の数日分だけあればよいのだが、アプリのログ出力をがんばる気力がない。

そういうときに使うのが logrotate コマンドだ。
そういうときじゃなくても、ログファイルを管理するのに便利だから覚えておきたいところだ。


logrotate 自体は単なるコマンドで、実行されたら設定ファイル置き場にある各アプリの設定ファイルに従ってログファイルを操作する。

誰かが実行しないと動かないので、そこは systemctl やら cron やらを使うようになっている。
Ubuntu 20.04 ではどちらにも仕掛けてあって、 systemctl のタイマーが動いているなら systemctl から、そうでなければ cron から実行するようになっている。

 

と、さも詳しげに書いているのだが、実はまだうまく動かせていない・・・。
直接 logrotate コマンドを動かすと自アプリのログファイルが整理されたのだが、自動に任せると何も行われていないのだ。


まず、 systemctl か cron かを見ていこう。

わかりやすいのは cron の方だと思う。
デフォルトのままなら /etc/cron.daily/logrotate にスクリプトがあるだろう。

$ cat /etc/cron.daily/logrotate 
#!/bin/sh

# skip in favour of systemd timer
if [ -d /run/systemd/system ]; then
    exit 0
fi

# this cronjob persists removals (but not purges)
if [ ! -x /usr/sbin/logrotate ]; then
    exit 0
fi

/usr/sbin/logrotate /etc/logrotate.conf
EXITVALUE=$?
if [ $EXITVALUE != 0 ]; then
    /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
fi
exit $EXITVALUE

スクリプトの通り、cron で実行しない条件が2つある(青文字のところ)。

  • /run/systemd/system ディレクトリがある場合
  • /usr/sbin/logrotate という実行ファイルがない場合

手元の Lubuntu 20.04 はどちらも一致しないので、cron では動かないことになる。

 

では systemctl で動いてくれるのだろう。

$ systemctl list-timers logrotate
NEXT                        LEFT          LAST                        PASSED    UNIT            ACTIVATES       
Sun 2022-04-17 00:00:00 JST 2h 22min left Sat 2022-04-16 20:48:31 JST 48min ago logrotate.timer logrotate.service

1 timers listed.
Pass --all to see loaded but inactive timers, too.

改行されて見づらいが、これを書いているのが 2022-04-16 の 21時半くらいで、 list-timers の NEXT には 2022-04-17 となっているから、次回は明日の 0時に動いてくれるということなのだろう。
なので、タイマーとしては少なくとも動いていると考えて良かろう。

では、systemctl として logrotate が動いているのかどうか。

$ journalctl -u logrotate
...(略)...
Apr 10 00:00:14 あああ systemd[1]: Finished Rotate log files.
-- Reboot --
Apr 11 23:16:00 あああ systemd[1]: Starting Rotate log files...
Apr 11 23:16:01 あああ systemd[1]: logrotate.service: Succeeded.
Apr 11 23:16:01 あああ systemd[1]: Finished Rotate log files.
-- Reboot --
Apr 16 20:48:31 あああ systemd[1]: Starting Rotate log files...
Apr 16 20:48:32 あああ systemd[1]: logrotate.service: Succeeded.
Apr 16 20:48:32 あああ systemd[1]: Finished Rotate log files.

うーん、いま見ている Ubuntu は VirtualBox で動かしていて、あまり長い時間動作させないのだった。
よくわからんが、少なくともエラーにはなっていないので大丈夫じゃないかな。

ただ、起動時に動かしたことしかわからない。
そもそも動作する周期はどうなっているのか?

$ cat /etc/systemd/system/timers.target.wants/logrotate.timer 
[Unit]
Description=Daily rotation of log files
Documentation=man:logrotate(8) man:logrotate.conf(5)

[Timer]
OnCalendar=daily
AccuracySec=12h
Persistent=true

[Install]
WantedBy=timers.target

一日1回か。

しかしこの logrotate.timer にはコマンドも何もないので、service ファイルがあるのか。

$ find /etc/systemd/ -name "*logrotate*"
/etc/systemd/system/timers.target.wants/logrotate.timer

/etc/systemd にはないやん。

しかし /lib/systemd にはあった。

$ find /lib/systemd/ -name "*logrotate*"
/lib/systemd/system/logrotate.service
/lib/systemd/system/logrotate.timer

どうも、 /lib/systemd の方がオリジナルで、 /etc/systemd にもあればそちらが優先されるそうだ。

/usr/lib/systemd/system/と/etc/systemd/system/ - tsunokawaのはてなダイアリー
https://tsunokawa.hatenablog.com/entry/2018/10/05/013034

/lib/systemd の方は

ExecStart=/usr/sbin/logrotate /etc/logrotate.conf

となっていたので、systemctl としても logrotate は呼び出しているはずだ。


では、次は logrotate 自体を見ていこう。

/var/log には logrotate の付いたファイルがなかった。
その代わり、logrotate を実行したときの状態ファイルはあった。

$ ls -l /var/lib/logrotate/status
-rw-r--r-- 1 root root 1624 Apr 16 20:48 /var/lib/logrotate/status

たぶん VirtualBox を立ち上げたときの時刻だろう。 cat で見てもそのくらいの時間になっている項目がいくつかあった。

なので、systemctl から logrotate が実行されていないということはないと思う。
起動時だけ別ルートで呼び出すとかやっている可能性もなくはないが、あんまりそんなことはしないんじゃないかと思う。

 

うーん、 systemctl が起動したときには logrotate が動いている、 logrotate を単独で実行したときにはローテートが実行される、となると、 systemctl がタイマーのところで動いてくれていないということになるのか。


ならば再び systemctl に戻ろう。
timer はこの辺りだそうだ。

systemd/タイマー - ArchWiki
https://wiki.archlinux.jp/index.php/Systemd/%E3%82%BF%E3%82%A4%E3%83%9E%E3%83%BC

logrotate.timer で実行に関係がありそうなのはこの辺りか。

[Timer]
OnCalendar=daily
AccuracySec=12h
Persistent=truen

OnCalendar はカレンダーイベントに合わせて動作するための設定項目。
まあ、これは見たままだろう。

 

AccuracySec はこのページにないな。

systemd.timer(5) — Arch manual pages
https://man.archlinux.org/man/systemd.timer.5

"accuracy"は「正確さ」。
値のデフォルト値は 1分。

Within this time window, the expiry time will be placed at a host-specific, randomized, but stable position that is synchronized between all local timer units.

などと書いてあるので、その範囲内でランダムな時間で実行するということか?
デフォルトなら 1分以内ということだろう。
そしてここの設定なら・・・12時間以内??

なるほど、ログを処理するだけだからあせることはないということか。
システムが暇なときにやってくれれば良いよ、というところなのだろう。

そして私がログがローテートしないと思ったのも、OnCalendar を "hourly" にして daemon-reload し、時間になるまで待って比較的すぐに確認していたからだろう。

毎時実行なのに 12時間以内にやればいいよとなったら、いつ実行されるかわからんわな。

2022/03/27

[RN] 自作証明書をクライアントで使いたい

Android で、aarで組み込んだライブラリの方で HTTPS サーバが起動していて、アプリはその HTTPS サーバに接続してリクエストするような作りになっているとしよう。
その HTTPS サーバは公開しないけど、なんかあったら嫌なので自分で作った証明書を使っている。
さて、そこにどうやってアクセスすると良いのだろうか?

 

最初は「JavaScript self signed」などで検索していたのだが、よく出てくるのが「reject unauthorizedオプションを無効にする」というやつだった。
どう見ても、認証を無視する設定だと思う。開発中はまだよいかもしれんが、今回はしょっちゅう証明書を作り直すわけでもないから認証する方で実装しておきたい。

ようやく出てきたのがこちら。

javascript - nodejs - error self signed certificate in certificate chain - Stack Overflow
https://stackoverflow.com/questions/45088006/nodejs-error-self-signed-certificate-in-certificate-chain

'ca' などというオプションがあるのね。
しかしこれ、 require で 'https' とかやってるし、React Native では使えない気がする。


そう、今回は React Native でやりたいのだ。
React Native は Node.js ではないので同じことができるとは限らない。

Networking · React Native
https://reactnative.dev/docs/network

fetch() は聞いたことがある。
Mozzila の MDN 参照と言うことは node-fetch ではなく標準な fetch() なのだろう。
ということはファイルシステムなんかは使えないと思う。

[Android]How to fetch() from https server with self-signed certificate · Issue #6995 · facebook/react-native
https://github.com/facebook/react-native/issues/6995

うーーーん・・・・・・・・・。

これは Native 側に実装して呼び出すようにした方が簡単だし確実そうだなぁ。

2022/03/23

Lambda関数?

いろいろと耳を塞いで生きてきたと思う。
運が良ければスルーできるのだが、そうならない場合も多い。

Lambda関数はどうだろう?
まだわからないのだが、調べるに越したことはない気がしている。


まずはこちらから。

アロー関数 - TypeScript Deep Dive 日本語版
https://typescript-jp.gitbook.io/deep-dive/future-javascript/arrow-functions

アロー関数の説明で、「または」ということで Lambda関数という呼び名を紹介している。
アロー関数は私もしばしば使う。 function でも => でもどっちでもよいときはどうしたもんだか、と悩みはするものの、アロー関数じゃないとダメなところもたまにあるので、そんなに忌避しているわけではない。

this がどうのこうのは JavaScript の話だと思う。

となると、アロー関数が Lambda 関数とも呼ばれるような要素と言ったら「function と打つのが長い」しか当てはまらないと思うのだよ。

しかし、 Kotlin なんかは fun だし、Golang(に Lambda関数があるかは知らんが) は func だ。これを長いというのはちょっと無理があるだろう。
となると、変数に代入するような書き方ができるとかか?

 

検索すると AWS のも出てくるが、そちらで調べていくと今の知識だと間違えそうなのでやめておく。
あとは Excel と Python が出てきた。
よし、Python で見よう。

Pythonのlambdaって分かりやすい - Qiita
https://qiita.com/nagataaaas/items/531b1fc5ce42a791c7df

最初に「無名関数」と説明してある。
あれ、無名関数って別の呼び方をされてなかったっけ?
無名クラスか。 Java だったような気がするが、入れ子でクラスを書くときにそういうのが出てきたような。

hiro99ma blog: [android]Java8への思いやりを形に
https://blog.hirokuma.work/2015/11/androidjava8.html

Android Studio が私が書いたコードを勝手に省略しようとするから腹が立ったという話だ。
このときもあんまりわからずに終わっているが、結局は見栄えだとか手軽とか、そこら辺が魅力ということなのだろうか。


もっと一般的な何かがあるはずだ。

language agnostic - What is a lambda (function)? - Stack Overflow
https://stackoverflow.com/questions/16501/what-is-a-lambda-function

一気に難しそうな話になってしまった・・・。
少なくとも、私の力で読み解くのは難しそうだ。

無名関数 - Wikipedia
https://ja.wikipedia.org/wiki/%E7%84%A1%E5%90%8D%E9%96%A2%E6%95%B0

ラムダ式・・・関数型言語・・・うぅ、頭が・・・。

 

しかし、JavaScript ってこんな書き方ができるんだ、と思っていた部分の一つが Lambda関数 / Lambda式 / 関数オブジェクト / 無名関数 と呼ばれるものの概念などを言語に落とし込んだ結果なのだろう。

なので、あまり難しく考えず、JavaScript のアロー関数など、と覚えておくことにしよう。どうせ正解はないのだ。

2022/03/21

[win10] AutoHotKeyで複数ウィンドウを対象にする

ショートカットキーのアサインを自分の好きに設定したいことがある。
カスタマイズできるアプリであればよいのだが、できないアプリの方が多いだろう。

私は Windows10 を主に使っているので AutoHotkey を使っている。

AutoHotkey
https://www.autohotkey.com/

AutoHotkey の前は窓使いの憂鬱を使っていたと思うが、Windows7 くらいで開発が終わってしまったので乗り換えたような気がする。


AutoHotkey はいろいろできるのだが、私はキーアサインの変更でしか使っていない。

Hotkeys - Definition & Usage | AutoHotkey
https://www.autohotkey.com/docs/Hotkeys.htm

CapsLock を Ctrl として使っているのだが、それは AutoHotkey ではなくレジストリで変更するタイプにしている。
Ctrlキーとの組み合わせも登録できるならよかったのだが、さすがにできない。

「Change Key」非常駐型でフリーのキー配置変更ソフト - 窓の杜
https://forest.watch.impress.co.jp/library/software/changekey/

Ctrl2cap - Windows Sysinternals | Microsoft Docs
https://docs.microsoft.com/ja-jp/sysinternals/downloads/ctrl2cap

キーの入替は AutoHotkey でもできるのだが、レジストリで変更しているのでアプリがなくても有効だし、なにより「強い」感じがする。

Remapping Keys (Keyboard, Mouse and Joystick) | AutoHotkey
https://www.autohotkey.com/docs/misc/Remap.htm#registry

 

 

AutoHotkey でも PowerToys でもだったが、 Ctrlキーを押すタイプのアサインをしていると、そのアサインを長押しすると Ctrlが抜けて文字がそのまま打ち込まれてしまうことがある。

たとえば Ctrl + n にカーソル移動を割り当てていた場合、 Ctrl + n を長押ししているとときどき「n」がそのまま打ち込まれてしまうのだ。

カーソル移動のキーは長押しすることが多いのだが、テキストエディタでこれが発生すると非常に困る。 Android Studio のように自動保存するタイプだと気付かずに保存されてしまうこともあるし。
レジストリでの変更が「強い」と思うのはこういうのがないところだろう。

 

AutoHotkey のことを調べたが解決しなさそうなので、ソースコードの編集をするアプリ以外は AutoHotkey を通すようにしてみようと考えた。
まあ AutoHotkey がどうやって処理しているのかは知らないのだけど、スルー条件に一致したら何もせずにアプリにキーが通知されるのではないだろうか。そうならば、テキストエディタならキーアサインの変更はだいたいできるので、面倒ではあるがアプリ側で対応すればよいと思う。

そんなわけで、私がよく使っている EmEditor, Visual Studio Code, Android Studio 以外を処理するように書いてみた。

#InstallKeybdHook

SendMode Input

#If !WinActive("ahk_exe emeditor.exe") and !WinActive("ahk_exe studio64.exe") and !WinActive("ahk_exe Code.exe")
    ^f::Send,{right}
    ^b::Send,{left}
    ^n::Send,{down}
    ^p::Send,{up}
    ^h::Send,{backspace}
#If

sc029::Send,{Esc}
^+f::Send,^{f}
^+b::Send,^{b}

 

最初、普通の if 文で書いていたのだがうまく動かずに悩んでしまった。 Hotkey の方は #If 文を使うのだった。

Ctrl+F をカーソル移動に割り当てると、検索をどのキーに割り当てるのかで悩むので、私は Ctrl+Shift+F にしている。
新規作成は Explorer の方でやるから Ctrl+N は使ってよいし、印刷もしないから Ctrl+P もよい。
Ctrl+B は何かというと、Excel でフォントを Bold にするときだろう。

2022/03/12

文字で図を描く

まとめ

  • GraphViz はすごい
  • ASCIIFlow もよいよね

 


おはなし

何を言っているんだか、というタイトルになってしまったが、開発ドキュメントのことだ。

私は比較的図を描くのが好きな方だと思う。
図にできないようであれば言葉でも説明できないだろう、と思っているのだろう。

かなりの場合で Excel を使っている。
Windows10 の「新規作成>Excel」でよく使う Excelテンプレートのファイルを作るように変更するくらいには使っている。
いまだに、Excel より使い勝手のよいツールを見つけられていないのだ。

計算もできる、図も描ける。
編集できるのが印刷単位ではないので広く使えるし、なんなら表だって作ることができる。
画像も貼り付けられるし、それでいてファイルサイズはあまり大きくならない。
OpenOffice 系のツールも、Word や Excel の代替としてはよいと思うのだが、図が描きにくいのだ。そういう意味では Web版の Excel は私に合わないかもしれない。

思い返すと、Excel より前に開発でよく使っていたのは Visio だった。
Visio といっても、Microsoft に買収される前だ。
これはよいね、と思って使っていたら Microsoft に買収されて、これはもっとよくなるかと思ったら・・・・、いかんいかん、昔のことは忘れよう。

ともかく、アプリの好き嫌いは個人でけっこう差があるし、マシンスペックによって受ける印象が全然違うこともあるしで、私の目線で話すことは全然客観的ではない、とだけ言わせてもらおう。

 

話を戻して。

履歴管理と画像ファイルの相性はそれほど良くない。
ファイルが変更されたことは分かるが、どの部分が変更されたかがわかりづらいためだ。

図を描くテキストファイルがあるなら、それを履歴管理すれば良いという考え方もある。
筆頭に上がるのは GraphViz だろうか。
なんなのだろうね、このツールは。

私が初めて GraphViz に関わったのは Doxygen だろうか。 Doxygen には class図みたいなものや C言語の include グラフのようなものを出力する機能があるのだ。
call グラフや caller グラフも出力するし、なんだこれは、と衝撃を受けた記憶がある。

PlantUML もそうだ。
あれも PlantUML の書式でファイルを作っていけばシーケンス図やクラス図などが出力できるのだが、最終的には GraphViz を使っているのではなかったか。
なんなんだ、あれは!

 

GraphVizギャラリーがあった。

https://graphviz.org/gallery/

なんかもう、グラフってなんだろう?と思ってしまうくらいにいろいろな出力があるな。
ともかく、文字列で描画を指示できるのであれば、履歴管理しても多少は差分がわかりやすいだろう。


GraphViz はよいとしよう。
それ以外にないだろうか?
つまり、書式では指示しづらいような図だ。

+--------+
|        |
| hiro99 +---+
|        |   |   +------+
+--------+   +--->      |
                 |  ma  |
                 |      |
                  +--+---+
  +--------+         ^
  |        |         |
  | kuma99 +---------+
  |        |
  +--------+

このような図で十分な場合、私は asciiflow legacy を使っている。
"Legacy" が付かない方もあるのだが、うまく使えなかったので legacy の方にしている。

これはいわゆるアスキーアートのような形で図を描いている。
表示が固定幅フォントなどでできるなら、まあそこそこ履歴管理しても相性は悪くないのではないかな。

[js] Uint8Array はオブジェクト

まとめ

  • React Native → Android へ Uint8Array を渡したいとしても、base64 でエンコードするのが無難そう

 


おはなし

前の記事で ArrayList<Byte> を ByteArray に変換する話をした。
しかし、その前の条件が間違えていた。
React Native から Uint8Array を Android に渡したのだが、Uint8Array はオブジェクトであって配列ではないということだ。

Error: Exception in HostFunction: Malformed calls from JS: field sizes are different.

こんな例外が発生したのだ。

たぶん number[] を渡せばうまくいくのだろうが、やりたいことはそうではない。
おそらく Uint8Array も Kotlin の ByteArray と同じように効率が良いオブジェクトなのだと思う。
それを for とかでぐるぐる回して number[] に入れて渡すなんてことはしたくないのだ。

 

そんなわけで Uint8Array を眺めている。

Uint8Array
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array

まず、 Uint8Array.toString() で文字列にして String.toByteArray() で戻してみた。
・・・ダメ。
toString() は中のデータを無理やり文字列として扱う、ではなく、中のデータを 1バイトずつコンマ区切りで出力する、というものだった。

toLocaleString() がたぶんそういうことをしてくれそうなのだけど、locale の指定をするということは何かしらの変換をバイト単位とかで行うということだと思う。
それだったら・・・それだったら base64 にしてやりとりするのが一番無難じゃないか!

base64 なら無理やりな変換ではなくバイナリ向きだし、文字列になるし、何も問題はない。
問題なのは、せっかくプリミティブ向けに型を作ってあるのに申し訳ないという気持ちだけだ。

 

というわけで、こうなりましたとさ。

JavaScript側 : base64String = Buffer.from(Uint8ArrayData).toString('base64')
Android側 : Base64.decode(base64String, Base64.DEFAULT)

[kotlin] Windowsでコンパイル

最近、話が長くなっていかん。
なので、先にまとめた内容を書いて、あとにだらだらと書き綴ろう。

 

まとめ

  • kotlinc を初回実行するときには結構な量のダウンロードが行われる
  • Kotlin の Array<E> は配列、ArrayList<E> は List<E> の仲間
  • ArrayList の初期値ありは arrayListOf() で作ればよい
  • ArrayList<Byte> を ByteArray に変換するには .toByteArray() を使えばよさそうだ

 


おはなし

 

Android で Kotlin の勉強しつつやっているのだが、勉強のために Android アプリを作るのはめんどくさい。
PC 上で動かせるようにしよう。

Kotlin command-line compiler | Kotlin
https://kotlinlang.org/docs/command-line.html

GitHub から一式をダウンロード(今の最新は 1.6.10) して、zip を展開。
あんまりファイルが大きくなくて良かった。

Hello World の ktファイルを作ってコンパイル。

>bin\kotlinc.bat test.kt

うわー、ダウンロードが始まった・・・。
しかも結構かかる・・・。

最終的に、 %USERPROFILE%.konan に 1GB くらいダウンロードされた。
JavaScript に対する TypeScript みたいな関係だと思っていたけど、Windowsネイティブなコードを吐くようにしているからだろうか?

まあいい。
深くは考えまい。

なお、コンパイルすると program.exe という実行ファイルができた。


初回だし、次回やるかも分からんので、気になっていることを調べて終わろう。
やりたいのは、Array<Byte> でもらったデータを ByteArray に変換したい、だ。

それぞれ違いはなんじゃ?と調べてみたのだが、

  • ArrayList<Byte> は Javaでいうところの Byte[]
  • ByteArray は Javaでいうところの byte[]

らしい。

java - Difference between ByteArray and Array<Byte> in kotlin - Stack Overflow
https://stackoverflow.com/questions/9457942/difference-between-bytearray-and-arraybyte-in-kotlin

もしかしたら ArrayList<Byte> かもしれんが、ともかくオブジェクトかプリミティブ型かの違いが大きいということだろう。
Kotlin は「[]」みたいな配列の作り方ではなく「Array<T>」のような形でしか書けないので、T がプリミティブ型だと効率が悪いだろうってことで ByteArray 型が別に用意されているんだと思う。

 

変換したい背景をもう少し話すと、React Native 側のデータが Uint8Array 型で、それを Android 側で処理することになった。
React Native と Android の型は異なるので、この型はこうなるというマッピングがある。

Argument Types
https://reactnative.dev/docs/native-modules-android#argument-types

JavaScript の Uint8Array は Array だろうから、Java だと ReadableArray というものになる。
ReadableArray は React Native で提供される型でこうなっている。

react-native/ReadableArray.java at main · facebook/react-native
https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java

この中で一番 ByteArray に近そうなのはこれだろう。

ArrayList<Object> toArrayList();

・・・と思ったのだが、ArrayList<Object> なので ArrayList<Byte> ではない。
これは as で変換できてほしい(願望)。

そして ArrayList<Byte> を ByteArray に変換したい、というところなのだ。
いや、それは正確ではないな。実際にやりたいのはファイル保存なのだ。 ByteArrayInputStream というクラスがあったので ByteArray に変換したいだけであって、InputStream になるなら ByteArray である必要はないし、ファイルに保存できるなら InputStream である必要もない。

ノウハウがないときは近道が分からないから困りますな。


Kotlin の ArrayList には toByteArray() というメソッドがある。
これでいいのかな?

toByteArray - Kotlin Programming Language
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/to-byte-array.html

 

では、まず ArrayList を作ろう。
・・・書き方が分からん。
Kotlin は ArrayList<T> しかないと思っていたのだが、 Array<T> もあるのか。

Arrays
https://kotlinlang.org/docs/basic-types.html#arrays

Array なら arrayOf() で初期値が作れそうなのだが、 ArrayList は?
そもそも、 Array と ArrayList の違いは何なのか??

 

ArrayList はこんな感じなので、MutableList の仲間。

class ArrayList<E> : MutableList<E>, RandomAccess

class ArrayList<E> : MutableList<E>, RandomAccess, AbstractMutableList<E>

List を Array っぽく使えるようにしただけで、あまり Array と関係はないのだろう。
なので初期化も ArrayList のコンストラクタを使えばよかろう。

 

ArrayList のコンストラクタは 2つ。
空の ArrayList を作るか Collection<E> で初期値を持つか。
Collection に arrayListOf() という関数があるそうだ。

arrayListOf - Kotlin Programming Language
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/array-list-of.html

いやー、これは ArrayList の方にもリンクを貼っておいてほしいよ。わからんよ、探したよ。
全体的に~Of() という関数は ~型 の new関数みたいに作ってあるように見える。

 

01: fun main() {
02:     // val va: ArrayList<Byte> = arrayListOf(1, 2, 3, 253, 254, 255)
03:     val va: ArrayList<Byte> = arrayListOf(1, 2, 3, -3, -2, -1)
04:     val v8: ByteArray = va.toByteArray()
05:     for (v in v8) {
06:         println(v)
07:     }
08: }

>.\program
1
2
3
-3
-2
-1

Java と同じく Byte 型は int8 の範囲なので 128~255 はダメだった。
まあ、問題はそれだけだ。

[win10]音量のデフォルト値は100

最近、ワイヤレスのヘッドセットを購入した。
Windows10 で Bluetooth 接続して音を鳴らすと、音量が 100 になっていて非常にビックリした。

まあ、下げれば良いだけなのだが、デフォルト値が 100% というところが困ったものだと思う。
そしてデバイスごとに値を覚えているので、いまの音量と同じになるつもりでデバイスを切り替えると 100 だった、ということがある。

例えば、これは今の私の PC の状況だ。

image

ワイヤレスのヘッドセットをつないだので一番上の「ヘッドホン」が選択されている。
これがさっきは音量 100 だったので、今は 27 に変更している。

さて、ここで zoom や Google Meet を始めたいとしましょう。
ここでは Meet で話を進める。
今回はマイクも使いたいから、Meet の設定画面でマイクのデバイスを設定しよう。
スピーカーとマイクが別のデバイスだと警告が出るので、じゃあスピーカーも同じものを選びましょう。

image

警告が消えた。

image

で、ここでスピーカーのテストをすると大音量になってビックリする、というわけだ。
Windows の方でハンズフリーの方の音量を確認するとこうなっていた。

image

そう、別デバイスとして判断されるので、ヘッドホンのほうで設定した音量とは別になっており、デフォルト値の 100 が使われているのだ。

 

このデフォルト値を変更できれば良いのだけど、探しても見つからんかった。

なので今できるのは、新しいデバイスをつないだらとにかく全部の音量を見直す、ということくらいだ。

2022/02/28

Windows10にインストールしているアプリたち(2022年)

Windows 環境を作り直したついでに、インストールしているアプリたちをリストにしておこう。

 

最終更新日:2022/03/06


Chocolatey

Chocolatey Software | Chocolatey - The package manager for Windows
https://chocolatey.org/

ちょこらてい?

Ubuntu でいうところの apt みたいなものだ。
winget や scoop とかいろいろあると思うのだが比較したことはない。最初に気付いたアプリがそれだったというところだ。

GUI はそれを GUIで見せてくれるアプリだ。
便利ではあるのだが、これをインストールするといくつか追加でインストールされるものが出てくるので、ちょっと哀しい気持ちになる。なんで AutoHotKey が必要になるんだろうね?

ノートPCで使っていた設定を export して持って来ることができたのはよかったのだが、一覧の取得が今ひとつだったりする。ノートPC の方では普通に取れているので、いきなり import したのが何か良くなかったのか?

インストーラが、ものによってはフル設定でインストールされるのか、要らない設定でインストールされてしまうこともある。VLC media player でコンテキスト設定は不要だったのだけど、それを外す方法が分からなかったりとか。

インストールを Chocolatey で行って、アンインストールを Windows から行うと、 choco のリストには残ったままになってしまった。
これはまだ対処の仕方を調べていない。

 

それでも、あちこちからダウンロードとインストールを繰り返すことを考えると非常に楽である。


C電卓

C電卓
http://millgran.fc2web.com/soft.html#cCalc

10進数、16進数、2進数で表示してくれる電卓アプリ。
Windows のストアアプリにある電卓でもプログラマー用はあるのだが、私の感覚にちょっと合わなかったのだ。


Executor

Executor
https://executor.dk/

ランチャーだ。
コマンド入力タイプのランチャーだ。

bluewind をずっと使っていたのだが、メンテナンスされなくなったので乗り換えた形だ。

なんだろうね、なんでメニューがあるのにわざわざランチャーを使うのだろうね。
私にもまだ答えは出ないのだが、不思議なことにメニューを使うアプリとランチャーを使うアプリがなぜか棲み分けされている。


TeraTerm
Windows Terminal

Tera Term Open Source Project
https://ttssh2.osdn.jp/index.html.ja

Windows Terminal を入手 - Microsoft Store ja-JP
https://www.microsoft.com/ja-jp/p/windows-terminal/9n0dx20hk701?SilentAuth=1&wa=wsignin1.0&activetab=pivot:overviewtab

並べるようなものでは無いかもしれんが、Terminal ということでまとめてみた。

SSH するなら TeraTerm、コマンドラインなら Windows Terminal というところか。
TeraTerm が cmd.exe を扱えたなら Windows Terminal は使わなかっただろう。

 

私の環境では WSL は使っておらず、Linux系のアプリ開発は VirtualBox で行っている。
正直なところ、Linux環境をメインにして VirtualBox などに Windows をインストールした方がメリットが大きいような木はするのだが、踏み出せないところだ。

Windows のコマンドラインを使うのも、だいたい Android で adb を使いたいだけだったり、 npm を使いたいだけだったりするので、実はそれほどコマンドラインのコンソールを使いたいことはない(開発以外では)。

Windows Terminal はコンテキストメニューに「このフォルダから開く」が追加されるので、それだけで使っている気がする。


AutoHotKey と PowerToys

AutoHotkey
https://www.autohotkey.com/

Microsoft PowerToys | Microsoft Docs
https://docs.microsoft.com/ja-jp/windows/powertoys/

なぜこの2アプリが並ぶかというと、ショートカットキーの割り当て関係だからだ。

Windows のショートカットキー割り当てとしては、レジストリを変更するタイプが昔はほとんどだった。
しかし最近はアプリレベルで差し替えるタイプも増えていて、それがこの 2つだと思っている。

 

ただ、どちらも完全に置き換えはできない。
今のところ AutoHotKey を使って、PowerToys のキー割り当て機能は無効にしている。

単発のキーはどちらもできるのだ。
問題は長押しというか、押しっぱなしにしたとき。
私はカーソル移動だけは Emacs っぽくなっていて、

Ctrl+ P : Previous で ↑
Ctrl+ N : Next で ↓
Ctrl+ F : Forwar で →
Ctrl+ B : Back で ←
Ctrl+ H : backspace

という割り当てを使っている。
カーソル移動なのでしばしば長押しにするのだが、たとえば Ctrl+N を長押ししていると、たまに Ctrl がはずれて n が入力されたりするのだ。
Intellij のように重たいエディタを使っていると起きやすいのだが、そうでなくても起きる。


PDIC

PDIC/Unicode Release
http://pdic.la.coocan.jp/unicode/

かなり昔から使っている。
Nifty Serve というサービスがあったのだが、そこで電子辞書ファイルがダウンロードできて、それがこの形式だったのだ。
当時は数日掛けてダウンロードしたよなぁ、と懐かしい気分になる。

今やオンラインでの翻訳サービスが無料で使えるので使わなくて済みそうなものだが、無料版だとお仕事での翻訳(サイトを日本語で読みたいとかではなく、業務で外部に出せない内容を翻訳したいときなど)で使うわけにはいかないので、ちょっとした単語検索によかったりする。


TTClock

TTClock - Chihiro's Page
http://chihiro718.jpn.org/software/TTClock/index.html

カスタマイズしてもしなくてもよさそうな、タスクトレイに表示されている時計のカスタマイズアプリ。
私はこんな感じで表示させている。

image

年表示はまだよいのだけど、曜日と秒数がほしい。
昔は曜日なんて必要としてなかったけれど、家にいる時間が長いと曜日も分からなくなってきてね。。。

ポップアップで令和表示してくれると助かるときもあるのだが、まあそっちはなくてもだいたいなんとかなる。
元号での表示がいるのって、いまやお役所くらいだしね。


Windows Firewall Control

Windows Firewall Control
https://www.binisoft.org/wfc.php

Windows標準の Firewall 設定を管理するツール。
最初は binisoft さんが出していたが、Malwarebytes社のシリーズの一つになったのかな?

この手のアプリをインストールしたことがある人はわかると思うが、通信を許可制にすると問題が起きることが多い。初めて通信するアプリの場合はユーザにどうするか確認してくれるのだが、その確認待ちの間に通信失敗と判断してアプリがエラーになるのだ。
たまに聞いてこずに遮断されることもあり、そういう場合は裏でエラーになったことにも気付かない、みたいなこともある。

通信を許可したアプリであってもアプリのアップデートが行われることで再設定が必要になったり、アップデートするアプリをダウンロードするために許可しないといけなかったり。DropBox なんかがそうで、これに気付かないとアップデートされないのだ。

ユーザに聞かずに通す設定もあるのだが、それだとそもそも意味がない気がするので、PC を入れ替えてアプリをたくさんインストールするときはそういう Learning モードにして、落ち着いてきたら確認するモードに切り替えたりしている。

 

そんな面倒しかないツールを使うのは、やはりよくわからないアプリを通してしまうことが怖いからだ。
よくわからないアプリなので気付かないこともあると思うし、そもそも通信しないアプリがあまり多くないので結局許可しないといけなかったりするのだが、いつでも禁止にできると思うと心が安らぐ(?)のだ。

そういう意味では、Learning モードで自動登録にしておいて、あとから遮断していくというやり方がよかったかもしれない。

 


FileMenu Tools
Easy Context Menu

FileMenu Tools
https://www.lopesoft.com/index.php/en/download/filemenu-tools

Easy Context menu v1.6
https://www.sordum.org/7615/easy-context-menu-v1-6/

コンテキストメニュー関連。

似たようなことができる二人なのだが、Easy Context Menu の方がコンテキストメニューを減らすのには向いていると思うし、FileMenu Tools は SendTo が変更できる。

あと、Easy Context Menu はショートカットを作ったときの名前に「- ショートカット」と後ろに付くのを止めるオプションもある。地味に便利。 choco install ecm なのは探しにくいと思った。

2022/02/27

Windows10とnvm

JavaScript を使うことがある。主に node.js で。

node.js を Windows で使う場合、node.js のインストーラをダウンロードしてインストールするだろう。
しかし、環境によって node.js のバージョンを指定されることがある。
Java で jdk8 だの jdk11 だのの選択があるのと同じようなものだ。
個人で開発するときならまだよいのだが、プロジェクトでやるときには同じバージョンをインストールしていないと面倒なことになりかねない。

 

Linux で作業するときには nvm を使っていた。

nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions
https://github.com/nvm-sh/nvm

POSIXコンパチブルと書いてあるように、Windows だと使えない。
似たようなものとして nvm-windows がある。

coreybutler/nvm-windows: A node.js version management utility for Windows. Ironically written in Go.
https://github.com/coreybutler/nvm-windows

GitHub からバイナリをダウンロードしてインストールすればよいのだが、Chocolatey で環境を作るようにしているのでそちらでインストールを今までしていた。
ただ、これを書いている時点(2022/02/27)で GitHub でのバージョンは 1.1.9 なのだが、Chocolatey の方は 1.1.51.1.7 だった。
なので、比較的最近まで 1.1.7 をインストールして使っていた。

 

最近まで 1つの node.js バージョン(v14系)しか必要としていなかったのだが、v16系を使う必要が出てきた。
ようやく nvm が nvm っぽいことをするときがやってきたのだ。
v16.14.0 をインストールすることはできたのだが、 npm i するとエラーになるのだ。

エラーメッセージがなんだったかもう忘れてしまったのだが、1.1.9 をインストールするとよいという回答が見つかったので GitHub 版にしたらエラーが出なくなった。

その後 1.1.7 に入れ替えても動いたのだが、ようわからんので 1.1.9 のままにしている。
おそらく Chocolatey の nvmは chocolatey-nvm-windows が使われているのだと思う。

なら chocolatey の nvm でバージョンを 1.1.9 と指定したらいけるんじゃないの?と試してみたのだが、nvm 1.1.5 の方でバージョンを指定しても 1.1.7 がインストールされてしまった。ダメか。


node.js を普通にインストーラーでインストールすると、 Program Files\node.js というディレクトリを作ってインストールされる。
nvm 1.1.7 を使うと Program Files\node.js はショートカットというかたぶんシンボリックリンクになって、本体が ProgramData\node.js に置かれるようになっていたと思う。

 

nvm 1.1.9 では AppData\Roaming\nvm にインストールされた。
しかし「nvm use」をそのまま実行するとエラーになる。

>nvm use 16.14.0
exit status 1: �A�N�Z�X�����ۂ����܂����B

読めないのだ・・・。

管理者権限を持った状態で nvm use すると動作する。
そのときに Program Files\node.js にシンボリックリンクが張られた。

やっぱりオリジナルが Program Files\node.js にインストールするようになっているので避けられないのだろうか。
これはこれで面倒だ。


Microsoft のページに代替のマネージャーが載っていた。

代替のバージョン マネージャー
https://docs.microsoft.com/ja-jp/windows/dev-environment/javascript/nodejs-on-windows#alternative-version-managers

vscode とも連携できるようなので nvs をインストールしてみた。
Chocolatey からもできる。それに Windows 以外でも使える。

jasongin/nvs: Node Version Switcher - A cross-platform tool for switching between versions and forks of Node.js
https://github.com/jasongin/nvs

コマンドは nvm と同じような感じだが、 nvs add を使うのがよいのかな。

>nvs add 16.14.0
Downloading [#############################################################] 100%
Extracting  [#############################################################] 100%

>nvs use 16.14.0
PATH += %LOCALAPPDATA%\nvs\node\16.14.0\x64

>node --version
v16.14.0

ほう。
nvs use はその場で PATH に追加するだけなので権限は必要にならなかった。

 

それになんと、ディレクトリごとに自動で切り替える機能もある。
bash と powershell 向けと書いてあるが・・・あれ、cmd.exe では使えないのだろうか。

>nvs auto on
Automatic switching is not supported from a Windows Command Prompt.
Use PowerShell instead.

ダメか。
なんか powershell は苦手なのだが、node.js で使うだけだから我慢しよう。
文字色がダメなんだと思う。「node --version」だとこうなるのだ。

image

背景が真っ黒なら、まだなんとかなるか。

image

色をカスタマイズしたいと言うよりは、色分けを止めてほしいだけなのだが、見つからなかった。
それに、vscode を起動して自動的に Terminal が希望したディレクトリを開いた場合には auto が効かないようだったので無理して powershell を使う必要もなさそうだ。

 

手動で行う場合は、.node-version というファイルを作ってバージョン名を書いておく。
そして nvs use と打つと設定してくれる。
.nvmrc でもよいように書いてあったのだが、そちらだと動かなかった。 cmd.exe だったからか?
まあいいや。

Linux でやるときには nvm と direnv を組み合わせていたのだけど、 nvs ならこれだけで切り替えられるので便利そうだ。

 

あとはまあ、無意識のうちに nvm と打ち込んでしまうのが難点というかまだ慣れていないというかだな。

2022/02/26

速くなったPC (2022年)

数回に掛けて記事にしたデスクトップPCの置き換え作業。

hiro99ma blog: PCを作り替えたい(2022年)
https://blog.hirokuma.work/2022/02/pc2022.html

 

hiro99ma blog: 6年くらい経つとマザーボードを交換して起動ディスクを認識しないのかもしれない
https://blog.hirokuma.work/2022/02/6.html

 

hiro99ma blog: PCの入替はやはり大変だ(2022年)
https://blog.hirokuma.work/2022/02/pc2022_23.html

問題がなければ今回の記事で終わりだ。


まず、光るマザーボード&メモリについて。

私はてっきり光るのは「ゲーミング」というものだけだと思っていた。
CPUクーラーにも LED有無があるくらいだし、何も書いていないなら光らないだろう、そう思っていたのだ。
だって、マザーボードの名前が「Steel Legend」ですよ。お堅い名前ですよ。

・・・現実は非情であった。

image

光るというか、ぐりぐり光っている。ゲーミングの光り方だ。
イルミネーションだ!

写真には収まっていないがメモリも光っている。
装着前は「最近のメモリはヒートシンクもしっかりしてるのねー」なんて思って眺めていたのだが、その部分は LEDを光らせるためにあったのね。

 

イルミネーションだが、マザーボードの方は UEFI の設定で消すことができた。
まさか Advance の中の方にあったとは・・・。もっと表の方にあるかと思っていたよ。
メモリはユーティリティをインストールすれば制御できると思うが、まだやってない。

 

なんでわざわざ消すかというと、マザーボードに通電しているだけ、本体が起動していない状態でも光り続けるからだ。
組み立てた当日はケースのカバーを開けたままだったので、夜中に部屋がキラキラしていたものだ。
カバーをすればほとんど気付かない。

なら消さんでもいいやん、となりそうだが、見えないところで光ってもらうのが何だか申し訳ない気持ちになってきてね・・・。
ケースのカバーはもしかしたら透明な方がよいのでは?みたいな思考になってしまいそうな気がしたので、その世界に走らないように歯止めをしたのだ。

 

正直なところ、光る前までは否定的だったのだけど、実際光ってみると悪くないと思うようになった。
光る=ゲーミング=ゲームをする人向け、というイメージだったが、よく考えればゲーム中はかなり集中しているだろうから見ている暇はない。見るなら休憩中とかじゃなかろうか。何度やってもクリアできないときとか。

そうであれば、ゲームしていない人だって眺めていてよいはずだ。
むしろ休憩を取りにくいオフィスなんかでこそキラキラしているべきではないだろうか。
みんなの PCがキラキラしているオフィス。
キラキラ~キラキラ~

いや、キラキラは少ない人数で楽しむのがよいのかもしれない。


さて、前回組み立ての時にいろいろトラブルはあったものの、あれから問題はない。
いろいろ新規インストールすることになって面倒はある、多々あるのだが、面倒であって問題ではないのだ。

 

忘れていけないのは、そう、 PC が速くなったのだ。
そのために苦労した(+お金)のだ。

Cinebench というベンチマークアプリで計測したのだが、変更前が 3200 くらい、変更後が 13600 くらい。
桁が違いますわー。
そういえば前回のマシンはオーバークロックしてみてもよかったかな、と今さら思った。忘れていた。

ちなみに、ノートPC は 1700 くらいだ。
前のデスクトップの半分くらいの速度で動いているというわけではない。体感的にも。
なので、今年はこのノートPC のままでいてもよいかもしれない。


Android Studio のビルドも劇的に・・・とは言えないかもしれないが、短くなった。
Unity も試してみると良いのだろうが、もともとデスクトップPC では動かしていないので比較できない。ただこのノートPC でやるようなスペックじゃないだろうということはわかってきた。

以前は「終わりましたか?終わりましたか?」と上司に伺いを立てるような気分で待っていたが、今はそこそこ早く処理が終わるので「終わったか、なら待っておれ」みたいな心の余裕ができるようになった。まだキャッシュがなかったりしてダウンロードの時間がかかったりするものの、理由があって理不尽なわけではないので待つことができる。

 

そうね、問題といえるものではないが、以前使っていたパーツ類をどうしようかという残件がある。
なんたって、今週までは普通に使えていたのだ。いくらスペックの桁が違ったとしても、そこまで悪いものではない。
罪悪感・・・というと大げさだが、そんな気持ちだ。

かといって、また戻りたいかと言われると無理だ。
ああそうさ。私は自分勝手な生き物さっ。
そう偽悪的につぶやいて酒を飲むのであった。

2022/02/23

PCの入替はやはり大変だ(2022年)

PC を入れ替えるのは予定通りだったのだが、Windwos10 の起動ドライブを認識しなくなるのは予定外だった。

まあ、Windows の入れ直しと言っても、当面の開発なら Android Studio と VirtualBox があればなんとかなるね・・・というのが甘い考えだったことを認識して早数時間。

 

細かいところがいつもと違うのが非常にストレスになるので、そこに時間がかかる。
デフォルトで扱えるようになっているとこのコストが下がるのだが、もうダメだ。

バックアップから元に戻すならそこまで面倒ではないのだが、ゴミが残っている、という気持ちを持ったまま数年間すごすのも嫌なものだ。


失敗談の方が興味あると思うので、書いておこう。

  • DIMM が 4つあるうち、一番 CPUに近いところがクーラーとぶつかる
    • CPU に近い方から、A1, A2, B1, B2 となっていて、A1 が使えなくなった
    • A1-B1 で組、 A2-B2 で組ということなので、A2-B2 を使うようにした
      • これが次の悲劇を生む
  • 立ち上がらない
    • ASRock のマザーボードは Dr.Debug という 7セグの表示がある
    • 0d=メモリ関係ということだった
    • DIMM を挿し直してもダメ
    • A1-B1 に挿し直すことにする
      • CPUクーラーが干渉するので、取り外して 180度回転させれば良いだろう
      • あれ? CPUクーラーが CPUからはずれないぞ??
      • がんばって外すと、CPU ごと抜けた!
        • 幸いなことにピンは折れたり曲がったりしてなかった
      • CPU と CPUクーラーを引き剥がすのが大変だった
        • CPU のピンを触らないように、しかしかなり力を入れて CPU を回転
        • 少しずつ動いたので、回転と同時に移動させる
        • そうすることでグリスの影響を薄くしていくと、ようやくはがれた
        • 再度 CPUを挿して、グリスも継ぎ足してクーラーを付け直した
    • A1-B1 に挿し直すと起動した
      • でも、それってこれ以上増設できないと言うことになるのでは・・・?
  • 起動ドライブを認識してくれなかった
    • たぶん Legacy BIOS 向けになっていたからだろう
    • 別 PC で GPT に変換したけど認識してくれなかった。まだ何かあったのか。
    • あきらめて新規インストールした
  • 今まで HDMI と DVI でデュアルモニタにしていたのだが、DVI ポートがなかった
    • DisplayPort があったので、DisplayPort ケーブルを買いにいった
    • 1.4 対応だったので 1.4 のケーブルにしたのだが、1.2 のに比べると値段が倍くらい高い
    • PC とモニタの距離はそんなに離れてないと思って 1メートルのケーブルにしたが短かった...
    • ふだん、メインモニタが正面、サブモニタが右という並びにしているが、メインモニタ--PC 間では遠かったのでサブモニタにすることでかろうじて届くようになった
    • しかし、PC が DisplayPort の方をメイン出力にするので、何かあるとサブモニタの方に出力されるのがもどかしい。Windows のプライマリモニタを設定するとだいたいいけるのだけど、たまになんかある。
  • HDMI の方がなぜか 640x480 でしか認識してくれない
    • 本当はサブもメインも 1920x1080
    • 確かにモニタはそんなに新しくないのだが、一応はプラグアンドプレイ対応している
    • にもかかわらず 640x480 にしかならない
    • Radeon 関係でインストールしたアプリの中に解像度を作り出す機能があったので、それで対応した

 

いろいろありすぎだ。
おかげでインストールするものが全然終わらない。
本当に大変ですわ。

6年くらい経つとマザーボードを交換して起動ディスクを認識しないのかもしれない

パソコンの部品が届いた。

hiro99ma blog: PCを作り替えたい(2022年)
https://blog.hirokuma.work/2022/02/pc2022.html

せっせと組み立て、BIOS ... 今は UEFI というのか、その設定画面まで表示された。
あとは今まで使っていた Windows10 の起動ディスクを指定して立ち上げるだけ、と思っていたのだが、ドライブは認識しているもののどれもこれも起動ディスクとして扱われないようなのだ。唯一 DVDドライブが出てきた。

 

あれー、なんでー、といろいろ調べていると CSM なるものがあるそうだった。

【サポート情報】最新世代のマザーボードでのLegacy BOOTには注意しよう - ツクモ東京地区 店舗BLOG
https://blog.tsukumo.co.jp/tokyo/2020/09/legacy_boot.html

今回は B550チップセットのマザーボードにしたのだが、設定を見ると確かに CSM は無効になっていた。
じゃあ有効にしたらいいやんと思ったのだが、どうも項目が見当たらない。
Ryzen7 5700G なので、ここに書いてある「CPU内蔵グラフィックスを使用する場合~」のことが該当して有効にできないということだろうか。

 

あきらめた私は、Windows10 を新規インストールすることにした。
新しい酒は新しい革袋に、というわけではないけど、6年もあるとあれこれインストールしてはアンインストールしてを繰り返しているので、大量にゴミが残っていると思う。

そうだ、これは無駄な時間ではないのだ、きれいにするための必要な時間なのだ!

chocolatey を使ってインストールしているなら、export するとまとめてインストールできるそうだ。

Chocolatey Software Docs | Export
https://docs.chocolatey.org/en-us/chocolatey-gui/user-interface/main-window/actions/export

まあ、前環境で export してなかったけどね!!
ノートPC のがあるから多少はましだろう。

2022/02/20

docker いろいろ消したい

訳がわからなくなったり、全部無かったことにしたかったり、空き容量が足りなくて削除したくなったり、そんな気分になることがあると思う。

docker rm `docker ps -qa`
docker rmi `docker images -qa`
docker network rm `docker network ls -q`
docker volume rm `docker volume ls -q`

このくらいやれば、だいたい消えるんじゃなかろうか。

 

これは関係ないが、docker ps は横に長く出過ぎて見づらいことがある。
私のところでは Images のところが長いことが多いので、 --format で Images などを出さない docker ps を登録した。

docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Command}}\t{{.Ports}}"

これで「CONTAINER ID, NAMES, STATUS, COMMAND, PORTS」だけになる。

2022/02/19

PCを作り替えたい(2022年)

うちにはデスクトップPCとノートPCがある。
デスクトップPCは2016年の4月に部品を買って作った記録が残っていた。
そろそろ新しくしても良い季節ではないだろうか。


2016年の構成はこうだ。

  • CPU: Intel Core i5 6500BOX (3.20GHz/6M/C4/T4)
  • マザーボード: ASRock Z170 Extreme4(Z170 1151 ATX)
  • メモリ: DDR4-1700 8GBx2
  • 電源: ATX 550W
  • SSD: 240GB

ケースと電源は使い回してよいんじゃなかろうか。
これの1世代前のPCが残っていて、そのときはケースを買い直しているため、今は2つケースがあるのだ。
廃棄してしまえばよいと思いつつ、なんかこう踏ん切りが付かず物置として使われている(ケースの上が平たいので)。
ここでケースをまた買うと、たぶん捨てるのが面倒で増えるだけになってしまうと思う。

 

この構成だと Windows11 には対応していない。
TPM もないし CPU も対応外だ。
まあ、今の時期に買えばそういう心配はないだろうし、きっと Windows11 ready みたいな表記もあるんじゃなかろうか。

 

電源が心配な気もするが、まあなんとかなるんじゃないの?

 

SSD はちょっと気にしている。
メインの SSD は 2016年に買ったものだが、それ以外に HDD が 2つくらいと SSD が 2つくらい使っている。
フルバックアップ用に外付けUSB のもある。

HDD は Windowsの「ファイルの履歴」で割り当てているのと、ほとんど使わなくなったけど捨てて困るかもしれないデータを押し込めるのに使っている。使ってないけど余らせるのももったいないので接続しているという状況だ。
ファイルの履歴も、設定しているのを忘れているくらいには使っていない。パフォーマンスが落ちないなら有効に戻しても良いけど、開発で使っている限りでは git やクラウドに保存している方しか思いつかないので、昔ほど重要ではない。

サイズが異なる SSD にシステムを移動できるなら、2016年の SSD からコピーして使いたいというくらいかな。
Windows をインストールし直したい気持ちはものすごくあるのだが、また環境を作り直すというのも精神的につらい。


まずは CPU を決めねば。

ここ 2世代ほど Intel だったので、今回は AMD にしようか。
以前は Android Studio でエミュレータが高速に動かすには HAXM が~なんて考えていたけど、実機で動かすならどうでもよい話だ。

あと、最近思ったのは、意外と私がやってる作業って CPUパワーが必要なんじゃなかろうか?ということだ。
大したことをやってないからそこそこのパワーで良かろうと思っていたのだけど、コンパイルも並列でやったりしてようやく「普通」な時間で行えるようになっている

 

というところで考えると、Ryzen7 5700G とかだろうか。

AMD Ryzen™ 7 5700G | AMD
https://www.amd.com/ja/products/apu/amd-ryzen-7-5700g

AMD Ryzen 7 5700G BOX |パソコン通販のドスパラ【公式】
https://www.dospara.co.jp/5shopping/detail_parts.php?bg=1&br=10&sbr=1299&mkr=25&ic=474643&lf=0

ドスパラで 46,300円かー。
あ、グラフィックボードは買い足すつもりがないので G が付いたやつにしている。
これより 1つ下がると Ryzen5 5600G なのかな。

AMD Ryzen™ 5 5600G | AMD​
https://www.amd.com/ja/products/apu/amd-ryzen-5-5600g

AMD Ryzen 5 5600G BOX |パソコン通販のドスパラ【公式】
https://www.dospara.co.jp/5shopping/detail_parts.php?bg=1&br=10&sbr=1299&mkr=25&ic=474644&lf=0

33,300円と 13,000円お安い。
ただまあ、これも 5年くらい使うんだろうからケチらない方がよいだろう。


マザーボードというものもあるが、AMD の Ryzen だと選択肢が Socket AM4 しかないので探しやすい。
まあ、数はたくさんあるんだけど少なくとも動かないことはないだろう。

チップセットは B550 と X570 というのが並んでいる。
数字が大きい X570 の方が新しいかと思いきや、 B550 の方が新しいようだ。

ソケットAM4チップセット | AMD
https://www.amd.com/ja/products/chipsets-am4

Ryzen 5000シリーズはどちらでもよいそうだ。

よくわからんが・・・ B550 でよいのではなかろうか。新しい方だし。
でも X570 の方が全体的に値段が高めなので性能も高めなのだろうか。
たぶんどれを選んだとしても、あとから違いに気付くとは思えない私であった。。。

なので、わかりやすくインターフェースだけで考える。
Type-C はほしいかな。できれば背面に。
あとは SSD の M2 も使ってみたい。「M.2」と間にドットが入るのか。

M.2 SSD はなんとかなりそうだが、Type-C の方があまりなさそうだ。
今使ってる ASRock のは Type-C が付いているようなので、それでよいかな。

どれを選んだとしても、メモリは DDR4 なのは同じだけど PC1700 はサポートしていないようだ。
だからメモリも一緒に買うことになる。
M.2 SSD を使うならそれもだ。
あああ、お金がかかることだよ。

B550 の ASRock というところまで絞ったところ、Steel Legend と PG Riptide との 2つがあてはまった。
もう違いが分からないけど、Steel Legend は耐久性を考慮しているようなので、長いこと使うつもりならこっちにしておくのが無難なのかな。
むかし、マザーボードのコンデンサから液漏れして買い替えたことがあるので、耐久性は気になるのだ。


あとは適当に選ぼうと思ったが、M.2 SSD は買ったことがないので値段を見ておこう。

SSD、ソリッドステートドライブ、ハイブリッドハードドライブ-ドスパラ
https://www.dospara.co.jp/5pc_parts/shopping/br115/

同じサイズと仕様っぽく見えても値段がいろいろある・・・。

1TB くらいなら、書込速度が 5000MB/s くらいだと 2万円付近というのが相場のようだ。PCIe 4.0 だとその速度が出せて、 PCIe 3.0 だと 3000MB/s くらいになるようだ。
500GB ならその半分かというと、どうもこのサイズだと 5000MB/s というのが出てこない。

 

B550 はそもそも PCIe 4.0 に対応しているんだろうか?

  • 1 x Hyper M.2 Socket (M2_1)
  • 1 x M.2 Socket (M2_2)

うーん、M2_1 の方は 64 Gb/s or 32Gb/s、M2_2 の方は 16Gb/s という読み方で良いのかな?
Gen4x4 とか Gen3x2 とかがそういうもののように書いてある。

PCIe 4.0 のほうは大体 5000MB/s だが、PCIe 3.0 はまちまち。
500GB くらいあれば事足りるのだが、それは今だけかもしれないから 1TB にするべきか・・・。
ここでケチってもなあ、でもストレージって消耗品だからなあ、でも途中で別のにコピーしたりする手間は嫌だなあ・・・

金額と手間の間で悩む私であった。

 

メモリは、32GB でほどほどならよいかな。
G.Skill という会社のは初めてだが、そんなに悪いものでもないだろう。
同じ G.Skill社の DDR4-3600 でも、

の3つが並んでいてあせった。
たぶん 19, 16, 18 はレイテンシーだから 16 のが一番高いのだろう。そこまで気にしないので一番安い 19 のでよかったのだけど、QVLページに AMD が載っていないので 3番目のにしよう。


では。

くぅ、消費税も加えると 11.5万円くらいか・・・。
あ、価格が税込みだった。10.4万円くらいだ。

ノートPC を購入すると思えば安いものだが、しばらくするとノートPCも買わねばならんだろう。
いや、いつも同じ時期に両方とも買い替えるから、どちらも同じようなスペックになって、同じような時期に買い替えないといけなくなってしまうのだ。

よし、当面はノートPC の買い換えは止めておこう。後のことは分からんが、今のところは止めておこう。

2022/02/14

LXQtのメニューカスタマイズメモ

メモです。個人メモです。

Lubuntu 20.04 で LXQt を使っている。メニューをカスタマイズした。

image


大元のファイル?

/etc/xdg/menus/lxqt-applications.menu

個人用?

$HOME/.config/menus/lxqt-applications.menu

Configure "Applicationi menu" で指定するファイル

image

image

$HOME/lxqt-applications.menu というファイルはないので自作したと思われる(記憶にない)。
/etc の lxqt-applications.menu をコピーしてきたようだ。

 

.config の menu ファイルは /etc の menu ファイルを Include した上で設定をしているようなので、 /etc の方を編集したくないからコピーして持ってきたのだろう。

$HOME/.local/share/applications にランチャーの設定ファイルを置くと反映してくれたようだ。

2022/02/13

[golang] testing

コードを書いたら極力テストは行うべきだ(戒め)。
というわけで、golang のテストを調べる。

過去の私もいくらかテストについては調べていたようだ。

hiro99ma blog: [golang]本体無しでテストをしたい (1)
https://blog.hirokuma.work/2020/03/golang-1.html

しかし、もう記憶にない。

最近は fuzzing というのもあるようだが、golang v1.18 以降らしいので扱わない。


まず、コード以外のルールから。

  • ファイル名は _test.go で終わるようにする
    • そうすると go build の対象にはならず go test の対象になる
  • "testing" を import する
  • 関数名は "Test" で始まるようにする
    • そうするとテスト関数という扱いになる
  • 関数名で "Test" の次は大文字で始める

関数名はともかく、ファイル名は気をつけねば。テスト用じゃないのに _test.go と付けてしまうことがあり得なくもないからだ。golang って大文字小文字ルールもそうだけど、名前で決められていることがしばしばあるからあせるね。

あれ、golang の関数名は UTF-8 を使えるのだろうか?
使える場合、日本語なんかは大文字扱いされるのだろうか?

 

main.go

01: package main
02: 
03: import (
04:     "fmt"
05: )
06: func よしお() string {
07:     return "よしお"
08: }
09: 
10: func main() {
11:     fmt.Printf("%s\n", よしお())
12: }

main_test.go

01: package main
02: 
03: import "testing"
04: 
05: func Testよしお(t *testing.T) {
06:     yoshio := よしお()
07:     if yoshio != "よしお" {
08:         t.Error(`not yoshio`)
09:     }
10: }

$ go test -v
=== RUN   Testよしお
--- PASS: Testよしお (0.00s)
PASS
ok      github.com/hirokuma/go-test1    0.012s

よいらしい。
t.Error() の中がバッククォートになっているが、これは本でそう書いてあったからだ。別にバッククォートである必要はない。
JavaScript だとバッククォートなら ${xxx} みたいにして変数を展開できるが、golang だと bash のヒアドキュメントみたいにそのまま展開されるというだけのようだ。 raw string literal というらしい。

 

ちなみに、Test の次を小文字にするとテスト関数と見なされず実行されなかった。
一時的に実行したくない場合なんかによいかもしれぬ。

 

あと、本に書いてあることで興味があるのはカバレッジだろうか。
ただ載っているとおり -run=Coverage だとテストが実行されなかった。

$ go test -v -run=Coverage -coverprofile=c.out
testing: warning: no tests to run
PASS
coverage: 0.0% of statements
ok      github.com/hirokuma/go-test1    0.003s

-cover だと実行された。

$ go test -v -cover -coverprofile=c.out
=== RUN   Testよしお
--- PASS: Testよしお (0.00s)
PASS
coverage: 50.0% of statements
ok      github.com/hirokuma/go-test1    0.003s

結果は、載っているとおりに実行するとブラウザが立ち上がった。

go tool cover -html=c.out

image

まあ、main関数は仕方ない。

と思ったが、main関数もテストできるのか。

Test the main function in Go | GO.into Development
https://mj-go.in/golang/test-the-main-function-in-go

main関数は特別なものだと思っていたが、go test なら普通に呼び出せるのか。

func TestMain(t *testing.T) {
    main()
}

普通に呼べた。

image

 

go test できれいにテストを回したいなら、実装の方も多少はテストを意識して実装する必要があるだろう。
めんどくさいという気もするが、その方が実装も小さい単位になってメンテナンスしやすくなることも多い。そこまで悪くない話だと思っている。
main関数も、中に全部書いてしまうのではなく本体は別関数にして戻り値をテストしやすくする、みたいな。

私はそこら辺が苦手な気がする。
なんというか、見える範囲に揃っていないとよくわからなくなるというか。
コース料理よりも丼にいろいろ載っている方がうれしいというか。
誰に言い訳してるんだ、私は。


私が作ったことがある golang のテストは、RC-S370/S のエンコードくらいだった。

go_pasori370/msg_test.go at master · hirokuma/go_pasori370
https://github.com/hirokuma/go_pasori370/blob/master/dev/msg_test.go

よく見ると t.Fatail を使っているな。
t.Error は継続し、t.Fatal は止めるようだ。
私は途中で止めてしまいたいのだが、一通り全部通してみたいと考える方が普通なのかもしれないがどうなんだろう?

なるほど、最初のエラーで止めるオプションがあるのか。

unit testing - Stop on first test failure with `go test` - Stack Overflow
https://stackoverflow.com/questions/32046192/stop-on-first-test-failure-with-go-test

Testよしお() で失敗するようにして試してみよう。

まずは普通に実行すると、Testよしお() も TestMain() も実行されている。

$ go test -v
=== RUN   Testよしお
    main_test.go:8: not yoshio
--- FAIL: Testよしお (0.00s)
=== RUN   TestMain
よしお
--- PASS: TestMain (0.00s)
FAIL
exit status 1
FAIL    github.com/hirokuma/go-test1    0.014s

これを -failfast オプションを付けると、 TestMain() は実行されていない。

$ go test -v -failfast
=== RUN   Testよしお
    main_test.go:8: not yoshio
--- FAIL: Testよしお (0.00s)
FAIL
exit status 1
FAIL    github.com/hirokuma/go-test1    0.003s

開発後半になって、テスト実行にものすごく時間がかかるようになった場合なんかによいのかもしれない。


直接 go test とは関係ないのだが、どこでエラーが起きたかの根源を知りたいことは多い。
error を返すようにしたとして、"InvalidParam: -20 < 0" とか返されたとしても困るのだ。
コマンドを実行してエラー、とかなら因果関係がわかりやすいのだが、サーバー的なプログラムでログの中にエラーが残っていたとしても追跡できなくては意味が無い。

がんばって fmt.Errorf() なんかで書いていくのだけど、まあ面倒だ。
エラーにした理由はもちろん、自分のパッケージ名や関数名を書かないと誰だか分からないからだ。
そしてそれをログにも出すのか return するだけなのかも結構悩む。その関数が A からも B からも呼ばれるようであれば自分の情報を返すだけではなく、呼び出し元の方でも何かやってもらわないと追えないからだ。ログに出せば間違いはないのかもしれないが、ログがたくさん出るというのもあまりうれしくない。

 

今までどうしてきたかというと、組み込み開発だとそもそもログを出せる状況にないことがほとんどだったし、文字で出せないからビットのパターンで表したりとかしてたしね。エラーになっても仕方ないのでそもそもリセット掛けて再起動するし。
そんなわけで、私のエラー処理は初心者レベルだと認識している。
精進がいりますな。

[golang] interfaceの勉強

go.mod をだいたいわかったつもりでいたけど、go mod tidy してモジュールが見つかりませんと言われると自信がなくなりますな。
はい、忘れるために別の勉強をしましょう。


interface は class とセットで存在するイメージだったのだが、golang には class はない。予約語にもない(どうでもいい話だが、予約語は英語だと"keywords"なので検索しづらい)。
とはいえ、C++ では class と struct が姉妹みたいなものなので違和感はない。

 

プログラミング言語Goの例であげられているのは io パッケージの Writer
短いので貼り付けよう。

type Writer interface {
	Write(p []byte) (n int, err error)
}

Write という名前で、引数として []byte を持ち、戻り値が (int, error) という構成になっていればよいということになる。
ただ、

func Write(p []byte) (n int, err error) {
  return 0, nil
}

みたいな「関数」だとダメだというか、意味が無いと思う。

func (x SomeStruct) Write(p []byte) (n int, err error) {
  return 0, nil
}

のように「メソッド」になっていないとありがたみがないだろう。
メソッドになっているから、複数のメソッドがあるけど interface が同じだから呼び出すことができる、という使い方になるだろう。

 

他の言語では、interface が先にあって、その interface を持つ class を実装することを明示的に書くことが多いと思うが、Go言語は明示的に書かないようになっている。
コードの検索をするときには探しづらいのだが、実装しているときにはあれこれ書かなくて良いので便利なのかもしれない。

明示的に書かなくて良いので、全然 interface を考えずに実装していたメソッドを interface になっているようなつもりで呼ぶことができる。本では「暗黙的に満足される」と書いてある。

 

あとは、宣言した interface 型を組み合わせたり含んだりする interface を作ることもできるということと、中身に何も書かない空インターフェース(empty interface)も作ることができるというくらいか。


というだけにしては本のページが多いので、何か気をつけることがあるのだろう。

 

まず、 empty interface。
これは C言語の voidポインタみたいな感じで何も要求しない interfaceだ。

package main

import (
    "fmt"
)

type dummy1 struct {}
func (d *dummy1) Print(param interface{}) {
    fmt.Printf("dummy1 int: %d\n", param)
}

type dummy2 struct {}
func (d *dummy2) Print(param interface{}) {
    fmt.Printf("dummy2 string: %s\n", param)
}

type dummys interface {
    Print(param interface{})
}

func main() {
    d1 := &dummy1{}
    d2 := &dummy2{}
    d  := []dummys{d1, d2}
    d[0].Print(123)
    d[1].Print("abc")
}

最初は dummy1.Print は param int、dummy2.Print は param string にして「interface{} を引数にしておけばなんでもいけます」みたいな例にしようとしたのだが、それだとコンパイルエラーになった。
interface の中の interface{} は、そのまま interface{} として実装されないといけないようだ。まあ当たり前か。

 

あとは使い方の注意点や型アサーションのことや助言などが書かれている。


empty interface が int や string のような基本型もとれるということは、最初に書いたようなただの関数であっても interface を使って活用させる方法があったりするのだろうか?

でも、あったとしてもあんまりわかりやすいことにはならない気がするので、気にしないことにしよう。