ikuo’s blog

育児やエンジニアリングについて

RSGTが終わり、2024年が始まった

RSGT2024に参加してきました。今年も最高だったー! いつも素晴らしい場を作っている運営、コミュニティ、参加者の方々には尊敬と感謝でいっぱいです。

昨年はRSGT直後からゴタゴタし、起こっていることを受け入れて環境に適応するだけで精一杯でした。忙しかった。 正直記憶があまりありません。人は忙しくなると容易に良くないとわかっているはずの行動を取る習性があるように思います、なぜでしょうか。

年始の休みもあって、最近やっと少しずつ前を向いて将来のことを考えられるようになったとおもいます。余裕大事。

そんなわけで超個人的な体験を備忘録とアウトプットのリハビリを兼ねて書きました。 今年も最高のギャザリングでした!関わったすべての皆様本当にありがとうございました!!

印象に残ったこと

また受け入れてもらえた

去年はコミュニティに参加する気力がわかず遠ざかってってしまっていて、正直なところ今年のRSGTが始まる前はワクワクよりも緊張のほうが大きかったです。

一年ぶりにお会いできたかたがたがちゃんと僕のことを覚えてくれていて、変わらず声をかけてくれたのが本当に嬉しかったです。 あー僕ここにいていいんだ、と思えました。

自分も誰かにとってそんなふうになれるように...

もちろん初めましてての方とも多くの出会いがありました。 最終日に菩薩会に偶然参加できたのもラッキーで楽しかった!

Woody と再開

朝イチ廊下を歩いていたので声をかけて、5年ぶりの再会を喜びました。 僕にとってキャリアの転機となった存在の一人で、ぼくのスーパースターでありヒーローなんですけど本当に気さくな方で大好きです。 朝から最高のスタートダッシュ切れました。

2日めの朝9時位についたら誰もいない main hall で一人座ってたので雑談しました。日本観光大変楽しまれたみたいです。 ラーメン食べなね、っておすすめしときました。

彼のセッションは生視聴3回目だけど大好きなのでまた聞きに行ってまたパワーをもらえました。やはり公演は生です。無限に推せます。

RSGT, 年々海外スピーカーの方増えていて本当にすごいです。 だってHeidi HelfandさんやMichael FeathersさんやWoody ZuillさんやZuzi SochovaさんやJoe Justiceさんが普通に廊下あるいてるんですよ...。海外カンファレンスじゃないですか...。

日本にいながらこんな体験ができるなんてなんとういう贅沢でしょうか。チケット安すぎます。というか実質無料です。

ノリックと再開

お互いしばらくコミュニティから離れていたけど、数年ぶりに再開して、でも以前と変わらず話ができたのがすごく良かった... 子育ての悩みを共有したり、サイゼにはいけなかったけど飲みにも行けたし最高でした。持つべきものは83の友。写真取ればよかった!

むらみんさんと再会

またまた一年ぶりで、むらかみさんとの再会も本当に嬉しかった。 セッション隣りに座って気兼ねなく感想戦に突入できる安心感でついつい話しかけに行ってしまう。。 新しいチャレンジも聞かせてもらって、いつも前に進み続けてるのほんとに尊敬できます。いつもだけど今回も良い影響めっちゃもらえました!

ようさんのセッション

www.docswell.com

ここ数年ようさんのアウトカムの話を最初に聞いて、背筋を伸ばし直しています。 本当に大切なことなんですけど忙しくなると意識できなくなっちゃうんですよね...

ようさんとも久しぶりにお話できて嬉しかった。 Discordの僕の小さな疑問をちゃんと拾ってくれて、廊下で声かけていただいて議論してくれるのさすがすぎました。嬉しかった〜ありがとうございました!

漆原さんのセッション

エモかった。エモい開発と売上は両立する。 エモいから始めよ。 そしてオタクしか勝たん。推していこうぜ。

お金がないと会社に何が起こるのか、を経験した今、心を抉られるような内容でした。 ディベロッパーとして大切にしなければいけないことを深く心に刻めたように思います。

カケハシオールスターズのみなさんと感想戦

いつもXで公開イチャイチャチームワークを見せつけてくれるカケハシのドリームチームの皆さんと飲みに。 みなさん気さくで面白くて、最高の時間を過ごしました。飲みすぎて次の日ちょっとお酒残ってましたがw

ゆのんさんがホテルに帰ろうとするいくおを捕まえて引っ張ってきてくれて、なんというか流石です。あの場の実現に大きな感謝です!

あれ?エヴァガンダムの話しかしてないような...

いくおが「毎日この人たちと働けるんですよ、いいでしょ」って本当に嬉しそうに言ってたのが印象的で、正直にこれは羨ましいです。。

いくおのセッション

speakerdeck.com

会場を巻き込んで全員を楽しませる、学びと元気が得られるセッション。 Discord見ながらのセッション進行すごすぎます。度々ひろってくれてありがとう!

スライドは全人類必読、全ソフトウェアエンジニア必修です。

対話なんだよ。対話が大事なんだよなぁ...。

38秒デッドラインを超えたのも最高のオチでした。

おがさわらさんにキャリア相談

だいすきなおがさわらさんとの再会も本当に嬉しかったし、一緒に飲みに行けたのもほんとにさいこうでした。 夜も酒も深まった感想戦2日目、今の自分のもやもやをおがさわらさんに相談したらズバッともやが晴れました。 今までの無限ループは何だったんだと。。凄すぎます。また飲みましょう!

シニアのメンターいない問題について話して、ぼくは結構深刻なんですが僕と同じとか上の人達は一体どうしてるんでしょうか。。 どなたか相互メンターやりませんか?

OST

英語を楽しく学ぶ方法と、異なる文化でフィードバックをする方法について。

そういえばかねごんさん英語発音めっちゃ上手かったです。どうやって練習してんのか聞けばよかった。

第二言語で議論するとどうしても母国語での議論より時間がかかってしまうので 結局最後までこのグループで話してましたが、Zuzi さんやフレディさん(スペルわからぬ...)と西洋、US、アジア圏、日本の意思決定の方法やコミュニケーション文化の違いについて、それを踏まえてどの用にフィードバックするか、みたいな話ができて大変楽しかったです。

ぼくが「日本って変で特殊な文化じゃね?」みたいな間抜けな質問をしたときも、 いやどこも特別な文化を持ってるんだよと、言われてみれば至極当然な回答をもらいまして、もう一生忘れません。

Woodyにキャリア相談

OSTのあと、Woodyに今後のキャリアについて相談してみました。 完全に僕の個人的な悩みのために時間取ってくれて本当にありがたく嬉しかったです...

彼のキャリアへの向き合い方と、今の僕に足りないものをまたまたズバッと言い当ててくれました。 本当にありがとう、とお伝えすると My Pleasure といつものように笑いかけてくれます。一生推せます。

そういえば英語ちょっと上達してた

仕事はほぼ英語なので、毎日のことだと自分の上達はなかなか認識できないのですが、英語セッションは苦なく理解できるし(日本語チャットと行き来するのは厳しいが)、 海外スピーカーの方に質問や議論するときもより深い話ができるようになってて、 そういえばこれたぶん去年より上達してるな、とふと思いました。

仕事ではまだまだうまくならないとなんですが、上達に目を向けるのもたまには大事かな、と。

OSTでたくさん話したTzeに「英語で話しかけてくれてありがとう、受け入れられたように感じたよ」と言われたのが印象的です。 Non Japanese Speaker の方々ともギャザれて、お互いに楽しめるような場を作れるといいなと思っていたので、その点は少し貢献できたかなと思います。

日本人はやっぱりシャイなのか"I Speak English"タグ付ける人本当に少ないんですけど、 程度に差はあれどみんな英語できるんですよね。僕はシャイは乗り越えたので毎年つけます。もっと交流するときっとお互いもっと楽しめると思います!

とはいえ言語の壁の厚さも痛感しているし痛いほど理解できるんですけどね...。


今年のテーマは自分の内省を含めた対話になりそうです。

40代になって、自分が本当にやりたいことを深く内省することろから始めようかと。 そしてもっと外の世界と繋がる機会を増やそう、と。おがさわらさんとWoodyの助言でもあります。

ことしもやっていきです。

GPTs are GPTs - を読んだので簡単にまとめ

GPTs are GPTs: An Early Look at the Labor Market Impact Potential of Large Language Models

を読んだ。まずこのタイトルの意味だが、ChatGPTの Generative Pre-trained Transformer が、General-Purpose Technologies つまり汎用的に利用できる技術、と掛かっている。はじめからある程度意識されていたのかな...

TL; DR

  • ChatGPT及び周辺アプリケーション開発により、80%の労働者が10%のタスクに影響を受け、19%は半分のタスクに影響を受ける
  • 高所得の仕事はより大きく影響を受ける可能性がある

Discraimer: 特に専門家でもないので、正確な内容は論文をあたってくださいませ

アブスト

アブストから

USでは、GPTs(ここではGPT-4)の導入により、少なくとも10%のタスクに影響を受ける労働者が80%に上り、19%は最低でも50%のタスクが影響を受ける。
影響はすべての賃金レベルに及び、高所得の仕事はより大きな影響を受ける可能性がある。
-- Translated by GPT-4

なかなか衝撃的な内容だった。

ざっと理解した内容をメモしておく。

前提:影響の定義、Exposure "露出"

まず3章で、"Exposure" の定義を理解しておく必要がある。

we define exposure as a measure of whether access to a GPT or GPT-powered system would reduce the time required for a human to perform a specific DWA or complete a task by at least 50 percent.
"Exposure" をGPTまたはGPT搭載システムへのアクセスが、特定のDWA(Detailed Work Activity、詳細な作業活動)やタスクを実行するために人間が必要とする時間を少なくとも50%削減するかどうかの尺度として定義しています。
-- Translated by GPT-4

GPTsによりタスクにかかる時間が半分になる場合、これを Exposure (とりあえず露出と訳す)されている、と定義する。

さらにE0~E2まで、露出のレベルを定義する。(LLMはもちろんLerge-Language Modelの略)

  • No exposure/露出なし (E0)
    • タスクを完了するのに必要な時間がほとんど短縮されないか、まったく短縮されない
  • Direct exposure/直接的な露出 (E1)
    • GPT-4を単独で使用することで、タスクを完了するのに必要な時間を少なくとも半分(50%)短縮
  • LLM+ Exposed (E2)
    • LLMに追加のソフトウェアを開発することで、タスクに必要な時間を少なくとも半分短縮

これら E0 ~ E2 を用いて、以下の3つの尺度を定義する。

α -- E1
β -- E1 + 0.5 * E2 (補完的なツールやアプリケーションの開発に追加投資が必要)
ζ -- E1 + E2 (GPT + GPTパワードソフトウエアを用いた場合の最大露出上限)

α が ChatGPTのみを利用した場合、ζ が周辺サービスを利用したと仮定する最大影響上限くらいで思っとけばおkと思う。これらの尺度を人間によるアノテーションと、機械学習モデルの2つで評価する。

これらを頭に入れておいて、4章のResultsをざっと見ていく。

4章 Result

4.2 Wages and Employment / 賃金と雇用

fig 3

左が職業ベース、右が労働人口ベースの露出割合。 右の図から、人間のアノテータは α50露出(ChatGPTに直接50%のタスクが時間半減)される労働人口は ~2.4%程度だが、49.6%の労働人口は𝜁50露出(ChatGPT+周辺アプリで50%のタスクが時間半減)される、と見積もっている。

このグラフの縦の範囲が潜在的なChatGPT周辺アプリ開発によるインパクトを示唆している、とまあそうなんだけど宣伝的な文句が入っていて印象的。

4.3 Skill Importance/スキルの重要性

Our findings indicate that the importance of science and critical thinking skills are strongly negatively associated with exposure, suggesting that occupations requiring these skills are less likely to be impacted by current language models. Conversely, programming and writing skills show a strong positive association with exposure, implying that occupations involving these skills are more susceptible to being influenced by language models
調査結果から、科学や批判的思考スキルの重要性は、露出と強い負の関連があることが示されており、これらのスキルが必要な職業は、現行の言語モデルによる影響を受けにくいことが示唆されます。逆に、プログラミングやライティングスキルは、露出と強い正の関連があり、これらのスキルを含む職業は、言語モデルの影響を受けやすいことが示されています
-- Translated by GPT-4

プログラミングには科学や批判的思考スキルが必要だと思うが... という点はおいておいて、単純にコードを書く、という行為自体がGPTsによってタスク完了時間が半減するというのは想像に難くない。というかすでに起こっているし、もっと生産性は上がるだろうと考える。

4.4 Barriers to Entry/エントリーの障壁

Bariiers to Entry とは、特定の職種につくために必要な経験や教育レベルのことのようだ。 "ジョブゾーン" という職業レベル分類があるらしく、概ね以下の定義。

In the ONET database, there are 5 Job Zones, with Job Zone 1 requiring the least amount of preparation (3 months) and Job Zone 5 requiring the most extensive amount of preparation, 4 or more years. We observe that median income increases monotonically across job zones as the level of preparation needed also increases, with the median worker in Job Zone 1 earning $30, 230 and the median worker in Job Zone 5 earning $80, 980.
ONETデータベースには、ジョブゾーン1からジョブゾーン5までの5つのジョブゾーンがあり、ジョブゾーン1では最も少ない準備(3ヶ月)が必要で、ジョブゾーン5では最も多くの準備(4年以上)が必要です。準備に必要なレベルが増加するにつれて、ジョブゾーンごとに中央値の収入が単調に増加し、ジョブゾーン1の中央値の労働者が30,230ドルを稼ぎ、ジョブゾーン5の中央値の労働者が80,980ドルを稼いでいることがわかります。
-- Transaleted by GPT-4

ジョブゾーンの数字が上がるほど就業しづらい業種で、年収も高いという感じ。 そしてジョブゾーンごとに先程の β露出をプロットした結果がこれ。

fig 5

exposure increases from Job Zone 1 to Job Zone 4, and either remains similar or decreases at Job Zone 5. Similar to Figure 3 in 5, we plot the percentage of workers at every threshold of exposure. We find that, on average, the percentage of workers in occupations with greater than 50% 𝛽 exposure in Job Zones 1 through 5 have 𝛽 at 0.00% (Job Zone 1), 6.11% (Job Zone 2), 10.57% (Job Zone 3), 34.5% (Job Zone 4), and 26.45% (Job Zone 5), respectively.
ジョブゾーン1からジョブゾーン4までの露出が増加し、ジョブゾーン5では同様か減少します。図5の図3に類似して、露出のすべてのしきい値で労働者の割合をプロットします。平均して、ジョブゾーン1から5までの職業で、50%以上の𝛽露出を持つ労働者の割合は、それぞれ0.00%(ジョブゾーン1)、6.11%(ジョブゾーン2)、10.57%(ジョブゾーン3)、34.5%(ジョブゾーン4)、および26.45%(ジョブゾーン5)であることがわかります。
-- Transaleted by GPT-4

知的労働の割合が大きいほど、GPTsによる影響を受けるくらいに思っておけばよいだろうか。(ジョブゾーン4-5は変わらないけど)まあそうだろうなという感想。

fig 6

各指標での最も露出が高い職業。Blockchain Engineerが何度も出てくるのは謎。なぜBlockchainに限定されるのか...。。 Mathmatician(数学者だよね?)は意外に感じる。。普通にProgrammerやSoftwareEngineerがランクインしても良さそうだけど。仕事全体に対して、コードを書いている時間は意外と相対的に少ないのでそのあたりが影響しているんだろうか。なぜBlockchainEngineer...(再)

感想

あくまで "Exposure" を見積もっていて、その定義は "タスク完了に必要な時間が半分になる" なので、これが例えば α50 の露出が50%なら半分の人が仕事を失う、というわけではない認識。

しかしシンプルに、半分のタスク量が半分の時間で終われば、全体にかかる時間は75%になるわけだから生産性はそれだけで33%UPなわけで、仕事の総量が変わらないとすると最悪ケースで33%の人が職を失うリスクがある?

楽天的に考えれば週休3日実現、みたいなこともあり得るかもしれないけど。

6章に経済の混乱に対する警鐘が少しだけ記載されている。

6.2 Implications for US Public Policy
The introduction of automation technologies, including LLMs, has previously been linked to heightened economic disparity and labor disruption, which may give rise to adverse downstream effects.
自動化技術の導入、LLMを含む、は以前から経済格差の拡大や労働の混乱と関連しており、これが悪影響を及ぼす可能性があります。

While it is outside the scope of this paper to recommend specific policy prescriptions to smooth the transition to an economy with increasingly widespread LLM adoption, prior work such as (Autor et al., 2022b) has articulated several important directions for US policy related to education, worker training, reforms to safety net programs, and more.
本稿の範囲を超えていますが、LLMの採用が広がる経済への移行をスムーズにするための具体的な政策提案をすることはできませんが、以前の研究(Autor et al., 2022b)では、教育、労働者研修、安全網プログラムの改革など、米国の政策に関連するいくつかの重要な方向性が明確にされています。
-- Transaleted by GPT-4

フム...

とりあえずざっと読んだだけだけど、定量的なサーベイに当たることができてよかった。これからどうなるかという議論のベースにできる情報の一部を得られたように感じる。
(GPT-4以降Twitterなどを眺めていて、根拠のない「これからどうなる」論に正直ちょっとうんざりしていたところもある)

ソフトウェアエンジニアとしての将来に不安がないといえば嘘になるが、今までと変わらず新しい技術と環境に適応し、変化を楽しんでいきたいと思う。

プログラマ35歳定年説と35歳の僕へ

note.com

これは、この素晴らしい記事のもうひとつのシナリオです。

プログラマ35歳定年説

日本だけなのかはわからないですが、この業界でまことしやかに囁かれるうさの一つに「プログラマ35歳定年説」というのがあります。 日本でIT業界に身を置く人間であれば、おそらく聞いたことがあるのではないでしょうか。

ぼくが駆け出しソフトウェアエンジニアとして働き出した20代、先輩や上司がよく言ってました。

体力がついていかなくなるとか、新しいことを吸収するスピードが遅くなるだとか、給料が上がらないだとか。
なんだかもっともらしい理由がついていて、実際僕も半信半疑ながら信じていました。

そしてなぜかあのころは「誰よりも早く出世してやる!」とかおもっていて、実際割と早めにマネジャー職になりました。

当時の会社のえらいひとに「もう新しいC言語覚えなくていいよ!」と言われたのをよく覚えています。

38歳になった。定年した?

はてさて気がつけば38歳。つい先日39歳になりました。日課のオンライン英会話にて:

"What do you do?"
"I'm... what is say, Software Engineer. I prefer call myself a Programmer tho."

ソフトウェアエンジニアなのかプログラマなのか...はおいておいて、今僕はいわゆるIC, Individual Contributor として働いています。

最近それなりにICという概念も浸透してきていると思いますが、日本語にはどうもぴったり来る表現がなく...
平社員、といえばそうなんですけど、ざっくりマネジャーではない職、という感じでしょうか。専門職といえばそれもまあそうなんですけど、じゃぁスペシャリストですか?というとまたちょっと違うんですよね。

とにかく38歳になった今も、現場で、最前線で、コードを書いています。定年はまだしばらく先になりそうです。あれ?マネジャーになって、出世したかったんじゃなかった?
ICと言っても、それなりにシニアになるとコードを書くだけが仕事ではなくて、チームや組織のあれやこれや、なんやかんやマネジメントに近い仕事もあったりはします。
まあでもこういうのも含めて、現場でコードを書いて、プロダクトを作って、顧客に届けるのが好きで、その魅力に取り憑かれてしまっているんだなと思います。広告はいいぞ。

...ということは、件の定年説というのはウソだったのでしょうか?
自分のN=1の実体験から、少なくとも反証はありますね。何だウソかー。
35歳の僕、アレ、うそだったよー。だいじょぶだよー。楽しくやれてるよー。

周りを見ても僕の同年代や年上で、現役バリバリでコードを書いているひとはたくさんいます。もちろん今の会社にも、外にも。

でもちょっと視野を広げてみるとやはり少数派なのかなぁ、とも思います。 みんなそれぞれコードだけじゃないスキルを身に着けて、少しずつ違うキャリアを見つけていっているんですね。
同じ年でCTO,VPoEなどエンジニアリング部門のトップを務める人もいます。みんなすごい。 どう考えて、どうそのスキルを磨いて、どうやってそのキャリアにたどりついたんだろう。

...あれ?やっぱりアレ、本当なのかな?Nどれくらい取った?ヒストグラム引いた?Thresholdは35でいいの?

鏡のようなあなたはどう考える?

アジャイルコミュニティの中でたくさんのすごい人たちに出会ったのですが、とびきりヤバくて印象深い人がいます。

いろんな偶然でめぐり合わせ、

めずらしい”いくお”という同じ名前で、

さらに同い年というではありませんか。

そして話してみるとなんだか不思議と気が合う。アジャイルコミュニティで出会うたびいちゃいちゃする仲になりました。

そんなもうひとりの"いくお"は今、VPoEとして組織を率い、Agileを推進し、世の中を前に進めている。

おなじころに同じように働き始めた同じ名前の2人が、一人はマネジャー、組織のキャリアパスを、一人はIC、現場のキャリアパスをそれぞれ歩んでいます。
きっとぜんぜん違う景色を見てるんだろうな。でもなぜか同じようなものを見ていて、同じ目線で話ができるような気もする。

そんないくおからお誘いをもらって、DevLOVE15周年という大変めでたいイベントで、 いままでの15年と、これからの15年について語らうという機会をもらいました。

note.com

はてさて、二人のいくおからどんな化学反応が生まれるのか。 15年というのはなかなか長い月日ですね、小学生が中学校卒業しますよ。

たぶん僕たちが一番楽しむと思うのですが、(これは僕が登壇するときのポリシーでもあります)
みなさんといっしょに今までの15年をふりかえり、そしてこれからの15年に思いを馳せる -- これまでとこれからの架け橋になるような話になればな、と思っています。

devlove.doorkeeper.jp

On/Offline問わず。"現場"でお会いしましょう!

We will meet again! - RSGT2022に参加しました!

今年もRSGTに参加してきました!

今年は初日はオンサイトで参加したのですが、様子を見て2日目以降はオンラインに切り替えて参加しました。 自分の状況に応じて柔軟に選択でき、ハイブリットカンファレンスの恩恵にフルにあずかりました!

We will meet again!

初日だけでもオンサイトで参加できて、懐かしい人達にたくさん会えて「ああみんなちゃんと存在してくれていてよかった」などと率直に思いました。 やっぱりオフラインはいいですね。

ああぼくは友達に会いにカンファレンスに行ってるんだなあ、と再認識しました。

「約束しなくても、あの場所に行けばみんなに会える」

これがすごく心地が良いのです。 いつかまた会えるその日... 次のRSGTまで、みんな元気で安全に!

Day1

というわけで、拝聴したセッションの感想などとともに今年の記録をしたためておこうと思います。

[Keynote] Agile Program Management: Scaling Collaboration Across the Organization

Johanna さんは個人的に大ファンでして、お話が聞けて大変嬉しかったです。

思い返せば3年前、初めての海外カンファンレンスAgile2018に参加したときのこと。 コーチズクリニックみたいなブースがありまして、勇気を出してサインアップしてみたのですが、そのときに対応してくれたのがJohannaさんでした。 図を交えながらたどたどしい英語で説明する僕の話を優しく頷きながら聞いてくれあたと、 僕の目を真っ直ぐ見て、

「Start Kanban.」

と一言。背筋がビリビリしました。このときの光景を今でも鮮明に覚えています。 人にはそれそれ何かにハマるきっかけがあると思うのですが、ぼくがアジャイルに傾倒したきっかけの1つは間違いなくこの瞬間でした。 というわけでファンなのです。

セッションはProductの上位概念、ProgramManagementについて。全然消化できてないですが、 1980とかにハードウェアの領域ですでにこんな開発方針が行われていたということにまず驚きました。

複数フィーチャーチームでのアーキテクチャの取り扱いについて質問してみました。 チームからそれぞれ人を排出して、アーキテクトコミッティーみたいなチームを構成するのが良いのでは、とのご回答を頂いて、 これもまた次のチャレンジかなと思います。

[Session]「いい感じのチーム」へのジャーニー

2年ぶりの生セッションはいつもお世話になっているようさんのセッション。

www.docswell.com

ようさんは昨年月イチで雑談(というかほぼ僕の相談)をしてくださって、転職したばかりで周りが見えなくなっていた昨年、大変助けていただきました。

「いい感じのチーム」とは。

僕にとってのいい感じのチームは「明日も仕事に行きたくなる」チームかなー、などと考えながら聞いていました。 挙げられていた特徴はそれぞれい感じのチームの必要条件だなと思える納得感のあるもの。

考え方中心で、コンテキストが固定されていなかったので、 今までの経験が思い起こされたり、今のチームの課題が浮き彫りになって大変共感できました。

話の進め方、会場の空気感も含めて、2年ぶりの生セッションの期待に応えて有り余る最高の体験でした!

[Session] Remote trust

なんと会場とイタリアをつなぐセッション。 これもハイブリッドカンファレンスならではの試みですね。

speakerdeck.com

けっこううちでも見るオンラインしぐさが実装されていたし、困りごとも共感できるものが多くてみんな同じ世界に生きてるんだなぁと思えるセッションでした。

コミュニケーションの質が劣るぶん信頼の構築には時間はかかるけど、頻度を上げることで信頼の醸成はできそうだなぁというのが最近のぼくの見解です。

"Build and gather trust, then focus on processes" は共感しかないです。

[Session] アジャイル開発のミライ

毎年楽しみにしているおよべさんのセッション。

speakerdeck.com

ウォーターフォールの30年後に生まれたAgile, そこから30年後にはどんな世界が待っているでしょう。

2点しかないので30年という周期性を持つかはわからないなー、なんてひねくれたことを考えながら、

JoshのModernAgileやAristerのHertOfAgile,DianaのAgile Fluency, WoodyのMobProgramming, Mobius, Scaled Agile, Spotify, etcetc...

Agileの傘の下で新しいアイディアはどんどん生まれていて、Agile自体も変化しているなーとも思います。

あらたさんもこちらの記事で言及されていたのですが、

dev.classmethod.jp

アジャイルの延長線上にあるものから破壊的な変化は生まれないんじゃないかな、と僕も思います。 アジャイル自体が漸近的でインクリメンタルな変化を内包しているようにも感じますし。 次の大きなムーブメントは、それが破壊的なものであるとしたらアジャイルコミュニティの外からやってくるのかもな、と。

セッションの中でも言及ありましたが、チーム開発に対するアンチテーゼはありそうかなと考えます。 チームという概念/境界自体がなくなって、プロダクトがあって、進行するプロジェクトがあって、 そこに自律的に関わる個人がネットワークを形成して、そのネットワークが会社を形作る...みたいな。

あるいは人間がソフトウエアを書くということ自体が否定されて... 機械がシステムを作るとか。 人間はたくさん間違えますし、そもそも人間が関わること自体がソフトウェア開発の複雑さの根源なんじゃないか的な。 その時人間は何をするんでしょうね、まぁそうなったら僕はおまんま食い上げなわけなんですが。

[ギャザる] たくさん話した

あとはホワイエでだべっていました。 本当になんのとりとめもない世間話で、近況の報告とかおひさしぶりですの挨拶とか。

でもこの時間もほんとうに尊かったと思います。これこれ、これなんだよ。

おがさわらさん、たざわさん、つねさん、TKさん、nolickさん、よしだ師匠、いきいきのいくお、色んな人と久しぶりに顔を合わせることができました。みなさんありがとうございます!

[Slide] 強くてニューゲームなプロダクト開発

生で見に行くことはできなかったのですが、スライドを読み返してめちゃめちゃ面白かったセッション。

speakerdeck.com

「くそ!何度タイムリープしても負債に勝てない...!」

もうわかり味が深くて、、、世界線を変えるくらいの変動が必要なわけですよね。

  • 「メンテナンスできなくなったときに負債になる」
  • 「システムのLTVを最大化する」
  • 「負債ではなく税金でマネジメント」

などそもそもメンテしづらいコードを入れ込まないための仕組みと、それでも起こった負債を定期的に取り除く仕組みが組織的に実装されており、大変参考になりました。

IidaさんとはDiscordでご挨拶できたので、その前に読んでおけばこれについておはなしできたのに!とちょっと後悔しました。 またお見かけしたらぜひこの話をしたいなーと思います。

Day2

ここからオンラインで参加しました。

手練たちがオンサイトに集まっていたこともあるのか、あるいはオンサイトの重力に引きずられてか、 正直なところ去年と比較するとオンライン側が活気がないなーと感じました。 僕自身もオンサイトにいたDay1はオンラインを見る余裕がまったくなかったですし。

もちろんオンラインも最大限楽しみましたけどね!

そんな中、ずっとボイスチャンネルに佇んでいてくれたえーちゃんさんの存在はもはや心の支えでした。 何度もお話させていただいて、オンラインでもギャザリング感があって最高でした!

[Keynote] Leading Skilled Agile Teams: Investing in Team Outcomes with the Agile Fluency® Model

Agile Fluency モデルはなにかの機会で触れたことはあったのですが、うろ覚えだったので新鮮な気持ちでセッションを楽しめました。

Fluencyとは意識しなくてもいいくらい自然にできること、Thinking fast and slow の(というかRindaさんの)System1,2の話を思い出しながら聞いていました。

[Session] Extreme Small Patterns -チームを100倍理解する方法-

毎年楽しみにしているきょんさんのセッション。

いつものようにぜんぜん理解が追いついてないですが、、 時間単位を変えて、より小さな粒度で自分たちの行動を捉えることで、よりチームを理解する...

以前モブプログラミングをしているチームを観察してパタンのようなものを抽出する試みをしたとき、 ”レベル分け”と各レベルでの核となるしぐさとしてまとめたのですが、 なるほどチームのしぐさには大きいものと小さいもの、包含関係と時間的変化があるのか、という気付きがありました。

ところで、チームが有機的であるというのはどういう意味なんでしょう? 外界からの刺激に対して、毎回アウトプットが異なる? あるいは(かつ?)それらが時間的に変化する?

一定認識できる繰り返し現れる行動があるわけですから、完全にランダムなわけではなくて確率的なモデルで記述できるのでは? 時間変化と合わせて

T = g(E, t) where T stands for Team, E stands for Environment

みたいな。時間で微分するとチームの向いてる方向がわかるかも?

セッション中にあった膨大なパタンの記録はチームのスナップショットなのかな? それとも時間的な変化も内在していて、コンテキストを形成して、その文脈の中でこそチームという存在の認知に近づける?構成的内在? (大怪我しそうなのでこのあたりで、、)

...などなど、RSGTが終わってしばらく、ぼんやり考えていました。

[Session] 挫折を通して見つけた、組織変革のスケールへの踏み出し方

nolickさんがんばれ!という気持ちで楽しみました!

Coach's Clinic

ことしはきろーさんに最近会社で起こっていることを相談してみました。 まるで見てきたかのように今起こってることを言い当てられて流石だなぁと...。。

色々ヒントを頂きました!

Day3

OST

OSTはオンサイトとオンラインが分断しがちで、そもそもオンラインだと議論に余計に時間がかかるしオンサイト行きたかったなーと思っていたのですが、 Zoom 部屋+集音マイク+iPad+Miro構成によってハイブリッド環境でのオンサイトとオンラインとの議論がかなりスムーズになり、コミュニティのインクリメントを感じました。

「リモート x スクラム ぶっちゃけどう?」というタイトルでここ2年のもやもやを議題にしてみたのですが、 僕含めオンラインの情報欠如つらいよね、という人もいれば、オンラインでも困ってない、むしろオンラインのほうがいいという方もいたり。

結論が出したかったわけではないし共感してほしかったわけでもなくて、ただ参加者の方々の声が聞きたいと思って立てたお題だったので、目的は達することができました。 結構人があつまって、付箋書いてくれたけど喋れなかった方とかもいらっしゃったと思うのでそこが申し訳なかったです。

むらみーさんの「オブザーバブルに保つ、オブザーバビリティを最大化する」という意見が個人的に腹落ちしていて、これからも意識していきたいなーと思える収穫でした。

