ikuo’s blog

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

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とも組み合わせてみようかな。
実戦投入に向けて、よりディープに使い込んでみたいなと思えるプロダクトでした!

サイバーエージェントを退職して、スマートニュースにJoinしました

f:id:martin_lover_se:20210215223225j:plain

あっという間に過ぎ去りましたが、11月にサイバーエージェントを退職し、スマートニュースにJoinしまして、3ヶ月が経ちました。 試用期間も終わってどうにかこうにかやっておりますので、所謂退職エントリというやつを書いてみようとおもいます。

TL; DR

  • サイバーエージェントでなにしてたの
    • 広告プロダクトの開発
      • Tracking, DMP, AdNetwork, DSP
    • いろんなロール
      • バックエンドエンジニアとか
      • エンジニアリングマネジャーとか
      • プロダクトオーナーとか
      • スクラムマスターとか
    • ゼミ
  • なんでやめるの
    • 大好きだったチームが解散してしまった
    • のもあるけど、天啓だったような気もしている
  • なんでスマートニュースなの
    • 「ピンときた」から
    • 迷ったので楽しそうな方を選んだ
  • スマートニュースでなにするの
    • 広告プロダクトの開発
      • 広告おもしろいよ広告
    • エンジニアリングに軸足を置く

誰ですか

すやまといいます。 バックエンドをベースに、ソフトウェアエンジニアをかれこれ15年ほどやっております。 アジャイルとかスクラムとかモブプログラミングとかが好きです。

サイバーエージェントに入社して、インターネット広告が大好きになりました。

サイバーエージェントでなにしてたの

あ、はじめに断っておきますが、サイバーエージェントは素晴らしい会社ですよ。嫌になってやめたとかでは決してないです。

間違いなく国内最高クラスのテックカンパニーであり、インターネット広告では言わずもがな先頭をひた走る存在です。 やりがいのある面白い仕事が山程ありますし、優秀でやる気のあるエンジニア/ビジネスの人達に囲まれて仕事ができます。 そんなにたくさんの環境で働いたわけではないですが、おそらくエンジニアにとっては国内トップクラスの環境と言えると思います。

やめたけど今でもサイバーが大好きですし、機会があればまた働きたいですね。

サイバーで働いた中で一番印象的なのはやっぱり育児休業をとったことですかね。

martin-lover-se.hatenablog.com

にも書いたように、2度の育児休業を取得しましたがみんな気持ちよく送り出してくれましたし、 当然のように復帰して変わらず重要な仕事をまかせてもらっていました。 この点だけでもこの会社の文化のすばらしさがわかるかと思います。

5年半の仕事を通じてエンジニアとして大きく成長出来たと思っています。本当に感謝しかありません。

すこし何をやっていたかを思い出しながら綴ってみます。

広告計測プロダクト / EM

初めて配属されたのは広告効果を計測するプロダクトでした。 すごいシンプルに言うと、広告を配信してクリックされた数とかコンバージョンが出た数を数えるためのプラットフォームです。

どんなキラキラした開発が待っているのかとおもっていたら、 SubversionとJava6 + Servlet(SpringかSeasarだったかも?) + Tomcatみたいなかんじで 「お、おう」と同時に「ハイ!いけます!!」みたいな気持ちだったのをよく覚えています。

体制変更やらで組織が色々ゴタゴタしている時期でもあって、入社して半年もしないうちにエンジニアリングマネジャーをやることになってしまい、 マネジャー向いてないと感じてSIerやめたのにまたマネジャーをやるという。コードも書きながらみたいなプレイングマネジャーのスタイルでしたけど。

この後しばらくエンジニアリングマネジャーをやるんですが、この頃の僕は典型的なマイクロマネジメントスタイルで、 当然チームも全然うまく行かなかったし僕も疲弊していたように思います。

技術的にもいろいろチャレンジさせてもらいました。 このころ組織全体でScala押しで、僕もScalaを始めました。今でもScalaは結構好きな言語です。

ほどなくして広告計測プロダクトを新しく作ろうというプロジェクトが立ち上がって、 そこでゼロからアーキテクチャの設計をさせてもらう機会にありつきました。 Scalaはもちろんakka-streamingやsparkなど当時モダンと思われたものを片っ端から詰め込んで、挑戦的で楽しかったです。

あと300台くらいあるMySQLを含めたシステム全体のデータセンター移行とかもやりましたね。もちろん無停止で...。 これはすごい大変でしたけど楽しかったですし、良い経験になりました。

DMPプロダクト/ EM, PO

その後の体制変更などの流れで、DMPのプロダクトのEMをやるようになりました。 すごいシンプルに言うと、広告を配信するためのユーザー属性とかのデータベースみたいなものですね。

