関数を呼び出すときに関数のパラメーター名がわからない場合の対処


13

ここにあなたの考えを聞きたいプログラミング/言語の問題があります。

私たちは、ほとんどのプログラマーが従うべき規約を開発しました。これは言語構文の一部ではありませんが、コードを読みやすくするのに役立ちます。もちろん、これらは常に議論の問題ですが、ほとんどのプログラマーが同意すると思う少なくともいくつかのコア概念があります。変数に適切な名前を付け、一般的な名前を付け、極端に長くならないように行を作成し、長い関数、カプセル化などを避けます。

しかし、まだコメントしている人がいないという問題があり、それが最大の問題かもしれません。関数を呼び出すときに引数が匿名になるという問題です。

関数は数学に由来しますが、f(x)には明確な意味があります。これは、関数にはプログラミングで通常行われるより厳密な定義があるためです。数学の純粋な関数は、プログラミングの場合よりもはるかに少ないことができ、はるかに洗練されたツールです。通常、1つの引数(通常は数値)のみを受け取り、常に1つの値(通常は数値)を返します。関数が複数の引数を取る場合、それらはほとんどの場合、関数のドメインの単なる余分な次元です。つまり、1つの引数は他の引数よりも重要ではありません。確かに明示的に順序付けされていますが、それ以外には意味の順序付けはありません。

しかし、プログラミングでは、関数を定義する自由度が高くなります。この場合、それは良いことではないと主張します。一般的な状況では、このように定義された関数があります

func DrawRectangleClipped (rectToDraw, fillColor, clippingRect) {}

定義を見ると、関数が正しく記述されていれば、何が何であるかは完全に明確です。関数を呼び出すとき、IDE /エディターで次の引数がどうあるべきかを伝えるインテリセンス/コード補完のマジックを持っているかもしれません。ちょっと待って。実際に通話を作成しているときに必要な場合、ここで不足しているものはありませんか?コードを読んでいる人にはIDEの利点がなく、定義にジャンプしない限り、引数として渡された2つの長方形のどちらが何に使用されるかわかりません。

問題はそれ以上です。引数がローカル変数に由来する場合、変数名のみが表示されるため、2番目の引数が何であるかさえわからない状況があります。たとえば、次のコード行を見てください

DrawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])

これはさまざまな言語でさまざまな程度に緩和されますが、厳密に型指定された言語でさえ、変数に適切な名前を付けたとしても、関数に渡すときの変数の型については言及しません。

通常はプログラミングの場合と同様に、この問題には多くの解決策があります。多くはすでに一般的な言語で実装されています。たとえば、C#の名前付きパラメーター。しかし、私が知っているすべてには重大な欠点があります。すべての関数呼び出しですべてのパラメーターに名前を付けると、コードが読みやすくなる可能性はありません。多分、プレーンテキストプログラミングが提供する可能性を超えているように感じます。ほとんどすべての領域でJUSTテキストから移動しましたが、まだ同じコードを使用しています。コードに表示するためにより多くの情報が必要ですか?テキストを追加します。とにかく、これは少し接線になっているので、ここで停止します。

2番目のコードスニペットに答えた1つの回答は、おそらく最初にいくつかの名前付き変数に配列をアンパックしてから使用するでしょうが、変数の名前は多くのことを意味する可能性があり、呼び出される方法は必ずしもあなたがするべき方法を教えてくれません呼び出された関数のコンテキストで解釈されます。ローカルスコープには、leftRectangleとrightRectangleという名前の2つの四角形があります。これは、それらが意味的に表すものであるが、関数に与えられたときに表すものに拡張する必要がないためです。

実際、呼び出された関数のコンテキストで変数に名前が付けられている場合、その関数呼び出しで潜在的に可能な情報よりも少ない情報を導入しており、コードが悪いコードになる場合があります。rectForClippingに格納する四角形を作成するプロシージャと、rectForDrawingを提供する別のプロシージャがある場合、DrawRectangleClippedへの実際の呼び出しは単なるセレモニーです。新しいことは何も意味せず、ネーミングで既に説明しているにもかかわらず、コンピューターがまさにあなたが望むものを正確に知っているように存在する行。これは良いことではありません。

私はこれについて新鮮な視点を聞きたいです。私はこれを問題と考える最初の人ではないと確信していますが、どのように解決しますか?


2
私は正確な問題が何であるかについて混乱しています...ここにはいくつかのアイデアがあるようですが、どれがあなたの主なポイントであるかはわかりません。
FrustratedWithFormsDesigner

1
関数のドキュメントには、引数が何をするかが記載されています。コードを読んでいる人がドキュメントを持っていないかもしれないが、実際にはコードが何をするのか、そしてコードを読んで得た意味が知識に基づいた推測であるとは知らないことに反対するかもしれません。読者がコードが正しいことを知る必要がある状況では、ドキュメントが必要になります。
ドーバル

3
@Darwin関数型プログラミングでは、すべての関数にはまだ1つの引数しかありません。「複数の引数」を渡す必要がある場合、パラメータは通常、タプル(順序付けする場合)またはレコード(配列にすることを望まない場合)です。さらに、関数の特殊なバージョンをいつでも簡単に作成できるため、必要な引数の数を減らすことができます。ほとんどすべての関数型言語は、タプルやレコードの構文を提供するので、値を束ねことは無痛で、あなたは自由のための構図を取得(することができますチェーン機能するタプルを取るものと帰りのタプル。)
Doval

1
@Bergi人々は純粋なFPでより一般化する傾向があるので、関数自体は通常より小さく、より多くなると思います。しかし、私は大丈夫です。Haskellやギャングとの実際のプロジェクトに取り組んだ経験はあまりありません。
ダーウィン

4
答えは「変数に「deserializedArray」という名前を付けないで」だと思いますか?
whatsisname

回答:


10

関数の頻繁な使用方法は、コードの記述、特にコードの読み取りに混乱を招く可能性があることに同意します。

この問題に対する答えは、部分的に言語に依存します。前述のとおり、C#には名前付きパラメーターがあります。この問題に対するObjective-Cのソリューションには、よりわかりやすいメソッド名が含まれます。たとえば、stringByReplacingOccurrencesOfString:withString:明確なパラメータを持つメソッドです。

Groovyでは、一部の関数はマップを取得し、次のような構文を許可します。

restClient.post(path: 'path/to/somewhere',
            body: requestBody,
            requestContentType: 'application/json')

一般に、関数に渡すパラメーターの数を制限することにより、この問題を解決できます。2-3が良い制限だと思います。関数がより多くのパラメーターを必要とするように見える場合、デザインを再考します。しかし、これは一般的に答えるのが難しい場合があります。関数でやりすぎていることがあります。パラメータを保存するためのクラスを検討することが理にかなっている場合があります。また、実際には、多くの場合、多数のパラメーターを受け取る関数には、オプションの多くが含まれていることがよくあります。

Objective-Cのような言語でも、パラメーターの数を制限することは理にかなっています。1つの理由は、多くのパラメーターがオプションであることです。例については、rangeOfString:およびNSStringのバリエーションを参照してください。

Javaでよく使用するパターンは、流なスタイルのクラスをパラメーターとして使用することです。例えば:

something.draw(new Box().withHeight(5).withWidth(20))

これは、クラスをパラメーターとして使用し、流styleなスタイルのクラスで、コードを読みやすくします。

上記のJavaスニペットは、パラメーターの順序がそれほど明白でない場合にも役立ちます。通常、座標ではXがYの前に来ると想定します。また、通常、幅より前に高さを規則として見ていますが、それでもまだ明確ではありません(something.draw(5, 20))。

私もいくつかのような機能を見てきましたdrawWithHeightAndWidth(5, 20)が、これらでさえあまり多くのパラメータを取ることはできません。


2
Javaの例を続ける場合、順序は確かに非常に注意が必要です。例えばAWTから次のコンストラクタを比較:Dimension(int width, int height)及びGridLayout(int rows, int cols)(行数が高さ、意味はGridLayout第1の高さと有するDimension幅)。
ピエールアラード

1
このような矛盾は、非常にPHP(と批判されているeev.ee/blog/2012/04/09/php-a-fractal-of-bad-design例えば、):array_filter($input, $callback)array_map($callback, $input)strpos($haystack, $needle)array_search($needle, $haystack)
ピエールArlaud

12

ほとんどの場合、関数、パラメーター、および引数の適切な命名によって解決されます。しかし、あなたはすでにそれを探求し、それが欠陥を持っていることを発見しました。これらの欠陥のほとんどは、呼び出しコンテキストと呼び出しコンテキストの両方で、少数のパラメーターを使用して関数を小さく保つことにより軽減されます。呼び出している関数はいくつかのことを一度に行おうとしているため、特定の例には問題があります。ベース矩形の指定、クリッピング領域の指定、描画、特定の色での塗りつぶし。

これは、形容詞のみを使用して文を書き込もうとするようなものです。より多くの動詞(関数呼び出し)をそこに入れ、文の主題(オブジェクト)を作成すると、読みやすくなります。

rect.clip(clipRect).fill(color)

ひどい名前clipRectcolor持っている場合(そうでない方がいい)でも、コンテキストからそのタイプを識別することができます。

デシリアライズされた例は、呼び出し側のコンテキストが一度に多くのこと、つまりデシリアライズと描画を試みているため、問題があります。意味があり、2つの責任を明確に分離する名前を割り当てる必要があります。少なくとも、次のようなもの:

(rect, clipRect, color) = deserializeClippedRect()
rect.clip(clipRect).fill(color)

多くの読みやすさの問題は、人間がコンテキストとセマンティクスを見分けるために必要な中間段階をスキップしすぎて簡潔にしようとすることによって引き起こされます。


1
私は意味を明確にするために複数の関数呼び出しを文字列化するというアイデアが好きですが、それは問題の周りにただ踊っていませんか?基本的には「文章を書きたいのですが、訴えている言語は私に許可しないので、最も近いものだけを使用できます」
ダーウィン

