Оценка успеха/неудачи подпрограммы

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

Например, предположим, что у меня есть следующий код (X::Argument::BadFormat — это обработчик исключений, производный от Exception::Class):

    package My::Module;

    use strict;
    use warnings;

    sub new{#does things unrelated to the current question}

    sub my_sub {
        my ($self,$possible_value) = @_;

        if ($possible_value =~ q{\w}) { #Affect value to current object
            $self->{field} = $possible_value;
        }else{ #throw an exception
            X::Argument::BadFormat->throw(
                arg             => 'possible_value',
                expected_format => 'something that looks like a word',
                received_value  => $possible_value,
            );
        }
    }

В тестовом файле я буду запускать такие тесты, как:

my $object = My::Module->new();
throws_ok(sub {$object->my_sub('*')}, 'X::Argument::BadFormat', 'Faulty value will raise an exception');
ok($object->my_sub('turlututu'));

Легко проверить, когда:

  • sub возвращает значение,
  • условия теста должны вызывать исключение,

Однако, когда я просто устанавливаю значение поля в текущем объекте, у меня нет причин что-либо возвращать.

В таком случае:

  • достаточно ли простого выполнения кода, чтобы оценить дополнительный вывод как «истинный»?
  • Должен ли я добавить явное «возврат 1;» ?
  • действительно ли sub возвращает последнюю оценку, в этом случае успех
  • тест в "если"? Что-то еще, о чем я не подумал, но что очевидно для всех?

person David Verdin    schedule 15.12.2016    source источник
comment
почему бы просто не проверить правильность установки атрибута объекта через attr напрямую или лучше через геттер? например: my $p='blah'; $obj->my_sub($p); is $obj->{field}, $p, "my_sub() set the field attr ok";   -  person stevieb    schedule 15.12.2016
comment
Конечно. Я понимаю, что должен был сформулировать свой вопрос по-другому. Реальная проблема заключается в том, чтобы иметь возможность оценить, правильно ли завершилась подпрограмма, не только для аффектов полей, но и всякий раз, когда подпрограмма не должна возвращать значение. Так понятнее?   -  person David Verdin    schedule 15.12.2016
comment
Ой. читая вопрос, я понимаю, что нет такого понятия, как общий ответ, не так ли? Мне нужно явно указывать возвращаемое значение или проверять действия, которые он должен выполнять с моим объектом.   -  person David Verdin    schedule 15.12.2016
comment
Это правильно.   -  person stevieb    schedule 15.12.2016
comment
Отдельно следует отметить, что этот тест, вероятно, должен быть $possible_value =~ /\A\w+\z/, чтобы шаблон был регулярным выражением, а не строкой в ​​одинарных кавычках, и он проверял, что вся строка состоит из словесных символов. То, что у вас было, будет проверять только то, что оно содержит хотя бы один символ слова.   -  person Borodin    schedule 15.12.2016
comment
Конечно. Это был просто быстрый пример, но тем не менее вы правы!   -  person David Verdin    schedule 15.12.2016


Ответы (3)


Sub, которому не нужно возвращать значение, должен заканчиваться

return;

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

Предполагая, что вы добавите явный возврат:

Ваш throws_ok тест выглядит нормально. Затем вы должны проверить, правильно ли установлено поле. Ваш тест ok не нужен, так как ваша подпрограмма ничего не вернет.

person ysth    schedule 15.12.2016
comment
Однако с оговоркой — иногда $possible_value может быть оценено как «истина», а иногда — как ложь. Я не уверен, что это хорошо, и предпочел бы явный возврат. - person Sobrique; 15.12.2016
comment
@Sobrique Я предпочитаю всегда делать явный возврат - person ysth; 15.12.2016
comment
Спасибо. Просто чтобы быть ясным - и извините за сумбурность: если я добавлю явный возврат в конце, будет ли тест ok () оцениваться как истинный или мне все равно не следует использовать этот тест? - person David Verdin; 15.12.2016
comment
@David Если у вас есть return;, он вернет undef. У вас должен быть return;, чтобы вы знали, что вы возвращаете, когда возвращаемое значение не требуется. Это дает вам стабильный интерфейс. Но return не является истинным значением, поэтому ok foo() не удастся. Как сказал ysth, вы можете избавиться от этого конкретного теста, потому что все, что он делает, это проверяет, является ли возвращаемое значение истинным значением, а это бесполезный тест. - person simbabque; 15.12.2016
comment
Вместо этого @DavidVerdin, поскольку вы, по сути, пишете метод установки, вам нужен тест, который проверяет, правильно ли установлено значение. Вы бы сделали это, позвонив геттеру. Если есть $object->field, вы можете сделать is $object->field, q{turlututu}, q{field got set correctly};. Вы также можете получить доступ к базовому hashref, но я бы не рекомендовал этого делать. Также может иметь смысл добавить еще один тест, который проверяет, что field не было установлено, если выдается исключение. Но это, по сути, то, что сказал Стивиб. Вы смотрели на Лося? - person simbabque; 15.12.2016
comment
@David иногда сеттеры возвращают старое значение, которое перезаписывается. Это тоже может быть хорошей идеей. - person simbabque; 15.12.2016
comment
Спасибо. Да, я смотрел на лося, и это выглядело чертовски круто. Однако я слышал двух критических замечаний по поводу лося: 1- для него требуется треть CPAN и 2- он неудобен для производительности. По-вашему, справедливы ли эти критические замечания? - person David Verdin; 15.12.2016
comment
Давайте продолжим обсуждение в чате. - person David Verdin; 15.12.2016
comment
чтобы было ясно, return; ничего не возвращает в контексте списка и undef в скалярном контексте. - person ysth; 15.12.2016
comment
Ни в коем случае, отсутствие return не проблема. Проблема в том, как называется саб. - person ikegami; 15.12.2016
comment
@ikegami не уверен, с кем ты разговариваешь или что ты хочешь сказать. Вы можете объяснить? - person ysth; 15.12.2016
comment
@ysth, вы сказали, что подпрограмма, которой не нужно возвращать значение, должна заканчиваться на return;. Для этого нет причин (кроме документации). Подпрограмма, не имеющая возвращаемого значения, не должна проверять возвращаемое значение, поэтому добавление return; не имеет значения. Ошибка здесь не в отсутствии return, а в выполнении ok(my_sub()). Обратите внимание, что ok(my_sub()) по-прежнему не работает с return;. - person ikegami; 15.12.2016
comment
@ikegami: я верю в программирование с осторожностью. Подпрограмма должна защищаться от тех, кто пытается использовать возвращаемое значение, делая его чем-то предсказуемым. И ошибки как таковой в исходном коде не было, так как саб вернул бы turlututu. ИМО правильное исправление добавляет явный возврат и удаление ok. - person ysth; 16.12.2016
comment
Добавление return; не является защитным кодом, поскольку undef так же бессмысленно, как и любое другое значение. Возможно вы были бы правы, если бы порекомендовали добавить return 1;, истинное значение, указывающее, что подпрограмма прошла успешно. - person ikegami; 16.12.2016
comment
добавление возврата; предотвращает непредсказуемость возврата (для тех немногих, кто использует возвращаемое значение). установка возвращаемого значения в 1 только для тех немногих нежелательных глупо и побеждает самодокументацию return; в конце, показывающую, что подпрограмма не имеет возвращаемых значений. - person ysth; 16.12.2016

В этом случае я бы просто проверил правильность установки атрибута объекта. Это все, что делает этот конкретный саб. Если он настроен нормально, саб завершился правильно. Если он не был установлен, что-то пошло не так, прежде чем саб закончился.

my $p='blah'; 
$obj->my_sub($p); 

is $obj->{field}, $p, "my_sub() set the field attr ok";

Было бы лучше, если бы у атрибута field был геттер, чтобы вы не нарушали инкапсуляцию, но я отвлекся.

person stevieb    schedule 15.12.2016
comment
Спасибо. Я позабочусь, чтобы убедиться, что объект был правильно изменен, и перестану задаваться вопросом, существует ли магический общий метод оценки. - person David Verdin; 15.12.2016

Perl по умолчанию возвращает результат последнего выполненного кода.

Например:

print main();

sub main {
    my $var = 9 * 7;
}

print выведет 63. Если на ваш код может повлиять вывод данной подпрограммы, вам необходимо установить возвращаемое значение (обычно считается лучшей практикой всегда устанавливать явный возврат в конце подпрограммы/метода).

print main();

sub main {
    my $var = 9 * 7;
    return;
}

print ничего не выведет.

Лично я всегда стараюсь установить возвращаемое значение в зависимости от контекста, к которому будет возвращаться подпрограмма, но если вы пишете код, который будут использовать другие люди, то, как правило, безопаснее просто сделать return;.

Дополнительное пояснение от Perl::Critic (ссылка на конкретную политику):

Подпрограмма «main» не заканчивается «return» в строке 8, рядом с «sub main {».

Подпрограммы::RequireFinalReturn (серьезность: 4)

Требовать, чтобы все подпрограммы завершались явно одним из следующих действий: return',carp', croak',die', exec',exit', goto', or throw'.

Подпрограммы без явных операторов возврата в конце могут сбивать с толку. Может быть сложно определить, каким будет возвращаемое значение.

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

   package Password;
    # every time the user guesses the password wrong, its value
    # is rotated by one character
    my $password;
    sub set_password {
        $password = shift;
    }
    sub check_password {
        my $guess = shift;
        if ($guess eq $password) {
            unlock_secrets();
        } else {
            $password = (substr $password, 1).(substr $password, 0, 1);
        }
    }
    1;

В этом случае последний оператор в check_password() является присваиванием. Результатом этого присваивания является неявное возвращаемое значение, поэтому неправильное угадывание возвращает правильный пароль! Добавление «возврата»; в конце этой подпрограммы решает проблему.

Единственным допустимым исключением является пустая подпрограмма.

Будьте осторожны при устранении проблем, указанных в настоящей Политике; не ставьте вслепую «возврат»; оператор в конце каждой подпрограммы.

person interduo    schedule 15.12.2016
comment
Вы должны включить ссылку на цитату Perl::Critic. - person simbabque; 15.12.2016
comment
Я только что запустил perlcritic -1 --verbose 11 из своей командной строки, но вот страница cpan для модуля, из которого он взят: Perl::Critic::Policy::Subroutines::RequireFinalReturn - person interduo; 15.12.2016