ジンジャー研究室

長めのつぶやき。難しいことは書きません。

2023 年、改めて React と Elm Architecture を比較する

最近 React のドキュメントが新しくなったということで読んでみた。第一印象としては、とにかく懇切丁寧で React というか JavaScript すら初心者という読者でも基礎的な考え方が身に付くようになっている。ただ、深い内容まで読み進めると「同じ Virtual DOM のフレームワークでも Elm とだいぶ違うな」と改めて思った。

これはどちらが良いとか悪いということではなく、一長一短あると思う。筆者は長いこと Elm を使ってきたが React も嫌いではなく、趣味を含め色々な場面で重宝している。ただ、 Elm Architecture の提供するシンプルな仕組みには依然として価値があると思っており、それがあまり世の中に知られていないのが勿体無い。というのが、この記事を書こうと思った動機である。

昔は「部分的に取り入れても Elm メリットは享受できないから Elm やってよ」だったが、そんな余裕はなくなってきたので良いアイデアだと思ったら盗んで欲しい。

状態の居場所

React の状態はコンポーネントの各所に偏在している一方、 Elm の状態はコンポーネント*1の外にある。 次のコードは、Elm アプリケーションを定義する最もシンプルな3つの型である。

init : model
update : msg -> model -> model
view : model -> Html msg

この中の model というのが「状態」を表す型であり、init が初期状態、 update が状態を更新する関数である。見ての通り、view は model を外部から受け取る純粋な関数であり、 React の useState のように内部で状態が初期化・更新されることは決してない。このように Elm アプリケーションの状態は view とは完全に独立しているため、view のないヘッドレスなアプリケーションを init と update だけで構築してテストすることも可能である。

一方、React の場合はまずコンポーネントがなければ何も始まらない。実は React でも自前で Virtual DOM のライフサイクルを制御しようと思えばできるのだが、そんなことをしている人は誰もいない。チュートリアルや有名フレームワークはまず App というコンポーネントを定義し、その中で useState するように教えている。

コンポーネントの中で状態を扱うと、 DOM のライフサイクルの影響を受けやすい。例えば、一度マウントしたコンポーネントを一時的に消して復活させると、そのコンポーネントの状態は保存されていない。そのような状況下でも状態を維持するためには一つ上の階層にコンポーネントの状態を持っておく必要がある。

Elm では、最初から全ての状態をコンポーネントの外に持っているため、そのような問題は起こらない。また、状態をコンポーネントの外に持っているとリセット操作も直接的にできる。React でコンポーネントの状態をリセットしたいときに key を使うことがある。これはコンポーネントの状態がライフサイクルの影響を受けることを逆に利用しているのだが、直感的かどうかはやや疑わしい。

と、ここまで Elm の状態管理のメリットを述べてきたが、欠点もある。全ての状態を外で管理しているため、コンポーネントの外側にボイラープレートが増えるのだ。ただし、私見を述べると「想像するほどの面倒さではない」とは思っている。

コンポーネントの外に書かれるボイラープレートとは、例えば「あるコンポーネント A でイベントが発火したら、そのコンポーネント A の状態を更新する」というものだ。これだけ聞くと、コンポーネント内部のあらゆるイベントハンドリングを使う側が行わなければいけない(言い方を変えるとカプセル化を破壊する)ように思えるが、実際にはそうならない。コンポーネントの外側で把握すべきことは「何らかのイベントが発生したら何らかの更新をする」ということだけだ。運送業者が宅配便の中身を把握していないのと同じだ。

全ての状態がコンポーネントの外にあるということは、巨大なツリー構造のデータ(model)を描画関数(view)に渡すということになり、これも何となく乱暴に思える。が、規則的にネストすることによって、ある view が扱う状態はその view が関心のある局所的な部分だけで済むようになる。例えば elm-spa-example の Page 以下のモジュールは、それぞれのページに必要な Model や Msg が定義されており、 view もそれ以外の情報を参照しない。また、React.memo 相当の処理をする Html.lazy 関数もあるのでコスト面も問題ない。

副作用の扱い

React は useReducer という仕組みによって、状態の更新ロジックを分離することができる。ここで reducer 関数には、何らかのアクションを受け取って古い状態を新しい状態に更新する処理を書く。が、ここで reducer 関数は「純粋」である必要があり、つまりは非同期処理が書けないという問題が発生する。例えば HTTP リクエストを送信しつつ、何らかの状態を更新するには、前者をイベントハンドラ内に書き、後者を reducer で行う必要がある。すると、関連のある二つの処理が分離され、かえって見通しが悪くなる(useReducer の使い方を間違っていたら教えてほしい)。

