効率と反復、そして計画づくり(1)

あるSF小説に次のようなフレーズがありました。

ロケットの燃焼効率は...

効率とは至る所で使われている言葉です。例えば私の車は月に一度リッターあたりの走行距離をスマホに通知してきます。燃料効率を定期的に教えてくれるのです。他にも「仕事の効率がいい」だとか「工場の稼働効率」など、多くの場面で使用されます。効率とは一定の”なにか”に対して、生み出される”なにか”の割合のことみたいです(wikipedia)。

効率

私は昔あるプロジェクトの立ち上げにチームメンバー(エンジニア)として参加したことがあります。ビジネスサイドの人からプロジェクトの目的、提供したい価値、想定されるおおよその期間などの大枠を確認し、チームメンバーと一緒に目的を達成するためにはどのようなストーリーが必要かを考え、次のようなストーリーと計画を作りました。(当然ですが当時のストーリーや計画をそのまま見せるわけにはいかないので内容を変えています)

サービスが大きく3つのコンポーネントに分かれていたので、それぞれのコンポーネントごとに必要なストーリーをマッピングしたのです(色はコンポーネントを、数字は順序を表しています)。ストーリーは小さく、内容についてもメンバー間で認識齟齬がない状態にできました。一見良さそうに見えるストーリーですが、私は強い違和感を感じ始めていました。過去の経験からもっと良いストーリーと計画があるはずだと感じていたのです。

Source Lines Of Code

SLOC(Source Lines Of Code)はソフトウェアの規模を表す指標の一つです。コードの行数が多ければ大規模であり、少なければ小規模である、という非常にシンプルな指標です。このSLOCを用いると、ソフトウェア開発における効率を一つの側面から数値化できそうです。つまり、単位時間あたりのSLOCです。1時間あたりに書けるコード行数が多ければ多いほどコードの生産量が高く効率が良いと判断できそうです。

先程のストーリーは、暗黙的にもまさに単位時間あたりに書かれるコードの行数を最大化することを念頭に置いています。フロントエンドからデータベースまで、頭から順番にコードを書いていけば時間を有効に活用できるからです。コンポーネント間の行き来(フロントエンドからバックエンド、バックエンドからデータベース)も3回で済むため、作業のスイッチコストも低いはずです。単位時間あたりのコード行数が増えれば、効率は高まり、プロジェクトはうまくいくはず。そのように考えるかもしれません。

しかし私の経験では、これは大抵うまくいきません。

いくつか理由がありますが、その一つが「開発過程での経験による学習」を無視してしまっていることだと思います。「頭から順番にコードを書いていけば無駄がなく時間を有効に活用できる」ということは暗黙的な前提として「始める前から正しい設計やコードを考えきれている」ということになります。しかし現実のプロジェクトでは私の経験上事前に正しい設計やコードを全て洗い出すことはほぼ不可能です。2~3時間(時には数日)かけて設計を念入りに行ったとしても書き始めてみたらすぐに思っていたのと違うと気が付きます。想像していたよりも処理が複雑だったとか、レイヤーはこんなに必要ではなかった、外部サービスの呼び出しが一定確率で失敗することが分かった、レガシーな部分が一部残っていてそれが作成している機能に影響を与えていることが分かった、などなど。行動が結果を生み、想定と現実の乖離に気が付き始めます。

SLOCに対する効率を最大化することを前提としたストーリーとその計画はたちまち多くの「新しい発見」の波にさらされます。後から分かったことを念頭に再度ストーリーを洗い直し、バックエンドまで進んだけれどフロントエンドに戻ったり、「完了」したストーリーが実は不完全だったことに気が付きます。今度こそはと新しいストーリーとその計画で走り始めようとしますが、また発見の波に飲まれることになります。これがプロジェクトの期間中続きます。予定していたリリースはどんどんと変更されていき、今計画のどのあたりにいるのという問いに自信を持って答えられなくなっていきます。

それではどうしたら良いのでしょうか?なにか良い方法、良い考え方はないでしょうか?

反復

そもそも、「始める前からどうしたらうまくいくか分からない」という問題はソフトウェアの世界だけなのでしょうか?そのような問題が我々の日常で起こっているとしたら。

私は数年前に人生で初めて車を購入しました。子どもが生まれることを想定して、ファミリーカータイプを選びました。一般的な乗用車の大きさですが、軽自動車と比べると車幅は広く、久々の運転に緊張したことを覚えています。納車がちょうど引っ越しのタイミングと重なり、その車を始めての駐車場に停めようとしていました。駐車場を確認していたところ、あることに気が付きました。そこは近隣の家の関係で前向きで駐車する決まりになっていたのです。さらに駐車スペースも広いとは言えず、ファミリーカータイプの車を駐車するには明らかに難しそうでした。