[Keynote] アメリカの超巨大クラウドの中の人に転生したガチ三流プログラマが米国システム開発の現実をリークする話

最of高、すごいキーノート...キーノーティでした。 最高にアガる!と思いきや、悔しい!と感じたり、感情が大きく揺さぶられました。

去年のいつだか、ClubHouseで川口さんと牛尾さんがUSでのソフトウェア開発事情について話されていたのも聞いていたのですが、改めてすごいなと。。 本当に色々考えるところがあったので、別途ブログを書きたいと思っています。

人によってすごく捉え方が違うんじゃないかなと思います。。色んな人とこれについて話してみたいです。

バーチャルサイゼに集合な

その後Discordで去年同様バーチャルサイゼリヤを立てて、思う存分雑談を楽しみました。
(すいません サイゼリア のタイポチャンネルを立てたのは私です、愛が足りませんでした)
(よく思い出すと去年も同じ間違いをしたような...)
(その後nolickさんに指摘いただきました)

いきいきのほうのいくおと会社の元同僚を絆げたのも良かったです。

そういえばもうひとりのいくおのいきいきブランディングがすごすぎて、「いきいきじゃないほうのいくお」として生きていこうかと思ったのですがあんまりなので「無印いくお」になりました。今年も高度にハイコンテキストなイチャつきを展開してすいませんでした。

懐かしい人達にもご挨拶できたし、あとねもとさんにもやっとご挨拶できて何より。 夜中まで金の話とか評価の話とか札幌の話とかetcetcで楽しみました。

さとりゅーさんの「本開いた!えらい!1Pよんだ!えらい!」メソッドが僕にすごく相性良さそうだったのでやっていこうとおもいます。これ汎用性高いですね。


というわけで、ことしも1年分の元気をチャージしました! プロポーザルかけるかはわからないけど、2022年はコミュニティにもう少し顔を出していきたいなーと思います!

ぼくのRSGT2022を最高にするための3つのこと

この記事はRSGT2022に参加する僕のために書いています。

さっこんの情勢を踏まえ、今年のRSGTは初日オンサイトで参加することを決めました。 オミクロン株の流行が予断を許さない状況で、2, 3日目は様子を見てオンサイト・オンラインを決めようとおもっています。 オンラインでも参加してみると違いが際立って面白いかも。

TL; DR

  • パッション受信に全集中
  • 会いたいに人みんな会う
  • 偶然を楽しむ

martin-lover-se.hatenablog.com

を読み返したら今と考えていることがまったく同じ&全体重乗ってて笑いました

なんでやるの

振り返ると2021年はほとんどカンファレンスに参加せず、コミュニティからも少し遠ざかっていました。 転職で環境や役割が変わったとか、単純に仕事と家庭が忙しくて精一杯だったとか色々言い訳はあるんですが、 やっぱりオンラインはどうも肌にあわない、という感覚がどこか心につっかえていて、「あーーー...」と思ううちにいくつものカンファレンスが目の前を通り過ぎていきました。

(当然、オンラインで参加される方、またはカンファレンス自体、関連するいかなる個人・団体を非難したり貶める意図はまったくありません。)

今年は状況的にも多少マシですし、何より一年分の元気をもらいに行こうと思い立ちました。

なにをやるの

もはやオフラインで会えること自体がすごく希少な価値を持つ世の中になってしまいました。 そして少なからずリスクを取って行くことになるので、とにかくオンサイトでしかできないことを全力で楽しみたいと思います。 もちろん感染対策に細心の注意を払った上で、です。

パッション受信に全集中

2年ぶりの生セッションです。

以前に書いたように、ぼくはカンファレンスにエネルギーを貰いに行っています。 そしてスピーカーの方から感じるエネルギーはやっぱり大きいです。

なので生視聴のときはなるべくデジタル機器から離れ、ちょっと寂しいけど実況からも離れ、スピーカーの方の、そしてその"場"のエネルギーを感度全開で受信したいです。 そして能動的参加者として、「わかる」は全力でうなずいて、わからなかったら全力で首を傾げて、自分も会場の"場"の一部を形成することに集中します。

そしてセッションの最後には、手がもげるほどの拍手で全力で称賛を送ります。

今回は初日のキーノートが僕をAgile沼に引きずり込んだ一人、Johanna Rothmanさんでこちらも非常に楽しみです!

会いたい人みんなに会う

むかしどこかのカンファレンスで川口さんと話しているときに、だれかが「カンファレンスには友達に会いに来てるんだよ」といってたという話をしてくださって、それがすごくしっくり来たというか印象に残っています。 (その割には何という曖昧な記憶、、

みんながみんなオンサイトに来れるわけではもちろんないのですが、積極的に廊下に出て、あいたくてもリアルで会えなかった人たちみんなと言葉を交わしたいですね。 いつもの「いくおが話をしたそうにこちらを見ている」から一歩踏み出して、知ってる人みんなにあいさつしに行きます。どうか相手をしてやってください。

もちろん今年もオンラインに精神をアップロードするので、オンライン参加の方はオンラインで会いましょう! DiscordIDは suyama#7142 です!

偶然を楽しむ

なんかそういう不思議で素敵なことが起こる場じゃないですかRSGTって。

去年からの引用です。

見たいセッションは今年もたくさんあるけど、予定はあまりかっちり決めず、 積極的に廊下に出て(2回め)、楽しさ駆動でその場でおこったことを大切にしたいと思います。

去年フルオンライン参加だった身としては、オンラインからオンサイトに働きかけるのはなかなか難しかったので、 セッションのとき以外はオンラインにも積極的に顔を出して、オンライン - オンサイトでの素敵なことも期待したいですね。 脳とDiscordまだつながらないのかな。ミームを撒くしか。


こんな感じでしょうか。去年よりはずいぶん解像度が低いですが、 文章を書くのもなんだか久しぶりなのでそのせいか、オンサイトが思い出せない弊害か、はたまた消えてしまった熱量の影響か。 しかしブログ書いたら高まってきました!!

Anyway, 久しぶりに360度最高解像度の非圧縮情報を浴びることになるので脳が焼き切れるんじゃないかなどと思うわけですが、 みなさん RSGT でおあいしましょう!!!!

ソースからKotlinのcoroutineを完全に理解する

Disclaimer: タイトルは釣りです。読んでも理解できません。。

Server Side Kotlin はじめました。

f:id:martin_lover_se:20210919232224p:plain

Kotlinといえばcoroutineですね。公式ドキュメントがすごく良くできていて、ここを読めば使い方は大体理解できます。

なんですが使っているうちにだんだんこれどうなっとんねん、という気持ちが強くなってきたので、 コードを追い始めました。

思考の垂れ流しなので、ほとんど自分が後で見直すためのメモです。

環境は

  • org.jetbrains.kotlin:kotlin-stdlib:1.5.30
  • org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2
  • jdk correto-11.0.9

です。

coroutine を巡る旅へ

KotlinでいうCoroutineの実体って何?

ざっくりこのクラス図が頭に入っていると追いやすいです。

https://github.com/takahirom/kotlin-coroutines-class-diagram

だいたい、suspend functionの実行をContextとScopeで階層的に管理するやつ、位に思っとけば良いかなーという感想です。

coroutineの起点は CoroutineScope.launch/async ですが、この関数を開けてみると SnandaaloneCoroutineとDefferdCoroutineというやつを作っていることがわかります。

DefferdCoruotine(async)     --> AbstractCoroutine -> CoroutineScope
StandaloneCoroutine(launch) -┘

というような継承関係となっていて、CoroutineScope has a CoroutineContext です。 実行時の環境情報はCoroutineContextが保持しています。

なんですが、同じく

AbstractCoroutine -> Job -> Element -> CorutineContext

という継承関係もあって、Corutine自体がCoroutineConetxtでもある... という構造なので大変話しがややこしいです。

CoroutineScopeがCoroutineContextと結合可能だったりします。

public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineScope =
    ContextScope(coroutineContext + context)

これはおそらく根本的にはCoroutineはScopeで厳密に親子関係を管理したいという要求からきていて、おそらくkotlinのcoroutineにおける根本の設計思想の一つだと思うんですが、 この親子関係を表現するためのComposite-patternの変形のようなかんじなのかなーなどと思いつつ。

Deep Dive into kotlinx-coroutines-core

もう少し深掘っていきます。 launch は

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

となっていて、新しいContextを作ってCoroutineを起動するんですが、 CoroutineScopeでもありCoroutineContextでもあるStandaloneCoroutineをJobとして返すことでユーザーに開示するAPIを制限するんですね、 大変面白いです。

また、blockはsuspend functionとして定義されているのですが、このsuspend functionというやつが曲者であとで出てきます。

ここで余談ですが

ところで、

launch {
}

これは最後のパラメタが関数のとき、()の外にラムダを書けるというkotlinの機能によって成立していると思うんですが、suspendの記述がないですね。 自動的に変換されるのでしょうか? suspend lambda というやつらしいですね。

このlambdaを()の外に出せる機能は大変良いですね、死ぬほどコードの見通しが良くなります。 https://kotlinlang.org/docs/lambdas.html#passing-trailing-lambdas

あと, launch関数が拡張関数を用いてBuilders.common.ktにまとめられてるのも大変わかりやすいです。モジュール構造自体もたいへん勉強になりました。

本題に戻りましょう

更に遡ると、 CoroutineContextはkotlinx-coroutines-core-jvmではなくて、stdlib-common のほうに組み込まれていることに気が付きます。 suspend はkotlin予約語のビルドイン機能になっているので、 中断可能なデータ構造としての広義のcoroutineは言語ビルドインになっていて、それを階層的に扱うためのライブラリとしてkotlinx-coroutines-core-jvmが提供されている、という位置づけのようですね。

ではcoroutine.startが何をやっているかと言うと、 ここが引数の順番を変えてしまっていて少しトリッキーなんですが、

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    :
    coroutine.start(start, coroutine, block)
:
AbstractCoroutine.kt

    public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
        start(block, receiver, this)
    }

block は launch で指定された処理、 receiver は launchで作成したStandaloneCoroutine、 そして this もまた、実行したCoroutineなのでStandaloneCoroutineです。

CoroutineStartはEnumなんですが、それをそのまま実行していて面食らいました。 これは kotlin の機能で、Enumに関数を実装できるうえに operator invoke で自身を実行できるようにしています。

    @InternalCoroutinesApi
    public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
        when (this) {
            DEFAULT -> block.startCoroutineCancellable(receiver, completion)
            ATOMIC -> block.startCoroutine(receiver, completion)
            UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
            LAZY -> Unit // will start lazily
        }

こうして指定されたblockを、生成したCoroutineをreceiverとして起動していることがわかります。また、completionもおなじCoroutineです。 ここで見慣れない Continuation というやつが出てくるのですが、これはAbstractCoroutineが継承しています。

public abstract class AbstractCoroutine<in T>(
    parentContext: CoroutineContext,
    initParentJob: Boolean,
    active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {

Continuationの定義はこんな感じで、

public interface Continuation<in T> {
    /**
     * The context of the coroutine that corresponds to this continuation.
     */
    public val context: CoroutineContext

    /**
     * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
     * return value of the last suspension point.
     */
    public fun resumeWith(result: Result<T>)
}

contextとresumeWithをインターフェースとして持ちます。こいつは stdlib の方で定義されていますね。 CoroutineScopeも has a CoroutineContext なのでまた紛らわしいですが、このContinuationがここから先のキモになっています。

block.startCoroutineCancellable は, Cancellable.ktでsuspend funに生やしていて

Cancellable.kt

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
    receiver: R, completion: Continuation<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
) =
    runSafely(completion) {
        createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
    }

runSafelyはtry-catchでFailureをdispachするだけのものなので、実体はcreateCoroutineUninterceptedということになります。 で、このcreateCoroutineUniterceptedというのは、stdlibに定義された suspend fun の関数で

@kotlin.SinceKotlin public fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(receiver: R, completion: kotlin.coroutines.Continuation<T>): kotlin.coroutines.Continuation<kotlin.Unit> { /* compiled code */ }

とこうなっているんですが、IntrinsicsKt.class ... classファイルなんですよねぇ。

さらに深淵へ

ちょっと行き詰まったので、ドキュメントを調べていきます。 https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md

Continuation passing style

Every suspending function and suspending lambda has an additional Continuation parameter that is implicitly passed to it when it is invoked.

hmhm, これが completion: Continuation で、まあ要するにCallbackですね。CallbackHellからasync/awaitスタイルの書き下しに変換する仕組みはこのあたりにありそうです。 ここまでのコードリーディングと合わせて、だいたいsuspend fun が状態を保持したまま処理をsuspend/resumeできる機構を備えていて、CoroutineScopeはそれをScope/階層化して管理しながら実行するもの、くらいのことがわかりました。

ググっているとデコンパイルして内容を確認してみよう、みたいな記事がいくつか出てくるので、やってみます。 今回はJava Decompiler を使いました。なんかjava11で起動するとエラーになりますね。java8で起動します。

さっき躓いたIntrinsicsKtを覗いてみます。

IntrinsicsKt extends IntrinsicsKt__IntrinsicsKt extends IntrinsicsKt__IntrinsicsJvmKt で、ほとんどの処理がこのJvmKtに書いてあります。

createCoroutineUninterceptedはこんな雰囲気で、

@SinceKotlin(version = "1.3")
  @NotNull
  public static final <R, T> Continuation<Unit> createCoroutineUnintercepted(@NotNull Function2 $this$createCoroutineUnintercepted, Object receiver, @NotNull Continuation completion) {
    :
    Continuation probeCompletion = DebugProbesKt.probeCoroutineCreated(completion);
    int $i$f$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt = 0;
    CoroutineContext context$iv = probeCompletion.getContext();
    :
    return ($this$createCoroutineUnintercepted instanceof BaseContinuationImpl) ?
    :
      (Continuation<Unit>)new IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$4(probeCompletion, context$iv, probeCompletion, context$iv, $this$createCoroutineUnintercepted, receiver));
  }

パラメータチェックのあと IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt というクソ長いクラスのインスタンスを作っていることがわかります。 この createCoroutineUnintercepted を呼び出す suspend fun, つまりもともとの block は、 $this$createCoroutineUnintercepted となっていて、ここでは単なるFunction2として渡されています。

このクソ長いクラスは ContinuationImpl を実装していて、ここで $super_call_param$3 - $this$createCoroutineUnintercepted - つまりもともとの block が ContinuationImpl の completion として新たな Continuation のインスタンスが生成されることがわかります。

  public static final class IntrinsicsKt__IntrinsicsJvmKt$createCoroutineFromSuspendFunction$2 extends ContinuationImpl {
    private int label;
    
    public IntrinsicsKt__IntrinsicsJvmKt$createCoroutineFromSuspendFunction$2(Function1 $captured_local_variable$0, Continuation $captured_local_variable$1, CoroutineContext $captured_local_variable$2, Continuation $super_call_param$3, CoroutineContext $super_call_param$4) {
      super($super_call_param$3, $super_call_param$4);
    }
  }

これでやっと suspend fun から Continuation が生成できました。

再びkotlinの世界へ

startCoroutineCancellableに戻ります。

createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)

こうでした。 intercepted() は状態を見て自分を返すので、結局 resumeCancellabeWith が呼ばれます。 この関数はDispatchedContinuationでContinuationに生えていて

DispatchedContinuation.kt

public fun <T> Continuation<T>.resumeCancellableWith(
    result: Result<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
    is DispatchedContinuation -> resumeCancellableWith(result, onCancellation)
    else -> resumeWith(result)
}

