一時変数と行の長さの要件


10

Martin Fowlerのリファクタリングを読んでいます。それは一般的に優れていますが、ファウラーの推奨の1つが少し問題を引き起こしているようです。

Fowlerは、一時変数をクエリに置き換えることを推奨しているため、これの代わりに:

double getPrice() {
    final int basePrice = _quantity * _itemPrice;
    final double discountFactor;
    if (basePrice > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice * discountFactor;
}

あなたはヘルパーメソッドに引き出します:

double basePrice() {
    return _quantity * _itemPrice;
}

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice() * discountFactor;
}

一般的に私は同意しますが、一時変数を使用する理由の1つは、行が長すぎる場合です。例えば:

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

それをインライン化しようとすると、行が80文字より長くなります。

代わりに、コードチェーンができますが、それ自体はそれほど読みやすくはありません。

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

2つを調整するためのいくつかの戦略は何ですか?


10
80文字は、私のモニターの1の約3分の1です。80文字の行に固執することがあなたにとってまだ価値があると確信していますか?
jk。


あなた$host$uriホストが設定または他の入力から読み込まれていない限り、私はそれがラップをしていても、それらが同じ行にあることを好むか、エッジをオフに行くだろう-ものの例は、一種工夫のです。
イズカタ2013年

5
それほど独断的である必要はありません。この本は、役立つときに使用できるテクニックのリストであり、いつでもどこでも適用する必要がある一連のルールではありません。ポイントは、コードをより保守しやすく読みやすくすることです。リファクタリングがこれを行わない場合は、使用しません。
Sean McSomething 2013年

80文字の制限は少し過剰だと思いますが、同様の制限(100?)は妥当です。たとえば、縦向きのモニターでプログラミングすることがよくあるので、余分な長い行が煩わしい場合があります(少なくともそれらが一般的な場合)。
Thomas Eding 2013

回答:


16

方法
1.行の長さに制限があるため、より多くのコードを表示して理解できます。それらはまだ有効です。
2. ブラインドコンベンションに対する判断を強調します。
3. パフォーマンスを最適化しない限り、一時変数は使用しないでください。
4.複数行のステートメントでの配置に深いインデントを使用しないでください。
5.長いステートメントをアイデアの境界に沿って複数の行に分割します。

// prefer this
var distance = Math.Sqrt(
    Math.Pow(point2.GetX() - point1.GetX(), 2) + // x's
    Math.Pow(point2.GetY() - point1.GetY(), 2)   // y's
);

// over this
var distance = Math.Sqrt(Math.Pow(point2.GetX() -
    point1.GetX(), 2) + Math.Pow(point2.GetY() -
    point1.GetY(), 2)); // not even sure if I typed that correctly.

推論
一時変数に関する私の(デバッグ)問題の主な原因は、それらが変更可能になる傾向があることです。つまり、コードを作成するときにそれらは1つの値であると想定しますが、関数が複雑な場合、他のコードの一部が途中で状態を変更します。(または、逆の場合、変数の状態は同じままですが、クエリの結果が変更されます)。

パフォーマンスを最適化するのでない限り、クエリを使用することを検討してください。これにより、その値の計算に使用したロジックが1か所に保持されます。

あなたが与えた例(Javaと... PHP?)はどちらも複数行のステートメントを許可しています。行が長くなった場合は、分割してください。 jqueryソースはこれを極端にします。(最初のステートメントは69行目まで実行されます!)必ずしも同意するわけではありませんが、一時変数を使用する以外にコードを読みやすくする方法は他にもあります。

いくつかの例
1. PythonのPEP 8スタイルガイド(最も美しい例ではありません)
2. PearスタイルガイドのPaul M Jones(道路の真ん中の引数)
3. Oracleの行の長さ+折り返しの規則(80文字に保つための便利な戦略)
4. MDN Javaプラクティス(慣習よりもプログラマーの判断を強調)


1
問題のもう1つの部分は、一時変数がその値よりも長持ちすることが多いことです。小さなスコープブロックでは問題ではありませんが、大きなブロックではそうです、大きな問題です。
ロスパターソン

8
一時的な変更が心配な場合は、constを付けてください。
Thomas Eding 2013年

3

一時的な変数の代わりにヘルパーメソッドを使用するための最良の議論は、人間の読みやすさです。人間として、一時的な変数よりヘルパーメソッドチェーンの読み取りに問題がある場合、それらを抽出する必要がある理由はわかりません。

(間違っている場合は修正してください)


3

80文字のガイドラインに厳密に従う必要はなく、ローカルの一時変数を抽出する必要もないと思います。ただし、同じ考えを表現するためのより良い方法については、長い列や地域の臨時雇用者を調査する必要があります。基本的に、これらは特定の関数または行が複雑すぎるため、それを分解する必要があることを示しています。ただし、タスクを不適切な方法で分割すると状況が複雑になるだけなので、注意が必要です。だから私は物事を再利用可能でシンプルなコンポーネントに分解するつもりです。

あなたが投稿した例を見てみましょう。

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

私の観察では、すべてのtwilio api呼び出しは「https://api.twilio.com/2010-04-1/」で始まるため、非常に明白な再利用可能な関数が必要です。

$uri = twilioURL("Accounts/$accountSid/Usage/Records/AllTime")

実際、URLを生成する唯一の理由はリクエストを行うことであると考えているので、次のようにします。

$response = TwilioApi::makeRequest("Accounts/$accountSid/Usage/Records/AllTime")

実際、URLの多くは実際には「Accounts / $ accountSid」で始まるので、おそらくそれも抽出します。

$response = TwilioApi::makeAccountRequest($accountSid, "Usage/Records/AllTime")

そして、twilio apiをアカウント番号を保持するオブジェクトにすると、次のようになります。

$response = $twilio->makeAccountRequest("Usage/Records/AllTime")

$ twilioオブジェクトを使用すると、単体テストが簡単になるという利点があります。オブジェクトに、実際にはtwilioを呼び出さない別の$ twilioオブジェクトを与えることができます。これにより、twilioが高速になり、奇妙なことを行わなくなります。

もう一つ見てみましょう

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

ここで私はどちらかについて考えます:

$params = MustacheOptions::buildFromParams($bagcheck->getParams());

または

$params = MustacheOptions::build($bagcheck->getFlatParams());

または

$params = MustacheOptions::build(flatParams($backCheck));

どちらがより再利用可能なイディオムかによって異なります。


1

実際、一般的なケースではこれについて著名なファウラー氏に反対します。

以前にインライン化されたコードからメソッドを抽出する利点は、コードの再利用です。メソッド内のコードは最初の使用から切り離され、コピーして貼り付けることなく、コードの他の場所で使用できるようになりました(コピーされたコードの一般的なロジックを変更する必要がある場合は、複数の場所で変更を加える必要があります)。 。

ただし、同等で、しばしばより大きな概念的価値は「価値の再利用」です。ファウラー氏は、これらの抽出されたメソッドを呼び出して、一時変数を「クエリ」に置き換えます。さて、何がより効率的ですか。特定の値が必要な複数回のたびにデータベースにクエリを実行するのか、それとも一度クエリを実行して結果を保存するのか(値が変化しないと予想されるほど静的であると想定)

あなたの例の比較的些細なものを超えるほとんどすべての計算について、ほとんどの言語では、1つの計算の結果を保存しておく方が、計算を続けるよりも安上がりです。したがって、オンデマンドで再計算するという一般的な推奨は不誠実です。これにより、開発者の時間とCPU時間が増え、メモリの節約にもなります。これは、最近のほとんどのシステムでは、これら3つの中で最も安価なリソースです。

これで、ヘルパーメソッドを他のコードと組み合わせて、「遅延」させることができます。最初の実行時に変数を初期化します。それ以降のすべての呼び出しでは、メソッドが明示的に再計算するように指示されるまで、その変数が返されます。これは、メソッドのパラメーター、またはこのメソッドの計算が依存する値を変更する他のコードによって設定されたフラグである可能性があります。