Elm では話は至ってシンプルで、reducer 関数に相当する update 関数が副作用を同時に起こせるようになっている。これは上のコードを「副作用あり版」に変形したものだ。

update : msg -> model -> (model, Cmd msg)

Cmd というのはコマンド(Command)のことで、これを Elm ランタイムに返すことで副作用を実現する。例えば HTTP リクエストの場合、Elm ランタイムに「リクエストを送信してくれ」というコマンドを送る。すると Elm ランタイムはレスポンスを msg 型に変換し、再び update 関数を呼んでその msg を渡してくれる。Cmd msg という型は「msg 型でコールバックされるコマンド」を表している。

同じことが init にも言える。

init : (model, Cmd msg)

この型が示す通り、 Elm では初期化プロセスとして副作用を起こすことが最初から想定されており、あまり特別感はない。実際、初期データを取得するために HTTP リクエストを送信するというのはありふれた話だ。しかし何故か React のドキュメントでそのケースが扱われるのはかなり後の方で、探すのに苦労してしまった。「エスケープハッチ」扱いはどうにもモヤモヤする。

コールバック関数は update だけ

先日、同僚氏に「Elm で dispatch はどうすれば良いか」と尋ねられて、ああ確かに Elm はそういうものがないな、と思った。

なぜか。最初のコードを再掲するので、今度は view 関数の戻り値を見てほしい。

init : model
update : msg -> model -> model
view : model -> Html msg

Cmd msg が「msg 型でコールバックされるコマンド」ならば、Html msg は「msg 型でコールバックされる HTML」である。すなわち、イベントは msg の型で update 関数に渡される。何かがあれば必ず update 関数を通るし、それが唯一のコールバック関数であるということだ。この強力な性質により、 Elm のコードは誰が書いても大体似たような感じになる。

シンプルさを貫いた設計

Simple vs Easy という話がどこまで共通認識になっているのかは分からないが、 React に比べて Elm は圧倒的に「シンプル」側に倒した設計になっている。個人的に考えるシンプルさのメリットは以下のようなものだ。

  • コードを見れば何が起きているか分かる
  • 覚えなければいけない特別なルールが少ない
  • 予想外の挙動が起きにくい

ただし、これらのメリットによって喜ぶ程度は、かなり人によりバラツキが大きいように思う。ぶっちゃけ、シンプルで美しいからと言って機能性の乏しさを我慢できるのはオタクだけなのではないかと思ったりする。実際、ボイラープレートが多いのは客観的に見ても面倒だと思っており、「シンプルさのために多少の面倒は我慢しなさいよ」と言うのは乱暴だろう。

しかし、考えてみれば古来から言われてきた Model と View の分離、 React も是としている「ビューは純粋な関数」を何よりも体現できているのが、この Elm Architecture であると思う。ある種の完成された形として後世に受け継いでいきたい。

Elm Architecture に関しては公式ガイドがかなり分かりやすく書いてあるので、詳しくはこちらを参照してほしい。 guide.elm-lang.org

*1:Elm コミュニティはコンポーネントという呼び方を避けているが、この記事は React ユーザ向けに書かれているので気にしないことにする

極端なことを言っている時、何を考えているのか

たまに極端なことを言ったりすることがある。

「ぶっちゃけ X って要らなくない?」
「それなら X しちゃえばいいんじゃないか」

そうしたら、ある日そこから議論が収集つかない方向にいってしまい、後になって「ああ、あれはそういう意図だったんですか。あんまり極端な物言いをされると拒否反応を起こすのでやめてください」のようなことを言われた。まあ会社なのでそこまで出鱈目なことは言っていなくて真面目に議論していたつもりだったのだが、なるほどそれで噛み合わなかったのかと妙に納得してしまった。

色々考えているうちに、自分がそういう極端なことを言っている時に何を考えているのか整理したくなった。

自分の中の常識や思い込みを壊したい

長い時間をかけて自分の中に培われた常識や思い込みを、時に疑ったり真逆の思考をしてみたくなることがある。

例えば、ずっと静的型付き言語でやってきたのにいきなり「型なんて要る?」とか言い出してみる。この時「いや普通に考えて要るだろ」という心の声をまず押し殺す。そして「わざわざ分かりきってることを書くの面倒なんだよな」とか「ノイズが少ない方がコード読みやすくね?」とか「スキーマを変えるときに書き直す箇所が極力少ない方がいいよね」とか「型の名前を書くためにいちいちライブラリを調べに行くのだるくね?」とか、そっち派の思考にどっぷり使ってみる。すると「全然ダメだと思ってたけど良いこと沢山あるじゃん」となる。そこで結論が出ればそこで終わり、動的型付けで行きましょうという話になる。が、そういうわけにも行かないので今度は反対意見を出してみる。「書いたり読んだりする時間が多少増えたところで、後でバグを追跡している時間の方が長いから全体としては大幅な短縮になる」とか「僕らは Matz みたいな天才じゃないのでしょうもないところで良く間違える」とか。で、何回か反論を繰り返していると最終的に「少なくとも自分にとっては数百行を超えるコードは静的型付き言語の方がメリットが大きい」みたいな結論になる。

こうして、あえて真逆の思考に乗ることで「意外とメリットも多くあった」ことが分かったし、自分が今の思想を持っている根拠も確認することもできた。全体として解像度も上がった。最初から自説を全く疑わなかった場合、得られるものは少ないと思う。ついでに言うと、相手の言い分を理解していた方が議論を進めやすい。「なるほど、あなたの言っていることはもっともです。しかし〜」と続ければ、相手は自分の主張が理解されたことにまず安心するし、続く議論も建設的なものになる。

この例では自説は最終的に曲がらなかったが、最終的に完全に否定されることもある。しかし、むしろその方が成長できるので良かったりする。「あの時はこんな風に考えてたな」と語ることに何も恥じることはない。価値観のアップデートは心地良いものだ。

「盲信している人」と思われたくない

思えば、自分の考えを疑う癖がついたのは新卒時のある先輩社員の影響を多分に受けている。

当時、その先輩が「コードは極力綺麗に書け」と口を酸っぱくして言っていた。ほとんど洗脳に近い状態になっており、研修後の仕事でそれを実践した。プロジェクトの進捗は思わしくなかったがコードは綺麗に書いていたので仕方ないかくらいに思っていたのだが、ついに先輩に「この調子だとコードを綺麗に書こうとする人にはプロジェクトを任せるなってことになるよ」と言われてしまった。その時、固く信じていた「コードは(いつでも・極力)綺麗に書くべき」はもっと早く疑うべきだったと反省した。

ところで、その職場では Java が使われていたのが、ある日 Ruby の話をしていた時に「いやいや型書かないとかw」みたいなことを口走ったら、先輩が動的型付け派の立場を借りて猛反論してきた。「僕は静的型付けの方が良いと思ってるけど、世の中には動的型付けの方が良いと言っている人も沢山いるし、現にそれで生産性を上げている例が沢山あるが、そこに対してはどう思うの?君は静的型付けのメリットを色々説明してくれたけど、それは本当にメリットと言えるの?」と、確かそんな感じで説教された気がする。

彼は自身の考えも疑っていた。「僕はどうしても今流行りの関数型言語のメリットがわからない。コードをいくらか読んだがカプセル化が破壊されていて酷いものだ。しかし流行っているからにはメリットがあるのだろうし、自分がその境地に至れていないだけかもしれない。だからどういうメリットがあるのか経験者の君に教えてほしい」と言ってきた。自分の理解できないものにも価値を見出そうという姿勢は見習うしかない。

これらの経験から、自分の考えをとことん疑ってみる癖がついたと思う。自分の言っているメリットは本当にメリットなのか、「なぜ」を繰り返して論拠を緻密に言語化することに労を割いている。逆に「盲信している人」は思慮が浅くかっこ悪いと思うようになったのかもしれない。だから Elm にどっぷり浸かりながら「関数型そんなに良い?」とか「オブジェクト指向いいよね」とか「普通は React じゃね?」とか「C++ いいじゃん」とか言ってしまう。「盲信している人」の宣伝は、推しの評価をかえって下げてしまう。選定された理由が説得力を持たないからだ。

「一理ある」をきっかけに議論を進めたい

話を戻す。極端なことを言っている時、相手にもゼロベースで考え直してもらうことを暗黙に期待していると思う。

