web-dev-qa-db-ja.com

関数が受け入れるパラメータの数に関するガイドラインはありますか?

私が使用するいくつかの関数には6つ以上のパラメーターがあることに気づきましたが、ほとんどのライブラリーでは、3つ以上かかる関数を見つけることはほとんどありません。

多くの場合、これらの追加パラメーターの多くは、関数の動作を変更するためのバイナリーオプションです。これらの重要なパラメータ関数のいくつかはおそらくリファクタリングされるべきだと私は思います。数が多すぎるためのガイドラインはありますか?

134
Darth Egregious

ガイドラインは見たことがありませんが、私の経験では、3つまたは4つを超えるパラメーターを取る関数は、次の2つの問題のいずれかを示しています。

  1. 関数が多すぎます。それはいくつかの小さな関数に分割されるべきであり、それぞれがより小さなパラメータセットを持っています。
  2. そこに隠れている別のオブジェクトがあります。これらのパラメーターを含む別のオブジェクトまたはデータ構造を作成する必要がある場合があります。詳細については、この記事の Parameter Object パターンを参照してください。

これ以上の情報がなければ、何を見ているのかを知るのは困難です。おそらくあなたがしなければならないリファクタリングは、現在関数に渡されているフラグに応じて、親から呼び出される小さな関数に関数を分割することです。

これを行うことによって得られるいくつかの良い利益があります:

  • コードが読みやすくなります。個人的には、1つのメソッドですべてを実行する構造体よりも、説明的な名前を持つ多くのメソッドを呼び出すif構造体で構成される「ルールリスト」を読む方がはるかに簡単です。
  • よりユニットテストが可能です。問題をいくつかの小さなタスクに分割し、個々に非常に単純なタスクを実行しました。ユニットテストのコレクションは、マスターメソッドのパスを確認する動作テストスイートと、個々のプロシージャごとの小さなテストのコレクションで構成されます。
119
Michael K

「クリーンコード:アジャイルソフトウェアの職人技のハンドブック」によると、0が理想的であり、1つまたは2つは許容可能で、特別な場合は3つ、4つ以上は許容されます。

著者の言葉:

関数の引数の理想的な数はゼロ(ニラディック)です。次に1つ(モナディック)が続き、2つ(ダイアディック)がそれに続きます。 3つの引数(3項)は可能な限り避けてください。 3つ以上(ポリアディック)には、非常に特別な正当化が必要です。したがって、とにかく使用しないでください。

この本には、パラメータが大規模に議論される関数についてのみ説明している章があるので、この本は、必要なパラメータの量の良いガイドラインになると思います。

私の個人的な意見では、何が起こっているかがより明確になると思うので、1つのパラメーターは誰よりも優れています。

例として、私の意見では、メソッドの処理内容がより明確であるため、2番目の選択の方が優れています。

LangDetector detector = new LangDetector(someText);
//lots of lines
String language = detector.detectLanguage();

vs.

LangDetector detector = new LangDetector();
//lots of lines
String language = detector.detectLanguage(someText);

多くのパラメータについて、これは一部の変数を単一のオブジェクトにグループ化できることの兆候である可能性があります。この場合、多くのブール値は、関数/メソッドが複数のことを実行していることを表すことができます。この場合、これらの動作をそれぞれ別の関数でリファクタリングする方が適切です。

44
Renato Dinhani

アプリケーションのドメインクラスが正しく設計されている場合、関数に渡すパラメーターの数は自動的に減少します。これは、クラスがジョブの実行方法を知っており、作業を行うのに十分なデータがあるためです。

たとえば、3年生のクラスに割り当てを完了するように依頼するマネージャークラスがあるとします。

正しくモデリングすれば、

3rdGradeClass.finishHomework(int lessonId) {
    result = students.assignHomework(lessonId, dueDate);
    teacher.verifyHomeWork(result);
}

これは簡単です。

正しいモデルがない場合、メソッドは次のようになります

Manager.finishHomework(grade, students, lessonId, teacher, ...) {
    // This is not good.
}

