Конструктор Perl должен возвратить undef или “недопустимый” объект?

Я вполне уверен, что это не возможно. Однако я также не уверен, нужен ли Вам целый комплект разработчика для устанавливания инструментов разработчика. Довольно много инструментов установлены наряду с XCode, который мог бы быть дополнительным. Однако я думаю, что Вы являетесь неудачливыми для того, чтобы не должный быть стиснуть зубы и использовать wget или DownThemAll или некоторый другой менеджер загрузок, который позволит Вам медленно загрузить инструменты разработчика в блоках.

Каждый раз, когда я устанавливаю OS X, я устанавливаю инструменты разработчика, как правило, просто потому что он открывает мир доступного программного обеспечения чрезвычайно. Возможно, необходимо рассмотреть выполнение этого в будущем также.

10
задан brian d foy 30 September 2009 в 17:07
поделиться

4 ответа

Это зависит от того, какие конструкторы вы хотите использовать.

Остальная часть этого ответа основана на моих личных наблюдениях, но, как и в случае с большинством вещей Perl, Best Practices действительно сводится к «Вот один способ сделать это, который вы можете использовать или оставить в зависимости от ваших потребностей». Ваши предпочтения в том виде, в каком вы их описали, полностью обоснованы и последовательны, и никто не должен вам говорить иначе.

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

С другой стороны, если вы предпочитаете, чтобы этого не происходило, я бы предпочел 2 вместо 1, потому что проверить наличие неопределенного объекта так же легко, как проверить наличие некоторой переменной-флага. Это не C, поэтому у нас нет строгого ограничения типа, говорящего нам, что наш конструктор ДОЛЖЕН возвращать объект этого типа. Так что возвращение undef и проверка на него для установления успеха или неудачи - отличный выбор.

«Накладные расходы» на сбой конструкции учитываются в определенных крайних случаях (когда вы не можете быстро потерпеть неудачу прежде чем нести накладные расходы), так что для тех, кто может предпочесть метод 1. Опять же, это зависит от того, какую семантику вы определили для построения объекта. Например, я предпочитаю выполнять тяжелую инициализацию вне конструирования. Что касается стандартизации, я думаю, что проверка того, возвращает ли конструктор определенный объект, является таким же хорошим стандартом, как и проверка флаговой переменной.

РЕДАКТИРОВАТЬ: В ответ на ваше изменение об инициализаторах, отклоняющих случай № 2, я не понять, почему инициализатор не может просто вернуть значение, указывающее на успех или неудачу, вместо того, чтобы установить переменную-флаг. На самом деле вы можете использовать оба, в зависимости от того, сколько подробностей вы хотите о произошедшей ошибке. Но было бы вполне допустимо, чтобы инициализатор возвращал истину в случае успеха и undef в случае неудачи.

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

РЕДАКТИРОВАТЬ: В ответ на ваше изменение об инициализаторах, отклоняющих случай № 2, я не понять, почему инициализатор не может просто вернуть значение, указывающее на успех или неудачу, вместо того, чтобы установить переменную-флаг. На самом деле вы можете использовать оба, в зависимости от того, сколько подробностей вы хотите о произошедшей ошибке. Но было бы вполне допустимо, чтобы инициализатор возвращал истину в случае успеха и undef в случае неудачи.

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

РЕДАКТИРОВАТЬ: В ответ на ваше изменение об инициализаторах, отклоняющих случай № 2, я не понять, почему инициализатор не может просто вернуть значение, указывающее на успех или неудачу, вместо того, чтобы установить переменную-флаг. Фактически, вы можете захотеть использовать оба, в зависимости от того, сколько подробностей вы хотите о произошедшей ошибке. Но было бы вполне допустимо, чтобы инициализатор возвращал истину в случае успеха и undef в случае неудачи.

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

РЕДАКТИРОВАТЬ: В ответ на ваше изменение об инициализаторах, отклоняющих случай № 2, я не понимаю, почему инициализатор не может просто вернуть значение, указывающее на успех или неудачу, а не устанавливать переменную-флаг. На самом деле вы можете использовать оба, в зависимости от того, сколько подробностей вы хотите о произошедшей ошибке. Но было бы вполне допустимо, чтобы инициализатор возвращал истину в случае успеха и undef в случае неудачи.

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

РЕДАКТИРОВАТЬ: В ответ на ваше изменение об инициализаторах, отклоняющих случай № 2, я не понимаю, почему инициализатор не может просто вернуть значение, указывающее на успех или неудачу, а не устанавливать переменную-флаг. Фактически, вы можете захотеть использовать оба, в зависимости от того, сколько подробностей вы хотите о произошедшей ошибке. Но было бы вполне допустимо, чтобы инициализатор возвращал истину в случае успеха и undef в случае неудачи.

