ここ数年、私たちは徐々に少しずつ改善されたコードに徐々に移行してきました。ようやく、少なくともSOLIDに似たものへの切り替えを開始しましたが、まだ完全ではありません。切り替え以来、開発者からの最大の不満の1つは、以前はすべてのタスクで開発者が5〜10個のファイルに触れるだけで済む、数十から数十のファイルをピアレビューおよびトラバースすることに耐えられないことです。
切り替えを開始する前は、アーキテクチャは次のように編成されていました(1桁または2桁多いファイルが許可されています)。
Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI
ファイルに関しては、すべてが非常に直線的でコンパクトでした。明らかにコードの重複、密結合、頭痛の種がたくさんありましたが、誰もがそれを横断して理解することができました。完全な初心者、これまでVisual Studioを開いたことがない人は、ほんの数週間でそれを理解できました。全体的なファイルの複雑さの欠如により、初心者の開発者や新入社員にとっても、あまり時間をかけずに貢献し始めることが比較的簡単になります。しかし、これは、コードスタイルの利点が出てこなくなるところです。
私はコードベースを改善するためのあらゆる試みを心から支持していますが、このような大規模なパラダイムシフトについて、チームの他のメンバーからプッシュバックを受けることは非常に一般的です。現在最大の問題点は次のとおりです。
ユニットテストは、時間の無駄であり、個々のコードよりも全体としてコードの処理テストを迅速に行うことができると信じているため、チームにとって信じられないほど大変な売りでした。ユニットテストをSOLIDの推奨として使用することは、ほとんどの場合無駄であり、現時点ではほとんど冗談になっています。
クラス数はおそらく、克服すべき最大のハードルです。 5〜10個のファイルを使用していたタスクは、70〜100個を使用できるようになりました。これらのファイルはそれぞれ異なる目的を果たしますが、膨大な量のファイルが膨大な量になる可能性があります。チームからの反応は、主にうめき声と頭を掻く音でした。以前のタスクでは、1つまたは2つのリポジトリ、1つまたは2つのモデル、ロジックレイヤー、およびコントローラーメソッドが必要でした。
簡単なファイル保存アプリケーションを構築するために、ファイルが既に存在するかどうかを確認するクラス、メタデータを書き込むクラス、抽象化するクラスDateTime.Now
ユニットテストの時間、ロジックを含むすべてのファイルのインターフェース、各クラスのユニットテストを含むファイル、DIコンテナーにすべてを追加する1つ以上のファイルを挿入できます。
中小規模のアプリケーションの場合、SOLIDは非常に簡単に販売できます。誰もがメンテナンス性のメリットと容易さを理解しています。しかし、SOLIDは非常に大規模なアプリケーションなので、組織と管理を改善して、増大する困難を乗り越える方法を見つけようとしています。
最近完了したタスクに基づくファイルボリュームの例をもう少し強く示したいと思いました。ファイル同期リクエストを受信するために、新しいマイクロサービスの1つにいくつかの機能を実装するタスクが与えられました。リクエストが受信されると、サービスは一連の検索とチェックを実行し、最後にドキュメントをネットワークドライブと2つの個別のデータベーステーブルに保存します。
ドキュメントをネットワークドライブに保存するには、いくつかの特定のクラスが必要でした。
- IBasePathProvider
-- string GetBasePath() // returns the network path to store files
-- string GetPatientFolderName() // gets the name of the folder where patient files are stored
- BasePathProvider // provides an implementation of IBasePathProvider
- BasePathProviderTests // ensures we're getting what we expect
- IUniqueFilenameProvider
-- string GetFilename(string path, string fileType);
- UniqueFilenameProvider // performs some filesystem lookups to get a unique filename
- UniqueFilenameProviderTests
- INewGuidProvider // allows me to inject guids to simulate collisions during unit tests
-- Guid NewGuid()
- NewGuidProvider
- NewGuidProviderTests
- IFileExtensionCombiner // requests may come in a variety of ways, need to ensure extensions are properly appended.
- FileExtensionCombiner
- FileExtensionCombinerTests
- IPatientFileWriter
-- Task SaveFileAsync(string path, byte[] file, string fileType)
-- Task SaveFileAsync(FilePushRequest request)
- PatientFileWriter
- PatientFileWriterTests
したがって、これは、15のクラス(POCOとスキャフォールディングを除く)の合計で、かなり単純な保存を実行します。いくつかのシステムでエンティティを表すPOCOを作成し、他のORMと互換性のないサードパーティシステムと通信するためにいくつかのリポジトリを構築し、特定の操作の複雑さを処理するロジックメソッドを構築する必要がある場合、この数は大幅に増加しました。
簡単なファイル保存アプリケーションを構築するために、ファイルが既に存在するかどうかを確認するクラス、メタデータを書き込むクラス、DateTime.Nowを抽象化するクラスがあります。これにより、ユニットテストの時間を注入できるようになり、すべてのファイルのインターフェイスにロジック、各クラスの単体テストを含むファイル、およびすべてをDIコンテナーに追加する1つ以上のファイル。
単一の責任という考えを誤解されたと思います。クラスの単一の責任は「ファイルを保存する」かもしれません。これを行うには、その責任をファイルが存在するかどうかを確認するメソッド、メタデータを書き込むメソッドなどに分類します。これらの各メソッドには、クラス全体の責任の一部である単一の責任があります。
抽象化するクラスDateTime.Now
いいですね。しかし、必要なのはそのうちの1つだけであり、環境機能を抽象化する責任を持つ単一のクラスに他の環境機能でラップすることができます。繰り返しになりますが、複数のサブ責任を持つ単一の責任。
「ロジックを含むすべてのファイルのインターフェース」は必要ありません。たとえば、副作用のあるクラスのインターフェースが必要です。ファイルまたはデータベースの読み取り/書き込みを行うクラス。そしてそれでも、それらはその機能の公開部分にのみ必要です。したがって、たとえばAccountRepo
の場合、インターフェイスは必要なく、そのリポジトリに挿入される実際のデータベースアクセス用のインターフェイスのみが必要な場合があります。
ユニットテストは、時間の無駄であり、個々のコードよりも全体としてコードの処理テストを迅速に行うことができると信じているため、チームにとって信じられないほど大変な売りでした。ユニットテストをSOLIDの推奨として使用することは、ほとんどの場合無駄であり、現時点ではほとんど冗談になっています。
これは、ユニットテストも誤解していることを示しています。単体テストの「ユニット」はコードのユニットではありません。コードのユニットとは何ですか?クラス?方法?変数?単一の機械語命令?いいえ。「ユニット」とは、分離の単位、つまりコードの他の部分から分離して実行できるコードを指します。自動テストが単体テストであるかどうかの簡単なテストは、結果に影響を与えずに他のすべての単体テストと並行して実行できるかどうかです。単体テストにはさらにいくつかの経験則がありますが、それが重要な基準です。
したがって、コードの一部が実際に全体として他の部分に影響を与えることなくテストできる場合は、それを実行します。
常に実用的であり、すべてが妥協であることを忘れないでください。 DRYに忠実であるほど、コードをより強く結合する必要があります。抽象化を導入すればするほど、コードのテストは容易になりますが、理解が難しくなります。イデオロギーを避け、理想とそれをシンプルに保つことの良いバランスを見つけてください。開発と保守の両方で最大効率のスイートスポットがあります。
5〜10個のファイルを使用していたタスクは、70〜100個を使用できるようになりました。
これは、単一責任原則(SRP)の反対です。その時点に到達するには、機能を非常にきめ細かく分割する必要がありますが、それはSRPの目的ではありません。これを行うと、凝集度の主要な概念が無視されます。
SRPによると、ソフトウェアは、変更する可能性のある理由によって定義される線に沿ってモジュールに分割する必要があります。これにより、他の変更を必要とせずに、単一の設計変更を-just oneモジュールに適用できます。この意味での単一の「モジュール」は複数のクラスに対応する可能性がありますが、1つの変更で数十のファイルに触れる必要がある場合、それは本当に複数の変更であるか、SRPを間違っています。
最初にSRPを作成したボブマーティンは、数年前に状況を明確にするために ブログ投稿 を書きました。 「変更する理由」がSRPの目的のために何であるかについてある程度議論します。それは全体として読む価値がありますが、特別な注意に値するものの中でSRPのこの別の言い回しがあります:
同じ理由で変化するものをまとめます。さまざまな理由で変化するものを分離します。
(私の強調)。 SRPはnotで物事を可能な限り小さな部分に分割することについてです。それは良い設計ではなく、あなたのチームは抵抗するのが正しいです。これにより、コードベースの更新と維持が難しくなります。単体テストの考慮事項に基づいてチームを売り込もうとしているように思えるかもしれませんが、それはカートを馬の前に置くことになります。
同様に、インターフェース分離の原則は絶対的なものとして解釈されるべきではありません。 SRPほど細かくコードを分割する理由はありません。SRPと非常によく一致しています。インターフェイスにsomeクライアントが使用しないsomeメソッドが含まれていることは、それを分割する理由にはなりません。あなたは再びまとまりを探しています。
さらに、深い継承階層を支持する理由として、オープンクローズの原則またはリスコフの置換の原則を採用しないことをお勧めします。スーパークラスを持つサブクラスほど密結合はなく、密結合は設計上の問題です。代わりに、それが意味をなす場合は常に、継承よりも構成を優先してください。これにより、カップリングが減り、特定の変更が必要なファイルの数が減り、依存関係の逆転とうまく一致します。
5〜10個のファイルを使用していたタスクは、70〜100個を使用できるようになりました。
これは嘘です。タスクは5-10ファイルしか取りませんでした。
ファイル数が10未満のタスクは解決していません。どうして? C#を使用しているためです。 C#は高水準言語です。 Hello Worldを作成するためだけに10個を超えるファイルを使用しています。
書いていないので気づかないでしょう。だからあなたはそれらを見ないでください。あなたは彼らを信頼しています。
問題はファイルの数ではありません。それは今あなたが信じられないほど多くのことが起こっているということです。
したがって、これらのテストが成功したら、.NETのファイルを信頼する方法でこれらのファイルを信頼できるようになるまで、それらのテストを機能させる方法を理解してください。これを行うことが単体テストのポイントです。ファイルの数を気にする人はいません。彼らは信頼できないものの数を気にしています。
中小規模のアプリケーションの場合、SOLIDは非常に簡単に販売できます。誰もがメンテナンス性のメリットと容易さを理解しています。しかし、SOLID非常に大規模なアプリケーション。
非常に大規模なアプリケーションでは変更は難しく、何をしてもかまいません。ここで適用する最良の知恵は、ボブおじさんからではありません。これは、マイケルフェザーズの著書「レガシーコードの効果的な使用」に含まれています。
書き換えフェストを開始しないでください。古いコードは、絶対に得た知識を表しています。問題があり、新しく改善されたパラダイムで表現されていないためにそれを投げ捨てるXは、新しい問題のセットを求めているだけで、勝った知識はありません。
代わりに、テスト可能にする古いテスト不可能なコードを作成する方法を見つけてください(Feathersのレガシーコードが話します)。この比喩ではコードはシャツのようなものです。大きなパーツは自然なシームで結合されているため、シームを削除する方法でコードを分離することができます。これを行うと、コードの残りの部分を分離できるテスト「スリーブ」を接続できます。これで、テストスリーブを作成すると、作業シャツでこれを行ったので、スリーブに自信があります。 (今、この比喩は傷つき始めています)。
このアイデアは、ほとんどのショップと同様に、最新の要件は作業コードにのみあるという前提に基づいています。これにより、実証済みの動作ステータスのすべてのビットを失うことなく、実証済みの動作中のコードに変更を加えることができるテストでそれをロックダウンできます。テストのこの最初の波が準備できたら、「レガシー」(テスト不可能な)コードをテスト可能にする変更を開始できます。継ぎ目テストがこれを常にサポートしていることを伝えることでシームテストがバックアップし、新しいテストはコードが実際に行っていることを実際に実行することを示しているため、大胆になることができます。
これは何と関係がありますか:
SOLIDに切り替えた後、大幅に増加したクラスの数を管理および整理しますか?
抽象化。
あなたは私が悪い抽象化でコードベースを嫌うようにすることができます。悪い抽象化は私を内面から見させるものです。中を見ても驚かないで。私が期待していたものとほぼ同じです。
わかりやすい名前を付け、読みやすいテスト(例)を使用してインターフェイスを使用する方法を示し、検索できるように整理して、10個、100個、または1000個のファイルを使用してもかまいません。
わかりやすい名前の物を見つけてください。良い名前の物を良い名前の物に入れなさい。
このすべてを正しく行うと、他の3〜5個のファイルに依存するだけで、タスクを完了するためにファイルを抽象化します。 70-100ファイルはまだ残っています。しかし、彼らは3対5の後ろに隠れています。これは、3対5がその権利を信頼している場合にのみ機能します。
したがって、本当に必要なのは、人々が信頼するこれらすべてのものとテストの良い名前を考え出すための語彙です。それがなければあなたも私を夢中にさせるでしょう。
@Delioth 良い点を作る 成長する痛みについて。食器洗い機の上の食器棚にある料理に慣れると、朝食バーの上の料理に慣れる必要があります。いくつかのことを難しくします。いくつかのことが簡単になります。しかし、人々が料理の行き先に同意しないと、あらゆる種類の悪夢が引き起こされます。大規模なコードベースでは、一度に移動できるのは一部の料理のみです。これで、料理が2か所にあります。ややこしい。料理が本来あるべき場所にあると信頼するのを難しくします。これを乗り越えたい場合は、皿を動かし続けるだけです。
それに関する問題は、あなたが朝食バーの上に皿を持っていることがこのすべてのナンセンスを通過する前にそれだけの価値があるかどうか本当に知りたいことです。さて、私がお勧めできるのはキャンプに行くことだけです。
新しいパラダイムを初めて試す場合、それを適用する必要がある最後の場所は、大規模なコードベースです。これはチームのすべてのメンバーに当てはまります。 SOLIDが機能する、OOPが機能する、または関数型プログラミングが機能することを信じて、誰もそれを理解するべきではありません。すべてのチームメンバーは、おもちゃのプロジェクトにおける新しいアイデア、それが何であれ、少なくともそれがどのように機能するかを見ることができます。うまくいかないところを見ることができます。大きな混乱を起こす直前に、それを学ぶことができます。
安全な場所でプレイできるようにすることで、新しいアイデアを取り入れ、料理が新しい家で本当に機能するという自信を与えることができます。
コードが十分に分離されていないか、タスクのサイズが大きすぎるかのようです。
コードの変更すべき codemodまたは大規模なリファクタリングを実行している場合を除き、5〜10ファイルにする必要があります。 1つの変更が多くのファイルに影響を与える場合、おそらく変更がカスケードされることを意味します。いくつかの改善された抽象化(単一の責任、インターフェースの分離、依存関係の逆転)が役立つはずです。また、多分too単一の責任で、もう少し実用的な方法(短くて薄い型の階層)を使用することもできます。コードが何をしているのかを知るために何十ものファイルを理解する必要がないので、コードも理解しやすくなるはずです。
また、あなたの仕事が大きすぎることを示している可能性もあります。 「ちょっと、この機能を追加する」(UIの変更とAPIの変更とデータアクセスの変更とセキュリティの変更とテストの変更などが必要です)の代わりに、より機能的なチャンクに分割します。これは、ビット間に適切なコントラクトを設定する必要があるため、確認と理解が容易になります。
そしてもちろん、単体テストはこのすべてを助けます。彼らはあなたにまともなインターフェースを作ることを強います。テストに必要なビットを挿入するのに十分な柔軟性をコードに強いることを強制します(テストするのが難しい場合、再利用するのは難しいでしょう)。また、エンジニアが多ければ多いほどテストの必要性が高まるため、過剰エンジニアリングから人々を遠ざけます。
ここですでに述べたいくつかのことを詳しく説明したいと思いますが、オブジェクトの境界がどこに描画されるかという観点からは、もっと詳しく説明します。ドメイン駆動設計に似たものに従っている場合、オブジェクトはおそらくビジネスの側面を表すことになります。たとえば、Customer
およびOrder
はオブジェクトです。さて、あなたが出発点として持っていたクラス名に基づいて推測した場合、AccountLogic
クラスにはanyアカウント。ただし、オブジェクト指向では、各クラスはコンテキストとアイデンティティを持つことを目的としています。 Account
オブジェクトを取得してからAccountLogic
クラスに渡して、そのクラスにAccount
オブジェクトを変更させないでください。これが貧血モデルと呼ばれるもので、OOをうまく表現していません。代わりに、Account
クラスはAccount.Close()
のような動作をする必要がありますまたはAccount.UpdateEmail()
、およびこれらの動作はアカウントのそのインスタンスにのみ影響します。
現在、これらの動作の処理方法は、抽象化(つまり、インターフェース)によって表される依存関係にオフロードできます(多くの場合、そうする必要があります)。 Account.UpdateEmail
は、たとえば、データベースやファイルを更新したり、サービスバスなどにメッセージを送信したりする場合があります。また、将来的には変更される可能性があります。そのため、Account
クラスは、たとえばIEmailUpdate
に依存している可能性があります。これは、AccountRepository
オブジェクトによって実装される多くのインターフェースの1つである可能性があります。 IAccountRepository
インターフェイス全体をAccount
オブジェクトに渡したくない場合があります。これは、他の(任意の)アカウントを検索して見つけるなど、あまりにも多くのことを行うためです。アクセスできるAccount
オブジェクトですが、AccountRepository
がIAccountRepository
とIEmailUpdate
の両方のインターフェースを実装している場合でも、Account
オブジェクトは必要な小さな部分へのアクセス。これは、インターフェース分離の原則を維持するのに役立ちます。
現実的には、他の人が述べたように、クラスの急増に対処している場合、SOLID原則(および、拡張してOO)を間違った方法で使用している可能性があります。= SOLIDは、コードを複雑にするのではなく、簡略化するのに役立つはずです。しかし、SRPのようなものが何を意味するのかを実際に理解するには時間がかかります。しかし、より重要なことは、SOLID作品は、ドメインと境界のあるコンテキスト(別のDDD用語)に大きく依存します。特効薬も万能薬もありません。
私が一緒に働く人々に強調したいもう1つのこと:繰り返しますが、OOPオブジェクトは動作を持つ必要があり、実際、データではなくその動作によって定義されます。オブジェクトの場合プロパティとフィールドしかありませんが、おそらく意図した動作ではありませんが、動作があります。他のセットロジックがないpublic-writable/settableプロパティは、その包含クラスの動作が、理由や理由を問わず誰でもその間に必要なビジネスロジックや検証なしでそのプロパティの値を変更するための時間が許可されます。これは通常、ユーザーが意図する動作ではありませんが、貧血モデルがある場合、それは通常、クラスがそれらを使用する人に発表する動作です。
したがって、これは、15のクラス(POCOとスキャフォールディングを除く)の合計で、かなり単純な保存を実行します。
それはクレイジーです。しかし、これらのクラスは私が自分で書くようなものに聞こえます。それでは、それらを見てみましょう。今のところ、インターフェースとテストは無視してみましょう。
BasePathProvider
-ファイルを扱う重要なプロジェクトでIMHOが必要です。なので、もうそんなものがあり、そのまま使えると思います。UniqueFilenameProvider
-確かに、あなたはすでにそれを持っていますね。NewGuidProvider
-GUIDを使用するためだけに見ている場合を除き、同じケースです。FileExtensionCombiner
-同じケース。PatientFileWriter
-おそらく、これは現在のタスクのメインクラスです。私には、それはよさそうです。4つのヘルパークラスを必要とする1つの新しいクラスを記述する必要があります。 4つのヘルパークラスはすべて再利用可能に聞こえるので、コードベースのどこかにすでにあるはずです。それ以外の場合は、不運(実際にチームのメンバーがファイルを作成してGUIDを使用するのですか?)か、またはその他の問題のいずれかです。
テストクラスに関しては、新しいクラスを作成したり更新したりするときに、必ずテストする必要があります。したがって、5つのクラスを記述することは、5つのテストクラスを記述することも意味します。ただし、これによってデザインが複雑になることはありません。
インターフェースに関しては、DIフレームワークまたはテストフレームワークのいずれかがクラスを処理できない場合にのみ必要です。あなたはそれらを不完全なツールの犠牲として見るかもしれません。あるいは、それらを、より複雑なものがあることを忘れさせる有用な抽象概念として見るかもしれません-インターフェースのソースを読み取るのは、その実装のソースを読み取るよりもはるかに短い時間です。
抽象化によっては、単一責任クラスの作成、および単体テストの作成は正確な科学ではありません。学習時に一方向に大きく振りすぎて極端に行き、意味のある規範を見つけることは完全に正常です。振り子が大きく振れすぎて動かなくなったようです。
これがRailsから外れているのではないかと思うところです。
ユニットテストは、時間の無駄であり、個々のコードよりも全体としてコードの処理テストを迅速に行うことができると信じているため、チームにとって信じられないほど大変な売りでした。ユニットテストをSOLIDの推奨として使用することは、ほとんどの場合無駄であり、現時点ではほとんど冗談になっています。
ほとんどのSOLID原理(確かに唯一の利点ではありません)から得られる利点)の1つは、コードの単体テストを簡単に記述できることです。クラスが抽象化に依存している場合、抽象化をモックできます。分離された抽象化はモックするのが簡単です。クラスが1つのことを行う場合、複雑さが低くなる可能性が高いため、考えられるすべてのパスを簡単に確認およびテストできます。
チームが単体テストを作成していない場合、関連する2つのことが発生しています。
まず、これらのインターフェイスとクラスのすべてを作成するために、十分なメリットを実現せずに多くの追加作業を行っています。単体テストを書くことで私たちの生活がどのように簡単になるかを確認するには、少し時間と練習が必要です。ユニットテストを書くことを学ぶ人がそれに固執する理由がありますが、あなたは自分のためにそれらを発見するのに十分長く持続する必要があります。あなたのチームがそれを試みていない場合、彼らは彼らが行っている余分な仕事の残りが役に立たないように感じるでしょう。
たとえば、リファクタリングが必要になるとどうなりますか? 100の小さなクラスがあり、変更が機能するかどうかをテストするテストがない場合、それらの追加のクラスとインターフェースは、改善ではなく負担のように見えます。
次に、単体テストを作成すると、コードが実際に必要とする抽象化の量を理解するのに役立ちます。私が言ったように、それは科学ではありません。私たちはひどく始めて、あちこちで向きを変えて、良くなります。単体テストには、SOLIDを補完する独特の方法があります。抽象化を追加する必要がある場合、または何かを分解する必要がある場合、どのようにしてわかりますか?言い換えれば、あなたが「十分にしっかり」していることをどうやって知るのですか?多くの場合、答えは、何かをテストできない場合です。
たぶんあなたのコードは多くの小さな抽象化とクラスを作成することなくテスト可能でしょう。しかし、テストを作成していない場合、どうすればわかりますか?どこまで行くの?私たちは物事をどんどん小さくしていくことに夢中になることができます。うさぎの穴です。コードのテストを作成する機能は、目的を達成したことを確認するのに役立ちます。これにより、執着をやめ、先に進み、さらに多くのコードを書くことができます。
単体テストはすべてを解決する特効薬ではありませんが、開発者の生活をより良いものにする本当に素晴らしい弾丸です。私たちは完璧ではありませんし、テストも完璧ではありません。しかし、テストは私たちに自信を与えます。私たちはコードが正しいことを期待しており、それが間違っているのではなく、逆ではないことに驚いています。私たちは完璧ではなく、テストも完璧ではありません。しかし、コードがテストされるとき、私たちは自信を持っています。コードがデプロイされたときに爪を噛み、今回何が壊れるのか、それが私たちのせいなのかどうか疑問に思う可能性は低くなります。
その上、こつをつかんだら、ユニットテストを作成することでコードの開発が速くなり、遅くなることはありません。干し草の山の中の針のような問題を見つけるために、古いコードを再訪したりデバッグしたりする時間を減らします。
バグが減り、より多くのことができるようになり、不安を自信に置き換えます。流行りやヘビのオイルではありません。本物だ。多くの開発者がこれを証明します。あなたのチームがこれを経験していない場合、彼らはその学習曲線を突き抜けてこぶを乗り越える必要があります。チャンスを与えて、彼らがすぐに結果を得られないことを悟ってください。しかし、それが起こったとき、彼らは喜んで彼らを喜ばせ、彼らは決して振り返ることはないでしょう。 (または、それらは孤立した孤独になり、単体テストと他のほとんどの蓄積されたプログラミング知識がどのように時間の無駄であるかについて怒っているブログ投稿を書くでしょう。)
切り替え以来、開発者からの最大の不満の1つは、以前はすべてのタスクで開発者が5〜10個のファイルに触れるだけで済む、数十から数十のファイルをピアレビューおよびトラバースすることに耐えられないことです。
すべての単体テストに合格すると、ピアレビューははるかに簡単になります。そのレビューの大部分は、テストに意味があることを確認することだけです。