(なんかこの人は変なことを言っているが、どういう意図なのか興味あるな。一見あり得ない発想だが、よく考えてみると言っていることは一理あるし、変に思うのは自分の前提が間違っているのかもしれないな。)

と、少々わざとらしいが受け手に期待する反応はこんな感じだ。そして

「それってどういう意味ですか?」

と聞いてくれたならば、「待ってました」とばかりに真意を説明するだろう。「一理ある」というのがポイントで、極端な意見には欠点が沢山あるので、そこを否定に来られても困る。こちらも完璧だとは思っていなくて、伝えたいのは「一理ある」の部分だけだ。言い回しを極端にしたのは、その一番言いたい一点をインパクト強めに伝えるためだ。 そこを汲み取ってもらえたのであれば、「じゃあ、その良い点を上手く活かすために欠点となっている課題を解決しましょう」という風に建設的に議論が進むはずだ。

しかし、このやり方は相手の受け取り方に大きく依存するため、諸刃の剣ではある。

(なんかこの人は変なことを言っているな。)

で終わってしまうと、そこで詰む。こちらの真意が伝わる前に相手が論破モードに入ってしまい「その意見は色々おかしいんですよ。いいですか。」と始まってしまう。こうなるともう何を言っても「いいえ、それはこういう点で穴があります」となり建設的な議論とは程遠くなってしまう。こちらの意図、つまり「打開策のアイデアを出すために、一旦常識的な考えを捨てました。もちろん穴があるので塞ぐ方法を一緒に考えてください」という意図が伝わらないといけない。素直にそう言えばいいという話ではあるが。

建設的なモードに入るか論破モードに入るかは相手との関係性にもよる。当たり前だが、信頼していない相手の言うことはおかしく聞こえる。「ストーリーポイントなぞ要らん」と言っているのが有名なアジャイルコーチだったら、一発で「なるほど」と思って聞くと思う。

定説を覆してマイノリティの賛同を得たい

世の中的には常識になっているが、自分にとってはしっくりこない言説があったとする。最初は「そんなものかな」と、騙されたと思って従ってみるが、だんだん違和感が隠しきれずストレスが溜まってくる。そのうちに

「有名な人が言ってるからみんな影響されてるだけじゃないの?」
「多数の二流・三流の声が大きいだけで、一流は全然違う考えを持ってるんじゃないの?」
「本当はみんな違和感を感じてるけど、空気を読んで言えないだけじゃないの?」

などと考え始めてしまう。 「マイクロサービスで生産性を上げよう」みたいなやつだ。しばらく経ってから「やっぱり基本はモノリス」の波が来たが、最初は誰も否定できる空気ではなかったと思う。

とは言え、何者でもない人が真っ向から反対しても勝ち目はないし、何より自分の立場が危うくなる。仕方がないので、なんとか適当なノリを醸し出しつつ本音を言ってみたりする。予想通りあまり賛同は得られないが、引っかかった人がたまにいいねを押してくれる。

無難な表現は面白くない

そして最後に、単純に面白い言い回しで興味を引きたいという話もある。

バランスの良い当たり前の意見を普通に言ってもあまり面白くない。ちょっとひねりが効いていたり、インパクトのある表現が必要だ。これが政治家だとちょっと皮肉の効いたギャグでも揚げ足を取られて大変なことになるが。インフルエンサーが放つ意外な言葉も面白い。こんまりが片付けを諦めたと言えば、それだけでもう面白い。

とまあ、大体こんな感じで考えていることは整理できたかな。

Cloudflare Workers + Durable Objects でホワイトボードを作ってみた

f:id:jinjor:20220321214948p:plain

Cloudflare の比較的新しい機能、 Durable Objects を使ってオンラインホワイトボードを作ってみた。

github.com

エレベーターピッチ(テンプレはこちらからお借りしました)

- 1秒で適当な概念図を描き始めて議論をしたい
- ソフトウェア開発者向けの、
- Whiteboard というプロダクトは、
- オンライン共同編集アプリです。
- これは直感的かつ雑念の入る余地のない最低限の操作ができ、
- Google Jamboard とは違って、
- Slack コマンドから一瞬でログインできる仕組みが備わっている。

本当に Jamboard に勝ってるの?まあ、お遊びなので許して。 あと、今のところ組織内で使う想定だから自分でデプロイしないと使えないよ。

Durable Object を雑に紹介

