stefafafan の fa は3つです

"すてにゃん" こと id:stefafafan のブログです

ISUCON13 に参加した (最終スコア 17996点) #isucon

今年もISUCONという大会に参加しました。去年と同様「天下n品」という同僚2名とのチームで挑戦しました(が、1人諸事情により参加が難しくなったため、今回は2人で最初から最後までやりました)。例年通りGoでやりました。なおスコアについては、自分たちで観測できた最終的なもので、再起動試験を経た後どうなったかは未確認です。
isucon.net

去年はこちら: ISUCON 12予選参加したけど最終スコア12171点で惨敗 #isucon - stefafafan の fa は3つです

感想

  • 2人だとインフラ担当とアプリ担当みたいな感じになって、特にインフラを主に自分がみていたがほぼマニュアル見る時間もなく、アプリケーションも結局一度もブラウザでみることはなかった
  • マニュアル見ていないから画像ファイル書き出しのやつで無駄に時間かけてしまった、マニュアルはみたほうがいい
  • 今年色々と忙しくて素振りせずに挑んでしまったが、素振りはしたほうがいい
  • とはいえ、去年までチームで使っていたScrapboxテンプレートが活用できたので、ドキュメントがあると便利
  • サーバの負荷の様子をみながら適宜重いところからやれていたので、まあ悪くはなかった
  • 最後のほうに大きめな変更を入れてバタバタしてfailしたまま終了しそうになったので、コードフリーズをはやめにするべき

楽しかったです、おつかれさまでした!以下はタイムラインのメモです。

タイムライン

  • 8:58 空のGitHubリポジトリやメモするためのScrapbox会場を用意
  • 10:00 環境構築開始
  • 10:18 ssh用のconfigを用意し、チームメンバーに共有
  • 10:19 とりあえずベンチ入れて、3636点を確認
  • 10:36 3台ともGit管理下に置き終える
  • 10:45 頃 それぞれのサーバのスペックおよび入っているミドルウェアのバージョンやDBのテーブルの行数などを調べてページにまとめる
  • 11:08 それぞれのサーバでMySQLのスローログ有効化やnginxアクセスログをltsv形式に変える
  • 11:24 pprof および、チームの内製計測ツール導入
  • 11:29 再びベンチマークを動かす、3390点
    • 内製ツール、今回不参加メンバーに毎回お願いしていたので、なんかうまく動いていないことに気づいたもののあきらめて、手で素朴にalpやpt-query-digestを実行することにした
    • ベンチ回しているときにtopを確認し、DB負荷が支配的だったので、pt-query-digestの結果をとりあえずみることに
    • SELECT * FROM livestream_tags WHERE livestream_id = 7522\G とかがあがっていて、INDEX足すかN+1かをやっていく必要がありそうね〜と話していた。
  • 11:49 チームメンバーがINDEX足して回っていそうだったので、とりあえずいつものGoのDB周りの設定を適当に入れた、4193点
    • conf.InterpolateParams = true
    • 再起動試験対策のために、db.Pingするコード調整
    • db.SetMaxOpenConns 周りのコードを足して、適当に 25 に設定した
  • 12:00 3人目のメンバーがやってくるが、今回は参加難しいとの連絡
  • 12:03 チームメンバーが一通りindex貼って回ってくれた、12651点
  • 12:17 ベンチを再び回してtopの様子をみた。DB負荷が高すぎる
  • 12:20 チームメンバーがN+1みてくれていそうだったので、適当にMySQLのmy.cnfをいじってみる。 12597点
    • あまり効果なさそう
  • 12:36 isupipe database を別ホストにわけようとする
  • 12:59 ホストの分割に成功、5086点
    • なぜか「名前解決成功数」が増えたのに「売上」は下がって悲しむ
  • 13:16 分割後に改めてpt-query-digestをかける
    • isupipe では SELECT image FROM icons WHERE user_id = 1071\G が最強になっていたので、これはnginxから返すやつやろうと判断
  • 13:23頃から Goで画像を返す部分でついでに画像をファイルに書き出すようにしつつ、nginxで画像を返すというのを試し始める
    • nginxの書き方にしばらく悩む
  • 13:28 チームメンバーがN+1を一部潰す、12139点
  • 13:51-14:20 ベンチマーカーに問題があり、画像ファイルをnginxから返すやつが試せずに放心
  • 14:23 画像のやつ試す、17692点
  • 14:31 計測の様子みれてなかったので、ログをtruncateして再度enqueueするものの、みんなが同時にベンチマーカー使っているためタイムアウトし続ける
  • 14:47 再度試したら、17977点。iconsのスロークエリの順位は下がっているが、alpをみると全部200として返していてあれ?となる
  • 15:09 nginxの設定もGo側も凡ミスがいくつかあることに気づいて調整
  • 15:21 引き続き調整して、18986点。alpではまだ200を返していて、なんで304じゃないんだっけとなる
    • ここまで自分はマニュアルを読んでなかったが、チームメンバーがマニュアルにこれについてちょっと書いてることを言ってくれて、一旦これでマージすることにした
  • 16:13 画像の件何回も実行すると初期化時に失敗しそうだったので、initializeの時にディレクトリ以下に書き出した画像を一括で消すようにした。19663点
  • 16:15 最新のmainブランチに戻して再度ベンチ。19839点
    • まだDBが重い
  • 16:38 サーバ上でブランチの切り替えをせずにベンチ入れ続けていることに気づいて何度もミスった。改めてベンチしたら 15121点になってなぜか下がっている。
    • pt-query-digestの2位に COMMIT\G がいて気になる
  • 16:51 MySQL 8のバイナリログおよびREDO LOG無効化とpurge。15145点
  • 17:06 PowerDNSを3台目で動かそうと試みる
    • /etc/powerdns/pdns.d/gmysql-host.conf という設定ファイルと env.sh環境変数をなんかいじったらよさそうと試す
    • あとはmysql userの権限調整
  • 17:38 それぞれのサーバのmy.cnfなどを手で書き換えていたので手動で設定を見直して揃えたりして、再起動を試す
    • 再起動後、毎回failするようになる。チームメンバーも画像ファイル周り触っていたし自分はPowerDNS周り変えていたので何がおかしいんだ?となる
    • 結局PowerDNSを別ホストにするのを戻して、成功することを確認
  • 18時間際 アプリケーションログを出すのをやめた、maxOpenConnsを25から適当に50に増やす。17996点
    • nginxのアクセスログを無効化したもののギリギリ間に合わずに入らない

