大規模な技術プロジェクトを進める私の方法
新しいプロジェクトをゼロから立ち上げるときも、大きな機能を実装するときも、大規模なリファクタリングに着手するときも、大きな技術プロジェクトへのモチベーションを保ち、完了させるのは難しいものです。私にとてもよく効いているのは、実際に動く成果を継続的に確認し、それを基準に作業の順番を決める方法です。
新しいプロジェクトを始めたときの、あのワクワクする感覚は誰もが経験しているでしょう。最初の数週間は、早くコンピューターに向かって作業したくてたまりません。ところが時間が経つにつれて、気が散ったり、言い訳を考えたりして、少しずつ取り組む時間が減っていきます。それが仕事なら、無理やりゴールまで這っていくことになりますが、毎日が苦痛です。趣味なら、何年も経ってから振り返り、「あれはどうなっていたかもしれない」と思い出すことになります。
大きなタスクを、目に見える形で前進を実感できる単位に分割すると、プロジェクトのあいだずっとワクワクした気持ちを保ちながら、仕事を終えられる傾向があると学びました。人によってモチベーションの源も行動力も違うので、あなたには合わないかもしれません。ただ、大まかな一般論として言えば、良いデモにワクワクしないエンジニアには、私はまだ出会ったことがありません。だから、常に自分に良いデモを見せることを目標にします。
この記事で話すことが目新しいと主張しているわけではありません。よく知られたソフトウェアエンジニアリングやマネジメントの手法と、さまざまな点で共通しています。ここでは、私が自分の大きな技術的な仕事にどう取り組んでいるか、そしてなぜその方法を取っているのかを共有するだけです。
この記事では、現実的で具体的な経験を共有できるよう、全体を通して私のターミナルエミュレーターのプロジェクトを例に使います。他にも使えたプロジェクトはたくさんありますが、これは仕事とは関係なく、最近のものなので記憶も新しいからです。
プロジェクトを完成させられない人を責めているわけではない、という点ははっきりさせておきたいと思います。楽しんでいて、達成感を得ているなら(あるいは、単に気にしていないなら)、それで十分です。あなたに幸あれ、そしてこれからも思う存分やってください。この記事は、もっとプロジェクトを完成させたい人、あるいは単に、私がどうやってプロジェクトをより多く完成させようとしているのかを知りたい人に向けたものです。
スタートライン
最初は、大きなプロジェクトが手元にあり、どう始めるかを考えなければなりません。私にとって、ここがいちばん難しいところです。適切な出発点をあれこれ悩んで、何時間も、ときには何日も費やすことがあります。
私のターミナルエミュレーターには、完成させるつもりなら必ず必要になると分かっていた大きな構成要素がいくつもありました。ターミナルのパース、シェルプロセスの起動と管理、フォントの描画、グリッドの描画、入力処理(キーボードやマウス)などです。「完成」までの道のりには、比較的大きなサブプロジェクトが何百個もあります。
もし最初の目標を「起動できて Neovim を動かせるターミナルを見ること」にしていたら、大変なことになっていたでしょう。未知の未知まで考えればなおさらです。この目標は、ただ大きすぎるように聞こえます。その道のりに、GUI の描画、プロセスの起動、ターミナルのパース、状態管理といった構成要素がたくさんあることは直感的に分かります。これは悪い目標です。大きすぎるので、1〜2か月もすれば興味を失ってしまうでしょう。
そうではなく、できるだけ早く結果を見られる現実的なプロジェクトとは何かを考えるようにしています。このフィルターを適用すると、実行可能なサブプロジェクトの数は大幅に絞られます。たとえば、次のようなものです。
- VT パース――ターミナルのエスケープシーケンスをパースする
- 空のウィンドウの描画――ウィンドウを開き、何もないキャンバスを描画する
- 子プロセスの起動――bash や zsh、fish のような子シェルを起動し、TTY を設定して、その出力(最初のシェルプロンプトなど)を読み取れるようにする
この段階で、大きなサブプロジェクトをすべて列挙しようとはしません。プロジェクトがどんな大まかな形になるかを何となく把握し、単独で構築でき、なおかつ何らかの実際の成果を目で確認できるものを一つ見つけるだけです。
この段階では、経験が最も大きな助けになります。経験豊富なエンジニアほど、プロジェクトがどんな大まかな形になるかを、より効果的に思い描けるものです。さまざまなサブコンポーネントをより正確に特定し、それらがどう組み合わさるかも理解できます。経験が少ない場合や、私が不慣れな分野の場合は、とりあえず最善の推測をして、いずれ作ったものを捨てる可能性が高いと覚悟します。
初期の成果
初期の作業は、目に見えないことが多く、そのため具体的な成果を確認するのが難しく感じられます。たとえばターミナルの VT パースに取り組むことにした場合、何らかの UI も接続しなければ、それが動いているところを見ることはできません。別のプロジェクトでデータベースのスキーマと最小限の API に取り組む場合も同じで、CLI や GUI といったクライアントを書かなければ、その成果を見ることはできません。
最初に選んだサブプロジェクトが UI なら、もちろんすぐに成果を確認できます。とはいえ、さまざまな理由から、私はフロントエンドから始めることはほとんどなく、通常はバックエンドから始めます。そしてどんな場合でも、いずれバックエンドにたどり着き、同じような難題に直面することになります。
この段階を乗り越えるための最良の道具は、自動テストです(この段階では通常、ユニットテストです)。自動テストを使えば、実際にコードを実行して動いていることを確認できますし、良い習慣でもあります。
ここから、最初のいくつかのタスクを選ぶ際の指針がもう一つ得られます。グラフィカルなものではないなら、あまり手間をかけずにテストできるものを選び、成果を確認できるようにするのです。
私のターミナルでは、まず VT パースから始めることにしました。当時の私はターミナルの一部である VT についてあまり詳しくありませんでしたが、とても簡単にテストできそうだと感じたからです。文字列としてサンプル入力を与え、パースされたアクションやイベントが出力されることを期待すればよいのです。
「テスト 1 件に合格」「テスト 4 件に合格」「テスト 13 件に合格」といった進展を見ていくのは、私にとって最高にワクワクします。自分が書いたコードを実行して、ちゃんと動いている。しかも、大きなプロジェクトにとって重要なサブコンポーネントの一つで前進していることが分かります。
デモへ向けてスプリントする
初期のサブプロジェクトで目指すのは、完成したサブコンポーネントを作ることではありません。十分なサブコンポーネントを作り、デモに至る道の次の作業へ進めるようにすることです。✨
この取捨選択は、機能だけに現れるものではありません。アルゴリズムや設計上の判断にも現れます。たとえば将来的には、本物のデータベースや凝ったデータ構造を使ったり、ストリーミングデータに対応したりする必要があると分かっているかもしれません。しかし最初の作業では、メモリ上のデータや、辞書のような組み込みのデータ構造を使い、すべての入出力を最初にまとめて受け取るだけで済ませられます。
これは重要な取捨選択だと思うので、繰り返します。完璧さを、前進の敵にしてはいけません。さらに言えば、将来必ず改善しなければならないと分かっていることを理由に、次の作業へ進むのを止めてはいけません。目標はデモにたどり着くことです。
何に取り組んでいるときでも、前のセクションで説明した自動テストのフィードバックを挟みながら、週に1〜2本のデモを作るようにしています。
デモを作ると、非常に貴重なプロダクトフィードバックも得られます。完全に機能していなくても、それが使っていて気持ちいいかどうかはすぐ直感的に分かります。これらは「実用最小限の製品(MVP)」ではありません。本当に実用になるわけではないからです。それでも、エンジニアにとって価値のある自己省察を促すには十分です。
ここは、経験がむしろ悪影響を及ぼすことがあると思う分野です。完璧なものを作ろうとして身動きが取れなくなり、ようやくデモにたどり着いたころには、ひどいものだと気づくシニアエンジニアを見てきました。実装がひどいのではありません。プロダクトや機能そのものが、本当にひどいのです。
ターミナルで最初に選んだ作業は VT パースだったことを思い出してください。初期段階で確認できたのは、自動テストが動くことだけでした。最初のデモにたどり着くため、私はコマンドを実行し、その出力を取得して VT パーサーに渡し、パースできたもの(またはできなかったもの)をすべて出力するシェルスクリプトを書きました。やがて、この CLI を最初の「UI」として改良していきました。ターミナルのグリッドを ASCII で描画するのです。
そのおかげで、man や ls のような単純なプログラムだけでなく、vim のような複雑なプログラムも実行し、パーサーが動くところを確認できました(壊れるところを見るのも、それはそれで同じくらいワクワクします)。
このケースでは、私が書いていた CLI は長期的にはほとんど役に立たず、結局かなり早い段階で捨てることになりました。それでも、デモとしてそれを作るのに費やした1〜2日は、進歩しているという大切な感覚を与えてくれました。何かが動くところを見ることが、モチベーションの維持に役立ったのです。
自分のために作る
このセクションは、仕事で割り当てられたプロジェクトよりも、個人プロジェクトに当てはまるでしょう。他の人に使ってもらうソフトウェアを公開したいと思っている場合でも、必要になったときに必要なものだけを作り、できるだけ早く自分で使い始めてください。
自分自身が抱えている問題に取り組むときのほうが、いつもモチベーションが高まります1。そして、自分のために設計したプロダクトが自分の役に立たないなら、他の人にもうまく機能しない可能性が高いでしょう。だから、デモから現実に使えるプロダクトへ進むときは、自分に必要だと思う機能だけを最短距離で作ることを目指します。
私のターミナルの場合、まず自分のシェル設定(fish)を読み込めるようにし、そこから Neovim を起動して使えるようにすることでした。そこで、そのために必要な機能だけに作業を集中させました。プログラムが使うエスケープシーケンスだけ、普段使っているフォントの描画だけ、といった具合です。最初は、スクロール、マウスによる選択、検索、タブや分割表示などの機能を省きました。
その後、自分のターミナルを日常的に使い始めました。この段階では、たいてい何度か出だしでつまずきます。省いたり忘れたりした機能が、実は必要だったと気づくのです。最初にターミナルを使ったときは、矢印キーを押しても何も起こらず、微妙ではあるもののワークフローを壊す描画バグもありました。そこで使うのをいったんやめることになりますが、次に取り組む具体的なタスクが得られます。
それに、自分が書いたコードで動いているソフトウェアを使うのは、いつも大きな誇りを感じます。それが、作業を続けるモチベーションの維持にもたいてい役立ちます。
まとめ
大きな問題を小さな問題に分解します。重要なのは、それぞれの小さな問題について、作業の成果を確認できる明確な方法があることです。
大きな問題のデモにつながる部分を前進させるのに十分なところまで小さな問題を解決したら、次の小さな問題へ進みます。
ソフトウェアを実行可能なデモとして作り始められるだけの小さな問題を解決したら、さらに機能を追加して反復を続けます。できるだけ頻繁にデモを作ります。
自分のソフトウェアを使い始められるようにする機能を優先します(個人プロジェクト、自分が実際に抱えている問題を解決する仕事のプロジェクトなど)。その後も、まず自分の問題を解決し続けます。
将来の改善に必要になったら各コンポーネントに戻って反復し、このプロセスを必要に応じて繰り返します。
結論
だいたい、これです。個人プロジェクト、チームプロジェクト、仕事のプロジェクト、学校のプロジェクトなどで、この一般的なパターンに従ってきました。これが、私がモチベーションを保つ方法です2。
ただ、ここでは触れていないこともたくさんあります。リリースについては話していません。リリースすることがモチベーションになる人が多いのは知っています。しかし、プロジェクトを成功させるためにリリースする必要はないと思います。私の場合、リリースはあまりにも大きなイベントなので、長期的なモチベーションにはなりません。ツールについても話していません(Git のワークフローや CI など)。複数の職場でこのプロセスを使ってきましたが、そのときどきの職場で確立されているプロセスに合わせてきました。ほかにもいろいろあります。
このことから、これがどれほど個人的なプロセスなのかが分かると思います。誰もが、健全な方法でモチベーションを支えてくれるプロセスを見つける必要があるのだと思います。私の場合、成果を見ることが非常に強いモチベーションになると分かりました。そこで、それを軸に仕事のスタイルを作り上げてきましたが、今のところうまくいっています。
脚注
記事をランダムに読む