web-dev-qa-db-ja.com

パラメータをオブジェクトとして渡すことは、プログラミングの習慣として不適切ですか?

したがって、オブジェクトをパラメーターとして受け取るメソッドを作成するのが好きな人がいるので、「非常に柔軟」にできます。次に、内部的に、さまざまな型を処理するために、直接キャスト、リフレクション、またはメソッドのオーバーロードを行います。

これは悪い習慣だと思いますが、その理由を正確に説明することはできません。ただし、読みにくくなる場合があります。これが悪い習慣になる理由は他にもありますか?それらは何でしょうか?


したがって、一部の人々は例を求めています。彼は次のようなインターフェースを定義しています:

public void construct(Object input, Object output);

そして、いくつかをリストに入れてこれらを使用することを計画しているため、ビットを構築して出力オブジェクトに追加します。

for (ConstructingThing thing : constructingThings)
    thing.construct(input, output);
return output;

次に、constructを実装するものに、入力/出力に一致する適切なメソッドを見つけてそれを呼び出し、入力/出力を渡す、不安定なリフレクションがあります。

私は彼に言い続けます、それは機能します、私はそれが機能することを理解しますが、すべてを単一のクラスに入れます。私たちは再利用と保守性について話しているのですが、彼は実際に自分を制約していて、彼が思っているよりも再利用が少ないと思います。保守性は、おそらく現在は彼にとっては高いですが、将来的には彼や他の誰にとっても非常に低くなるでしょう。

101
Risser

実用的の観点から、私はこれらの問題を見ます:

  • 考えられる実行タイプエラーの膨張-Javaを使用して回避できる多くの動的タイプチェックが強力なタイプチェッカーを含まない限り。
  • 不要なキャストがたくさん
  • メソッドがそのシグネチャによって何をするかを理解する難しさ

理論の観点から、私はこれらの問題を見ます:

  • クラスのインターフェースのコントラクトの欠如。すべてのパラメーターがObjectタイプである場合は、クライアントクラスに有益な情報を宣言していません。
  • 過負荷の可能性の欠如
  • オーバーライドの誤り。メソッドをオーバーライドし、そのパラメータータイプを変更することで、継承に関連するすべてのものを壊すことができます。
104
Jack

このメソッドは Liskov置換原理 に違反しています。

メソッドがObjectタイプのパラメーターを受け入れる場合、メソッドに渡すオブジェクトはすべて機能することを期待しています。

Objectの特定のサブクラスのみが許可されているようです。どのタイプが許可されているかを知る唯一の方法は、メソッドの詳細を知ることです。言い換えれば、この方法は宣伝されているほど柔軟性がありません。

110
Aaron Kurtzhals

次の直接的な影響を検討します。

読みやすさ

彼と一緒に仕事をすることは、世界で最も楽しい経験とは言えません。型が最終的にどうなるか、またはパラメーターがどうなるかは決してわかりません。それであなたの時間を無駄にしてくれて感謝しているとは思えません。

タイプ署名

Javaはすべてオブジェクトです。では、最後にそのメソッドは何をするのでしょうか?

速度

リフレクション、キャスト、型チェックなどはすべて、パフォーマンスの低下につながります。どうして?彼の意見では、物事を柔軟にするからです。

対自然

Javaは誕生以来強く型付けされてきました。 JavaScriptまたはPythonまたはRuby=動的に型付けされた方言が必要な場合)の記述を開始するように指示します。彼の背景はおそらくそれらの1つに強く基づいています。

柔軟性

問題の同僚と同じ用語を使用する場合、Javaはすでに柔軟性を理解しています。動的に型付けされた言語によって装着された馬の眼鏡でそれを見る意味はありません。

インターフェース、ポリモーフィズム、共分散と逆分散、ジェネリックス、継承、抽象クラス、およびメソッドのオーバーロードは、いわゆる「柔軟性」に寄与するもののほんの一部です。柔軟性は、特定のタイプから別のタイプに実質的に何でも特化することは簡単であるという事実によって与えられます(通常、直接の上位タイプまたは下位タイプを特化することを意味します)。

ドキュメント

プログラミングの主な目標の1つは、再利用可能で安定したコードを記述することです。あなたの例のドキュメントはどのように見えますか?他の開発者がそのようなものを使用できるようになるまでの学習時間はどれくらいですか?自己修正型のアセンブラーコードを作成し、その責任を負うことをお勧めします。

53
flavian

ここにはすでに良い答えがたくさんあります。二次的な考えを追加したい:

このパターンは、おそらく "コードのにおい"です。

つまり、「究極の」柔軟性のためにオブジェクトをメソッドに渡す必要がある場合、メソッドの定義が不十分である可能性が高いです。強く型付けされたパラメーターを持つことは、呼び出し元への「契約」を示します(「これ」のように見えるものが必要です)。そのようなコントラクトを指定できない可能性が最も高いのは、メソッドの処理が多すぎるか、スコープの定義が不十分であることです。

41
jtahlborn

これは、コンパイラーが行う型検査を取り除き、オブジェクトが具象型にキャストされたときにランタイム例外をさらに引き起こす可能性があります。したがって、無効なキャストがすべてキャッチされて適切に処理されない限り、エンドユーザーには未処理の例外が表示されます。

22

これは一般に悪い習慣であるという他の回答にも同意します。

Objectを渡す方が優れている場合がある特定のケースが1つあります。最終的に文字列を処理する場合、XMLまたはJSONを作成する場合はe.g。です。その場合、次のようなものの代わりに:

_public void addAttribute(String s) { 
  //... add s to the XML
}
_

私は好む:

_public void addAttribute(Object o) {
   String s = String.valueOf(o);  // perhaps check for null explicitly
   // now add s to the XML
}
_

これにより、呼び出しコードに無限の_String.valueOf_が保存されます。また、varargとうまく連携します。そして、オートボクシングでは、intsfloatsなどを渡すことができます...すべてのオブジェクトはtoString()を持っているので、これは、おそらくLiskovなどに違反しません。 。

16
user949300

はい。 Javaは厳密にtypecastedであると想定されています。ただし、パラメータとしてObjectを使用すると、このルールに実質的に違反します。さらに、プログラムのバグが発生しやすくなります。

そして、それがgenericsが導入された主な理由の1つです。

5
Ankit

柔軟性があり、複数のタイプのデータを処理できることがまさに、メソッドのオーバーロードとインターフェースの作成目的です。 Javaには、型チェックによるコンパイル時の安全性を保証しながら、これらの状況の処理をより簡単にする機能がすでにあります。

また、メソッドが操作するデータを厳密に定義することが不可能である場合、そのメソッドは処理が多すぎる可能性があり、より小さなメソッドに分解する必要があります。

私が読んだ経験則が好きです(コードコンプリートではそう思います)。ルールは、関数またはメソッドがその名前を読むだけで何をするかを知ることができるはずであると述べています。そのアイデアを使用してfunction\methodに明確な名前を付けることが本当に難しい場合(または名前にAndを使用する必要がある場合)、関数/メソッドは実際に多くのことを行っています。

5
Peter Smith

私は実際にはそのようなパッケージで一度作業する必要がありました。それはGUIパッケージでした。デザイナーはPascalのバックグラウンド出身で、基本クラスだけをどこにでも渡すのは素晴らしいアイデアだと思いました。

20年間で、このツールキットでの作業は、私のキャリアの中で最も迷惑な経験の1つでした。

このようなコードで作業する必要があるまでは、パラメーターのデータ型にどれだけ依存しているかがわかりません。 「connect(Object dest)」というメソッドがあると言ったら、どういう意味ですか? 「connect(Object website)」と表示されてもあまり役に立ちませんが、「connect(String website)」または「connect(URL website)」と考える必要はありません。

"connect(Object website)"は文字列またはURLを取ることができるので素晴らしいアイデアだと思うかもしれませんが、それは、プログラマーがサポートすることに決めたケースとそうでないケースを推測する必要があることを意味します。せいぜい、開発の試行錯誤部分のほとんどをコーディング時からコンパイル時まで移動させ、最悪の場合、開発プロセス全体を濁らせて煩わしくします。

私(そしてほとんどのJavaプログラマー、私は信じています)は、コンストラクターへのパラメーターのグループを調べて使用するコンストラクターを確認し、次に渡す必要のあるオブジェクトを構築する方法を確認することで、常にAPI呼び出しチェーンを把握していますあなたが持っている、または作ることができるオブジェクトに到達するまで、それは不完全な例(Cでの私のJava以前の経験)を粗末なドキュメントで検索するのではなく、パズルを組み立てるようなものです。

パズルのようにコードを作成することは一時的なもののようですが、それは驚くほどうまく機能します。

私はこれが答えられたことを知っていますが、これは、できるだけ少ない人々がもう一度それを経験する必要があることを確認するためのモチベーションを本当に感じた迷惑な十分な経験でした。

2
Bill K

まず、具体的なオブジェクトとしてパラメーターを渡すと、間違ったオブジェクトを使用することによるエラー(予期しないクラスのインスタンス)がコンパイル時に検出されるため、修正が速くなります。他の場合では、エラーは実行時に表示され、検出および修正が難しくなります。

次に、インスタンスの実際のクラス、リフレクションなどをチェックするコードは、直接呼び出しよりも遅くなります

2
Evans

私が従うルールは...パラメータの定義は、メソッドに必要なだけ具体的でなければならず、それ以上でもそれ以下でもないはずです。

私が必要とするのが列挙型を反復することだけである場合、paramは配列やリストではなく列挙型として定義されます。これにより、任意のタイプの列挙型を使用でき、特定のタイプへの変換を回避できます。

また、列挙型が必要なため、オブジェクトとして定義しません。列挙可能でないオブジェクトは、このメソッドでは機能しません。メソッドのシグネチャを読んでそれが何を意味するのかわからないのは紛らわしく、実行時のテストでのみエラーを見つけることができます。