テックリードとして技術的施策をチームに提案する際に意識すべきポイント

私はいま会社でテックリードをしていますが、いちエンジニアとして技術的改善をチームに提案するスキルに関してまだ課題感を持っています。その際同じくチームに所属しているエンジニアリングマネージャー(EM)の方にヘルプしていただき、実際に提案資料をペアライティングした中で湧いたイメージがあるのでブログとしてまとめて自分の考えを整理してみようと思います。

効果的なストーリーテリングのための既存のフレームワーク

技術的な提案をするには効果的なストーリーテリングをするスキルが必要だと考えています。私はストーリーテリングに関して詳しくありませんが、仕事を通じて以下の二つのフレームワークを知りましたので軽く紹介します。世の中には他にもいろいろあると思いますが、基本的な考え方は近いのかなと想像しています。

空・雨・傘

EMの方と会話する中で「空・雨・傘」というフレーズを何度か耳にしました。恥ずかしながら自分はこのフレームワークを知らずに仕事してきましたが、問題解決・思考整理のための手法の一つだそうです。

dev.classmethod.jp

「空に黒い雲がある(現状認識)」→「雨が降りそう(現状分析)」→「傘を持って行こう(解決)」のような整理の仕方をしようというものです。

STARフレームワーク

別の文脈で「STARフレームワーク」も仕事を通じて知りました。こちらは評価面談に向けた自分の仕事を振り返る時に使ってはどうかと同僚に以前共有されたことがあり、実際に使ってきたものです。

miro.com

「Situation(状況)」「Task(課題)」「Action(行動)」「Result(結果)」の頭文字をとっていて、実際にどのような状況をどういう行動をとって結果をもたらしたのかというのを人に伝える際に説得力のあるストーリーテリングができるようになるというものだそうです。STARフレームワークは面接で自己アピールをするのに適しているそうです。

順序立てて話すということ

結局ここで紹介した二つのフレームワークはどちらも物事を一つのストーリーとして順序立てて話しましょうということだと理解しました。何か突発的な問題や課題をエンジニアが認識する際はすぐさま解決策を考えて行動したくなることがあったりしますが、選択肢が複数ありトレードオフもさまざまな場合はしっかりなぜそういう選択をするべきなのかという話をして説得力を持たせたいものです。

話に説得力を持たせたい時に事実と憶測や感情などが混じっていると話を聞いている側はコメントもしづらく「いいですね」とは言いづらい。そうならないためにも、「空・雨・傘」では「空」と「雨」を明確にわけているし、「STARフレームワーク」では相手に伝えることを意識して「状況」や「課題」をひとつずつちゃんとわけています。

相手を意識して伝えるということ

順序立ててまとめることにより、提案の説得力はあがります。とはいえそれで満足するのではなく、結局誰かに納得してもらうことが本来の目的のはずなのでその相手の立場にたって資料や文章をまとめることを意識する必要があります。

テックリードの立場でエンジニアチームに向けて「今後これこれこういう理由でこの技術的選択をします」と伝えるだけなのであれば技術面での前提知識は既に揃っていたり、そもそも議論の余地もないケースであれば共有するだけでOKだったりします。

一方で、「2月からエンジニア1名の工数を1ヶ月使ってこういった改善を行いたいと思っています」みたいなことをプロダクトオーナー(PO)に向けて伝えたいのであればしっかりとそう思い立った理由をPOが理解できる形でまとめなければいけません。POが非開発職バッググラウンドだったりする場合は特にそうです。

具体的に必要なものは最初に話していたストーリーテリングの他に、以下の2点もあると思います。

  • 相手の知りたい情報を簡潔に書く
    • 具体的には、実施した場合のコストと見込まれる効果を明示する(費用対効果)
  • 非開発職にも伝わるような日本語を使う

どのような施策を実行したら効果が出るのかエンジニアで検討する際は細かくいろいろ考えるかもしれませんが、PO目線では課題・分析・選択肢・コスト・効果が知りたいのでそこを簡潔に伝えられると難解な文章を読み解いてもらう必要がなくなってよいです。

また、PO vs. 開発者みたいに切り分けて考えるのも良くないです。何なら技術的施策を考えるテックリードも技術領域に特化したPOとも言えます。プロダクトの次の施策を考えてなぜやるべきかをPOがまとめるのと同じように、TLも技術的施策を丁寧に進められるとよいのかなと思います。

まとめ

テックリードとして技術的施策をチーム(特にPO)へ提案する時の考え方や整理の仕方についてまとめました。大事なのは以下の2点だと改めて認識しました。

  1. 効果的なストーリーテリングをするために、状況(事実)と分析(考え)を明確にわけて順序立ててまとめること
  2. 相手の求めている情報が何かを意識した上で簡潔に伝えること

テックリードでなくとも大きめな技術的施策を推進していくためにはこのように他職種と上手く連携するためのコミュニケーションや提案術が大事になっていくと思いますので、ぜひ参考になれば嬉しいです。

Webアプリケーションエンジニアとして1on1してもらう際に考えていること

同僚が1on1の際に他の人がどういう話をしているのか気にされていたので、便乗してブログに書きます。

ということで人が1on1の時間に何を考えてどう使っているのか気になっている

1on1で何を話すか考えてる - tomato3713’s blog

前提

  • 株式会社はてなは新卒から所属しており、今年で9年目
  • 現在チームでテックリードをしている。Webアプリケーションエンジニアとしてコードも日々書いている
    • とはいえ自分もメンティーは持っていなくて、1on1してもらう側の1人

最近1on1に向けて考えていること

前提に書いてある通り、自分はこの会社では古参で普段の仕事の仕方だったり同僚とのやりとりやカルチャーについての大きな悩みや不安はないです。その代わり、テックリードや30歳になったエンジニアとしての悩みや考えはあります。ということで以下のようなことを考えています。

  • テックリード
    • どのような振る舞いをしていけるとより頼れるテックリードとなれるかを壁打ちしてもらいたい
    • どのようにして他の仕事を進めつつも技術ロードマップや技術的負債の解消を進められるかを壁打ちしてもらいたい
  • 30歳エンジニア
    • 社内のエンジニア的にはひとまず中堅くらいのグレードにはなれたが、今後のキャリアパスについてどういう選択肢がありそうか意見を伺いたい
    • 引き続き同じ会社やエンジニアとして長く働くためにはどういうところを深く掘っていくとよいのか、自分のモチベーションを維持して楽しく働き続けるためにはどういうことを意識するとよいか壁打ちしてもらいたい
  • チーム
    • 一緒に働いている同僚にもっと活躍してもらうにはTLとして主に技術方面で何かやれそうなことないか壁打ちしてもらいたい

実際の1on1の時間の使い方

基本的には上記のようにテーマは決まっているけど、その時々で重点項目が変わるイメージです。また、メンターが「今回何にフォーカスして話したいですか」と聞いてくれるので、そこであらかじめ明示したりします。全部のことを同時に話すと長くなるので、「自分のキャリアパスについて話したいです」みたいな感じでそれだけについてしばらく話して終わりみたいなイメージです。

1on1される側の状況・フェーズによって内容が変わるはず

上記はあくまでも中堅30歳エンジニアの一例であるため、新卒エンジニアや入社直後の中途エンジニアの場合は内容は全然違う形となると思っています。

  • 会社での経験年数が浅いメンバー(新卒・中途ともに)
    • カルチャーに馴染むにはどうするとよいのか、みたいなところにフォーカスするのではないか(気持ちよく仕事ができるようにならないと、アウトプットも出しづらい)
  • エンジニアや社会人としての経験年数が浅いメンバー(新卒など)
    • まずは目の前のタスク、目の前のプロジェクトの進行を安定させ、さらに今後もどんどん改善できるようになり自立していくまでの道のりをメンターと一緒に探っていくというのがテーマになっていくのではないか
      • 状況次第ではメンターが最初は答えを提示するところから、グラデーションで徐々にメンティーに自主的に考えてもらうようにしていく

エンジニアの課題感の抽象度

エンジニアの持つ課題感は経験とともに抽象度が徐々にあがっていくはずなので、1on1でする話も少しずつ一般的なことになっていくものだと思います。そして、悩みの答えをもらうというよりは、答えを自分で探るための壁打ちになるのだと思っています。

  • 抽象度
    • 社会人スキル:
      • 個人でうまくタスクを進めるには
      • 他職種と上手くタスクを進めるには
      • プロジェクトを上手く進行するには
      • 課題やボトルネックを発見して提案するには
      • チームのデリバリー(開発速度など)を改善するには
      • メンバーの成長なども加味して仕事を効率的に進めるには
      • デリバリーとディスカバリー(企画など)の相互作用を改良して効率的に仕事を進めるには
      • 他チームや会社を巻き込んで改善していくには
      • ...
    • エンジニアスキル:
      • 仕事で触っているプロダクトでちょっとしたコードが書けるようになるには
      • 仕事で触れているプロダクトで一から設計して機能を足すことができるようになるには
      • 効率よく手戻りが少ない状態でタスクを分解見積もりして進めるには
      • プロダクトの将来などを加味してよい設計のコードが書けるようになるには
      • プロダクトの周辺技術に詳しくなったりコントリビューションするには
      • 他チームや社外の一般的なプラクティスのインプットやアウトプットができるようになるには
      • 自チームで特定の領域をリードできるようになるには
      • 他チームや会社を巻き込んで特定技術領域をリードできるようになるには
      • 業界で認知されているエンジニアになるには

適当に書いてみましたが、後半になるにつれて解像度が低くなっていきます。結局自分のスキルレベルに応じて見える範囲や見る余裕のある範囲が限られてきます。個人のタスクやチームの仕事のことしか考えられないうちは1on1でそこをどう改善していくかという話が主となります。また、エンジニアとして「技術」と「仕事の進め方」をまずフォーカスすると思いますが、ここに「メンバーの成長」「プロダクトの成長」なども視座があがるにつれて増えていきます。

エンジニアのキャリアパス

キャリアパスについても上がっていくにつれて抽象度があがっていきます。

  • 入社直後のエンジニア
  • チームでちょっとした施策をおまかせできるエンジニア
  • チームでプロジェクトをリードしてもらえるエンジニア
  • テックリードスクラムマスター・シニアエンジニアなど
  • エンジニアリングマネージャー(EM)・スペシャリスト(IC)など

後者になるにつれて結局個人としてやりたいことは一人一人違っていって特定の型にはめられないです。序盤はこうしましょうねとメンターから提案されますが、テックリードやEMになろうというタイミングではチームや会社によってテックリードやEMの役割は十人十色(結局いい感じにやってねという期待になっていく)ので自分で考えつつメンターに壁打ちしてもらうことになっていきます。逆に言うと後者になっていくとどの会社でも働ける即戦力人材になっているということになります。

まとめ

同僚へのアンサーとして書き始めたところ筆が乗って最終的にかなり一般化した内容の記事になったと思います。結局言いたいことはメンティーとしてはスキルや経験次第でメンターとどういう話をするのか随時変わるものだということと、メンターもそれをわかっているはずなので気軽に相談していけるといいのかなと思います。そして余裕が出始めたら将来を見据え、どういうエンジニア人生を辿っていくとよいかをゆっくりと考えていくと良いのかなと思います。

Go のアプリケーションで uber-go/zap を使っている場合は暗黙的にサンプリングが有効になっていないか見直しましょう

表題の通り、Goでロギングに GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go. を使っている場合、サンプリングの設定を見直しましょう。

背景

GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go. のFAQによると、 NewProductionConfig を使ってセットアップすると内部で勝手にサンプリングが有効になるようです。

