2017-12-18 12 views
7
var a = 1; 
function myFunction() { 
    ++a; 
    return true; 
} 

// Alert pops up. 
if (myFunction() && a === 2) { 
    alert("Hello, world!") 
} 

// Alert does not pop up. 
if (a === 3 && myFunction()) { 
    alert("Hello, universe!") 
} 

https://jsfiddle.net/3oda22e4/6/条件の中でものを変更する関数を使用して、条件を順序に依存させるのは悪い習慣ですか?

myFunction変数をインクリメントし、何かを返します。インクリメントする変数を含むifステートメントのような関数を使用すると、条件は順序に依存します。

これを行うのは良いか悪い習慣ですか?なぜですか?

+4

わかりやすくするために、私はそのような副作用を避けるようにしたいと思います。しかし、より詳細なユースケースを提供すると役に立つかもしれません。 – msrd0

+3

コードはほとんど実用的ではありません。非常に多くの点でそれは説明を無視して悪いです。それはあなたが(1)Abstractionについて聞いたことがないかのようです。 ... (2)継承。 ... (3)多型。関数を呼び出してジョブを実行し、必要なときに呼び出します。この関数は外部的に一貫した答えを返さなければなりません。 –

+3

@JonGoodwinこれは継承または多型のどちらと関係がありますかわかりません。 – Bergi

答えて

12

条件は、条件で使用する変数を変更するかどうかにかかわらず、順序に依存します。例として使用した2つのifステートメントは異なり、myFunction()を使用するかどうかにかかわらず異なります。彼らは同等である:私の意見では

if (myFunction()) { 
    if (a === 2) { 
    alert("Hello, world!") 
    } 
} 

// Alert does not pop up. 
if (a === 3) { 
    if (myFunction()) { 
    alert("Hello, universe!") 
    } 
} 

、あなたのコード内の悪い習慣はあなたが条件内で条件のオペランドの値を変更することは事実ではありませんが、あなたのアプリケーションの状態を内部に露出して操作されているという事実この状態変化変数をパラメータとして受け付けない関数もあります。私たちは通常、そのスコープ外のコードから関数を分離し、戻り値を使用して残りのコードに影響を与えようとします。グローバル変数は時間の90%であり、コードベースが大きくなるにつれてトレース、デバッグ、解決が困難な問題が発生する傾向があります。

+2

これを追加するには、条件の順序依存が最適化に役立つことがあります。我々はそれを短絡と呼んでいる。私は[この質問](https://stackoverflow.com/questions/9344305/what-is-short-circuiting-and-how-is-it-used-when-programming-in-java)の回答を読むことをお勧めします短絡評価の簡単な説明。 –

6

それは悪い習慣は、次の理由のために、です:

  • コードは、よく構成されたコードよりもはるかに少ない読み取り可能です。コードが後で第三者によって検査される場合、これは非常に重要です。

  • myfunctionが後で変更された場合、コードフローは完全に予測できず、大きな文書の更新が必要になることがあります。

  • 小さくて単純な変更は、コードの実行に大きな影響を与える可能性があります。

  • アマチュアに見えます。

+0

これは非常に曖昧です。あなたが改善のための具体的な提案をしたなら、あなたの答えはより良いでしょう。 – TinkerTenorSoftwareGuy

5

尋ねる必要がある場合は、ほとんど実践的なことではありません。はい、あなたが言及した理由はまあまあ悪い習慣です:論理演算のオペランドの順序を変えることは結果に影響を与えてはならないので、一般に条件の副作用は避けるべきです。特にそれらが関数に隠されているとき。

関数が純粋である(状態を読み込むだけで何らかのロジックを実行する)かどうか、または状態を変更するかどうかは、その名前から明白になるはずです。あなたはこのコードを修正するには、いくつかのオプションがあります:if前に、関数呼び出しを入れ

  • を:

    function tryChangeA() { 
        a++; 
        return true; 
    } 
    
    var ok = tryChangeA(); 
    if (ok && a == 2) … // alternatively: if (a == 2 && ok) 
    
  • if内の変異が明示的に行います。

    function testSomething(val) { 
        return true; 
    } 
    
    if (testSomething(++a) && a == 2) … 
    
  • 入れます呼び出される関数内のロジック:

    function changeAndTest() { 
        a++; 
        return a == 2; 
    } 
    
    if (changeAndTest()) … 
    
+0

私は実際にあなたの最初の提案は良いものだと思う。 (ifの前に関数呼び出しを入れてください) 他の2つはまだ状態の変化でテストを混ぜます。 述語は決して状態を変更すべきではありません。 –

+1

@TiagoCoelho私は最初のものが最高であることに同意しますが、私は絶対に "決して"と言っていません。確かに 'changeAndTest'はもはや純粋な述語ではありませんが、条件の中で' regex.match(string) 'や' i - 'のようなことをするパターン(特にループ)があります。 'const ok = changeAndTest(); if(ok)... 'を実行します。 – Bergi

3

コードは、実際には1つの悪い習慣、より多くを提示:

var a = 1; 
function myFunction() { 
    ++a; // 1 
    return true; 
} 

if (myFunction() && a === 2) { // 2, 3, 4 
    alert("Hello, world!") 
} 

if (a === 3 && myFunction()) { // 2, 3, 4 
    alert("Hello, universe!") 
} 
  1. は異なるスコープ内の変数を変異させます。これは問題かもしれませんが、通常は問題です。

  2. ifステートメントの条件内で関数を呼び出します。 これは問題を引き起こすことはありませんが、それは本当にきれいではありません。 その関数の結果を、おそらくわかりやすい名前の変数に代入する方が良い方法です。これは、コードを読んで誰でも、そのifステートメントの中で正確に何をチェックしたいのかを理解するのに役立ちます。ちなみに、関数は常にtrueを返します。

  3. いくつかのマジックナンバーを使用します。 誰かがそのコードを読んでいると想像してください。大きなコードベースの一部です。その数字は何を意味していますか?より良い解決策は、名前のついた定数でそ​​れらを置き換えることです。

  4. さらに多くのメッセージをサポートする場合は、さらに条件を追加する必要があります。 これを構成可能にする方がよいでしょう。

次のように私は、コードを書き換えることになる:

const ALERT_CONDITIONS = { // 4 
    WORLD_MENACE: 2, 
    UNIVERSE_MENACE: 3, 
}; 

const alertsList = [ 
    { 
    message: 'Hello world', 
    condition: ALERT_CONDITIONS.WORLD_MENACE, 
    }, 
    { 
    message: 'Hello universe', 
    condition: ALERT_CONDITIONS.UNIVERSE_MENACE, 
    }, 
]; 


class AlertManager { 
    constructor(config, defaultMessage) { 
    this.counter = 0; // 1 
    this.config = config; // 2 
    this.defaultMessage = defaultMessage; 
    } 

    incrementCounter() { 
    this.counter++; 
    } 

    showAlert() { 
    this.incrementCounter(); 
    let customMessageBroadcasted = false; 
    this.config.forEach(entry => { //2 
     if (entry.condition === this.counter) { 
     console.log(entry.message); 
     customMessageBroadcasted = true; // 3 
     } 
    }); 

    if (!customMessageBroadcasted) { 
     console.log(this.defaultMessage) 
    } 
    } 
} 

const alertManager = new AlertManager(alertsList, 'Nothing to alert'); 
alertManager.showAlert(); 
alertManager.showAlert(); 
alertManager.showAlert(); 
alertManager.showAlert(); 
  1. 代わりにいくつかの変数に依存している機能の束のそれ自身の内部状態を使用して正確な機能を持つクラスを、どこにでも置くことができます。クラスを使用するかどうかは、選択の問題です。それは別のやり方で行うことができます。

  2. 構成を使用します。つまり、メッセージを追加したいのですが、コードに触れる必要はありません。たとえば、その構成がデータベースから来ているとします。

  3. これは、関数の外側スコープの変数を変更しますが、この場合は問題は発生しません。

  4. 明確な名前の定数を使用します。 (まあ、それが良いかもしれませんが、私の場合は、例を与えてください)。

+0

私は、関数が本当に純粋な関数である限り、条件文の一部として述語として関数呼び出しを使用することに何か問題があるとは思わない。関数の結果を格納する場所としてのみローカルシンボルを導入することは、はるかに悪いようです。 – Pointy

+0

私の意見では、条件文での関数呼び出しの使用は混乱しています。私は、記述的な名前の変数に結果を代入することを好む。次に例を示します。 'const hasFuel = driveCar(1000); if(!hasFuel){ refill(); } ' –

+0

これは基本的な問題を解決するためにOOPを必要としません。 OOPを打破する前に、問題の中核を見てください。元の問題は関数型プログラミングで解決できます。アーキテクチャを追加することは別の問題です。 – TinkerTenorSoftwareGuy

5

MyFunctionは、Tell, Don't Askという原則に違反します。

MyFunctionは何かの状態を変更し、コマンドにします。 MyFunctionが成功した場合、または何らかの形でaをインクリメントできない場合は、trueまたはfalseを返してはなりません。それは仕事を与えられ、それは成功するか、現時点でその仕事が不可能であることが分かった場合には、例外を投げるべきです。

ifステートメントの述語では、MyFunctionがクエリとして使用されます。

一般的に言えば、クエリは副作用を示すべきではありません(つまり、観察できるものは変更されません)。良いクエリは、同じ入力に対して同じ出力を生成する必要があるという点で、計算のように扱うことができます(「冪等元」として記述されることもあります)。

これらは、あなたと他の人がコードについて理由を知る手助けとなるガイドラインであることを理解することも重要です。コードが混乱の原因となります。になります。コードについての混乱はバグの孵化です。

あなたのコード例のように使用できるTrier-Doerパターンのような良いパターンがありますが、それを読んでいる人は名前と構造に何が起こっているのかを理解していなければなりません。

+0

あなたはmoveNext()データベース関数で本当に苦労するつもりです。内部ポインタを操作し、呼び出されるたびに異なる結果を返します。 – danny117

1

ものを変更する機能。世界には何が来ていますか?この関数はstuffを変更し、呼び出されるたびに異なる値を返す必要があります。

トランプカードのデッキにはdealCard機能を考慮してください。それはカード1-52を扱う。呼び出されるたびに、別の値を返す必要があります。

function dealCard() { 
    ++a; 
    return cards(a); 
} 

/*私たちは簡潔にするために、我々はデッキが無限大であると52 */

でループしないと仮定します*/*/

をシャッフルされたアレイカードを仮定します

+0

電話がかかったのでキッチンの整理をしたくないということが私の主張でした。しかし、もし私がクリーニングサービスを手配していると、彼らは15分で終わると言っている、それは別の話です。この場合、 'a'が' dealCards'に属していて、 'a'を変更してそのメソッドを呼び出すと誰もが問題ないです。 Rubyの配列シャッフルは、シャッフルされたまったく新しい配列を提供するだけなので、混乱するようなものでもありません。 – Greg

+0

Greg。順序依存のより多くの関数。 getNationalDebt( "USA");時間();イケア(「キッチン」); – danny117

+0

これらのすべては、私が推論し信頼できるようにテストすることができる、予測可能で信頼性の高いプログラムへのインプットでなければなりません。 – Greg

関連する問題