正しい関数は、独自のクラス(単一の責任)に委任されており、作業を行うのに十分なデータを持っているため、正しいモデルは常にメソッド呼び出し間の関数パラメーターを減らします。

パラメーターの数が増えるのを見るたびに、モデルをチェックして、アプリケーションモデルが正しく設計されているかどうかを確認します。

ただし、いくつかの例外があります。転送オブジェクトまたは構成オブジェクトを作成する必要がある場合、大きな構成オブジェクトを作成する前に、最初にビルダーパターンを使用して小さな作成オブジェクトを作成します。

26
java_mouse

他の回答が取り上げない1つの側面はパフォーマンスです。

十分に低いレベルの言語(C、C++、アセンブリ)でプログラミングしている場合、特に関数が大量に呼び出される場合、多数のパラメーターが一部のアーキテクチャーのパフォーマンスに非常に悪影響を与える可能性があります。

たとえばARM=で関数呼び出しが行われると、最初の4つの引数はレジスタr0からr3に配置され、残りの引数はスタックにプッシュする必要があります。引数の数を5未満に保つと、重要な関数にかなりの違いが生じる可能性があります。

非常に頻繁に呼び出される関数の場合、各呼び出しの前にプログラムが引数を設定する必要があるという事実でさえ、パフォーマンスに影響を与える可能性があります(r0からr3は、呼び出された関数によって上書きされる可能性があり、次の呼び出しの前に置き換えられるため、この点で引数はゼロが最適です。

更新:

KjMagは、インライン化の興味深いトピックを取り上げます。インライン化は、純粋なアセンブリで書き込む場合と同じ最適化をコンパイラーが実行できるようになるため、いくつかの点でこれを軽減します。つまり、コンパイラは、呼び出された関数で使用されているパラメータと変数を確認し、レジスタの使用を最適化して、スタックの読み取り/書き込みを最小限に抑えることができます。

ただし、インライン化にはいくつかの問題があります。

  1. 同じコードが複数の場所から呼び出された場合、同じコードがバイナリ形式で複製されるため、インライン化により、コンパイルされたバイナリが大きくなります。これは、Iキャッシュの使用に関しては有害です。
  2. コンパイラーは通常、特定のレベル(3ステップIIRC?)までのインライン化のみを許可します。インライン関数からインライン関数からインライン関数を呼び出すことを想像してみてください。 inlineがすべてのケースで必須として扱われた場合、バイナリの増加は爆発します。
  3. inlineを完全に無視するか、実際にエラーが発生したときに実際にエラーを表示するコンパイラはたくさんあります。
16
Leo

パラメータリストが5つを超える場合は、「コンテキスト」構造またはオブジェクトの定義を検討してください。

これは基本的に、いくつかの実用的なデフォルトが設定されたすべてのオプションパラメータを保持する単なる構造です。

Cの手続き型の世界では、単純な構造で十分です。 Javaでは、C++の単純なオブジェクトで十分です。オブジェクトの唯一の目的は「パブリック」に設定可能な値を保持することなので、ゲッターやセッターをいじらないでください。

7
James Anderson

いいえ、標準的なガイドラインはありません

しかし、多くのパラメーターを持つ関数をより耐えられるようにするいくつかの手法があります。

list-if-argsパラメータ(args *)またはdictionary-of-argsパラメータ(kwargs**

たとえば、pythonでは:

// Example definition
def example_function(normalParam, args*, kwargs**):
  for i in args:
    print 'args' + i + ': ' + args[i] 
  for key in kwargs:
    print 'keyword: %s: %s' % (key, kwargs[key])
  somevar = kwargs.get('somevar','found')
  missingvar = kwargs.get('somevar','missing')
  print somevar
  print missingvar

// Example usage

    example_function('normal parameter', 'args1', args2, 
                      somevar='value', missingvar='novalue')

出力:

args1
args2
somevar:value
someothervar:novalue
value
missing

または、オブジェクトリテラル定義構文を使用できます

たとえば、AJAX GETリクエストを起動するためのJavaScript jQuery呼び出しは次のとおりです。

$.ajax({
  type: 'GET',
  url: 'http://someurl.com/feed',
  data: data,
  success: success(),
  error: error(),
  complete: complete(),
  dataType: 'jsonp'
});

JQueryのajaxクラスを見ると、lot(約30)設定できるプロパティが他にもあります。主な理由は、ajax通信が非常に複雑だからです。幸い、オブジェクトリテラル構文を使用すると、作業が簡単になります。


C#インテリセンスは、パラメーターのアクティブなドキュメントを提供するため、オーバーロードされたメソッドの非常に複雑な配置が見られることも珍しくありません。

Python/javascriptのような動的に型付けされた言語にはそのような機能がないため、キーワード引数とオブジェクトリテラル定義を確認する方がはるかに一般的です。

複雑なメソッドを管理するには、オブジェクトリテラル定義( C#でも )をお勧めします。これは、オブジェクトがインスタンス化されるときに設定されているプロパティを明示的に確認できるためです。デフォルトの引数を処理するには、もう少し作業が必要になりますが、長期的には、コードがはるかに読みやすくなります。オブジェクトリテラル定義を使用すると、ドキュメントへの依存を解消して、コードが一見何をしているかを理解できます。

私見、オーバーロードされたメソッドは非常に過大評価されています。

注:私が覚えている場合は、C#のオブジェクトリテラルコンストラクターに対して読み取り専用アクセス制御が機能するはずです。これらは基本的に、コンストラクターでのプロパティの設定と同じように機能します。


動的に型付けされた(python)や関数型/プロトタイプのjavaScriptベースの言語で重要なコードを記述したことがない場合は、ぜひ試してみてください。それは啓発的な経験になる可能性があります。

関数/メソッドの初期化へのすべての、すべてのアプローチのためのパラメーターへの依存を破るのは、最初に怖いかもしれませんが、不必要な複雑さを追加する必要なしに、コードでもっと多くのことを学ぶことができます。

更新:

おそらく静的型付け言語での使用を示す例を提供する必要がありましたが、現在静的型付けされたコンテキストで考えていません。基本的に、動的に型付けされたコンテキストであまりにも多くの作業を行っているため、突然切り替えることはできません。

私が知っていることdoオブジェクトリテラル定義構文は、静的型付け言語(少なくともC#およびJava)で完全に可能です。前。静的に型付けされた言語では、それらは「オブジェクト初期化子」と呼ばれます。 Java および C#での使用を示すいくつかのリンクを次に示します。

5
Evan Plaice

個人的には、2つ以上が私のコード臭いアラームをトリガーします。関数をoperations(つまり、入力から出力への変換)と見なす場合、1つの操作で2つを超えるパラメーターが使用されることはまれです。 Procedures(つまり、目標を達成するための一連のステップ)は、より多くの入力を必要とし、時には最良のアプローチですが、最近のほとんどの言語では標準になります。

しかし、繰り返しになりますが、それはルールというよりもガイドラインです。異常な状況や使いやすさのため、2つ以上のパラメーターをとる関数がよくあります。

3
Telastyn

Evan Plaiceが言っているように、私は連想配列(または言語の同等のデータ構造)を可能な限り関数に単純に渡すことの大ファンです。

したがって、(たとえば)これの代わりに:

<?php

createBlogPost('the title', 'the summary', 'the author', 'the date of publication, 'the keywords', 'the category', 'etc');

?>

をやる:

<?php

// create a hash of post data
$post_data = array(
  'title'    => 'the title',
  'summary'  => 'the summary',
  'author'   => 'the author',
  'pubdate'  => 'the publication date',
  'keywords' => 'the keywords',
  'category' => 'the category',
  'etc'      => 'etc',
);

// and pass it to the appropriate function
createBlogPost($post_data);

?>

Wordpressはこのように多くのことを行い、私はそれがうまく機能すると思います。 (上記の私のコード例は架空のものであり、それ自体がWordpressの例ではありません。)

この手法を使用すると、大量のデータを関数に簡単に渡すことができますが、それぞれを渡す順序を覚えておく必要はありません。

関数の引数の順序を潜在的に変更する必要がない場合(たとえば、Yet Another Argumentを渡す必要があることに気付いた場合など)ではなく、関数のパラメーターを変更する必要がない場合にも、この手法が役立ちます。まったくリストします。

これにより、関数定義を書き直す必要がなくなるだけでなく、関数が呼び出されるたびに引数の順序を変更する必要もなくなります。それは大きな勝利です。

2

Robert Martinの "Clean Code:A Handbook of Agile Software Craftsmanship"で関数の引数の理想的な数がゼロになるようにアドバイスするためのコンテキストをさらに提供するために、筆者は次のように述べています。

議論は難しいです。彼らは多くの概念的な力を持っています。それが私が例からそれらのほとんどすべてを取り除いた理由です。たとえば、例のStringBufferを考えます。インスタンス変数にするのではなく、引数として渡すこともできましたが、読者はそれを見るたびに解釈する必要があったでしょう。モジュールが語るストーリーを読んでいるとき、includeSetupPage()includeSetupPageInto(newPageContent)よりも理解しやすいです。引数は、関数名とは異なる抽象化レベルにあり、その時点では特に重要ではない詳細(つまり、StringBuffer)を知る必要があります。

上記のincludeSetupPage()の例の場合、この章の最後にリファクタリングされた「クリーンなコード」の小さなスニペットがあります。

// *** NOTE: Commments are mine, not the author's ***
//
// Java example
public class SetupTeardownIncluder {
    private StringBuffer newPageContent;

    // [...] (skipped over 4 other instance variables and many very small functions)

    // this is the zero-argument function in the example,
    // which calls a method that eventually uses the StringBuffer instance variable
    private void includeSetupPage() throws Exception {
        include("SetUp", "-setup");
    }

    private void include(String pageName, String arg) throws Exception {
        WikiPage inheritedPage = findInheritedPage(pageName);
        if (inheritedPage != null) {
            String pagePathName = getPathNameForPage(inheritedPage);
            buildIncludeDirective(pagePathName, arg);
        }
    }

    private void buildIncludeDirective(String pagePathName, String arg) {
        newPageContent
            .append("\n!include ")
            .append(arg)
            .append(" .")
            .append(pagePathName)
            .append("\n");
    }
}

著者の「思想」は、小さなクラス、関数の引数の数が少ない(理想的には0)、非常に小さな関数を主張しています。私も彼に完全に同意しませんが、私はそれが考えさせられるものであるとわかり、理想としてのゼロ関数引数の考えは検討する価値があると感じます。また、上記の小さなコードスニペットでもゼロ以外の引数関数もあるので、コンテキストに依存すると思います。

(そして、他の人が指摘したように、彼はまた、引数の数が増えるとテストの観点からそれが難しくなると主張します。しかし、ここでは主に上記の例と関数引数なしの彼の理論的根拠を強調したいと思います。)

0
sonny

以前の answer は、関数のパラメーターが少なければ少ないほど、うまく機能していると述べた信頼できる作者に言及しました。答えは理由を説明しませんでしたが、本はそれを説明します、そしてあなたがこの哲学を採用する必要がある理由として私が個人的に同意する最も説得力のある理由の2つがあります:

  • パラメータは、関数の抽象化レベルとは異なる抽象化レベルに属しています。つまり、コードの読者は、関数のパラメーターの性質と目的について考える必要があります。この考え方は、対応する関数の名前と目的よりも「低いレベル」です。

  • 関数へのパラメーターをできるだけ少なくする2番目の理由は、テストです。たとえば、10個のパラメーターを持つ関数がある場合、たとえばユニットのすべてのテストケースをカバーする必要があるパラメーターの組み合わせの数を考えます。テスト。パラメータが少ない=テストが少ない。

0