Reflectionの使用に問題はありますか?


49

理由はわかりませんが、リフレクションを使用するときは常に「ごまかし」のような気がします-おそらく、自分が取っているパフォーマンスヒットのせいでしょう。

私の一部は、それがあなたが使用している言語の一部であり、あなたがしようとしていることを達成できるなら、なぜそれを使用しないのかと言います。私の他の部分は、リフレクションを使用せずにこれを行うことができる方法がある必要があると言います。多分それは状況次第だと思います。

リフレクションを使用する際に注意する必要のある潜在的な問題は何ですか?従来の解決策を見つけようとするのにどれだけの労力を費やす価値がありますか?


2
言語と環境に依存すると思います。一部の人はそれをサポートし、さらには奨励しています。時々、Javaで作業するとき、より良いリフレクションのサポートを望んでいました。
FrustratedWithFormsDesigner

18
まあ、「不正行為のリフレクション」とそれを有効に使用することを間違えるのはそれほど難しくないと思います。プライベートメンバーを調べたり、プライベートメソッドを呼び出して(インターフェイスを回避するため)使用すると、不正行為になる可能性が非常に高くなります。それを使用して型と対話する場合、それ以外の場合は知識がなくても、おそらく大丈夫です(データバインディング、プロキシなど)。
ファルコン

1
どの言語を使用していますか?Brainfuckにはリフレクションがなく、Pythonはまったく異なります。
ジョブ

1
また、リフレクションを介してプライベートメソッドにアクセスできる理由も理解できませんでした。私の直感によれば、それは禁止されるべきです。
ジョルジオ

3
このような言葉の悪い質問は、編集なしで27の賛成票を得たとは信じられません。
アーロンノート

回答:


48

いいえ、不正行為ではありません。これは、一部のプログラミング言語の問題を解決する方法です。

現在では、最良の(最もクリーンで、シンプルで、保守しやすい)ソリューションではないことがよくあります。より良い方法があれば、実際にそれを使用してください。ただし、そうでない場合もあります。または、存在する場合、それは非常に複雑で、多くのコードの複製などを伴い、実行不可能になります(長期的に維持するのが困難です)。

現在のプロジェクトの2つの例(Java):

  • 一部のテストツールでは、リフレクションを使用してXMLファイルから構成を読み込みます。初期化されるクラスには特定のフィールドがあり、構成ローダーはリフレクションを使用してfieldX、クラス内の適切なフィールドに名前が付けられたXML要素を照合し、後者を初期化します。場合によっては、識別されたプロパティからその場で簡単なGUIダイアログボックスを構築できます。リフレクションがなければ、これは複数のアプリケーションにわたって数百行のコードを必要とします。そのため、リフレクションを使用することで、大したことなく簡単なツールをすばやくまとめ、重要ではない部分(Webアプリのテスト、サーバーログの分析など)に集中できるようになりました。
  • 従来のWebアプリの1つのモジュールは、DBテーブルからExcelシートにデータをエクスポート/インポートすることを目的としていました。重複したコードがたくさんありましたが、もちろん重複はまったく同じではなく、バグの一部も含まれていました。リフレクション、イントロスペクション、注釈を使用して、重複のほとんどを排除し、コードの量を削減しましたコードをより堅牢にし、保守や拡張をより簡単にする一方で、5Kから2.4K未満。リフレクションの賢明な使用のおかげで、そのモジュールは私たちにとって問題ではなくなりました。

一番下の行は、他の強力なツールと同様に、反射も足で自分を撃つために使用することができます。いつ、どのように使用する(しない)かを学習すれば、それ以外の難しい問題に対するエレガントでクリーンなソリューションを提供できます。悪用すると、他の点では単純な問題を複雑でい混乱に変えることができます。


8
+1リフレクションは、変換の理由で中間オブジェクトがある場合、データのインポート/エクスポートに非常に役立ちます。
エドジェームズ

2
また、データ駆動型アプリで非常に便利です-の大量のスタックを使用する代わりに、if (propName = n) setN(propValue);XMLタグにコードプロパティと同じ名前を付け、ループを実行できます。また、このメソッドを使用すると、後でプロパティを簡単に追加できます。
マイケルK

Reflectionは、FluentNHibernateなどのFluentインターフェイスで頻繁に使用されます。
スコットホイットロック

