こちらの記事は、John Sonmez 氏により2018年9月に公開された『 Master a New Codebase in Record Time 』の和訳です。
本記事は原著者から許可を得た上で記事を公開しています。
爆速で新しいコードベースをマスターする
君は新しいチームに入った。 プロジェクトのコードベースは巨大だ。 どこから手を付ければいいのかわからない。 君は馬鹿だと思われたくないし、チームメイトは皆、自分の仕事で忙しい。 さて、どうやったら、経験レベルに関係なく、全く初めてのコードベースで生産性を高めることができるものか?それも素早くだ。
これは開発者であれば誰もが通る道だ。 未知のコードベースを持つ既存のプロジェクトに飛び込むことは定期的に起こる。 転職したばかりなのか、同じ会社内でチームを切り替えただけなのかは関係なく、やらなければならないことは同じだ。ソースコードを読み、作業を始め、そして何も壊さないということである。
君はどうする?
新しいプロジェクトに参加するときに必要なことは、ソースコードを学ぶことであるということ以上に分かりきったことはないだろう。 どんなクラスが存在して、そのクラスが何をしていて、異なる機能や働きのコアロジックがどこにあるのか把握する必要があるのだ。
残念ながら、_コード以外_にも学ばならなければならないことがあるのだ。 開発の経験を積むにつれて、自分の仕事はソフトウェア開発だけではなく他にもたくさんあることを知るようになる。そしてソフトウェアにはコードだけでなくもっと多くの要素があるということも。
コードはソフトウェアがどのように書かれて実行されるかということはもちろん、暗黙的な前提条件とプロダクトが提供されるドメインに関する知識をも含有している。 君の仕事はそれをすべて学び理解することだ。
君はプロダクトのメンタルモデルを組み立てる必要がある。 そのためには、それについて多くのことを理解する必要があるが、その一部は最初のうちははっきりとは見えない。
メンタルモデルに必要なすべての情報を知るには、取り組もうとしているコードベースの経験が豊富な1人以上の開発者から学ぶのが一番いい方法だ。 ただし、そのような人は一人もチームに存在しないか全員忙しすぎるか分からないが、常に可能であるとは限らないのだ。
そこで、自力でコードベースを学ぶいくつかの方法をここで教えよう。
プロダクトのコンテキストはどうなっているか?
何よりもまず、君はプロダクト・サービスのコンテキストを理解する必要がある。
コンテキストとは、ユーザーや他製品・他サービスとの関連でそのソフトウェアがどのような位置づけにあるかということだ。 コンテキストの理解によって、君は状況をつかむことができるのだ。 コンテキストを理解することで、プロジェクトとプロジェクトで使用されるすべての外部サービスに関して、自分がどうすればよいのか分かるようになる。
一部のサービスは厳密に言うとAPIサービスであり、エンドユーザーはまったく使わない。 モバイルアプリのような他のサービスは、エンドユーザーによってダイレクトに使用される。 どちらの場合も、その製品はサードパーティのサービスを利用して、自身の機能の一部を提供するケースが多い。
プロジェクトのコンテキストを自力で判別するには、そのプロダクトが使用するライブラリ群、言いかえると依存パッケージを調べる必要があるだろう。 依存パッケージを見つける方法は、言語とビルドツールによって異なる。 各言語とビルドツールには依存パッケージを処理する独自の方法があるため、プロジェクトでプロダクトのビルドに使用しているツールチェーンを理解することが重要なのだ。
ツールチェインを理解するには、そのプロダクトをどうやってビルド・実行しているか理解する必要がある。 運が良ければ、ビルドプロセスを説明するドキュメントがいくらかあるだろう。
Ruby on Railsなどの一部のプロジェクトは規約ベースだ。つまり、すべてのRuby on Railsプロジェクトは同じ設計、同じディレクトリ構成、同じ基本的ツールチェーンに準拠している。 その結果、規約ベースのプロジェクトを実行させるのは、少し簡単になる。これまで君が一度も同じようなプロジェクトの経験が無くてもだ。
多くのJavaプロジェクトのように、プロジェクトがより設定ベースのプロジェクトである場合、タスクは少し複雑になる。 多くのJavaプロジェクトはApacheのMavenを使用しているが、古い場合はAntを使っているか、ややもすると新しい場合はGradleを使っているかもしれない。 見つけだすにはに探るしかない。
私が知る限りではビルドツールというのは、特定のファイルを使用してタスクを保存し、その他の特定のファイルを使用してプロジェクトの依存パッケージを保存している。 だから、私はまずそれのファイルに目をつける。
npmを使用するプロジェクトのpackage.jsonやAntベースのプロジェクトのbuild.xmlなど、ビルドファイルであることがわかっているすべてのファイルの最上位ディレクトリを調べるのが私のやり方だ。 それらのファイルが見つからない時は、見たことのないファイルや不明な形式のファイルを探す。 次に、それらのファイルについてググって、何が出てくるかを確認する。 十中八九、これによってビルドツールを見つけ出すことができる。
使用されているビルドツールがわかったら、そのツールのコマンドラインオプションを使用して、利用可能なすべてのタスクを確認できる。 情報をGoogleで調べるか、manページを使用するかがたいていの場合、使用可能なタスクを返すオプションを見つける最も簡単な方法だ。 たとえば、Rakeは-Tオプションを使用して利用可能なすべてのタスクを表示してくれる。
理想的には、タスクには、コードのビルドと実行に最適なタスクを示すための適切な名前が付けられる。 そうでない場合は、各タスクを実行して何が起こるかを確認すればよいのだ。
さて、君は今コードをビルドして実行できたと仮定しよう。(直接インタラクションできるプロダクトであれば)プロダクトを触ってみよう。 それが何をしてどのように機能するのかをただ理解したいだけの子供のようにそれを使ってみるのだ。 また、ユーザーのことを頭に入れて、ユーザーが様々な作業を行うのにそのプロダクトをどのように最初から使っていく必要があるかを確認しよう。 これらのアクションによって、プロダクトの機能と使用方法の概要がわかる。
また、テストコードがある場合は、私はこの時点でも実行するようにしている。 まず、テストすべて通過するとき、思わず頬が緩むような心地良さが得られる。 第二に、テストが失敗した場合、コードとテストにを調べてより多くを学ぶ機会が得られる。 第3に、テストコードを実行すると、単体テスト、機能テスト、統合テスト、受け入れテストなど、どの程度、そのコードがテストされていてどのようにテストされているかの全体的な理解が得られる。
実行中のプロダクトをいじることができたら、残りのコンテキストを把握していけばよい。
ここでの私が最初にやることは、コードの依存関係を調べることだ。 すなわち、プロジェクトのビルドと実行に使用されるライブラリのことだ。
私は依存関係ファイルを調べて、それぞれが何をしているのか明確に理解するように心がけている。 依存関係リストを確認するこの作業は、ソースコード中で使用されているサードパーティのソフトウェアを識別するのに役立つが、開発中のプロダクトのいくつかの特長や機能を見つけるのにも役立つ可能性があるのだ。
最近の仕事で、私は2つのRailsアプリの開発を引き継いだ。 どちらも約80%完了していたが、以前の開発者が退職したため、大半のコードを自力で理解しなければならなかった。 RailsアプリはGemfileを使用して依存関係を管理する。私はアプリが何を使用しているかを理解するためにはGemfileを調べる必要があるということを知っていたのだ。
Gemfileを調べて、見慣れないGemをそれぞれ調べたところ、いくつか興味深いことがわかった。 各アプリは認証を処理するために特定のgem(Devise)を使用し、どちらもMySQLではなくPostgreSQLを使って、1つのアプリはStripeを使用していて、そのアプリはOAuth機能も提供していた。 さらに、各アプリにはAWSのGemが存在しており、これは機能の一部にAWSを利用していることを示唆していた。 結局のところ、どちらもAWSのOpsWorksプラットフォームにデプロイ・ホスティングされているということがわかった。
2つの新しいプロジェクトでこのプロセスを実行した結果、[各アプリの機能](http://www. amazon. com/exec/obidos/Asin/0134494164/makithecompsi-20)について詳しく理解できた上に、プロジェクトと連携しているいくつかの外部システムについてより理解することができた。
プロダクトのアーキテクチャと設計はどうなっているか?
アーキテクチャと設計という用語は、しばしば同じ意味で使われる。 それらは関連しているものの、これらは実際には2つの異なる概念だ。 アーキテクチャはシステムの上位レベルの観点であって、設計は下位のクラスレベルの観点である。
プロジェクトのアーキテクチャは、プロジェクトの複数の「観点」で構成され、セキュリティ、パフォーマンス、変更可能性、テスト容易性、保守性、モジュール性、回復力、可用性、さらには市場投入までの時間といった非機能要件や品質属性を含有している。
対照的に、設計とはデザインパターンが現れ始めるところだ。 このレベルにおいて、さまざまなクラスがどのように相互に関係して連携しているかを理解する。
新しいコードベースを習得するためにどのようにこれら2つの概念を利用できるか詳しく見ていこう。
アーキテクチャを解き明かす
アーキテクチャは、大きなスケールでそのプロダクトの一般的なレイアウトを理解するのに役立つ。 コンパイル時(モジュール)観点、ランタイム(コンポーネントとコネクタ)観点、およびデプロイメント(割り当て)観点を確認することで、アーキテクチャを組み立てることができる。 これらの各観点は、システムのさまざまな品質を捉えており、組み合わせることでプロダクトのより完全な全体像を形成する。
コンパイル時の観点は、ほとんどの開発者がアーキテクチャとして考えるものだ。 それは統一モデリング言語(UML)クラス図に似ている。コードの書き込みまたは読み取り中にコードがどのように編成されるかを示す。 それは役割をモジュールにグループ化する。各モジュールは複数のクラスから構成されている。
ランタイムの観点は、システムの実行中にその一部をどのように理解できるかを教えてくれる。 ランタイムの相互作用をコンポーネントにグループ化し、それらのコンポーネントがコネクタとどのように相互作用すしているかを詳しく教えてくれる。 これらのコンポーネントはそのプロダクトがどのようにしてプロセスとスレッドを使用するかを教えてくれるが、それに限らない。
デプロイメントの観点は、そのプロダクトがサーバー(またはクライアントデバイス)に物理的にどのように配置されているか、およびそれらのサーバーがどのように関係しているかを教えてくれる。 これは、ソフトウェアが実行される物理環境を詳しく示している。
コードを見ただけでは、アーキテクチャを自分で明らかにするのは難しい場合があるだろう。 Railsのように、プロダクトが「設定より規約」を採用していればラッキーだ。すでに君のやるべき作業の多くは完了している。それはそういったプロダクトは同じ基本アーキテクチャに準拠しているからだ。
設定ベースのプロジェクトで作業している場合、アーキテクチャの解明はもっと大変だろう。というのもこれらのプロジェクトはアーキテクチャに関して制約を持たず、したがって一つ一つが異なるものになるからだ。 それらは元々それを開発した開発者の影響を完全に受けていて、アーキテクチャ決定の背後にある理由はほとんど文書化されていない。
規約と構成いずれの場合でも、大規模なプロジェクトは長期間にわたって進行し、さまざまな開発者が関与する。これらの開発者には、アーキテクチャの決定とプロジェクトの変更を適切なドキュメントに落とし込める者と落とし込めない者(こちらの方が可能性が高い)がいる。
残念ながら、既存のシステムでは、アーキテクチャの観点を解き明かすことは簡単ではない。 コードのコンパイル時の観点を解き明かすのが難しいのは、それが非常に高レベルなので、低レベルの関係がプロセスを複雑にするためだ。 この観点は、システムのクラスを抽象化し、パーツまたはモジュール全体がどのように連携するかを教えてくれる。 これらのモジュールを見つけることは難しい。というのもクラスがモジュール内にいくらか編成されている場合でも、それらの境界をモジュールレベルで維持することはめったにないからだ。
プロジェクトが進み、開発者が加入・脱退するにつれて、すべての人がアーキテクチャがどういうものであるか知っているとは限らないため、気付かないうちにアーキテクチャから外れたことをしてしまうかもしれない。 そのため、プロジェクトが進むにつれて、元のアーキテクチャは消えてしまう可能性がある。
パッケージと名前空間内のクラス編成を使用して、プロジェクトがどのようにアーキテクトされているか初めて理解することができるだろう。 この手法を、後で説明するUMLクラス図と組み合わせて使用すると、プロジェクトに何のモジュールが存在するかをよりよく理解できる。
ランタイムの観点はを解き明かすのはもう少し簡単だが、大抵はそれほど有用ではないものだ。 ps
のようなオペレーティングシステムツールと、スレッドを表示できるデバッガを組み合わせることで、それを明らかにすることは可能だ。 特定のスレッドで実行されているクラスまたはクラスのグループを確認すると、実行時にプロジェクトのどの部分が互いに通信しているかを確認するのに役立つ。 私が取り組んできたほとんどのプロジェクトでは、ランタイムの観点は、意図的なマルチスレッド化が行われている場合にのみ役立った。
RailsのCapistranoスクリプトなどのデプロイメントスクリプトを確認することで、そのプロダクトのデプロイメントの観点を解き明かすことができる。 AWSのOpsWorksなど、サービスがデプロイ先のシステムがわかっている場合は、そのシステムのサービスダッシュボードから、どのサーバーで何のアプリケーションが実行されているかを知ることもできるだろう。
設計を解き明かす
プロダクトの設計を解き明かすのはもう少し簡単だ。というのはクラスは設計を通じて構築され関連付けられるからだ。 プロジェクトが使用するデザインパターン、およびクラスが互いにどのように関連し、通信するかを明らかにするのに役立つツールが存在する。
プロジェクトのすべてまたは一部のUMLクラス図を生成するツールを使うのが一番簡単な方法だ。 クラス図に慣れていない場合は、クラスを表示してくれて(あたりまえかな?)、例えば、継承、構成といった関係を教えてくれる。 Eclipse、IntelliJ、Visual StudioなどのIDEは、UMLクラス図を生成したり、UMLクラス図を生成するプラグインをサポートしている。 スタンドアローンツールもある。 一覧をここでみることができる 。
明らかにしたコンパイル時のアーキテクチャモジュールと一緒に、生成されたクラス図を使うことによって、みつけたことを検証することができる場合がある。 疑わしいモジュール境界の周りにクラス図を生成して、メンタルモデルが当てはまるかどうかを確認できる。
UMLシーケンス図は、2番目に有用な図だ。 これは、クラス同士がどのようにどういう順序でどのように連携しているか教えてくれる。これは、システムを介したデータのフローを決定するのに有用だ。
前にも言ったと思うが、多くのIDEは直接またはプラグインを介してシーケンス図の構築をサポートしている。 しかし、私が使用したものはすべて、ダイアグラムを生成するためにはコードを実行してプロファイルを作成する必要があった。これにより、明示的かつ簡単に自分で実行できるコードパスの有用性が制限されてしまう。
JavaやC#などの静的型付き言語を使用している場合、EclipseやVisual StudioなどのIDEは、何が特定のメソッドを呼び出しているかを教えてくれる。 この機能を繰り返し使うことで、どのクラスがどのように通信するかを理解しやすくなる。 それから、興味のあるコードの部分に対して独自のシーケンス図を作成できる。
過去の仕事では、Javaに似た静的型付け言語であるAdobe Flex(AS3)を使用してチームにいました。 私は、プロジェクトが新しく大規模だったので、どのクラス同士が関連していて、それらがどのように配置されているかを視覚的に理解したかった。 ビジュアルパラダイム製品は、Flexに対応している数少ない製品の1つだった。 その製品を使用して、ソースコードをいくつかのかたまりに分けてシーケンス図を生成した。プロジェクト全体で実行するには大きすぎたためだ。
結果の図は、そのクラスが持つ関係の数によって、どのクラスが重要である可能性が高いかをすばやく確認するのに役立った。 残念なことに、プロジェクト全体をより含めようとするにつれて、ダイアグラムはあまり役に立たなくなっていった。 膨大な数の関係が、パターンの解明を困難にしだしたのだ。
とはいえ、コードのかたまりに対してシーケンス図を作成することは_非常に_重要であることがわかった。 けれども、自動生成されたシーケンス図は、手動で作成する場合ほど役には立たない。 多くの場合、私は他のシステムと通信しているコードのかたまりから手をつけて、プロジェクトとそれらの外部システムとの間の呼び出しのシーケンス図を作り上げるようにしている。
また、スレッド、その他のプロセス、またはワーカーを使用するプロジェクトの一部について、シーケンス図を作成したことがあるが、何が起こるかを理解する上で非常に有用であるとわかった。
ファイル配置はどうなっているか?
コードベースを学習する3番目のコツは、 ファイルはディレクトリ構造において_どこ_に配置されているかを知ることだ。
当然のことながら、Ruby on Railsのように、規約ベースのプロジェクトでは、すべて同じようなレイアウトに従っているため、場所の特定が少し簡単になる。 と言っても、君が何かする必要が全くないと言っているのではない。 lib
ディレクトリの中に何があるか見てほしい。また、ビューのファイルはどこに保存されているだろうか。 app
ディレクトリ下にはassets
、models
、controllers
、views
ディレクトリがあるはずだが、それら以外には何のディレクトリが存在するだろうか?
Javaのような言語で、ファイルとそれに関連するパッケージの物理的な場所に厳しい要件がある場合は、そのディレクトリ配置に慣れておくと、利用可能なパッケージを見つけるのに役立つ。
Rubyのようなパッケージからファイルへのロケーション規則がない言語の場合、使用されている名前空間を調べればよい。 私が取り組んできたRubyプロジェクトのほとんどすべては、Javaのような名前空間からファイルへのロケーション規則を使用していた。
パッケージ(または名前空間)を調べると、運が良ければそれらが合理的な方法でグループ化されていることがわかり、アーキテクチャレベルのモジュールを見つけるのに役立つ場合がある。 クラスがパッケージにどのようにグループ化されているか、また、どのパッケージが一緒にグループ化されているかは、それらが同じモジュール内にあることを意味している可能性がある。
バグまたは小さな機能から手をつける
コードをいじって加えた変更が最終的に出力される結果がどのように変化するかを確認することは、コードの理解を深めるのに大いに役立つ。 このとき優先度が低く、重大度の低いバグ・機能から手をつけるのがおすすめだ。コードに手を加えるのが比較的容易だからだ。
私の場合、新しいプロジェクトで方向を探るときはプロジェクトのタイプに応じて戦略を用意する。
1)バグにスタックトレースが添付されている場合は、そのスタックトレースの中で最も高い位置に書かれているソースコード内のクラスから手をつけるようにする。 (エラーがライブラリで発生した場合、これはトップに書かれていない可能性がある)。
2)そのプロジェクトがユーザーインターフェイス(UI)をもっていて、現在取り組んでいるバグまたは機能にUIコンポーネントがある場合、変更したいUIで使用されている文字列リテラルを検索することから始める。 これを作業の開始点として利用し、それを辿っていくことでビジネスロジックがどこにあるか見つけることができる。
3)プロジェクトが規約ベースであれば、まず、最も理解できるビュー、コントローラー、またはモデル(または、設計の側面が欠陥や機能に最も影響を与える可能性があるもの)を探すことから始める。 「理解できる」というのは単純にどのクラスが関与しているのかについて推測可能であるという意味だ。
4)プロジェクトがWebアプリ・Webサービスの場合、どのURLパスが関係している可能性があるか、また、どのクラスがそのURLパスへのリクエストを処理しているかを見つけるようにする。
5)バグまたは機能が上で述べた基準のいずれにも合致しない場合は、私が変えたいものに近いコードを探し始める。当てるにはのいくらか経験が必要である(必要でない場合もある)。 テストコードを見ることも、必要なコードを見つけるのに役立つ。
これらの手法を使用すると、ほとんどの場合、修正したいコードのかたまりを見つけることができる。 それから、UI、テスト、ログ、または単純な標準出力の中に確認できる小さな変化を見つけて作業を始めることができる。
もしテストコードが存在していれば、これらの小さな変化とテストを組み合わせることで、関係しているコードのかたまりを正確かつ迅速に狙いをつけることができる。 このプロセスを実行する際に留意すべきことの1つは、仮説を検証することだ。
人は何かを特定の方法で行う方法・理由について疑いも検証も無しに仮説を作ることで、何かを壊しがちになる。 既に特定の設計が選ばれていることは、たとえそれが自身に気に入らなかったり、メリットがわからなかったりしたとしても、何らかの十分な理由がある可能性があるということを意識する必要がある。 何かを壊すリスクを最小限に抑えるため、コードを研究している間は、そのコードベースについては自分は初心者なのだということを常に意識するようにしよう。
プロジェクトの作業中に興味深いと思ったところをすべてドキュメント化するように。 仮説が間違っていたということが分かった場合は、それを文字に起こそう。 君がその仮説を立てる最後の新しい開発者ではないからだ。 見つけたことを文書化することは、将来のチームメンバーの作業をスピードアップするのに役立つだろう。
まとめ
新しいコードベースに取り組むことは決して簡単ではないが、圧倒される必要はない。 シンプルなテクニック、一連のツール、およびいくつかの実験を用いることで、どんなプロジェクトでも知識と理解をすばやく得ることができる。
新しいコードベースに直面したときは、次の手順を思い出すようにしよう。
1)ビルドスクリプトを調べて、プロジェクトのビルドするのに使われるツールチェーンとプロセスを解明する。
2)可能であれば、プロダクトがビルドできたら実際に使って、その機能を確認してみる。
3)プロジェクトが依存するライブラリを調べて、使用・依存しているサービス・システムを見つける。
4)UML図の生成ツールを使用して、プロジェクトのすべてまたは一部のクラス図とシーケンス図を作成する。
5)コードを読みながら、シーケンス図を手動で作成する。
6)デプロイメントスクリプト(Capistranoなど)またはドキュメントを調べて、プロダクトがハードウェアにどのようにデプロイされているかを調べる。
7)プロジェクトがファイルシステムにどのように配置されているか、およびクラスがパッケージまたは名前空間にどのようにグループ化されているかを確認して、アーキテクチャを明らかにする
8)小さな機能または優先度の低いバグを使って、コードに足を踏み入れ、コードが実際に何をしているかを解き明かす。 将来の開発者がプロジェクトをより速く理解するのに役立つと思われる興味深いものはすべてドキュメント化する。
すべてのスキルと同様に、このプロセスには実践が必要であって、すべてのテクニックがすべての開発者またはプロジェクトでうまくいくわけではない。 特定のプロジェクトで何の手法が効果的であるか意識し、うまくいった手法に注力して、本当に上手になるようにする。 これらは、経験豊富な開発者が経験論的に開発したメタスキルの一部だが、目的に合わせて学習・洗練することにより、プロセス全体をスピードアップできる。
翻訳協力
Original Author: Ethan Urie
Original Article: Master a New Codebase in Record Time
Thank you for letting us share your knowledge!
この記事は以下の方々のご協力により公開する事が出来ました。
改めて感謝致します。
選定担当: @_masa_u
翻訳担当: @_masa_u
監査担当: @r_pg10
公開担当: @_masa_u
ご意見・ご感想をお待ちしております
今回の記事は、いかがだったでしょうか?
・こうしたら良かった、もっとこうして欲しい、こうした方が良いのではないか
・こういったところが良かった
などなど、率直なご意見を募集しております。
いただいたお声は、今後の記事の質向上に役立たせていただきますので、お気軽にコメント欄にてご投稿ください。Twitterでもご意見を受け付けております。
みなさまのメッセージをお待ちしております。