8.簡潔なコードで再包装
1.小型リフォーム
再構築とは,既存の機能を維持しながらコードの下位構造を変更することである.
1.再構築の機会
public class Profile {
private Map<String,Answer> answers = new HashMap<>();
// ...
private int score;
private String name;
public Profile(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void add(Answer answer) {
answers.put(answer.getQuestionText(), answer);
}
public boolean matches(Criteria criteria) {
score = 0;
boolean kill = false;
boolean anyMatches = false;
for (Criterion criterion: criteria) {
Answer answer = answers.get(
criterion.getAnswer().getQuestionText());
boolean match =
criterion.getWeight() == Weight.DontCare ||
answer.match(criterion.getAnswer());
if (!match && criterion.getWeight() == Weight.MustMatch) {
kill = true;
}
if (match) {
score += criterion.getWeight().getValue();
}
anyMatches |= match;
// ...
}
if (kill)
return false;
return anyMatches;
}
public int score() {
return score;
}
public List<Answer> classicFind(Predicate<Answer> pred) {
List<Answer> results = new ArrayList<Answer>();
for (Answer answer: answers.values())
if (pred.test(answer))
results.add(answer);
return results;
}
@Override
public String toString() {
return name;
}
public List<Answer> find(Predicate<Answer> pred) {
return answers.values().stream()
.filter(pred)
.collect(Collectors.toList());
}
}
Profileクラスのmatches()メソッドから見ると,条件文は複雑である.したがって、matches()メソッドの複雑さを低減して、コードが何を担当しているのかをよりよく理解したい場合があります.2.方法抽出:2番目の重要な再構築友人
boolean match =
criterion.getWeight() == Weight.DontCare ||
answer.match(criterion.getAnswer());
このセクションを個別の方法として抽出し、複雑さを解消します.では、繰り返し文には単純な宣言しか残っていません.match変数は,条件が答えに合致するか否かのみを表す.public boolean matches(Criteria criteria) {
score = 0;
boolean kill = false;
boolean anyMatches = false;
for (Criterion criterion: criteria) {
Answer answer = answers.get(
criterion.getAnswer().getQuestionText());
boolean match = matches(criterion, answer);
if (!match && criterion.getWeight() == Weight.MustMatch) {
kill = true;
}
if (match) {
score += criterion.getWeight().getValue();
}
anyMatches |= match;
}
if (kill)
return false;
return anyMatches;
}
private boolean matches(Criterion criterion, Answer answer) {
return criterion.getWeight() == Weight.DontCare ||
answer.match(criterion.getAnswer());
}
2.方法のためにもっと良い家を探す
新しい抽出されたマッチング()メソッドは、Profileオブジェクトに関係なく、AnswerクラスとCriterionクラスに関連しています.したがって,matches()メソッドをCriterionクラスに移動する.CriterionオブジェクトはAnswerオブジェクトを知っているが,このドメインは成り立たないからである.つまり,AnswerクラスはCriterionクラスに依存しない.matches()メソッドをAnswerクラスに移動した場合は、双方向依存関係になります.
public class Criterion implements Scoreable {
// ...
private Weight weight;
private Answer answer;
private int score;
public Criterion(Answer answer, Weight weight) {
this.answer = answer;
this.weight = weight;
}
public Answer getAnswer() { return answer; }
public Weight getWeight() { return weight; }
public void setScore(int score) { this.score = score; }
public int getScore() { return score; }
public boolean matches(Answer answer) {
return getWeight() == Weight.DontCare ||
answer.match(getAnswer());
}
}
移動後、Profileクラスの繰り返し文は次のとおりです.for (Criterion criterion: criteria) {
Answer answer = answers.get(
criterion.getAnswer().getQuestionText());
boolean match = criterion.matches(answer);
if (!match && criterion.getWeight() == Weight.MustMatch) {
kill = true;
}
if (match) {
score += criterion.getWeight().getValue();
}
anyMatches |= match;
}
また,応答領域変数に割り当てられた文はかなり長く複雑である.Answer answer = answers.get(
criterion.getAnswer().getQuestionText());
上のコードは「Demeter法則」(要約すると他のオブジェクトに伝播する連鎖メソッド呼び出しを避けるべき)に違反しており,簡潔ではない.この点を改善する最初のステップは、答え割り当て文の右側を新しいメソッド「答え生成」()に抽出することです.
public boolean matches(Criteria criteria) {
score = 0;
boolean kill = false;
boolean anyMatches = false;
for (Criterion criterion: criteria) {
Answer answer = answerMatching(criterion);
boolean match = criterion.matches(answer);
if (!match && criterion.getWeight() == Weight.MustMatch) {
kill = true;
}
if (match) {
score += criterion.getWeight().getValue();
}
anyMatches |= match;
}
if (kill)
return false;
return anyMatches;
}
private Answer answerMatching(Criterion criterion) {
return answers.get(criterion.getAnswer().getQuestionText());
}
一時変数の使い方は多種多様である.一時変数として、コストの高い計算値をキャッシュに入れるか、メソッド本体で変更された内容を収集します.また、一時変数は、コードの意図を明確にするために使用することができる.3.自動と手動で再包装
ここで,答え領域変数はコードの明確性を増やさずに1回のみ使用する.変数を除去し、「応答一致」を埋め込みます.
for (Criterion criterion: criteria) {
boolean match = criterion.matches(answerMatching(criterion));
if (!match && criterion.getWeight() == Weight.MustMatch) {
kill = true;
}
if (match) {
score += criterion.getWeight().getValue();
}
anyMatches |= match;
}
また、ほとんどのIDEは内部再構築を自動化しています.matches()メソッドの詳細は削除されたので、高度なポリシーを簡単に理解できます.したがって,メソッドのコアターゲットを区別することができる.
public boolean matches(Criteria criteria) {
score = 0;
boolean kill = false;
for (Criterion criterion: criteria) {
boolean match = criterion.matches(answerMatching(criterion));
if (!match && criterion.getWeight() == Weight.MustMatch) {
kill = true;
}
if (match) {
score += criterion.getWeight().getValue();
}
}
if (kill)
return false;
return anyMatches(criteria);
}
private boolean anyMatches(Criteria criteria) {
boolean anyMatches = false;
for (Criterion criterion: criteria)
anyMatches = criterion.matches(answerMatching(criterion));
return anyMatches;
}
相互に分離したコードを新しいメソッドとして抽出すると自動化できないため,手動で再包装する必要がある.次のテストに失敗しました.@Test
public void matchAnswersTrueWhenAnyOfMultipleCriteriaMatch() {
profile.add(answerThereIsRelocation);
profile.add(answerDoesNotReimburseTuition);
criteria.add(new Criterion(answerThereIsRelocation, Weight.Important));
criteria.add(new Criterion(answerReimbursesTuition, Weight.Important));
boolean matches = profile.matches(criteria);
assertTrue(matches);
}
この解決策は、anyMatches値を更新するときに複合割り当て演算子(|=)を使用することです. private boolean anyMatches(Criteria criteria) {
boolean anyMatches = false;
for (Criterion criterion: criteria)
anyMatches |= criterion.matches(answerMatching(criterion));
return anyMatches;
}
4.大げさな再構築?
同様に、一致するすべての総重み付けを計算するコードを抽出する.
public boolean matches(Criteria criteria) {
calculateScore(criteria);
boolean kill = false;
for (Criterion criterion: criteria) {
boolean match = criterion.matches(answerMatching(criterion));
if (!match && criterion.getWeight() == Weight.MustMatch) {
kill = true;
}
}
if (kill)
return false;
return anyMatches(criteria);
}
private void calculateScore(Criteria criteria) {
score = 0;
for (Criterion criterion: criteria)
if (criterion.matches(answerMatching(criterion)))
score += criterion.getWeight().getValue();
}
最後に,不整合の有無を決定する必須条件の論理を抽出する.public boolean matches(Criteria criteria) {
calculateScore(criteria);
if (doesNotMeetAnyMustMatchCriterion(criteria))
return false;
return anyMatches(criteria);
}
private boolean doesNotMeetAnyMustMatchCriterion(Criteria criteria) {
for (Criterion criterion: criteria) {
boolean match = criterion.matches(answerMatching(criterion));
if (!match && criterion.getWeight() == Weight.MustMatch)
return true;
}
return false;
}
現在、新しい方法と重複文はそれぞれ3つになっています.パフォーマンスのメリットを分析してみましょう.1.奨励:明確かつテスト可能な単位
matches()メソッドは,アルゴリズム全体がきちんと整理されていることをすぐに理解できるようになった.現在のコードは、次の順序のアルゴリズムに従います.
2.性能の心配:心配する必要はない
matches()メソッドを再パッケージすると、AnyteMatches()、CalculateScore()、donotmeetAnyMustMatchCriterion()メソッドの各メソッドに標準条件の重複文があります.3つの新しい重複文マッチング()メソッドの潜在的な実行時間は、元の4倍です.
パフォーマンスに直ちに問題が発生しない場合は、最適化に時間を費やすのではなく、コードを清潔に保つ必要があります.最適化されたコードは多くの点で問題があります.通常、コードの可読性が低く、メンテナンスコストが高く、設計も柔軟ではありません.
逆に、簡潔な設計は、パフォーマンスを最適化するときにすぐに応答することができます.シンプルな設計は、モバイルコードの柔軟性を提供し、他のアルゴリズムの適用も容易です.
パフォーマンスに直ちに問題が発生した場合、他のことをする前に、まず問題の深刻さを測定し、その後、以前のコードの速度がどれだけ速いかを決定するために小さなテストコードを作成し、再コンパイル後のコードを判断し、比較します.
リファレンス
Reference
この問題について(8.簡潔なコードで再包装), 我々は、より多くの情報をここで見つけました https://velog.io/@jsj3282/8.-깔끔한-코드로-리팩토링-하기テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol