2023 年振り返り

仕事

CI やデプロイ、ライブラリ更新など非機能要件に関する作業が多い一年でした。来年はもう少し機能要件にも関わっていけるといいなと思いました。

OSS

仕事に使っているライブラリに機能追加しました。

github.com

今年は Goa にたくさん Pull request を出した気がしたので数えてみたら 15 回でした。 v3.13 でデフォルトルーターが変更された影響が大きく、それで不安定になった箇所の修正をしたりしました。また、長年気になっていた細かい不具合の修正や、いつかやろうと思っていた機能追加などに着手し始めたので、年が明けてもマイペースに改善に取り組みたいと思っています。

ISUCON

だらだらと参加してあまり成果が出ずに終わってしまった感じでした。それ自体は例年と変わらないのですが、今年はその不甲斐なさが非常にストレスでした。来年以降は参加するのであれば何かしら準備をして臨みたいですし、それが難しければ参加を見送ります。

ゲーム

メイドインアビス 闇を目指した連星

アニメと漫画を履修済みだったので世界観が再現できているか心配だったのですが、個人的にはかなり高評価な作品でした。「度し難いゲーム」というレビューもよく目にしましたが、慎重に進んでいけば理不尽に死んでしまうような場面もほとんどなく、中ぐらいの難易度で上手くまとまったゲームバランスだと感じました。グラフィックはもう少し頑張って欲しかった気もしますが、 Switch なので最初から全く期待していなかったこともあり許せる範囲でした。グラフィックのしょぼさと引き換えにサクサク動くので、もっさりするくらいならこれで良かったと思います。

store-jp.nintendo.com

ジュラシック・ワールド・エボリューション: コンプリート エディション

映画のジュラシックパークを運営するシミュレーションゲーム。恐竜が生育しやすい環境整備や餌の用意、観光客用に恐竜を見やすい展望台の設置やホテルの建設、自然災害や恐竜の脱走などの対策などやることがたくさんあって結構大変です。このゲームをやってから動物園に行ったら、運営者目線での改善点が思いついて面白かったです。

store-jp.nintendo.com

No Man's Sky

宇宙探索するゲーム。星、動植物、鉱物などゲーム中の多くの要素がプロシージャル生成されているのが興味深いです。 1800 京以上の膨大な惑星が存在する史上最大のオープンワールドゲームで、広大な宇宙を旅している感覚が味わえます。宇宙好きな人にはぜひプレイしてもらいたいです。発表から 10 年が経過するにも関わらずいまだに無料のアップデートが頻繁に提供されているのがすごすぎる。一度気に入ったら非常に長い期間楽しめる作品だと思います。

store-jp.nintendo.com

買い物

無印良品 体にフィットするソファ

リビングデくつろぐ用に購入しました。すぐに気に入ったようで無事子供の特等席になりました。

Keychron K3 Pro

キーボードはもうずっと Apple Wireless Keyboard を使っていて特に不満はなかったのですが、昨今のメカニカルキーボードの流行が気になってはいたので気分転換に購入してみました。イメージしていた昔のメカニカルとは違い剛性があって気持ちのいい打ち心地です。 RGB バックライトモデルを購入したのですが、純正のキーキャップは光が透過しないので XVX のキーキャップに交換して使っています。ホットスワップにも対応しているのでいずれ別のスイッチも試してみたいところです。タイピングが楽しくなったことでコードを書くモチベーションも上がったので、引き続きキーボード周り投資はしていきたいですが、足を踏み外すと恐ろしいキーボード沼が広がっているので気をつけないといけません。

XVX ロープロファイル キーキャップ

review

Analogue Pocket

以前から気になっていたのですがいつも売り切れ状態で入手は難しいかなと半分諦めていましたが 12 月の在庫復活に参戦し無事手に入れることができました。まだ届いたばかりであまり触れていませんが、ゲームボーイ系のゲームがネイティブで動作する他、 nanoloop というシーケンサーが内蔵されているので DTM にも活用できるようです。懐かしのゲームボーイソフトを入手しようととりあえずブックオフに行ってみましたが、数本しか置いていませんでした。 Analogue Pocket を入手する際は予めソフトも目星をつけておいた方が良さそうです。

www.analogue.co

総括

今年は保育園でもらってきた病気が家庭内で流行する頻度がとても高かったです。来年はみんなで健康に暮らしたい...

ErrorsWithStack という Goa v3 プラグインを作った

errorswithstackGoa v3プラグインで、オリジナルのサービスエラーにスタックトレースを追加します。このプラグインgithub.com/cockroachdb/errors/withstack に依存しています。

github.com

プラグインの有効化

プラグインを有効にするには、下記のように errorswithstack を import します。

import (
  _ "github.com/tchssk/goaplugins/v3/errorswithstack"
  . "goa.design/goa/v3/dsl"
)

コード生成への影響

プラグインを有効化すると goa ツールの gen コマンドの挙動が変わります。

