Строки или перечислимый тип: что лучше использовать в качестве параметров функций, если набор возможных значений ограничен
7К открытий7К показов
Рассказывает Arne Mertz
Порой в качестве входных данных разрешен фиксированный набор строк. В дальнейшем они хранятся и как-то используются. Перечислимый тип будет лучшим вариантом.
Пример
На прошлой неделе мы с коллегой работали над простым заданием. Тестирующий фреймворк мог обрабатывать два типа сообщений, посылаемых API: ошибки и предупреждения. API также отправляет сообщения, и нужно было настроить фреймворк для их обработки. Ничего удивительного: эта троица (сообщение, ошибка, предупреждение) практически неразлучна.
Добиться нужной функциональности было легко. К двум уже объявленным “шагам” в Cucumber нужно было добавить третий. Две функции вызывались этими шагами, и мы написали третью. Таким образом, мы добавили поддержку сообщений.
Всё работало. Мы могли проверить код и выпить по чашке кофе, но совесть наша была не чиста. Мы столкнулись с повторами кода и, что ещё хуже, сами написали третий повтор.
Don’t repeat yourself
В результате мы унифицировали все три “шага”, добавив ещё один параметр: строку, принимающую значение "info"
, "warning"
или "error"
. Функция, вызываемая в этом “шаге”, тоже получала тип сообщения в качестве параметра, и так далее, до уровня доступа к API.
Тем не менее, ещё было, над чем поработать: строка, содержащая тип сообщения, всё ещё использовалась на всех уровнях.
Перечислимые типы вместо строк
Эти строки стоит конвертировать в перечислимый тип. Ниже приведены их основные преимущества.
Сравнения и опечатки
Строки могут содержать любые последовательности символов, даже бессмысленные. Если где-то в сравнении была допущена опечатка, найти её будет непросто. Перечислимые типы же, напротив, являются идентификаторами, и компилятор пожалуется на несоответствие. Рассмотрим в качестве примера маленькую функцию:
В нашем примере помеченная строка никогда не будет достигнута, поскольку messageType
никогда не будет равен "waring"
. Я сделал эту опечатку, но мой напарник был достаточно внимателен, чтобы её заметить. В противном случае мне пришлось бы тратить время на отладку. Используй я перечислимый тип, IDE и компилятор сразу бы указали мне на ошибку.
Соответствие типов
Вернёмся к предыдущей функции. Вызовем её:
Упс. Мы только что хотели напечатать сообщение типа "Something bad happened!"
с текстом "error"
. Если бы тип сообщения был перечислимым, такой проблемы бы не возникло.
По-хорошему, стоит обернуть сообщения в их собственный класс или структуру, поскольку в большинстве случаев мы будем передавать и использовать текст и тип вместе.
Switch/case
В C++ мы не можем использовать строки в условном операторе switch
. Вместо этого приходится нагромождать if/else
. А вот enum’ы — можем! Кроме того, мы сможем получать предупреждения компилятора или статического анализатора, если забудем про перечислимый тип.
Производительность
Я не рекомендую гнаться исключительно за производительностью. Но в нашем случае, кроме производительности, мы получим ещё и более поддерживаемый код, поэтому стоит разобраться чуть подробнее.
Сравнения перечислимых типов обычно быстрее, чем сравнения строк. Размер перечислимого типа равен размеру целого типа а строки могут быть весьма большими. Кроме того, оператор switch
может быть траслирован в jump-таблицы, что может быть эффективнее if/else
.
Перечислимые типы не обязательно более производительны, чем строки. Тем не менее, они явно не хуже, и это всё, о чём стоит беспокоиться.
Заключение
Если у вас есть фиксированный набор строк, используемый в качестве входных или выходных данных, его однозначно стоит конвертировать в enum.
7К открытий7К показов