Есть ли проблемы с использованием Reflection?


49

Я не знаю почему, но я всегда чувствую, что я "обманываю", когда использую рефлексию - возможно, это из-за удара по производительности, который я знаю, который я беру.

Часть меня говорит, что если это часть языка, который вы используете, и он может выполнить то, что вы пытаетесь сделать, то почему бы не использовать его. Другая часть меня говорит, что должен быть способ, которым я могу сделать это, не используя рефлексию. Я думаю, может быть, это зависит от ситуации.

На какие потенциальные проблемы я должен обратить внимание при использовании рефлексии, и насколько я должен быть обеспокоен ими? Сколько усилий стоит потратить, чтобы попытаться найти более традиционное решение?


2
Я думаю, это зависит от языка и окружающей среды. Некоторые поддерживают это, даже поощряют это. Иногда, работая в Java, я хотел улучшить поддержку рефлексии.
FrustratedWithFormsDesigner

18
Ну, я полагаю, не так сложно различить «мошенническое отражение» и его правильное использование: когда вы проверяете приватные члены или используете его для вызова приватных методов (для обхода интерфейсов), тогда это, скорее всего, обман. Когда вы используете его для взаимодействия с типами, о которых иначе не знаете, это, вероятно, нормально (привязка данных, прокси и т. Д.).
Сокол

1
Какой язык вы используете? Brainfuck не имеет отражения, а Python просто другой.
Работа

1
Я также никогда не понимал, почему разрешается доступ к закрытым методам через рефлексию. По моей интуиции это должно быть запрещено.
Джорджио

3
Трудно поверить, что такой плохо сформулированный вопрос получил 27 голосов без какого-либо редактирования.
Aaronaught

Ответы:


48

Нет, это не обман - это способ решения проблем в некоторых языках программирования.

Теперь это часто не самое лучшее (самое чистое, самое простое, самое простое в обслуживании) решение. Если есть лучший способ, используйте его действительно. Однако иногда нет. Или, если есть, это просто намного сложнее, включает много дублирования кода и т. Д., Что делает его невозможным (трудно поддерживать в долгосрочной перспективе).

Два примера из нашего текущего проекта (Java):

  • некоторые из наших инструментов тестирования используют рефлексию для загрузки конфигурации из файлов XML. Класс, который должен быть инициализирован, имеет определенные поля, и загрузчик конфигурации использует отражение, чтобы сопоставить названный элемент XML fieldXсоответствующему полю в классе и инициализировать последний. В некоторых случаях он может создать простое диалоговое окно с графическим интерфейсом на основе идентифицированных свойств на лету. Без размышлений это заняло бы сотни строк кода в нескольких приложениях. Поэтому рефлексия помогла нам быстро и без особых усилий собрать простой инструмент и позволила нам сосредоточиться на важной части (регрессионное тестирование нашего веб-приложения, анализ журналов сервера и т. Д.), А не на несущественном.
  • Один модуль нашего старого веб-приложения предназначался для экспорта / импорта данных из таблиц БД в таблицы Excel и обратно. Он содержал много дублированного кода, где, конечно, дублирования были не совсем одинаковыми, некоторые из них содержали ошибки и т. Д. Используя отражения, самоанализ и аннотации, мне удалось устранить большую часть дублирования, сократив объем кода по сравнению с более чем От 5К до 2,4К, что делает код более надежным и простым в обслуживании и расширении. Теперь этот модуль перестал быть для нас проблемой - благодаря разумному использованию рефлексии.

Суть в том, что, как и любой мощный инструмент, можно использовать отражение, чтобы выстрелить себе в ногу. Если вы узнаете, когда и как (не) использовать его, это может принести вам элегантные и чистые решения других сложных проблем. Если вы злоупотребите этим, вы можете превратить простую в остальном проблему в сложный и безобразный беспорядок.


8
+1 Отражение невероятно полезно для импорта / экспорта данных, когда у вас есть промежуточные объекты по причинам конверсии.
Эд Джеймс

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

Отражение интенсивно используется в интерфейсах Fluent, таких как FluentNHibernate.
Скотт Уитлок

4
@Michael: Пока вы не решите переименовать одно из этих свойств в классе и не обнаружите, что ваш код взрывается. Если вам не нужно поддерживать сопоставимость с тем, что генерирует ваша программа, это нормально, но я подозреваю, что это не большинство из нас.
Билли ONEAL

1
@ Билли, я не хотел тебе противоречить, я согласен с тем, что ты написал. Хотя приведенный вами пример является ИМХО более частным случаем общего правила «избегайте изменения общедоступных интерфейсов».
Петер Тёрёк

37

Это не обман. Но это обычно плохая идея в рабочем коде по крайней мере по следующим причинам:

  • Вы теряете безопасность типов во время компиляции - полезно, чтобы компилятор проверял, что метод доступен во время компиляции. Если вы используете рефлексию, вы получите ошибку во время выполнения, которая может повлиять на конечных пользователей, если вы не будете достаточно хорошо тестировать. Даже если вы поймаете ошибку, отладку будет сложнее.
  • Это вызывает ошибки при рефакторинге - если вы обращаетесь к члену на основе его имени (например, используя жестко запрограммированную строку), то это не изменится большинством инструментов рефакторинга кода, и вы сразу же получите ошибку, которая может быть довольно трудно выследить.
  • Производительность медленнее - отражение во время выполнения будет медленнее, чем статически скомпилированные вызовы методов / поиск переменных. Если вы делаете рефлексию лишь изредка, это не имеет значения, но это может стать узким местом в производительности, когда вы делаете вызовы через рефлексию тысячи или миллионы раз в секунду. Однажды я получил 10-кратное ускорение в некотором коде Clojure, просто устранив все отражения, так что да, это реальная проблема.

Я бы предложил ограничить использование рефлексии следующими случаями:

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

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


3
+1 - есть несколько вариантов использования для размышления; но большую часть времени я вижу, как программисты используют это, они пишут над проектами с серьезными недостатками. Определите интерфейсы и попытайтесь удалить зависимость от отражения. Точно так же как GOTO, у этого есть его использование, но большинство из них не хорошо. (Некоторые из них хороши, конечно)
Билли ONEAL

3
Помните, что приведенные выше пункты могут относиться или не относиться к вашему любимому языку. Например, в Smalltalk рефлексия не снижает скорость, и при этом вы не теряете безопасность во время компиляции, потому что вычисления полностью связаны с запозданием.
Фрэнк Ширар

Отражение в D не имеет недостатка, о котором вы упомянули. Поэтому я бы сказал, что вы обвиняете реализацию в некоторых языках больше, чем в самой референции. Я мало что знаю о smalltalk, но, по словам @Frank Shearar, у него тоже нет этих недостатков.
Deadalnix

2
@deadalinx: К какому недостатку вы относитесь? Есть несколько в этом ответе. Проблема производительности - единственное, что зависит от языка.
Билли Онил

1
Я должен добавить, что вторая проблема - ошибки, возникающие при рефакторинге, - вероятно, самая серьезная. В частности, если полагаться на что-то, имеющее определенное имя, оно мгновенно ломается, когда имя этой вещи меняется. Причиненные ошибки могут быть менее информативными, если вы не будете очень осторожны.
Фрэнк Шиарар

11

Отражение - это еще одна форма метапрограммирования, и такая же действительная, как и параметры на основе типов, которые вы видите на большинстве языков в наши дни. Отражение является мощным и универсальным, а рефлексивные программы имеют высокий уровень ремонтопригодности (при правильном использовании, конечно) и в большей степени, чем чисто объектно-ориентированные или процедурные программы. Да, вы платите за производительность, но я бы с радостью пошел на более медленную программу, которая во многих или даже в большинстве случаев более удобна в обслуживании.


2
+1, это помогает мне понять , что отражение есть . Я программист на C ++, поэтому никогда не задумывался над этим. Это помогает. (Хотя, признаюсь, с моей точки зрения, Reflection выглядит как попытка примирить недостатки в языке. Мы, ребята из C ++, используем для этого шаблоны. Поэтому я полагаю, вы могли бы сказать то же самое. :)
greyfade