4
@Michael:クラス内のこれらのプロパティのいずれかの名前を変更し、コードが爆発することがわかるまで。プログラムが生成するものとの比較可能性を維持する必要がない場合、それは問題ありませんが、私はそれが私たちのほとんどではないと思います。
ビリーONeal

1
@ビリー、あなたと矛盾するつもりはなかった、あなたが書いたことに同意する。ただし、あなたが持ってくる例は、「パブリックインターフェイスの変更を避ける」という一般ルールのサブケースです。
ペテルトレック

37

不正行為ではありません。しかし、少なくとも以下の理由から、通常は実動コードでは悪い考えです。

  • コンパイル時の型の安全性が失われます -コンパイル時にメソッドが利用可能であることをコンパイラに確認させると役に立ちます。リフレクションを使用している場合、十分なテストを行わないと、実行時にエラーが発生し、エンドユーザーに影響を与える可能性があります。エラーをキャッチしたとしても、デバッグはより困難になります。
  • リファクタリング時にバグが発生します-名前に基づいてメンバーにアクセスしている場合(ハードコードされた文字列を使用する場合など)、ほとんどのコードリファクタリングツールによって変更されず、すぐにバグが発生します。追跡するのは難しい。
  • パフォーマンスは遅くなります -実行時のリフレクションは、静的にコンパイルされたメソッド呼び出し/変数検索よりも遅くなります。たまにリフレクションを行う場合は重要ではありませんが、リフレクションを介して毎秒数千または数百万回呼び出しを行う場合、これがパフォーマンスのボトルネックになる可能性があります。一度すべてのリフレクションを排除することで、Clojureのコードを10倍高速化したことがあるので、これは本当の問題です。

リフレクションの使用を次の場合に限定することをお勧めします。

  • 以下のための迅速なプロトタイピングや、それが最も簡単な解決策がある「使い捨て」のコード
  • 以下のための本物の反射ユースケース、ユーザーが実行時に任意のオブジェクトのフィールド/メソッドを調べることができ、例えばIDEツーリング。

その他の場合はすべて、反射を回避するアプローチを考え出すことをお勧めします。適切なメソッドでインターフェイス定義し、メソッドを呼び出すクラスのセットに実装することで、ほとんどの単純なケースを解決できます。


3
+1-リフレクションにはいくつかのユースケースがあります。しかし、ほとんどの場合、プログラマがそれを使用しているのを見ると、彼らは深刻な欠陥のある設計を紙に書きます。インターフェイスを定義し、リフレクションへの依存を削除しようとします。GOTOと同じように、その用途もありますが、ほとんどは良くありません。(もちろん、それらのいくつかは良いです)
ビリーONeal

3
上記の点は、お気に入りの言語に適用される場合と適用されない場合があります。たとえば、Smalltalkではリフレクションに速度のペナルティはありません。また、計算は完全に遅延限界であるため、コンパイル時の安全性も失われません。
フランクシェラー

Dのリフレクションには、あなたが言及した欠点はありません。したがって、一部の言語での実装を非難自体よりも非難すると主張します。私はsmalltalkについてはあまり知りませんが、@ Frank Sheararによれば、それらにも欠点はありません。
-deadalnix

2
@deadalinx:どの欠点を参照しますか?この答えにはいくつかあります。言語に依存するのは、パフォーマンスの問題だけです。
ビリーONeal

1
2番目の問題(リファクタリング中に発生するバグ)がおそらく最も深刻であることを付け加えます。特に、特定の名前を持つものに依存することは、その名前が変更されると即座に中断します。このように引き起こされたエラーは、あなたが非常に注意しない限り、有益ではありません。
フランクシェラー

11

リフレクションは、最近のほとんどの言語で見られる型ベースのパラメーターと同様に、メタプログラミングの別の形式であり、同様に有効です。リフレクションは強力で汎用的であり、リフレクションプログラムは(もちろん正しく使用すると)保守性が高く、オブジェクト指向プログラムまたは手続き型プログラムよりも優れています。はい、パフォーマンスの価格をお支払いいただきますが、多くの場合、またはほとんどの場合でも、より保守しやすい低速のプログラムを喜んでお勧めします。


