web-dev-qa-db-ja.com

クリーンなコード:パラメーターが少ない関数

私はロバートC.マーティンのClean Codeの最初の章を読みましたが、それはかなり良いように思えますが、疑わしい点があります。 (認識的に)関数にはできるだけ少ないパラメーターを含める必要があります.3つ以上のパラメーターが関数には多すぎることも示唆しているため(非常に誇張され、理想的であることがわかります)、疑問に思い始めました...

グローバル変数を使用する方法と関数に多くの引数を渡す方法はどちらもプログラミングの方法としては不適切ですが、グローバル変数を使用すると、関数内のパラメーターの数を大幅に減らすことができます...

だから私はあなたがそれについてどう思うか聞きたかったのですが、関数のパラメータの数を減らすためにグローバル変数を使う価値はありますか?どのような場合ですか?

私が思うのは、それはいくつかの要因に依存するということです。

  • ソースコードサイズ。
  • 関数の平均におけるパラメーターの数。
  • 関数の数。
  • 同じ変数が使用される頻度。

私の意見では、ソースコードのサイズが比較的小さい場合(コードの600行未満など)、多くの関数があり、同じ変数がパラメーターとして渡され、関数には多くのパラメーターがあるため、グローバル変数を使用する価値がありますが、知りたい...

  • 私の意見を教えてください。
  • ソースコードが大きい他のケースなどについてどう思いますか?

P.S。私は この投稿 を見ました、タイトルは非常に似ていますが、彼は私が知りたいことを尋ねません。

49
OiciTrap

私はあなたの意見を共有しません。私の意見では、グローバル変数を使用することは、あなたが説明した品質に関係なく、より多くのパラメーターよりも悪い習慣です。パラメータが多いとメソッドの理解が難しくなる可能性がありますが、グローバル変数はテスト性の悪さ、同時実行のバグ、密結合など、コードに多くの問題を引き起こす可能性があります。関数にいくつのパラメーターがあっても、本質的にグローバル変数と同じ問題はありません。

...同じ変数がパラメータとして渡されます

mayデザインの匂いです。システム内のほとんどの関数に同じパラメーターが渡されている場合、新しいコンポーネントを導入することで対応すべき分野横断的な懸念がある可能性があります。同じ変数を多くの関数に渡すことは、グローバル変数を導入する正当な理由になるとは思いません。

質問の1つの編集で、グローバル変数を導入するとコードが読みやすくなる可能性があることを示しました。同意しません。関数パラメーターはシグニチャーで宣言されているのに対して、グローバル変数の使用は実装コードでは隠されています。関数は理想的には純粋でなければなりません。それらはパラメータのみを操作する必要があり、副作用はありません。純粋な関数がある場合は、1つの関数だけを見て、その関数について推論できます。関数が純粋でない場合、他のコンポーネントの状態を考慮する必要があり、推論するのがはるかに困難になります。

114
Samuel

グローバル変数ペストのようなは避けてください。

引数の数(3や4など)にハードリミットを設定するつもりはありませんが、do可能であれば、引数の数を最小限に抑える必要があります。

structs(またはC++のオブジェクト)を使用して、変数を1つのエンティティにグループ化し、それを(参照により)関数に渡します。通常、関数は、いくつかの異なるものを含む構造またはオブジェクトを、structに対して何かをするように関数に指示する他のいくつかのパラメーターと共に渡されます。

香りが良く、クリーンなモジュール式コードの場合、 単一責任の原則 に固執するようにしてください。構造体(またはオブジェクト)、関数、およびソースファイルを使用してそれを行います。これを行うと、関数に渡されるパラメーターの自然数が明らかになります。

構文ではなく、認知負荷について話している。だから問題は...このコンテキストのパラメーターisは何ですか?

パラメータは、関数の動作に影響を与える値です。パラメーターが多いほど、取得できる値の組み合わせが多くなるほど、関数についての推論が難しくなります。

その意味で、関数が使用するグローバル変数はareパラメーターです。これらは、署名に表示されないパラメーターであり、構築順序、アクセス制御、および残留性の問題があります。

