Crystal Programming. Введение на основе проекта в создание эффективных, безопасных и читаемых веб-приложений и приложений CLI - Джордж Дитрих
Шрифт:
Интервал:
Закладка:
После регистрации двух типов мы видим, что массив MODELS содержит их. Наконец, это еще раз показывает, что его можно было разрешить при вызове до или после регистрации связанного типа. Как упоминалось ранее в этой главе, макросы не имеют такой же типизации, как обычный код Crystal. Из-за этого к макросам невозможно добавлять ограничения типов. Это означает, что пользователь может передать в макрос .register_model все, что пожелает, что может привести к не столь очевидным ошибкам. Например, если они случайно передали "Time" вместо Time, это приведет к следующей ошибке: неопределенный метод макроса 'StringLiteral#resolve'. В следующем разделе мы собираемся изучить способ сделать источник ошибки более очевидным.
Создание пользовательских ошибок времени компиляции
Ошибки времени компиляции — одно из преимуществ компилируемого языка. Вы сразу же узнаете о проблемах, вместо того, чтобы ждать, пока этот код будет выполнен, чтобы обнаружить ошибку. Однако, поскольку Crystal не знает контекста конкретной ошибки, он всегда будет выводить одно и то же сообщение об ошибке одного и того же типа. Последняя функция, которую мы собираемся обсудить в этой главе, связана с выдачей ваших собственных ошибок во время компиляции.
Пользовательские ошибки времени компиляции могут быть отличным способом добавить дополнительную информацию к сообщению об ошибке, что значительно облегчает жизнь конечному пользователю, поскольку ему становится понятнее, что необходимо сделать для устранения проблемы. Возвращаясь к примеру в конце последнего раздела, давайте обновим наш макрос .exclude_type, чтобы обеспечить лучшее сообщение об ошибке в случае передачи неожиданного типа.
В последних нескольких главах мы использовали различные макрометоды верхнего уровня, такие как #env, #flag и #debug. Другой метод верхнего уровня — #raise, который вызывает ошибку во время компиляции и позволяет предоставить собственное сообщение. Мы можем использовать это с некоторой условной логикой, чтобы определить, не является ли значение, переданное нашему макросу, Path. Наш обновленный макрос будет выглядеть так:
macro exclude_type(type)
{% raise %(Expected argument to 'exclude_type' to be
'Path', got '#{type.class_name.id}'.) unless type.is_a?
Path %}
{% EXCLUDED_TYPES << type.resolve %}
end
Теперь, если бы мы вызвали макрос с "Time", мы бы получили ошибку:
In mutable_constants.cr:43:1
43 | exclude_type "Time"
^-----------
Error: Expected argument to 'exclude_type' to be 'Path', got 'StringLiteral'.
Помимо отображения нашего специального сообщения, он также выделяет вызов макроса, вызвавший ошибку, и показывает номер строки. Однако есть кое-что, что мы можем сделать, чтобы еще больше улучшить эту ошибку.
Все типы макросов, с которыми мы работали, произошли от базового типа макроса ASTNode, который предоставляет базовые методы, общие для всех узлов, откуда и берет свое начало метод #id, который мы использовали несколько раз. Этот тип также определяет свой собственный метод #raise, который работает так же, как и метод верхнего уровня, но выделяет конкретный узел, на котором он был вызван.
Мы можем реорганизовать нашу логику, чтобы использовать это, используя type.raise вместо простого повышения. К сожалению, в этом случае результирующая подсветка ошибок такая же. В Crystal есть несколько серьезных ошибок, связанных с этим, так что, надеюсь, со временем ситуация улучшится. Тем не менее, следовать этой практике по-прежнему рекомендуется, поскольку она не только дает читателю более ясное представление о том, что такое недопустимое значение, но также делает код пригодным для будущего.
Ограничение универсальных типов
Обобщенные шаблоны в Crystal обеспечивают хороший способ уменьшения дублирования, позволяя параметризовать тип для поддержки его использования с несколькими конкретными типами. Хорошим примером этого могут быть типы Array(T) или Hash(K, V). Однако обобщенные типы Crystal в настоящее время не предоставляют встроенного способа ограничения типов, с помощью которых может быть создан универсальный тип. Возьмем, к примеру, следующий код:
abstract class Animal
end
class Cat < Animal
end
class Dog < Animal
end
class Food(T)
end
Food(Cat).new
Food(Dog).new
Food(Int32).new
В этом примере имеется общий тип еды, который должен принимать только подкласс Animal. Однако по умолчанию вполне нормально иметь возможность создавать экземпляр Food, используя тип, отличный от Animal, например Int32. Мы можем использовать специальную ошибку времени компиляции в конструкторе Food, чтобы гарантировать, что T является дочерним элементом Animal. В конечном итоге это будет выглядеть так:
class Food(T)
def self.new
{% raise "Non animal '#{t}' cannot be fed." unless T <=
Animal %}
end
end
В этом новом коде попытка выполнить Food(Int32).new вызовет ошибку во время компиляции.
Возможность определять собственные ошибки времени компиляции может существенно сократить время, необходимое для отладки проблемы. В противном случае неопределенные ошибки могут быть дополнены дополнительным контекстом/ссылками и в целом станут более удобными для пользователя.
Резюме
Ура! Мы подошли к концу части книги, посвященной метапрограммированию, рассмотрели много нового и продемонстрировали, насколько мощными могут быть макросы Crystal. Я надеюсь, что вы сможете применить свое более глубокое понимание макросов и этих шаблонов для решения сложных задач, с которыми вы можете столкнуться в рамках ваших будущих проектов.
В следующей части мы рассмотрим различные инструменты поддержки Crystal, например, как тестировать, документировать и развертывать ваш код, а также как автоматизировать этот процесс!
Часть 5: Вспомогательные инструменты
Crystal поставляется в комплекте с различными вспомогательными функциями и инструментами, которые помогут создать все необходимое для создания надежных и удобных в использовании приложений после того, как само приложение будет написано. Это включает в себя платформу тестирования, которая гарантирует, что приложение продолжает функционировать должным образом, и систему документации, которая облегчает другим пользователям изучение того, как пользоваться приложением, и поддерживается природой самого языка, что упрощает его развертывание. Давайте начнем!
Эта часть содержит следующие главы:
• Глава 14, тестирование
• Глава 15, Документирование кода
• Глава 16, Развертывание кода
• Глава 17, Автоматизация
• Приложение А, Настройка инструментария
• Приложение В, Будущее Crystal
14. Тестирование
Если вы помните, в Главе 4 «Изучение Crystal посредством написания интерфейса