2
+1、これは反射とは何かを理解するのに役立ちます。私はC ++プログラマーなので、実際に振り返ったことはありません。これは役立ちます。(ただし、私は告白する私の観点から、リフレクションは、言語の欠乏を和解の試みのように思えることを私たちはC ++男はそのためのテンプレートを使用するので、私はあなたがそれの同じことを言うことができると仮定:)。。。
greyfade

4
パフォーマンスについて-場合によっては、リフレクションAPIを使用して完全にパフォーマンスコードを書き戻すことができます。たとえば、.NETでは、現在のアプリにILを書き込むことができます。したがって、「リフレクション」コードは他のコードと同じくらい高速になります(ただし、すでに多くのif / else / whateverを削除できることを除きます)正確なルールを
見つけた

@greyfade:ある意味、テンプレートはコンパイル時のみのリフレクションを行っています。実行時に実行できることには、再コンパイルせずにアプリケーションで強力な機能を構築できるという利点があります。パフォーマンスは、柔軟性と引き換えに、予想よりも多くの時間(C ++のバックグラウンドが与えられた場合)の許容可能なトレードオフになります。
ドナルドフェロー

あなたの答えがわかりません。リフレクションを使用するプログラムは、リフレクションがパフォーマンスを必要としない場合にオブジェクト指向および手続き型プログラムに取って代わるべき優れたプログラミングモデルであるかのように、リフレクションを使用しないプログラムよりも保守しやすいと言っているようです。
-DavidS

8

確かにそれはあなたが何を達成しようとしているかに依存します。

たとえば、依存性注入を使用して、チェックするメディアの種類(MP3ファイルまたはJPEGファイル)を決定するメディアチェッカーアプリケーションを作成しました。シェルは、各タイプに関連する情報を含むグリッドを表示する必要がありましたが、を表示するのかを知りませんでした。これは、そのタイプのメディアを読み取るアセンブリで定義されます。

したがって、グリッドを正しく設定できるように、表示する列の数とそのタイプと名前を取得するためにリフレクションを使用する必要がありました。また、他のコードや構成ファイルを変更せずに、挿入したライブラリを更新(または新しいライブラリを作成)できることも意味していました。

他の唯一の方法は、チェックするメディアのタイプを切り替えたときに更新する必要がある構成ファイルを保持することでした。これにより、アプリケーションに別の障害点が生じていました。


1
「確かにそれはあなたが何を達成しようとしているかに依存します。」+1
DaveShaw

7

ライブラリ作成者である場合、Reflectionは素晴らしいツールであるため、受信データに影響を与えません。リフレクションとメタプログラミングを組み合わせることで、ライブラリを任意の呼び出し元とシームレスに連携させることができます。コード生成などを行う必要はありません。

ただし、アプリケーションコードのリフレクションを阻止しようとしています。アプリ層では、インターフェイス、抽象化、カプセル化など、さまざまなメタファーを使用する必要があります。


そのようなライブラリは、しばしば使用するのが難しく、避けるのが最善です(例えば、Spring)。
スリダールサルノバト

@Sridharでは、シリアル化APIを使用したことはありませんか?または、ORM / micro-ORMですか?
マークグラヴェル

私は自分の選択なしでそれらを使用しました。何をすべきか言われていなければ、回避できたはずの問題にぶつかりました。
スリダールサルノバト

7

Reflectionは、開発者向けのツールを構築するのに最適です。

ビルド環境でコードを検査し、コードを操作/初期化するための適切なツールを潜在的に生成できるためです。

一般的なプログラミング手法としては有用ですが、ほとんどの人が想像するよりも脆弱です。

リフレクション(IMO)の実際の開発用途の1つは、一般的なストリーミングライブラリの作成を非常に簡単にすることです(クラス記述が変更されない限り(その後、非常に脆弱なソリューションになります))。


実際、春は「ほとんどの人が想像するよりも脆い」。
スリダールサルノバト

5

リフレクションの使用は、よく意識して使用しないと、オブジェクト指向言語ではしばしば有害です。

StackExchangeサイトで見た悪い質問の数を失いました。

  1. 使用中の言語はオブジェクト指向です
  2. 作成者は、どのタイプのオブジェクトが渡されたかを調べ、そのメソッドの1つを呼び出したい
  3. 著者は、反省が間違った手法であることを受け入れることを拒否し、これを「私を助ける方法がわからない」と指摘する人を非難します

