ここに私があなたの考えを聞きたいプログラミング/言語問題があります。
ほとんどのプログラマが従うべき規則を開発しましたが、これは言語構文の一部ではありませんが、コードを読みやすくするために役立ちます。これらはもちろん常に議論の問題ですが、ほとんどのプログラマーが納得できるコアな概念が少なくともいくつかあります。変数に適切な名前を付け、一般的に名前を付け、行が極端に長くならないようにし、長い関数、カプセル化などを回避します。
しかし、まだ誰もコメントしていない問題があり、それが一番の問題かもしれません。関数を呼び出すときに引数が匿名になるという問題です。
関数は数学に由来します。ここで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への実際の呼び出しは単なる儀式です。新しいことを意味せず、名前ですでに説明したものの、コンピューターが正確に何を望んでいるかを知るための行。これは良いことではありません。
私はこれについて新鮮な視点を聞いてみたいです。私がこれを問題と考える最初の人ではないので、どうやってそれを解決しますか?
関数の頻繁な使用方法は、コードの作成、特にコードの読み取りの混乱を招く可能性があることに同意します。
この問題への答えは、部分的に言語に依存します。おっしゃったように、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))
_
これは、クラスをパラメーターとして使用し、流暢なスタイルのクラスを使用して、コードを簡単に読み取れるようにします。
上記のJavaスニペットは、パラメーターの順序がそれほど明確でない場合にも役立ちます。通常、座標ではXがYの前に来ると想定しています。通常、高さは幅の前に表示されますが、まだ明確ではありません(something.draw(5, 20)
)。
drawWithHeightAndWidth(5, 20)
のようないくつかの関数も見ましたが、これらでもあまり多くのパラメーターを取ることができず、読みにくくなる可能性があります。
ほとんどの場合、関数、パラメーター、および引数の適切な命名によって解決されます。しかし、あなたはすでにそれを調査し、それが欠陥を持っていることを発見しました。これらの欠陥のほとんどは、呼び出しコンテキストと呼び出されたコンテキストの両方で、少数のパラメーターを使用して関数を小さく保つことで軽減されます。呼び出している関数がいくつかのことを一度に実行しようとしているため、特定の例は問題があります。基本の長方形を指定し、クリッピング領域を指定し、それを描画し、特定の色で塗りつぶします。
これは形容詞だけを使って文章を書こうとするようなものです。そこに動詞(関数呼び出し)を追加し、文の主語(オブジェクト)を作成すると、読みやすくなります。
rect.clip(clipRect).fill(color)
clipRect
とcolor
にひどい名前が付けられていても(そうしてはいけません)、コンテキストからそれらのタイプを識別できます。
呼び出し側のコンテキストが一度に多くのことを実行しようとしているため、逆シリアル化された例には問題があります。意味のある名前を割り当て、2つの責任を明確に区別する必要があります。少なくとも、次のようなもの:
(rect, clipRect, color) = deserializeClippedRect()
rect.clip(clipRect).fill(color)
可読性の問題の多くは、人間がコンテキストとセマンティクスを識別するために必要な中間段階をスキップして、簡潔すぎるようにしようとすることによって引き起こされます。
実際には、より良い設計によって解決されます。適切に作成された関数が3つ以上の入力を取ることは例外的に珍しく、発生した場合、それらの多くの入力がいくつかのまとまりのあるバンドルに集約できないことはまれです。これにより、関数の分割やパラメーターの集計が非常に簡単になるため、関数に必要以上の操作を行わせる必要がありません。 1つは2つの入力を持ち、名前を付けるのが簡単になり、どの入力がどれであるかがより明確になります。
私のおもちゃの言語には、これに対処するための phrases の概念があり、他のより自然言語に焦点を当てたプログラミング言語には、それに対処する他のアプローチがありましたが、それらすべてに他の欠点があります。さらに、フレーズであっても、関数に適切な名前を付けるための構文にすぎません。大量の入力を受け取る場合、適切な関数名を作成するのは常に困難です。
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]
})
、これは有効で正しいand質問に対してすばらしい。
もちろん、これには適切な条件が必要です。Javascriptでは、これはCなどよりもはるかに実行可能です。JavaScriptでは、これにより、XMLの軽量版として広く普及している現在広く使用されている構造表記が誕生しました。これはJSONと呼ばれます(すでに聞いたことがあるかもしれません)。
次に、objective-Cを使用する必要があります。関数定義は次のとおりです。
- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject
そしてここではそれが使用されています:
[someObject performSelector:someSelector withObject:someObject2 withObject:someObject3];
Rubyにも同様の構成があり、キー値リストを使用して他の言語でそれらをシミュレートできると思います。
Javaの複雑な関数の場合、関数の表現でダミー変数を定義したい。左から右の例の場合:
Rectangle referenceRectangle = leftRectangle;
Rectangle targetRectangle = rightRectangle;
doSomeWeirdStuffWithRectangles(referenceRectangle, targetRectangle);
より多くのコーディングのように見えますが、たとえば、leftRectangleを使用し、コードの将来のメンテナーに理解できないと思われる場合は、「Extract local variable」を使用して後でコードをリファクタリングできます。
私のアプローチは、一時的なローカル変数を作成することですが、LeftRectange
およびRightRectangle
と呼ぶだけではありません。むしろ、私はより長い名前を使用して、より多くの意味を伝えています。私はよく名前をできるだけ区別しようとします。それらの両方を呼び出さない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++がわからない場合はアンパサンドを無視してください)。
関数が適切な説明なしでいくつかの奇妙なことを行う場合、おそらくリファクタリングする必要があります!