Является ли шаблон «метапрограммирование» в Java хорошей идеей?

29

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

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

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

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

Преимущества этого подхода перевешивают недостатки? Должен ли я вместо этого:

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

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


Возможно, я должен немного прояснить. 12 копий выполняют очень похожую задачу, но имеют незначительные различия. Различия находятся в разных местах всей функции, поэтому, к сожалению, существует множество условных выражений. В действительности существует 6 «режимов» работы и 2 «парадигмы» работы (слова, придуманные мной). Чтобы использовать функцию, нужно указать «режим» и «парадигму» работы. Это никогда не динамично; каждый кусок кода использует ровно один режим и парадигму. Все 12 пар мод-парадигма используются где-то в приложении. Функции точно названы от func1 до func12, причем четные числа представляют вторую парадигму, а нечетные числа представляют первую парадигму.

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

Фэнъян Ван
источник
Если падение производительности достаточно серьезно, чтобы гарантировать 12 версий функции, то вы можете застрять в нем. Если вы выполните рефакторинг для одной функции (или будете использовать дженерики), будет ли падение производительности настолько плохим, что вы потеряете клиентов и бизнес?
FrustratedWithFormsDesigner
12
Тогда я думаю, что вам нужно провести свои собственные тесты (я знаю, что вы сказали, что профилирование сложно, но вы все еще можете делать грубые «черные ящики» тесты производительности системы в целом, верно?), Чтобы увидеть, насколько велика разница является. И если это заметно, то я думаю, что вы, возможно, застряли с генератором кода.
FrustratedWithFormsDesigner
1
Это может звучать так, как будто бы он выиграл от некоторого параметрического полиморфизма (или, может быть, даже от дженериков), вместо того, чтобы вызывать каждую функцию под другим именем.
Роберт Харви
2
Имена функций func1 ... func12 кажутся безумными. По крайней мере, назовите их mode1Par2 и т.д ... или, может быть, myFuncM3P2.
user949300
2
«если они изменяют вместо этого сгенерированный программным способом файл» ... создайте файл только при сборке из « Makefile» (или любой другой системы, которую вы используете) и удалите его сразу после завершения компиляции . Таким образом, у них просто нет возможности изменить неправильный исходный файл.
Бакуриу

Ответы:

23

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

Что касается решений как можно скорее:
то, что можно сделать, это добавить пользовательский этап компиляции. Если вы используете Maven, который на самом деле довольно прост, другие автоматизированные системы сборки, вероятно, тоже справятся с этим. Написать файл с различным расширением , чем .java и добавить пользовательский шаг , который ищет ваш источник для файлов , как это и восстанавливает фактическую .java. Вы также можете добавить огромный отказ от ответственности в автоматически сгенерированный файл, объясняющий, что не следует его изменять.

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

РЕДАКТИРОВАТЬ

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

Проще говоря: это не так.

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

Ordous
источник
19
Извините, но я должен не согласиться. Вы недостаточно знаете код ОП, чтобы принять это решение за него. Генерация кода это мне кажется, что он содержит больше технических долгов, чем оригинальное решение.
Роберт Харви
6
Комментарий Роберта невозможно переоценить. Каждый раз, когда у вас появляется «заявление об отказе от ответственности за изменение файла», то есть «технический долг», эквивалентный магазину обналичивания чеков, которым управляет нелегальный букмекер по прозвищу «Акула».
CorsiKa
8
Разумеется, автоматически сгенерированный файл не входит в исходный репозиторий (в конце концов, он не является исходным, это скомпилированный код), и его следует удалить, ant cleanили каким-либо другим аналогом. Нет необходимости помещать заявление об отказе в файл, которого даже нет!
Йорг Миттаг
2
@RobertHarvey Я не принимаю никаких решений для ФП. ОП спросил, стоит ли иметь такие шаблоны в том виде, в котором он у них есть, и я предложил (лучший) способ их обслуживания. Это не идеальное решение, и, на самом деле, как я уже говорил в самом первом предложении, ситуация в целом плохая, и гораздо лучшим способом продвижения вперед является устранение проблемы, а не ее шаткое решение. Это включает в себя правильную оценку того, насколько критичен этот код, насколько сильно снижается производительность, и есть ли способы заставить его работать без написания большого количества плохого кода.
Орден
2
@corsiKa Я никогда не говорил, что это долговременное решение. Это будет такой же долг, как и исходная ситуация. Единственное, что он делает, это уменьшает волатильность, пока не будет найдено систематическое решение. Скорее всего, это будет включать в себя переход на новую платформу / фреймворк / язык, поскольку, если вы сталкиваетесь с такими проблемами в Java - вы либо делаете что-то чрезвычайно сложное, либо крайне неправильно.
Орден
16