なんとかその日は駐車スペースに車を入れることができましたが、曲がる角度の調整が非常に難しく隣の車に擦ってしまわないか大量の汗を流す羽目になりました。こんな恐怖を毎回抱きながら車を出し入れしたくはありません。車の出入りが少ない夜間にどうやったら駐車がうまくできるか試行錯誤することにしました。上の図の矢印のルート(駐車場の入口から自分の駐車スペースまで)を何度も行き来しているうちにあることに気が付きました。敷地内に入ってきた後、できるだけ右側を走行してから駐車スペースに入っていけば角度の調整がやりやすいことに気がついたのです。これで少しは楽になりましたが、どうしても車体の先頭との距離が掴みづらく、右側の車を傷つけてしまわないかまだ心配が残りました。さらに練習を続けるとまたあることに気が付きます。私の駐車スペースの後方だけ、マンションの階段部分になっており少しスペースが余っていました。このスペースを活用することはできないか?というアイデアを思いつき、試しに以下の図のように車を動かしてみたところ、とても余裕を持って駐車することができたのです。

そのルートはこうです。

  1. まず自分の駐車スペースを通り過ぎて斜めの状態にする。
  2. ハンドルを右に切り、バックで階段部分に車の後方を入れる。
  3. ハンドルを左に切り、正面の駐車スペースに入れる。

妻にもこのルートを教え、家族で安全に車を出し入れできるようになりました。

私は「狭い駐車スペースに前向きで駐車するうまいやり方が分からない」状態からその駐車場の特性を活かして安全に駐車する方法を発見し、問題を解決することができたのです。これと同じように、おそらく皆さんもこれまでの人生で(もしくは日常的に)「はじめからうまくいく方法がわからない」問題に対し、同じ様な方法をとって問題に対処しているのではないでしょうか?難しいゲームで初めてボスと対戦する、初めて音楽のレッスンを受ける、水泳を始める、などなど。どの問題においても、何度も挑戦することで習熟度を高め、乗り越えてきたはずです。

つまり我々は、はじめからうまくやる方法がわからない問題への対処として、意識的か無意識的かにかかわらず、反復を利用することがあります。繰り返すうちに「問題の構造」や「効果的な手段」を発見し学習することで、上手なやり方を身に着けているはずです。

これを先程のストーリーと計画に対して転用してみると、どんなことが考えられるでしょうか。

学習と活用の機会損失

先程のストーリーを見返してみると、反復が存在しないことがわかります。ストーリーは各コンポーネント内に存在しており、コンポーネントを横断していません。フロントエンドの実装を終えてから、バックエンドに移り、最後にデータベースの変更を行い、初めてリリースができるようになります。はじめから終わりまでの往復は存在せず、一度でやりきる計画になっています。

反復の欠如は、次の3つの問題を引き起こします。

一つ目は、開発過程における学習を活かしにくいという問題です。例えばバックエンドの処理を実装しているときにもっと良いアーキテクチャを思いつき、フロントエンドとの通信の方法を変えたくなったとします。でも、フロントエンドの実装は既に終わってしまっているので、今から変更するとなると実装してきたコードを大きく変える必要があるかもしれません。「完了」としたはずのストーリーを再び「TODO」に戻すことへの心理的抵抗も加わります。結果としてより良い方法を発見したとしても、今の実装のまま進むことを選択しがちです。学習を活かしたくても、活かしにくい進め方になっているのです。このような妥協を積み重ねていった先に、どのような結末が訪れるかは言うまでもないでしょう。

二つ目は、システム全体への洞察が計画の後半まで得られないという問題です。一度決めたアーキテクチャで頭から順番に実装をするので、システムの全体像をつかめるのが計画の最後の方になります。データがどのように流れるのか、どの処理がどれほど複雑になっているのか、どこがパフォーマンスのボトルネックになるのか、といったシステムに対する重要な洞察が計画の後半部分に集中します。もしくは全てのストーリーを完了させてからやってくるかもしれません。このタイミングで深い洞察を得られたとしても、改善できる余地は殆ど残っていません。

三つ目は、反復がないとそもそも得られない知見があるという問題です。コードへの理解は初めて書いた時点ではなく、使い続けることで徐々に育っていきます。例えばバックエンドの処理の一部を終わらせ、それを再びフロントエンドから眺めてみて初めて違和感に気がつく場合があります。「このエンドポイントなんか一貫性がないな」「JSONレスポンス大きすぎるな」など書いた時点では気がつけなかったことに、別の視点から気が付ける場合があります。この例ではフロントエンドとバックエンドを例にしましたが、一つのコンポーネント内部(クラス間や関数間)でも同じことが起こります。反復がない場合、Aを書いたら次はBに移ったっきりになるので、Bの実装をAから眺めてみる機会が存在しません。

SLOCベースで考えるならたしかに多くの行数を達成できているでしょう。しかし全てを終えたあと全体を見渡してみると、積み上げてきたコードのいくつかが間違った方向への投資だったと気がつくかもしれません。もしくは計画の最後の方でコンポーネント同士を繋げたら上手く機能しないことが判明するかもしれません。反復を無くし、SLOCへの効率を最大化しようとした結果、本当に必要だった学習とそれを活用する機会が失われてしまいます。

Gregor HohpeはAgile Is the Steering Wheel, Not the Gas Pedalで「アジャイルな手法とは車のステアリングであってアクセルペダルではない」と言っています。(ステアリング:車体の進行方向を変えるための装置の総称)ソフトウェア開発は直線上を走るレースと言うよりも、障害物レースであり、旋回能力が重要であると説いているのです。

つまり、無闇にSLOCの効率を最大化しようとすることは、旋回に必要な学習の機会を失ったまま猛スピードで走り続けることと同義なのです。気がついたときには壁に激突しているかもしれません。

学習とそれを活用する機会の創出はどうすればいいのでしょうか。この問いに対する私なりの考えは「縦ではなく横」で考えること、です。

縦と横

一つのストーリーをフロントエンド、バックエンド、データベースを横断するような形にします。各コンポーネントを薄くスライスするようなイメージです。それぞれのストーリーでは各コンポーネントの実装が完了しませんが、ユーザが操作できる何らかの機能を完成させるようにします。そのため、受け入れテストやデプロイメントパイプラインの実行、リリースを各ストーリーごとに行うことができます。リリースできたら再度フロントエンドからはじめ、この反復を繰り返していきます。各コンポーネントは概ね10%、20%、30%と段階的に実装されていきます。

反復があることで先程の問題、(1)開発過程における学習の活かしにくさ、(2)洞察が計画の後半に集中すること、(3)得られない知見があること、の3つに対して対処しやすくなります。途中で良いアイデアを思いついたなら、完了したストーリーをTODOに戻すのではなく、次のストーリーで試すという選択が取りやすくなります。各コンポーネントを横断することで、早い段階からシステムの全体像が明確になりやすく、重要な洞察を早期に得られる機会も増えるはずです。さらに、書かれたコードを使ったり読み直すことで、一度書いただけでは得られなかった知見を得られる機会も増えます。

例えば、2つ目のストーリーでフロントエンドの処理を書いているとしましょう。バックエンドに対する呼び出しを行おうとしたところ、1つ目のストーリーで実装したエンドポイントに一貫性がないことに気が付きます。この場合、以降のストーリーでバックエンドに触れる際に修正することができます。ストーリーを進めながら学習した内容を活かす機会が生まれやすくなるのです。

学習した内容を活かす機会の増加は、システムの成長方向を調整する機会の増加へと繋がります。早い段階からすべてのコンポーネントに触れており、リリースしながら進めているため「実はこのデータは利用できない」「本番環境では外部APIとの通信に時間がかかる」というような、重要な洞察に早期に気づける可能性が増えます。壁に激突する直前でこれらのことに気がつくよりも、遠くに壁があるのでこのまま直線で進むとまずい、と早めに察知できるほうが問題に対処しやすくなるでしょう。

「アウトプット」と「アウトカム」で考えてみると、「縦と横」の違いが明確になるかもしれません。縦(コンポーネント内部に閉じる)で開発を行えば横(コンポーネントを横断する)よりもアウトプットの効率は高まります。単位時間あたりにまとめて沢山のコードを書きやすくなるからです。しかし、アジャイルな開発において重視するべきはアウトプットだけではありません。アウトプットによって生じた変化、アウトカムにも同様に(もしくはそれ以上に)着目するべきです。コードを書いたり読んだり、コンポーネント間をつなげたり、リリースしたりすることによって我々の理解は変化するはずです。(おそらくステークホルダーにも変化があるはずです。この話はまた別の機会に)この理解の変化を捉え、次のストーリー、イテレーションに活かしていけば「障害物レース」をうまく突破できるでしょう。反復的な開発はアウトプットだけではなくアウトカムに着目する機会を増やすことでステアリングの能力を得ようとしているのです。

よって、アジャイルな開発における効率とはSLOCではなく、学習と調整の量です。コードをどれだけ大量に生産できるか?ではなく、何を学び進行方向をどの程度頻繁に調整できるか?が問われます。(そしてその先に、価値のあるソフトウェアを提供できているか?という問いが続きます)

不確実性

反復的な開発は、他のエンジニアたちの言葉と自然と重なります。Andrew Hunt, David Thomasは達人プログラマーで閃光弾という比喩を用いて最初から全レイヤーを薄く貫通させる実装をすることを提案しています。言葉は違いますが、これはまさに「縦と横」の概念と一致するものです。さらに、Alistair Cockburn(アジャイルマニフェストの署名者の一人)はWalking Skeletonというアーキテクチャの全レイヤーを貫通する、最小限の実装を行うことを提案しています。こちらも反復や閃光弾と本質的な考えが一致しています。

