Ранее я говорил, что конструкторы должны быть свободны от кода и ничего не делать, кроме инициализации атрибута. С тех пор наиболее часто задаваемый вопрос : как насчет проверки аргументов? Если они «сломаны», какой смысл создавать объект в «недействительном» состоянии? Такой объект выйдет из строя позже, в неожиданный момент. Разве не лучше бросить исключение в самый момент создания экземпляра? Быстро провалиться, так сказать? Вот что я думаю.
Давайте начнем с этого кода Ruby:
1
2
3
4
5
6
7
8
|
class Users { def initialize(file) @file = file end def names File.readlines( @file ).reject(&:empty?) end } |
Мы можем использовать его для чтения списка пользователей из файла:
1
|
Users. new ( 'all-users.txt' ).names |
Есть несколько способов злоупотребить этим классом:
- Передайте
nil
в ctor вместо имени файла; - Передайте что-то еще, что не является
String
; - Передайте файл, который не существует;
- Передайте каталог вместо файла.
Видите ли вы разницу между этими четырьмя ошибками, которые мы можем сделать? Давайте посмотрим, как наш класс может защитить себя от каждого из них:
01
02
03
04
05
06
07
08
09
10
11
12
|
class Users { def initialize(file) raise "File name can't be nil" if file.nil? raise 'Name must be a String' unless file.is_a?(String) @file = file end def names raise "#{@file} is absent" unless File.exist?( @file ) raise "#{@file} is not a file" unless File.file?( @file ) File.readlines( @file ).reject(&:empty?) end } |
Первые две потенциальные ошибки были отфильтрованы в конструкторе, а две другие — позже, в методе. Почему я сделал это так? Почему бы не поместить их все в конструктор?
Потому что первые два компрометируют состояние объекта , а с двумя другими — его поведение во время выполнения. Вы помните, что объект является представителем набора других объектов, которые он инкапсулирует, называемых атрибутами. Объект класса Users
не может представлять nil
или число. Он может представлять только файл с именем типа String
. С другой стороны, то, что этот файл содержит и действительно ли это файл — не делает состояние недействительным. Это только создает проблемы для поведения.
Хотя разница может выглядеть неуловимо, это очевидно. Существует две фазы взаимодействия с инкапсулированным объектом: соединение и разговор .
Во-первых, мы инкапсулируем file
и хотим быть уверены, что это действительно файл. Мы еще не говорим об этом, мы не хотим, чтобы это сработало для нас, мы просто хотим убедиться, что это действительно объект, с которым мы сможем поговорить в ближайшем будущем. Если это nil
или число с float
, у нас наверняка будут проблемы в будущем. Вот почему мы поднимаем исключение из конструктора.
Затем идет вторая фаза, где мы делегируем управление объекту и ожидаем, что он будет вести себя правильно. На этом этапе у нас могут быть другие процедуры проверки, чтобы убедиться, что наше взаимодействие пройдет гладко. Важно отметить, что эти проверки очень ситуативны. Мы можем вызывать names()
несколько раз, и каждый раз возникает другая ситуация с файлом на диске. Для начала он может не существовать, а через несколько секунд он будет готов и доступен для чтения.
В идеале язык программирования должен обеспечивать инструменты для первого типа проверок, например, со строгой типизацией . В Java, например, нам не нужно проверять тип file
, компилятор обнаружит эту ошибку раньше. В Kotlin мы сможем избавиться от проверки NULL, благодаря их функции нулевой безопасности . Ruby менее мощный, чем эти языки, поэтому мы должны проверять «вручную».
Таким образом, чтобы подвести итог, проверка в конструкторах не является плохой идеей, при условии, что проверки не касаются объектов, а только подтверждают, что они достаточно хороши для работы позже.
Опубликовано на Java Code Geeks с разрешения Егора Бугаенко, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Проверка объекта: отложить или нет?
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |