関数名の引数の名前をより一般的にエンコードしないのはなぜですか?[閉まっている]


47

クリーンなコードの作者は、例を示します

assertExpectedEqualsActual(expected, actual)

assertEquals(expected, actual)

前者は、議論がどこに行くのか、それから生じる潜在的な誤用を覚える必要がなくなるため、より明確であると主張した。しかし、どのコードでも前者の命名スキームの例を見たことはなく、後者を常に見ています。著者が主張するように、後者が前者よりも明確であれば、なぜ前者を採用しないのですか?


9
これは議論のための素晴らしい質問だと思います。しかし、客観的な答えで答えられるものではありません。したがって、この質問は意見に基づくものとして閉じられるかもしれません。
陶酔

54
多くの人が最初の命名体系に反対するのは、それが過度に冗長だからです。特にのためにassertEquals()、そのメソッドはコードベースで数百回使用されているため、読者は一度慣習に慣れることが期待できます。異なるフレームワークには異なる規則があります(例(actual, expected) or an agnostic (左、右) `)が、私の経験では、せいぜい混乱の原因にすぎません。
アモン

5
利益はその利益に比べて非常に小さいため、正気な人はおそらく立ち去るでしょう。より流fluentなアプローチが必要場合は、assert(a).toEqual(b)関連するいくつかのアサーションを連鎖させる可能性のある場所(IMOが不必要に冗長であっても)を試してください。
アドリアーノRepetti

18
実際の値と期待値が値であることをどのようにして知ることができますか?きっとあるはずassertExpectedValueEqualsActualValue?しかし、待って、どのように我々はそれを使用するかどうか覚えています==.equalsまたはObject.equals?あるべきassertExpectedValueEqualsMethodReturnsTrueWithActualValueParameter
user253751

6
この特定の方法では、2つの引数の順序は重要ではないことを考えると、この命名スキームの利点を支持することを選択するのは貧弱な例のようです。
スティーブンランド

回答:


66

入力するのが多く、読むのが多いから

最も単純な理由は、ユーザーが入力する回数を減らすことを好み、その情報をエンコードすることで入力回数が増えることです。それを読むとき、たとえ引数の順序がどうあるべきかに精通していても、すべてを読む必要があるたびに。引数の順序に精通していない場合でも...

多くの開発者がIDEを使用しています

IDEは、多くの場合、ホバーするかキーボードショートカットを使用して、特定のメソッドのドキュメントを表示するメカニズムを提供します。このため、パラメーターの名前は常に手元にあります。

引数をエンコードすると、複製と結合が発生します

パラメーターの名前は、それらが何であるかをすでに文書化しているはずです。メソッド名に名前を書き出すことで、メソッド署名にもその情報を複製しています。また、メソッド名とパラメーターの間の結合を作成します。言うexpectedと、actualユーザーの皆様に混乱しています。からに移動assertEquals(expected, actual)するassertEquals(planned, real)場合、関数を使用してクライアントコードを変更する必要はありません。からにassertExpectedEqualsActual(expected, actual)移動assertPlannedEqualsReal(planned, real)すると、APIに重大な変更が加えられます。または、メソッド名を変更しないため、すぐに混乱します。

あいまいな引数の代わりに型を使用する

本当の問題は、同じ型であるため簡単に切り替えられる曖昧な引数があることです。代わりに、型システムとコンパイラを使用して正しい順序を強制できます。

class Expected<T> {
    private T value;
    Expected(T value) { this.value = value; }
    static Expected<T> is(T value) { return new Expected<T>(value); }
}

class Actual<T> {
    private T value;
    Actual(T value) { this.value = value; }
    static Actual<T> is(T value) { return new Actual<T>(value); }
}

static assertEquals(Expected<T> expected, Actual<T> actual) { /* ... */ }

// How it is used
assertEquals(Expected.is(10), Actual.is(x));

これは、コンパイラレベルで実行でき、逆方向に取得できないことを保証します。別の角度からアプローチすると、これは基本的にHamcrestライブラリがテストのために行うことです。


5
IDEを使用している場合、バルーンヘルプにパラメーター名があります。使用しない場合、関数名を覚えていることは引数を覚えていることと同じなので、どちらの方法でも何も得られません。
ピーター-モニカの復活

29
assertExpectedEqualsActual「入力するのが多く、読むのが多いから」に反対するなら、どうすれば支持できますassertEquals(Expected.is(10), Actual.is(x))か?
ruakh

9
@ruakhそれは比較できません。assertExpectedEqualsActualそれでもプログラマーは引数を正しい順序で指定することに注意する必要があります。assertEquals(Expected<T> expected, Actual<T> actual)署名は全く異なるアプローチである正しい使用を強制するためにコンパイラを使用します。あなたは、例えば、簡潔にするためにこのアプローチを最適化することができますexpect(10).equalsActual(x)...が、それは問題ではなかった
ホルガー

6
また、この特定のケース(==)では、引数の順序は実際には最終値とは無関係です。順序は、副作用(障害の報告)にのみ関係します。問題を注文するとき、それは(わずかに)より理にかなっているかもしれません。たとえば、strcpy(dest、src)。
クリスチャンH

1
特に、重複と結合に関する部分については同意できません...関数パラメーターが名前を変更するたびに、関数名も変更する必要がある場合、その関数のすべての使用法を追跡する必要があります。同様にそれらを変更します...それは、私、私のチーム、そしてコードを依存関係として使用している他のすべての人にとって、多くの破壊的な変更を行います
...-mrsmn

20

プログラミングに関する長年の議論について尋ねます。冗長性はどれくらい良いですか?一般的な答えとして、開発者は、引数を指定する余分な冗長性は価値がないことを発見しました。

冗長性は、常により明確になることを意味しません。検討する

copyFromSourceStreamToDestinationStreamWithoutBlocking(fileStreamFromChoosePreferredOutputDialog, heuristicallyDecidedSourceFileHandle)

copy(output, source)

両方に同じバグが含まれていますが、実際にそのバグを見つけやすくしましたか?原則として、デバッグが最も簡単なのは、バグのある少数のものを除き、すべてが最大限簡潔な場合であり、それらは何が問題なのかを伝えるのに十分冗長です。

冗長性を追加する長い歴史があります。たとえば、一般的に人気のない「ハンガリー記法」がありlpszNameます。それは一般に、一般的なプログラマーの大衆の道端で落ちました。ただし、メンバー変数名(mNameまたはm_Nameなどname_)に文字を追加すると、一部のサークルで引き続き人気があります。他の人はそれを完全に落としました。私はたまたま物理シミュレーションのコードベースで作業しています。そのコーディングスタイルドキュメントでは、ベクトルを返す関数は関数呼び出し(getPositionECEF)でベクトルのフレームを指定する必要があります。

Appleで人気のある言語に興味があるかもしれません。Objective-Cには、関数の署名の一部として引数名が含まれています(関数[atm withdrawFundsFrom: account usingPin: userProvidedPin]はドキュメント内でwithdrawFundsFrom:usingPin:。として記述されています。これが関数の名前です)。Swiftは同様の決定を行い、関数呼び出しに引数名を入れる必要があります(greet(person: "Bob", day: "Tuesday"))。


13
他のすべての点はさておき、それが書かれていれば、 はるかに読みやすくなりますそれがどれほど簡単だったか見てみましょう!それは、その巨大な壊れた単語サラダの途中で小さな変更を見逃すのは簡単すぎて、単語の境界がどこにあるのかを理解するのに時間がかかるからです。スマッシングの混乱。copyFromSourceStreamToDestinationStreamWithoutBlocking(fileStreamFromChoosePreferredOutputDialog, heuristicallyDecidedSourceFileHandle)copy_from_source_stream_to_destination_stream_without_blocking(file_stream_from_choose_preferred_output_dialog, heuristically_decided_source_file_handle)
tchrist

1
obj-C構文withdrawFundsFrom: account usingPin: userProvidedPinは、実際にはSmallTalkから借用しています。
joH1

14
@tchristは、神聖な戦争が関係するトピックについて正しいことを確信していることに注意してください。反対側は常に間違っているとは限りません。
コートアンモン

3
@tchrist Addingunderscoresnakesthingseasiertoreadnotharderasyouseeは引数を操作しています。ここでの答えは大文字を使用しましたが、これは省略しています。 AddingCapitalizationMakesThingsEasyEnoughToReadAsYouCanSeeHere。第二に、10のうち9回は、名前を超えて成長することはありません[verb][adjective][noun](各ブロックは省略可能です)、簡単な大文字と小文字を使用しても読みやすい形式:ReadSimpleName
Flater

5
@tchrist-あなたの研究の科学(無料の全文リンク)は、下線スタイルを使用するように訓練されたプログラマーがラクダの場合よりも下線スタイルを読む方が速いことを単に示しています。また、データは、経験のある被験者ほど差が小さいことを示しています(そして、学生である被験者のほとんどは、特に経験がなかった可能性が高いことを示唆しています)。これは、ラクダケースの使用により多くの時間を費やしたプログラマが同じ結果をもたらすことを意味するものではありません。
ジュール

8

「Clean Code」の作者は正当な問題を指摘していますが、彼の提案する解決策はかなり洗練されていません。通常、不明瞭なメソッド名を改善するより良い方法があります。

彼はassertEquals(xUnitスタイルの単体テストライブラリから)どちらの引数が期待され、どの引数が実際であるかを明確にしないことは正しい。これも私に噛みついた!多くの単体テストライブラリがこの問題を指摘し、次のような代替構文を導入しています。

actual.Should().Be(expected);

または類似。それは確かによりassertEqualsもはっきりしていますが、よりもずっと良いですassertExpectedEqualsActual。そして、それははるかに構成可能です。


1
私は肛門であり、推奨される順序に従いますが、結果fun(x)が5になると予想される場合、順序を逆にすると何がうまくいかないのassert(fun(x), 5)でしょうか?どうやって噛んだの?
エモリー

3
@emory jUnitは(少なくとも)expectedとの値からthouroughエラーメッセージを作成することを知っているactualので、それらを逆にすると、メッセージが不正確になる 可能性があります。しかし、私はそれがより自然に聞こえることに同意します:)
joH1

@ joH1それは私には弱いようです。失敗したコードは失敗し、コードを渡すことはassert(expected, observed)、行うかどうかにかかわらず合格しますassert(observed, expected)。より良い例は次のようなものですlocateLatitudeLongitude-座標を逆にすると、真剣に混乱します。
エモリー

1
@emory単体テストで賢明なエラーメッセージを気にしない人は、古いコードベースで「Assert.IsTrue failed」に対処しなければならない理由です。デバッグするのは非常に楽しいです。ただし、この場合、問題はそれほど重要ではないかもしれません(引数の順序が一般に重要なファジー比較を行う場合を除く)。流Fluなアサーションは、実際にこの問題を回避し、コードをより表現力豊かにする(そして起動するためのより良いエラーメッセージを提供する)ための素晴らしい方法です。
Voo

@emory:引数を逆にすると、エラーメッセージが誤解を招きやすくなり、デバッグ時に間違ったパスに送られます。
ジャックB

5

ScyllaとCharybdisの間のパスを明確にするために、無駄な冗長性(目的のないランブリングとも呼ばれます)と過度の簡潔さ(不可解な簡潔さ)を避けようとしています。

そのため、評価するインターフェイス、2つのオブジェクトが等しいというデバッグアサーションを行う方法を検討する必要があります。

  1. アリティと名前を考慮できる他の機能はありますか?
    いいえ、名前自体は明確です。
  2. タイプに意味はありますか?
    いいえ、無視します。あなたはすでにそれをしましたか?良い。
  3. 引数は対称ですか?
    ほぼエラーが発生すると、メッセージは各引数表現をそれぞれの場所に配置します。

それで、その小さな違いが重要であり、既存の強力な慣習によってカバーされないかどうかを見てみましょう。

引数が意図せずに入れ替わった場合、意図した聴衆は不便ですか?
いいえ、開発者もスタックトレースを取得します。バグを修正するには、とにかくソースコードを精査する必要があります。
完全なスタックトレースがなくても、アサーションの位置はその疑問を解決します。そして、それさえ欠けていて、それがどちらであるかというメッセージから明らかでないなら、それはせいぜい可能性を2倍にします。

引数の順序は規則に従っていますか?
そうであるようです。せいぜい弱い慣習に思えますが。

したがって、違いはまったく取るに足らないように見え、引数の順序は、それを関数名にエンコードするためのあらゆる努力が負の効用を持つという十分に強い慣習によってカバーされます。


順序はjUnitでは重要かもしれません。jUnitはexpectedand の値から特定のエラーメッセージを作成しますactual(少なくともStringsでは)
joH1

私は...私はその部分をカバーだと思う
デュプリケータ

あなたはそれを言及したが、考えてみます。assertEquals("foo", "doo")エラーメッセージがある与えますComparisonFailure: expected:<[f]oo> but was:<[d]oo>...音がより多くのことを値は、メッセージの意味を反転う入れ替える私に対称。とにかくあなたが言ったように、開発者にはエラーを解決するための他のインジケータがありますが、それは私見を誤解させ、デバッグに少し時間がかかる可能性があります。
joH1

少なくともAT&TとIntelの構文が存在する限り、両方の陣営(dest、srcとsrc、dest)がこれについて議論していることを考えると、引数の順序に「慣習」があるという考えは面白いです。そして、単体テストでの役に立たないエラーメッセージは、強制されずに根絶されるべき疫病です。「Assert.IsTrueが失敗しました」と同じくらい悪いです(「ユニットテストを実行してデバッグする必要があるので、もう一度実行して、ブレークポイントを置くだけです」、順序が正しいかどうかを確認してください」)。
VOO

@Voo:問題は、それを間違えた場合の「損傷」はごくわずかであり(ロジックはそれに依存せず、メッセージユーティリティが大幅に損なわれることはない)、IDEを記述するときにパラメータ名が表示されることですとにかく入力します。
デュプリケータ

3

多くの場合、論理的な明確さは追加されません。

「追加」と「AddFirstArgumentToSecondArgument」を比較します。

たとえば、3つの値を追加するオーバーロードが必要な場合。何がもっと理にかなっていますか?

3つの引数を持つ別の「追加」?

または

「AddFirstAndSecondAndThirdArgument」?

メソッドの名前は、論理的な意味を伝える必要があります。それが何をするかを伝えるべきです。どんな段階を踏むかをミクロレベルで伝えることは、読者にとってそれを容易にするものではありません。引数の名前は、必要に応じて追加の詳細を提供します。さらに詳細が必要な場合は、コードがすぐそこにあります。


4
Add可換演算を提案します。OPは、注文が重要な状況に関心を持っています。
ロージーF

Swiftでは、add()関数を定義する場合、たとえばadd(5、to:x)またはadd(5、plus:7、to:x)またはadd(5、plus:7与える:x)を呼び出しますそれに応じて。
gnasher729

3番目のオーバーロードは「Sum」という名前にする必要があります
-StingyJack

@StringyJack Hmm .. Sumは命令ではなく、名詞であるためメソッド名には適さなくなります。しかし、あなたがそのように感じ、あなたがそれについて純粋主義者になりたいなら、2引数バージョンもSumという名前にする必要があります。Addメソッドを使用する場合は、オブジェクトインスタンス自体に追加される引数が1つ必要です(引数は数値型またはベクトル型である必要があります)。2つ以上の引数の種類(名前を付けるものは何でも)は静的です。次に、3つ以上の引数バージョンは冗長になり、プラス演算子を実装します。
マーティンマート

1
@Martin待って何?sumは完璧に隠語の動詞です。「要約する」というフレーズでは特に一般的です。
Voo

2

私は他の答えによって示唆された何かを追加したいと思いますが、明示的に言及されているとは思わない:

@puckは、「関数名で最初に言及した引数が実際に最初のパラメーターであるという保証はまだありません」と述べています。

@cbojarは「曖昧な引数の代わりに型を使用する」と言っています

問題は、プログラミング言語が名前を理解しないということです。名前は不透明でアトミックなシンボルとして扱われます。したがって、コードのコメントのように、関数の名前と実際の動作との間には必ずしも相関関係はありません。

次のassertExpectedEqualsActual(foo, bar)ようないくつかの選択肢(このページや他の場所から)と比較してください。

# Putting the arguments in a labelled structure
assertEquals({expected: foo, actual: bar})

# Using a keyword arguments language feature
assertEquals(expected=foo, actual=bar)

# Giving the arguments different types, forcing us to wrap them
assertEquals(Expected(foo), Actual(bar))

# Breaking the symmetry and attaching the code to one of the arguments
bar.Should().Be(foo)

これらはすべて、冗長な名前よりも多くの構造を持っているため、言語に不透明なものを見ることができます。関数の定義と使用法もこの構造に依存しているため、実装が実行していること(名前やコメントなど)と非同期になることはありません。

このような問題に遭遇したり予見したりするとき、イライラしてコンピューターに向かって叫ぶ前に、まず、そのコンピューターをまったく非難するのが「公平」かどうかを尋ねます。言い換えれば、マシンには、私が望んでいたものと私が求めたものを区別するのに十分な情報が与えられていましたか?

のような呼び出しassertEqual(expected, actual)は理にかなっているassertEqual(actual, expected)ので、それらを混同させたり、マシンが先に進んで間違ったことをしたりするのは簡単です。私たちが使用している場合assertExpectedEqualsActual、代わりにそれはなるかもしれない、私たちは間違いを犯す可能性が低いが、それはマシンにこれ以上の情報を与えない(それは英語を理解することはできません、と名前の選択は意味論に影響を与えるべきではありません)。

キーワードの引数、ラベル付きフィールド、特殊タイプなど、「構造化」アプローチをより好ましいものにしているのは、余分な情報も機械可読であるため、機械が誤った使用法を見つけて、正しいことを行えるようにするためです。assertEqual唯一の問題は、不正確なメッセージになりますので、場合には、あまりにも悪いことではありません。より不吉な例として、がありますがString replace(String old, String new, String content)、これは混同しやすく、String replace(String content, String old, String new)意味がまったく異なります。簡単な解決策は、ペアを取得することです[old, new]。これにより、ミスが発生するとすぐにエラーがトリガーされます(型がなくても)。

型を使用しても、「必要なものをマシンに伝える」ことができない場合があります。たとえば、「stringly typed programming」と呼ばれるアンチパターンは、すべてのデータを文字列として扱います。これにより、引数の混同(この場合など)、ステップの実行の忘れ(エスケープなど)、誤って不変式の破壊(例えば解析不能なJSONの作成など)

これは「ブール盲目」とも関連しています。ブール盲点(または数値など)をコードの一部で計算しますが、別の部分で使用しようとすると、実際に何を表しているのかがわかりません。これらを混同している、など。これを、わかりやすい名前(たとえば)LOGGING_DISABLEDではなく、混同するとfalseエラーメッセージが発生する個別の列挙型と比較してください。


1

引数がどこに行くのかを覚える必要がなくなるからです

ほんとに?関数名で最初に言及した引数が実際に最初のパラメーターであるという保証はまだありません。盲目的に非常にばかげた名前に頼るよりも、調べて(またはIDEにそれをさせて)妥当な名前のままにしておく方が良いでしょう。

コードを読むと、パラメーターに名前を付けたときにどうなるかが簡単にわかります。copy(source, destination)はのようなものよりずっと理解しやすいcopyFromTheFirstLocationToTheSecondLocation(placeA, placeB)です。

著者が主張するように、後者が前者よりも明確であれば、なぜ前者を採用しないのですか?

異なるスタイルには異なる視点があり、反対のことを述べている他の記事の著者を見つけることができるからです。誰かがどこかで書いたものすべてを追いかけようとすると狂ってしまうでしょう;-)


0

パラメーター名を関数名にエンコードすると、関数の記述と使用がより直感的になることに同意します。

copyFromSourceToDestination( // "...ahh yes, the source directory goes first"

関数やシェルコマンドの引数の順序を忘れがちであり、多くのプログラマーはこの理由からIDEの機能または関数参照に依存しています。名前に引数が記述されていると、この依存関係に対する雄弁な解決策になります。

ただし、ほとんどの場合、名前付き変数が使用されるため、引数の記述は、ステートメントを読まなければならない次のプログラマーにとって冗長になります。

copy(sourceDir, destinationDir); // "...makes sense"

これの簡潔さは、ほとんどのプログラマーに勝ち、個人的には読みやすくなります。

編集:@Blrflが指摘したように、最初に関数の名前を覚えておく必要があるので、エンコードパラメータは結局「直感的」ではありません。これには、関数参照を検索するか、とにかくパラメーターの順序情報を提供するIDEからヘルプを取得する必要があります。


9
したがって、悪魔の擁護者を1分間演じることができれば:関数の完全な名前を知っているときだけ直感的です。あなたが知っている場合はそこコピー機能だとあなたはそれがだかどうか覚えていないcopyFromSourceToDestinationか、copyToDestinationFromSourceあなたの選択肢は試行錯誤によってそれを見つけるか、参考資料を読んでいます、。部分的な名前を完成できるIDEは、後者の自動化されたバージョンです。
Blrfl

@Blrflそれを呼び出すポイントはcopyFromSourceToDestination、あなたがそうだと思うならcopyToDestinationFromSource、コンパイラはあなたのバグを見つけるでしょうが、それが呼ばれたならcopy、それはそうしないだろうということです。strcpy、strcatなどが先例を設定しているため、コピールーチンのパラメータを間違った方法で取得するのは簡単です。簡潔な方が読みやすいですか?mergeLists(listA、listB、listC)はlistBとlistCからlistAを作成しますか、それともlistAとlistBを読み取ってlistCを書き込みますか?
ロージーF

4
@RosieF引数が何を意味するのかよくわからなければ、コードを書く前にドキュメントを読んでいたでしょう。さらに、より詳細な関数名を使用しても、順序が実際に何であるかを解釈する余地がまだあります。コードをよく見ていない人は、関数の名前にあるものが引数の順序を反映しているという慣習を確立したことを直感することはできません。彼らはそれを事前に知るか、ドキュメントを読む必要があります。
Blrfl

OTOH、destinationDir.copy(sourceDir); //「...もっと意味がある」
クリスチャンH

1
@KristianHどの方向がdir1.copy(dir2)機能しますか?わからない。どうdir1.copyTo(dir2)
maaartinus
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.