ベータ版の公式のアナウンスが分かりやすい。エッジから状態を管理するオブジェクトにアクセスできる。各オブジェクトは JavaScript クラスのインスタンスで、それぞれ裏側にストレージを持てる。Workers KV と違って強い一貫性がある。

f:id:jinjor:20220321222300p:plain

オブジェクトは ID が同じであれば1個しかないので、例えばチャットアプリで WebSocket を使う場合、部屋に対応したオブジェクトを作っておけば全ての接続を1箇所に集められる。オブジェクトは地理的に有利な位置に自動的に配置されるので、主に国内で使う分にはあまり遅延を気にしなくてよさそう。

上の絵もホワイトボードで描いたよ。

個人的なモチベーション

無料で手軽にアプリ作りたいの民なので大手クラウドを使いたくなくて Heroku とか Netlify とかを使ってたんだけど、エッジで動作してデータも持てるというのは初めて見た。よく見たら無料じゃなかったけど月5ドルだから安い。

で、ずっと触ってみたいと思いつつ作るネタがないなと言ってたら、リモート全盛で手軽に使えるオンラインホワイトボードが欲しくなった。そんなの既にあるじゃん?でも、もっとこう思い立ったらパッと雑に書き始められるシンプルなやつ。ということで Slack コマンドを叩いたら部屋が出来るようにした。

感想

チャットアプリのサンプルコードがあったので、そこから適当にいじれば1週間くらいで終わるかな〜などと甘く考えていたら1ヶ月半かかってしまった。が、結果的には満足のいくものができた。 意外と Workers に癖があるので、一度あらゆる罠を踏みながらテンプレを作っておくと次から楽できそう。

以下、得られた知見など。

基本的にはサンプルと同じ

チャットがホワイトボードになるだけなので、基本的にやることは一緒。部屋の状態を管理するのに Room というクラスを作る。 ただし、今回の仕様では部屋を際限なく作っていいわけではない。存在しない部屋にアクセスしたらエラーにする必要があるし、アクティブな部屋の数に上限を設けるために部屋の数を数える必要がある。ここで問題になるのが、全てのオブジェクトを一覧したり存在確認をするための API が用意されていないこと(REST API にはあるが、これは管理者用でアプリケーション内で使うものではない気がするし、何より遅い)。 仕方がないのでそれ専用の RoomManager なるクラスのシングルトンを作って管理することにした。当たり前だけどシングルトンにしてしまうと分散しないのでまあ微妙。(かといって KV は結果整合だし、いい方法ないかな)

Miniflare を使う

公式のツール wranglerクラウド環境に接続して開発する想定なのか、知らずに本番環境に繋いでしまって驚いたりした。ローカルで動作確認するなら Miniflare を使おう。これはコミュニティ製のツールだったのが本家に取り入れられたものらしい。.env ファイルが読めたり、気が利いている。 ちなみに開発中の wrangler 2.0 ではローカル実行がサポートされるらしい。期待。

Node.js 環境ではない

Node.js の標準モジュールが使えず、代わりに Web 標準の API が用意されている。 Web Crypto API とかはあんまりカジュアルに使えない感じで苦労した。 セキュリティも厳しく eval が使えない。それ自体は別にいいのだが ajv が内部で eval を使っているらしくエラーを出してしまった。困って調べたら@cfworker/json-schemaなるものを発見。なんとかなった。リポジトリを見ると他にも色々あるようだ。

itty-router

express みたいな router 欲しいなと思って探したら、まさに Cloudflare Workers のために作られたitty-routerというライブラリがあったので、便利に使っている。

静的ファイルの配信

「そんなの public フォルダ指定するだけでしょ」って思ったら違って、 Workers KV を使うらしい。(ちなみに手軽な用途であればチャットのサンプルコードみたいに HTML ファイルを直接バイナリとして import することもできる) @cloudflare/kv-asset-handler をインストールしてサンプルの通りにやる。

その中で __STATIC_CONTENT_MANIFEST を import するという妙な仕様があり、そのままだとモジュールを解決できない。結論から言うと TypeScript + esbuild ならこうするのが楽。

// @ts-ignore
import manifest from "__STATIC_CONTENT_MANIFEST";
esbuild --external:__STATIC_CONTENT_MANIFEST ...

これをちゃんとやらないとアップロードしたファイルのパスが解決できない(ハッシュを含めたファイルパスとのマッピングをやっているっぽい)。ちなみに Miniflare は env 変数の中に __STATIC_CONTENT_MANIFEST を含めてくれているが、これは独自仕様なので本番では動かない。

