Привет хабр! На днях мне в очередной раз на глаза попал код вида  В коде есть одна довольно важная особенность: получателю крайне хотелось бы знать, что произошло на самом деле. Ведь в одном случае у нас проблемы с работой системой, а в другом — мы просто прогреваемся. Однако модель не дает нам этого (в угоду отправителю, который зачастую является автором модели).
Более того, даже факт "возможно, что-то не так" происходит из того, что коллекция Volatilities пуста. Что в некоторых случаях может быть и корректно. Я уверен, что большинство опытных разработчиков встречало в коде строки, в которых заключалось тайное знание в стиле "если выставлена эта комбинация флажков, то от нас просят сделать A, B и C" (хотя по самой модели этого не видно). С моей точки зрения, подобная экономия на структуре классов сказывается крайне негативно на проекте в будущем, превращая его в набор хаков и костылей, постепенно трансформируя более-менее удобный код в legacy. Важно: в статье я привожу примеры, которые полезны для проектов, в которых несколько разработчиков (а не один), плюс которые будут обновляться и расширяться в течении хотя бы 5-10 лет. Всё это не имеет смысла, если в проекте один разработчик лет на пять, или же если после релиза никаких изменений не планируется. И логично, если проект необходим всего на пару месяцев, нет смысла вкладываться в четкую модель данных. Однако если же вы занимаетесь долгоиграющим — добро пожаловать под кат. Зачастую одно и то же поле содержит объект, который может иметь разные смысловые значения (как в примере). Однако для экономии классов разработчик оставляет только один тип, снабжая его флагами (или комментариями в стиле "если тут ничего нет, значит ничего не подсчиталось"). Подобный подход может замаскировать ошибку (что плохо для проекта, однако удобно для команды, которая поставляет сервис, ведь баги-то не видны снаружи). Более корректный вариант, который позволяет даже на дальнем конце провода узнать, что происходит на самом деле — это использовать интерфейс + визиторы. В этом случае пример из заголовка превращаяется в код вида: При подобного рода обработке: В ряде других языков (например, Scala или Kotlin) есть ключевые слова, которые позволяют запретить наследоваться от определенного типа, при определенных условиях. Таким образом мы на этапе компиляции знаем всех возможных наследников нашего типа. В частности, пример выше можно переписать на Kotlin так: Получилось чуть меньше кода, однако теперь мы в процессе компиляции знаем, что все возможные наследники VolatilityResponse находятся в одном с ним файле, а значит следующий код не скомпилируется, так как мы не перебрали все возможные значения класса. Однако стоит помнить, что подобные проверки работают только для функциональных вызовов. Код ниже соберется без ошибок: Рассмотрим относительно типичную разработку для базы данных. Скорее всего, где-то в коде у вас будут идентификаторы объектов. Например:  Вроде стандартный код. Типы даже совпадают с аналогичными в базе данных. Однако вопрос: корректен ли код ниже? Ответ — скорее всего нет, так как мы сравниваем Id пользователя и Id группы в первом примере. А во втором ошибочно ставим id из Group как id от User. Как ни странно, это довольно просто исправить: просто заведите типы GroupId, UserId и так далее. Таким образом создание User работать уже не будет, так как у вас не сойдутся типы. Что невероятно классно, ведь вы смогли рассказать о модели компилятору. Более того, у вас будут правильно работать методы с однотипными параметрами, так как теперь они не будут повторяться: Однако вернемся к примеру со сравнением идентификаторов. Он чуть сложнее, так как надо запретить компилятору сравнивать несравнимое в процессе сборки. И сделать это можно следующим образом: Как результат: Зачастую в наших приложениях для типов вводятся дополнительные правила, которые легко проверить. В худшем случае ряд функций выглядит примерно так: То есть функция принимает на вход довольно широкий тип, а далее запускает провеки. В общем случае это неправильно, так как: Корректное поведение: Как результат мы получаем: Вводя довольно строгую типизацию, нельзя также забывать о том, что при передаче данных в Sql, нам все-таки надо получить настоящий идентификатор. И в этом случае логично немного обновить типы, оборачивающие один string: Говоря о создании большого числа типов, можно часто встретить два диалектических аргумента: Строго говоря, оба аргумента зачастую приводят без фактов, однако: Важно: подобными оптимизациями следует заниматься только тогда, когда вы получили гарантированные факты, что именно микротипы тормозят приложение. По моему опыту, подобная ситуация скорее невозможна. С большей долей вероятности у вас будет тормозить тот же логгер, потому что каждая операция ждет сброса на диск (причем всё было приемлемо на компьютере разработчика с M.2 SSD, однако пользователь со старым HDD видит совсем другие результаты). Однако, сами приемы: Еще раз повторю важный пункт из заглавия: все вещи, описанные в статье, имеют смысл в больших проектах, которые разрабатываются и используются годами. В те, где осмысленно уменьшать стоимость поддержки и удешевлять добавление новой функциональности. В остальных случаях зачастую резоннее всего как можно быстрее сделать продукт, не заморачиваясь тестами, моделями и "хорошим кодом". Однако именно для долгих проектов разумно использовать максимально строгую типизацию, где мы в модели сможем строго описать, какие значение в принципе возможны. Если ваш сервис иногда может возвращать нерабочий результат, то выражайте это в модели и показывайте разработчику явно. Не добавляйте тысячу флажков с описаниями в документации. Если ваши типы могут быть одинаковыми в программе, однако они разные по сути бизнеса, то и определяйте их именно как разные. Не стоит их смешивать, пусть даже типы их полей совпадают. Если у вас есть вопросы к производительности — примените научный метод и сделайте тест (а лучше — попросите независимого человека проверить всё это). При таком сценарии вы на самом деле будете ускорять программу, а не просто потратите время коллектива. Однако так же верно и обратное: если есть подозрение, что ваша программа или библиотека тормозит, то сделайте тест. Не надо говорить, что всё хорошо, просто покажите это в цифрах. User