以下 典型的な例を示します。

オブジェクト指向のポイントのほとんどは

  1. あなたは物自体で構造/概念を操作する方法を知っている関数をグループ化します。
  2. コードのどの時点でも、オブジェクトのタイプについて十分に知って、関連する関数を把握し、それらの関数の特定のバージョンを呼び出すように依頼する必要があります。
  3. すべての関数に正しい入力タイプを指定し、各関数に関連する情報以上の情報(およびそれ以上の情報)を持たせないようにすることで、前のポイントの真実を確認します。

コードのいずれかの時点で、渡されたオブジェクトに対してポイント2が有効でない場合、これらの1つ以上が真です

  1. 間違った入力が入力されています
  2. コードの構造が不十分です
  3. あなたは一体何をしているのか分かりません。

スキルの低い開発者はこれを取得せず、コードのどの部分でも何でも渡すことができ、(ハードコーディングされた)可能性のセットから必要なことを実行できると信じています。これらのバカは反射をよく使います。

OO言語の場合、リフレクションはメタアクティビティ(クラスローダー、依存性注入など)でのみ必要です。これらのコンテキストでは、適切で正当な理由で何も知らないコードの操作/構成を支援する汎用サービスを提供しているため、リフレクションが必要です。他のほとんどの状況では、リフレクションに手を伸ばす場合、何か間違ったことをしているので、渡されたオブジェクトについてこのコードが十分に知らない理由を自問する必要があります。


3

代替として、リフレクションされたクラスのドメインが明確に定義されている場合、実行時にリフレクションを使用する代わりに、リフレクションと他のメタデータを使用してコードを生成することです。FreeMarker / FMPPを使用してこれを行います。から選択する他のツールがたくさんあります。これの利点は、簡単にデバッグできる「実際の」コードなどになることです。

状況に応じて、これによりコードを大幅に高速化することができます-または単にコードを大量に膨張させることができます。リフレクションの欠点を回避します。

  • コンパイル時の型安全性の喪失
  • リファクタリングによるバグ
  • 遅いパフォーマンス

先に述べた。

リフレクションが不正行為のように感じる場合、それはあなたがよくわからない多くの当て推量に基づいているためであり、あなたの腸はこれが危険であることを警告しています。リフレクションに固有のメタデータを独自のメタデータで強化する方法を必ず提供してください。ここでは、発生する可能性のある実際のクラスのすべての癖と特殊なケースを説明できます。


2

不正行為ではありませんが、他のツールと同様に、解決するために使用する必要があります。定義により、リフレクションを使用すると、コードを介してコードを検査および変更できます。それがあなたがする必要があるものであるなら、反射は仕事のためのツールです。リフレクションはメタコードに関するものです。コードをターゲットとするコード(データをターゲットとする通常のコードとは対照的)。

適切なリフレクションの使用例は、汎用Webサービスインターフェイスクラスです。典型的な設計は、プロトコル実装をペイロード機能から分離することです。そのTため、ペイロードを実装する1つのクラス(呼び出しましょう)と、プロトコルを実装する別のクラス()がありますPTかなり簡単です。あなたがしたいすべての呼び出しについて、それがすることになっていることを何でもする1つのメソッドを単に書いてください。Pただし、Webサービス呼び出しをメソッド呼び出しにマップする必要があります。冗長性を回避し、P再利用性を高めるため、このマッピングを汎用にすることが望ましいです。Reflectionは、T実行時にクラスを検査し、クラスのPコンパイル時の知識なしに、Webサービスプロトコルを介して渡された文字列に基づいてメソッドを呼び出す手段を提供します。T。「コードに関するコード」ルールを使用すると、クラスのデータの一部としてPクラス内にコードがあると主張できTます。

しかしながら。

Reflectionでは、言語の型システムの制限を回避するためのツールも提供されます。理論的には、すべてのパラメータをtypeとして渡しobject、リフレクションを通じてメソッドを呼び出すことができます。強力な静的型付け規則を実施することになっている言語であるVoilàは、構文がはるかに複雑であることだけが、遅延バインディングを備えた動的型付け言語のように動作します。私がこれまでに見たそのようなパターンのすべてのインスタンスは汚いハックであり、常に言語の型システム内の解決策が可能であり、あらゆる点でより安全で、よりエレガントで、より効率的でした。 。