secret を一括で追加

環境変数にあたるものを CLI で暗号化して送信するのだが、1つずつしかできなくて面倒だった。

wrangler secret put <name>

.env から自動で同期するスクリプトを書いた。

GraphQL API で利用状況を確認する

もちろん利用状況はダッシュボードに書いてあるのだが、微妙に自分が見たい情報からピントが外れているというか、このページに書いてあるような料金の計算をするには情報が足りない。 そこで、GraphQL API を使う。 REST API もあるが Analytics 関連は deprecated になっていて現在は GraphQL API のみ。で、 API ドキュメントが見つからないと思って探してみたら、Introspection 機能を使って調べてねとのこと。要するにクエリを投げると型情報とかが返ってくるので自力で探ってみてね、と。なにそれ辛い。 get-graphql-schema みたいなツールもあるけどあんまりパッとした結果も得られなかったので、結局 GraphQL クライアントをインストールしてサジェストを出しながら頑張った。

そして自作ダッシュボードを得た(図は一部)。

f:id:jinjor:20220321235410p:plain

ところで、請求額が無限に膨らむのが怖いので、アプリケーションの仕様として、アクティブな部屋数の上限、編集可能な時間の上限、部屋に入れる人数の上限などを細かく自由に設定できるようにしている。

Durable Object が消えてくれない

Durable Object を消すための API がない。フォーラム によると、ストレージを空にすれば勝手に消えてくれるようなことが書いてある。やってみたが、消えてくれなくて困っている。

Cloudflare 以外でハマったところ

  • Slack の OAuth はブラウザであらかじめワークスペースにログインしていないとフォームが出せない
    • public distribution を有効にするとこの問題は解消するが、サーバー側でワークスペースをチェックする必要がある
  • SameSite 属性が strict だとリダイレクトと同時にクッキーをセットできない
    • lax にした
  • websocket プロトコルに upgrade する時に HTTP エラーを返しても、 JavaScript 側でレスポンスにアクセスする手段がない
    • 一旦 WebSocket 接続し、瞬時に切断するしかない
  • iOS で input にフォーカスするのはユーザーによるアクションと同期していなければいけない
    • ロングタップの処理で setTimeout していたのでダメだった
    • touchend 時にフォーカスすることにした
  • spawn したサーバーを終了しても子プロセスが残る

一言

Durable Objects かなり遊べるよ!

負債展

技術的負債、色々あると思ったので並べてみた。

  • スピード重視負債「ビジネスが軌道に乗るまではスピード重視ね」
  • ビジネス要求の変化負債「頑張ってやってもらったけど、その機能もう要らないわ」
  • マーケティングの失敗負債「鳴り物入りでリリースしたのに誰も使ってくれない...」
  • 要求分析失敗負債「この機能欲しいって言ってたよね...違うの...」
  • 重要顧客負債「どうしても必要な機能だと言うので if 分岐で対応しますね...」
  • 法改正負債「この実装だと今後はダメだって...」
  • 上司の無理解負債「リファクタリングの時間が全然取れないんだけど...」
  • コーディング能力不足負債「なんで動いてるのか分からないけどヨシ...」
  • 技術知識不足負債「そんな綺麗なやり方あったの知らなかった...」
  • 怠惰負債「本当は共通化した方がいいけど、めんどくさいからやめた」
  • TODO 負債「// TODO: あとで時間がある時に直す」
  • 設計負債「この API で実現するの不可能じゃね?いや、ここにフィールド生やせば...」
  • 野心負債「この設計はグローバル展開を見据えたもので...」
  • YAGNI すぎ負債「このコード、後先のこと何も考えてないでしょ...」
  • 中途半端負債「最低限動くところまでは終わったので、残りは Issue に積んで完了にします」
  • 無茶な納期負債「これ1週間でやるのマジ...」
  • 無茶なデザイン負債「この UI を実現するには...専用の API 生やさないと」
  • 人間関係負債「あの部分の担当者、話しかけにくくて...」
  • マイナー機能負債「誰が使ってるか分からないけど...とりあえず残しておいて」
  • プロトタイプ負債「そこは当時 UI でなんとかしてた時の名残りです...」
  • 流行負債「当時はそのフレームワークが流行ってて...」
  • 言語機能負債「当時はその書き方しか出来なくて...」
  • 文化継承負債「意味があるのか分からないけど、昔からこのやり方なので...」
  • 退職負債「あの人が辞めたので分かる人が誰もいません...」