t просто возвращает значение, указывающее на успех или неудачу, а не устанавливает флаговую переменную. На самом деле вы можете использовать оба, в зависимости от того, сколько подробностей вы хотите о произошедшей ошибке. Но было бы вполне допустимо, чтобы инициализатор возвращал истину в случае успеха и undef в случае неудачи.

t просто возвращает значение, указывающее на успех или неудачу, а не устанавливает флаговую переменную. Фактически, вы можете захотеть использовать оба, в зависимости от того, сколько подробностей вы хотите о произошедшей ошибке. Но было бы вполне допустимо, чтобы инициализатор возвращал истину в случае успеха и undef в случае неудачи.

7
ответ дан 3 December 2019 в 20:05
поделиться

Я предпочитаю:

  1. Делайте как можно меньше инициализаций в конструкторе.
  2. квакать с информативным сообщением, когда что-то пойдет не так.
  3. Используйте соответствующие методы инициализации, чтобы предоставлять сообщения об ошибках для каждого объекта и т. д.

Кроме того, возврат undef (вместо кваканья) - это нормально, если пользователям класса может быть все равно, почему именно произошла ошибка, только если они получили действительный объект или нет.

Я презираю методы, которые легко забыть is_valid или добавить дополнительные проверки, чтобы гарантировать, что методы не вызываются, когда внутреннее состояние объекта не определено должным образом.

Я говорю это из очень субъективная точка зрения без каких-либо заявлений о передовой практике.

5
ответ дан 3 December 2019 в 20:05
поделиться

Я бы не рекомендовал №1 просто потому, что это приводит к большему количеству кода обработки ошибок, который не будет написан. Например, если вы просто вернете false, это нормально.

my $obj = Class->new or die "Construction failed...";

Но если вы вернете объект, который недействителен ...

my $obj = Class->new;
die "Construction failed @{[ $obj->error_message ]}" if $obj->is_valid;

И по мере увеличения количества кода обработки ошибок вероятность его записи уменьшается. И это не линейно. Увеличивая сложность вашей системы обработки ошибок, вы фактически уменьшаете количество ошибок, которые она выявляет при практическом использовании.

Вы также должны быть осторожны, чтобы ваш недопустимый объект, о котором идет речь, умирает при вызове любого метода (кроме is_valid и error_message ), что приводит к еще большему количеству кода и возможностям для ошибок.

Но я согласен, что возможность получить информацию об ошибке имеет ценность, что делает возврат false (просто return , а не return undef ) хуже. Традиционно это делается путем вызова метода класса или глобальной переменной, как в DBI.

мой $ dbh = DBI-> connect ($ data_source, $ username, $ password) или die $ DBI :: errstr;

Но он страдает от A) вам все равно нужно писать код обработки ошибок и B) его допустимо только для последней операции.

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

my $obj = Class->new;

Традиционные рекомендации Perl о том, чтобы исключать исключения в коде библиотеки как невежливые, устарели. Программисты Perl (наконец) принимают исключения.Вместо того, чтобы писать код обработки ошибок снова и снова, сильно и часто забывая, исключения DWIM. Если вы не уверены, просто начните использовать autodie ( посмотрите видео pjf об этом ), и вы никогда не вернетесь назад.

Исключения согласовывают кодировку Хаффмана с фактическим использованием. Обычный случай, когда конструктор ожидает, что он просто работает, и желает получить ошибку, если это не так, теперь является наименьшим количеством кода. Редкий случай, когда требуется обработать эту ошибку, требует написания специального кода. И специальный код довольно маленький.

my $obj = eval { Class->new } or do { something else };

Если вы обнаруживаете, что оборачиваете каждый вызов в eval , вы делаете это неправильно. Исключения называются так потому, что они исключительны. Если, как в вашем комментарии выше, вам нужна изящная обработка ошибок ради пользователя, воспользуйтесь тем фактом, что ошибки всплывают в стеке. Например, если вы хотите предоставить красивую страницу с ошибкой пользователя, а также зарегистрировать ошибку, вы можете сделать это:

eval {
    run_the_main_web_code();
} or do {
    log_the_error($@);
    print_the_pretty_error_page;
};

Она вам нужна только в одном месте, наверху стека вызовов, а не разбросана повсюду. Вы можете воспользоваться этим с меньшими приращениями, например ...

my $users = eval { Users->search({ name => $name }) } or do {
    ...handle an error while finding a user...
};

Происходят две вещи. 1) Пользователи-> поиск всегда возвращает истинное значение, в данном случае массив ref. Это делает простой my $ obj = eval {Class-> method} or do работой. Это необязательно. Но что более важно 2) вам нужно только добавить специальную обработку ошибок для Users-> search . Все методы, вызываемые внутри Users-> search , и все методы, которые они вызывают ... они просто генерируют исключения.И все они пойманы в один момент и обработаны одинаково. Обработка исключения в точке, которая заботится о нем, делает код обработки ошибок намного более аккуратным, компактным и гибким.

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

my $obj = eval { Class->new }
  or die "Construction failed: $@ and there were @{[ $@->num_frobnitz ]} frobnitzes";

Исключения:

  • Делайте правильные вещи, не задумываясь вызывающим
  • Требовать наименьшее количество кода для наиболее распространенного случая
  • Предоставлять наибольшую гибкость и информацию о сбое для вызывающего

Модули, такие как as Try :: Tiny исправляет большинство проблем с зависанием, связанных с использованием eval в качестве обработчика исключений.

Что касается вашего варианта использования, когда у вас может быть очень дорогой объект и вы хотите попробовать и продолжить его частичное построение ... для меня это пахнет ЯГНИ. Вам это действительно нужно? Или у вас раздутый объектный дизайн, который слишком рано выполняет слишком много работы. Если вам это действительно нужно, вы можете поместить информацию, необходимую для продолжения построения, в объект исключения.

5
ответ дан 3 December 2019 в 20:05
поделиться

Сначала напыщенные общие замечания:

  1. Работа конструктора должна заключаться в следующем: Учитывая допустимые параметры конструкции, вернуть допустимый объект.
  2. Конструктор, который не создает корректный объект, не может выполнить свою работу и поэтому является идеальным кандидатом для генерации исключений.
  3. Убедиться в том, что созданный объект является допустимым, является частью работы конструктора. Передача заведомо плохого объекта и полагаться на то, что клиент проверит, что объект действителен - верный способ получить недействительные объекты, которые взрываются в отдаленных местах по неочевидным причинам.
  4. Проверка того, что все правильные аргументы находятся на месте до вызова конструктора является работой клиента.
  5. Исключения обеспечивают тонкий способ распространения конкретной ошибки, которая произошла, без необходимости иметь в руках сломанный объект.
  6. return undef; всегда плохо[1]
  7. bIlujDI' yIchegh()Qo'; yIHegh()!

Теперь к собственно вопросу, который я буду понимать как "что вы, darch, считаете лучшей практикой и почему". Во-первых, отмечу, что возврат ложного значения при неудаче имеет долгую историю Perl (например, большая часть ядра работает именно так), и многие модули следуют этому соглашению. Однако оказалось, что это соглашение порождает некачественный клиентский код, и все новые модули от него отказываются.[2]

[Подтверждающий аргумент и примеры кода для этого оказываются более общим случаем для исключений, который побудил создать autodie, и поэтому я не буду поддаваться искушению привести этот пример здесь. Вместо этого:]

Необходимость проверки успешного создания на самом деле более обременительна, чем проверка наличия исключения на соответствующем уровне обработки исключений. Другие решения требуют от непосредственного клиента проделать больше работы, чем нужно, чтобы получить объект, работы, которая не требуется, когда конструктор не работает, выбрасывая исключение.[3] Исключение гораздо более выразительно, чем undef, и не менее выразительно, чем передача обратно разрушенного объекта для целей документирования ошибок и аннотирования их на различных уровнях в стеке вызовов.

Вы даже можете получить частично построенный объект, если передадите его обратно в исключении. Я думаю, что это плохая практика в соответствии с моим представлением о том, каким должен быть контракт конструктора со своими клиентами, но такое поведение поддерживается. Неловко.

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

[1]: Под этим я подразумеваю, что мне неизвестны случаи использования, когда return; не является строго превосходным. Если кто-то обратится ко мне по этому поводу, мне, возможно, придется открыть вопрос. Так что, пожалуйста, не надо. ;)
[2]: Согласно моему крайне ненаучному воспоминанию о модульных интерфейсах, которые я прочитал за последние два года, подверженному как отбору, так и предубеждениям подтверждения.
[3]: Обратите внимание, что выброс исключения все равно требует обработки ошибок, как и другие предложенные решения. Это не означает обертывание каждого инстанса в eval, если вы не хотите делать сложную обработку ошибок вокруг каждой конструкции (а если вы думаете, что хотите, то вы, вероятно, ошибаетесь). Это означает обернуть вызов, который способен осмысленно действовать на исключение, в eval.

2
ответ дан 3 December 2019 в 20:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: