私はこのケースにいくらか出会うことがよくありますが、ウェブ上で似たような議論がほとんどないことに驚いています。 この質問 は非常に関連していますが、私の問題は、より一般的な"do X if Y"ではなく"do X if必要」。そのリンクの答えは、プレフィックスEnsure
を使用することですが、Xがメソッドの意図でないことを確認すると、Wordは適合しません。
私が考えているシナリオはこれです:
void mayPerformAction() {
// Do some preparatory calculations
// ...
if (shouldPerform) {
// Perform action
// ...
}
}
2つの別々のメソッド(shouldPerformAction()
とperformAction()
)を使用していない理由は、条件とアクションの両方がいくつかの準備計算に依存しているためです。今、私の質問は:メソッドの最も論理的で読みやすい名前は何ですかmayPerformAction()
?
明確にするために、アクションが時々not実行される可能性があることを呼び出し側にとって重要です。それ以外の場合は、performAction()
を使用するのが理にかなっています。
これは一種のXY問題であり、複数の解決策が投稿されていることは認めます。要約する:
performAction()
。if (shouldPerform()) performAction()
。最良のアプローチは、条件がどれほど「深刻」であり、準備計算がどれほど高価であるかにかかっているように思います。そのため、今のところ質問には答えません。
あなたは構造的な考え方に囚われています。名前は実装の詳細を抽象化する必要があります。彼らにとって、それは短い手になってはならない。
IfBirthdayBuyCake();
ひどい名前です。ここに裸で書いたかのように、実装の詳細をひどく公開します。
私はより良い名前とより良い抽象化に手を伸ばすでしょう。
celebrateHoliday();
どこかでこのようなものを見つけるでしょう
if ( birthday() ) {
buyCake();
}
そしておそらく他のいくつかの休日。あなたがそれを呼ぶとき、それはまったく何もしないかもしれません。つまり、電話をかけるときに、今日が休日かどうかを知る必要はありません。知らないことはその詳細からあなたを解放します。それがいい名前です。
メソッド名に条件を含めることはお勧めしません。呼び出しコードを条件付きで読み取る場合は、use a conditional:
_if (cakeIsNeeded) buyCake();
_
または、3項演算子または短絡回路が必要な場合:
_cake = cake == null ? buyCake() : cake;
cake = cake || buyCake();
_
そうでない場合は、サイレントに繰り返し呼び出しを無視するか、 memoization を使用するか、メソッドで例外をスローして繰り返し呼び出しを処理します—特定のメソッドに最も適切で 最小の驚き 。
wouldのように繰り返し呼び出すようなメソッド名がある場合、アクションを再実行しますが、wo n'tは、「驚き"は、オプションのforce
、skipCache
、または同様のブールパラメータを追加することです。 (フラグの名前は、メソッド名やスキップロジックに関連している必要があります。)
そうは言っても、メソッドdoes.ではなく、呼び出し元needsを意味する動詞を探す傾向があります。/=/=/ 、呼び出し元はcake
を望んでおり、それがどこから来たかはあまり気にしません。 getCake()
またはfindCake()
を使いたいような気がします。
これらの名前はどちらもCake
が返されることを伝えています。それらは、Cake
がどのように配置されるかを発信者に明らかにしません。購入したり、カウンターから入手したり、魔法のエルフが作成したりできます。これらは実装の詳細です。
これらすべてに対する重要な警告の1つ:これらの命名パターンは非常に慣用化されている傾向があります。theyがこれを処理する方法の例については、言語の内部ライブラリを参照してください。また、チームに相談して、自分のinternalイディオムを決定してください。
それでも二重のコード実行を防ぐより良い代替案がある場合は、遠慮なくお知らせください:)
preparatory calculations
は非常に高価であり、2回計算するとパフォーマンスが大幅に低下します(そのため、 時期尚早の最適化 の場合ではありません)条件とアクションを2つのメソッドに分離します。
if (shouldPerform()) doPerformAction()
はるかに直感的です。状態計算に対処するには、preparatory calculations
を次のようにクラスの状態に入れます。
public class MyClass {
private CalculationResult preparatoryCalculationResult = null;
public boolean shouldPerform() {
initIfNeccessary();
return preparatoryCalculationResult.shouldPerform();
}
public void doPerformAction() {
initIfNeccessary();
preparatoryCalculationResult.doPerformAction();
}
private void initIfNeccessary() {
if (preparatoryCalculationResult == null) {
preparatoryCalculationResult = 42; // very expensive calculation ;-)
}
}
}
「performActionIfNeeded」(アクションが必要かどうかを巧みに判断し、その場合にのみ実行するメソッド)と「performActionIfWanted」(アクションを実行し、実行のみを行うことを巧みに判断するメソッド)が合理的に共通しています。その場合)。
続けて:
明確にするために、アクションが実行されない場合があることが発信者にとって重要です。
発信者は何が起こるかについてある程度の知識を持っているようです。したがって、呼び出し側がアクションを実行するかどうかを決定できることは保証されていると思います。
例:
関数は、パターンに一致するすべてのファイルを削除する必要がありますが、それらの合計サイズが何らかの値を超えた場合のみです。スキャンは高価ですが、状態を確認するために実行する必要があります。削除アクションでは、スキャンが2回行われないようにするために、スキャンに関する知識が必要です。
私は単にこれを2つの方法に分割します。
// Returns a ScanResult containing files matching a pattern
ScanResult performScan(Pattern pat);
// Deletes a list of files
void delete(List<File> files);
これで条件を簡単に外部化できます。
ScanResult result = obj.performScan(...)
if(result.totalSize() > 100 * 1024) {
obj.delete(result.getFiles());
}
問題の一部は、元のメソッドのvoid
戻り型です。成功の程度が異なることを示すには、何らかの結果の構造体オブジェクトを呼び出し元に返すことをお勧めします。これにより、メッセージをUIまたは類似したものに戻す必要がある場合に備えて、何が起こったか、何が起こらなかったかに関する情報が発信者に提供されます。また、呼び出されたメソッドがすべての呼び出しで重い操作を実行しない可能性があることをコードに触れるように、次のコーダーにすばやく/明確に通知します。
public class OperationResult
{
public OperationState State { get; set; }
public string OperationMessage { get; set; } // result codes, etc
public enum OperationState { NoOperationPeformed, OperationSuccessful } // can have more if needed
}
OperationMessage
(または呼び出しに関するその他のデータ)が必要な場合は、上記のクラス全体を返すか、関数の「実際の」動作を通知するだけの場合はenum OperationState
のみを返すことができます。スキップされました。
仲間のコーダーは、そのvoid
戻りから、メソッドが常に同じことを行うと想定する可能性があります。上記を返すことは、IDE intellisenseを通じて、他の可能性があることを示すのに役立ちます。