謎ツール

今の会社に入ってから作ったものを思い出してみた(順不同)

  • Slack の新規チャンネルを通知するチャンネル
  • 社員の休暇予定を Google カレンダーに同期したり、今日休みの人を Slack に投稿したりするツール
  • ユーザーストーリーマッピングを管理する Web アプリ(引退)
  • 同僚氏の DSLシンタックスハイライトする VSCode 拡張
  • CircleCI の workflow のボトルネックになっている job を可視化するツール
  • ドキュメントのスクリーンショットが古くないかチェックするツール
  • Elm の UI ドキュメント的なやつ
  • フロントエンドの生成物に feature flag とか SVG パスとか言語とかを色々埋め込むビルドツール
  • フロントエンドのファイルサイズ増減を PullRequest に報告するツール
  • フロントエンドが API を正しく叩いているかをチェックするテストフレームワーク
  • Open API 定義からテスト補助モジュールを自動生成するツール
  • API を動的に生やして、そこに対するリクエストを記録する Web アプリ(テスト用)
  • UI デザインの更新差分を Slack に通知するツール(引退)
  • 深夜に Slack や Twitter にアクセスしないようにするツール(引退)

結構、面白おかしくやってた。

自分のやりたいことを正確に把握するのは難しい

キャリアの話になると、学生に向けて「自分の本当にやりたいことに向き合いましょう」というメッセージが発せられることが多い。それに対する学生側のよくある悩みは「やりたいことが見つかりません」「やりたいことがよく分かりません」というものだ。その様子を見ていて「やりたいことなんて考えなくても常にあるし簡単じゃん」と思ってたんだけど、最近は「本当にやりたいことを把握するのはめちゃくちゃ難しい」と思っている。

以下は個人的な話で、キャリアというよりは趣味レベルの話だけど、それでも難しいと感じている。

他人のやりたいことの影響を受ける

例えば SNS に、個人で Web サービスを作っている人とか、 OSS に貢献している人とかがいると、なんとなく自分もそれがやりたいような気持ちになってしまう。実際に Web サービスを作り始めてみると、ある程度動くようになったところでふと「よく考えたら別に Web サービスを運営したいわけでもないしお金を稼ぐモチベーションもないな」と気づいてしまう。

OSS も自作ライブラリは作るけど、他人のライブラリを一緒に良くしていこうとはあまり思わないので「貢献する」というモチベーションがない。パッケージとして公開するけど、ユーザーから issue や PR が来ると面倒臭いので最近は公開したくないほどだ。

フォローしている人の中には自作のプログラミング言語を作る人も多い。自分も面白そうだなと思ってちょっと作ってみて、ある程度動いたところで「こんなもんか」と思って満足した。でも好きな人は理論を体系的に学んでいるし、何より継続している。やはり冷静になると彼等ほどは好きでないことに気づいてしまう。

仕事の影響を受ける

仕事でなんらかのテーマを扱っていたとして、それが趣味に波及するパターン。例えば、 Swagger が面倒臭いからシュッとしたツールが欲しいみたいな場合に、プライベートでもそういうライブラリを作ったり色々調べ物をしていたりする。でも良く考えたら Swagger はほとんど仕事でしか使わないので、それに時間をかけるのは勿体無い。

あとは繁盛しているサービスでもないのに無駄にスケーラブルなアーキテクチャを考えてしまったりする。もっと手軽な方法もあるが、仕事と全然違うことをすると仕事に活かせなくてコスパが悪いと考えてしまう。

スキルの影響を受ける

仕事で使っているのが Web 関連の技術なので、なんとなく新しいアプリを作り始めると Web アプリになる。本当は VST プラグインが作りたいが C++ が分からないので WebAudio API で作ろうとしたり、その延長で OAuth とか SNS でシェアする機能とかを付けたりしてしまう。しかし WebAudio API をいくらこねくり回しても VST プラグインは作れない。

流行りに乗ってしまう

「これからはこの技術が来る!」とか言われると、「今これに乗っておけば将来それが流行った時に楽できるのではないか」と思って飛びついてしまう。かつては Kubernates であり、今は Rust であったりする。アーリーアダプターはエコシステムが貧弱だろうがものすごい労力をかけて進んでいけるので「将来」を「現在」にすることができるが、ただ「楽したい」程度のモチベーションでは将来は永遠に将来のままだ。ぶっちゃけ既存の道具を使う方が楽。