Реал обслуживания реален, или он вас просто беспокоит? Если это только беспокоит вас, оставьте это в покое.

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

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

Майк Данлавей
источник
10

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

Кроме того , если вы делаете обнаружить , что вам нужны все функции, вы можете захотеть использовать Javassist для генерации кода программно. Как уже отмечали другие, вы можете автоматизировать это с Maven .

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

Почему бы не включить какой-то шаблон препроцессора \ генерации кода для этой системы? Пользовательский дополнительный шаг сборки, который выполняет и генерирует дополнительные исходные файлы Java перед компиляцией остальной части кода. Вот как часто работает включение веб-клиентов из файлов wsdl и xsd.

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

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

Обобщения Java не дают никакого преимущества в производительности из-за стирания типов в языке, вместо него используется простое приведение типов.

Из моего понимания шаблонов C ++ они скомпилированы в несколько функций для каждого вызова шаблона. Вы получите дублированный временной код во время компиляции для каждого типа, который вы храните в std :: vector, например.

Питер Смит
источник
2

Никакой здравый Java-метод не может быть достаточно длинным, чтобы иметь 12 вариантов ... и JITC ненавидит длинные методы - он просто отказывается их оптимизировать должным образом. Я видел ускорение в два раза, просто разделив метод на два более коротких. Может быть, это путь.

OTOH, имеющий несколько копий, может иметь смысл, даже если они были идентичными. Поскольку каждый из них используется в разных местах, они оптимизируются для разных случаев (JITC профилирует их, а затем помещает редкие случаи в исключительный путь.

Я бы сказал, что генерация кода не имеет большого значения, если есть веская причина. Накладные расходы довольно низкие, и правильно именованные файлы сразу приводят к их источнику. Некоторое время назад, когда я генерировал исходный код, я вставлял // DO NOT EDITкаждую строку ... Я думаю, этого достаточно.

maaartinus
источник
1

Нет ничего плохого в упомянутой вами «проблеме». Из того, что я знаю, это точный вид дизайна и подход, используемый сервером БД для обеспечения хорошей производительности.

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

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

randomA
источник
1

Возможно, вы захотите проверить, можете ли вы по-прежнему использовать некоторые разумные абстракции и использовать, например, шаблон метода шаблона, чтобы написать понятный код для общей функциональности и переместить различия между методами в «примитивные операции» (в соответствии с описанием шаблона) в 12. подклассы. Это значительно улучшит удобство сопровождения и тестируемость, и может фактически иметь ту же производительность, что и текущий код, поскольку JVM может встроить вызовы метода для примитивных операций через некоторое время. Конечно, вы должны проверить это в тесте производительности.

Ханс-Петер Стёрр
источник
0

Вы можете решить проблему специализированных методов, используя язык Scala.

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

Кроме того, в Scala есть синтаксические макросы, которые позволяют многое делать с кодом во время компиляции безопасным для типов образом.

Кроме того, в Scala можно решить и распространенную проблему объединения типов примитивов в боксы: он может выполнять универсальную специализацию для примитивов, чтобы избежать автоматического упаковывания с помощью @specializedаннотации - эта вещь встроена в сам язык. Итак, в основном вы напишите один общий метод в Scala, и он будет работать со скоростью специализированных методов. И если вам также нужна быстрая общая арифметика, это легко сделать, используя «шаблон» Typeclass для внедрения операций и значений для различных числовых типов.

Кроме того, Scala потрясающая во многих других отношениях. И вам не нужно переписывать весь код в Scala, потому что Scala <-> Java-совместимость превосходна. Просто убедитесь, что вы используете SBT (инструмент сборки scala) для сборки проекта.

Сардж Борщ
источник
-1

Если рассматриваемая функция велика, преобразование битов «mode / paradigm» в интерфейс и последующая передача объекта, реализующего этот интерфейс, в качестве параметра функции могут работать. GoF называет это паттерном «Стратегия», iirc. Если функция мала, увеличение накладных расходов может быть значительным ... кто-то уже упоминал профилирование? ... больше людей должны упомянуть профилирование.

mjfgates
источник
это больше похоже на комментарий, чем на ответ
комнат
-2

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

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