反復も、閃光弾も、Walking Skeletonも、表現が異なりますが本質は同じです。「不確実なものは着手前ではなく、アウトプットによって生じたアウトカムによって明らかになる」ということです。この記事では主に開発者の理解の側面に焦点を当てましたが、不確実性は他にもあります。ユーザのニーズ、組織や市場の変化など、ソフトウェア開発を取り巻く環境というのは程度の差こそあれ多くの不確実を内包しているはずです。不確実性を否定するのではなく、むしろ不確実性をうまく利用し「障害物レース」を乗り越える手段の一つが、反復や閃光弾、Walking Skeletonだと思います。

計画づくり

「縦ではなく横」というのは少し抽象度の高い理想的な考え方です。現実のプロジェクトでこんな綺麗にストーリーを並べられるのでしょうか?常にスライスできるのでしょうか?どうやって横にすればいいのでしょうか?

これら問いに対する私の考えはまた次の記事で書こうと思います。

参考

  1. Agile Is the Steering Wheel, Not the Gas Pedal
  2. 達人プログラマー
  3. Walking Skeleton

正解にしていく、ということについて

ヒコロヒーのポッドキャストを聞いていて、とても印象深いエピソードがありました。

#73 人生の選択、幸せな選択 - 岩場の女(ヒコロヒー) | Podcast on Spotify

人生の選択に迫られたときにどうすればいいのかわからない、選択をした後に正しい道なのか悩む、後悔してしまう。そんな質問にヒコロヒーが答えていました。その時私は家事をしなが聞き流していたんですが、そういえば同じことを別の人から聞いたことを思い出しました。確か「意思決定したものを正解にしてくために、頑張ることが大切」みたいな感じだったと思います。当時(多分3年前)これを聞いたときにはなんのことかさっぱりわかりませんでした。だって、どっちを選べばいいか迷っていることに変わりはないじゃんって。

ポッドキャストを聞く少し前に私は履歴書や経歴書を書きました。社会人になった頃から今までなのでおおよそ8、9年とかの年月になります。できるだけ正確に記憶を掘り起こし、どんなことをやってきたか、どんな課題をどう解決していったか、自分なりにどう乗り越えたのか、それらを順番に整理していました。何年も前のプロジェクトのことを思い出す機会なんてそうないので、すごく懐かしい気持ちになります。そして、ふと今までやってきた仕事でやらなければよかった、無駄だったと思うものがないことに気が付きました。それで、ようやく意思決定したことを正解にしていくという言葉の意味が理解できました。

変化し続ける自分

経歴書を眺めて何年も前のことを思い出してみると、あのときの判断は今だったらこうするとか、あのときはこう感じたけれど今では全く別の感じ方になるとか、色々と自身の変化を感じます。あまりにも考え方、価値観が変わっているので、全く別の人間がある期間ごとに入れ替わっていると思うこともできそうでした。この、全く別の人間という捉え方は当然比喩なのですが、あながち間違っていないのではないかと思います。

私は今まで選択をする前と後ですごく悩み、メンタルが落ちてしまうタイプでした。後悔したくないという思いや不安感が強く、選択をした数年後を思い浮かべてどっちのほうが良いのか比べることを頭の中でぐるぐると思い浮かべていました。自分という存在はある地点に固定されていて、状況や環境が変化する、そんな見方をしていたんだろうと思います。

でも、今過去を振り返ってみると、悩んでいた時の自分と今では別の自分になってしまっていて、全く異なる考え方・価値観で過去を見ていることに気が付きます。良し悪しの判断基準自体が更新されており、過去の選択を後悔するというよりも「Aがいいか?Bがいいか?」という問い自体がリフレームされてしまっていて意味を持たなくなってしまったのです。つまり、自分というのはある地点に固定されておらず、出来事を線のように繋いでいきながら都度別の人間に変化し続ける存在なのではないかと思います。

過去の何かを選択したときの自分、その後なんとかしようと苦労していた自分、今振り返っている自分、未来の自分。それぞれの時期における課題、環境、技量、感情、考え方、価値観は異なっているはずです。それぞれ別の人間として捉えてみると、ある時点の迷いや問いかけが、別の時点の自分にとっては全く別の響き方に変わっているはずです。

点と線

年始にNetfilixで「ひゃくえむ。」を見ました。

映画『ひゃくえむ。』公式サイト | 9月19日(金)全国公開

その中で個人的に印象深いシーンがあります。ネタバレになってしまうので詳しくは書きませんが、主人公達4人がリレーを行うシーンです。主人公ともう1人はとても速く走ることができる一方で、残りの2人はそんなに速いわけではありませんでした。私が良いなと思ったのは、技量の差はあれど主人公を含めた4人が自分の区間を全力で走ろうと集中していることと、他のメンバーが各々の区間を精一杯走ってくれると信頼していることです。これがまさに、「意思決定したものを正解にしてく」ために必要なマインドセットなのではないかと思います。

自分が選択した道を走っているのは孤立した一人の人間ではなく複数の異なる価値観や考え方を持った別々の人間。各々がそれぞれの区間(期間)を走っている。過去の自分がバトン(経験・考え方など)をつなぎ、今の区間(現在)を通して未来の自分に渡していく。このように自分の過去と未来を捉えることができないでしょうか。

今の区間を走る自分からすると過去のランナーは未熟で失敗が多いように見えるかもしれません。周りが見えていなかったり、大きく転んでしまっていたり、体調が優れず速度が出せなかったかもしれません。それでも今の区間にバトンがあるのは過去の自分がバトンを届け続けたから。過去の自分は完璧ではなかったけれども、その時のベストを尽くしてくれたと認める。

一方、現在の自分がうまく走れているか不安になるかもしれません。走り出したことが正解なのかだったり、この道がどこに続くかを考えて足が止まってしまうことがあるかもしれません。でも、過去のランナーが精一杯走って繋いでくれたバトンを今の自分がどう活かすかに集中し、次の走者(未来の自分)に渡せば、その人がこれまでの行動と経験を糧として新しい人間に成長し、今を含めた過去を正解にするため全力で走ってくれると信頼する。

過去の自分を認め、受け取ったバトンを今の区間で最大限活用し、未来の自分を信頼して託すこと。この見方をすることができれば、選択とは「その瞬間の正解探し(点)」ではなく、時間の流れの中での「解釈のプロセス(線)」の始まりだと捉えることができるのではないかと思います。

今が過去を作る

その一方で、依然として私たちは「事実としての不正解」を突きつけられることがあります。評価が下がる、目標の未達成、試験に落ちた、など。事実は変えることができません。何を選択したか、どんな事が起こったのか、過去の出来事は固定されています。

私が高校受験をする時に当時の学年主任が昔のある生徒の話をしてくれました。その生徒は高校受験に失敗し、滑り止めの高校に行かなくてはいけなくなってしまったのです。他の生徒が第一希望に受かる中、一人だけ失敗してしまい大きなショックを受けていました。志望校に落ちた場合は滑り止めに入学する事を事前に両親と先生に約束していたそうですが、彼は来年受験し直すと言うことを聞かず、説得するのに大変だったそうです。最終的には彼が折れ、滑り止めの高校に入学する事になりました。

数年後、先生は偶然コンビニで高校を卒業したその生徒と再会したそうです。高校生活がどうだったか聞いたら、その生徒は「高校生活ですか?楽しかったですよ」と何事も無かったかのように答えたそうです。入学時は辛かったけれど、友人もたくさんできて、充実した学校生活を過ごせたそうです。当時、学年主任の先生はこれから受験する中学生の私たちに緊張をほぐすためにこの話をしたんだと思います。

でも、今はこの話が「今が過去を作っている」*1一つの例として見えるのです。

その生徒が受験に失敗し何もしなかったら失敗した事実だけが残り続けたはずです。でも彼はおそらく不安の中高校に通って、そして卒業し、振り返った時に楽しかったと思える過去を作った。つまり、「過去の出来事」は固定されていますが、「過去の解釈」というものは固定されていません。私たちはこれまで積み上げてきた経験や知識をもって過去を解釈し、その都度作り上げているのだと思います。

この立場に立ってみれば、我々は「事実としての不正解」を突きつけられたとしても、それを「解釈としての正解」にするため次の「事実としての正解」へと走り続けるができるはずです。今は正解だとは思えなくても未来の自分が「あの時失敗だと思ったけれども、実は正解にするための一つの出来事だった」と解釈できるように、今できることに集中する。そうすることが、過去と未来の走者(自分)に対する最大のリスペクトになるのではないかと思います。

あとがき

私の祖母は3年ほど前に他界しました。私は父から電話でそれを知りました。通話に出た直後父からの「今話せるか」と言う言葉を聞いたとき、祖母が亡くなったことを何故かすぐに悟りました。

祖母が亡くなったとき、家族は誰もその場にいることができませんでした。急死だったため、母も父も急いで病院に行きましたが、すでに息を引き取っていたそうです。当然私も最後の瞬間に立ち会うことは出来ませんでした。

私の両親は共働きのため、子供の頃はよく祖母が家に来て私と妹の世話をしてくれました。祖母は人の話をよく聞きますが、あまり自分の話をしませんでした。数少ない祖母の話の中で特に記憶に残っているものがあります。祖母が子供の頃は戦時中で、空襲警報が鳴り響く中、泣いて動けなくなった友人の手を引き家まで帰った話です。いつも落ち着いて見える祖母が、生きるか死ぬかの幼少期を過ごしてきたことに驚いたことを覚えています。