gen コマンドの出力は次のように変更されます:

  1. すべてのエラー初期化用ヘルパー関数は、 WithStackDepth() を使ってオリジナルのサービスエラーにスタックトレースを追加するよう変更されます。

     func MakeInternalError(err error) *goa.ServiceError {
     -   return goa.NewServiceError(err, "internal_error", false, false, true)
     +   return goa.NewServiceError(withstack.WithStackDepth(err, 1), "internal_error", false, false, true)
     }
    

ミドルウェア

Goa エンドポイントミドルウェアを使ってエラーをキャプチャーすることができます。 GetOneLineSource() を使って呼び出し元を抽出することができます:

func ErrorLogger(logger *log.Logger) func(goa.Endpoint) goa.Endpoint {
    return func(e goa.Endpoint) goa.Endpoint {
        return goa.Endpoint(func(ctx context.Context, req any) (any, error) {
            res, err := e(ctx, req)
            if err != nil {
                file, line, _, ok := withstack.GetOneLineSource(err)
                if ok {
                    logger.Printf("%s:%d: %v", file, line, err) // file.go:15 something went wrong
                }
            }
            return res, err
        })
    }
}

もしくは GetReportableStackTrace() を使うこともできます;

func ErrorLogger(logger *log.Logger) func(goa.Endpoint) goa.Endpoint {
    return func(e goa.Endpoint) goa.Endpoint {
        return goa.Endpoint(func(ctx context.Context, req any) (any, error) {
            res, err := e(ctx, req)
            if err != nil {
                if st := withstack.GetReportableStackTrace(errors.Unwrap(err)); st != nil {
                    if len(st.Frames) >= 1 {
                        frame := st.Frames[len(st.Frames)-1]
                        logger.Printf("%s:%d: %v", frame.AbsPath, frame.Lineno, err) // /path/to/file.go:15 something went wrong
                    }
                }
            }
            return res, err
        })
    }
}

エラーの基になる具体的な値は ServiceError です。型アサーションして条件を作ることもできます:

func ErrorLogger(logger *log.Logger) func(goa.Endpoint) goa.Endpoint {
    return func(e goa.Endpoint) goa.Endpoint {
        return goa.Endpoint(func(ctx context.Context, req any) (any, error) {
            res, err := e(ctx, req)
            if err != nil {
                if serviceError, ok := err.(*goa.ServiceError); ok {
                    if serviceError.Fault {
                        file, line, _, ok := withstack.GetOneLineSource(err)
                        if ok {
                            logger.Printf("%s:%d: %v", file, line, err) // file.go:15 something went wrong
                        }
                    }
                }
            }
            return res, err
        })
    }
}

また report.ReportError を使って Sentry にエラーを送ることもできます:

func ErrorReporter() func(goa.Endpoint) goa.Endpoint {
    return func(e goa.Endpoint) goa.Endpoint {
        return goa.Endpoint(func(ctx context.Context, req any) (any, error) {
            res, err := e(ctx, req)
            if err != nil {
                report.ReportError(err)
            }
            return res, err
        })
    }
}

AutoTrailingSlash という Goa v3 プラグインを作った

autotrailingslashGoa v3プラグインで、末尾スラッシュのついた HTTP リクエストをハンドリングできるようにします。

Goa v3 は、v3.13.0 でデフォルトの HTTP ルーターを chi に切り替えました。この変更により、末尾にスラッシュが付いているリクエストが v3.12.4 以前と同じようには処理されなくなりました。このプラグインは以前の httptreemux のような動作を再現します。

github.com

プラグインの有効化

プラグインを有効にするには、下記のように autotrailingslash を import します。

import (
  _ "github.com/tchssk/goaplugins/v3/autotrailingslash"
  . "goa.design/goa/v3/dsl"
)

子供に文字の読み方を教える用 Web アプリを作った

作りました。

tchssk.github.io

画面にアルファベットを 1 文字ずつ表示するだけのシンプルなアプリです。

元々、アルファベット表を 1 文字ずつ指差しながら読んであげて復唱してもらう、という遊びをやっていたのですが、何度もやっていると指差しが面倒になってきました。ちょうどいいアプリがないか探してみると、その手の教育アプリは無数にあり過ぎて選ぶのが大変な上、キャラクターなどで装飾されているものが多く文字に集中するのが難しそうだったので、自分で作ることにしたという経緯です。

初期版は白背景に黒文字で作ったのですが、あまりにも無骨過ぎたので背景色だけ付けました。

これを使い始めて子供から「英語やりたい」と言ってくれたので作って良かったです。成長に合わせてまた改良していきたいと思います。

2022 年振り返り

ライフイベント

息子が生まれました。

受託開発

昨年から継続しています。使っている技術について記事を書きました。

zenn.dev

自分が経験したベストプラクティスを集めた構成になっているので、一般化できる部分は今後も記事にしていきたいと思っています。

OSS

仕事で使っている Slack のライブラリを修正したりしました。

github.com