@Darwin私見では、プログラミング言語をより自然言語に近づけることによって、これを改善できるわけではありません。自然言語は非常に曖昧であり、文脈でしか理解できず、実際には確信が持てません。すべての用語には(理想的には)ドキュメントと利用可能なソースがあり、構造を明確にする括弧とドットがあるため、関数呼び出しの文字列化ははるかに優れています。
maaartinus

3

実際には、より良い設計によって解決されます。よく書かれた関数が3つ以上の入力を受け取ることは非常にまれであり、それが発生した場合、それらの多くの入力が凝集バンドルに集約できないことはまれです。これにより、関数を分割したり、パラメーターを集約したりするのが非常に簡単になり、関数の処理が多くなりすぎないようにします。1つには2つの入力があるため、名前を付けるのが簡単になり、どの入力がどの入力であるかがより明確になります。

私のおもちゃの言語にはこれに対処するフレーズの概念があり、他のより自然言語に焦点を当てたプログラミング言語には他のアプローチがありますが、すべて他の欠点があります。さらに、フレーズであっても、関数の名前をより良いものにするための素晴らしい構文にすぎません。たくさんの入力が必要な場合、適切な関数名を作成することは常に困難になります。


フレーズは本当に一歩前進のようです。一部の言語には同様の機能がありますが、広く普及しているFARです。言うまでもなく、マクロを正しく使用したことがないC(++)純粋主義者からのすべてのマクロ嫌悪により、人気のある言語ではこのような機能はありません。
ダーウィン

一般的なトピックへようこそドメイン固有言語、何か私は本当に多くの人がの...(1)利点を理解してほしい
Izkata

2

Javascriptを(またはのECMAScript)、例えば、多くのプログラマがに慣れて育ちました

単一の匿名オブジェクトに名前付きオブジェクトプロパティのセットとしてパラメーターを渡します。

そして、プログラミングの実践として、プログラマーからライブラリーへ、そしてそこから他のプログラマーへと伝わり、それを気に入って使用し、さらにライブラリーなどを作成しました。

呼び出す代わりに

function drawRectangleClipped (rectToDraw, fillColor, clippingRect)

このような:

drawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])

、これは有効で正しいスタイルです。

function drawRectangleClipped (params)

このような:

drawRectangleClipped({
    rectToDraw: deserializedArray[0], 
    fillColor: deserializedArray[1], 
    clippingRect: deserializedArray[2]
})

、それはあなたの質問に関して有効かつ正確いいです。

もちろん、これには適切な条件が必要です。Javascriptでは、たとえばCよりもはるかに実行可能です。javascriptでは、XMLの軽量版として普及した現在広く使用されている構造表記法が誕生しました。JSONと呼ばれます(すでに聞いたことがあるかもしれません)。


その言語について構文を検証するのに十分な知識はありませんが、全体的にこの投稿が好きです。かなりエレガントに見えます。+1
ITアレックス

多くの場合、これは通常の引数と組み合わされます。つまりparamsこの関数のように、1〜3個の引数の後に(多くの場合、オプションの引数とそれ自体がオプション)が続きます。これにより、多くの引数を持つ関数を非常に把握しやすくなります(私の例では、2つの必須引数と6つのオプション引数があります)。
maaartinus

0

次にObjective-Cを使用する必要があります。関数定義は次のとおりです。

- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject

そして、ここで使用されます:

[someObject performSelector:someSelector withObject:someObject2 withObject:someObject3];

ルビーには似たような構造があり、他の言語でキーと値のリストを使用してシミュレートできると思います。

Javaの複雑な関数の場合、関数の表現でダミー変数を定義するのが好きです。左から右の例:

Rectangle referenceRectangle = leftRectangle;
Rectangle targetRectangle = rightRectangle;
doSomeWeirdStuffWithRectangles(referenceRectangle, targetRectangle);

より多くのコーディングのように見えますが、たとえば、leftRectangleを使用して、コードの将来のメンテナーに理解できないと思われる場合は、「ローカル変数の抽出」を使用して後でコードをリファクタリングできます。


そのJavaの例について、なぜそれが良い解決策ではないと思うのかという質問に書きました。あれについてどう思う?
ダーウィン

0

私のアプローチは、一時的なローカル変数を作成することです-しかし、ちょうどそれらを呼び出すことではないLeftRectangeRightRectangle。むしろ、より意味を伝えるためにやや長い名前を使用しています。私はよく、名前をできる限り区別しようとします。たとえば、両方のsomething_rectangle役割を左右しない場合は、両方を呼び出さないようにします。

例(C ++):

auto& connector_source = deserializedArray[0]; 
auto& connector_target = deserializedArray[1]; 
auto& bounding_box = deserializedArray[2]; 
DoWeirdThing(connector_source, connector_target, bounding_box)

そして、私はワンライナーのラッパー関数またはテンプレートを書くかもしれません:

template <typename T1, typename T2, typename T3>
draw_bounded_connector(
    T1& connector_source, T2& connector_target,const T3& bounding_box) 
{
    DoWeirdThing(connector_source, connector_target, bounding_box)
}

(C ++を知らない場合は、アンパサンドを無視してください)。

関数が適切な説明なしでいくつかの奇妙なことを行う場合-おそらくリファクタリングが必要です!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.