ここではプロダクトオーナーの役割を経験させてもらいました。

データサイエンティストのマネごとのようなデータ分析をしたり、競合の調査をしたり、 スクラムを本格的にやっていたのでプロダクトバックログを作ったり優先順位を考えたり、とかそんな仕事をしてました。

このときは師匠がスクラムマスターをやってくれていたのですが、師匠の助けもあってうまく回っていたと思っていて、 うまくいっているスクラムチームを経験できたのもすごく良かったです。

POをやった感想としては、ビジネスをどうこうする能力は自分には皆無だなーと。 POとして必要と思われる素質で、おそらくぼくにあったのはプロダクトへの愛と情熱で、後はからっきしという感じでした。

まあうまく出来ないことも、何が大変なのかもやってみないとわからないところもありますし、経験という意味ではプラスだったかなと思います。 POは今でも全くうまくできる気がしないですね...。多分もうすすんでやることはないと思います。

動画広告配信プロダクト / TL, SM

子供が生まれるタイミングで異動がありまして、配信系のプロダクトに配属になりました。 AdNetworkといってメディアにタグを張ってもらって広告を返したりとか、DSPといってオークションで広告枠を買い付けたりとか、まあなんやかんやで広告を配信するプロダクトです。

広告配信するのってめっちゃ楽しいんですよー。何が楽しいのかと言われると長い話になるんですが。 まあぼくは配信サーバーのログ眺めてるだけでご飯何倍でもイケる類の人なので... ウフフ...ワシのサーバーが広告配信しとるじゃろ...ウフフフ...

結果的にこのチームに一番長く在籍することになりました。

2回の育児休業を挟んで2回ともここに復帰させてもらったんですが、 今までの仕事人生で一番といっていいくらい居心地のいいチームでした。

MLのプロダクトへの導入、配信アルゴリズムの設計/実装、DSPの実装などなど、技術的にも様々チャレンジさせてもらいましたし、 フルタイムモブプログラミング、スクラム+カンバンなど開発プロセスの面でも、組織の中でも先鋭的なチャレンジが出来ていたと思っています。

このころ、こういったテクニカルなところの意思決定やサポート、開発プロセスやDeveloperExperienceの改善...といった領域でチームの役に立てているという実感が持ててきました。 本とか後述のゼミとかで得た知識と実践が噛み合ってきた感触があって、こういうところに自分のエンジニアとしての強みがあるのかな、と思い始めました。

このチームの話をアジャイルコミュニティや関連のカンファレンスでなんどか発表させていただいて、コミュニティにもつながりができたりと、充実していました。


こんなかんじで、サイバーエージェントではアドテクで主要な領域はSSP以外は一通り仕事で関わらせてもらいました。 ドメイン知識も深まりましたし、何よりインターネット広告の仕事が大好きになりました。

いま、インターネットの多様性を支えているのは紛れもなく広告です。 ざんねんながら嫌われることも少なからずあるインターネット広告ですが、 ぼくはこの仕事に誇りを持っているし、子供にも胸を張って話します。とうちゃんはインターネットを支える仕事をしているぞと。

これからも...まあ仕事がある限り、少なくともしばらくはこの仕事を続けていきたいと思っています。

ゼミ

サイバーエージェントでの仕事を思い出す上でもう一つ重要な要素がありまして、 「ゼミ」と呼ばれる制度です。 この辺を読んでいただければゼミ制度について御理解いただけると思います。

www.cyberagent.co.jp

  • 業務時間の一部を使って良い
  • 予算がつく
  • 一定のコミットと成果が求められる

というのが基本ルールですが、参加者の自主性を非常に重んじるありがたい制度でした。 これがすごく成果も出てるんですよね。

僕は2種類/6つのゼミに参加しました。

機械学習

「音楽を機械学習で創る」というゼミに機械学習なんもわからん状態で入らせてもらってから、

  • GANでショパンピアノ曲を学習させて作曲してみたり、
  • 音楽の分類&レコメンドシステムをつくってみたり、
  • 強化学習で株取引するAI(笑)を書いたり、

とまあ楽しみながら機械学習について学べた大変良い機会でした。

同じことに興味がある人達と一緒にやる、詳しい人の指導を受けながらやるのは上達のいい方法だなと思います。

あとこれらのゼミを通じて「論文を読んで実装する」機会が何度もあったのですが、これはそれまでにない経験で結構考え方が変わりました。 以前は論文読むのって必要なんですっけ?みたいな感覚でしたが、 仕事でも論文読むようになりましたし、そんな特別なことじゃないんだなー、くらいに思うようになって、フロンティアに近づけたかんじがします。

アジャイル

後半2年ほどは、アジャイル関連のゼミに在籍していました。 もうサイバーをやめてしまった人も多いけど、このゼミや関連するイベントを通じて出会った人たちとは今でも親交が続いていて、それだけでも大変に価値がありました。

コミュニティでの人々との出会いや、CSM/CSDといった研修での師匠との出会い、 またAgileConfへの参加を通じて海外にも友だちができたり、 もう語り尽くせないほどいろいろなものを得て、エンジニアとしての幅も大きく広がったと思います。


...そんなわけでゼミで得た知識や経験は仕事にも生かされていて、2年も師の元で勉強を続けると仕事でも使えるようになるんだなぁという感覚です。 エンジニアのキャリアップという意味でも、会社への還元という意味でもほんとうに素晴らしい制度だったと思いますし、 こういうところにサイバーエージェントの強さがあるんだろうなと思います。

今後も是非続けていってほしいです。

なんでやめるの

前述の居心地のいいチームのプロダクトがCloseすることになって、チームも解散することになってしまった... というのが直接的な原因です。 社内でポジションを探しつつ、社外にも目を向けてみようと思って転職活動をはじめたら運良く良いオファーをいただいてしまった...という感じですね。

本当にすごく居心地のいいチームだったので、このままコンフォートゾーンにとどまり続けていいのか...という気持ちも正直なところ少しあったかなと、今となっては思います。

まぁこういう転機は起こるべくして起こるとも言いますし、そういうタイミングということだったのかなーという気もします。

コロナ禍での転職活動や準備については、機会があればまた書きます。ええ、書きたい気持ちはいつでもあるんですよ。

なんでスマートニュースなの

一言でいうと「ピンときたから」です。ここで働いたら楽しいだろうなー、と言うのが想像できたので。

一応、今回の転職活動では2つの軸をおいていて、

  • ハンズオンでエンジニアリングができる、コードが書けるポジション
  • グローバルな環境

この2つの条件に完璧にマッチしたのがスマートニュースでした。

転職活動をはじめて、エージェントから紹介されるまで全く知らなかったのですが、 スマートニュースは世界中から超つよつよエンジニアが集まる国内でも最高峰のテックカンパニーですし、 USでもサービスを展開し、中国/USにもオフィスがあって、日本オフィスでも多くの外国人エンジニアが働くガチグローバル企業です。

インタビューのプロセスはかなり長いのですが、プロセスを通じて全く不快な思いをしなかったこと、 インタビューで出会った人たちみんなと「この人達と働きたいなー」と思えたのも良かったです。

あともちろん広告の仕事がある、も。もはや運命感じちゃいますねー。

ありがたいことにほかにも複数企業からオファーをいただけて、給与とか条件面だけ見たらスマニューより良いところもあったのですが、

「迷ったら楽しそうな方を選ぶ」

というのが最近のコンセプトでして、会社のステージ的にも挑戦が多そうなスマニューにJOINさせてもらうことにしました。

スマートニュースでなにするの

転職活動を始めたときに決めたように、エンジニアリングをゴリゴリやっています。 チームつくって設計してコード書いてデプロイして...

今は完全リモートで、きほんすべてオンラインで仕事を進めているのですが、 徐々に友だちも増えてきて、楽しくやっています。

みんなほんとに優しくて仕事が楽しそうで、良い雰囲気の会社です。しかもみんなすごい仕事ができるという。。 エンジニアもほんとにドン引きするほど優秀な人ばっかりですね、これがグローバルレベルか... っていうかんじです。

あと、世の中で英語喋れんのお前だけ、という気持ちになれるので英語の練習に精が出ます。実際仕事で必要なので身も入りますしね。

全社で他のチームがやっていることを共有する機会がけっこうあるのですが、 話を聞いてるだけでも面白そうな仕事がゴロゴロあるし、すごく距離感が近くてワクワクします!

そもそもみんなで1つの同じアプリケーションを開発してるわけで、これは僕は今までにない経験なんですが、めっちゃエキサイティングですね! みんなが同じ船に乗ってるのを感じられます。

広告ももちろんそうですが、サービスとしても新しいものをどんどん出していて、いろんなことがすごいスピードで動いています。 意思決定も動きも早いですね!ついでに実装もくっっっそ早いです。キャッチアップするのも大変。。

と、いうわけでそんなチャレンジングでエキサイティングな環境なスマニュー、 We are hiring です。 バックエンドエンジニアのポジションはこんなかんじのJDです。

wrkbl.ink

