В довольно большом проекте есть исходный файл с несколькими функциями, которые чрезвычайно чувствительны к производительности (вызывается миллионы раз в секунду). Фактически, предыдущий сопровождающий решил написать 12 копий функции, каждая из которых отличается незначительно, чтобы сэкономить время, затрачиваемое на проверку условий в одной функции.
К сожалению, это означает, что код является PITA для поддержки. Я хотел бы удалить весь дублирующий код и написать только один шаблон. Однако язык Java не поддерживает шаблоны, и я не уверен, что дженерики подходят для этого.
Мой текущий план состоит в том, чтобы написать вместо этого файл, который генерирует 12 копий функции (практически одноразовый расширитель шаблона). Я, конечно, дал бы обильное объяснение того, почему файл должен быть создан программно.
Меня беспокоит то, что это может привести к путанице у будущих сопровождающих и, возможно, к появлению неприятных ошибок, если они забудут восстановить файл после его изменения, или (что еще хуже), если они изменят вместо этого сгенерированный программным путем файл. К сожалению, если не считать переписывания всего этого в C ++, я не вижу способа это исправить.
Преимущества этого подхода перевешивают недостатки? Должен ли я вместо этого:
- Возьмите удар производительности и используйте одну, обслуживаемую функцию.
- Добавьте объяснения, почему функция должна дублироваться 12 раз, и любезно возьмите на себя бремя обслуживания.
- Попытайтесь использовать дженерики в качестве шаблонов (они, вероятно, не работают таким образом).
- Кричите на старого сопровождающего за то, что он делает код зависимым от производительности для одной функции.
- Другой метод для поддержания производительности и ремонтопригодности?
PS Из-за плохого дизайна проекта, профилирование функции довольно сложно ... однако, бывший сопровождающий убедил меня, что снижение производительности недопустимо. Я предполагаю, что под этим он подразумевает более 5%, хотя это полное предположение с моей стороны.
Возможно, я должен немного прояснить. 12 копий выполняют очень похожую задачу, но имеют незначительные различия. Различия находятся в разных местах всей функции, поэтому, к сожалению, существует множество условных выражений. В действительности существует 6 «режимов» работы и 2 «парадигмы» работы (слова, придуманные мной). Чтобы использовать функцию, нужно указать «режим» и «парадигму» работы. Это никогда не динамично; каждый кусок кода использует ровно один режим и парадигму. Все 12 пар мод-парадигма используются где-то в приложении. Функции точно названы от func1 до func12, причем четные числа представляют вторую парадигму, а нечетные числа представляют первую парадигму.
Я знаю, что это худший дизайн, если целью является ремонтопригодность. Но он кажется "достаточно быстрым", и этот код некоторое время не нуждался в каких-либо изменениях ... Стоит также отметить, что исходная функция не была удалена (хотя, насколько я могу судить, это мертвый код) поэтому рефакторинг будет простым.
источник
Makefile
» (или любой другой системы, которую вы используете) и удалите его сразу после завершения компиляции . Таким образом, у них просто нет возможности изменить неправильный исходный файл.Ответы:
Это очень плохая ситуация, вам нужно реорганизовать это как можно скорее - это технический долг в худшем случае - вы даже не знаете, насколько важен код на самом деле - только предположите, что он важен.
Что касается решений как можно скорее:
то, что можно сделать, это добавить пользовательский этап компиляции. Если вы используете Maven, который на самом деле довольно прост, другие автоматизированные системы сборки, вероятно, тоже справятся с этим. Написать файл с различным расширением , чем .java и добавить пользовательский шаг , который ищет ваш источник для файлов , как это и восстанавливает фактическую .java. Вы также можете добавить огромный отказ от ответственности в автоматически сгенерированный файл, объясняющий, что не следует его изменять.
За и против использования созданного ранее файла. Ваши разработчики не получат свои изменения в .java. Если они действительно запустят код на своей машине перед фиксацией, они обнаружат, что их изменения не имеют никакого эффекта (ха). И тогда, возможно, они будут читать отказ от ответственности. Вы абсолютно правы в том, что не доверяете своим товарищам по команде и будущему себе, помня о том, что этот конкретный файл необходимо изменить по-другому. Это также позволяет автоматическое тестирование, так как JUnit скомпилирует вашу программу перед запуском тестов (и также регенерирует файл)
РЕДАКТИРОВАТЬ
Судя по комментариям, ответ получился таким, как будто это способ заставить эту работу работать бесконечно, и, возможно, можно использовать ее в других важных для вашего проекта частях вашего проекта.
Проще говоря: это не так.
Дополнительное бремя создания собственного мини-языка, написания для него генератора кода и его обслуживания, не говоря уже о том, чтобы обучать его будущим сопровождающим, в конечном итоге является адским. Вышесказанное позволяет только более безопасный способ решения проблемы, пока вы работаете над долгосрочным решением . То, что это займет, выше меня.
источник
ant clean
или каким-либо другим аналогом. Нет необходимости помещать заявление об отказе в файл, которого даже нет!Реал обслуживания реален, или он вас просто беспокоит? Если это только беспокоит вас, оставьте это в покое.
Реальна ли проблема производительности, или предыдущий разработчик только думал, что это так? Проблемы с производительностью чаще всего возникают не там, где, как считается, даже при использовании профилировщика. (Я использую эту технику, чтобы надежно найти их.)
Таким образом, существует вероятность того, что у вас есть некрасивое решение не проблемы, но это также может быть и уродливым решением реальной проблемы. Если есть сомнения, оставьте это в покое.
источник
Действительно убедитесь, что в реальных производственных условиях (реалистичное потребление памяти, реалистичное включение сбора мусора и т. Д.) Все эти отдельные методы действительно влияют на производительность (в отличие от наличия только одного метода). Это займет несколько дней вашего времени, но может сэкономить вам недели, упрощая код, с которым вы работаете.
Кроме того , если вы делаете обнаружить , что вам нужны все функции, вы можете захотеть использовать Javassist для генерации кода программно. Как уже отмечали другие, вы можете автоматизировать это с Maven .
источник
Почему бы не включить какой-то шаблон препроцессора \ генерации кода для этой системы? Пользовательский дополнительный шаг сборки, который выполняет и генерирует дополнительные исходные файлы Java перед компиляцией остальной части кода. Вот как часто работает включение веб-клиентов из файлов wsdl и xsd.
Вы, конечно, будете обслуживать этот препроцессор \ генератор кода, но вам не придется беспокоиться об обслуживании дублированного основного кода.
Поскольку во время компиляции генерируется Java-код, за дополнительный код не нужно платить за производительность. Но вы получаете упрощение обслуживания благодаря наличию шаблона вместо всего дублированного кода.
Обобщения Java не дают никакого преимущества в производительности из-за стирания типов в языке, вместо него используется простое приведение типов.
Из моего понимания шаблонов C ++ они скомпилированы в несколько функций для каждого вызова шаблона. Вы получите дублированный временной код во время компиляции для каждого типа, который вы храните в std :: vector, например.
источник
Никакой здравый Java-метод не может быть достаточно длинным, чтобы иметь 12 вариантов ... и JITC ненавидит длинные методы - он просто отказывается их оптимизировать должным образом. Я видел ускорение в два раза, просто разделив метод на два более коротких. Может быть, это путь.
OTOH, имеющий несколько копий, может иметь смысл, даже если они были идентичными. Поскольку каждый из них используется в разных местах, они оптимизируются для разных случаев (JITC профилирует их, а затем помещает редкие случаи в исключительный путь.
Я бы сказал, что генерация кода не имеет большого значения, если есть веская причина. Накладные расходы довольно низкие, и правильно именованные файлы сразу приводят к их источнику. Некоторое время назад, когда я генерировал исходный код, я вставлял
// DO NOT EDIT
каждую строку ... Я думаю, этого достаточно.источник
Нет ничего плохого в упомянутой вами «проблеме». Из того, что я знаю, это точный вид дизайна и подход, используемый сервером БД для обеспечения хорошей производительности.
У них есть много специальных методов, позволяющих обеспечить максимальную производительность для всех видов операций: объединение, выбор, агрегирование и т. Д., Когда применяются определенные условия.
Короче говоря, генерация кода, подобная той, которую вы считаете плохой идеей. Возможно, вам следует взглянуть на эту диаграмму, чтобы увидеть, как БД решает проблему, аналогичную вашей:
источник
Возможно, вы захотите проверить, можете ли вы по-прежнему использовать некоторые разумные абстракции и использовать, например, шаблон метода шаблона, чтобы написать понятный код для общей функциональности и переместить различия между методами в «примитивные операции» (в соответствии с описанием шаблона) в 12. подклассы. Это значительно улучшит удобство сопровождения и тестируемость, и может фактически иметь ту же производительность, что и текущий код, поскольку JVM может встроить вызовы метода для примитивных операций через некоторое время. Конечно, вы должны проверить это в тесте производительности.
источник
Вы можете решить проблему специализированных методов, используя язык Scala.
Scala может использовать встроенные методы, это (в сочетании с простым использованием функции более высокого порядка) позволяет избежать дублирования кода бесплатно - это похоже на главную проблему, упомянутую в вашем ответе.
Кроме того, в Scala есть синтаксические макросы, которые позволяют многое делать с кодом во время компиляции безопасным для типов образом.
Кроме того, в Scala можно решить и распространенную проблему объединения типов примитивов в боксы: он может выполнять универсальную специализацию для примитивов, чтобы избежать автоматического упаковывания с помощью
@specialized
аннотации - эта вещь встроена в сам язык. Итак, в основном вы напишите один общий метод в Scala, и он будет работать со скоростью специализированных методов. И если вам также нужна быстрая общая арифметика, это легко сделать, используя «шаблон» Typeclass для внедрения операций и значений для различных числовых типов.Кроме того, Scala потрясающая во многих других отношениях. И вам не нужно переписывать весь код в Scala, потому что Scala <-> Java-совместимость превосходна. Просто убедитесь, что вы используете SBT (инструмент сборки scala) для сборки проекта.
источник
Если рассматриваемая функция велика, преобразование битов «mode / paradigm» в интерфейс и последующая передача объекта, реализующего этот интерфейс, в качестве параметра функции могут работать. GoF называет это паттерном «Стратегия», iirc. Если функция мала, увеличение накладных расходов может быть значительным ... кто-то уже упоминал профилирование? ... больше людей должны упомянуть профилирование.
источник
Сколько лет программе? Скорее всего, более новое оборудование устранило бы узкое место, и вы могли бы перейти на более поддерживаемую версию. Но должна быть причина для обслуживания, поэтому, если ваша задача не состоит в том, чтобы улучшить кодовую базу, оставьте все как есть.
источник