Помимо отображения нашего специального сообщения, он также выделяет вызов макроса, вызвавший ошибку, и показывает номер строки. Однако есть кое-что, что мы можем сделать, чтобы еще больше улучшить эту ошибку.
Все типы макросов, с которыми мы работали, произошли от базового типа макроса 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. Тестирование