戦争を経験し、親となり、孫の世話をし、孤独に亡くなってしまった。亡くなる前の2年ほどはコロナ期間でなかなか会うことができず、最期の時も立ち会えなかったことを今でも後悔しています。

祖母が亡くなってからちょうど一年後に自分は親になりました。子どもを育てたことなんてないので、想像を絶するほど大変な日々が続いてます。子から強い風邪をもらったり、子どもがケガをして夜中に病院に急いだり、毎日が目まぐるしく感じます。自分が子どもの頃は分からなかったけど、育児の大変さを身をもって知り両親や祖父母に感謝する気持ちが湧いてきます。

しかし、そんな大変な日々でも、一日のなかで何度も子どもに愛されていると思う瞬間が増えてきています。できることが増えたり、感情を共有できたり、意思疎通できたり。自分は元々子供に興味がなかったのですが、実際に自分が親になってみると子の存在が自分に大きな影響を与えていることに気がつきました。親は子に愛されているとよく聞きますが、これは本当のようです。

それで、ふと、祖母は私の世話をしているとき大変だっただろうけれど、自分が考えているよりももしかしたら幸せだったのかもしれないと思うようになりました。私が子から愛されていると感じると同じように、祖母も私から愛されていると思う瞬間があったのではないかと思うのです。自分の子供を育てる経験を通じて、祖母の死に対する向き合い方が少しづつ変化し始めている気がします。

過去の出来事は変えられませんが、解釈を変えることはできるはずです。我々は誰かが支えてくれたから走り始めることができ、今の区間まで辿り着くことが出来たのだと思います。親、祖父母、友人、同僚など他者が支えてくれた事実を正解にするかは今の区間にいる自分たちで決められるはずです。そう考えるとやはり、今に集中することが過去と未来の自分を含め、支えてくれた人に対する最大のリスペクトになるのではないか思います。

軽いメモを書くつもりが、気がついたら長文になっていました。そういえば「ひゃくえむ。」の原作はまだ読んでいません。週末に買いに行こうと思います。

*1: 「言葉のズレと共感幻想」細谷 功 (著), 佐渡島 庸平 (著)

情報と解釈

生活環境は常に変化している。だから、その変化に適応していくことを考えた方が良い。

ある日、パートナーから言われた一言です。子どもが生まれ、育児に手惑い、社会人としてのキャリア的な成長がこれ以上見込めないのではないか、と私が弱音を吐いた内容に対する返事でした。落ち込み気味だった私の思考が浮上する感覚がありました。そうか、そういう考え方もあるんだな、と。

知っていることが多ければ良いという前提

そもそも、私がこれ以上成長できないと考えていた理由は育児に時間がかかるがゆえに勉強のための時間を確保できないからでした。私はソフトウェア開発者(プログラマー)で、継続的に技術力を身に着けていくことがこの業界では多少なりとも必要になります。秀でた能力を身に着けたいのであれば尚更です。勉強できる時間が大きく制限されるようになるほど、成長できないのではと無意識で前提を置いていました。

私が無意識で置いていた前提は、状況 に対して利用可能な 情報 を知っていればその場において有利に事を進められる、というものです。例えばJavaJSONを扱うJacksonライブラリの使い方を事前に知っていれば実際にJacksonを使う場面で役立てられます。実際、私が尊敬する先輩プログラマーは私には知らないことを駆使して様々な問題を解決していました。そのため、良いプログラマーになるためには如何に多くの情報を知っているかが重要である、と思うようになっていました。

いわゆる物知りになれればいいな、みたいな感じですね。なので、情報を集める時間が不足することに対してネガティブな印象が強かったし、これから先は努力をしても現状維持くらいの成長しか見込めない気がしていました。

パートナーからの言葉をきっかけに、このような前提を無意識的に持っていたことに気が付きました。同時に、もしかするとこの前提は間違いではないにせよ、少なくとも現状に合っていないのではないか?もっと違う、より今の状況にフィットした、より良い考え方があるのではないだろうか?と考えるきっかけになりました。そしてたまたまその時手元に細谷さんが書かれた「具体と抽象」という本が手元にあり、この本を読みながら自分の考え方の枠組みを捉え直してみよう、と思い立ったわけです。

情報には抽象度がある

具体と抽象は、私に新しい視点を与えてくれました。個々の事象を別のものとして捉えるのではなく、 解釈を通して構造を捉えることで、具象から抽象度の高い知識を得られる という考え方です。情報には抽象度があり、より高い抽象度であれば汎用的であり、より低い抽象度であれば具体的になります。この視点に立って物事を俯瞰して捉え直してみると、様々な経験からより多くを学べるのではないかと思えるようになったのです。

例えば、最近私の身におきたこととして「アジャイルなソフトウェア開発では、反復的な開発が重要だ」ということを、いくつもの経験を解釈してみることでより深く理解できました。アジャイルな開発ではソフトウェアを段階的に育てていきますが、うまく行っているプロジェクトとそうでないプロジェクトでは開発に反復的な考え方をあらゆるレベルで取り入れているかの違いがあることに気がついたのです。この記事では、このできごとについてこれ以上踏み込みませんが、重要なのは視点を変えることで、具象から抽象度の高い知識を得られたということです。これはつまり、日々の経験(具象)からより多くを学ぶことができるということでもあります。

抽象度は高ければ高いほど汎用的な知識になり、特定の状況に縛られることなく、様々な場面で利用する事ができます。私の体験談で出てきた「反復的な開発」という抽象度の高い知識は特定のプロジェクトに限ったものではありません。アジャイルな開発をする際にはいろいろな場面で活用できる汎用性の高い知識になっているはずです。個々の経験を解釈することで状況に依存しない知識を得ることができたのです。

この前提に立ってみると、知っていることが多いほど有利というのは必ずしも成り立たないことがわかります。知っていること(経験したこと)が多いだけでは状況の変化に耐えることが難しいからです。重要なのは何を知っているかではなく、知っていることや経験したことをどのように解釈し、 何を見出すことができたか ではないかと思います。自分なりに解釈をすることで考えを深め、より高い抽象度を持って物事を捉えることができれば変化の激しい時代においても知識や経験を積み上げていけるはずです。

この考え方はインターネットの記事や本を読むことで得られる情報に対しても同じことができると思います。ただ文字を追うだけではそれ以上の情報は手に入りません。著者はどんな視点を持って主張しているのか?背景にある考え化や価値、原則はなんだろうか?一歩踏み込んで考えることでより抽象度が高く汎用的な知識を手に入れることができるはずです。

情報から本質を抜き出し抽象化することで変化への備えになる

ソフトウェア開発の世界ではAIの登場など目まぐるしく状況が変化し続けています。今日役に立った知識や考え方は、明日になると別のなにかが登場し置き換わっているかもしれません。しかし、それは世の中の本質でもあるとも思います。誰も変化から逃れることはできません。私も個人が自由に使える時間の大幅な減少を経験しています。

この記事で私が書いたものは、いわば変化への備えなのかもしれません。状況ごとにゼロから知識を構築していくのではなく、特定の状況だけに縛られることのない抽象度の高い学びを得ることができればそれを積み上げていくことができるはずです。たとえプログラミング言語フレームワーク開発プロセス、人、会社が変わったとしても自分なりの解釈を通じて得た汎用性の高い学びは状況に縛られずに役に立つはずです。

パートナーの一言をきっかけに私の考え方は大きく変わりました。重要なのはどれだけ時間があるかではなく、時間をどのように使ったか、だと気がついたからです。何かを学ぶのに時間は多少なりとも必要です。しかし、その時間で何をしたか、どう考えたのかで学びの質は変化するはずです。(少なくとも私は変化を感じることができました)この体験談が他の人に役に立てれば幸いです。

Option型のmapとbindの使い分け

F#のOptionにはmapとbindの関数があります。この2つの関数は値がSomeの場合に、値を変換するという点では同じですが、変換に使用する関数の戻り値の型が異なります。

int option がある時、

  • map は int -> T の関数を受け取ります。
  • bind は int -> T option の関数を受け取ります。

※Tは任意の型です。

以下の例ではmapを使ってOptionの数値からOptionの文字列へ変換しています。mapはSomeをアンラップして値を取り出し、引数で受けた関数を使って値を変換し、結果を再びSomeでラップします。

let age = Some 25
let message = age |> Option.map (fun x -> sprintf "%i years old" x)
// Some(25 years old)

一方でbindはSomeをアンラップして値を取り出し、引数で受けた関数に渡して値を変換するまではmapと同じです。異なるのは、変換後の値がOptionであり、その値をそのまま返すという点です。mapのようにSomeで再びラップしません。以下の例もOptionの数値からOptionの文字列へ変換していますがmapの動作と異なり、引数がSomeの場合でも戻り値がNoneになっています。

let age = Some 25
let ageWhen1990 age = if age < 35 then None else Some (35 - age)
let message = age |> Option.bind ageWhen1990
// None

使い分けとしては、mapは変換に必ず成功する場合に使用すると良く、bindは変換に失敗する可能性がある時に使えば良さそうです。

Reactの状態管理ライブラリRecoilを使ってみる

フロントエンドは、よくVueを使っているプロジェクトにいることが多いのですが、ここ最近は個人の時間でReactを勉強していました。状態管理ライブラリを探していたところRecoilというものがあることを知り、少し触れてみました。

この記事ではAtomの使い方について、簡単に書いておこうかと思います。

https://recoiljs.org/docs/introduction/core-concepts#atoms

環境

viteを使いプロジェクトを作成します。React + TypeScript のテンプレートを指定しています。

npm create vite@latest recoil-my-playground -- --template react-ts

ディレクトリに移動して、Recoilをインストールします。

npm install recoil

package.json の中身は次のとおりです。これを書いている時点でのRecoilのバージョンは、0.7.7 でした。

{
  "name": "recoil-my-playground",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "recoil": "^0.7.7"
  },
  "devDependencies": {
    "@eslint/js": "^9.8.0",
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react": "^4.3.1",
    "eslint": "^9.8.0",
    "eslint-plugin-react-hooks": "^5.1.0-rc.0",
    "eslint-plugin-react-refresh": "^0.4.9",
    "globals": "^15.9.0",
    "typescript": "^5.5.3",
    "typescript-eslint": "^8.0.0",
    "vite": "^5.4.0"
  }
}

atom を定義する

atomは状態の最小限単位です(名前からも最小単位であることが想像しやすいですね)。Recoilではatomを定義し、Reactコンポーネントatomの値を読み込み/書き込みすることができるようになっています。

atomは次のように定義できます。

import { atom } from "recoil";

export const helloTypographyState = atom<string>({
  key: "helloTypographyState",
  default: "this is read from recoil atom",
});

ここでは、helloTypographyState を定義しました。このatomはstring型の値を保持し、デフォルトが this is read from recoil atomとしています。Reactコンポーネントはこの値にアクセスすることができます。

atom を読み込む

atomを読み込むにはuseRecoilValueを使います。useStateと同じ感じです。

上記のatomを読み込んで画面に表示するコンポーネントは次のように実装できます。

import { useRecoilValue } from "recoil";
import { helloTypographyState } from "../store/HelloTypographyState";

export function HelloTypography() {
  const helloTypography = useRecoilValue(helloTypographyState);
  return <h1>Hello, {helloTypography}</h1>;
}

atomを使うことでコンポーネントが表示する文字列を、コンポーネントから切り離して管理することができました。

atom を書き込む

atomを書き込むにはuseSetRecoilStateを使います。

次のButtonChangeHelloTypographyコンポーネントでは、先ほど定義した helloTypographyStateをChangeに変更します。

import { useSetRecoilState } from "recoil";
import { helloTypographyState } from "../store/HelloTypographyState";

export function ButtonChangeHelloTypography() {
  const setHelloTypography = useSetRecoilState(helloTypographyState);
  return <button onClick={() => setHelloTypography("Change")}>Change</button>;
}

これで、同じatomを共有する2つのReactコンポーネントを定義することができました。atomの値は変化が起きると値を読み込んでいるコンポーネントは最新の値で再レンダリングされる仕組みになっています。そのため、atomを書き込むコンポーネントatomの値をどのように変更すればよいかだけに注力すればよく、再レンダリングをしなければならないReactコンポーネントや、再レンダリングのタイミングはRecoilが管理してくれます。

「具体と抽象」を読んで思ったこと

先日、具体と抽象を買って読み始めたのだが、ふと技術本も抽象度が高い内容を扱っているものと具体的な内容を扱っているものに分けることができそうだと気がついた。

例えば、特定の言語にの使い方やフレームワークの解説は具体的。一方で、プログラミングスタイルやリファクタリングなどを扱うものは抽象的。

仮にこのように分類してみると、抽象度が高い内容を扱ったものであればいろいろな現場、プロジェクト、会社で役に立つし、具体的な内容を扱っているものは扱っている内容と同じ仕事をする時において特に効果を発揮すると言える。

Scala 3 でfor内包表記を使ったコードのメモ

scala 3 を使っていて、いい感じにコードを書くことができたので、ここにメモ。

CSVから値を読み込んで直積型に変換する処理を実装していた。

登場する型はRecipeとRecipeRecord。

CSVから読み込んだデータはRecipeRecordで表現されているので、それをRecipeに変換する。

case class RecipeRecord(name: String, description: String)

def convertToRecipe(recipeRecord: RecipeRecord): Either[String, Recipe] = for {
  _     <- validateRecipeName(recipeRecord.name)
  _     <- validateRecipeDescription(recipeRecord.description)
  recipe = Recipe(recipeRecord.name, recipeRecord.description)
} yield recipe

変換をする時に、バリデーションを行う。

バリデーションで失敗すると、EitherのLeft[String]が返るようにしている。一方で、バリデーションが成功すればRight[Unit]が返る。

これをfor内包表記で使うことで、一連の処理をつなげて書くことができた。for内包表記のおかげで短絡も実現できたので、バリデーションが失敗すると関数全体の戻り値はLeft[String]になる。バリデーションが成功すればRithg[Recipe]が返される。

クライアントコードは変換に成功すればRecipe型を、失敗すれば原因を表す文字列を受け取ることができる。