4
Реализация производительности - в некоторых случаях вы можете использовать API отражения для полной записи кода производительности. Например, в .NET вы можете записать IL в текущее приложение - следовательно, ваш код "отражения" может быть таким же быстрым, как и любой другой код (за исключением того, что вы можете удалить большую часть if / else / what, как вы уже выяснили точные правила)
Марк Грэвелл

@greyfade: в некотором смысле шаблоны делают отражение только во время компиляции. Возможность делать это во время выполнения имеет то преимущество, что позволяет создавать мощные возможности в приложении без перекомпиляции. Вы торгуете производительностью ради гибкости, приемлемого компромисса чаще, чем вы ожидаете (учитывая ваш опыт в C ++).
Donal Fellows

Я не понимаю ваш ответ. Похоже, вы говорите, что программы, использующие рефлексию, более удобны в обслуживании, чем те, которые этого не делают, как будто рефлексия является превосходной моделью программирования, которая должна вытеснять объектно-ориентированные и процедурные программы, если производительность не требуется.
DavidS

8

Конечно, все зависит от того, чего вы пытаетесь достичь.

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

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

Единственным другим способом было бы иметь файл конфигурации, который нужно было бы обновлять, когда я переключал тип проверяемого носителя. Это привело бы к еще одной точке отказа для приложения.


1
«Конечно, все зависит от того, чего ты пытаешься достичь». +1
DaveShaw

7

Reflection - отличный инструмент, если вы автор библиотеки и, следовательно, не имеете никакого влияния на входящие данные. Комбинация рефлексии и метапрограммирования может позволить вашей библиотеке беспрепятственно работать с произвольными вызывающими абонентами без необходимости проходить через циклы генерации кода и т. Д.

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


Такие библиотеки часто сложны в использовании и их лучше избегать (например, Spring).
Шридхар Сарнобат

@Sridhar, так что вы никогда не использовали API сериализации? Или какой-нибудь ORM / микро-ORM?
Марк Гравелл

Я использовал их без собственного выбора. Это деморализует столкновение с проблемами, которых я мог бы избежать, если бы мне не сказали, что делать.
Шридхар Сарнобат

7

Отражение отлично подходит для создания инструментов для разработчиков.

Поскольку это позволяет вашей среде сборки проверять код и потенциально генерировать правильные инструменты для манипулирования / init, проверяйте код.

Как общая методика программирования, она может быть полезной, но более хрупкой, чем думает большинство людей.

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


Действительно, весна «более хрупкая, чем думает большинство людей».
Шридхар Сарнобат

5

Использование рефлексии часто вредно в ОО-языках, если не используется с большой осведомленностью.

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

  1. Используемый язык - ОО
  2. Автор хочет выяснить, в какой тип объекта был передан объект, а затем вызвать один из его методов.
  3. Автор отказывается признать, что рефлексия является неправильной техникой, и обвиняет любого, кто указывает на это, как «не знающего, как мне помочь»

Вот являются типичными примерами.

Большая часть ОО заключается в том, что

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

Если в какой-либо точке вашего кода точка 2 недопустима для объекта, который вы передали, то один или несколько из них верны

  1. Введен неправильный ввод
  2. Ваш код плохо структурирован
  3. Вы понятия не имеете, что, черт возьми, вы делаете.

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

Для ОО-языков отражение должно быть необходимо только в мета-активности (загрузчики классов, внедрение зависимостей и т. Д.). В этих условиях рефлексия необходима, потому что вы предоставляете общую услугу, которая помогает манипулировать / конфигурировать код, о котором вы ничего не знаете по уважительной и законной причине. Практически в любой другой ситуации, если вы стремитесь к размышлению, вы делаете что-то не так, и вам нужно спросить себя, почему этот фрагмент кода недостаточно знает об объекте, который был ему передан.


3

Альтернатива в тех случаях, когда область отраженных классов четко определена, заключается в использовании отражения вместе с другими метаданными для генерации кода вместо использования отражения во время выполнения. Я делаю это с помощью FreeMarker / FMPP; Есть много других инструментов на выбор. Преимущество этого заключается в том, что вы получаете «настоящий» код, который можно легко отладить и т. Д.

В зависимости от ситуации, это может помочь вам сделать код намного быстрее - или просто увеличить объем кода. Это позволяет избежать недостатков отражения:

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

упомянутый ранее.

Если рефлексия кажется обманом, то это может быть потому, что вы основываете ее на большом количестве догадок, в которых вы не уверены, и ваша интуиция предупреждает вас, что это рискованно. Обязательно предоставьте способ улучшить метаданные, присущие отражению, с помощью собственных метаданных, где вы сможете описать все причуды и особые случаи реальных классов, с которыми вы можете столкнуться.


2

Это не обман, но, как и любой инструмент, его следует использовать для решения своих задач. Отражение по определению позволяет вам проверять и модифицировать код с помощью кода; если это то, что вам нужно сделать, то отражение - это инструмент для работы. Отражение - это все о метакоде: коде, который нацелен на код (в отличие от обычного кода, нацеленного на данные).

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

Тем не мение.

Reflection также предоставляет вам инструменты, позволяющие обойти ограничения системы типов языка - теоретически вы можете передавать все параметры как типы objectи вызывать их методы посредством отражений. Вуаля, язык, который должен обеспечивать строгую дисциплину статической типизации, теперь ведет себя как динамически типизированный язык с поздним связыванием, только синтаксис гораздо более сложный. Каждый отдельный пример такого паттерна, который я видел до сих пор, был грязным взломом, и неизменно было бы возможным решение в системе типов языка, и оно было бы более безопасным, более элегантным и более эффективным во всех отношениях. ,

Существует несколько исключений, таких как элементы управления графическим интерфейсом, которые могут быть привязаны к данным к различным не связанным типам источников данных; требовать, чтобы ваши данные реализовывали определенный интерфейс только для того, чтобы вы могли связывать данные, это нереально, и программист не должен реализовывать адаптер для каждого типа источника данных. В этом случае использование отражения для определения типа источника данных и настройка привязки данных является более полезным выбором.


2

Одна из проблем, с которой мы столкнулись в Reflection, это когда мы добавили в смесь Obfuscation . Все классы получают новые имена, и внезапная загрузка класса или функции по имени перестает работать.


1

Это полностью зависит. Примером чего-то, что было бы трудно сделать без размышления, является репликация ObjectListView . Он также генерирует код IL на лету.


1

Отражение является основным методом создания систем, основанных на соглашениях. Я не удивлюсь, обнаружив, что он интенсивно используется в большинстве сред MVC. Это основной компонент в ОРМ. Скорее всего, вы уже используете компоненты, созданные с ним каждый день.

Альтернативой для такого использования является конфигурация, которая имеет свой собственный набор недостатков.


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 вместо char, и это было немного лучше, но не почти 10-кратное отражение.)


1
Лучшая оптимизация - передать функтор. Скорее всего, будет встроен JIT-компилятором во время выполнения и будет более понятным, чем рефлексия.
Билли Онил

Это было бы более ремонтопригодно, и я сначала попробовал, но это было недостаточно быстро. JIT определенно не включал его, вероятно, потому что в моем случае был второй внутренний цикл над выполняемыми операциями.
Крейг Гидни

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

Я хотел избежать стандартного примера «структурного равенства». Отражение не обязательно для самоизменяющегося кода, но оно, безусловно, помогает.
Крейг Гидни

На самом деле, нет. Вы можете сделать это на языках без отражения просто отлично.
Билли Онил

-2

Это никоим образом не обманывает ... Скорее, он позволяет серверам приложений запускать классы, созданные пользователем с именем по их выбору, что обеспечивает гибкость для пользователя, а не обманывает его.

И если вы хотите увидеть код любого файла .class (на Java), то есть несколько бесплатных декомпиляторов!


Декомпиляция не является функцией отражения.
Билли ONEAL

да, это не так ... Но я хотел сказать, что если вам хочется мошенничать при использовании рефлексии (которая предоставляет вам подробную информацию о любом классе в java), то вы ошибаетесь, потому что рефлексия очень полезна, и если вы получаете подробности класс - это то, что касается вас, это легко сделать с помощью любого декомпилятора ...
ANSHUL JAIN

2
Отражение - полезная концепция в некоторых случаях, да. Но 99,9% времени я вижу, как он используется, я вижу, что он использовал, чтобы взломать ошибку дизайна.
Билли ONEAL
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.