double? _basePrice; //not sure if Java has C#'s "nullable" concept
double basePrice(bool forceCalc)
{
   if(forceCalc || !_basePrice.HasValue)
      return _basePrice = _quantity * _itemPrice;
   return _basePrice.Value;
}

さて、この簡単な計算では、保存するよりもさらに多くの作業が実行されるため、一般的にはtemp変数を使用することをお勧めします。ただし、通常は複数回実行したくない、コードの複数の場所で必要な、より複雑な計算の場合は、この方法を使用します。


1

ヘルパーメソッドには場所がありますが、データの一貫性を確保し、変数のスコープを不必要に拡大することに注意する必要があります。

たとえば、あなた自身の例が引用します:

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;      <--- first call
    else discountFactor = 0.98;
    return basePrice() * discountFactor;                <--- second call
}

明らかに_quantity_itemPriceはグローバル変数(または少なくともクラスレベル)であるため、これらの変数は外部で変更される可能性があります。getPrice()

したがって、最初の呼び出しbasePrice()が2番目の呼び出しとは異なる値を返す可能性があります。

したがって、ヘルパー関数は複雑な数学を分離するのに役立つ可能性があることをお勧めしますが、ローカル変数の代わりとして、注意する必要があります。


また、不条理還元を回避する必要があります-の計算をdiscountFactorメソッドに割り込む必要がありますか?だからあなたの例は次のようになります:

double getPrice()
{
    final double basePrice      = calculateBasePrice();
    final double discountFactor = calculateDiscount( basePrice );

    return basePrice * discountFactor;
}

特定のレベルを超えて分割すると、実際にはコードが読みにくくなります。


コードを読みにくくするための+1。過度のパーティション分割は、ソースコードが解決しようとしているビジネス上の問題を隠すことができます。クーポンがgetPrice()で適用される特別な場合がありますが、それが関数呼び出しのチェーンの奥深くに隠されている場合、ビジネスルールも隠されます。
Reactgular 2013年

0

名前付きパラメーター(ObjectiveC、Python、Rubyなど)を持つ言語で作業する場合、一時変数はあまり役に立ちません。

ただし、basePriceの例では、クエリの実行に時間がかかる場合があり、結果を一時変数に保存して後で使用することができます。

しかし、あなたのように、私は明確さと行の長さを考慮して一時変数を使用しています。

プログラマーがPHPで次のことをするのを見たこともあります。興味深くデバッグに最適ですが、少し奇妙です。

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs

0

この推奨の背後にある理論的根拠は、アプリケーションの他の場所で同じ事前計算を使用できるようにすることです。リファクタリングパターンカタログのTempをQuery置き換えるを参照してください。

その後、新しいメソッドを他のメソッドで使用できます

    double basePrice = _quantity * _itemPrice;
    if (basePrice > 1000)
        return basePrice * 0.95;
    else
        return basePrice * 0.98;

           http://i.stack.imgur.com/mKbQM.gif

    if (basePrice() > 1000)
        return basePrice() * 0.95;
    else
        return basePrice() * 0.98;
...
double basePrice() {
    return _quantity * _itemPrice;
}

したがって、ホストとURIの例では、同じURIまたはホスト定義を再利用する予定がある場合にのみ、この推奨事項を適用します。

これに該当する場合は、名前空間が原因で、グローバルuri()またはhost()メソッドを定義しませんが、twilio_host()やarchive_request_uri()などのより詳細な名前を定義します。

次に、行の長さの問題について、いくつかのオプションが表示されます。

  • のようなローカル変数を作成しますuri = archive_request_uri()

理論的根拠:現在のメソッドでは、URIを前述の方法にする必要があります。URI定義はまだ因数分解されています。

  • 次のようなローカルメソッドを定義します。 uri() { return archive_request_uri() }

Fowlerの推奨を頻繁に使用する場合、uri()メソッドが同じパターンであることがわかります。

言語の選択のために 'self。'でローカルメソッドにアクセスする必要がある場合、表現力を高めるために最初のソリューションをお勧めします(Pythonでは、現在のメソッド内でuri関数を定義します)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.