Goa の機能追加も少しやりました。

github.com

買い物

今年はゲーム以外で目立った買い物がありませんでした。

Gran Turismo 7

久しぶりのナンバリングタイトル。これの為に PS5 を探し求めていましたが結局購入できず PS4 版でプレイしました。

ライセンスのオールゴールド達成で燃え尽きてしまいあまりやらなくなってしまった。

Splatoon 3

ついに発売されたスプラトゥーンの新作。ラグとバグが酷くクオリティはまだ前作に及ばないので今後に期待ですね。

文句言いながらかなりプレイしている模様。

eXtremeRate Switch Pro コントローラー DIY 交換用グリップハウジングケースカバー

プロコンを何台か所有しているのですが補償が切れた個体の外装を交換してみました。少し値は張りますが品質は結構良いです。

総括

保育園に行き出してからどんどん新しい言葉を覚えたり長女の成長が著しいです。あとはうまくこのイヤイヤ期を乗り越えたいところ ...

2021 年振り返り

個人での受託開発

知り合いの会社からシステム開発を依頼され、春先から個人での受託開発を始めました。日中の本業がフルタイムなので、夜間の副業として作業できる時間数で契約しています。インフラからアプリケーションまで、技術面のすべてを担当しているので結構大変ですが、それだけに大きな裁量があるので、自分の思い通りにシステムを構築できる楽しさがあります。あと収入が増えることで家計が助かるという点も大きいですね。確定申告が例年より面倒になると思うので今から気が重いですが ...

OSS

副業を始めたこともありあまりコードは書けませんでしたが、 Goa の機能追加や修正はいくつかできました。

github.com

github.com

あとは新しく使い始めたライブラリが増えたことで、ドキュメントを読んでいて typo を見つける頻度が上がりました。これからも修正を出していきたいですね。

github.com

買い物

Dyson Airwrap Complete

家族への誕生日プレゼントとして購入しましたが、自分も使わせてもらっています。シャワー浴びたあとこれで髪を乾かすとさらさらつるつるになってすごい。

Brother P-TOUCH CUBE PT-P710BT

ファイルやアルバムのラベリング用に購入しました。解像度はそこまで高くないですが、ちょっとオシャレなフォントでプリントしてアルバムに貼るとそれなりに様になります。

Apple Watch Series 5

自分の誕生日プレゼントに購入。外出中はマスク着用で Face ID が使えないので、連携させて iPhone のロック解除に使っています。 Apple Watch 側で Apple Pay が使えるのも便利。

Nintendo Switch (有機ELモデル)

ゲオの抽選に当たったので勢いで購入。本当は PS5 が当たって欲しい ...

[asin:B098B8PFXY:detail]

総括

子供の成長は本当に早いですね。ちょっとずつ単語を喋れるようになりつつあるので言葉での意思疎通ができるようになるのが楽しみ。

GORM v1 から v2 に移行してみた

ふと思い立って個人プロジェクトの GORM を v1 から v2 に移行してみました。

まずはプロジェクトに GORM v2 をインストールします。

$ go get gorm.io/gorm

import を github.com/jinzhu/gorm から gorm.io/gorm に変更します。 gofmt での一括置換が便利です。

$ gofmt -w -r '"github.com/jinzhu/gorm" -> "gorm.io/gorm"' *.go
-"github.com/jinzhu/gorm"
+"gorm.io/gorm"

ドライバは blank import ではなく普通に import する形になりました。

-_ "github.com/jinzhu/gorm/dialects/postgres"
+"gorm.io/driver/postgres"

ドライバの Open()gorm.Open() で包む形に変更します。

-db, err = gorm.Open("postgres", databaseURL)
+db, err = gorm.Open(postgres.Open(databaseURL), &gorm.Config{})

DDL 系のメソッドは Migrator 配下に移動されたのでそれを介して呼び出します。また、メソッドは直接 error を返すので従来のように Error フィールドから取り出す必要がありません。

-if err := db.DropTable(table).Error; err != nil {
+if err := db.Migrator().DropTable(table); err != nil {

エラーハンドリングは errors.Is() で行います。

-if gorm.IsRecordNotFoundError(err) {
+if errors.Is(err, gorm.ErrRecordNotFound) {

また Postgres に関してはドライバが https://github.com/lib/pq から https://github.com/jackc/pgx に変更になっているので、ドライバに依存したコードがある場合は変更する必要があります。私のプロジェクトではテーブルが見つからなかった場合のテストに修正が必要でした。

-assert.EqualError(t, err, `pq: relation "foo" does not exist`)
+assert.EqualError(t, err, `ERROR: relation "foo" does not exist (SQLSTATE 42P01)

とりあえずこれだけの変更で移行することができました。

メジャーバージョンのアップグレードにしては修正が必要な箇所がかなり少ない印象です。おそらく互換性を意識して API の大規模な変更を避けたのでしょう。

これなら商用プロジェクトでも比較的容易に移行が行えるかもしれませんね。