resumeWithを呼びますが、いまContinuationはContinuationImplで ContinuationImpl -> BaseContinuationImpl ですから、ここのresumeWithが呼ばれて

    public final override fun resumeWith(result: Result<Any?>) {
        // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
        var current = this
        var param = result
        while (true) {
            // Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
            // can precisely track what part of suspended callstack was already resumed
            probeCoroutineResumed(current)
            with(current) {
                val completion = completion!! // fail fast when trying to resume continuation without completion
                val outcome: Result<Any?> =
                    try {
                        val outcome = invokeSuspend(param)
                        :
            }
        }
    }

と、invokeSuspend() を呼びます。

BaseContinuationImpl.kt
    protected abstract fun invokeSuspend(result: Result<Any?>): Any?

abstract 関数になってまして、じゃあこれ誰が実装するのというと、さっきのドキュメントのこのあたりの説明を読むとsuspend fun あるいは suspend lambdaを実装するとコンパイラが対応するinvokeSuspendを実装した匿名クラスを吐くらしいんですね。

ふたたび深淵へ

せっかくなのでかんたんなテスト用クラスを作ってこれも確認してみます。

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

suspend fun say() {
    delay(100)
    print("World!")
}

fun main() {
    runBlocking {
        launch {
            print("Hello, ")
            say()
        }
    }
}

このコードはこんなクラスファイルを吐きます。

SuspendKt$main$1$1.class
SuspendKt$main$1.class
SuspendKt$say$1.class
SuspendKt.class

main$1が launch に渡した suspend lambda, say$1 が suspend fun say() ですかね。

  static final class SuspendKt$say$1 extends ContinuationImpl {
    int label;
    
    SuspendKt$say$1(Continuation $completion) {
      super($completion);
    }
    
    @Nullable
    public final Object invokeSuspend(@NotNull Object $result) {
      this.result = $result;
      this.label |= Integer.MIN_VALUE;
      return SuspendKt.say((Continuation<? super Unit>)this);
    }
  }
  static final class SuspendKt$main$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Job>, Object> {
    int label;
    
    SuspendKt$main$1(Continuation $completion) {
      super(2, $completion);
    }

    @Nullable
    public final Object invokeSuspend(@NotNull Object $result) {
      CoroutineScope $this$runBlocking;
      Object object = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch (this.label) {
        case 0:
          ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);
          $this$runBlocking = (CoroutineScope)this.L$0;
          return BuildersKt.launch$default($this$runBlocking, null, null, new Function2<CoroutineScope, Continuation<? super Unit>, Object>(null) {
                int label;
                
                @Nullable
                public final Object invokeSuspend(@NotNull Object $result) {
                  String str;
                  boolean bool;
                  Object object = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                  switch (this.label) {
                    case 0:
                      ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);
                      str = "Hello, ";
                      bool = false;
                      System.out.print(str);
                      this.label = 1;
:

こんな感じで、invokeSuspendが実装されたContinuationImplを吐いてくれたことがわかりました。 ※ SuspendLambda extends ContinuationImpl です。

また、invokeSuspendがstateMachineとして実装されている様子も垣間見えます。 当面、どのように実行されるかがわかって満足したのでこのあたりで。

Appendix

"コンテナファースト" Java フレームワーク Quarks を試してみた

Quarksとは

https://ja.quarkus.io/

今更ぼくが紹介するまでもないと思うんですが、Redhat さん主導で開発されている新気鋭の Java フレームワークで、コンテナ環境での実行をメインターゲットにしている点が特徴ですかね。 会社で話題になっていたので試してみました。

ちょっとにわかには信じがたい性能のふれこみですね!

https://ja.quarkus.io/assets/images/quarkus_metrics_graphic_bootmem_wide.png

TL;DR

  • Native 版実行コンテナを作って起動するまで
# 雛形作成
❯ mvn io.quarkus:quarkus-maven-plugin:1.12.2.Final:create \
      -DprojectGroupId=org.acme \
      -DprojectArtifactId=getting-started \
      -DclassName="org.acme.getting.started.GreetingResource" \
      -Dpath="/hello"cd getting-started
# install Extension
❯ ./mvnw quarkus:add-extension -Dextensions="container-image-docker"
# build
❯ ./mvnw package -Pnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true
# go!
❯ docker run -i --rm -p 8080:8080 ikuo.suyama/getting-started:1.0.0-SNAPSHOT
  • エコシステム含め開発体験◎、実戦投入に向けてより詳細に使い込んでみたい

開発

とりあえずチュートリアルをやってみます。ドキュメントは多くがすでに日本語化されていて気合を感じますね。 ちょっと分かりづらい気もする、、とくに初学者にはつらそうです(けどおそらくそこはターゲットではなさそう

ja.quarkus.io

セットアップ

❯ mvn io.quarkus:quarkus-maven-plugin:1.12.2.Final:create \
      -DprojectGroupId=org.acme \
      -DprojectArtifactId=getting-started \
      -DclassName="org.acme.getting.started.GreetingResource" \
      -Dpath="/hello"

mvn コマンド一発で雛形と Hello world, test 一式が生成されます。 .gitignore も含まれていて、IntelliJ の設定ファイルもちゃんとかいてある!

ああちゃんと使う人のことが考えられているなぁと感じます。 細かいけどこういう配慮があるプロダクトは信頼できますよね。

起動

開発モードで起動

cd getting-started
❯ ./mvnw compile quarkus:dev
:
istening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-03-13 22:48:41,751 INFO  [io.quarkus] (Quarkus Main Thread) getting-started 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.12.2.Final) started in 1.098s. Listening on: http://localhost:8080
2021-03-13 22:48:41,754 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-03-13 22:48:41,754 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy]

curlしてみます。いいですね!

❯ curl localhost:8080/hello
Hello RESTEasy⏎

もちろんホットリロード対応で、変更を加えるとアプリケーションが高速にリロードされます。

コードはこんなノリです

@Path("/hello")
public class GreetingResource {

    @Inject
    GreetingService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello RESTEasy";
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/greeting/{name}")
    public String greeting(@PathParam("name") String name) {
        return service.greeting(name);
    }
}

サンプルはCRUDEすら無いシンプル(すぎる)アプリケーションだけど、 まんま JAX-RS だし Java で Backend やってる人はだいたいふんいきでわかるんじゃないでしょうか。

Native Build

目玉のネイティブ実行ファイルを作成してみます。

ja.quarkus.io

ここをなぞるだけです。 ビルド時に、native プロパティを指定すると GraalVM なネイティブ実行ファイルが作成できます。簡単。

❯ ./mvnw package -Pnative

めんどうくさそうなところは飛ばして、とりあえず実行すると当然死にます。

[ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:1.12.2.Final:build (default) on project getting-started: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[ERROR]     [error]: Build step io.quarkus.deployment.pkg.steps.NativeImageBuildStep#build threw an exception: java.lang.RuntimeException: Cannot find the `native-image` in the GRAALVM_HOME, JAVA_HOME and System PATH. Install it using `gu install native-image`
[ERROR]     at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.getNativeImageBuildRunner(NativeImageBuildStep.java:306)
[ERROR]     at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.build(NativeImageBuildStep.java:102)
[ERROR]     at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR]     at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[ERROR]     at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR]     at java.base/java.lang.reflect.Method.invoke(Method.java:566)
[ERROR]     at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:920)
[ERROR]     at io.quarkus.builder.BuildContext.run(BuildContext.java:277)
[ERROR]     at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2415)
[ERROR]     at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
[ERROR]     at java.base/java.lang.Thread.run(Thread.java:829)
[ERROR]     at org.jboss.threads.JBossThread.run(JBossThread.java:501)
[ERROR] -> [Help 1]
:

ふむ。GraalVM をインストールしてないからですね。それはそうだ。ドキュメント読みましょう。でもインストールするのは面倒くさいです。

どうやら GraalVM をインストールしなくても、ビルド用イメージを引っ張ってきてコンテナ内でビルドしてくれるオプションがあるみたいです。すばらしい。 もちろんMacOS用の実行ファイルにはならないですけど。

❯ ./mvnw package -Pnative -Dquarkus.native.container-build=true

今度は違うエラーで死。OOMだそう。

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  31.431 s
[INFO] Finished at: 2021-03-13T23:19:52+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:1.12.2.Final:build (default) on project getting-started: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[ERROR]     [error]: Build step io.quarkus.deployment.pkg.steps.NativeImageBuildStep#build threw an exception: java.lang.RuntimeException: Failed to build native image
[ERROR]     at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.build(NativeImageBuildStep.java:282)
[ERROR]     at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR]     at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[ERROR]     at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR]     at java.base/java.lang.reflect.Method.invoke(Method.java:566)
[ERROR]     at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:920)
[ERROR]     at io.quarkus.builder.BuildContext.run(BuildContext.java:277)
[ERROR]     at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2415)
[ERROR]     at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
[ERROR]     at java.base/java.lang.Thread.run(Thread.java:829)
[ERROR]     at org.jboss.threads.JBossThread.run(JBossThread.java:501)
[ERROR] Caused by: java.lang.RuntimeException: Image generation failed. Exit code was 137 which indicates an out of memory error. Consider increasing the Xmx value for native image generation by setting the "quarkus.native.native-image-xmx" property
[ERROR]     at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.imageGenerationFailed(NativeImageBuildStep.java:439)
[ERROR]     at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.build(NativeImageBuildStep.java:254)
[ERROR]     ... 10 more
[ERROR] -> [Help 1]
:

