web-dev-qa-db-ja.com

前のメソッドから次のメソッドを呼び出す...なぜこれが悪いデザインなのですか?

createWorld()が本当に長く、分割する必要がある場合は、createLight()createEarth()createPlants()、およびcreateAnimals()

だから、当然私はします:

_function createLight(){
    //work 1
}
function createEarth(){
    //work 2
}
function createPlants(){
    //work 3
}
function createAnimals(){
    //work 3
}

function createWorld(){
    createLight()
    createEarth()
    createPlants()
    createAnimals()
}
_

しかし、私は多くの新しい開発者が私が暫定的に「Stairstepアンチパターン」と呼んでいることをしているのを見ています。それは次のようになります:

_function createWorld(){
    //create light
    //work1
    createEarth()
}
function createEarth(){
    //work 2
    createPlants()
}
function createPlants(){
    //work 3
    createAnimals()
}
function createAnimals(){
    //work 4
}
_

これはもっと悪いことだと確信していますが、私の意見だけで同僚を揺さぶることはできないと思います。 1つのステップがcreateButterfly()である場合、それが「次のステップ」であるという理由だけで、その関数の最後にcreateGrasshopper()を呼び出すビジネスがないと思います。

また、createButterflyThenCallCreateGrasshopper()という名前を付けても、createButterflyコードをうまくテストできず、いくつかのステップがあると、関数が呼び出されている場所がわかりにくくなるので、デザインはまだお粗末です。

私はこれをStairstepアンチパターンと呼んでいます。

_|
|createWorld(){
              |
              |createEarth(){
                            |
                            |createPlants(){
                                           |createAnimals()
_

これは悪いデザインだと私は思いますか?これはなぜ悪いデザインなのですか?

7
AwokeKnowing

#2 createEarthcreatePlantsの明らかな事実に加えて、彼らの名前が彼らがしていることを偽っていないこと、ここでの主な欠陥は 単一レベルの抽象化 原則:

createEarthは、抽象化なしで直接いくつかの作業を行い、次にcreatePlantsを呼び出します。これは、追加の作業を行う別個の抽象化です。しかし、後者は以前に行われた作業と同じレベルの抽象化でなければなりません。したがって、異なるレベルの抽象化が混在しています。

これを修正しようとすると、通常、最初に#2からcreatePlantsを次のようにリファクタリングします。

function createPlants(){
    doWork3()  //work 3
    createAnimals()
}

これですべてが同じ抽象化レベルになり、名前の問題を解決できます:doWork3は動物や植物を作成するため、createPlantsに、createPlantscreateLifeに名前を変更する必要があります。

  function createLife(){
      createPlants()  
      createAnimals()
  }

いくつかの追加のリファクタリング手順の後、ケース#1で自動的に終了することは明らかだと思います。これはSLA原則:createWorldの内部で、各呼び出しは同じレベルの抽象化。他の各メソッドの内部では、(うまくいけば)そこで行われる作業も1つの抽象化レベルで行われます。

技術的には、ケース#2も機能することに注意してください。問題は、何かを変更する必要があるとき、コードを保守または進化させる必要があるときに始まります。ケース#1は、ほとんどが独立したビルディングブロックを作成します。これらは、より簡単に理解、テスト、再利用、拡張、または再注文できます。 #2の場合、各ビルディングブロックは別のビルディングブロックに依存します。これにより、以前のすべての利点がボードよりも高くなります。

18
Doc Brown

必要以上に強く結合されているため、これは悪い設計です。

関数が1つのアグリゲーターに統合されている場合は、それぞれを分離して使用(およびテスト)できます。チェーンでCreateEarthCreatePlantsを呼び出すと、コードからその柔軟性が取り除かれます。それはforces作成される植物です通常プロセスですが、ビジネスがやって来て、植物を必要としない海の世界または火山の世界を望んでいるときに何が起こりますか?

関数を分離することで、この必然的の変更により適切に対応できます。

3
Telastyn

「過度の呼び出しの入れ子」のようなものです。個別のメソッドを持つことの要点は、アルゴリズムの単一の論理部分、理想的には再利用可能な部分を分割することです。機能的に区別できない場合や再利用できない場合は、分解しないことを検討してください。unlessと、読みやすさが大幅に改善され、はるかに大きなコードが破壊されます。複数のページにまたがって、より保守しやすいコンポーネントに分割します。通常、これは、複数の場所から呼び出されることを意図していなくても、各部分が機能的に独立している程度の方法で行うことができます。

も参照してください 関数が1回しか呼び出されないときに、別の関数を作成するのが適切な場合はいつですか?

2
Brad Thomas

私が目にする最大の問題の1つは、#2がワールドの構築に課す厳密な順序の制約です。 createAnimals()の前にcreatePlants()したい場合はどうなりますか?間に何かする必要があると判断した場合はどうなりますか?最初の例では、1行のコードを移動するのと同じくらい簡単です。 2番目の例では、少なくとも2つのメソッドを書き直す必要があります。

1
ToddR