スピード感があって、技術的にもチャレンジングで、エンジニアにとっては最高の環境だといえます!

ご興味ある方、お気軽にTwitter @martin_lover_se などでお声がけくださいー。カジュアルにおはなししましょうー!

あと 2/26 にこんなイベントをやります!

smartnews.connpass.com

スマートニュースにJoinして、3ヶ月で感じたバックエンドエンジニアとして働くたのしさとかやりがいを赤裸々にお伝えできればと思っています! ぜひお誘い合わせの上おこしください〜

~「ギャザリング」とは心の所作である~ RSGT2021に参加しました!

f:id:martin_lover_se:20210113104519p:plain

RSGT2021に参加しました! 結論からいうと、やっぱりRSGTは最高でした。

今年はハイブリッドという新しい形での開催でしたが、 オンラインとオフラインが見事に融合した、まさにハイブリッドカンファレンスと呼ぶにふさわしい場でした!

「ギャザリング」とは心の所作である

考えてみれば 2020, 会場が大崎ブライトコアから御茶ノ水ソラシティに移ったときも、RSGTは変わらずRSGTでした。 そしてフルオンライン参加となった今回も、やはりたしかに「RSGTに参加した」という確固たる感覚があります。 このことから導かれる結論は一つ。

そう、ギャザリングとは心の所作だったのです。

ぼくたちは会場にギャザっていたのではなく、RSGTという概念にギャザっていたのです。

スクラムの名のもとに集まり、あるものはエネルギーを交換し、あるものは暗黙知を伝達し、またあるものは形式知暗黙知に変換し。 これが僕が、おそらくみんなが愛するRSGTだと、これこそがギャザリングなのだと、今年は一層強く感じることができました。

この「場」は決して偶発的に発生したわけではなく、

  • ステイブルチームたる運営の皆様の周到な準備
  • 去年一年間コミュニティに培われたオンラインカンファレンスの叡智、インクリメント
  • 参加者のコミットメント

などなど、これらの集大成としてなされた必然だったのかなと。

スクラムじゃん」とは及部(@TAKAKING22) さん。まさにそのとおりだと思います!

Discordの住人たれ

今年は前回の記事

martin-lover-se.hatenablog.com

で宣言したとおり、朝Discordに「ゴーストをアップして」Discordに住み着きました。

f:id:martin_lover_se:20210113004731p:plain f:id:martin_lover_se:20210113004631p:plain f:id:martin_lover_se:20210113004812p:plain

そして、

「オンサイトでやっていた行動をオンラインで再現する」

徹頭徹尾これを貫きました。 廊下チャンネルに「ウロ...ウロ...」とかしょっちゅう書き込んでいたので、初対面の方からはおそらくヤバい奴だと思われたことでしょう。

オンラインにはオンラインならではの楽しみ方があるというのはもっともな話なのですが、今回は新しいオンラインカンファレンスの楽しみ方を模索する、という僕の実験でもありました。 因果かどうかは定かではありませんが、結果的には冒頭述べた通り最高の3日間を過ごすことができました!

当日の動きはだいたいこんな感じです。

