GoのイテレータにRubyっぽいコレクション操作を提供するパッケージを作った

GitHub - aereal/iter: iter provides utility functions about standard iter.

使い方

pkg.go.devを見てもらえるとよい。

Chunkを取り上げると、第一引数を n 個ごとの要素に分割したイテレータを返す。

たとえば一度に最大500個までの引数を受け取るAPIへリクエストを送る処理を実装する場合、送りたい引数リストを500件ずつに分割して送りたい。 そういう時に Chunk を使うとよい。

for chunk := range seq.Chunk(args, 500) {
  _ = sendRequest(ctx, chunk)
}

他に、2つの iter.Seq を引数にとり同じ添字に位置する要素をペアにした iter.Seq2 を返す Zip などもある。

モチベーション

Rubyなどにあるような高級なコレクション操作が言語を問わず使えると嬉しい。「ある条件を満たす限り先頭から要素をとりつづけて~」と説明するより「takeWhileしたい」と言って通じるほうが断然話が早いのは間違いない。

高級なコレクション操作をGo向けに提供する試みに対してこれまで個人的に否定的な態度をとってきたが、2つの変化が追い風となり便利さが勝ったと判断したので作ることにした。

理由のひとつがジェネリクスの導入。

ジェネリクス導入以前のGoは型システムが貧しく、コレクション操作を一般化しようとすると、コード生成を用いて利用者が使う具象型ごとに適応させるかさもなくば any (interface{}) を使ってキャストに頼るかしかなかった。

素朴なコレクション操作のためにひたすらにコード生成を強いるのは利用者の負担が大きいし、そのようなライブラリ・ツールを実装することを考えると割に合わない気がした。

キャストする場合、当然コレクションと見なせない型であれば実行を停止せざるをえないが、panicしてシグネチャからエラーを取り除くにせよ、キャスト失敗をエラーとして伝えるシグネチャにするにせよ、利便性や安全性などの観点でそれぞれ懸念がある。

ジェネリクスはこれらの問題を(ほぼ)解決してくれる。

もうひとつの理由がイテレータの整備、より具体的にはrange-over-funcの登場である。

対象とするコレクションがどんな性質か・コレクションを加工としてどんなデータを得たいかによって効率的なプログラムの書き方が変わってくる。

Goのコレクション操作ライブラリの利用者は、for文を使って何度も書いてきた定型的な操作を任せたいのであって、コレクションそのもののサイズやキャパシティ管理だとか排他制御だとかまでを手放したいわけではない。

range-over-funcの導入で拡充されたイテレータは、これまで言語仕様で特定の型だけを特別扱いして規程されていた反復処理の実装を利用者が制御できるよう拡張しつつ、イテレータプロトコルに則って先に挙げたデメリットを解消ないし抑えて一般化しやすくするもので、これが最後の後押しとなった。

イテレータプロトコルに則れば途中で反復を止めることも可能だから、無限リストやストリームのようなコレクションも適切に扱える。

実際、 Zip はpull型のイテレータを使っているので、渡したイテレータが勝手に終端しない無限リストのような振る舞いをしても要素を反復できる。

むすび

Go自身の進化によりコレクション操作のライブラリが提供・利用しやすくなったので恩恵に最大限与るために作ったよ、というご紹介だった。

なんでも入れるつもりはなくて、たとえばmapのような操作は十分に単純なので入れるつもりはない。

また、イテレータからスライスであるとかマップであるとか具象へ変換するような処理やいわゆる畳み込みに類されるものの導入も消極的。

端的に言うと range の右側に書く組み合わせ可能なグッズだけ集めることに価値を見出しているかんじ。

Goの見た目をRubyとかScalaっぽくするジョークグッズを作るつもりはなく、あくまで普段からよく使うが初見でテストなしに遭遇すると境界条件が気になるような処理が集まっていて嬉しい……そういう実用的なライブラリを目指す。

どうぞご利用ください。

gotest2rdf: 失敗したGoのテストをreviewdogで報告するためのユーティリティを作った

# gotest2rdfの紹介

github.com

![reviewdogがテストの失敗をGitHubの行コメントで報告している様子](https://raw.githubusercontent.com/aereal/gotest2rdf/main/docs/image.png)

gotest2rdfというツールを作りました。go testの出力を受け取り、Reviewdog Diagnostic Formatに変換します。

これをreviewdogに渡すことで失敗したテストやスキップしたテストをPull Requestに行コメントで報告してくれます。

もちろんreviewdogのreporterを指定すればChecksとして報告もできますし、GitHub以外のサービスも対応できます。

ビルドジョブの失敗通知でテストが失敗したことがわかったけど、じゃあどこが落ちとるねんということを知るためにいちいちジョブの出力を見にいくのは地味に手間なので、元々linterなどで活用していたreviewdogを使えないかと考えたことがきっかけです。

# reviewdogとは

GitHub - reviewdog/reviewdog: 🐶 Automated code review tool integrated with any code analysis tools regardless of programming language

> Automated code review tool integrated with any code analysis tools regardless of programming language.

……ということでlinterなどの解析結果を報告してくれるツールです。

おおまかな仕組みとしては、ファイル・行・列・メッセージからなる解析結果を渡すと、コードをホスティングしているサービスのAPIを呼び出していいかんじに見せてくれます。

GitHubのPull RequestやGitLabのMerge Request画面で「この変更ではこうした解析が得られましたよ」という情報を見せることで、開発者にコード改善の示唆を与えてくれます。

典型的には、いわゆるlinterと呼ばれるよりよい書き方へ統一するよう支援してくれるツールの報告に使われることが多いです。

しかし用途はlinterに限られずコードを解析した結果をコードの変更に紐付けて表示するためならなんでも使えます。自動テストもコードの品質を担保する役割も担うことから広義のソフトウェア解析といえるでしょう。

何より既に述べたように、どのテストがどのように失敗したのか、できるだけ画面遷移を減らして知れるとよいですし、不可解な失敗に遭遇した時にレビュアーとのあいだでコミュニケーションする良い機会にもなります。

# Reviewdog Diagnostic Format (RDF) について

ReviewdogはVimのerrorformatなどいくつかサポートしていますが、独自に定義した[Reviewdog Diagnostic Format](https://github.com/reviewdog/reviewdog/tree/master/proto/rdf)という形式もサポートしています。

詳しい仕様やモチベーションはREADMEに譲りますが、機械的に解析しやすく特定の言語やツールに依存しないポータブルかつリーズナブルなフォーマットが世の中に存在しなかったので定義した、ということです。

Protocol BuffersおよびJSON Schemaが公開されているので、準拠も楽です。

# go testの-jsonオプション

go testには `-json` というオプションがあり、これは名前から想像できるようにテスト結果をJSON形式で出力してくれます。
正確にはいわゆるJSON Linesに近いもので、JSON表現を改行区切りにしたものです。

go testのプレーンテキスト出力はパースしやすいとは言えませんし、そもそも仕様が定かなのか・安定しているのかは不明ですし、 `-json` オプションがあるのにわざわざプレーンテキストをパースする意味もありません。

# gotest2rdfの実装について

ここまででgo test -jsonで出力されたJSON LinesをReviewdog Diagnostic Formatに変換してあげれば、失敗したテストをreviewdog経由で報告することができそうだということがわかりました。

しかし単純な変換ができるわけではありません。

go testはテストスイートごとに成功 (pass), 失敗 (fail), スキップ (skip) のいずれかが記録されるのみで、失敗やスキップと理由が直接紐付けられているわけではありません。

よく使う `t.Errorf` などの関数は実は `t.Logf(...); t.Fail()` とほぼ同じです。実際、go testのJSON出力でactionがfailのmessageはテストスイートの名前などが含まれるのみで、 `t.Errorf` に渡した文字列などは含まれません。

渡した文字列はfailより前にoutputというactionのイベントとして記録されます。

`t.Logf()` などの出力はoutputというイベントとして記録されることと `t.Errorf` が `t.Logf(); t.Fail()` と同等ということを踏まえると理解はできるのですが、これが便利かというとそうではありません。

大抵の場合は `t.Errorf` に渡した文字列でアサーションが失敗した理由について詳しく報告しているので、この出力も解析結果として含まれていてほしいはずです。

なのでgotest2rdfではある程度outputイベントをバッファリングしておき、failやskipなどに遭遇したらその時点までに見つかったoutputイベントの出力をfailやskipの補足情報だとみなしてRDFのmessageに含めます。

バッファリングではデフォルトで3行です。これはとても長い出力を無闇にメモリ上に確保することによるパフォーマンスの劣化を気にした……というわけではなく、出力の混同をできるだけ抑えるためです。

以下のようなコードを考えます:

```go
import "testing"

func TestBlah(t *testing.T) {
t.Log("supplemental info")
if 4%2 != 0 {
t.Error("the number theory system maybe corrupted")
}
}
```

5行目のアサーションが失敗した場合、書き手が意図するアサーション失敗の情報はthe number theory system maybe corruptedだけのはずです。

しかし4行目のsupplemental infoも単なるoutputというイベントであり区別は難しいです。

なのでヒューリスティックにバッファリングしておき、それまでに登場した出力をすべて関連あるものとみなすという手段をとっています。

ファイルの内容を読み取って構文解析すればもう少し精度は上げられるかもしれませんが、けっきょく仕様上、failとoutputの関連付けがなされない以上ヒューリスティックに頼るほかありませんし、関係ないかもしれない出力が多少混じっても実用上はそんなに困らないので実装のリーズナブルさをとりました。

# むすび

とても便利なのでぜひご利用ください。

あなたが決断に悩むのは、あなたがフォロワーシップを持つことで緩和されるのかも

手を動かす人は無条件に偉い

手を動かさない人より手を動かす人のほうが偉い。

たとえもっと上手くやれる人がいたとしても、その能力を適切なタイミングで発揮しなければ能力がないのと一緒。

同様に、自分のほうが上手くやれると思っていても、あなたが実際にその能力を発揮しないのであれば能力は無いのと同じ。

あなたの能力が要求を満たすことはおそらく永遠に来ない

ピーターの法則によれば組織に属する人々は、無能を露呈するまで昇進し続ける。 つまり放っておけば自分の能力を越えた仕事が課せられるようになるということ。 これは能力の成長曲線とは関係なしに働く力学と捉えられている。

ということは「○○ができないので××という仕事をやるには時期尚早だ」と思っていても、○○ができるようになるころには最早××は要求されずさらに難しい別の仕事が求められるようになる。

失敗するなら早めに

常に要求が能力を上回るのであれば失敗のリスクがある程度付いてまわる。 つまづいて、軌道修正して、またやり直して、の連続で成長していく。このサイクルをたくさん回せるほど目標達成に近付いていく。

悩んでいる時間はこのサイクルに含まれない。1ヶ月悩んでいたら、1ヶ月ぶん損することになる。

一般に時間が経つほど今より良い未来が期待されるので、悩んで時間を浪費すればするほど自分への期待値が高まって首を絞めることになる。

今年出るiPhoneより10年後のiPhoneのほうが期待が持てるでしょう。少なくとも10年前に戻れば次の年のiPhoneは格段に良くなっていると信じられたでしょう。

あなたの不安はどこから?

そもそも失敗すると起こる悪いこととはなんだろう? 周りからの失望? 人事評価が下がる? 会社の業績が悪化する?

とりわけ周りからの評価は、自分で制御できずかつインパクトがとても大きく見られがち。 実際にいま周りにいる人間から評価が下がる体験をしたことがなくともそう信じられがちで、自身の過去の経験や想像から補完されてそう捉えられることが多い。

この補完材料の中には 自身が他人に向けるであろう視線 も含まれていないだろうか? つまり 誰か失敗した人を見た時に自身はこう見るであろう という想像が厳しく冷たいものだから、それが自分に返ってくることを恐れている。

将来、自分が失敗した時に周りがどう評価するかは本質的に予測が難しいし制御できないが、自身の想像が無尽蔵に悪い方向に膨らんでいくことは、自身の認識をアップデートすることである程度修正が望める。

自己効力感でしか癒されない

「自分にはできない」という無能感には「確かに自分でもやれる」という自己効力感でしか抗えない。

自己効力感は直接的な成果が挙げられないと得難い。だから言葉尻の応援が無意味とは言わないが、それが自己効力感に繋って良い結果を引き寄せるという期待を持つのは無謀と言わざるをえない。 てるてる坊主を作ることを否定はしないが、それが戦略かと問われたらNoというほかない。

だから周りは成功に近付くために本当に効果的な助言や手伝いをしないといけない。

やることが多くてパンクして困っているという人に「応援しています!」という声かけはおよそ意味をなさない。

周りをサポートするところから

難しい仕事や責任の重い仕事に挑戦している人より周りのほうがリラックスしていて視野を広く持てるということはよくある話。

そういうリラックスした立場でいる時に「自分だったらどうする?」を考えて直接的にスキルを高める努力をしてもいいし、携わっている人の様子を見てどんな困りごとに直面しているかを把握して、どんなサポートをすればいいかを考えてもいい。

たとえば他チームの振り返り議事録を見て、KPTでやっていたら挙げられていたProblemについて自分だったらどんな洞察を得るだろうか? と考えるのはすぐにできる素振り行為ではないでしょうか。

エンジニアであれば他チームのPull Requestを見て、自分だったらこう設計する・レビューでこういう観点を持つといったことを考えてもいい。

むすび

社内のesaに書いていたエッセイを加筆修正してここに記します。

自分は今まで仕事でいろいろな決断を迫られたり実際にこなしてきたけれど、うまくいったと評価できるものは例外なく背中を預けるに足る存在が周りにいたからで、何か想像を超えたすごい能力でなんとかしてきたわけではないよ、ということを共有するために書きました。

YAPC::Hiroshima 2024に参加し「好きな技術《コト》で、
生きていく技術」という話をした。

トークについて

speakerdeck.com

『好きな技術《コト》で、
生きていく技術』という題で話した。技術選択にどんな筋を通すか、技術選択というものを通じて職業人・趣味人としてどういう人生を送るか、という話。

YAPC::Kyoto 2023で聞いたid:ar_tamaさんのあの日ハッカーに憧れた自分が、「ハッカーの呪縛」から解き放たれるまでというトークが良かったこと・それがYAPCのコミュニティに強く受け入れられたことに思うところがあり、今までしたことのない系統の話題を話してみようと思った。

自分にとってとても挑戦的だったのでid:ar_tamaさんにお礼を言うことがYAPC::Hiroshima 2024の目的のひとつだったので、懇親会で無事に果たせてよかった。改めてこの場でもありがとうございます。

2つ前の同じ部屋でid:Songmuさんの「Blogを作り、育み、慈しむ - Blog Hacks 2024」というトークでも、砂場としてのブログの良さの話が出てきてシナジーを感じたので咄嗟に資料に追記するなど、こういうライブ感がカンファレンスで話す醍醐味。

what you likeというテーマからよく連想されるように、こういう技術選択に関する内容の応募は多く、正直落ちるだろうと思って慌てて別の内容でもうひとつプロポーザルを出したのだけれども、結果的にはこちらが採択された。

一際倍率の高い今回のYAPCの中でもこの手の話題のトークとして採択されたことをとても嬉しく思っているし、だからこそベストトーク賞をいただけなかったことがかなり悔しい。

これまで自分がカンファレンスに登壇する際は、基本的に作ったソフトウェアをひっさげて話すことばかりだった。その方が話しやすいというのはもちろんあるし、何より戦士たるもの拳のぶつかりあいですべてわかるみたいな気持ちもあった。

作ったモノがあると懇親会で「これうちでも欲しい」とか「こういう先行事例があるよ」といった話が聞けたり、後日実際に使われたりPRをもらったり……など題材としたソフトウェアで世界と繋がれる。だからトークそのものの反応というのは二の次で良いかと思っている。

一方、考え方を軸にしたトークが世界と繋がるには、聴衆の考え方・心を動かして大仰に言えば人生観を少しでも動かさなければいけないと考えている。

それを測るひとつの基準としてベストトーク賞が考えられるし、YAPCに参加して10年目だし、一度くらいはオーディエンスのことを目一杯意識したトークをしようと思い立った。

トークを良いかんじに組み立てるためにid:Soudaiさんに壁打ち相手になってもらい数ヶ月前から内容を練るなど、今まで体験したことのない取り組みができたのは良い経験になった。

だからこそ受賞に至らなかったことはただ悔しい。でもid:mitani49さんのVISAカードの裏側と “手が掛かる” 決済システムの育て方はおもしろかったしただただ納得。

次までに刀を研いでおきます。

YAPCについて

YAPC::Asia 2013 で『Vagrant と Chef で プログラマブルな 開発環境をつくる』話をした - Sexually Knowing

初めて参加した大きなカンファレンスがYAPC::Asia 2013で、初めて参加したYAPCも2013. つまり個人的にYAPC10年目ということになる。10年か……。

こういう思い入れもあり先述のように今回のトークには特別思うところがあったといえるかもしれない。

自分は首都圏に住んだことがないし、あまり人脈を広げるほうでもないので、こういうカンファレンスに参加する度に1人か2人は初対面の人が増えるので、10年以上こういうコミュニティに参加していても旧交の温めばかりになっていない。これってけっこうえらいのでは?

他に聞いたトークはいろいろあれど、とほほさんのキーノートはただただすごかった。まずけっこう80年代からゴリゴリにコンピュータと仕事しているという話がおもしろかったし、ちょくちょく挟まる話もいちいちインパクトが強かった。当時のUNIX向けにTCPスタックを実装したよ、とかさらりと触れられていたけれど、それひとつで余裕でトークになるのでは?

「どうして続けられるのか?」という問いに淡々と「好きだから続けられる」と答えていらっしゃったのが印象的で、言葉尻は飾っておらず平凡と言ってさえよいが、一連のトークを聞いたあとだと凄味すらあった。 これだけで広島に来てよかったと確信している。

旅行について

薄々感付いていたけど、車に乗るようになってからパッキングが冴えなくなった。

具体的には今回、ダッフルバッグと会期中に使うバックパックとボディバッグを持っていったのだけれど、たとえば駐車場からホテルに移動する時にバッグが多くて大変だった。

公共交通機関を使う時は基本的にバックパックひとつで済むよう持っていくものを厳選したり工夫を凝らしていたけれど、なまじ荷物を運ぶ空間が贅沢になったせいで怠惰になっている。

たとえ車移動であってもパッキングを突き詰めれば、たとえば自転車を積んでいく余裕などが生まれて出先でできることも変わってくるのでここらで意識を改めたい。

広島について

懇親会などで自然とそういう話になったのだけれど、京都を出てどこかに移住しようと考えた時に最後まで金沢市と迷ったのが広島市だった。

それくらい好きな街で、やはり水に囲まれた街は良いですね。金沢より栄えていて、公式懇親会の後にN次会をやるぞとなった時になんだかんだ深夜にやっている飲み屋がそこそこ見つかるのはすごい。

車に乗るようになると、都市高速が走っているのはけっこう驚くというか、発展度合いとしてはけっこう上のほうなんだよなということを思い出す。 しかし自分は金沢くらいの静かさがちょうどいい。

あと愛車の里帰りでもあるので、マツダミュージアムを見学したかったのだけれど土日はやっておらず月曜日はとっくに埋まっていたのでチャンスはなかった。日曜日で本社も閉まっており聖地巡礼もできなかった。次回以降の宿題とします。

「失敗しても何を残せるか」から逆算して選ぶ

何かをやる上で失敗しないに越したことはないですし、そのリスクはあらかじめ減らせたり排除できると良いのはもちろんですが、どうしたってゼロにはできません。

それが新規事業のような不確実性の高い領域であればなおさらで、正解の見えない世界での判断は少なからず博打の性質を孕むことになります。

ソフトウェアエンジニアとしてそういった場面で判断をする時に、合理性で選択肢を減らしていった後で残った選択肢を選ぶ時の決め手として、自分は「失敗しても何を残せるか」という観点を持ち出します。

正解のない世界で自分を鼓舞するためのある種のまじないのようなものですが、これについて掘り下げてみます。

プレモーテムとは

終わりを意識して始める: プロジェクトのプレモーテムを行う方法 [2022] • Asana

プレモーテム (premortem) とはプロジェクトの終了をまず予想し、そこから逆算してリスク要因を見つけたり必要な手順を発見する取り組みのことです。

これはポストモーテム (postmortem) という障害・事故の振り返りを指す言葉との対比として生まれました。

ポストモーテムは「検死」という意味で、そこから転じて障害や事故で生じた損害がいかにして起きたかを振り返るというアナロジーで使われだしました。

このアナロジーに無理矢理あてはめるなら、プレモーテムはさしづめ生前葬にあたるでしょう。

アジャイルサムライなどで取り上げられている「夜も眠れなくなる原因はなんだろう?」や「ご近所さんを探せ」はプレモーテムの一環といえます。

memento moriの精神を持つ

ポストモーテムの元々の意味を考えれば、事業を行う上での失敗は元の表現でいう死にあたります。

これは本当に会社の不可逆的な終わりを意味するかもしれませんし、事業やプロジェクトなどの継続可能性の否定にとどまるかもしれません。

いずれにせよ「死」を克服することはできないと考えるのが合理的です。

そうすると「死」がやってきた時に、我々は世界に何を残せたか・世界から何を受け取れたかが、事業やプロジェクトという「生」がどんなものだったかを表すことと言えるでしょう。

何も爪痕を残せなければただの徒労に思えるでしょうし、たとえ事業やプロジェクトの成功という天寿を全うできなくとも何か「これはやれた」「これは得られた」というものがあれば意義あるものだったと言えることでしょう。

何を残せるか

組織に所属して事業やプロジェクトに貢献することを選ぶ従業員として、もちろん事業やプロジェクトの成功を第一に考えるべきですが、同じくらいSWEとして何を残せるか・何が得られるかも尊重すべき・されるべき点だと考えています。

たとえ事業やプロジェクトが成功を収められずとも、今後のサービス開発に活かせるものがひとつでも得られたのであれば、一連の過程に価値はあるといえるでしょう。

言い換えれば事業やプロジェクトの成功 だけ を追い求めた時、もし失敗すれば後には何も残らず、ただ関係者には徒労感と虚無だけが残るでしょう。

追うべきものはなんでもよくて、ポートフォリオに加えたいと思っていた言語や機能でもいいし、新しいリーダーの抜擢でもいいし、組織体制でもなんでもいい。

それは携わるリーダーが率先して考えるべきで、特に職能軸のリーダーがメンバーのキャリアを守る意味でもあらかじめ考えておくべきことでしょう。

事業軸と職能軸のマトリクス型組織をとる意義のひとつであるといってもいいです。

むすび

「何の成果もあげられませんでした!」を避けるために、事業やプロジェクトの成功の定義を0点 or 100点のall or nothingではなく、100点をとれなかった時でも30点や60点の成果を狙えるよう、30点や60点だった世界をあらかじめ想像・定義しようという話でした。

all or nothingの定義だと、やがて失敗のリスクが大きすぎることを嫌って大胆な判断ができなくなり「これくらいならいけるだろう」という50点くらいの成功を狙うようになります。

しかし失敗する可能性はゼロではないので、実際は0点 or 50点というハイリスク・ローリターンな判断をとることになり、けっきょく「成功」しても大きく進めない上に、相変わらず失敗したあとは焼け野原というジリ貧の状態に陥りやすくなります。

「身の丈にあった判断をする」ということは言い訳をする余地をなくすことでもあり、それによって先に精神的に追い詰められてしまい、ポテンシャルを十分に発揮できずに失敗に繋がる……ということもままあります。

何より成功しても失敗してもどっちに転んでもインパクトの小さいプロジェクトに関わりたいとは思いません。どうせやるならワクワクするほうがよいです。

もちろん大胆不敵と蛮勇は同じではありません。蛮勇かどうかを知るためには、自分の身の丈がどれくらいかを知っておかなければいけません。

だから日頃からナイフを研いでおくことが大事で、そのために使えるもの・時間はうまく使おうと心がけています。この前書いた記事もそういう取り組みの一環でした:

作りたがりな自分を飼い慣らすための趣味プログラミング - Sexually Knowing

作りたがりな自分を飼い慣らすための趣味プログラミング

職業ソフトウェアエンジニアが普段の業務でソフトウェアを作る時は、過不足ないソフトウェア製作を通して試行錯誤を素早くこなして目的に至ることが求められているでしょう。

『リーン・スタートアップ』やアジャイル開発などで説かれている考え方です。

ソフトウェア産業が扱う領域がより高度で複雑になってきたために生じる不確実性とうまく付き合うためのやりかたで、今日では前提とされます。

一方で、それらを支えるソフトウェア技術の幅や深さを広げるためにはどうすれば良いか、必要性は理解されてはいても具体的な実現方法を模索しているという人が多いのではないでしょうか。

ここでは、会社としてどうするかという点から一旦離れ、一個人のソフトウェアエンジニアとしてどうするかという観点で考えをまとめたいと思います。

作りたがりな自分

自分は「作りたがり」な性質で、業務では常にオーバーエンジニアリングに走ってより新しいこと・おもしろいことを試したい欲望と戦っています。

手札を増やすことは個人としてもひいては組織にとって良い影響があると信じていますが、そういったお題目より何より、単に「そうしたい」からそうしたいのだ、という欲望を抱えているだけなのだと思います。

特に新しく何かを立ち上げるとか、作って置き換えるという時は、格好の機会なのでなんとかして欲望を満たしたくなります。

同時に、そういった新しく立ち上げたり置き換えるという瞬間は、不確定要素が多く見通しが悪い瞬間ともいえ、作るモノの核から逸れた部分に時間を割いては後々に自分たちの首が絞まることもよくよく理解しています。

まず、自分(たち)にこうした欲望があるかもしれないことを自覚し、そしてそれをうまく飼い慣らしてよりクリアな思考で立ち向かわなければ誰も幸せになりません。

作りたがりを満たす場

何かおもしろいことをやる・新しいことを試すことさえできればよく、その格好の機会が転がってくる場が業務であるというだけなので、業務以外に機会を求めることができれば葛藤せずに済みそうです。

その場として自分が気に入っているのが、タイトルにも挙げた趣味プログラミングです。

趣味プログラミングで作るモノを自分は 砂場プロダクト あるいは単に 砂場 と呼んでいます。

砂場では、自分の資源 (時間、お金、好奇心、etc.) が許す限り何をやっても良いし、何を選んでも良いです。つまり「作りたい」という欲望を存分に満たせます。

砂場での遊び方

いつもと違うことをしてみたいと考えるものの、いざ自由を手にしたら何をどうしたら良いか悩んでしまうかもしれません。

自分が砂場で遊ぶ時に考える軸を以下にいくつか挙げてみます。Webアプリケーションエンジニアなので、Webサービスを主な題材として想定しますがWebサービスによらない軸だと思います。

  • 実装する言語
    • 型システムの特性
      • 強い型付けか、弱い型付けか
      • 静的型付けか、動的型付けか、漸進的型付けか
      • 特定の仕組みや性質を備えているか
  • フレームワーク
    • モノリシックかビルディングブロックを組み合わせるか
  • 実行環境
    • インフラストラクチャ層を抽象化しているか露出しているか
  • データストア
  • ソフトウェアアーキテクチャ
  • CI環境
    • デプロイ方法
  • IaC
  • 監視
    • 死活監視

ぱっと思いついて、実際に変えてみたことのある点だけでもこれくらいあります。

次に大切なのが 題材は同じままでも良い ということです。つまり同じ砂場プロダクトのまま、データストアを入れ替えてみてもいいし、IaCツールを変えてみてもいいです。

むしろ動いているものはそのままに一部だけ移行するということのほうが現実にはよくありますし、チュートリアルをやるだけでは得難い体験なので価値があります。

砂場の見つけ方

砂場でどう遊べば良いかはわかったけど、そもそも何を作ればいいか思いつかないよ、ということもあるかもしれません。むしろそういう悩みのほうが多いでしょうか。

「何を作ればいいか悩む」という声に自分はよくポートフォリオかブログを作ってみてはどうか、と答えます。

ポートフォリオとは自分の経歴とかプロフィールをまとめたもので、自分の場合だと https://aereal.org/ です。

ポートフォリオはページ数は知れているし、更新頻度もそんなに高くないので立ち上げ時の負担が少ない割に、落ちていると格好がつかないので死活監視や変更の滑らかな反映などを真面目に考えたくなります。

SLOを高めたいという内発的動機付けが強く働くともいえます。

ブログは、Webアプリケーションとして素朴ながら拡張しがいがあってネタに困りにくいです。

たとえば:

  • 記事の全文検索
  • 下書き保存 (公開状態の管理)
  • 編集環境
  • 読者からのフィードバックを受け取る方法
  • 既存のブログからの移行方法

……などなどがあります。

ポートフォリオと同様に落ちていたらやはり気まずいのでちゃんとしようという気になりますし、そこそこ書いていれば検索エンジンクローラもやってくるので人間以外のトラフィックを扱う良い題材になります。

また、何かを試す時に都度新しく作らずとも、同じものを作り直してもよいです。これを 式年遷宮 と呼んでいます。

たとえば自分のポートフォリオは当初、GitHub Pagesでホストしていましたが内容や基本的な実装はそのままにFirebase Hostingに移行し、その後、フレームワークをNext.jsに変えたりなどしています。

ネタを見つけるのはやはり大変ですが、一度見つけたらちょっとずつ手を加えていけば飽きることはないでしょうし、自然と愛着が湧いて運用をしっかりしようという気持ちにもなれるはずです。

むすび

この記事では趣味プログラミングを通して、ソフトウェアエンジニアとしてのエゴとうまく付き合う方法について述べました。

しかし、あくまでこれは職業ソフトウェアエンジニア個人としての考え方を提示しただけで、組織がすべてのソフトウェアエンジニア従業員に強いるないし求めることは違うと考えています。

組織の観点ではあくまで、組織が提供するリソースにおいて従業員が然るべきスキルアップを図れるようになっているべきで、個人のプライベートな時間を費すことを求めるのは破綻しています。

それでも示唆は得られると思います。こういった考えや振る舞いを推奨したいのであれば、プログラミングをするだけの十分な余裕があることは必須です。

単純に残業時間が多くては物理的に確保できないでしょうし、普段の業務で心的な負担が強ければ、たとえ時間が余っていても余暇時間は疲れ切った内面を整えることに終始するほかないでしょう。

こうした考え方は特別新しいものではなく、古くはGoogleの20%ルールなどにあたることができます。

OpenTelemetry Collectorのconfmap providerを実装してみる

この記事はOpenTelemetry Advent Calendar 2023の4日目の記事です。


こんにちは・こんばんは・おはようございます、id:aerealです。最近は所属組織のブログをよく書いています。

2023年は個人的にOpenTelemetry元年を迎えたところ社会でも元年めいているようなので一緒に盛り上がるためにも、この記事では掲題の通りCollectorをカスタマイズするテクニックについて紹介したいと思います。

confmapとconfmap providerとは

confmapとはOpenTelemetry Collectorの設定ファイルを解決して決定的な値を作るためのパッケージです。

confmap providerとは設定ファイル中の ${env:DD_API_KEY} のような記述を解決するコンポーネントで、外部のデータソースから値を取得します。 秘匿情報やデプロイごとに動的に変えたい値を指定したい時にこのコンポーネントを使うことで、設定ファイルはそのままにCollectorの挙動を変えられます。

たとえば設定ファイル中の ${env:DD_API_KEY} のように記述すると環境変数DD_API_KEY を参照できます。

confmap providerを実装する

Collectorのcoreには環境変数の他、ファイルやHTTP/HTTPSエンドポイントと通信するconfmap providerが含まれています。

しかし商用環境でアプリケーションを動かすシーンでは十分とはいえず、たとえば秘匿情報はふつう専用のサービスに保存し、実行時に都度取得するでしょう。 たとえばAWSであればSSM Parameters StoreSecrets Managerが挙げられます。

当然、サービスに認証情報を渡す必要があり、認証情報をキャリーする場所はHTTPヘッダが普通なので、httpsproviderではやはり実用上十分とはいえません。

そこでconfmap providerを自作し、それを組み込んだOpenTelemetry Collectorをビルドし動かす手があります。 なおOpenTelemetry Collectorを自前でビルドする際の知見は筆者が所属組織の開発者ブログで書いた記事も参照してください: refs. Lambda Extensionと自家版OpenTelemetry Collector - Classi開発者ブログ

confmap providerを自作するにはconfmap.Providerインターフェースを実装する型をConfiProviderSetings.ResolverSettings.Providersに渡します。 refs. opentelemetry-lambda/collector/internal/collector/collector.go at own-dist · aereal/opentelemetry-lambda · GitHub

confmap.Providerは Retrieve() で仕事を果たします。引数に渡される uri はたとえば ${awsssm:/path/to/param} という記述であれば /path/to/param という文字列が入ります。 Retrieve() は、この引数を外部サービスなりに渡して得たデータをconfmap.Retrievedという型で包んで返す責務があります。

confmap.Retrieved はすべてのフィールドがプライベートなので confmap.NewRetrieved() 関数のみで作れる構造体です。 ドキュメントにあるようにおおむねJSONYAMLと相互に変換可能な型のみを渡せますが、marshal/unmarshalをカスタマイズする方法は少なくとも公開インターフェースとして現在提供されていないようですので、NewRetrieved()が許容する型をその通りに返さなければいけません。

以上を踏まえてAWS SSM Parameters Storeからパラメータを取得するconfmap providerを実装したコードが以下になります: otel-confmap-provider-awsssm/provider.go at main · aereal/otel-confmap-provider-awsssm · GitHub

AWS SDK Go v2を使ってssm:GetParameterを呼び、その結果を素朴にconfmap.NewRetrieved()に渡すだけです。

SSMとの認証はAWS SDKのデフォルトの認証情報チェーンに頼っています。即ちAWS環境ではメタデータエンドポイントを介して得られたトークンを使います。

ローカルなどその他の環境で動作させるには AWS_ACCESS_KEY_ID などの環境変数を設定するか ~/.aws/credentials に適切な値を置く必要がありますが本題から逸れるのでここでは詳しく触れません。 言い換えれば自作したconfmap providerで外部サービスへ認証するには環境変数などconfmap providerに頼らない方法で認証情報を得る必要があります。

また筆者が実装したParameters Storeから取得するconfmap providerは都度HTTPリクエストを投げればよいので使っていませんが、confmap providerが終了した際に呼ばれるイベントハンドラを設定することもできます。

つまりデーモンのように振る舞うconfmap providerを実装することができます。これはステートフルな通信プロトコルを使うことを示すだけではなく、設定のホットスワップに対応する意欲があればサポートできることも意味します (実際に変更を検知するコールバックにアクセスできます)。

むすび

以上の解説は公開されたインターフェースとドキュメントコメントによるものですが、confmapパッケージのドキュメント自体はWIPであると書かれているので、実装から離れた設計や思想の部分は誤解などがあるかもしれません。

現在のところconfmap providerはcontrib系リポジトリを含めても素朴なものしか見当たらないので、実際にWatcherFuncなどをどのように活用するべきなのか筆者も理解しかねています。

この記事がconfmap providerやOpenTelemetry Collectorの理解の一助になったり、自作してみるきっかけになれば嬉しいです。

良き監視ライフを!

YAPC::Hiroshima 2024で『好きな技術《コト》で、生きていく技術』について話します

fortee.jp

そういう話をします。

職業SWEなので仕事での経験ももちろん話しますが、同時に趣味SWEでもあるので仕事の時間と趣味の時間を最大限効率的に使ってより楽しくいろんな技術に触れて楽しく暮らしたらどうしたらいいんだろうね? 的な話になる予定です。

ざっくりいうとキャリア形成的な内容になるのかもしれませんが、個人的に自分自身がそういうキャリアとか人生観みたいなふわふわした話を壇上からすることに抵抗があるので、できるだけ作ったもの・やったことをベースに多くの人が感じている・感じてきた不安や悩みを掘り下げる一助になったらいいね、という気持ちで準備しようと思います。

というわけで来年の広島でお会いしましょう。僕は後日行く予定のマツダミュージアムの予約のことを考えます。

blog.yapcjapan.org

YAPC::Kyoto 2023に参加して『qron: Cloud Native Cron Alternativeの今』というトークをした #yapcjapan

qron: Cloud Native Cron Alternativeの今

掲題の通りのトークをした。元々YAPC::Kyoto 2020のトークとして採択されていたトピックを2023年版として話すことに。

オンサイトのカンファレンスに参加するのは2019年のbuilderscon以来なので2年半ぶりくらい。

#builderscon tokyo 2019で「自動作曲入門」について話した - Sexually Knowing

トークについて

speakerdeck.com

もともとあったアイデアを必要に駆られて仕事で作ったものの話。

2020年当時はできたてほやほやだったけど2023年はもう運用して数年が経とうとしているので「実際動かしてみてこうだったよ」という話も盛り込んだ。

結果として強気の40分枠で応募し、採択の運びとなった。自分で選んでおいてだけど40分のトークを黙って聞いているのはけっこう気合がいるので構成とか話し方とかに気を遣う。

最近は別のプロジェクトで絶賛がつがつ作っているところであるものの、ずっと世に出ていないしプロジェクトやチームはどんどん勝手にめちゃくちゃになっていくしで気が滅入っていたけど、感想ツイートとかでおもしろそうって言われているのを見ると自分の仕事がちゃんと見られた気がして嬉しかった。

あと質疑コーナー含めて突っ込んだやりとりができたのも良かった。ソフトウェアエンジニアとして生きているっていう実感がそこにある。

キーノートについて

はてなにいた頃ずっとお世話になっていた id:onishi さんではあるけれど、キーノートに登場するエピソードは以前に聞いたことはあってもその当時にどんな思いを抱いていたかといったことは意外と聞いたことがなくて「そういうことがあったんだ」という新鮮な驚きと共に、onishiさんのパーソナリティ・考え方の裏付けがとれたような気がして得心がいった。

onishiさんもnekokakさんも「やるべきことをやってくれる誰かを待つのではなく自分がやる」というようなことをトークで話していて、やっぱり手を動かした人だけが世界を変えるなんだなあ。

onishiさんはご自身をモブと謙遜・自虐されていたけれど、Web業界で花形とされているソフトウェアエンジニアの第一線とは少し距離があった・退いたというようなご自身の認識からしたらそういう表現は確かに的を射ていると評することもできるかもしれないと思う一方で「主人公」という生き方だけにこだわっていては果たし難かった功績を積み上げられてきたと思う。

イベントについて

前述の通りオンサイトのイベントは2年半ぶり。

もはやPerlを書いていないどころかエコシステムから遠ざかってからだいぶ経つけれど、YAPCに参加するタイプの人たちとはウマが合うなあと思った。

久しぶりに会う人・初めて会う人などなどと話しているうちに生粋のエンジニアとしての自分でいられているなあと感じてのびのびできた。

しかし2年半ぶりということで社交筋 *1 ががっつり落ちていて、日曜の夜にあったスピーカーディナーでは抜け殻になってただ蒸しカキを食べていた。

メンタルはともかくフィジカルの衰えも強く感じたのですぐに鍛えられるフィジカルは次に向けて鍛えておこう……。

仕事もプライベートも最近めちゃくちゃで果たして楽しめるのか不安だったけれど前日と併せて2日間楽しく過ごせた。


けっこうな数が知り合いが運営に関わっていることを知ってちょっと驚いたりもした。カンファレンスの運営ってものすごく大変と伺っているので。

よりカンファレンス運営が身近になったなと思う一方で、カンファレンスで日頃磨いてきた芸を披露することに改めてこだわりたいなという思いを改めた。 (別にスタッフになることと相反しないが……)

あらためてYAPC::Kyoto 2023開催おつかれさまでした、たのしかったです。

*1:社交するための筋肉

チケット販売締切が1/31に迫っているYAPC::Kyoto 2023で「qron: Cloud Native Cron Alternativeの今」というトークをします

blog.yapcjapan.org

元々YAPC::Kyoto 2020に応募していた「qron: Cloud Native Cron Alternativeの今」をベースにした話をする予定です。

2020年当時は運用を始めたてだったので「こういうコンセプトで作ったやで」っていう話を中心にするつもりだったのですが、2023年現在は運用して数年が経ったこともあってコンセプトの紹介に加え「実際こういうところがうまくいっている・うまくいっていない」という話をしたほうが現実味があってよさそうだなと思い、タイトルも「〜の今」としました。

久しぶりにオンサイト開催されるカンファレンスで喋れるということでワクワクしています。個人的には初春の京都に出かける口実ができたのも嬉しいですし、登壇予定の面々の中に見知った名前もあったので久しぶりに顔を合わせられること・カンファレンスの発表を通して知見や交流を深める機会がまた巡ってきたことも楽しみです。

開催日の3/17は日曜日で次の3/19が春分の日であるため、連休チャンスと見なされ既に宿は混み合っているそうです。自分含めて遠方から参加される方々は早速宿を取るのが良いでしょうね。 迷ったら予約、チケット購入が吉と今年のおみくじも言っていました、間違いありません。

ちなみにチケット販売は2023/1/31までです (refs. 【販売期間は2023年1月31日まで】 YAPC::Kyoto 2023 参加チケットの販売を開始します! - YAPC::Japan 運営ブログ)。墓地が無料の時代は終わった、生きて今すぐチケットを買おう!!!

2022年振り返り

作ったもの

他、ちょくちょく使っているライブラリに軽微なバグ修正の報告をしたりPRをしたり、などなど。

hoist-gql-errorsは最近仕事で必要に駆られて書いたんだけど、Datadog APMの仕様に詳しくなったのでそのうち改めてまとめておきたい。

振り返り

サラリーマンをやっている以上仕方ないのかもしれないが自分の思うようなことがやれなくてフラストレーションを感じ続けた1年だった。

別にそんなに独り善がりなことを考えているわけではないとは思っていて「組織が良い方向に向かうにはこういうことをやったほうが良いと思うし自分もやりたいからやらせてくれ」っていうような話で、マネージャーにも了承してもらえていたけれどやんごとなき事情でそれより優先してやらないといけないことが出てきて……みたいなかんじ。

で、その「やらないといけないこと」というのが有り体に言って楽しくない。

別に労働ってそんなもんでしょと言われたらそうかもしれないけれど、フルタイム労働者だったら1日の1/3の時間を捧げるわけで、週5日勤務だったら1年の1/4になる。 1年の1/4の時間をかけるならどうせなら楽しくてやりがいのあるものであってほしい。

もちろんフルタイムの労働をやめるという方向性も考えられるけれど、2つの点から直近ではあまり考えていない。

1つは収入の安定性の面。車を買って趣味になったので安定して (継続して) 今以上の水準の収入がほしいし必須だということ。

もう1つは感情的な話で、やっぱり事業会社の事業に継続して携わることで領域を問わずいろいろやれるのは自分の性格に合っている。 いろいろ関わることで苦しいところもあるけれど「ここが良くなればサービスの質が上がってユーザもハッピーになれるのに」というところが見える場面はどこにいてもあると思っていて、そういう時に自分の管掌外だからといって見てみぬ振りをするのは性格上、おそらくとても難しい。

けっきょく無視することで別のストレスを感じてしまうなら、自分の仕事の範囲を変に決めきらずにおいておきたい。

というところで年明けのYAPC Kyoto 2023でなにか話したいけれど、何を話そうか……と悶々としている2022年でした。

趣味とOKRと私

以下は過去の社内LT大会で発表した時の資料です。

友人とOKRの話になって思い出したので発掘しました。

趣味にOKRを取り入れてみた感想としては、とても遠い・あるいは抽象的なゴールに向かう際に「まず何をやればいいんだろう」と道筋を立てるためのフレームワークであり、またゴールにどれだけ近付けたかを観測するためのフレームワークでもある、というものです。

Objectiveがゴールで、Key resultsがパンくずのようなイメージを持っています。Key resultsを達成できたかどうかはパンくずを拾ったかどうかといえるでしょう。


今日する話

スマブラと私

  • 強くなりたい!
  • 勝てると楽しい、負けるとイラ……イラ……
  • 強いプレイはかっこいい
    • コンボがちゃんと決まる
    • 動きが早い
    • 大胆な判断

スマブラのオンライン対戦と問題

  • レートマッチみたいな仕組み
  • ルールがいわゆるガチルールに統一されていない
    • あくまで「優先ルール」という扱いなので、アイテムありのワイワイルールの人とマッチしたりする
  • 一期一会なので、負けた相手と連戦して対策を積むことが難しい
  • プレイヤー毎ではなくプレイアブルキャラクターごとのレートなので、かなり格上のプレイヤーが使う別キャラとあたることもある
  • プレイヤーの回線品質がまちまちでめちゃくちゃラグい人とかがいる
  • 勝っても負けても成長の糧となりにくい、ストレスが溜まる

コミュニティに入る

  • スマブラプレイヤー・配信者のYouTubeメンバーシップに入っている人が参加できるDiscordコミュニティがある
  • いわゆるガチ勢が集まっている
    • 回線の品質が高い
    • 一貫したルールで対戦できる
    • 連戦できる
    • 自分よりちょっと格上の人とマッチングできる

勝てない

  • 勝てない!!!
  • コミュニティにいるのはスマブラ大好きな学生がほとんど
  • かけられる時間の量で勝てない
  • 恋に仕事に大忙しな社会人が勝つには 効率を極めるしかない

OKR

  • Objectives and Key Results
  • 目標と成果指標

目標は、場合によっては若干気後れするくらいの高いレベルに設定します。 成果指標は、数値化して測定し、簡単に評価できるようにします(Google では 0~1.0 の範囲で設定しています)。

from https://rework.withgoogle.com/jp/guides/set-goals-with-okrs/steps/introduction/

設定した目標

  • 1年以内にコミュニティのプレイヤーAさんにBO5で勝つ
    • プレイヤーAさんは良くコミュニティで対戦してくれる格上の方
    • BO5 = Best of 5; 3本先取で勝敗を決める方式
      • BO3と比べて運の要素が減る = より実力が出やすい
    • これまでの戦績: BO3どころか10戦やって1本取れるくらい

指標

  • 戦績
    • 週あたり2本は取る
  • プレイ
    • コンボ精度を高める
      • コンボAを1試合あたり5回試行する
        • 成功確率を80%以上にする
      • コンボB……
    • etc.

余談: Notionの活用

表を使うことで、シチュエーションに応じたコンボの確認をしやすくしている

1年やってみて

  • 結果的に未達だったけど得るものは多かった
    • 細分化した成長指標ごとに振り返ることで「これはできた」「これはまだまだ」という成長の足がかりが得られる
    • 「これはできるようになったじゃん」と自分を励ます
    • 各指標は目標より向上・改善する難易度が低いのでインクリメンタルなフィードバック・手応えを得やすい
      • e.g. コンボの精度が50→80%に上がったぞ!
  • 実際は1ヶ月、四半期、半年ごとに振り返っています

git pushするだけでGo製アプリケーションをリリースするGitHub Actionsのワークフローを整えた

Go製アプリケーションのリリース自動化

Goで書いたアプリケーションをリリースする際にやらないといけないことはいろいろある。

まず当然のこととしてコンパイルして成果物を作り、それをリリースする。 せっかくクロスビルドが容易なGoを使うので配布する成果物も可能な限り幅広い環境に対応させたい。

また、リリースに含まれる変更の概要をまとめたいわゆるChangelogのようなものも簡単に作れるとなおよい。

GitHubでホストするリポジトリのリリースについて主に考えたいので、リリースのホスティングGitHubのReleasesになる。 各Releaseの説明をChangelogとし、変更がもたらされたPull Requestのリンクも添えたい。

リリースするということは新しいバージョンを決めて、Gitのタグを打つ必要もある。

ひとつひとつはよくある作業だし特段難しいことはないが、いちいち手でやっていられないのでCIに任せたい。 掲題の通りGitHub Actionsでこれら一連の作業を自動化させるという話。

GitHub Actionsによるリリース自動化それ自体は目新しいトピックではない。しかしこの記事で紹介するワークフローはgit pushする以外に開発者がとるべきアクションはない。 具体的には、世の中の先行事例はGitのタグ発行は開発者の手作業で、それを契機にパイプラインを開始する……というものばかりだがそれも不要になる。

goreleaserにビルドまわりを任せる

最近はgoreleaserというツールがよく使われているのでビルドまわりはこれに任せる。

goreleaserには:

  • アプリケーションをビルドする
  • ビルドした成果物をGitHub Releasesにアップロードする

……という2点を任せる。

アップロード対象のリリースは実行したリポジトリに存在する最新のタグから同定される。 言い換えるとタグを打つのはgoreleaserの責務外となる。

semantic-releaseにバージョニングを任せる

[semantic-releaser][]というツールがある。JavaScript (Node) で実装されたCLIツールでConventional Commitsに従ってコミットログを書いておくとよしなに次のリリースバージョンを決めてくれる。

Conventional CommitsはSemantic Versioningを参照し、たとえば feat: blah blah ... とか書くと新機能の追加なのでminorの更新を含意する、などのルールを定義している。 Conventional Commitsに従うとコミットログから次のバージョンを決めるルールがツールを越えて相互運用できる。

他にもプラグインGitHub Releasesを作ったりGitタグを打ったり、あるいはNPMにアップロードするなどができる。

JavaScriptで書かれていることからもわかるように元々NPMパッケージのリリースを主眼に置いてエコシステムが整えられたツールだが、前述のようにリリースバージョンの決定以外はプラグインとして実装されているので、NPMパッケージのリリース以外にも使える。 必要なのはConventional Commitsに従ってコミットすることだけ。

つまり semantic-releaseを使ってGitタグの発行を含むバージョンアップ作業を自動化し、発行された新しいタグに成果物を作成・紐付ける作業をgoreleaserで自動化する というのがワークフローのあらましになる。

ワークフロー例

実際のワークフローを例に説明する。

pkgboundaries/ci.yml at main · aereal/pkgboundaries

上記ファイルから抜粋・簡略化したYAMLが以下。

  determine_release:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    outputs:
      will_release: ${{ steps.determine_release.outputs.new_release_published }}
    steps:
      - uses: actions/checkout@v3
      - id: determine_release
        uses: cycjimmy/semantic-release-action@v3.0.0
        with:
          dry_run: true
        env:
          GITHUB_TOKEN: ${{ github.token }}
  release:
    runs-on: ubuntu-latest
    needs:
      # - test
      - determine_release
    if: ${{ needs.determine_release.outputs.will_release }}
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: cycjimmy/semantic-release-action@v3.0.0
        env:
          GITHUB_TOKEN: ${{ github.token }}
      - uses: actions/setup-go@v3
        with:
          go-version: '1.18.x'
      - uses: actions/cache@v3
        with:
          path: ~/go/pkg/mod
          key: go-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            go-
      - uses: goreleaser/goreleaser-action@v2.9.1
        with:
          version: latest
          args: release --rm-dist
        env:
          GITHUB_TOKEN: ${{ github.token }}

determine_releaseとreleaseという2つのジョブに分けている。

determine_releaseは次のリリース予定を調べるジョブで「実行時点で新しいバージョンが発行されそうか」を示す真偽値 will_release を出力する。 cycjimmy/semantic-release-actionというsemantic-releaseを実行するActionがあり、その outputs.new_release_published という真偽値を参照している。 determine_releaseでは dry_run: true を指定し実際のリリースは行わず、バージョンアップ予定だけを調べる。

releaseジョブは実際にリリースを行う。 まず needs にdetermine_releaseを指定する。これは単に依存関係を宣言することに加えて needs コンテキスト経由でdetermine_releaseジョブの出力にアクセスする目的もある。 if: ${{ needs.determine_release.outputs.will_release }} で「 needs.determine_release.outputs.will_release がtrueだったらreleaseジョブを実行する」という意味になる。 新しいバージョンアップを要する変更がコミットされていなければリリース作業は行われない。

参考: - Workflow syntax for GitHub Actions - GitHub Docs - Contexts - GitHub Docs

続いてsemantic-releaseを実行し、GitHub Releasesを作る。 以下にsemantic-releaseの設定を引用する:

{
  "branches": [
    "main"
  ],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/github"
  ]
}

from pkgboundaries/.releaserc.json at 8f58c3dd5880534c1fd8cc8f513795722b52b279 · aereal/pkgboundaries

特別な設定は必要としない。デフォルトでNPMパッケージを公開する @semantic-release/npm が有効になっているのでそれ以外の有用なプラグインだけを明示しているだけに留まる。

最後にgoreleaserを実行する。 goreleaser-actionというインストールから実行までをよしなにやってくれるActionがあるのでこれを使う。 goreleaser/goreleaser-action: GitHub Action for GoReleaser

goreleaserはYAMLでいろいろ挙動をカスタマイズできるのだが、今回重要なのは release.mode になる。

release:
  mode: keep-existing

from pkgboundaries/.goreleaser.yml at 8f58c3dd5880534c1fd8cc8f513795722b52b279 · aereal/pkgboundaries

goreleaserもリリースノートの作成やGitHub Releasesの作成ができるが、今回のワークフローではそれらはsemantic-releaseに任せている。 mode: keep-existing を選ぶとタグに対応するGitHub Releasesが既に存在したらタイトルや本文を置き換えず成果物だけアップロードするという挙動になる。

こうすることでsemantic-releaseとうまく共存できる。

今後

リリース時に人間による確認を挟みたいという場合には、Environmentsが使えるかもしれない。

参考: Using environments for deployment - GitHub Docs

EnvironmentsはDeploymentsに関連する概念で、その名の通りデプロイ対象の環境を表す。 Environmentsはそれぞれデプロイ時に必須となるcommit statusやレビュアーを指定できる。 これら機能はBranch protection rulesと似ていて、たとえばproduction環境では所定のチームやユーザのレビューなしにデプロイできない、といった設定ができる。

参考:

GitHubにおけるDeploymentsは単にイベント履歴とそれらに応じたWebhookでしかない。 最近追加されたGitHub Actionsとの統合では、ジョブに environment: production のように記述することである環境の利用を宣言できる。 このジョブ実行開始時にDeployments APIでdeploymentとdeployment_statusが作成される。完了するとsuccessもしくはfailureの記録がGitHub Actionsによって行われる。

実際のデプロイ処理はジョブのstepとして自由に定義できるので、この記事で紹介したワークフローをデプロイ処理として記述することもできる。

するとEnvironmentsの機能でDeploymentsの開始時にチーム・ユーザによる確認を挟んだ上でリリースする、といったことが実現できる。

これは規模の大きいチームで厳格な統制を図りたい時に便利かもしれない。