私が使用しているいくつかの関数には6つ以上のパラメータがあることに気づきましたが、私が使用するほとんどのライブラリでは、3つ以上かかる関数を見つけることはまれです。
多くの場合、これらの追加パラメーターの多くは、関数の動作を変更するためのバイナリオプションです。これらの無数のパラメーター化された関数のいくつかはおそらくリファクタリングされるべきだと思います。数が多すぎる場合のガイドラインはありますか?
私が使用しているいくつかの関数には6つ以上のパラメータがあることに気づきましたが、私が使用するほとんどのライブラリでは、3つ以上かかる関数を見つけることはまれです。
多くの場合、これらの追加パラメーターの多くは、関数の動作を変更するためのバイナリオプションです。これらの無数のパラメーター化された関数のいくつかはおそらくリファクタリングされるべきだと思います。数が多すぎる場合のガイドラインはありますか?
回答:
ガイドラインを見たことがありませんが、私の経験では、3つまたは4つ以上のパラメーターを取る関数は、2つの問題のいずれかを示しています。
これ以上の情報がなければ、あなたが見ているものを伝えることは困難です。関数を現在の関数に渡されているフラグに応じて親から呼び出される小さな関数に分割する必要があるリファクタリングがチャンスです。
これを行うことで得られるいくつかの良い利益があります:
if
、1つのメソッドですべてを実行する構造よりも、記述的な名前で多くのメソッドを呼び出す構造で構成される「ルールリスト」を読む方がはるかに簡単です。「Clean Code:A Handbook of Agile Software Craftsmanship」によると、ゼロが理想であり、1つまたは2つが許容され、3つは特別な場合、4つ以上は決して許されません!
著者の言葉:
関数の引数の理想的な数はゼロ(niladic)です。次に1つ(単項)、2つ(2項)が続きます。可能な場合、3つの引数(3項)を避ける必要があります。3つ以上(ポリアディック)には非常に特別な正当化が必要です。
この本には、パラメータが大規模に議論されている関数についてのみ説明している章があるので、この本は必要なパラメータの量の良いガイドラインになると思います。
私の意見では、何が起こっているかがより明確だと思うので、1つのパラメーターは誰よりも優れています。
例として、私の意見では、メソッドが処理しているものがより明確であるため、2番目の選択の方が優れています。
LangDetector detector = new LangDetector(someText);
//lots of lines
String language = detector.detectLanguage();
対
LangDetector detector = new LangDetector();
//lots of lines
String language = detector.detectLanguage(someText);
多くのパラメーターについて、これは、いくつかの変数を単一のオブジェクトにグループ化できることを示す兆候である場合があります。これらの動作のそれぞれを別の関数でリファクタリングする方が適切です。
String language = detectLanguage(someText);
です。どちらの場合でも、まったく同じ数の引数を渡しましたが、言語が貧弱であるために関数の実行を2つに分割しただけです。
Matrix.Create(input);
でinput
は、たとえば.NETはIEnumerable<SomeAppropriateType>
どこにありますか?あなたは10個の要素の代わりに9を保持するマトリックスを作成したいときにも、別のオーバーロードを必要としないその方法
アプリケーションのドメインクラスが正しく設計されている場合、関数に渡すパラメーターの数は自動的に削減されます。クラスは仕事をする方法を知っており、仕事をするのに十分なデータがあるためです。
たとえば、3年生のクラスに課題を完了するよう依頼するマネージャークラスがあるとします。
正しくモデリングすれば、
3rdGradeClass.finishHomework(int lessonId) {
result = students.assignHomework(lessonId, dueDate);
teacher.verifyHomeWork(result);
}
これは簡単です。
正しいモデルがない場合、メソッドは次のようになります
Manager.finishHomework(grade, students, lessonId, teacher, ...) {
// This is not good.
}
正しい関数は独自のクラスに委任され(単一の責任)、作業を行うのに十分なデータがあるため、正しいモデルはメソッド呼び出し間の関数パラメーターを常に減らします。
パラメーターの数が増えるのを見るたびに、モデルをチェックして、アプリケーションモデルを正しく設計したかどうかを確認します。
ただし、いくつかの例外があります。転送オブジェクトまたは構成オブジェクトを作成する必要がある場合、ビルダーパターンを使用して、小さな構成オブジェクトを最初に作成してから、大きな構成オブジェクトを作成します。
他の答えが取り上げない側面の1つはパフォーマンスです。
十分に低レベルの言語(C、C ++、アセンブリ)でプログラミングしている場合、特に関数が大量に呼び出される場合、一部のアーキテクチャでは多数のパラメーターがパフォーマンスに非常に悪影響を与える可能性があります。
たとえば、ARMで関数呼び出しが行われる場合、最初の4つの引数はレジスタのレジスタr0
に配置されr3
、残りの引数はスタックにプッシュする必要があります。引数の数を5未満に保つと、重要な機能に大きな違いが生じる可能性があります。
非常に頻繁に呼び出される関数、プログラムは各呼び出しは、パフォーマンスに影響することができます(前の引数を設定する必要があることも、事実のためr0
にr3
呼び出された関数で上書きすることができるが、次の呼び出しの前に交換する必要があります)ので、その点で引数はゼロが最適です。
更新:
KjMagは、インライン化の興味深いトピックを取り上げます。インライン化は、コンパイラが純粋なアセンブリで記述する場合と同じ最適化を実行できるため、いくつかの点でこれを軽減します。つまり、コンパイラーは、呼び出された関数がどのパラメーターと変数を使用しているかを確認し、レジスターの使用を最適化して、スタックの読み取り/書き込みを最小限に抑えることができます。
ただし、インライン化にはいくつかの問題があります。
inline
すべての場合に必須として扱われた場合、バイナリ成長は爆発するでしょう。inline
か、実際にエラーが発生したときに実際にエラーを表示します。inline
。)
パラメータリストが5つ以上になったら、「コンテキスト」構造またはオブジェクトの定義を検討してください。
これは基本的に、いくつかの適切なデフォルトが設定されたすべてのオプションパラメータを保持する構造です。
C手続き型の世界では、単純な構造で十分です。Java、C ++では、単純なオブジェクトで十分です。オブジェクトの唯一の目的は「パブリック」に設定可能な値を保持することであるため、ゲッターやセッターを混乱させないでください。
いいえ、標準のガイドラインはありません
しかし、多くのパラメータを持つ関数をより耐えられるものにすることができるいくつかのテクニックがあります。
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クラスを見ると、設定できるプロパティがさらに多く(約30)あります。これは主に、ajax通信が非常に複雑だからです。幸いなことに、オブジェクトリテラル構文により作業が楽になります。
C#intellisenseはパラメータのアクティブなドキュメントを提供するため、オーバーロードされたメソッドの非常に複雑な配置を見るのは珍しいことではありません。
python / javascriptのような動的に型付けされた言語にはそのような機能がないため、キーワード引数とオブジェクトリテラル定義を見るのがより一般的です。
オブジェクトがインスタンス化されるときにどのプロパティが設定されているかを明示的に確認できるため、複雑なメソッドの管理にはオブジェクトリテラル定義(C#でも)が好まれます。デフォルトの引数を処理するにはもう少し作業が必要になりますが、長期的にはコードはずっと読みやすくなります。オブジェクトリテラル定義を使用すると、ドキュメントへの依存を解消して、コードが一目で何をしているかを理解できます。
私見、オーバーロードされたメソッドは非常に過大評価されています。
注:覚えていれば、C#のオブジェクトリテラルコンストラクターで読み取り専用アクセス制御が機能するはずです。基本的に、コンストラクターでプロパティを設定するのと同じように機能します。
動的に型指定された(python)および/または機能/プロトタイプjavaScriptベースの言語で自明でないコードを記述したことがない場合は、試してみることを強くお勧めします。それは啓発的な経験になる可能性があります。
最初に、関数/メソッドの初期化へのすべてのアプローチを行うためのパラメーターへの依存を破ることは怖いかもしれませんが、不必要な複雑さを追加することなく、コードでこれ以上のことを行うことを学びます。
更新:
おそらく静的に型付けされた言語での使用を示すために例を提供すべきでしたが、現在は静的に型付けされたコンテキストで考えているわけではありません。基本的に、動的に型付けされたコンテキストであまりにも多くの作業を行って、突然元に戻りました。
私が知っているのは、オブジェクトリテラル定義構文が静的型付け言語(少なくともC#とJava)で完全に可能であることです。静的に型付けされた言語では、それらは「オブジェクト初期化子」と呼ばれます。JavaおよびC#での使用方法を示すリンクを次に示します。
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の例ではありません。)
この手法を使用すると、多くのデータを関数に簡単に渡すことができますが、各データを渡す順序を覚えておく必要はありません。
また、リファクタリングするときが来ると、この手法に感謝します-関数の引数の順序を潜在的に変更する代わりに(まだ別の引数を渡す必要があることに気づいたときなど)、関数のパラメーターを変更する必要はありませんすべてのリスト。
これにより、関数定義を書き直す必要がなくなるだけでなく、関数が呼び出されるたびに引数の順序を変更する必要がなくなります。それは大きな勝利です。
以前の回答では、関数のパラメーターが少なければ少ないほど良いと述べた信頼できる著者に言及しました。答えは理由を説明しませんでしたが、本はそれを説明します、そして、ここにあなたがこの哲学を採用する必要がある最も説得力のある理由の2つがあり、私は個人的に同意します:
パラメーターは、関数の抽象化レベルとは異なる抽象化レベルに属します。これは、コードの読者が関数のパラメーターの性質と目的について考える必要があることを意味します。この思考は、名前の対応や機能の目的よりも「低いレベル」です。
関数のパラメーターをできるだけ少なくする2番目の理由はテストです。たとえば、10個のパラメーターを持つ関数がある場合、たとえばユニットのすべてのテストケースをカバーする必要があるパラメーターの組み合わせの数を考えます。テスト。パラメータが少ない=テストが少ない。
Robert Martinの「Clean Code:A Handbook of Agile Software Craftsmanship」で、関数引数の理想的な数がゼロであるというアドバイスについてもう少しコンテキストを提供するために、著者は次のことを彼のポイントの1つとして述べています。
引数は難しいです。彼らは多くの概念的な力を取ります。そのため、例からそれらのほとんどすべてを取り除きました。たとえば、例を見てみましょう
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)関数引数の数、および非常に小さな関数について論じています。私も彼に完全に同意していませんが、私はそれが思考を刺激するものであるとわかりました。また、上記の彼の小さなコードスニペットにもゼロ以外の引数関数があるため、コンテキストに依存すると思います。
(そして、他の人が指摘したように、彼はまた、より多くの引数がテストの観点からそれを難しくすると主張します。しかし、ここで主に上記の例とゼロ関数引数の彼の理論を強調したかったです。)
理想的にはゼロ。1つまたは2つは問題ありませんが、特定の場合は3つです。
通常、4つ以上は悪い習慣です。
他の人が指摘した単一の責任原則だけでなく、テストとデバッグの観点から考えることもできます。
1つのパラメーターがある場合、その値を知って、それらをテストし、それらのエラーを見つけるのは、「1つの要因しかないため、比較的簡単です。係数を大きくすると、全体の複雑さが急速に増加します。抽象の例:
「この天気で何を着るか」プログラムを検討してください。1つの入力-温度で何ができるかを検討してください。あなたが想像できるように、何を着るべきかの結果は、その1つの要因に基づいて非常に簡単です。次に、実際に温度、湿度、露点、降水量などが渡された場合にプログラムが何をする/できる/すべきかを考えます。