昨日は寝落ちしてしまった。 出社した週はやはり疲れ切っていて調子が悪い。 体力をつけないと。
ゼルダ
実はブレス オブ ザ ワイルドは周りが言うほどおもしろいと感じてなかったのだけれど、大絶賛の嵐なので何も言えなくなっていた。 いや、おもしろいのはおもしろいのだけれど、途中で飽きてしまったのだ。地図を全部埋めた段階で辞めてしまった。
ところで、別にゼルダの問題ではない。剣と魔法のファンタジーがだいたい駄目で、最初はおもしろいけどすぐに飽きてしまう。 Skyrim とか、ウィッチャーとかもしかり。
ファンタジーがなんか駄目なんだよな。かといって異世界転生はめっちゃ読んでるので良く分からない。 ファンタジーにもいろいろありそう。ロード・オブ・ザ・リングは苦手。
まあ、何が言いたいかと言うと、ティアーズ オブ ザ キングダムはノットフォーミーで買っておりません。
地震と眠み
早朝に地震が来てびっくりした。
ドミトリーに泊まっていたので、一斉に地震速報が鳴り出して地獄のようになってみんな起きだした。自分も寝過ごす心配があったのでそのまま起きてしまった。
手荷物検査潜って中に入ってゲートまで行ったら全然違う便の案内をしていて、あれーと思ったらまるまる1時間早く着いていた。完全に寝ぼけている。
虚しいのでラウンジに行ってオレンジジュースを飲みながらぼーっとしていた。仕事しても良かったけど眠くてあんまりやる気にならず。
飛行機でもなんか寝付けずに本を読んでいて、バスでも同様だったので本は読み終わったものの睡眠が足りてない状態。
家に着くまでは気が立っているのかまあまあ元気だったが、家に着いてシャワー浴びたら副交感神経優位になってしまって急に元気がなくなった。
眠すぎて吐き気があったが、普通に平日なので仕事を開始。ミーティングが終わって作業しようとするも眠すぎたのでちょっと昼寝をしてなんとか回復した。
睡眠は大切という話。
健康になりたい
楽しいを持続するために健康が課題なので運動を楽しくしたいですね
— APIなくなりました (@tomohi_ro) 2023年4月29日
最近はいろいろ楽しいことが増えているのだけれど、これを永続的に続けたい。 そうなるとやはり健康がネックになってくるということで、少しだけ健康に興味を持ち始めている。 これまでは妻からセルフネグレクトと言われるくらい健康に興味がなかった。
ひとまず軽い運動や筋トレを始めたり栄養について思いを馳せたりしているが知識がない。 積んでいたこの本を読むときがきたということでゴールデンウィークに読んでいた。
アジャイルというのはちょっとこじつけ感があったけど、健康について科学的な根拠も含めてうんちくが書かれていて完全に理解した気になれました。
健康とは、病気でないとか、弱っていないということではなく、肉体的にも、精神的にも、そして社会的にも、すべてが満たされた状態にあることを言います
WHOのこの定義がなかなかいいなと思ったのでこの精神でやっていきます。いまのところは満たされている気がするので健康です。
Goで特定のパターンのリファクタリングをASTを弄って自動化した
これは日記です。技術記事ではないので読みやすくはないです。
仕事のコードで特定のパターンでちょっと泥臭く書き換える必要のあるリファクタリングが必要になっているのだけれど、単純計算で100ファイル、1000箇所以上の書き換えが必要になっている。 これまでちまちま手作業でいろんな人が片手間で書き換えをやっていたのだけれど、無限に時間がかかりそうだったのでどうにか出来ないかと考えていた。
先行研究として id:hitode909 のASTを使ってリファクタリングするやつが記憶に残っていたのでもうちょっと簡易版で似たようなことをやってみた。
GoのAST周りはあんまり詳しくなくて、社内の静的解析ツールをちょっと弄ったくらいだったのでまずは勉強した。
id:motemen さんの Go のための Go を読んでふむふむという感じで理解した気になってとりかかるも、そんなにすぐに身に付くものでもないので ChatGPT (GPT-4) に相談しながら進める。
本格的なツールを作るつもりじゃなくて、リファクタリングを補助したいだけなので以下のような作戦でいった。
- 大まかな書き換えだけをやる
- 書き換えた箇所に FIXME コメントを残して確認出来るようにする
- 書き換えたことによって使わなくなった変数とかは手動で直す
これくらいのことならそんなにがんばらなくても出来そう。
最初は ast.Inspect しか知らなくて書き換えるの大変だなあと思っていたのだけれど、 astutil.Apply というのが使えると知ったのでこちらで進める。 書き換え前の状態と、書き換え後の状態でそれぞれコールバックを渡せるが、今回は書き換え前のみ使う。
astutil.Apply(f, func(c *astutil.Cursor) bool { // TODO: ここで対象を見つけて書き換える }, nil)
コールバックの引数に渡ってくる astutil.Cursor が Replace とか Deleta を持っているのでこれを使って書き換えていく。
書き換え対象を見つけるにはいま着目しているのがなんの型なのかを調べる必要がる。Cursorから取得できるNodeは ast.Node interface の状態なので型をちまちま見ていく。
n := c.Node() switch x := n.(type) { case *ast.AssignStmt: // 代入だったらここ case *ast.CallExpr: // メソッド呼び出しだったらここ }
ここからさらに詳細な型にキャストしていく必要がある。例えばメソッド呼び出しだった場合は以下のような感じでレシーバを取得できる。
if sel, ok := x.Fun.(*ast.SelectorExpr); ok { if ident, ok := sel.X.(*ast.Ident); ok { if ident.Name == "hoge" { // hoge.(何かのメソッド) という呼び出し } } }
型は素晴らしいのだけれど、こういうちょっとしたことをするにはちょっと面倒。
メソッド呼び出しの場合は引数などの情報を持っているのでこれも使う。
for _, arg := range x.Args { // もちろん arg は ast.Node 型なのでここでも型を見極める必要がある }
元の呼び出しを新しいメソッドの呼び出しに変えるにはASTを手動で組み立てて先述の Replace
メソッドを使う。
replaced := &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: ast.NewIdent("hoge"), Sel: ast.NewIdent("Bar"), }, Args: x.Args, } c.Replace(replaced)
これで例えば hoge.Piyo
みたいな呼び出しだったのが hoge.Bar
に書き換えられる。この例では引数はそのままにしているが、実際にはここも弄ることになったのでちょっと泥臭い感じに。
コメントは別途 ast.File のフィールドとして持っているので、適切なポジションを与えつつ append
するだけでよい。ただし parse.ParseFile するときの mode 引数に parser.ParseComments を与えておく必要がある。
f.Comments = append(f.Comments, &ast.CommentGroup{ List: []*ast.Comment{ { Slash: pos, Text: "// FIXME: 自動で置き換えたので確認してください", }, }, })
append
するだけで良いと言ったが実はこれでは駄目で、最後に出力するときに正しい位置に配置するにはポジション順にソートされている必要がある。これにしばらくハマってしまった。
sort.Slice(f.Comments, func(i, j int) bool { return f.Comments[i].Pos() < f.Comments[j].Pos() })
最後にASTをGoのコードとして正しくフォーマットしつつ元のファイルに書き戻すには format.Node を使う。第一引数を os.Stdout
にすると標準出力に出せるのでdry runとかを用意して確認用に出力すると良さそうです。
file, err := os.Create(filename) if err != nil { panic(err) } defer file.Close() format.Node(file, fset, f)
ここで少し問題があって、各Nodeが持っているポジションは元のコードの位置なので、例えば、引数の数を減らして書き戻したりすると不要な空行が生まれたりします。 この辺りは雑にやってしまったのでまだうまい解決方法を見つけていなくて課題になっています。 いい方法があったら教えてください。
だいぶ雑ながらも人力で置き換え箇所を見つけてちまちま書き換える必要がなくなってだいぶ楽になりました。 引数の数が可変だったり、残すもの、残さないものがあったりと人力でやるにも機械的にはやりにくいところだったのではかどりそうです。