いくつかの例外があります。たとえば、さまざまな非関連タイプのデータソースにデータバインドできるGUIコントロールなどです。データをバインドできるようにデータに特定のインターフェイスを実装するように命じることは現実的ではなく、プログラマーに各タイプのデータソース用のアダプターを実装させることもありません。この場合、リフレクションを使用してデータソースの種類を検出し、データバインディングを調整する方が便利な選択です。


2

Reflectionで発生した問題の1つは、難読化をミックスに追加したことです。すべてのクラスは新しい名前を取得し、クラスまたは関数をその名前で突然ロードすると動作しなくなります。


1

それは完全に依存します。リフレクションなしで行うのが難しいことの例は、ObjectListViewを複製することです。また、その場でILコードを生成します。


1

リフレクションは、コンベンションベースのシステムを作成する主な方法です。ほとんどのMVCフレームワーク内で使用されていることに驚かないでしょう。これはORMの主要なコンポーネントです。おそらく、あなたはすでにそれで構築されたコンポーネントを毎日使用しています。

このような使用の代替手段は構成であり、独自の欠点があります。


0

リフレクションは、他の方法では実際に実行できないことを実現できます。

たとえば、このコードを最適化する方法を検討してください。

int PoorHash(char Operator, int seed, IEnumerable<int> values) {
    foreach (var v in values) {
        seed += 1;
        switch (char) {
            case '+': seed += v; break;
            case '^': seed ^= v; break;
            case '-': seed -= v; break;
            ...
        }
        seed *= 3;
    }
    return seed;
}

内側のループの真ん中に高価なテストスマックがありますが、それを抽出するには、各演算子に対してループを1回書き換える必要があります。Reflectionを使用すると、ループを数十回繰り返すことなく、テストを抽出するのと同等のパフォーマンスを得ることができます(したがって、保守性が犠牲になります)。必要なループをその場で生成してコンパイルするだけです。

私は実際にこの最適化を行いましたが、状況はもう少し複雑で、結果は驚くべきものでした。パフォーマンスが大幅に向上し、コードの行数が少なくなりました。

(注:最初はcharではなくFuncを渡すのと同等の方法を試しましたが、わずかに改善されましたが、10倍の反射はほとんど達成されませんでした。)


1
より良い最適化は、代わりにファンクターを渡すことです。JITコンパイラーによって実行時にインライン化される可能性が高く、リフレクションよりも保守性が高くなります。
ビリーONeal

それはより保守性が高く、実際に最初に試してみましたが、十分な速さではありませんでした。おそらく私の場合、実行する操作に対して2番目の内部ループがあったため、JITは間違いなくインライン化していませんでした。
クレイグギドニー

また、あなたが持っているのは本当に自己修正コードであり、それは実際には反映されていないことを指摘します。一つは、サポート反射を行うほとんどの言語でリフレクションAPIを介してアクセスしますが、それは反射(例えば、それはC ++で可能になります)をサポートしていない言語で同じように簡単に行うことができます
ビリーONeal

標準的な「構造的平等」の例を避けたかった。自己修正コードにはリフレクションは必要ありませんが、確かに役立ちます。
クレイグギドニー

あんまり。あなたはそれを無反射言語でうまく行うことができます。
ビリーONeal

-2

不正行為ではありません...むしろ、アプリケーションサーバーがユーザーが選択した名前で作成したクラスを実行できるようにし、不正行為ではなく柔軟性をユーザーに提供します。

また、Javaの.classファイルのコードを表示したい場合は、いくつかの逆コンパイラが無料で利用できます!


逆コンパイルはリフレクション機能ではありません。
ビリーONeal

はい、そうではありません...しかし、リフレクション(Javaの任意のクラスの詳細を提供する)を使用している間に不正行為をしたい場合は、間違っていると言いたいと思います。クラスは...それよりも、あなたに関わることはすべての逆コンパイラで簡単に行うことができますされているものである
ANSHUL JAIN

2
反射は、場合によっては便利な概念です、はい。しかし、それが使用されているのを見る時間の99.9%は、設計エラーをハックするために使用されていることを示しています。
ビリーONeal
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.