上記のパラメーターが横断的関心事と呼ばれるものでない限り、つまり、すべてが使用し、何も変更しないプログラム全体の状態(ロギングオブジェクトなど)でない限り、関数パラメーターをグローバル変数に置き換えないでください。それらはまだパラメーターですが、より厄介です。

55
Quentin

私の質問は誤解に基づいています。 「クリーンコード」では、ボブ・マーティンは繰り返される関数パラメーターをグローバルに置き換えることを提案していません。これは本当にひどいアドバイスになります。彼はそれらを関数のクラスのprivate member variablesで置き換えることを提案しています。また、彼は小さい、まとまりのあるクラス(通常、前述の600行のコードよりも小さい)も提案しているため、これらのメンバー変数はグローバルではありません。

したがって、600行未満のコンテキストで意見がある場合は、「グローバル変数を使用する価値があります」と、叔父の意見を完全に共有します。ボブ。もちろん、「最大3つのパラメータ」が理想的な数であるかどうか、そしてこのルールが小さいクラスでもメンバー変数が多すぎる場合があるかどうかは議論の余地があります。私見これはトレードオフであり、どこに線を引くかは厳格なルールではありません。

34
Doc Brown

多くのパラメータを持つことは望ましくないと考えられていますが、フィールドまたはグローバル変数に変換することはかなり悪いなので、実際の問題は解決されませんが、新しい問題が発生します。

多くのパラメーターを持つこと自体は問題ではありませんが、問題がある可能性があるのは症状です。この方法を検討してください:

Graphics.PaintRectangle(left, top, length, height, red, green, blue, transparency);

7つのパラメーターを持つことは、明確な警告サインです。基礎となる問題は、これらのパラメーターが独立していないが、グループに属していることです。 lefttopは、Position- structureとして一緒に属し、lengthheightSize構造として一緒に属し、 redblueおよびgreenColor構造体として。そして多分Colorと透明度はBrush構造に属しますか?おそらくPositionSizeRectangle構造に一緒に属します。その場合、PaintRectangleメソッドに変換することも検討できます。 ] _代わりにオブジェクト?したがって、次のようになる可能性があります。

Rectangle.Paint(brush);

任務完了!しかし重要なことは、実際に全体的な設計を改善し、パラメーターの数の削減がこれの結果であることです。根本的な問題に取り組むことなく、パラメーターの数を減らすだけであれば、次のようなことを行う可能性があります。

Graphics.left = something;
Graphics.top = something;
Graphics.length = something;
...etc
Graphics.PaintRectangle();

ここでは、パラメーターの数は同じ削減を達成しましたが、実際に設計を作成しましたさらに悪い

結論:プログラミングのアドバイスや経験則では、根本的な理由を理解することが非常に重要です。

34
JacquesB

関数のパラメーターの数を減らすためにグローバル変数を使用する価値はありますか?

ない

この本の最初の章を読んだ

本の残りを読みましたか?

グローバルは単なる隠しパラメータです。彼らは別の痛みを引き起こします。しかし、それはまだ苦痛です。このルールを回避する方法を考えるのをやめます。それに従う方法を考えてください。

パラメータとは何ですか?

それはものです。きれいにラベル付けされたボックス。何を入れてもよいのに、箱の数が重要なのですか?

送料と手数料の費用です。

Move(1, 2, 3, 4)

読めますか?続けてみてください。

Move(PointA, PointB)

それが理由です。

そのトリックは パラメーターオブジェクトの導入 と呼ばれます。

そして、はい、あなたがしているすべてがパラメータを数えることであるならば、それはトリックです。あなたが数えるべきなのはアイデアです!抽象化!一度にどれくらい考えさせられますか?複雑にしないでおく。

これは同じ数です:

Move(xyx, y)

わあ!ひどい!ここで何が問題になりましたか?

アイデアの数を制限するだけでは不十分です。彼らは明確なアイデアでなければなりません。一体何がxyxですか?

今、これはまだ弱いです。これについて考えるより強力な方法は何ですか?

関数は小さくなければなりません。それ以上です。

ボブおじさん

Move(PointB)

関数に実際に必要な以上の機能を実行させるのはなぜですか?単一責任の原則は、クラスだけのものではありません。真剣に、1つの機能が10の関連するパラメーターを持つ過負荷の悪夢に変わるのを防ぐために、アーキテクチャ全体を変更する価値があります。

関数内の最も一般的な行数が1であるコードを見たことがあります。私はあなたがそのように書く必要があると言っているのではありませんが、私がグローバルであることがこのルールに従う唯一の方法であると私に言わないでください。その機能を適切にリファクタリングから抜け出そうとするのをやめます。あなたができることを知っています。それはいくつかの機能に分解されるかもしれません。実際にはいくつかのオブジェクトになるかもしれません。地獄では、その一部を完全に異なるアプリケーションに分割することさえできます。

本はあなたのパラメータを数えるようにあなたに言っていません。それはあなたが引き起こしている痛みに注意を払うように言っています。痛みを修正するものは何でも問題を修正します。ある痛みを別の痛みと交換しているときに気をつけてください。

7
candied_orange

グローバル変数を使用してパラメーターを削減することはありません。その理由は、グローバル変数は任意の関数/コマンドによって変更できるため、関数の入力が信頼できなくなり、関数が処理できる範囲外の値になりやすくなるためです。関数の実行中に変数が変更され、関数の半分に他の半分と異なる値があった場合はどうなりますか?

一方、パラメータを渡すと、変数のスコープがそれ自体の関数のみに制限され、関数が呼び出されると関数だけがパラメータを変更できます。

パラメータではなくグローバル変数を渡す必要がある場合は、コードを再設計することをお勧めします。

ちょうど私の2セント。

4
Dimos

私はボブおじさんと一緒にいて、3つを超えるパラメーターは避けなければならないことに同意します(関数で4つ以上のパラメーターを使用することはほとんどありません)。単一の関数に多くのパラメーターがあると、メンテナンスの問題が大きくなり、おそらく、関数が実行しすぎている/責任が多すぎるというにおいです。

OO言語のメソッドで3つ以上を使用している場合、パラメーターは何らかの方法で互いに関連していないため、実際にはオブジェクトを渡す必要がありますか?

また、より多くの(小さい)関数を作成すると、関数のパラメーターが3つ以下になる傾向が高くなることにも気づくでしょう。抽出関数/メソッドはあなたの友達です:-)。

より多くのパラメータを持つために、グローバル変数を使用しないでください!それは悪い習慣をさらに悪いものに交換することです!

2
bytedev

PHP 4から例をとると、mktime()の関数シグネチャを見てください:

  • int mktime ([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] )

混乱していませんか?この関数は「make time」と呼ばれますが、日、月、年のパラメーターと3つの時間パラメーターを受け取ります。彼らが行った順番を覚えるのはどれほど簡単ですか? mktime(1, 2, 3, 4, 5, 2246);が表示されたらどうしますか?他に言及せずにそれを理解できますか? _2246_は24時間制の "22:46"と解釈されますか?他のパラメータはどういう意味ですか?オブジェクトとしてはもっといいでしょう。

PHP 5に移動します。現在、DateTimeオブジェクトがあります。そのメソッドには、2つのsetDate()setTime()があります。それらのシグネチャは次のとおりです。

  • public DateTime setDate ( int $year , int $month , int $day )
  • public DateTime setTime ( int $hour , int $minute [, int $second = 0 ] )

パラメータの順序が最大から最小に変わることを覚えておく必要がありますが、それは大きな改善です。 6つのパラメータすべてを一度に設定できる単一のメソッドはないことに注意してください。これを行うには、2つの個別の呼び出しを行う必要があります。

ボブおじさんが話しているのは、平らな構造を避けることです。関連するパラメータは1つのオブジェクトにグループ化する必要があります。パラメータが4つ以上ある場合は、疑似オブジェクトがそこにある可能性が高く、適切なオブジェクトを作成してさらに分離することができます。 PHPには個別のDateおよびTimeクラスはありませんが、DateTimeには実際にDateオブジェクトとTimeオブジェクトが含まれていると考えることができます。

次のような構造になる可能性があります。

_<?php
$my_date = new Date;
$my_date->setDay(5);
$my_date->setMonth(4);
$my_date->setYear(2246);

$my_time = new Time;
$my_time->setHour(1);
$my_time->setMinute(2);
$my_time->setSecond(3);

$my_date_time = new DateTime;
$my_date_time->setTime($my_time);
$my_date_time->setDate($my_date);
?>
_

毎回2つまたは3つのパラメーターを設定する必要がありますか?時間のみまたは日のみを変更する場合は、簡単に変更できます。はい、オブジェクトを検証して、各パラメーターが他のパラメーターと連動することを確認する必要がありますが、それはいずれにせよ以前はそうでした。

最も重要なことは、理解しやすく、それゆえ維持することが簡単ですか?一番下のコードブロックは、単一のmktime()関数よりも大きいですが、理解しやすいと思います。プログラマーではない人でも、それが何をするのかを考えるのにそれほど苦労することはないでしょう。目標は、常により短いコードやより賢いコードではなく、より保守しやすいコードです。

ああ、グローバルは使用しないでください!

1
CJ Dennis

ここで良い答えがたくさんありますが、ほとんどはその点に取り組んでいません。なぜこれらの経験則は?それはスコープについてであり、依存関係についてであり、適切なモデリングについてです。

引数のグローバルは、単純化したように見えるだけでなく、実際には複雑さを隠しただけなので、さらに悪くなります。プロトタイプではもう表示されませんが、それを意識する必要があります(これは表示されないので難しいので)、関数のロジックに頭を動かしても役に立ちません。あなたの背中の後ろに干渉する他の隠されたロジックであります。スコープはあちこちに行き渡り、何でも依存関係を導入しました。なぜなら、何でも変数に干渉できるからです。良くない。

関数について維持する必要がある主なことは、プロトタイプを見て呼び出して、その機能を理解できることです。したがって、名前は明確で明確でなければなりません。しかし、また、議論が多いほど、それが何をしているのかを理解することが難しくなります。それはあなたの頭の中のスコープを広げます、あまりにも多くのことが起こっています、これがあなたが数を制限したい理由です。それはあなたがどんな種類の議論を扱っているかは重要です、いくつかは他のものより悪いです。大文字と小文字を区別しない処理を可能にする追加のオプションのブール値は、関数を理解するのを難しくしません。そのため、それについて大したことはありません。補足として、列挙型の意味は呼び出しで明白であるため、列挙型はブール値よりも優れた引数を作成します。

典型的な問題は、膨大な量の引数を使用して新しい関数を作成することではありません。解決しようとしている問題が実際にどれほど複雑であるかをまだ理解していない場合は、ほんの数個から始めます。プログラムが進化するにつれて、引数リストは徐々に長くなる傾向があります。モデルがあなたの心に定着したら、それはあなたが知っている安全な参照なので、あなたはそれを保持したいと思います。しかし、振り返ってみると、モデルはそれほど素晴らしいものではないかもしれません。ロジックの1つまたは複数のステップを逃し、1つまたは2つのエンティティを認識できませんでした。 「OK ...最初からやり直して、1、2日かけてコードをリファクタリングするか、...この引数を追加して、結局は正しいことを実行して、それで終了することができます。今のところ、このシナリオでは。付箋紙を移動できるように、このバグをプレートから取り除くため。」

2番目のソリューションを使用する頻度が高くなるほど、メンテナンスのコストが高くなり、リファクタリングが難しくなり、魅力がなくなります。

既存の関数の引数の数を減らすためのボイラープレートソリューションはありません。それらをコンパウンドにグループ化するだけでは、物事を簡単にするのではなく、カーペットの下の複雑さを拭くもう1つの方法にすぎません。それを正しく行うには、コールスタック全体をもう一度見て、何が欠けているか、間違っているかを認識する必要があります。

1
Martin Maat

グローバル変数の使用は、常にコーディングするのに簡単な方法のように見えますが(特に小さなプログラムでは)、コードの拡張が難しくなります。

はい、配列を使用してパラメータを1つのエンティティにバインドすることで、関数のパラメータの数を減らすことができます。

function <functionname>(var1,var2,var3,var4.....var(n)){}

上記の関数は編集され、[連想配列を使用]に変更されます。

data=array(var1->var1,
           var2->var2
           var3->var3..
           .....
           ); // data is an associative array

function <functionname>(data)

robert bristow-johnsonの回答 に同意します。構造体を使用して単一のエンティティにデータをバインドすることもできます。

1
Narender Parmar

多くの関数パラメーターの有効な代替手段は、パラメーターオブジェクトを導入することです。これはcomposedメソッドがあり、そのパラメーターの(ほとんど)を他の多くのメソッドに渡す場合に便利です。

単純なケースでは、これはプロパティとして古いパラメータしか持たない単純なDTOです。

1
Timothy Truckle

一緒に送信された多くのパラメーターをグループ化すると、改善されることが何度かありました。

  • 一緒に使用されているこの概念の集まりに適切な名前を付ける必要があります。これらのパラメーターを一緒に使用する場合、それらには関係がある(または一緒に使用してはならない)場合があります。

  • オブジェクトを使用すると、通常はこの機能が明らかに一見すると不思議なオブジェクトに移動できることがわかります。通常、テストは簡単で、凝集度が高く、カップリングが低いため、さらに優れています。

  • 次に新しいパラメーターを追加する必要があるときは、多くのメソッドのシグネチャーを変更せずに、パラメーターを含めるための素晴らしい場所があります。

したがって、100%動作しない場合がありますが、このパラメーターのリストをオブジェクトにグループ化する必要があるかどうかを確認してください。そしてお願いします。オブジェクトの作成を回避するために、タプルなどの型なしクラスを使用しないでください。オブジェクト指向プログラミングを使用しているので、必要に応じてさらにオブジェクトを作成してもかまいません。

0
Borjab

すべての敬意を払って、関数に少数のパラメーターを含めるというポイントを完全に逃したことは間違いありません。

アイデアは、脳は一度に多くの「アクティブな情報」しか保持できず、関数にnパラメータがある場合、nより多くの "コードの一部が何をしているかを簡単かつ正確に理解するためにあなたの脳にある必要があるアクティブな情報(Steve McConnell、Code Complete(より良い本、IMO))は、メソッドの本体の7つの変数について同様のことを述べています:まれに私たちはそれに到達しますが、それ以上、あなたは能力を失い、すべてをまっすぐに保つ能力を失います。

変数の数が少ないのは、このコードを操作する際の認識要件を小さく保つことができるため、コードをより効果的に操作(または読み取る)できるためです。これの副次的な原因は、十分に因数分解されたコードは、ますますパラメーターが少なくなる傾向があることです(たとえば、因数分解が不十分なコードは、大量のものを混乱にグループ化する傾向があります)。

値の代わりにオブジェクトを渡すことで、おそらくそれが実現する必要があるため、脳の抽象化のレベルを取得する可能性がありますはい、ここで動作するSearchContextがあります 15個のプロパティについて考える代わりにその検索コンテキスト内にある可能性があります。

グローバル変数を使用しようとすることで、完全に間違った方向に進んでしまいました。これで、関数のパラメーターが多すぎるという問題を解決できなかっただけでなく、その問題を取り込んで、それを投入しました心に留めておく必要があるはるかに優れた問題!

ここで、関数レベルだけで作業するのではなく、プロジェクトのグローバルスコープも検討する必要があります(うわー、なんてひどい!私は身震いします...)。あなたはあなたの前に関数のすべての情報さえ持っていません(がらくた、そのグローバル変数名は何ですか?)(私がこの関数を呼び出してからこれが他の何かによって変更されていないことを願っています)。

グローバルスコープの変数は、プロジェクト(大規模または小規模)で確認することが非常に悪いものの1つです。それらは、適切なスコープ管理の障害を示します。これは、プログラミングの非常に基本的なスキルです。彼ら。あります。悪の。

パラメータを関数から移動してグローバルに配置することで、床(または脚、または顔)で自分を撃ちました。そして神はあなたがこれを決定することを禁じますねえ、私は何かのためにこのグローバルを再利用することができます...私の心は思考に震えます。

全体の理想は、物事を管理しやすくすることであり、グローバル変数はそれを行うつもりはありません。私は、それらがあなたが反対の方向に行くためにあなたがすることができる最悪のことの1つであるとまで言うでしょう。

0
jleach