web-dev-qa-db-ja.com

リファクタリング:一時変数をクエリに置き換えます。このコード例は悪い習慣ですか?

質問する前に、これは自分のコードではないことに注意してください。 Refactoring Gur Webサイトから取得します。


enter image description here


私はしばしば左側のコードを使用していることに気づき、そのための主な動機が1つあります。右側のコードでは、同じ操作のスタックフレームを不必要に2回続けて押したりポップしたりしています。たとえば、基本価格が実際に1000を超えると仮定します。この条件付きのテストでは、基本価格がすでに計算されています...したがって、その条件付きの本体に到達するまでに、値がわかっています。 basePrice()を2回呼び出して再計算しても意味がありません。どちらの場合でも、基本価格が1000を超えるか、1000以下の場合、basePrice()への余分な呼び出しが必要になります。

質問:右側のコードは左側のコードよりも実際に利点がありますか?このサイトでは、他の方法でも基本価格を計算したい場合に備えて、コードの再利用性を高めることができると述べています。

7
AleksandrH

リファクタリングはベストプラクティスではありません。

リファクタリングは、外部から見える動作を変更せずにコードを変更する方法です。単一の適切に記述された単体テストは、触れることなく、左のコードと右のコードの両方に合格できるはずです。

左側のコードが常に問題であるかのように、この投稿を読んでいます。そうではありません。時々、右側のコードが問題です。これは、左側のコードを問題と見なした場合に実行できることを示しています。これは、右側のコードにも問題がないことを保証するものではありません。

右側のコードでは、同じ操作のスタックフレームを不必要に2回続けてプッシュおよびポップしています。

あなたは実際にそれを知りません。

2回呼び出す必要があるように見えますが、それがどのように記述され、コンパイラー/インタープリターが使用されているかによっては、最適化されてしまう可能性があります。

そうでない場合でも、計算が本当に重要でない限り、キャッシングによるパフォーマンスの向上は心配する価値がありません。

左側のコードの方がわかりやすいと主張する人もいます。右側のコードは実行する処理が少ないため、1つの処理に重点を置く方がよいと主張する人もいます。

基本価格への依存を外部化し、渡される明示的なパラメーターにすることで、どちらも改善できると私は主張します。これはさらに別のリファクタリングです。

それを行うことは客観的にリファクタリングです。それが良いという考えは主観的な意見です。主観的な選択をするときは、チームに尋ねてください。彼らはそれと共存しなければならないものです。

20
candied_orange

私にとって、ローカル変数を使用してクエリ結果(オブジェクトの状態のクエリ)をキャプチャすると、何か重要なことがわかります。つまり、これらのフィールド値のクエリを1つだけ実行して(1つの値を計算する)、私のロジックはその1つの値。これは明確でシンプルだと思います。

一方、フィールドへのアクセスを繰り返すと、直接でもメソッド呼び出しでも、これらの値が変わる可能性があると言われています。常に最新のものを使用するようにします。

残念ながら、この場合、if-conditionとthen-partまたはelse-partの間でフィールドが変更されると、このコードは実際には処理しません。このため、このようなコードは混乱を招きます。ここでロジックが基になるフィールドの変更を処理できない場合、なぜ作成者はクエリを繰り返す必要があると感じたのですか?

全体として、@ Robertが示唆しているように、クエリ計算をメソッドにリファクタリングしたローカル変数を引き続き使用できます。 (そして、上記のb/cになります。)

このリファクタリングを行うと、なぜ1つの乗算が独自の方法にされるのに、ある大きさに基づいて選択された乗算の割合がそうでないのかという疑問が生じます。これは、ある意味での改善点です。このメソッドは、ベース合計(他の場所で計算されたもの)から割引合計を計算します。つまり、コードにエンコードされたルールです。

ビジネスロジックはさまざまなキャンペーンやプロモーションによって変更される可能性があるため、次の論理的なアップグレードは、ifと定数をコードでハードコードするのではなく、何らかの方法でこのようなルールを実際に外部化(ランタイム置換可能)する必要があることです。

7
Erik Eidt

Candied_orangeの回答には同意しますが、ここで説明した特定のリファクタリングにより、検出が困難なバグや不安定性が発生する可能性があることに注意します。

与えられたリファクタリングは、quantityitemPriceの値がメソッドの実行中に変更されないことが保証されている場合にのみ、同等であることがわかります。可能性がある場合、ローカル変数を使用することは、単なる「ベストプラクティス」ではなく、正確さのために必要です。

basePrice()メソッドの作成とローカル変数の削除は、2つの異なる変更であることに注意してください。このメソッドの導入は、ローカル変数またはその逆の削除を示すものではありません。

なぜ値が変わるのですか?メソッドとしてすでに実装されていると仮定します。メソッドを繰り返し呼び出すと同じ結果になるかどうか知っていますか?おそらくbasePrice()メソッドには、価格の検索呼び出しが含まれています。最終的に、ifステートメントではbasePriceが1000を超えますが、割引の計算時には1000未満になります(またはその逆です)。ユニットテストはこの問題をキャッチしますか?このシナリオに特定のテストを設定しない限り、おそらく設定しません。

または、マルチスレッド環境の場合を検討してください。これらの値が他のスレッドによって変更される可能性のある値への参照である場合、同様の問題が発生する可能性があります。繰り返しになりますが、単体テスト(またはその他のテスト)では見つけるのが困難です。

これに対処する方法があります。これらの値を最終/変更不可にする(強くお勧めします)ここでの要点は、呼び出しを複数回行うことは常に間違っているわけではありませんが、そのような変更は本質的に同等ではないことを理解する必要があります。プログラム全体のコンテキストでこのような変更を考慮する必要があります。

3
JimmyJames

私が右側のコードを使用する主な理由は、それがnit-testing basePrice()を許可することです。

単体テストに値するものですか?多分;特定の詳細に大きく依存します。

2
Roger

これはリファクタリングに関するものです。つまり、その意味を変更せずにコードを変更します。左から右へ、または右から左へ、どちらでも好きなようにリファクタリングできます。

実際、ここには2つのリファクタリングがあります。式を関数で置き換えることと、式を格納する変数を(式または関数の)複数の評価で置き換えることです。どちらも良いこともあれば悪いこともあります。式が複雑で明白でないほど、また、使用される場所が多いほど、関数を使用するのが良くなります。また、評価に時間がかかるほど、結果を変数に格納する方が適切です。また、関数呼び出しがべき等であるか、別の値を返す可能性があるかという質問にも答えます。関数の結果がローカル変数に格納されている場合、変更されません。しかし、time()やrandom()のように変化することが予想される場合は、複数の呼び出しが必要になる場合があります。か否か。

1
gnasher729