Contributing to Complex Projects

Mitchell Hashimoto

複雑なプロジェクトへの貢献

原文は Mitchell Hashimoto により に公開されました。 このブログを購読する

オープンソースのメンテナー兼コントリビューターとして活動していると、よくこう聞かれます。どこから手をつければいいのか? 意味のある変更を加えるために、新しいプロジェクトにどうアプローチすればいいのか? 複雑なプロジェクトの内部など、いったいどうやって理解できるのか? と。

これらの問いは、オープンソースかプロプライエタリか、趣味か仕事かを問わず、あらゆるソフトウェアプロジェクトに当てはまります。私のアプローチはどの場合でも同じです。ただし、仕事としての開発との決定的な違いは、助けてくれる意思のある——ときには助ける義務さえある——他のエンジニアに直接アクセスできるのに対し、オープンソースではほとんど独力でやらなければならないという点です。

私は複雑なプロジェクトに取り組む際に繰り返し使っているパターンを確立し、この記事でそれを紹介します。このパターンが誰にでも合うとは思っていませんが、複雑なプロジェクトを学び、貢献しようとする際の自信につながれば幸いです。

本記事でいう複雑なプロジェクトとは、実装が一見して簡単に理解できないあらゆるソフトウェアプロジェクトを指します。この定義は主観的なもので、ある人にとって複雑に見えるプロジェクトが別の人にはそう見えないこともありますし、その逆もあります。

ステップ1:ユーザーになる

どんなプロジェクトの内部を理解するにしても、第一歩はそのプロジェクトのユーザーになることです。エキスパートになる必要はありませんが、私自身のこのステップの卒業基準は、そのプロジェクトを使って何か実際に動くものを作ってみることであり、それが小さく単純なものであっても構いません。例えば、Zigプログラミング言語に貢献する前に、私はいくつか 実際に動く ライブラリを作りました。

ユーザーとしてプロジェクトを使うことで、そのプロジェクトが何をできるのかを幅広く理解できます。リファレンスドキュメントを読むことと、実際にプロジェクトを使ってみることの間には大きな隔たりがあり、おもちゃのようなプロジェクトを作ってみることは、理論的な理解と実践的な理解のギャップを埋める重要なステップです。

さらに、プロジェクトのイディオムを学び始めることになります。それは文化的な土台をなし、なぜプロジェクトがそのように動き、なぜそのような機能を持っているのかといった理解を導くものです。これは、プロジェクトに携わる他の人々への共感を育むのに役立つだけでなく、どのような変更がプロジェクトにとって適切で、どのような変更がそうでないかを判断する指針にもなるため重要です。

この段階でコミュニティに参加することも強くお勧めします。IRCやDiscordに参加したり、ローカルのミートアップに出席したり、トークを視聴したりしてみてください。しばらくの間は、話すよりも聞くことに時間を費やしましょう。ここでの目的は、共感を得て、プロジェクトがどのように動いているかを学ぶことです。私は、ただ他の人が学ぶ様子を見ているだけで、どれだけ多くのことを学べるかにいつも驚かされます。

ステップ2:プロジェクトをビルドする

プロジェクトのビルド方法を学び、動作するバイナリ(あるいはそれに相当するもの)を手に入れてください。ビルドシステムや依存関係などを理解しようとする必要はありません。ガイドやウェブサイトなど、手元にあるものをただ真似して、ソースコードから実行可能なバイナリへと、自分の環境で確実かつ繰り返し変換できるようにするだけで十分です。

プロジェクトのビルド方法を学ぶ前にコードを読まないでください。ビルド方法を覚える前にプロジェクトのソースコードを理解しようとして、立ち往生している人をよく見かけます。私にとって、学習プロセスの一部は実験して壊してみることですが、ビルドできなければソフトウェアプロジェクトを実験して壊すことは困難です。

機能的に完全なビルドにこだわる必要はありません。複雑なプロジェクトでは、適切な依存関係、適切なシステム、適切な設定などが揃って初めて利用可能になる機能がよくあります。そうした場合は、そのことを気にする必要はありません。目標は、自分のシステムで十分に動くバイナリを手に入れることです。後のステップに進むにつれて、経験と自信がつき、より完全な機能を備えたビルドを追求できるようになります。

このステップでは、テストスイートの実行方法を学び、それをパスさせることもお勧めします。そうしておくと、後のステップで実験して壊してみることが容易になります。複雑なプロジェクトではテストスイート自体も複雑なことが多いので、場合によってはテストスイートの一部だけを動かせれば十分です——実験に足る範囲で構いません。

ステップ3:ホットパスの内部を学ぶ

内部を学ぶために、私は「trace down, learn up(下へ辿り、上へ学ぶ)」と呼んでいるアプローチを好んで使います。

Trace Down(下へ辿る)

私は一つの機能やユースケースから始め、外側から内側へと、その機能が辿るコードパスを追跡していきます。この過程では、通過したファイル、行、関数についてメモを取りますが、まだそれぞれがどのように動くのかを理解しようとはしません。これが「trace down」のフェーズです。

例えば、Zigコンパイラを学んだときは、Zigのソースコードから実行ファイルをビルドするzig build-exeコマンドを追跡することから始めました。この追跡の過程で、zig CLIのソース、build-exeサブコマンド、そして「Compilation」サブシステムへと辿り着き、そこからlexerやparserなどが呼び出されていることがわかりました。経路を辿るのに必要な範囲を超えて、実装の詳細を読み込むことはしませんでした。

この追跡メモから、通常、その機能がどのように動いているのかという全体像をつかむことができます。ファイル名や関数名などに基づいて、プロジェクトの主要なサブシステムを識別し始められるようになります。これにより、後の学習プロセスをより扱いやすい大きさの塊に分割することができます。

すべてを学ぼうとしないでください。よく見かける失敗は、プロジェクト全体を一行ずつ読もうとして、何週間も何ヶ月も迷子になり、最終的に挫折してしまうことです。焦点を絞り、機能ごとに学んでいきましょう。

ヒント:機能を選ぶときは、ユーザーとして親しみのあるものを選びましょう。また、可能であれば、表面的にはシンプルに見える機能を選んでみてください。例えば、コンパイラを学ぶために私が最初に追跡しようとしたZigプログラムは、2つの数値を足し合わせるだけで、何も出力しないものでした。

Learn Up(上へ学ぶ)

一つの機能を追跡し終えたら、実際にマッピングした各サブシステムがどのように動くのかを学ぶ番です。追跡フェーズではCLIやAPI呼び出しといった最も外側の地点から始めますが、学習フェーズでは最も内側の地点から始めることが多いです。

最も内側から始めるのは、そこが通常、最も基礎的で抽象化の度合いが最も低いからです。レイヤーを上がっていくにつれて、抽象化の度合いは高くなる傾向があり、構成要素を理解していなければ学ぶのが難しくなります。

特定のサブシステムを学び始めるときも、再帰的に「trace down, learn up」を適用します。まず公開された、エクスポートされているAPIサーフェスを調べ、次に各API呼び出しがどのように動くのかを学びます。上位のレイヤーはこの方法でサブシステムを利用するので、これが学習の指針になるだけでなく、スタックを上がっていくにつれて理解がより明確になります。

実験して壊してみる

「trace down, learn up」の過程では、実験して壊してみることが、仕組みを学ぶ上で非常に役立つと感じています。だからこそ、内部を読もうとする前にプロジェクトのビルド方法を学んでおくことが極めて重要なのです。

新しいログ出力を追加したり、ごく小さな新機能を実装したり、既存の機能を変更したりしてから、プロジェクトを再ビルドして何が起こるか見てみましょう。これは、自分が仕組みを本当に理解できているかを試す良い方法でもあります。

例えば、Zigのトークナイザーを学んでいたとき、私は新しいトークンを追加して、それらがトークナイズされることは確認しましたが、その後パーサーが失敗するのを見ました。次のシステム(パーサー)に進んだときには、その新しいトークンに何らかの動作を持たせてみました。といった具合です。

