В любой команде рано или поздно появляется человек, который совсем недавно прочитал книжку по Lisp или осилил Template Haskell и потому ему не терпится применить метапрограммирование на практике. Однако проблема заключается в том, что в большинстве случаев макросы или шаблоны создают больше проблем, чем решают. В этой заметке будет показано, почему так.
Примечание: Далее под макросами и шаблонами я буду иметь в виду средства метапрограммирования, не привязанные к конкретному языку. Это могут быть макросы в Clojure или Scala, шаблоны в Haskell, parse_transform и, опять таки, макросы в Erlang, шаблоны в C++ и так далее. Все эти средства примерно об одном и том же.
- Вообще, трудно представить или даже специально придумать задачу, которую невозможно решить без макросов. Мне в голову приходило использовать макросы 1-2 раза, и то задачу можно было решить иначе. Почти всегда проблему на самом деле можно решить обычными средствами языка.
- На практике использование макросов всегда приводит к существенному увеличению времени компиляции проекта. Также существенно увеличиваются требования к ресурсам, необходимым для компиляции, в особенности оперативной памяти;
- Никто не отменял обычную кодогенерацию. Например, Thrift , Protobuf и прочие делают в сущности то же самое, что вы хотите от макросов. Если же такие решения плохо интегрируются с вашей системой сборки или IDE, возможно, просто у вас фиговая система сборки или IDE.
- Допустим, код, полученный с помощью шаблонов или макросов, бросит исключение. Какие номера строк вы увидите в стэктрейсе? Сможете ли вы легко разобраться в проблеме и исправить ее, особенно, если проблемный код написан кем-то другим?
- Если вы используете шаблоны, то скорее всего сломаете мозг своей любимой IDE . Допустим, шаблон генерирует новые функции. Чтобы узнать о них, IDE нужно иметь встроенный интерпретатор вашего языка программирования. Скорее всего, она его не имеет, а значит будет подчеркивать сгенерированные функции красным, когда вы будете пытаться их использовать, так как в коде их как бы и нет.
- Возможно, вы пытаетесь замаскировать при помощи шаблонов дублирование кода. Есть менее радикальные способы борьбы с code smell. Дублированный код почти всегда можно вынести в отдельные методы. Или параметризовать различающиеся части, передав лямбду.
- Большинство программистов просто не умеют работать с макросами и шаблонами. Помните, что вы не одни в команде. Даже если ваши коллеги знают макросы, все равно их использование существенно усложнит понимание и поддержку кода.
- Зачастую макросы могут быть причиной странных и непонятных ошибок. Например, в Erlang вы можете собрать beam с какими-то макросами, затем обновить зависимость, в которой объявлен этот макрос, и собрать остальную часть проекта уже с другим макросом. Попробуйте потом отладить ошибки, которые посыпятся! Или, допустим, макрос использует текущее время. Вы один раз скомпилировали код, он закэшировался. Затем вы пересобираете проект и не понимаете, почему время не изменяется.
- Нередко макросы — это нестабильная и вообще экспериментальная фича языка. В Scala и так все постоянно меняется, а вы еще предлагаете взять макросы . В Template Haskell тоже вон все поменялось, теперь там есть какие-то типизированные макросы. Плюс к этому в реализации макросов нередко имеются какие-то мелкие косяки и неудобства. Так в Template Haskell шаблон не может быть объявлен и использован в одном файле.
- Вы изобретаете кучу различных DSL . Каждый язык при этом, понятное дело, отличается от других. Почему бы просто не писать на одном языке? Может быть, он просто недостаточно выразителен и следует найти язык получше?
Как видите, минусов у метапрограммирования масса, и, сказать по правде, я не вижу существенных плюсов.
Пожалуйста, поймите меня правильно. Не то, чтобы макросы абсолютно бесполезны и их никогда не следует использовать. В редких случаях они могут быть довольно полезны. В этом смысле они напоминают наследование, имплиситы в Scala или даже строгую статическую типизацию . Это все средства, без которых, вообще говоря, в большинстве случаев можно обойтись и потому они только приносят лишнюю сложность. Фокус в том, что в некоторых случаях они очень, очень удобны, так удобны, что их включают в язык. Scala, например, как бы «сложный», но очень «удобный» язык. А вот Scheme — язык «простой», но «неудобный». Был бы еще проще и неудобнее, не будь в нем макросов.
А используете ли вы метапрограммирование в своей работе и если да, то в каких задачах?