セッションが始まるとき...

  • まずそのセッションの部屋にいき(Discordの該当の部屋のテキストチャンネルに入る)
  • 席を確保し(zoomを起動し)
  • 着席を知らせる(Discordの該当チャンネルに「着席ィ!」などと書き込む - 知り合いや登壇者にアピール)
  • 以降セッションを見ているときは他のチャンネルは見ない(だってリアルでは人間は同時に複数の部屋に存在することはできないですからね、量子力学じゃあるまいし

セッションが終わったら...

  • まず廊下に出て(Discordの廊下チャンネルに入り、「ウロ..ウロ...」とか「キョロキョロ」とか書き込む)
  • だれも捕まらなければ丸テーブルに佇む(丸テーブルボイスチャンネルに一人で入る)
  • だいたい誰かが話しかけてくれるので雑談する
  • 雑談を堪能したら次のセッションへ(以後loop)

こんなムーブをしていました。

そういえば テラスルームにコーヒーとおやつを取りに行く、という所作を抜かしてしまいました。まだまだ解像度が足りないですね。

これは予め「廊下」や各部屋の名前のチャンネルが準備されていたことで成せたムーブでした。 コミュニティの知見に感謝です!

廊下の解像度を上げる

廊下って廊下だけじゃないんですよ(?)

ちょっとソラシティの廊下を心に描いてみると、 スポンサーブースがあって、チョット立ち止まって話ができるスペースがあって、奥に行くとソファーがあったりするわけじゃないですか。

その機能を少しずつ切り取って、オンラインに移植する試みをやっていました。 まあ「丸テーブル」とか「ソファ」とかのボイスチャンネルを作って、そこでだべってただけなんですが。

ぼく的にはこれは大変成功で、多くの人とここで交流できたし、「ちょっとソファーでやすもっか」みたいな行動も観測することができました。
(チャンネル数増やしてしまって申し訳ありませんでした。なけなしの気を使って、いつでも消せるようにテキストチャンネルはつくらないようにしてたんですよ...。)
(後述しますが全体的にコミュニケーションが非常にハイコンテキストになってしまって、初回参加の方には申し訳なかったな、と思っています。)

あと大変画期的だった出来事として、 KANE(@higuyume)さん、びば(@viva_tweet_x)さんが廊下を配信してくれて、ここで多くの人と話すことができました。

note.com

なにより、「心に廊下をインストールできた」こと、これが大きかったですね。 去年の記憶を頼りに廊下をイメージしていたのですが、映像がついてきて、しかもリアルタイムにオンサイト参加者とコミュニケーションが取れることで解像度が一気に高まりました。
もちろん配信の画質/音質もすばらしく、心の廊下の解像度向上に一役買っていたのはいうまでもありません。大変感謝しております、ありがとうございました!

また、忘れてはいけない、オンサイトからオンラインのぼくに何かにつけてメンションをくれたいくお仲間、「いきいきいくお」こと小田中(@dora_e_m)さんの存在も大きかったです。 彼のおかげでオンサイトとのつながりを常に感じることができました。
彼がファシリテートしたOSTは、ハイブリットOSTという新たな形態の一つの到達点ともいうべき場でしたね。圧巻でした。 ありがとう、いくお!

バーチャルぼっちを恐れない

とにかく雑談コミュニケーションを欲していた僕は、「丸テーブルで/少人数で/半プライベートな雑談をする」というあのオンサイト行動を再現しようと躍起になっていました。

当初の作戦は「廊下でウロウロし -> 知っている人を見つけ -> メンションしてボイチャに連れ込む」というものでしたが、 セッションが終わった後、Discordの廊下の廊下に出てくる人は割と限られていて、「知り合いを見つける」が成立しませんでした。そりゃそうか...。オンサイトの廊下は偉大ですね。

結局、休憩時間になったら廊下チャンネルにウロウロを書き込んで存在をアピールすると同時に、丸テーブルに一人佇むというムーブを完成させたのですが、 これは大変よく機能して、話したかった多くの人たちが話しかけてきてくれました。

じっさいボッチで佇んでいた時間は5分もなかったと思います。「こいつは誰かと話したいんだな」というアピールとして通じたのでしょう。 バーチャルぼっちでいることを恐れないだけで、多くの良質なコミュニケーション体験が得られました。

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

打ち上げはサイゼリヤをバーチャル空間に開店して行いました。入店してきてくれた方にはもれなく「いらっしゃいませ〜当店はセルフサービスとなっております」的な雑なアナウンスを行い、もれなく心にサイゼリヤをインストールさせていただきました。 これもおそらく、初対面の方からは(もしかしたら知り合いからも)狂人の類と思われたことでしょう。

サイゼリヤ」の命名については前回のブログにかいたのですが、「ボイスチャットルーム1」みたいな味気ない名前より、特定の、みんながわかるお店の名前で「サイゼで夜中までだべってさー」みたいな共通体験がえられるといいな、くらいの感覚です。

最終日に雑に「サイゼリヤ」チャンネルを作って、とりあえず知り合いを集めてみたところ、勝手知ったる距離感でオンライン同士でもテンポ良く話せて大変満足でした。個人的には、会話のテンポはその会話の満足度に大きく影響する大事なファクターです。 僕としたことがビールを買いそこねていて、それだけが心残りですね。

また、3~4人で話していれば誰かが入ってきてくれて、いつもより知り合いが増えたし、色んな人と話せたなーという感覚があります。 だいたい3人くらいボイスチャンネルに集まっていると、その後はどんどん人が増えてくる現象があるみたいですね。何度か観測しました。おもしろいですね。

オンサイトだと、同じお店にいないと会話できないですし、移動するのもなかなか難しいので、これはオンラインに軍配があがる良い例だったなと思います。

そういえば、今年は「下の中華に集まる」という現象が発生しなかったのが意外でしたね。 みんなが集まって今日のことを話すことが本質なのであって、場所はどこでもよい、ということでしょう。

僕はというと、そんなに中華に思い入れがあるわけじゃないですけど、「毎日終わったら中華に集まって宴会をしている」というその様式美を観測するのがたいへん好きだったので、正直なところ少しさみしかったです。 「誰かがいれば人が集まる」という現象もあるようですし、来年オンラインがあれば、勇気を出して声をかけてみようかなと思います。

能動的参加者

一つ反省として、全体に渡って超ハイコンテキストなコミュニケーションを多用してしまいました。 特に初参加の方、英語話者の方にとってのコミュニケーションや参入障壁になってしまった可能性は否めず、その点は申し訳なかったなと思っています。

今回のRSGTでは、 おがさわらさん(@bonbon_0605)/さいとうさん(@nolick1219)の「知り合いを増やす」という今最も必要とされているニーズを捉えた画期的なセッションや、 なべさん(@watanabeisan)の「初心者の館」など、積極的に初参加の方を仲間にしようという流れが顕著に観測できました。 ぼくにとって、このような行動が実践できる方々はほんとうに尊敬できます。

一方僕はあんまり器用でないし、人に気を使うのも周りに気を配るのも得意でないです。 僕にできることといえば、僕が目一杯楽しんで、その正のエネルギーを周りに少しでも伝播するくらいかなぁ、とかそんな事を考えていました。

もちろん、だれかの自由を奪う/だれかが不快に感じる行動をしないとか、最低限のルールを守った上で、です。 (自分で認知できる範囲で、ですが...。もし会期中の僕の行動でなにか不快な点を感じられた方は、大変お手数ですがぜひ僕に直接フィードバックを貰えればと思います。)

実際、今回はいままでのRSGTの中でも、もしかしたら登壇した前回よりも「RSGTを一緒に作っている」という感覚が強かったです。 よこみち師匠(@ykmc09) はこれを「能動的参加者」と称してくれました。

この能動的参加者の集合体がRSGTであり、それもこのカンファレンスの魅力の一つなのかな、と思います。


そんなわけで、今年も最高の3日間でした。 いやいや、いまもDiscordでは熱狂冷めやらぬDay #N が続いていますね!

長くなってしまったので、拝聴したセッションの感想はまた後日書きたいと思います。

ぼくのオンラインRSGT2021を最高にするための3つのこと

この記事はRSGT2021に参加する方に向けて書いています。

さっこんの情勢を踏まえ、今年のRSGTにオンサイトで参加することを諦めました。 RSGTは僕にとってすでに一年ぶんの元気をもらう大切な場所になっていて、苦渋の決断です。

オンラインで参加すると腹をくくったので、この制約のもとで最大限RSGTを楽しむにはどうすればいいか?を考えてみました。

もし賛同してくれる方がいらっしゃったら、ぜひDiscordでぼくにおこえがけください。IDは suyama#7142 です!

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

ですが、この記事は「僕がRSGTを楽しむために、いまから僕にできること」を念頭に、主に自分のために書きました。 もしこれを見て不快に感じる方がいらっしゃったら、申し訳ありません。 ぜひ僕に直接フィードバックをください。

TL; DR

結論から言うと、電脳化してDiscordに記憶とゴーストをアップします。

  1. オンラインこそセッションはLive視聴
    • Zoomはステージ、Discordはテーブルと思え
    • VideoONで登壇前/中の "目が合う" 的なコミュニケーション
    • 登壇後のまとめや感想などを何某かの手段で伝える
  2. バーチャル徘徊 & 気軽にメンション
    • バーチャル廊下をバーチャル徘徊して出会った知り合いにメンション飛ばす
    • だって廊下で会ったら挨拶するでしょ?
    • 知り合いをつかまえてバーチャルソファーで立ち話する
  3. バーチャルサイゼで打ち上げする
    • 打ち上げを企画して話したい人を誘う
    • 「終わった後あの店で集まってバカ話した」みたいなメタファーが必要
    • バーチャル中華の開店も所望

もしどういう動機でこれをやるのか、に興味を持っていただける方は以下読み進めていただけると嬉いです。

なんでやるの

先日2020年の振り返りを書いた際、「オンラインカンファレンスからオフラインのそれほどの価値を取り出せなかった」みたいなことを書きました。 偽りざる気持ちでしたが、今日RSGTをオンラインにする覚悟を決めたとき、

「あれ、ぼくオンライン最大限楽しむために自分からできること探してやったんだっけ?」

とふと気づき、やれることはやろうかな、と思ったので...。 思い立ったが吉日でばーーっと書き出してみました。いつもの雑なやつですがメモがこちらです。

f:id:martin_lover_se:20210105010832p:plain

何がイヤだったの

さて、ぼくは結局過去のオンラインカンファレンスで何が気に入らなかったのでしょうか。 自分の中でのカンファレンスへの期待値とオンラインでの体験が擦り合ってなかったのかな?と仮説立てて、これをとっかかりにすることにしました。 「物足りなさ」の体験から、「自分が本当に欲しかったもの」に気づいたようなフシもあるのですが。

1. カンファレンスに期待すること

半分以上「RSGTに期待すること」、かもしれません。

Anyway,

1.1. セッションからエネルギーを貰う

会場にいて話を聞いてると、登壇者の緊張や熱意、おそらくあったであろう産みの苦しみ、 または「今ぼくたちはええ話を聞いている」という満足感などなど、そういった空気感を登壇者と、参加者と、会場全体で共有できているように感じることがあります。

そういうセッションは僕にとって満足度が高いです。 話の内容だけでなく、この体験自体を求めていて、これがぼくのエネルギー源にもなっているんだと気づきました。

1.2. 生存確認/同窓会

RSGTへの参加は多分今年で4回目...あれ5回目? まあそれくらいで、だんだん界隈に友達も増えてきました。

いや、今はいつでもどこでも気軽に連絡できる世の中なんですけど、 あの場に行けばあの人達に会える、そんで大好きなアジャイルの話とか、あのセッションはどうだったとか、昔話とか近況報告とか、スター・ウォーズは教養だからなんとかの順番で見ろ、みたいなくだらない話とかとか、気の置けない仲間とそんな話をしに行く場だ、みたいな期待感が勝手にあります。

1.3. 偶然の出会い

隣りに座った人が同じ会社で、その後コミュニティ仲間になって今年RSGTに登壇するだとか、 セッションワクワクしながら席座ってふと隣を見たらIkuoだったとか、 Agile2019で出会ったペルーの友達と再会するだとか、 友だちに友だちを紹介してもらってだんだん友だちが増えていくとか、

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

2. これまでの経験と課題

繰り返しますが、これまで参加したオンラインカンファレンスの価値を毀損する意図は全くありません。 ぼくが感じたこと、ぼくが行動できてなかったことを自分の整理のために書いています。

2.1 セッション中の「会場とつながっている」という感覚の欠如

オンライン通話の技術的な問題で、前述の空気感が感じられないのが寂しいです。

オンラインで聞いたセッションも、内容はどれもオンサイトでみたものと遜色ない、素晴らしいものだったと思います。 にもかかわらず満足度がオンサイトのときほど高くないのは、会場の笑いや沈黙、衣服の擦れる音、登壇者の微細な動きや身振り、微妙な声の震えなどなど、おそらく「空気」を構成しているであろう要素がごっっっそり欠落しちゃうので、なんとなく物足りなさがあるんじゃないかなと。

また、「あとで録画を見れるから」と思ってつい仕事をしながら視聴してしまったり、あるいはザッピングのように複数のセッションを同時視聴するみたいな真似をしてしまったり、 セッションへの集中ぐあい、体重のかけ方もオンサイトのときより散漫になってしまっていたのも一因だと思います。

2.2 コミュニケーションが物足りない

ちょっと立ち話で近況報告、してたら話し込んじゃって...みたいなことが起こらなくて寂しいです。 これまでも「廊下」や「トイレ」など画期的なメタファーが発明されてきましたが、ぼくは十分に活用できていませんでした。

もちろんボイスチャット部屋もあるのですが、10人くらい集まってしまうとオンラインでのコミュニケーションは困難(e.g. 同時発話が実質ほぼ不可能なため、話題が一つに絞られてしまう)で、慣れている人たちの話を聞いていたら終わり、というケースが結構ありました。

人数が多いと発言しづらいし、開かれてるんだけど若干パーソナルスペース感があって人数を絞れる、ほらあの大崎ブライトコアのメインホールの横のソファーのところみたいなのとか、ホワイエの立ちテーブルみたいなやつがほしいのです。知り合いに「偶然」廊下で会ったので、ちょっと静かなとこで2~3人で立ち話する、みたいな体験がほしいのです。

あ、特定の方々の会話を聞きたいというニーズはもちろんあって、そういうシチュエーションを否定しているわけではないです。 ぼくが喋り足りないというかんじでしょうか。

2.3 飲み会がなんだかざんねん

これは僕の家庭の理由、かつ完全なる調整ミスなのですが、懇親会は家事のワークロードが最も高い時間帯(17:00 - 19:00)とかぶることが多いです。 風呂&家事&子どもたちの食事を済ませてから参加...と思っていたらあれよあれよという間に時間が過ぎて帰ってきたら解散済&残り火、みたいなことがありがっかりしました。

よしんば参加できても、前述の「人数多くても話題は1つ」縛りでお目当ての人としゃべるのはさらにハードルが高いです。 飲み屋なら同じテーブルに座ってれば隣の会話に少し聞き耳を立てながら隣の人と話す、とか、あそこ大声で盛り上がってる、みたいな複数トラックの会話とか、ジョッキだけ持ってあの人の隣行く、みたいなアタックが成立するわけですが、まず「近くに座ってる」というメタファーがDiscordにまだなく(スペチャとか近いものはありますが)、コミュニケーションを阻害する一因である気がしています。

なにをやるの

だいぶ寂しかったことが整理できたので、それらに対してできること/できそうなことを考えてみました。

1. セッションのLIVE視聴を大切にする

欲しい物を得るためには、オンラインのときこそLIVE視聴で、「今起こっていることを全力で感じて共有する」ことが必要なんじゃないかなと。 だってネットの向こうにから伝わる/伝えられる情報はすくないんですから。 過剰にこちらから発信するのと、集中して少しでも情報を拾うのと同時に欠損している情報を想像と経験で補完する処理が必要なはずです。

Zoomはステージ、Discordはテーブルだと思って、もちろん部屋は1つに絞って参加します。そもそもリアルでは複数の部屋に同時に入ったりできないですからね。

セッション中は

  • Video ON & 所作に を込めて送る
  • テキストチャットを盛り上げる
  • 集中するためメモをとる
    • あわよくば登壇者にフィードバックする

くらいをまずやってみて、会期中に調整しようと思います。

2. バーチャル徘徊 & 気軽にメンション

要は「廊下でばったり会って」「ちょっと立ち話した」このシチュエーションがほしいわけです。

???: 「え?話したいんならメッセなりDMなりしてZoomすればいいじゃん」違うんです。シャイなんです。 会場に行ったらきっとご無沙汰してるあの人にもあの人にもきっと会える、そうだあの人にも挨拶したいな、なんて思いながら廊下をうろちょろする、そういうもんでしょうカンファレンス!!

というわけで、休憩時間はDiscordの廊下をバーチャル徘徊しようと思います。「ウロウロ... キョロキョロ...」とかなんとかテキストチャットに流しとけばきっとだいじょうぶ。 そして廊下で知り合いを見つけたら(これは定義が難しいですがなんかええ塩梅でやります)すかさず声をかけ(メンションし)、そのへんの立ちテーブルとかソファ(というバーチャルボイスチャネル)に連れ込んで立ち話するわけです。イメトレは完璧。

あとは立ちテーブルとかソファみたいな、「廊下より少し狭いエリアで2~3人が話している」みたいなメタファを持ったボイスチャンネルを作って機能するか実験したいですが、そもそも勝手に作って良いのかわからないですし乱立しすぎても困る気もするし、要確認。

そしてどうか廊下で「キョロキョロ...」しているギターのアイコン suyama#7142 を見つけたら、優しく見守るか、前を横切ってみるか、声をかけてあげてください。

3. バーチャルサイゼで打ち上げ

RSGTといえば、終了後の中華ですよね。(今年はリアル開催はさすがに無いでしょうが...)

ぼくはちょっとひねくれ&シャイなので、中華以外の店によって景気付けしてから乗り込む、みたいな行動がとりたかったり、 「どこか違う店かもしれないけどみんなこのへんで飲んでるだろうから適当に声かけられる」みたいな状況が心地よいわけです。

まずスケジュール調整、家庭の方をバッチリ調整しておくことと、会いたい人とは約束しておくこと。 そして(店はなんでもいいんですけど)バーチャルサイゼに複数テーブルがある、みたいなバーチャル環境を作りたいです。 「中華で話してさぁ...」「ビアバーでさぁ...」みたいな共通の思い出にできるメタファーがほしいのです。 はてこれもDiscordに作って良いものか...これは会期中に調整したいですね。

というわけでここまで読んでくれた方!

とにかく金曜、OSTが終わったら集まろう!! ぼくとの約束だ!!


こんなかんじです。 サラっと書くつもりが全体重乗っけてしまいました。

オンラインはオフラインの代替じゃないから、オンライン専用の方法で楽しみなよとか、文句ばっかり言ってんじゃねーよとか、様々ご意見あると思います。

思いつくまま感情に任せてばーーっとかいたので、うまくない方法もあるでしょうし、すでに解決策がある課題もあるかもしれません。 できれば、よりうまいやり方をRSGTの期間中いっしょに模索していただけるとうれしいです。

オンサイトとのハイブリッド開催なので、オンサイトにいる人を捕捉するのもむずかしそうだなーとおもっています。

とにかく、あしたから3日間、ぼくのゴーストは Discord にいます。

RSGTで皆様にお会いできることを心から楽しみにしています!!