Logs are dropped intentionally by zap when sampling is enabled. The production configuration (as returned by NewProductionConfig() enables sampling which will cause repeated logs within a second to be sampled.

zap/FAQ.md at master · uber-go/zap · GitHub

このことを知らずにこのライブラリを使っていると、知らず知らずのうちにログの一部が欠損します。詳しい内部実装については同僚がまとめてくれていました。
zenn.dev

どうするとよいか

まず何はともあれ、GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go. を使っている場合いい機会なのでサンプリングの設定を見直しましょう。

  • NewProductionConfig() を使ってる場合は暗黙的にサンプリングが有効化されているため、意図通りかどうか確認しましょう
  • 明示的に設定していたとしても、ログの設定を入れたのが前任のエンジニアだったりすることがあるので、現状を知るためにも見ておきましょう

サンプリングを完全に無効化したい場合は、 Samplingnil を設定することで対処できます。もちろん、ログは出せば出すほど負荷はかかるためあらかじめ検討した上でサンプリングの方針を決めましょう。

c := zap.NewProductionConfig()
+ c.Sampling = nil

slog

Go 1.21より公式に slog package - log/slog - Go Packages が追加されているため、 GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go. からの乗り換えの検討もあるでしょう。調べた感じでは slog にはサンプリング機構はなさそうなので、移行時に認識しておきましょう。

また、実際サンプリングしたい場合はサードパーティのライブラリがあるそうです。ただし私は slog および slog-sampling はまだしっかり触れていないためこれ以上詳しいことは言えません。

github.com

OSSコントリビューションへの一歩に悩んでいる方向けにちょっとした事例を紹介

OSSへのちょっとしたコントリビューションに成功したので、どういう流れでコントリビュートしたのかブログに簡単に流れをまとめてみようと思います。OSS活動してみたいけどどういう流れでやれるのか気になっている方の参考になれば幸いです。なお今回の修正Pull Requestはこちらです。

github.com

経緯

GitHub App の権限で Songmu/tagpr を利用する - stefafafan の fa は3つです とかにも書いたのですが、個人的に作ったライブラリにこちらの Songmu/tagpr を導入したら便利になったので、そのままはてなで運用している以下のリポジトリとかでも使ってみてはどうかとブログチームの方に紹介しました(自分はブログチームではないのですが、Slackの雑談チャンネルでおもむろに紹介しました)。

github.com

このあと軽くウォッチしていたら、なぜか次のリリースバージョンが v0.0.1 になってしまう問題があり導入に手間取っていそうということに気づきました。自分で使ったときはこういう困りはなかったので不思議だな〜と思いながら過ごしていました(本来ならいま存在するタグの次のバージョンを判別して切って欲しい)。
github.com

おかしいな〜と思ったのと、紹介した側としてなんか申し訳なさのようなものがあったので自分で原因を軽く調べてみることにしました。

不具合の特定および修正までの流れ

自分も今回はたまたま上手くいきましたが、一般化すると以下のような流れで進めるとOSSに限らず不具合の原因の特定や修正まで持っていけると思います。

  1. 状況の把握
  2. 仮説を立てる
  3. 問題点を絞り込む
  4. 状況を再現できる最小の実装を用意
  5. 失敗するテストを書く
  6. 実装を修正する

状況の把握

今回の場合は、「v1.2.0のタグが最新なため、次にv1.2.1のタグが打たれる想定なのに、v0.0.1になっている」という状況でした。

そしてGitHub Actions上で今回のOSSは動作するので、当該のActionsのログを確認しました。ログをみたところ、「現行バージョンを v0.0.0 と誤判別してしまっている」ということがなんとなくわかりました。また、上記の hatena/hatenablog-workflows リポジトリではこのActionsを再度実行していましたが、何度やっても毎回「現行バージョンを v0.0.0 と誤判別してしまっている」になっていました。

さらに環境についてわかっていることは、自分がもともと Songmu/tagpr を導入した hatena/godash というリポジトリでは、tagpr の v1.1.2 を使っていたが問題はなく、 hatena/hatenablog-workflows では v1.1.3 を使っているということもわかりました。

仮説を立てる

わかっていることから仮説を簡単に立てます。今回は以下のようなことを考えました。

  • hatena/hatenablog-workflows の何かしらの設定が Songmu/tagpr の想定しないものになっている
    • 打たれているタグの形式が何かおかしいのかもしれない? (v1.2.0 みたいな形式で打たれているが実はvが全角だったり、特殊な空白文字が含まれているためにパースに失敗しているとか)
    • v1.2.0 みたいなタグと v1 タグが混合しているけどこれだと何か問題になるのかもしれない?
  • Songmu/tagpr v1.1.3 に何かしらBreaking changeが入っている
    • v1.1.2 では少なくとも他リポジトリでは想定通りの挙動だったため

大きく二分すると、自分たちのリポジトリ側の問題なのか、OSS側なのかとなります。もちろんOSSに不具合が入っていることは普通にありますが、大半の場合は利用させてもらっている側の設定ミスなどだったりする上に、情報不足のままissueなどを立てにいっても迷惑なのでまずは自分たちのリポジトリを疑います。

問題点を絞り込む

上記の仮説をもとに問題点を絞り込みます。

今回すべてGitHub Actions上で動いているので、ログに出力される文字列などから、 Songmu/tagpr のリポジトリを検索しなんとなく該当の処理が実行されているポイントを見つけます。

最終的には以下のコードに辿り着きました。 latestSemverTag という関数の戻り値をもとに現行タグを判別し、判別に失敗したら v0.0.0 とする、という処理がありました。一応この時点で v0.0.0 と書いてあるので相当あやしいですが、リポジトリのコード全部を把握しているわけではないので「あやしさはそこそこ高い」というつもりで覚えておきながらコードの処理を軽く追いました。
github.com

現行のタグのバージョンを判別するコードは、自分が修正を加える前は以下の形となっていました。 Songmu/gitsemvers というさらに別のライブラリでgit tagから対象のバージョン一覧を取得してから、tagpr の設定で vPrefix が有効かどうかをもとに最初にマッチしたバージョンを返すという実装になっていました。
github.com

仮説に書いていた「打たれているタグの形式が何かおかしいのかもしれない」や「v1.2.0 みたいなタグと v1 タグが混合しているけどこれだと何か問題になるのかもしれない」の肝として「Songmu/gitsemvers から正しく値が返っているのか?」というのが気になったので、手元にgo install して様子を確認しました。

$ go install github.com/Songmu/gitsemvers/cmd/git-semvers@latest

$ cd /path/to/hatenablog-workflows

$ git-semvers
v1.2.0
v1.1.3
v1.1.2
v1.1.1
v1.1.0
v1
v1.0.0

少なくとも手元で git-semvers を実行しても何もおかしなところはないので、latestSemverTag の実装の続きをみてみることにしました。特に vPrefix の箇所が読んでいてもよくわからず、Go Playgroundで触りながら実験しました。そして触っている中で閃きました。「vPrefixはConfigに指定できる *bool な設定だけど、今回は設定ファイルを作らずにSongmu/tagprを導入しようとしている。もしかして設定が nil な場合に意図しない挙動になっているのではないか」。

状況を再現できる最小の実装を用意

latestSemverTag の実装がv1.1.3で変わっているのもあってとても怪しいということで、引き続きGo Playgroundで状況を再現できるか確認しました。

まずは v1.1.2 時点の実装です。https://go.dev/play/p/GNga-fRcit7
Config の vPrefix 設定の有無や真偽に関わらず、とにかく常に最新のタグのバージョンを返していることを確認できました。

次に v1.1.3 時点の実装です。https://go.dev/play/p/4cNyuN3lcrt
Config の vPrefix が true の場合はv始まりの最初のタグを返すし、falseの場合はvから始まらない最初のタグを返すというのは v1.1.3 の意図通りの挙動になっていることは確認しました。一方で設定が nil かつ v からはじまるタグしかない場合は空文字を返すことがわかりました。今回の不具合のケースにあたります。

もし Songmu/tagpr セットアップで最初にまず .tagpr ファイルを作って vPrefix をセットしましょうという手順になっているのであればこの実装のままでよいですが、別にREADMEみてもそうはなっていないのと、作らなくても初回セットアップ時は .tagpr ファイルが作られるため、これは恐らく意図しない挙動だと判断しました。

失敗するテストを書く

ここまでわかったので、せっかくなので修正Pull Requestも作ってしまいたいです。幸い今回は前入っているPull Requestがそのまま参考になりました。

github.com

上記Pull Requestで TestLatestSemverTag というテストが生えていたのでここに今回失敗する状況のテストケースを足します。OSSへのContributeに限らず、不具合修正はまず失敗するテストケースを足してCI上でも意図通り失敗することを確認してから修正に入ることをおすすめします。

実装を修正する

上記のテストが失敗することを確認したら実装側を修正します。

今回修正するにあたって、「そもそも vPrefix の値が nil の場合の正常な挙動は何か」が一瞬だけ検討ポイントとなりました。デフォルト値が true なのか false なのか、メンテナに確認が必要なのではないか?

ただしここで思い出して欲しいのは、 v1.1.2 での挙動のことです。もともとの挙動は「バージョンの形式がなんであれ最新の値を返す」でした。今回の変更で突然仕様をさらに変更するのは意味がわからないし、v1.1.2の動いていた頃に戻したいのが本来やりたいことのはず。つまり「vPrefixが nil の場合はバージョンの形式を気にせず最新の値を使う」でよいはず。

そうしてできたのがこのブログの序盤に紹介した修正Pull Requestです。vPrefixが nil でない場合は v1.1.3 の挙動になるが、nilの場合は v1.1.2 の頃の挙動のままになるようにして修正しました。

github.com

何かおかしいな、不便だなと思ったときについでにコントリビュートする

私はもともと新卒の頃などはOSSへコントリビュートすることにハードルがありました。とにかくOSSを何かみつけて、Good First Issue ラベルをみつけないといけないのか?コードを読んでいかないといけないのか?と結構大変だなと思って中々できませんでした。

「普段開発しているソフトウェアが利用しているライブラリのコードをもとにコントリビュートできればよい」みたいなことを言われて「そうか」と思いながらも当初はあまりよく理解できていなかったと思います。ただ今回こうしてみると意味はわかってきたように思えますね。

結局は普通に仕事をしていて、自分の把握しているリポジトリのコードにある不具合は普通に治しているはずで、そこから調査をして実は原因が外部にあるとわかったら「外部のことはわからないや」と諦めるのではなくて外部のOSSもみにいくことがコントリビュートへの一歩だと思います。

別に最初からコード修正はする必要はなく、まず同じように困っている人がいないかissueで検索して、いたらそこの議論を読む。もしかしたら何かしらの意図があってそうなっているかもしれないし、暫定的な対処法もわかっているかもしれません。issueが見当たらない場合は再現方法をまとめて自分でissueを立てに行くのもコントリビュートの一つですね。

なので、コントリビュートできなくても焦ることはなく、違和感を感じたときや困ったときに少し外の世界を見に行くくらいからはじめましょうと改めて思った次第です。このブログ記事は新卒の頃の自分に向けたものでもあるということですね。