web-dev-qa-db-ja.com

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

理由はわかりませんが、リフレクションを使用すると、常に「だまされている」ように感じます。おそらく、私が知っているパフォーマンスヒットが原因です。

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

リフレクションを使用する際に注意する必要のある潜在的な問題は何ですか?それらについてどの程度心配する必要がありますか?従来の解決策を見つけるために費やす価値はどれくらいありますか?

51
P B

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

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

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

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

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

48
Péter Török

それは不正行為ではありません。ただし、少なくとも次の理由により、通常は量産コードでは不適切です。

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

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

  • quick prototypingまたは最も使いやすいソリューションである「使い捨て」コードの場合
  • 本物のリフレクションの使用例の場合、たとえばIDEユーザーが実行時に任意のオブジェクトのフィールド/メソッドを調べることができるツール。

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

38
mikera

リフレクションはメタプログラミングのもう1つの形式であり、最近ほとんどの言語で見られる型ベースのパラメーターと同じくらい有効です。リフレクションは強力で汎用的であり、リフレクションプログラムは(もちろん正しく使用した場合)保守性が高く、純粋にオブジェクト指向または手続き型のプログラムよりも優れています。はい、パフォーマンスの代償を払うことになりますが、多くの場合、またはほとんどの場合でも、より保守しやすい遅いプログラムを喜んで採用します。

11
DeadMG

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

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

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

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

8
ChrisF

リフレクションは、開発者向けのツールを作成するのに最適です。

ビルド環境でコードを検査し、コードを操作/初期化するための適切なツールを生成できる可能性があるためです。

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

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

7
Martin York

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

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

7
Marc Gravell

リフレクションの使用は、OO言語で十分な認識をもって使用しないと、多くの場合有害です。

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

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

ここある 典型的な例。

OOのポイントのほとんどはそれです

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

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

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

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

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

5
itsbruce

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

状況によっては、これによりコードを大幅に高速化することができます。リフレクションの欠点を回避します。

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

先に述べた。

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

3
Ed Staub

Reflectionで発生した問題の1つは、ミックスにObfuscationを追加したときです。すべてのクラスに新しい名前が付けられ、その名前でクラスまたは関数を突然ロードすると機能しなくなります。

2
Carra

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

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

しかしながら。

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

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

2
tdammers

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

1
Tangurena

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

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

1
Chris McCall

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

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

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回書き換える必要があります。リフレクションを使用すると、ループを数十回繰り返すことなく(その結果、保守性を犠牲にすることなく)、そのテストの抽出と同等のパフォーマンスを得ることができます。必要なループをその場で生成してコンパイルするだけです。

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

(注:最初に、charではなくFuncを渡すのと同じことを試しましたが、わずかに優れていましたが、10倍近くの反射は達成されませんでした。)

0
Craig Gidney