Object Validation: to Defer or Not?

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

Я ранее сказал, что конструкторы должны быть свободны от кода и не должны выполнять ничего, кроме инициализации атрибутов. С тех пор самый часто задаваемый вопрос звучит так: А что насчет проверки аргументов? Если они “неправильные”, то в чем смысл создания объекта в “неправильном” состоянии? Такой объект обязательно потерпит неудачу позже, в неожиданный момент. Разве не лучше бросить исключение в самый момент создания? Чтобы сразу же сорвать выполнение, так сказать? Вот что я думаю.

Начнем с этого кода на Ruby:

Мы можем использовать это для чтения списка пользователей из файла.

Существует несколько способов злоупотребления этим классом:

  • Передайте что-то другое, что не является String;

  • Передайте файл, который не существует;

  • Передайте каталог вместо файла.

Вы видите разницу между этими четырьмя ошибками, которые мы можем совершить? Давайте посмотрим, как наш класс может защититься от каждой из них:

Первые две потенциальные ошибки были отфильтрованы в конструкторе, а остальные две - позже, в методе. Почему я сделал это именно так? Почему бы не поместить все их в конструктор?

Потому что первые две компрометируют состояние объекта, в то время как с остальными двумя - его поведение во время выполнения. Вы помните, что объект представляет собой набор других объектов, которые он инкапсулирует, называемых атрибутами. Объект класса Users не может представлять nil или число. Он может представлять только файл с именем типа String. С другой стороны, то, что содержится в этом файле и является ли он действительно файлом, не делает состояние недопустимым. Это только вызывает проблемы для поведения.

Хотя разница может показаться тонкой, она очевидна. Существуют две фазы взаимодействия с инкапсулированным объектом: подключение и взаимодействие.

Сначала мы инкапсулируем file и хотим быть уверены, что это действительно файл. Мы еще не общаемся с ним, мы не хотим, чтобы он работал для нас, мы просто хотим убедиться, что это объект, с которым мы сможем поговорить в ближайшем будущем. Если это nil или float, у нас обязательно возникнут проблемы в будущем. Вот почему мы вызываем исключение из конструктора.

Затем вторая фаза - взаимодействие, где мы передаем управление объекту и ожидаем, что он будет вести себя правильно. На этой фазе у нас может быть и другие процедуры проверки, чтобы убедиться, что наше взаимодействие будет проходить гладко. Важно отметить, что эти проверки очень ситуационные. Мы можем вызывать names() несколько раз и каждый раз иметь различную ситуацию с файлом на диске. Сначала он может не существовать, а через несколько секунд будет готов и доступен для чтения.

В идеале, язык программирования должен предоставлять инструменты для первого типа проверок, например, с помощью строгой типизации. В Java, например, нам не нужно было бы проверять тип file, компилятор поймал бы эту ошибку раньше. В Kotlin мы могли бы избавиться от проверки NULL благодаря функциональности Null Safety. Ruby менее мощный, чем эти языки, вот почему мы должны проверять “вручную”.

Таким образом, подведем итог: проверка в конструкторах не является плохой идеей, при условии, что проверки не касаются объектов, а только подтверждают, что они достаточно хороши для работы в будущем.

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 14:02

sixnines availability badge   GitHub stars