web-dev-qa-db-ja.com

他の1つの関数でのみ使用される関数をその関数内に配置する必要がありますか?

具体的には、JavaScriptで記述しています。

関数Aが関数Aであるとしましょう。関数Aが関数Bを複数回呼び出しても、関数Bが他の場所で使用されていない場合は、関数Bを関数A内に配置する必要がありますか?

それは良い習慣ですか?または、関数Bを関数Aと同じスコープに配置する必要がありますか?

34
Gary

私は一般的に、特にJavaScriptにおいて、ネストされた関数を支持しています。

  • JavaScriptでは、関数の可視性を制限する唯一の方法は、関数を別の関数内にネストすることです。
  • ヘルパー関数はプライベートな実装の詳細です。同じスコープに置くことは、クラスのプライベート関数をパブリックにすることに似ています。
  • それがより一般的な用途であることが判明した場合、ヘルパーが現在その1つの関数でのみ使用されていると確信できるので、簡単に移動できます。言い換えると、コードがよりまとまりのあるものになります。
  • 多くの場合、パラメータを削除できるため、関数のシグネチャと実装の冗長性が低くなります。これは、変数をグローバルにすることと同じではありません。プライベートメソッドでクラスメンバーを使用するようなものです。
  • 競合を心配せずに、ヘルパー関数に、より適切で単純な名前を付けることができます。
  • ヘルパー関数は見つけやすくなっています。はい、あなたを助けることができるツールがあります。画面を少しだけ上に移動する方が簡単です。
  • コードは自然に一種の抽象化ツリーを形成します。ルートにいくつかの一般的な関数があり、いくつかの実装の詳細に分岐しています。関数の折りたたみ/折りたたみでエディターを使用する場合、ネストにより、同じ抽象化レベルで密接に関連する関数の階層が作成されます。これにより、必要なレベルでコードを調べて詳細を隠すことが非常に簡単になります。

多くの反対は、ほとんどのプログラマーがC/C++/Javaの伝統に育てられたか、そうであった誰かから教えられたという事実から来ていると思います。ネストされた関数は、プログラムを学んでいるときにあまり触れられなかったため、自然に見えません。それはそれらが役に立たないという意味ではありません。

34
Karl Bielefeldt

いくつかの理由で、グローバルスコープに配置する必要があります。

  • ヘルパー関数を呼び出し元にネストすると、呼び出し元の長さが長くなります。関数の長さはほとんどの場合負の指標です。短い関数は、理解、記憶、デバッグ、および保守が容易です。

  • ヘルパー関数にわかりやすい名前が付いている場合は、近くの定義を見なくても、その名前を読み取るだけで十分です。 do呼び出し元の関数を理解するためにヘルパー定義を確認する必要がある場合、その呼び出し元は多くの作業を行っているか、あまりに多くの抽象化レベルで同時に作業しています。

  • ヘルパーをグローバルに使用可能にすると、他の関数がヘルパーを呼び出して、結局それが一般的に役立つようになった場合にそれを呼び出すことができます。ヘルパーが利用できない場合、それをカットアンドペーストしたり、忘れて再実装したり、不十分にしたり、必要以上に別の関数を作成したりする誘惑に駆られます。

  • ヘルパー関数をネストすると、宣言なしで呼び出し元のスコープの変数を使用する誘惑が高まるため、ヘルパーの入力と出力が不明確になります。関数がどのデータに作用し、どのような影響を与えるかが明確に示されていない場合、それは通常、責任が不明確であることを示しています。ヘルパーのお尻をスタンドアロン関数として宣言すると、実際に何が行われるかを知る必要があります。

編集

それは私が思っていたよりも物議を醸す質問であることが判明しました。明確にするために:

JavaScriptでは、言語が他のスコープ制限メカニズムを提供しないため、大きなファイルスパニング関数がクラスの役割を果たすことがよくあります。確かにヘルパー関数はinsideのような疑似クラスであり、それらの外部ではありません。

そして、再利用のしやすさに関するポイントは、サブルーチンdoesがより広く使用されるようになった場合、それを完全に外して適切な場所に配置することをいとわないことを前提としています。文字列ユーティリティライブラリまたはグローバル構成レジストリ。このようにコードを順序付けしたくない場合は、通常の Method Object をより「ブロック状」の言語で行うのと同じように、サブルーチンをネストすることもできます。

14
Kilian Foth

クロージャー内に両方の関数を配置するための3番目のパスを提案します。それは次のようになります:

_var functionA = (function(){
    function functionB() {
        // do stuff...
    }

    function functionA() {
        // do stuff...
        functionB();
        // do stuff...
    }

    return functionA;
})();
_

両方の関数の宣言を [〜#〜] iife [〜#〜] でラップして、クロージャーを作成します。 IIFEの戻り値はパブリック関数であり、関数の名前の変数に格納されます。 public関数は、functionA()のように、グローバル関数として宣言された場合とまったく同じ方法で呼び出すことができます。戻り値は関数であり、関数の呼び出しではないため、最後に括弧がないことに注意してください。

このように2つの関数をラップすることで、functionBは完全にプライベートになり、クロージャーの外からはアクセスできなくなりますが、functionAにのみ表示されます。 functionAの定義を乱雑にしていません。

8
cbojar

関数A内で関数Bを定義すると、関数BはAの内部変数にアクセスできます。

したがって、関数A内で定義された関数Bは、BがAのローカル変数を使用または変更しているという印象(または少なくとも可能性)を与えます。 Aの外でBを定義すると、Bはそうではないことが明確になります。

したがって、コードをわかりやすくするために、BがAのローカル変数にアクセスする必要がある場合にのみ、A内にBを定義します。

0
Florian F