2
drewburlingame

彼がすることは、静的型システムを持つことのすべての利点を捨てることです。

単に:コンパイラとコードを読む人々の両方にとって、そのようなメソッドが何をするのかはそれほど明確ではありません。

静的型システムの最大の理由の1つは、プログラムの動作についてコンパイル時の保証があることです。コンパイラーは、コードが正しく使用されていることを確認でき、正しく使用されていない場合は、コンパイラー時エラーを発行します。同僚が書いているようなメソッドは、コンパイラ時のチェックが欠落しているため、実行時エラーが発生しやすいはるかに多くです。実行時エラーを準備し、より多くのテストを作成できるようにします。

もう1つの大きな問題は、メソッドが何をするのかがまったく明確でないことです。そのようなコードを読んだり、コードを保守したり、使用したりするのは大変なことです。

Javaはジェネリックを追加して、コードに関するより強力なコンパイル時の保証を提供します。Scalaは、その機能的アプローチ、および共変型と反変型の洗練された型システムでさらに進んでいます。あなたの同僚はこの開発ラインに逆行し、その利点をすべて捨てています。

代わりに適切に型指定されたオーバーロードされたメソッドを使用した場合、彼(およびあなた全員)は次のようになります。

  • これらのメソッドが正しく使用されているかコンパイラーがチェックします。
  • 許可される引数の組み合わせと許可されない引数の組み合わせを明確に示します。
  • これらの組み合わせのそれぞれを適切かつ個別に文書化する可能性。
  • コードの可読性と保守性。
  • それは彼が自分のメソッドをより小さな部分に分割するのを助け、デバッグ、テスト、そしてメンテナンスがより簡単になります。 (参照 他の開発者に短いメソッドを書くように説得する方法は? 。)
2
Petr Pudlák

タイプに何かを与えるために与える:

  1. ユーザーはセマンティックな説明をコーディングするためのインターフェース。
  2. 最適化プログラムがコード実行時間を増やしたり、コードサイズをより簡単に縮小したりできるように、解析するインターフェイスをコンパイラーに提供します。
  3. 開発サイクルの早い段階でコードエラーをキャッチする早期(静的)バインディング。

オブジェクトだけを使用してもほとんど情報が伝達されないため、上記の理由で言語に組み込まれたこれらの機能はすべて削除されます。

また、型システムは、これらすべての条件を考慮したis-a関係を備えた柔軟性のために設計されました。だからあなたの利点にそれを使用してください。保守不能なコードを作成することで無駄にしないでください。

特定の時間にこのようなことをする特別な理由があるかもしれませんが、これを行うことは、一般的に、これは一般的には、悪い習慣です。あなたがそれをしているなら、他の人に問題を見渡してもらい、それが別の方法でできるかどうか確かめてください。ほとんどの場合(99.999%)は可能です。

それがあり得ない0.001%で、少なくともあなたは全員参加していて、何が起こっているのかを知っています(おそらく、問題についていくつかの新鮮な目が必要です)。

1
Adrian

はい。これは、メソッドが渡された特定の入力に対して必要なことを実行できる保証がないため、不適切な習慣です。広く使用できるメソッドを作成するという目標は悪い目標ではありませんが、それを達成する方法。

この特定のケースでは、インターフェースの使用に最適な状況のように思えます。インターフェイスは、メソッドに渡されるオブジェクトが必要な情報を提供し、キャストを回避できるようにします。これがうまく機能しない状況もいくつかあります(たとえば、インターフェイス実装を追加できない封印されたクラスまたは基本クラスのいずれかで機能する必要がある場合など)が、メソッド全体では、有効な入力のみを受け取る必要があります。パラメーター。

1
AJ Henderson

これまでのところ、非常に詳細な答えはありますが、高水準の哲学と方法論に入る前でも、それらは怠惰で短く、単純であると私は本当に言わなければなりません。

彼らは、他の人が保守および理解できるようにコードに優れた情報を提供しておらず、手元にあるプログラミング言語の設計に反対しています。私はPerlが好きで、Objective-Cも好きです。ただし、私は、Java、C#、またはC++を好みに合わせて曲げたり、保守したり、読みやすく、効率的なコードを使用したりすることはしません。

はい、関数の動作や入力を変更する必要がある場合は、ここでいくつかの行の変更といくつかのリファクタリング手順を保存します。ただし、EclipseやXcodeなどの最近のほとんどのIDEは、単純な右クリックコマンドでリファクタリングを自動的に処理できることは言うまでもなく、コードを維持する必要のある人にとってはコストが高くなります。さらに、他の人が述べたように、渡される不明なオブジェクトタイプごとに、キャストごとに処理コストが増加します。適切なクラスもウィンドウからスローされることを知ってランタイムが実行する基本的な組み込みのスピードアップを想像します。

構文砂糖は常に開発者にとってソルトよりも優れていますが、両方のバランスの取れた食事はプロジェクトと関係する他の人(顧客、アプリケーション、他の開発者など)の健康を保証します

0
FaultyJuggler