quarkus.native.native-image-xmx を指定するといいよ、とあるけど、追加しても同様。 いま Docker コンテナ内でビルドしてるはずなので、 Docker 側の設定の問題じゃないかと踏んで確認してみたら、案の定何故か Docker のメモリ制限が 2G とかになっていました。 アップグレードされた際にリセットされてしまったのかな。とにかく適当に大きめに設定します。

:
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dsun.nio.ch.maxUpdateArraySize=100 -J-Dvertx.logger-delegate-factory-class-name=io.quarkus.vertx.core.runtime.VertxLogDelegateFactory -J-Dvertx.disableDnsResolver=true -J-Dio.netty.leakDetection.level=DISABLED -J-Dio.netty.allocator.maxOrder=1 -J-Duser.language=ja -J-Duser.country=JP -J-Dfile.encoding=UTF-8 --initialize-at-build-time= -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy\$BySpaceAndTime -H:+JNI -H:+AllowFoldMethods -jar getting-started-1.0.0-SNAPSHOT-runner.jar -H:FallbackThreshold=0 -H:+ReportExceptionStackTraces -H:-AddAllCharsets -H:EnableURLProtocols=http --no-server -H:-UseServiceLoaderFeature -H:+StackTrace getting-started-1.0.0-SNAPSHOT-runner
[getting-started-1.0.0-SNAPSHOT-runner:26]    classlist:   4,692.18 ms,  0.96 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]        (cap):     538.10 ms,  0.96 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]        setup:   2,192.05 ms,  0.96 GB
14:22:21,626 INFO  [org.jbo.threads] JBoss Threads version 3.2.0.Final
[getting-started-1.0.0-SNAPSHOT-runner:26]     (clinit):     528.50 ms,  1.95 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]   (typeflow):   9,915.57 ms,  1.95 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]    (objects):   9,587.44 ms,  1.95 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]   (features):     519.44 ms,  1.95 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]     analysis:  21,527.90 ms,  1.95 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]     universe:   1,069.14 ms,  2.67 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]      (parse):   2,067.55 ms,  2.67 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]     (inline):   3,448.59 ms,  3.37 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]    (compile):  13,496.82 ms,  4.52 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]      compile:  20,469.25 ms,  4.52 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]        image:   2,609.89 ms,  4.52 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]        write:   1,690.58 ms,  4.52 GB
[getting-started-1.0.0-SNAPSHOT-runner:26]      [total]:  54,532.52 ms,  4.52 GB
[WARNING] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] objcopy executable not found in PATH. Debug symbols will not be separated from executable.
[WARNING] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] That will result in a larger native image with debug symbols embedded in it.
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 60944ms
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:07 min
[INFO] Finished at: 2021-03-13T23:23:01+09:00

はいはい、今度は成功しました。メモリは5Gくらい食ったみたいですね。

いつもどおり target/ 下に実行ファイルができています。

❯ ll target
total 57632
drwxr-xr-x  14 ikuo.suyama  staff   448B  3 13 23:38 ./
drwxr-xr-x  14 ikuo.suyama  staff   448B  3 13 23:42 ../
-rwxr-xr-x   1 ikuo.suyama  staff    28M  3 13 23:23 getting-started-1.0.0-SNAPSHOT-runner*

... 28MB??

いやいやご冗談でしょう。。Javaですよ?いくら Hello World だけとはいえ。 linux 用にビルドしたものなので当然Macでは動きませんけど。。

Image作成

せっかくなので Docker Image も作成してみましょう。 package に -Dquarkus.container-image.build=true オプションが用意されていてスムーズです。 Document が少し分かりづらいけど、今は3通り Image ビルドする方法が用意されています。

ja.quarkus.io

どれかのエクステンションは追加しないと Image ビルドできないみたいですね。 こんなWARNが出ていることに気づかず、Image が生成されないなーとしばらく悩みました。

2021-03-13 23:33:07,289 WARN  [io.qua.config] (main) Unrecognized configuration key "quarkus.container-image.build" was provided; it will be ignored; verify that the dependency extension for this configuration is set or you did not make a typo

とりあえずDockerでビルドすることにして、quarkus-container-image-docker エクステンションを追加します。

❯ ./mvnw quarkus:add-extension -Dextensions="container-image-docker"

Native 版

ビルドするコマンドは以下。

❯ ./mvnw package -Pnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true

できました!

❯ docker images | grep getting-started
ikuo.suyama/getting-started                                                              1.0.0-SNAPSHOT        4e99006a3194   25 seconds ago   132MB

132MB! いやー小さいですね。Golang とかに比べたらまだ大きいのかも知れませんけど... ちなみに BaseImage には Redhat さんの ubi ってやつが使われてるみたいです。38MBくらい。

起動してみましょう。
※ グループ名は自分のものに置き換えてください。ビルド時のオプションで指定もできます。

❯ docker run -i --rm -p 8080:8080 ikuo.suyama/getting-started:1.0.0-SNAPSHOT
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-03-13 15:20:51,266 INFO  [io.quarkus] (main) getting-started 1.0.0-SNAPSHOT native (powered by Quarkus 1.12.2.Final) started in 0.017s. Listening on: http://0.0.0.0:8080
2021-03-13 15:20:51,266 INFO  [io.quarkus] (main) Profile prod activated.
2021-03-13 15:20:51,266 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]

早つ。1s かからない。いいかんじですね!

jar 版との比較

せっかくなので jar 版も作って、サイズなど比較してみましょう。 -Pnative せずにビルドします。GraalVMがいらないので -Dquarkus.native.container-build=true も不要です。

区別するために Image の名前を適当に変えておきます。こういうオプションがちゃんと用意されているところにも好感が持てます。

❯ ./mvnw package -Dquarkus.container-image.name=quarks-getting-started-jar
❯ d images | grep getting-started
ikuo.suyama/getting-started                                                              1.0.0-SNAPSHOT        4e99006a3194   9 minutes ago    132MB
ikuo.suyama/quarks-getting-started-jar                                                   1.0.0-SNAPSHOT        c0281da22b67   46 minutes ago   380MB

やはりjarだとサイズがおおきいです。約2.8倍!いや、ネイティブ版が小さすぎるんですけど。 起動してみます。

❯ docker run -i --rm -p 8080:8080 ikuo.suyama/quarks-getting-started-jar:1.0.0-SNAPSHOT
exec java -Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -XX:+ExitOnOutOfMemoryError -cp . -jar /deployments/quarkus-run.jar
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-03-13 15:27:38,805 INFO  [io.quarkus] (main) getting-started 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.12.2.Final) started in 0.673s. Listening on: http://0.0.0.0:8080
2021-03-13 15:27:38,830 INFO  [io.quarkus] (main) Profile prod activated.
2021-03-13 15:27:38,830 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]
^C2021-03-13 15:27:45,104 INFO  [io.quarkus] (Shutdown thread) getting-started stopped in 0.076s

java経由で起動するのでネイティブ版よりワンテンポ遅く感じます。

まとめ

ネイティブ実行ファイルの作成、コンテナイメージの作成まで非常にスムーズに実施できました。 おそらく本格的なアプリケーションを開発し始めるとそれなりに詰まるポイントがあるんでしょうけど、それでもかなり可能性を感じますね。
特にやはりGraalVMによるネイティブ実行ファイルのイメージサイズの小ささや実行速度はかなり魅力的に感じます。つぎはk8sとも組み合わせてみようかな。
実戦投入に向けて、よりディープに使い込んでみたいなと思えるプロダクトでした!