web-dev-qa-db-ja.com

関数をいくつかの内部関数に分割することはアンチパターンですか?

関数foo()を呼び出すことによって開始される長くて複雑なプロセスを想像してください。このプロセスにはいくつかの連続したステップがあり、各ステップは前のステップの結果に応じて異なります。関数自体は、たとえば、約250行です。これらの手順が、このプロセス全体の一部としてではなく、それ自体で役立つことはほとんどありません。

Javascriptなどの言語では、親関数内に内部関数を作成できます。これは、パラメーターとして渡されない限り、外部関数にはアクセスできません。

私の同僚は、次のことができると提案していますfoo()の内容を5つの内部関数に分割。各関数はステップです。これらの関数は、外部関数にはアクセスできません(親関数の外ではそれらの無用を意味します)。 foo()の本体は、これらの内部関数を1つずつ呼び出すだけです。

foo(stuff) {
    var barred = bar(stuff);
    var bazzed = baz(barred);
    var confabulated = confabulate(bazzed);
    var reticulated = reticulate(confabulated);
    var spliced = splice(reticulated);

    return spliced;

    // function definitions follow ...
}

これはある種のアンチパターンですか?または、これは長い関数を分割するための合理的な方法ですか?別の質問は、JavascriptでOOPパラダイムを使用する場合、これは受け入れられるかどうかです。 この方法でオブジェクトのメソッドを分割しても問題ありません、またはこれらのすべての内部関数をプライベートメソッドとして含む別のオブジェクトが保証されますか?

参照:

29
sbichenko

内部関数はアンチパターンではなく、機能です。

内部関数を外部に移動することが意味をなさない場合は、絶対にしないでください。

一方、ユニットテストを簡単に行えるように、それらを外部に移動することをお勧めします。 (フレームワークで内部関数をテストできるかどうかはわかりません。)250行以上の関数があり、ロジックに変更を加えた場合、何も壊していないのですか?あなたは本当にユニットテストが必要です、そしてそれは1つの巨大な関数で実行可能ではありません。

一般に、長い関数を小さい関数に分割することをお勧めします。 "extract method" と呼ばれる、コメントにちなんで名付けられた関数でコメントを置き換えることは、一般的なリファクタリング手法です。ええ、そうしてください!

@ Kilian が指摘したように、この関連記事も参照してください:

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

51
janos

それはアンチパターンではありません。それを行うには正しい方法です。

読みやすく、保守しやすく、テストしやすいです。重複を避けるのに役立ちます。

参照: クリーンコード (開発者向けの聖書)および この答え

15
Maxime ARNSTAMM

最も重要な問題は、複雑さを管理することです。内部メソッドは解決策の1つですが、最善の方法であるとは確信していません。

メソッドは長いので分割する必要がありますが、内部メソッドではメソッドはまだ長く、「チャプター」に分割されます。ただし、「コメントの章」よりはましです。

より良い解決策は、すべてのヘルパーメソッドと、重要なデータを収集する内部クラスを使用することです。さらに、このクラスは個別に単体テストできます。

これは、Fowlerの「Replace Method with Method Object」リファクタリングのバリエーションです。

5
andrew.fox

それはアンチパターンではありませんが、疑わしい動きです。それらの関数が親関数の外では価値がない場合、それらを分離する利点はなく、コードを追加するだけです。 「今、私たちは以前に騒がれたものをコンファメーションする」と言ってコードにいくつかのコメントを入れるだけで、より少ないコード行で同じ値を持つことができます。

編集:私は自分の答えを言い換えます。私はそこではあまり注意深い表現を使用していないためです。そして、コミュニティは反対票に基づいてそれを受け入れなかったようです。私が本当に言いたかったのは、読むときに余分なコードの量と注意の中断を考慮する必要があるということです。元の関数が十分に小さい場合(意見に基づいてどれだけ小さいか)は、内部関数間を行き来するよりも、いくつかの役立つコメントを含む1つのテキストストリームとして読む方が簡単です。 1つのコードチャンクは、いくつかの関数のコレクションよりも読みやすく、理解しやすいということは簡単にあり得ます。

一方、元の関数が1つのピースとして理解できるほど大きい場合、分割することは完全に理にかなっています。スコープが狭いという原則が間違いなくここで作用します。関数がそのために「十分に大きい」場合を理解することが重要です。コメントされたように元のものが10k LOCである場合、そのような判断をするのは簡単ですが、OPの例である250 LOCはどうでしょうか?私はそれが答えとして与えるために意見ベースになっていると思います。

2
Haspemulator

確かに、各「内部機能」を分離しておく方が良いです。小さな組み合わせ可能なパーツは、一体型の「大きな泥の塊」よりも保守がはるかに簡単です。ただし、用語に注意してください。関数とメソッドは、スタイルの観点からは同じものではありません。

メソッドは、1つのオブジェクト(「this」)を他のオブジェクトよりも優先する傾向があり、そのオブジェクトの有用性が制限されます。関数は特定の値から独立しているため、より柔軟で簡単に組み合わせることができます。

この場合、「foo」関数は明らかにそれらの「内部関数」の構成なので、そのように定義して、中間のボイラープレート(「stuff」、「barred」、「bazzed」、「 confabulated "、" reticulated "、" spliced "、" function(stuff){"および" return ...;} "):

var foo = [splice, reticulate, confabulate, baz, bar].reduce(compose, id);

「compose」および「id」関数をまだ取得していない場合は、次のとおりです。

var compose = function(f, g) { return function(x) { return f(g(x)); }; };
var id = function(x) { return x; };

おめでとうございます。fooの「役に立たない」と思われる内部から、信じられないほど有用で再利用可能な部分が2つできました。 「内部」関数の定義を見ない限り、私はこれ以上のことはできませんが、発見されるのを待って、もっと単純化、一般化、再利用性が利用できると確信しています。

2
Warbo

このタイプのコードは簡単にチェーンに変換できると思います。このような :

foo(stuff)
{
  return    
    stuff
      .bar()
      .baz()
      .confabulate()
      .reticulate()
      .splice()        
}

私の控えめな見解では、読みやすく、関心の分離を保ち、エラー率を制限します(ステップの1つが失敗すると、すべてのプロセスが失敗します)。

1
eka808

大きな関数を小さな関数に分割することはアンチパターンではありません。

ただし、これらすべての関数をメイン関数内で宣言すると、特定のリスクが生じます。内部関数は外部関数のすべての変数にアクセスできるため、JavaScript変数は可変であるため、内部関数が外部関数を変更しないことは保証できません。関数の状態。

この意味で、それらをネストすることにより、小さな関数を持つことの利点のほとんどを失います。それらはすべて共通の変更可能な状態を共有しているため、それぞれを個別に簡単に推論することはできません。

したがって、フラットな構造を維持してください。プライベート関数を非表示にする場合は、モジュールパターンまたは使用可能な多数のJavaScriptモジュールライブラリの1つを使用できます。

1
lortabac

はい、分割は優れています。読みやすさとテストのしやすさのためだけかもしれません。

別のアプローチは、関数を相互に呼び出すことかもしれません。

foo(stuff) {
   var spliced = splice(stuff);
   return spliced;
}

スプライスされた関数は網状データを必要とするため、最初にその関数を呼び出すことができます。

splice(stuff) {
    var reticulated = reticulate(stuff);
    //do splicing of reticulated
    return spliced;
}

そのため、すべての関数はデータを渡し、受け取った結果を処理します。

0
user145856