私は、Robert C. MartinがClean Codeに示した原則のいくつかを実装しようとしています。
私は注文の問題にひどく苦しんでいたクラスを持っていました。私は、コードを個別のクラスに抽出することにより、これのほとんどを解決しました。
メインクラスは次のようになっています。
_public class Planner {
public EstimateCollection ChosenEstimates { get; set; } // Want a better name for this..
public PlannedYear YearPlan { get; set; }
public Planner(List<Estimate> estimates) {
ChosenEstimates = new EstimateCollection(estimates);
YearPlan = ReadExistingPlan();
}
private PlannedYear ReadExistingPlan() {
var planReader = new PlanReader();
var rawPlan = planReader.GetScheduleFromDatabase(ChosenEstimates.StartDate, ChosenEstimates.EndDate);
var planConverter = new PlanConverter();
return planConverter.ConvertRowsToPlannedYear(rawPlan);
}
}
// This class was made by extracting most of what used to be in Planner's constructor
public class EstimateCollection {
public List<Estimate> Estimates { get; set; }
public DateTime StartDate { get; }
public DateTime EndDate { get; }
public EstimateCollection(List<Estimate> estimates) {
Estimates = estimates;
StartDate = GetEarliestStartDate();
EndDate = GetLatestEndDate();
}
private DateTime GetEarliestStartDate() {
// Do something to get start date from Estimates list
}
private DateTime GetLatestEndDate() {
// Do something to get end date Estimates list
}
}
_
今私が抱えている問題は、Plannerクラスのコンストラクター(8行目)にあります。最初にChosenEstimates
変数が設定され、次にYearPlan
変数が設定されます。 ReadExistingPlan()
はChosenEstimates
を使用するため、YearPlan
varを設定してからChosenEstimates
をReadExistingPlan()
メソッドで設定する必要があります。
パラメータとしてChosenEstimates
からReadExistingPlan()
を指定することもできますが、クリーンコードの例ではほとんどこれを実行できません。
これを回避できるように、コードにさらに構造的な変更はありますか?
今、私はどちらの問題をしたいかを選択しています。
ChosenEstimatesをパラメーターとしてReadExistingPlan()に提供することはできますが、クリーンコードの例ではほとんどこれを行いません。
実際にクリーンなコードは、303ページでこれを正確に実行します。
G31:非表示の時間的カップリング
一時的な結合が必要になることがよくありますが、結合を非表示にしないでください。 >関数の引数を、それらを呼び出す順序が明白になるように構造化します。次の点を考慮してください。_public class MoogDiver { Gradient gradient; List<Spline> splines; public void dive(String reason) { saturateGradient(); reticulateSplines(); diveForMoog(reason); } ... }
_3つの関数の順序は重要です。スプラインを網状にする前に勾配を飽和させてから、ムーグに飛び込むことができます。残念ながら、コードはこの一時的な結合を強制しません。別のプログラマーは、saturateGradientが呼び出される前にreticulateSplinesを呼び出し、UnsaturatedGradientExceptionを引き起こす可能性があります。より良い解決策は:
_public class MoogDiver { Gradient gradient; List<Spline> splines; public void dive(String reason) { Gradient gradient = saturateGradient(); List<Spline> splines = reticulateSplines(gradient); diveForMoog(splines, reason); } ... }
_これは、バケット旅団を作成することにより、時間的カップリングを公開します。各関数は次の関数が必要とする結果を生成するため、それらを順不同で呼び出す合理的な方法はありません。これにより機能が複雑になると文句を言うかもしれませんが、あなたは正しいでしょう。しかし、その余分な構文の複雑さは、状況の真の時間的な複雑さを明らかにします。インスタンス変数はそのままにしたことに注意してください。これらはクラスのプライベートメソッドで必要になると思います。それでも、時間的な結合を明示的にするために適切な議論が必要です。
ここでマーティン氏は、オブジェクト内でより機能的なアプローチを取ることを提唱しています。奇妙に思われるが、それは動作します。それはあなたが物事を順番に行うことを強制します。
ただし、まったく別の理由により、このクラスでは引き続き問題が発生します。オブジェクトの状態のChosenEstimates
とYearPlan
の両方を作成しています。しかし、YearPlan
は、事実上、ChosenEstimates
の関数です。したがって、YearPlan
は文字どおりChosenEstimates
の関数である必要があります。どういう意味ですか?
YearPlan
を保存する場所にChosenEstimates
を保存しないことを意味します。この2つは互いに同意しない可能性があります(特に、公開セッターを提供している場合)。 YearPlans
ゲッターがReadExistingPlan(ChosenEstimates)
を返すようにして、それらの整合性を強制します。
このようにして、あなたのPlanner
を奇妙な状態にすることはできません。
これをさらに一歩進めて、 コンストラクタで行われている余分な作業を排除する とすることができます。コンストラクターが引数を検証して状態を設定するだけの場合は、この方法を使用します。コンストラクターでそれ以上のことが起こった場合、それを取り除く方法を探します。コンストラクターの仕事は、オブジェクトメソッドの動作を変更することです。彼らのために彼らの仕事をすることではありません。
estimates
は唯一の状態フィールドである可能性があり、ここにある他のすべてのものは、それを開始点として使用します。しかし、ChosenEstimates
も同様です。 Planner
を新しいestimates
に渡して、ChosenEstimates
とestimates
を強制的に構築し、EstimateCollection
も構築します。ここでは何もestimates
を直接使用していないため、ここで裸になる理由はありません。
私は本格的なコードレビュー(ここではcodereview.stackexchange.comで行います)に発展しているように見えるかもしれませんが、これを行うことにより、それ自体がクラスから押し出されます。順不同のことはコンパイルされないため、今は順不同ではありません。
この回答はcandied_orangeの回答を拡張したものです。(回答とこのスタブを削除します。)
ReadExistingPlan
メソッドはChosenEstimates
フィールド全体を使用しません。むしろ、そのStartDate
およびEndDate
プロパティのみを使用します。呼び出し元が適切な量の情報をReadExistingPlan
に渡すと、コードがわかりやすくなります。
これが完了すると、ReadExistingPlan
フィールドの「依存関係」が完全な状態から縮小されているため、PlannedYear
メソッドをChosenEstimates
クラスに移動できる可能性があることがわかります。 -blownオブジェクトを2つのDateTime
フィールドに下げ、パラメーターとして渡すことができます。
ReadExistingPlan
をPlannedYear
の静的メソッド(ファクトリーメソッド)にすることも、コンストラクターメソッドにすることもできます。ただし、このメソッドにPlanner
からの追加が必要な場合は、さらに検討する必要があります。