Десять причин избегать метапрограммирования

11 февраля 2015

В любой команде рано или поздно появляется человек, который совсем недавно прочитал книжку по Lisp или осилил Template Haskell и потому ему не терпится применить метапрограммирование на практике. Однако проблема заключается в том, что в большинстве случаев макросы или шаблоны создают больше проблем, чем решают. В этой заметке будет показано, почему так.

Примечание: Далее под макросами и шаблонами я буду иметь в виду средства метапрограммирования, не привязанные к конкретному языку. Это могут быть макросы в Clojure или Scala, шаблоны в Haskell, parse_transform и, опять таки, макросы в Erlang, шаблоны в C++ и так далее. Все эти средства примерно об одном и том же.

  1. Вообще, трудно представить или даже специально придумать задачу, которую невозможно решить без макросов. Мне в голову приходило использовать макросы 1-2 раза, и то задачу можно было решить иначе. Почти всегда проблему на самом деле можно решить обычными средствами языка.
  2. На практике использование макросов всегда приводит к существенному увеличению времени компиляции проекта. Также существенно увеличиваются требования к ресурсам, необходимым для компиляции, в особенности оперативной памяти;
  3. Никто не отменял обычную кодогенерацию. Например, Thrift, Protobuf и прочие делают в сущности то же самое, что вы хотите от макросов. Если же такие решения плохо интегрируются с вашей системой сборки или IDE, возможно, просто у вас фиговая система сборки или IDE.
  4. Допустим, код, полученный с помощью шаблонов или макросов, бросит исключение. Какие номера строк вы увидите в стэктрейсе? Сможете ли вы легко разобраться в проблеме и исправить ее, особенно, если проблемный код написан кем-то другим?
  5. Если вы используете шаблоны, то скорее всего сломаете мозг своей любимой IDE. Допустим, шаблон генерирует новые функции. Чтобы узнать о них, IDE нужно иметь встроенный интерпретатор вашего языка программирования. Скорее всего, она его не имеет, а значит будет подчеркивать сгенерированные функции красным, когда вы будете пытаться их использовать, так как в коде их как бы и нет.
  6. Возможно, вы пытаетесь замаскировать при помощи шаблонов дублирование кода. Есть менее радикальные способы борьбы с code smell. Дублированный код почти всегда можно вынести в отдельные методы. Или параметризовать различающиеся части, передав лямбду.
  7. Большинство программистов просто не умеют работать с макросами и шаблонами. Помните, что вы не одни в команде. Даже если ваши коллеги знают макросы, все равно их использование существенно усложнит понимание и поддержку кода.
  8. Зачастую макросы могут быть причиной странных и непонятных ошибок. Например, в Erlang вы можете собрать beam с какими-то макросами, затем обновить зависимость, в которой объявлен этот макрос, и собрать остальную часть проекта уже с другим макросом. Попробуйте потом отладить ошибки, которые посыпятся! Или, допустим, макрос использует текущее время. Вы один раз скомпилировали код, он закэшировался. Затем вы пересобираете проект и не понимаете, почему время не изменяется.
  9. Нередко макросы — это нестабильная и вообще экспериментальная фича языка. В Scala и так все постоянно меняется, а вы еще предлагаете взять макросы. В Template Haskell тоже вон все поменялось, теперь там есть какие-то типизированные макросы. Плюс к этому в реализации макросов нередко имеются какие-то мелкие косяки и неудобства. Так в Template Haskell шаблон не может быть объявлен и использован в одном файле.
  10. Вы изобретаете кучу различных DSL. Каждый язык при этом, понятное дело, отличается от других. Почему бы просто не писать на одном языке? Может быть, он просто недостаточно выразителен и следует найти язык получше?

Как видите, минусов у метапрограммирования масса, и, сказать по правде, я не вижу существенных плюсов.

Пожалуйста, поймите меня правильно. Не то, чтобы макросы абсолютно бесполезны и их никогда не следует использовать. В редких случаях они могут быть довольно полезны. В этом смысле они напоминают наследование, имплиситы в Scala или даже строгую статическую типизацию. Это все средства, без которых, вообще говоря, в большинстве случаев можно обойтись и потому они только приносят лишнюю сложность. Фокус в том, что в некоторых случаях они очень, очень удобны, так удобны, что их включают в язык. Scala, например, как бы «сложный», но очень «удобный» язык. А вот Scheme — язык «простой», но «неудобный». Был бы еще проще и неудобнее, не будь в нем макросов.

А используете ли вы метапрограммирование в своей работе и если да, то в каких задачах?

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.