それから、こういうフロンティアを目の前にすると「画期的なシステムを発明したら一発当てられるのでは?」などと妄想してしまう。それは数年かけて会社を作ってやるくらいの覚悟が必要な話なのだが、 PoC とか言ってそれっぽいものを作って時間を無駄にしたりする。そりゃ PoC は簡単だよ。

ただ手を動かすのが楽しいだけ

プログラミングは楽しいから、ただ手を動かして何かを作って満足したいだけという場合がある。そう自覚しているのならそれでいいのだが、実際には「なんらかの目的のため」にやっていると思い込んで手を動かしていることが多く、ある程度手を動かして満足すると「実はそんな目的はなかったな、俺は一体なんのために...」となったりする。もちろんその過程で得られるものは多いので全く無駄な時間ではないのだが、何かもっと別の目的があるのであれば遠回りだ。

興味の対象が移り変わる

何かに夢中になっていたら、その過程で別の何かを発見してそちらに興味が移るパターン。例えば WebAudio API を触っていたら限界が見えてきたので wasm をやり始めて、 wasm をやっているうちに自作言語を作りたくなったりする。

連想式にやりたいことが増えていくので、優先順位をつけるべきだ。オーディオをやりたいのなら自作言語は我慢して適当な言語を wasm にコンパイルするのが一番早いはずだ。ただ問題は「やりたい順」に優先順位をつけると、その時点では自作言語が一番やりたいことなのでトップに来てしまう。ここで「いや、言語は今は別にいい」と言えなければならない。

短期的な報酬を求めてしまう

これが一番本質的で、ぶっちゃけここまでの話をなくしてこれだけでも話が成り立つのだが、具体的な話があった方が面白いかと思ってここまでダラダラ書いた。

ここでの報酬はほぼ承認欲求のことで、要するに「 Twitter で活動報告するといいねを押してもらえること」だったり「仕事で同僚に褒めてもらえること」だ。まあそれが長期的な目標に向けた取り組みの一環だったりすれば良いのだが、そこで報酬を得るために何かして後に何も残らないのは虚しい。このエントリも正直いいねが欲しくて書いているのでまあ虚しいが、こういう積み重ねが将来的に転職やら何かしら良いことに繋がると信じて書いている。

これが行き過ぎると本当に一発芸しかできなくなる危惧がある。あれをするのもこれをするのも、結局は SNS にアップするためなのだ...そして小さな満足を繰り返して消費される人生。

成果が出ないと面白くなくなる

上と言っていることは同じなのだが、長期的な報酬を得る前のフェーズでコストに対するメリットが伸び悩むと「これは自分のやりたいことじゃなかったな」と思ってしまう。途中で投げ出したことを認めないための防衛本能でもある。 ここまで書いてきたのは「大してやりたくないことに時間をかけてしまう」ことだが、下手をすると「実は本当にやりたかったことだが努力したくないのでやりたくなかったことにした」だけという可能性もある。

今まで「大してやりたかったことじゃなかった」と言って切り捨ててきたものの中に、実は本当にやりたかったことがあっただろうか?と自問するのが良いかもしれない。まあキャリアレベルの話をすると人生やり直さないと無理というのはあるだろうけど。

どうすればいいのか

今まで「気の向くままに色々やってきたが実はあんまりやりたくもないことに時間を割いていた」という話が多かったが、では自分の本当にやりたいことに時間を割くにはどうすればいいのか。ここまで書いてきてなんだが、実はまだ答えが出ていない。おそらく「他人は他人だから振り回されない」のも一つの答えだし、「自分のルーツを探る」も一つの答えだと思う。あるいは「そんなに難しく考えなくても総合的に自分の最もパフォーマンスの出るやり方を本能が選んでいるのだ」と楽観的に考えていいのかもしれない。あと、旅に出るとか。

ただ、一つ確かなのは冒頭の「自分の本当にやりたいことに向き合いましょう」はそんなに簡単ではないということ。 この言葉を聞いて勘違いしていたのは「やりたいことがあっても周りがそうさせてくれないから必死に抵抗しろ」というメッセージだと思ってたんだけど、実際には周りに何をされなくても自分自身をうまくコントロールできないということだった。

ああ、朝がきた。