Чи хороша ідея "метапрограмування" в Java на Java?


29

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

На жаль, це означає, що код є ПДФА для підтримки. Я хотів би видалити весь повторюваний код і написати лише один шаблон. Однак мова, Java, не підтримує шаблони, і я не впевнений, що дженерики для цього підходять.

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

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

Чи переваги такого підходу переважають недоліки? Чи слід замість цього:

  • Скористайтеся хітом продуктивності та використовуйте єдину, доцільну функцію.
  • Додайте пояснення, чому цю функцію потрібно дублювати 12 разів, і милосердно берете на себе тягар з обслуговування.
  • Спроба використовувати дженерики як шаблони (вони, ймовірно, не працюють таким чином).
  • Кричите на старий супровідник, щоб зробити код настільки залежним від продуктивності від однієї функції.
  • Інший метод підтримки продуктивності та ремонту?

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


Можливо, я повинен трохи допрацювати. 12 примірників виконують дуже схоже завдання, але мають хвилинну різницю. Відмінності є в різних місцях по всій функції, тому, на жаль, існує багато, багато, умовних тверджень. Є ефективно 6 "режимів" роботи та 2 "парадигми" роботи (слова, складені власноруч). Для використання функції вказується "режим" і "парадигма" роботи. Це ніколи не є динамічним; кожен фрагмент коду використовує рівно один режим і парадигму. Усі 12 мод-парадигмних пар використовуються десь у додатку. Функції влучно називаються func1 to func12, парні числа представляють другу парадигму, а непарні числа представляють першу парадигму.

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


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

12
Тоді я думаю, що вам потрібно зробити свої власні тести (я знаю, ви сказали, що профілювання є складним, але ви все ще можете робити грубі "тести на ефективність роботи системи" в цілому, правда?), Щоб побачити, наскільки велика різниця там є. І якщо це помітно, я думаю, ви можете застрягти з генератором коду.
FrustratedWithFormsDesigner

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

2
Іменування функцій func1 ... func12 видається божевільним. Принаймні, назвіть їх mode1Par2 тощо ... або, можливо, myFuncM3P2.
user949300

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

Відповіді:


23

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

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

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

EDIT

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

Простіше кажучи: це не так.

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


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

6
Коментар Роберта не можна завищувати. Кожен раз, коли у вас є "відмова від відповідальності, яка пояснює, що не змінювати файл", це еквівалент "технічної заборгованості" магазину чеків-готівки, яким керує незаконний букмекер під назвою "Акула".
corsiKa

8
Згенерований автоматично створений файл не належить до сховища джерела (адже це не джерело, зрештою, це компільований код), і його слід видалити ant cleanбудь-яким іншим вашим еквівалентом. Не потрібно ставити відмову у файл, якого навіть немає!
Йорг W Міттаг

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у кожен рядок ... Я думаю, що це достатньо заощадити.


1

Немає нічого поганого із згаданою вами «проблемою». З того, що мені відомо, саме такий дизайн і підхід використовується сервером БД, щоб мати хороші показники.

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

Коротше кажучи, генерація коду, як та, на вашу думку, погана ідея. Можливо, вам слід переглянути цю діаграму, щоб побачити, як БД вирішує проблему, подібну до вашої:введіть тут опис зображення


1

Ви можете перевірити, чи можете ви все-таки використовувати якісь обґрунтовані абстракції і використовувати, наприклад, шаблон методу шаблону, щоб написати зрозумілий код для загальної функціональності та перемістити відмінності між методами в "примітивні операції" (згідно з описом шаблону) у 12 підкласи. Це суттєво поліпшить ремонтопридатність та доказовість, і воно може мати фактично таку ж ефективність, що й поточний код, оскільки JVM може вбудувати метод призивів для примітивних операцій через деякий час. Звичайно, ви повинні перевірити це в тесті на працездатність.


0

Вирішити проблему спеціалізованих методів можна за допомогою мови Scala.

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

Крім того, у Scala є синтаксичні макроси, що дає можливість робити багато речей з кодом під час компіляції безпечним чином.

І загальну проблему боксу примітивних типів при використанні в дженеріках можливо вирішити і в Scala: він може робити загальну спеціалізацію для примітивів, щоб уникнути боксу автоматично, використовуючи @specializedанотацію - ця річ вбудована в мову сама. Отже, в основному ви напишете один загальний метод у Scala, і він буде працювати зі швидкістю спеціалізованих методів. І якщо вам також потрібна швидка загальна арифметика, це легко здійснити за допомогою "шаблону" Typeclass для введення операцій та значень для різних числових типів.

Також Scala приголомшлива багатьма іншими способами. І вам не доведеться переписувати весь код на Scala, оскільки сумісність Scala <-> Java відмінна. Просто переконайтеся, що для створення проекту використовуйте SBT (інструмент нарощування scala) .


-1

Якщо функція, про яку йдеться, велика, перетворення "режиму / парадигми" бітів в інтерфейс, а потім передача об'єкту, який реалізує цей інтерфейс як параметр, може працювати. GoF називає це "стратегією", iirc. Якщо функція невелика, збільшена накладні витрати можуть бути істотними ... хтось ще згадав про профілювання? ... більше людей повинні згадати про профілювання.


це читає більше як коментар, ніж відповідь
gnat

-2

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


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

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