メディアで補完する

このフェーズを通じて、細部にわたるコードの探索を、利用可能なあらゆるメディアで補完しましょう。書籍、動画、ブログ記事などです。内部構造を扱った文献がすでに存在するなら、ぜひ読んでください!

ただし、これらの資料だけでエキスパートになれると期待すべきではありません。ステップ1の「ユーザーになる」と同様に、「メンテナーになる」ことを目指すとき、実際に手を動かしてソースコードをいじってみることに代わるものはありません。これもまた、理論と実践の違いの一例です。

ヒント:内部を学ぶためのリソースが存在しない場合は、自分で書いてみてください!私もZigでそうしました。最新の同様のリソースが見つからなかったので、Zigコンパイラの内部構造について書きました。何かについて書くことは、自分の学びを定着させる良い方法であり、将来のコントリビューターの助けにもなります。

ステップ4:最近のコミットを読んで再実装する

内部を学ぶ最後のステップとして、学んだサブシステムに関連する最近のコミットを読み、なぜその変更が行われたのかを完全に理解できているか試します。これは学習における「教科書の巻末問題を解く」段階です。

プロジェクト全体のコミット履歴か、学んだサブシステムに関連する特定のファイルやフォルダのコミット履歴のどちらかを見ます。そして、まず解決策(コミットでの変更点)をじっくり見る、あるいは修正されたバグを確認して自分で修正を試み、同様の解決策にたどり着けるか確かめます。

「問題を解く」ために、対象のコミットの直前のコミットでリポジトリをチェックアウトします。修正されたバグを再現し(バグ修正の場合)、自分で解決策を実装してみます。最後に、自分の作業をメンテナーやコントリビューターによるコミットと比較します。

自分に与えるヒントは、必要な変更の規模(VCSのdiffにおける+/-の行数)だけです。まずは変更行数が50〜100行を超えるような変更は避けることをお勧めします。

ステップ5:小さな変更を加える

私は小さく始めて、徐々により大きなタスクに取り組んでいくのが好きです。この段階では、プロジェクトの技術的な構成要素は理解できています。次は人間的な構成要素について学ぶ番です。ここでの目標は、小さな変更を加えて、コントリビューションとレビューのプロセスを学ぶことです。

最も難しいのは、たいてい小さな変更のネタを見つけることです。ここに特効薬はありません。私はissueを眺めて、コントリビューターが取り組みやすそうなものを探します。たいてい何度か見当違いのスタートを切ったり、issueを諦めて別のものに挑戦したりします。やがて一つ見つかります。issueを見つけるまで、あるいは修正するまでの時間は苛立つほど長くかかるかもしれませんが、それは参加するための代償です。プロジェクトには、新しいコントリビューターを導くための「contributor friendly」ラベルが用意されていることがよくあります。

最近のほとんどのプロジェクトでは、コントリビューションのプロセスがきちんと文書化されているので、変更を実装したら、そのプロセスに正確に従ってください。以前のステップでコミュニティに参加していれば、誰かに声をかけて助けを求めたり、プロセスをダブルチェックしてもらったりする良い機会です。

例として、Zigへの私の最初のコントリビューションは、3行の変更でしたが、2晩かけて4〜5時間ほどかかりました(以前のステップにかけた時間は含みません)。もしこのバグが今日表面化したなら数分で修正できますが、そのレベルの習熟に達するには時間がかかるのです。

成功

ここまでくれば、複雑なプロジェクトを学び、コントリビューションにも成功したことになります!

複雑さを恐れないでください。あまりにも多くのエンジニアが、プログラミング言語、ブラウザ、データベースといった、典型的に複雑とされるプロジェクトを、魔法のようなもの、あるいは選ばれた高尚な存在だけのものと見なしていると思います。私は、すべてのプロジェクトは他の人間によって始められたのだということを思い出すようにしています。彼らにできたのなら、私にもできる。そして、あなたにもできるはずです。

私のプロセスを共有することで、他の人々が複雑なプロジェクトをより身近に感じられるようになることを願っています。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント