関数を呼び出すときに次のように書くことができれば、コードの読み取りがいかに簡単になるかを考えていました。
doFunction(param1=something, param2=somethingElse);
私はどんな欠点も考えることができません、そしてそれはコードをより読みやすくするでしょう。配列を唯一の引数として渡し、配列キーをパラメーター名として使用できることは承知していますが、それでもまだそれほど読みやすくはありません。
私が見逃しているデメリットはありますか?そうでない場合、なぜ多くの言語がこれを許可しないのですか?
パラメータ名を指定しても常にコードが読みやすくなるわけではありません。例として、min(first=0, second=1)
またはmin(0, 1)
?
前の段落の引数を受け入れる場合、パラメーター名の指定が必須ではないことは明らかです。すべての言語でパラメーター名の指定が有効なオプションにならないのはなぜですか?
少なくとも2つの理由が考えられます。
(名前付きパラメーターを必要とする)オプションパラメーターも実装せずに名前付きパラメーターを実装する言語を知らないことに注意してください-したがって、 Fluent Interfaces 。
コードを読んでいるときに、名前付きパラメーターを使用すると、コードを理解しやすくするコンテキストを導入できます。たとえば、次のコンストラクタを検討してください:Color(1, 102, 205, 170)
。それは一体どういう意味ですか?実際、Color(alpha: 1, red: 102, green: 205, blue: 170)
の方がはるかに読みやすいでしょう。しかし、悲しいかなコンパイラは「いいえ」と言います – Color(a: 1, r: 102, g: 205, b: 170)
が必要です。名前付きパラメーターを使用してコードを記述する場合、正確な名前を検索するのに不必要な時間を費やします。順序を忘れるよりも、一部のパラメーターの正確な名前を忘れた方が簡単です。
これは、ほぼ同じインターフェイスでポイントと期間の2つの兄弟クラスを持つDateTime
APIを使用したときに一度私に噛み付きました。 DateTime->new(...)
は_second => 30
_引数を受け入れましたが、DateTime::Duration->new(...)
は_seconds => 30
_を必要としており、他のユニットでも同様です。はい、それは絶対に理にかなっていますが、これは名前付きパラメーター≠使いやすさを示しました。
名前付きパラメーターがどのように不良になるかを示す別の例は、おそらく[〜#〜] r [〜#〜]言語です。次のコードは、データプロットを作成します。
_plot(plotdata$n, plotdata$mu, type="p", pch=17, lty=1, bty="n", ann=FALSE, axes=FALSE)
_
xおよびyデータ行の2つの位置引数が表示され、次に名前付きパラメーターのリストが表示されます。デフォルトにはさらに多くのオプションがあり、デフォルトを変更または明示的に指定したいオプションのみがリストされています。このコードがマジックナンバーを使用することを無視し、列挙型を使用することでメリットが得られると(Rが存在する場合)、問題はこれらのパラメーター名の多くがかなり判読できないことです。
pch
は実際にはプロット文字であり、すべてのデータポイントに対して描画されるグリフです。 _17
_は空の円、またはそのようなものです。lty
は線種です。ここで_1
_は実線です。bty
はボックスのタイプです。これを_"n"
_に設定すると、プロットの周囲にボックスが描画されなくなります。ann
は、軸の注釈の外観を制御します。それぞれの省略形が何を意味するのかを知らない人にとって、これらのオプションはかなり混乱しています。これは、Rがこれらのラベルを使用する理由も明らかにします。自己文書化コードとしてではなく、(動的に型付けされた言語であるため)キーとして値を正しい変数にマップします。
関数シグネチャには次のプロパティがあります。
異なる言語がこのシステムの異なる座標に到達します。 Cでは、引数は順序付けられ、名前が付けられておらず、常に必須であり、可変引数にすることができます。 Javaの場合も同様ですが、シグネチャがオーバーロードされる可能性があります。ObjectiveCでは、シグネチャは順序付けられ、名前が付けられ、必須であり、Cの周りの構文上の砂糖であるため、オーバーロードできません。
Varargsを使用して動的に型付けされた言語(コマンドラインインターフェイス、Perlなど)は、オプションの名前付きパラメーターをエミュレートできます。シグネチャサイズのオーバーロードを伴う言語には、位置オプションパラメータのようなものがあります。
名前付きパラメーターについて考えるとき、通常、名前付きのオプションの順序付けられていないパラメーターを想定しています。これらの実装は困難です。
オプションのパラメータにはデフォルト値を設定できます。これらは、呼び出された関数で指定する必要があり、呼び出しコードにコンパイルしないでください。そうしないと、すべての依存コードを再コンパイルせずにデフォルトを更新できません。
ここで重要な質問は、引数が実際に関数に渡される方法です。順序付けられたパラメーターを使用すると、argsをレジスターに渡すか、スタック上の固有の順序で渡すことができます。しばらくレジスタを除外する場合、問題は、順序付けられていないオプションの引数をスタックに配置する方法です。
そのためには、オプションの引数を順序付ける必要があります。宣言コードが変更された場合はどうなりますか?順序は重要ではないため、関数宣言での並べ替えによってスタック上の値の位置が変わることはありません。新しいオプションパラメータを追加できるかどうかも検討する必要があります。ユーザーの観点からは、以前はそのパラメーターを使用していなかったコードでも新しいパラメーターで動作するはずなので、これはそうです。そのため、宣言での順序の使用やアルファベット順の使用などの順序付けは除外されます。
これもサブタイピングとリスコフ置換原則に照らして検討してください。コンパイルされた出力では、同じ命令で、おそらく新しい名前付きパラメーターを持つサブタイプとスーパータイプでメソッドを呼び出すことができます。
明確な順序を設定できない場合は、順序付けされていないデータ構造が必要です。
最も単純な実装は、パラメータの名前と値を渡すだけです。これは、名前付きパラメーターがPerlまたはコマンドラインツールでエミュレートされる方法です。これにより、上記のすべての拡張機能の問題が解決されますが、スペースを大幅に浪費する可能性があり、パフォーマンスが重要なコードのオプションではありません。また、これらの名前付きパラメーターの処理は、単にスタックから値をポップするよりもはるかに複雑になりました。
実際には、必要な領域は文字列プーリングを使用することで削減できます。これにより、後の文字列比較をポインター比較に削減できます(ただし、静的文字列が実際にプールされることが保証できない場合は、2つの文字列を比較する必要があります)。詳細)。
代わりに、名前付き引数の辞書として機能する巧妙なデータ構造を渡すこともできます。これは、呼び出し側でキーのセットが静的に認識されるため、呼び出し側では安価です。これにより、完全なハッシュ関数を作成したり、トライを事前に計算したりできます。呼び出し先は、多少のコストがかかるすべての可能なパラメータ名の存在をテストする必要があります。このようなものはPythonで使用されます。
名前付きパラメーターを持つ関数が適切に拡張可能である場合、明確な順序付けは想定できません。したがって、ソリューションは2つしかありません。
関数宣言の変数名は通常、内部的な意味を持ち、インターフェイスの一部ではありません。たとえ多くのドキュメンテーションツールで変数名が表示される場合でも同様です。多くの場合、内部変数とそれに対応する名前付き引数には異なる名前が必要です。名前付きパラメーターの外部から見える名前の選択を許可しない言語は、呼び出しコンテキストを考慮して変数名が使用されない場合、それらの多くを得ることができません。
名前付き引数のエミュレーションの問題は、呼び出し側の静的チェックがないことです。これは、引数のディクショナリを渡す場合(Pythonを見て)、特に忘れがちです。辞書を渡すことが一般的な回避策であるため、これは重要です。 JavaScriptの場合:foo({bar: "baz", qux: 42})
。ここでは、値のタイプや特定の名前の有無を静的にチェックできません。
単に文字列をキーとして使用し、オブジェクトを値として使用することは、静的型チェッカーの存在下ではあまり役に立ちません。ただし、名前付き引数は、構造体またはオブジェクトリテラルでエミュレートできます。
_// Java
static abstract class Arguments {
public String bar = "default";
public int qux = 0;
}
void foo(Arguments args) {
...
}
/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});
_
ハンガリー記法がもはや広く使われていないのと同じ理由で。 Intellisense(またはMicrosoft IDE以外の場合の道徳的同等物)。最新のIDEのほとんどは、パラメーター参照の上にマウスを置くだけで、パラメーターについて知る必要があるすべてのことを教えてくれます。
とはいえ、C#やDelphiなど、多くの言語が名前付きパラメーターをサポートしています。 C#では、これはオプションであるため、使用したくない場合は使用する必要はありません。また、オブジェクトの初期化など、メンバーを具体的に宣言する方法は他にもあります。
名前付きパラメーターは、すべてではなく、オプションのパラメーターのサブセットのみを指定する場合に最も役立ちます。 C#では、プログラマーにこの柔軟性を提供するために多数のオーバーロードされたメソッドを必要としないため、これは非常に便利です。
名前付きパラメーターはリファクタリングソースコードの問題の解決策であり、ソースコードをより読みやすくすることを意図したものではありません。名前付きパラメーターは、コンパイラー/パーサーが関数呼び出しのデフォルト値を解決するのを支援するために使用されます。コードを読みやすくするには、意味のあるコメントを追加します。
foo(1)
のシグネチャが変更されるとfoo()
は壊れますが、foo(name:1)
は必要なものを壊す可能性が低いため、リファクタリングが難しい言語は名前付きパラメーターをサポートすることがよくありますプログラマーがfoo
に変更を加える手間が省けます。
次の関数に新しいパラメーターを導入する必要があり、その関数を呼び出すコードが数百または数千行ある場合はどうしますか?
foo(int weight, int height, int age = 0);
ほとんどのプログラマーは次のことを行います。
foo(int weight, int height, int age = 0, string gender = null);
現在、リファクタリングは必要なく、gender
がnullの場合、関数はレガシーモードで実行できます。
特定のgender
の値が[〜#〜] hardcoded [〜#〜]になり、age
の呼び出しになりました。この例として:
foo(10,10,0,"female");
プログラマーは関数定義を見て、age
のデフォルト値が0
であることを確認し、その値を使用しました。
これで、関数定義のage
のデフォルト値は完全に役に立たなくなりました。
名前付きパラメーターを使用すると、この問題を回避できます。
foo(weight: 10, height: 10, gender: "female");
新しいパラメーターをfoo
に追加できます。パラメーターを追加した順序を気にする必要はありません。また、設定したデフォルトが実際の真のデフォルト値であることを知っているため、デフォルト値を変更できます。
Bashは確かにこのスタイルのプログラミングをサポートしており、PerlとRubyは両方とも名前/値パラメーターリストの受け渡しをサポートしています(ネイティブハッシュ/マップをサポートするすべての言語と同じように)。パラメータに名前を付ける構造体/レコードまたはハッシュ/マップ。
ハッシュ/マップ/キー値ストアをネイティブに含む言語の場合、イディオムを採用してキー/値ハッシュを関数/メソッドに渡すだけで、必要な機能を取得できます。プロジェクトで試してみると、使いやすさから得られた生産性の向上、または欠陥の減少による品質の向上のいずれかから、何かを得たかどうかについてのより良い洞察が得られます。
経験豊富なプログラマーは、順序/スロットでパラメーターを渡すことに慣れていることに注意してください。また、このイディオムは、いくつかの引数(たとえば> 5/6)を超える場合にのみ価値があることに気付く場合もあります。また、多数の引数は(過度に)複雑なメソッドを示していることが多いため、このイディオムは最も複雑なメソッドに対してのみ有益であることがわかります。
それはc#がVB.netよりも人気があるのと同じ理由だと思います。 VB.NETはより「読みやすく」なりますが、たとえば、閉じ括弧の代わりに「end if」と入力すると、コードが埋め込まれ、最終的には理解しにくくなります。
コードを理解しやすくするのは簡潔さです。少ないほど良い。とにかく、関数のパラメーター名は通常かなりわかりやすいので、コードを理解するのにそれほど役立ちません。