Чи є проблеми з використанням Reflection?


49

Я не знаю чому, але я завжди відчуваю, що мене "обманюють", коли використовую рефлексію - можливо, саме через хіт вистави я знаю, що беру.

Частина мене каже, якщо це частина мови, якою ви користуєтесь, і вона може виконати те, що ви намагаєтеся зробити, то чому б не використовувати її. Інша частина мене каже, що повинен бути спосіб я це зробити, не використовуючи роздумів. Гадаю, може, це залежить від ситуації.

На які потенційні проблеми я повинен звернути увагу, коли використовую рефлексію, і наскільки я повинен їх хвилювати? Скільки зусиль варто витратити, щоб спробувати знайти більш звичайне рішення?


2
Я думаю, це залежить від мови та оточення. Деякі її підтримують, навіть заохочують. Іноді під час роботи на Java я хотів би отримати кращу підтримку роздумів.
FrustratedWithFormsDesigner

18
Ну, напевно, не так складно розібратися між "обдурванням обману" та правильним його використанням: Коли ви оглядаєте приватних членів або використовуєте їх для виклику приватних методів (для роботи над інтерфейсами), то це дуже ймовірно обман. Коли ви використовуєте його для взаємодії з типами, про які ви інакше не знаєте, то, ймовірно, це нормально (прив'язка даних, проксі-сервери тощо).
Сокіл

1
Якою мовою ви користуєтесь? Brainfuck не має рефлексії, а Python просто інший.
Робота

1
Я також ніколи не розумів, чому дозволено отримувати доступ до приватних методів через рефлексію. За моєю інтуїцією, це слід забороняти.
Джорджіо

3
Важко повірити, що таке неякісне запитання отримало 27 оновлень без редагування.
Aaronaught

Відповіді:


48

Ні, це не обман - це спосіб вирішити проблеми в деяких мовах програмування.

Зараз це найчастіше не найкраще (найчистіше, найпростіше, найпростіше в обслуговуванні) рішення. Якщо є кращий спосіб, використовуйте його справді. Однак іноді цього немає. Або, якщо є, це просто набагато складніше, що включає багато дублювання коду тощо, що робить його нездійсненним (важко підтримувати в довгостроковій перспективі).

Два приклади нашого поточного проекту (Java):

  • деякі наші інструменти тестування використовують відображення для завантаження конфігурації з XML-файлів. Клас, який потрібно ініціалізувати, має специфічні поля, і завантажувач конфігурацій використовує відображення для узгодження XML-елемента, названого fieldXз відповідним полем у класі, та для ініціалізації останнього. У деяких випадках він може створити просте діалогове вікно графічного інтерфейсу з виявлених властивостей на ходу. Без роздумів це займе сотні рядків коду в декількох додатках. Тому роздуми допомогли нам швидко, без особливої ​​суєти зібрати простий інструмент, і дали нам можливість зосередитись на важливій частині (тестування регресії нашого веб-додатка, аналізі журналів сервера тощо), а не на неактуальному.
  • один модуль нашої застарілої веб-програми мав на меті експортувати / імпортувати дані з таблиць БД на листи Excel та назад. Він містив багато дублюваного коду, де, звичайно, дублікації були не зовсім однаковими, деякі з них містили помилки і т. Д. Використовуючи роздуми, самоаналіз та анотації, мені вдалося усунути більшість дублювання, скорочуючи кількість коду з понад 5K нижче 2.4K, роблячи код більш надійним і простішим в обслуговуванні або розширенні. Тепер цей модуль перестав бути для нас проблемою - завдяки розумному використанню рефлексії.

Суть, як і будь-який потужний інструмент, відображення теж може використовуватися для того, щоб стріляти в ногу. Якщо ви дізнаєтесь, коли і як (не) ним користуватися, це може принести вам елегантні та чисті рішення інакших складних проблем. Якщо ви зловживаєте цим, ви можете перетворити інакше просту проблему в складний і потворний безлад.


8
+1 Роздум неймовірно корисний для імпорту / експорту даних, коли у вас є проміжні об'єкти з причин конверсії.
Ед Джеймс

2
Це також неймовірно корисно в керованих даними додатках - замість того, щоб використовувати масивний стек if (propName = n) setN(propValue);, ви можете називати свої (тобто) теги XML такими ж, як і ваші властивості коду, і виконувати цикл над ними. Цей метод також робить набагато простіше додавати властивості пізніше.
Майкл К

Відображення використовується широко в інтерфейсах Fluent, як FluentNHibernate.
Скотт Вітлок

4
@Michael: Поки ви не вирішите перейменувати одну з цих властивостей у класі та не виявите, що ваш код вибухає. Якщо вам не потрібно підтримувати порівнянність із речами, які створює ваша програма, це добре, але я підозрюю, що це не більшість із нас.
Біллі ONeal

1
@Billy, я не мав на увазі суперечити тобі, я згоден з тим, що ти написав. Хоча, приклад, який ви наводите, - це ІМХО більше підзаголовок загального правила "уникайте змін публічних інтерфейсів".
Péter Török

37

Це не обман. Але, як правило, погана ідея у виробничому коді хоча б з наступних причин:

  • Ви втрачаєте безпеку типу компіляційного часу - корисно, щоб компілятор переконався, що метод доступний під час компіляції. Якщо ви використовуєте рефлексію, ви отримаєте помилку під час виконання, яка може вплинути на кінцевих користувачів, якщо ви не перевірите достатньо добре. Навіть якщо ви вловите помилку, буде важче налагодити.
  • Це викликає помилки під час рефакторингу - якщо ви звертаєтесь до члена на основі його імені (наприклад, використовуючи жорстко закодовану рядок), це не зміниться більшістю інструментів рефакторингу коду, і миттєво виникне помилка, яка може бути цілком важко відстежити.
  • Продуктивність повільніша - відображення під час виконання буде проходити повільніше, ніж статично складені виклики методів / змінних пошуку. Якщо ви робите роздуми лише зрідка, то це не має значення, але це може стати вузьким місцем у тих випадках, коли ви телефонуєте через роздуми тисячі або мільйони разів в секунду. Одного разу я отримав 10-кратну швидкість у якомусь коді Clojure, просто усунувши всі рефлексії, так що так, це справжня проблема.

Я б запропонував обмежити використання рефлексії на такі випадки:

  • Для швидкого прототипування або «викидного» коду, коли це найпростіше рішення
  • Для справжніх роздумів використовують випадки , наприклад інструмент IDE, який дозволяє користувачеві досліджувати поля / методи довільного об'єкта під час виконання.

У всіх інших випадках я б запропонував розібратися в підході, який дозволяє уникнути роздумів. Визначення інтерфейсу з відповідними методами та його реалізація на наборі класів, на який потрібно викликати метод (и), як правило, достатньо для вирішення більшості простих випадків.


3
+1 - є кілька випадків використання для роздумів; але більшу частину часу я бачу програмістів, які використовують його, вони розробляють розробки з серйозними недоліками. Визначте інтерфейси та спробуйте зняти залежність від відображення. Як і GOTO, він використовує свої можливості, але більшість з них не є гарними. (Деякі з них хороші, звичайно)
Біллі ONeal

3
Майте на увазі, що зазначені вище пункти можуть або не стосуються вашої улюбленої мови. Наприклад, відображення не має штрафної швидкості в Smalltalk, а також не втрачаєш безпеку часу компіляції, оскільки обчислення цілком запізніли.
Френк Ширар

Відображення в D не має недоліків, які ви згадали. Тож я б заперечував, що ви звинувачуєте впровадження в деяких мовах більше, ніж саме рефелекцію. Я мало знаю про smalltalk, але за словами @Frank Shearar, він також не має недоліків.
deadalnix

2
@deadalinx: До якого мінусу ви звертаєтесь? У цій відповіді кілька. Питання про продуктивність - це єдине, що залежить від мови.
Біллі ONeal

1
Варто додати, що друга проблема - помилки, викликані під час рефакторингу - є, мабуть, найбільш серйозною. Зокрема, покладаючись на те, що певне ім’я порушується миттєво, коли ім’я цієї речі змінюється. Викликані таким чином помилки можуть бути менш інформативними, якщо ви не дуже обережні.
Френк Ширар

11

Відображення - це лише інша форма метапрограмування і настільки ж справедлива, як параметри на основі типу, які ви бачите на більшості мов у ці дні. Рефлексія є потужною і загальною, а рефлексивні програми - це високий порядок ремонту (звичайно, якщо їх правильно використовувати, звичайно) і більше, ніж суто об'єктно-орієнтовані або процедурні програми. Так, ви платите ціну продуктивності, але я б із задоволенням пішов на більш повільну програму, яка є більш досяжною у багатьох, а то й у більшості випадків.


2
+1, це допомагає мені зрозуміти , що відображення є . Я програміст на C ++, і тому я ніколи насправді не захоплювався роздумом. Це допомагає. (Хоча я зізнаюся, що з моєї точки зору, Роздуми здаються спробою усунути дефіцит мови. Ми, хлопці C ++, використовуємо для цього шаблони. Тож я думаю, ви могли сказати те саме. :)
greyfade

4
Повторна ефективність - у деяких випадках ви можете використовувати API відображення, щоб повністю записати код продуктивності. Наприклад,. з'ясував точні правила)
Marc Gravell

@greyfade: У певному сенсі шаблони відображають лише компіляцію за часом. Можливість зробити це під час виконання має перевагу в тому, що дозволяє створювати потужні можливості в додатку без необхідності перекомпілювати. Ви торгуєте ефективністю за гнучкість, прийнятну угоду більше часу, ніж ви могли очікувати (з огляду на ваш C ++ фон).
Стипендіати Дональ

Я не розумію вашої відповіді. Ви, здається, говорите, що програми, які використовують відображення, є більш доступними, ніж ті, які не роблять, як ніби відображення - це вища модель програмування, яка повинна витіснити об'єктно-орієнтовані та процедурні програми, якщо продуктивність не потрібна.
DavidS

8

Звичайно, все залежить від того, чого ви намагаєтесь досягти.

Наприклад, я написав програму для перевірки медіа, яка використовує ін'єкцію залежності, щоб визначити, який тип носія (MP3-файли чи JPEG-файли) перевірити. Оболонці потрібно було відобразити сітку, що містить відповідну інформацію для кожного типу, але вона не мала знання про те, що збирається відображати. Це визначено в зборі, який читає цей тип носія.

Тому мені довелося використовувати відображення, щоб отримати кількість стовпців для відображення та їх типи та назви, щоб я міг правильно встановити сітку. Це також означало, що я можу оновити введену бібліотеку (або створити нову), не змінюючи іншого коду чи файла конфігурації.

Єдиним іншим способом було б мати файл конфігурації, який потрібно було б оновити, коли я перемикав тип перевіреного носія. Це ввело б ще один момент відмови програми.


1
"Безумовно, все залежить від того, чого ви намагаєтеся досягти". +1
DaveShaw

7

Відображення - це дивовижний інструмент, якщо ви автор бібліотеки , і таким чином не маєте впливу на вхідні дані. Поєднання відображення та метапрограмування може дозволити вашій бібліотеці безперебійно працювати з довільними абонентами, без того, щоб вони перескакували через обручі генерації коду тощо.

Я все ж намагаюся відмовитись від відображення в коді програми ; на рівні програми ви повинні використовувати різні метафори - інтерфейси, абстракцію, інкапсуляцію тощо.


Такі бібліотеки часто важко використовувати і їх краще уникати (наприклад, Spring).
Шрідхар Сарнобат

@Sridhar, щоб ви ніколи не використовували жодного API серіалізації? Або будь-який ORM / мікро-ORM?
Марк Гравелл

Я використовував їх, не маючи власного вибору. Це деморалізувало натрапляння на проблеми, яких я міг би уникнути, якби мені не сказали, що робити.
Шрідхар Сарнобат

7

Роздум фантастичний для побудови інструментів для розробників.

Оскільки це дозволяє вашому середовищу збирання перевіряти код і потенційно генерувати правильні інструменти для маніпулювання / init перевіряти код.

Як загальна методика програмування, вона може бути корисною, але є більш крихкою, ніж уявляє більшість людей.

Одне з дійсних застосувань для роздумів (IMO) полягає в тому, що це робить написання загальної бібліотеки потокової передачі дуже простим (доки опис вашого класу ніколи не змінюється (тоді він стає дуже крихким рішенням)).


Справді, Весна «крихкіша, ніж більшість людей уявляє».
Шрідхар Сарнобат

5

Використання рефлексії часто шкідливе для мов ОО, якщо не використовується з великою обізнаністю.

Я втратив підрахунок, скільки поганих питань я бачив на сайтах StackExchange, де

  1. Мова, якою користується, є OO
  2. Автор хоче з'ясувати, який тип об'єкта передано, а потім викликати один із його методів
  3. Автор відмовляється визнати, що рефлексія - це неправильна техніка і звинувачує тих, хто вказує на це як "не знаючи, як мені допомогти"

Ось є типовими прикладами.

Більшість пунктів ОО полягає в тому

  1. Ви групуєте функції, які вміють маніпулювати структурою / концепцією із самою річчю.
  2. У будь-якій точці коду ви повинні достатньо знати про тип об'єкта, щоб знати, які відповідні функції у нього є, і попросити його зателефонувати в його конкретну версію цих функцій.
  3. Ви гарантуєте істинність попереднього пункту, вказуючи правильний тип введення для кожної функції, і кожна функція не знає більше (і не більше), ніж це стосується.

Якщо в будь-якій точці вашого коду точка 2 не є дійсною для переданого вами об'єкта, то одна чи декілька з них є істинними

  1. Неправильний введення подається в
  2. Ваш код погано структурований
  3. Ви не маєте уявлення про те, що чорт ви робите.

Погано кваліфіковані розробники просто не отримують цього і вірять, що їм можна передати все, що завгодно, в будь-якій частині свого коду і робити все, що вони хочуть, із (жорстко закодованого) набору можливостей. Ці ідіоти використовують відображення багато .

Для мов OO роздуми потрібно лише у мета-активності (навантажувачі класів, введення залежності тощо). У цих контекстах роздуми потрібні, оскільки ви надаєте загальну послугу для сприяння маніпулюванню / конфігурації коду, про який ви нічого не знаєте з доброї та законної причини. Майже в будь-якій іншій ситуації, якщо ви звертаєтесь до роздумів, ви робите щось не так, і вам потрібно запитати себе, чому цей фрагмент коду недостатньо знає про переданий йому предмет.


3

Альтернативою, у випадках, коли область відображених класів є чітко визначеною, є використання відображення разом з іншими метаданими для генерації коду , а не використання відображення під час виконання. Я роблю це за допомогою FreeMarker / FMPP; є безліч інших інструментів на вибір. Перевага цього полягає в тому, що ви закінчуєте "справжній" код, який можна легко налагодити тощо.

Залежно від ситуації, це може допомогти вам зробити набагато швидший код - або просто багато роздуття коду. Це дозволяє уникнути недоліків відображення:

  • втрата безпеки типу компіляції
  • помилки через рефакторинг
  • повільніша продуктивність

згаданий раніше.

Якщо рефлексія відчуває себе як обман, це може бути тому, що ви базуєте це на багатьох здогадах, в яких ви не впевнені, а ваша кишка попереджає вас, що це ризикує. Не забудьте запропонувати спосіб покращити метадані, притаманні рефлексії, за допомогою власних метаданих, де ви можете описати всі примхи та особливі випадки реальних класів, з якими ви можете зіткнутися.


2

Це не обман, але як і будь-який інструмент, його слід використовувати для того, що він вирішує вирішити. Відображення за визначенням дозволяє перевіряти та змінювати код за допомогою коду; якщо це те, що вам потрібно зробити, то рефлексія є інструментом для роботи. Відображення стосується мета-коду: Код, який націлює код (на відміну від звичайного коду, який націлює дані).

Прикладом хорошого використання роздумів є загальні класи інтерфейсу веб-сервісу. Типовою конструкцією є відокремлення реалізації протоколу від функціональності корисного навантаження. Тож у вас є один клас (назвемо його T), який реалізує вашу корисну навантаження, і інший, який реалізує протокол ( P). Tдосить просто: для кожного дзвінка, який ви хочете здійснити, просто напишіть один метод, який робить все, що він повинен робити. Pоднак, потрібно зіставити дзвінки веб-служб на виклики методів. Зробити це відображення загальним є бажаним, оскільки воно дозволяє уникнути надмірності та робить Pбагаторазовим використання. Reflection надає засоби для перевірки класу Tпід час виконання та виклику його методів на основі рядків, переданих Pчерез протокол веб-сервісу, без будь-якого знання класу в час компіляції.T. Використовуючи правило «код про код», можна стверджувати, що клас Pмістить код у класі Tяк частину своїх даних.

Однак.

Відображення також дає вам інструменти для подолання обмежень системи типів мови - теоретично ви можете передати всі параметри як тип objectі викликати їх методи через відображення. Voilà, мова, яка повинна застосовувати сильну дисципліну статичного набору тексту, тепер веде себе як динамічно набрана мова з пізнім зв'язуванням, лише що синтаксис є набагато більш досконалим. Кожен окремий екземпляр такого шаблону, який я бачив до цього часу, був брудним злом, і незмінно рішення в системі типу мови було б можливим, і воно було б безпечнішим, елегантнішим та ефективнішим у всіх відношеннях .

Існує кілька винятків, таких як елементи управління графічним інтерфейсом, які можуть бути пов'язані з різними непов'язаними типами джерел даних; мандат до того, що ваші дані реалізують певний інтерфейс лише для того, щоб ви могли прив'язувати дані, це не реально, і програміст не повинен реалізовувати адаптер для кожного типу джерел даних. У цьому випадку використання відображення для виявлення типу джерела даних та коригування прив'язки даних є більш корисним вибором.


2

Одна з проблем, які ми мали з Reflection, - це коли ми додали Obfuscation у суміш. Усі класи отримують нові імена і раптом завантаження класу чи функції за його назвою перестає працювати.



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;
}

В середині внутрішнього циклу є дорогий тестовий присмак, але для його вилучення потрібно переписати цикл один раз для кожного оператора. Рефлексія дозволяє нам отримати продуктивність нарівні з вилученням цього тесту, не повторюючи цикл десяток разів (і тим самим принести в жертву ремонтопридатність). Просто генеруйте та компілюйте потрібну вам петлю на льоту.

Я фактично зробив цю оптимізацію , хоча ситуація була трохи складнішою, і результати були дивовижними. Порядок покращення продуктивності та менша кількість рядків коду.

(Примітка. Спочатку я спробував еквівалент проходження у Func замість знака, і це було трохи краще, але не майже 10-кратне відображення.)


1
Кращою оптимізацією є передача функтора замість цього. Ймовірно, буде підкреслено під час виконання компілятором JIT і є більш досяжним, ніж відображенням.
Біллі ONeal

Це було б більш ретельно, і я насправді спробував це спершу, але це було недостатньо швидко. JIT, безумовно, не вкладав його, ймовірно, тому, що в моєму випадку був другий внутрішній цикл за виконаними операціями.
Крейг Гідні

Крім того, я зазначу, що те, що у вас є, є насправді самовиправляючим кодом, який насправді не є відображенням. Доступ до нього здійснюється через API відображення на більшості мов, які підтримують рефлексію, але це можна зробити так само просто на мовах, що не підтримують рефлексію (наприклад, це було б можливо на C ++)
Біллі ONeal

Я хотів уникнути стандартного прикладу «структурної рівності». Рефлексія не потрібна для самовиправлення коду, але, безумовно, допомагає.
Крейг Гідні

Не зовсім. Ви можете це зробити на мовах, що не відображають, просто чудово.
Біллі ONeal

-2

Ні в якому разі це не обман ... Швидше, це дає можливість серверам прикладних програм керувати класами, створеними користувачем, з обраним ім'ям, таким чином надаючи користувачеві гнучкість, а не обманюючи їх.

А якщо ви хочете побачити код будь-якого файлу .class (на Java), то є кілька декомпіляторів, доступних безкоштовно!


Декомпіляція не є відображенням.
Біллі ONeal

так, це не так ... Але я мав на увазі сказати, що якщо ти відчуваєш, як обман під час використання рефлексії (які надають тобі відомості про будь-який клас у Java), то ти не є помилкою bcoz-рефлексія - це дуже корисна концепція, і якщо ти отримуєш детальну інформацію про будь-яку клас - це те, що стосується вас, ніж це легко зробити з будь-яким декомпілятором ...
ANSHUL JAIN

2
Рефлексія - корисна концепція в деяких випадках, так. Але 99,9% часу, коли я бачу, що його використовують, я бачу, що він звик ламати навколо помилки дизайну.
Біллі ONeal
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.