Почему плохая идея создать универсальный сеттер и геттер с отражением?

49

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

Итак, может ли кто-то выразить словами, почему следует избегать рефлексии, особенно в случае универсального метода получения и установки?

гема
источник
12
Если вы просто хотите уменьшить шаблон, и Lombok, и Groovy будут генерировать геттеры и сеттеры тихо, но безопасно.
chrylis -на забастовке-
2
Как бы вы потребляли эти методы получения и установки на статическом языке, таком как Java? Мне кажется, что это не стартер.
Эсбен Сков Педерсен
@EsbenSkovPedersen Вы бы потреблять их очень как Map«s getи put, что является довольно неуклюжим способом делать вещи.
HAEM
2
IIRC, Lombok синтезирует геттеры / сеттеры во время компиляции, как диктуется соответствующей аннотацией, поэтому они: не влекут за собой потери производительности отражения, могут быть статически проанализированы / встроены, могут быть реорганизованы (IntelliJ имеет плагин для Lombok)
Александр

Ответы:

117

Недостатки размышлений в целом

Отражение сложнее понять, чем прямой код.

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

Код отражения недоступен для статического анализа

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

Но если место, которое использует метод получения, является чем-то вроде callGetter("Foo")и callGetterделает getClass().getMethod("get"+name).invoke(this), то вышеупомянутый метод не найдет его, и компилятор не будет жаловаться. Только когда код действительно будет выполнен, вы получите NoSuchMethodException. И представьте боль, в которой вы находитесь, если это исключение (которое отслеживается) проглатывается, callGetterпотому что «оно используется только с жестко закодированными строками, на самом деле это не может произойти». (Никто не станет этого делать, кто-то может поспорить? За исключением того, что ОП сделал именно это в своем ответе SO. Если поле переименовано, пользователи универсального установщика никогда не заметят, за исключением крайне неясной ошибки установщика, молча ничего не делающей. Пользователи получателя могут, если им повезет, заметить вывод консоли игнорируемого исключения.)

Код отражения не проверяется типом компилятором

Это в основном большой подпункт выше. Отражение кода это все о Object. Типы проверяются во время выполнения. Ошибки обнаруживаются модульными тестами, но только если у вас есть покрытие. («Это всего лишь получатель, мне не нужно его проверять».) По сути, вы теряете преимущество, используя Java над Python, которое вы получили в первую очередь.

Код отражения недоступен для оптимизации

Возможно, не в теории, но на практике вы не найдете JVM, которая встроит или создаст встроенный кеш для Method.invoke. Для такой оптимизации доступны обычные вызовы методов. Это делает их намного быстрее.

Отражение кода просто медленно

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

Недостаток универсального геттера / сеттера, в частности

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

Себастьян Редл
источник
8
Вы попали во все основные моменты. Практически идеальный ответ. Я могу поспорить, что геттеры и сеттеры немного лучше, чем публичные поля, но большая точка верна.
JimmyJames
2
Также стоит отметить, что геттеры / сеттеры могут быть легко сгенерированы IDE.
stiemannkj1
26
+1 Отладка логики отражений в масштабном проекте должна быть признана ООН пыткой и запрещена.
errantlinguist
4
"труднее понять для этих программистов." - для этих программистов это довольно сложно понять, но это труднее понять для всех, независимо от того, насколько они опытны.
BartoszKP
4
«Код отражения недоступен для статического анализа» - это. Так важно для включения рефакторинга. И по мере того, как инструменты статического анализа становятся все более мощными (например, поиск кода в последней версии Team Foundation Server), написание кода, который позволяет им работать, становится еще более ценным.
Дэн Джей
11

Потому что сквозные геттеры / сеттеры - это мерзость, которая не дает никакой ценности. Они не обеспечивают инкапсуляцию, так как пользователи могут получить действительные значения через свойства. Они YAGNI, потому что вы редко, если когда-либо, будете менять реализацию своего полевого хранилища.

И потому что это гораздо менее производительно. В C #, по крайней мере, метод получения / установки будет встроен прямо в одну инструкцию CIL. В своем ответе вы заменяете это тремя вызовами функций, которые, в свою очередь, должны загружать метаданные для отражения. Я обычно использовал 10x как практическое правило для затрат на рефлексию, но когда я искал данные, один из первых ответов включал этот ответ, который приблизил его к 1000x.

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

Telastyn
источник
2
Обратите внимание, что вопрос помечен Java, который имеет такой восхитительный дерьмо, как концепция Beans. Боба не имеет открытых полей, но это может привести поле через getX()и setX()методы , которые могут быть найдены с помощью отражения. Бины не обязательно должны инкапсулировать свои значения, они могут быть просто DTO. Так что в Java геттеры часто являются необходимой болью. Свойства C # намного, намного приятнее во всех отношениях.
Амон
11
C # свойства являются одетыми геттерами / сеттерами. Но да, концепция Бобов - это катастрофа.
Себастьян Редл
6
@amon Правильно, C # взял ужасную идею и сделал ее проще в реализации.
JimmyJames
5
@JimmyJames Это не страшная идея, правда; Вы можете поддерживать совместимость ABI, несмотря на изменения кода. Я полагаю, что у многих такой проблемы нет.
Кейси
3
@Casey Сфера этого выходит за рамки комментария, но построение интерфейса из внутренних деталей реализации является обратным. Это приводит к процедурному оформлению. Иногда метод getX является правильным ответом, но в хорошо спроектированном коде это встречается довольно редко. Проблема с геттерами и сеттерами в Java не в шаблонном коде вокруг них, а в том, что они являются частью ужасного подхода к проектированию. Делать это легче, чем облегчать себе удары по лицу.
JimmyJames
8

Помимо аргументов уже представлены.

Это не обеспечивает ожидаемый интерфейс.

Пользователи ожидают вызова foo.getBar () и foo.setBar (value), а не foo.get ("bar") и foo.set ("bar", value)

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

Питер Грин
источник
Мне кажется, что эта причина важнее всех остальных в других ответах.
Эсбен Сков Педерсен
-4

Конечно, это не плохая идея, просто плохая идея в обычном Java-мире (когда вам нужен только геттер или сеттер). Например, некоторые фреймворки (spring mvc) или librares уже используют его (jstl) таким образом, некоторые парсеры json или xml используют его, если вы хотите использовать DSL, вы собираетесь его использовать. Тогда это зависит от вашего сценария использования.

Ничто не является правильным или неправильным как факт.

как факт
источник