Действительно ли это - способ пойти о создании подпрограмм Perl?

Цель не является 100%-м покрытием кода, ни является им 80%-е покрытие кода. Модульный тест, являющийся легким записать, не означает, что необходимо записать его, и, модульные тесты, являющиеся твердым записать, не означают, что необходимо избежать усилия.

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

общая стоимость авторской разработки, поддержания и диагностирования проблем, отмеченных тестом (включая ложные положительные стороны) стоящий проблем, которые ловит определенный тест?

, Если проблема тест ловит, является 'дорогим' тогда, можно позволить себе приложить усилия к выяснению, как протестировать его, и поддержав тот тест. Если проблема, которую ловит тест, тривиальна тогда запись (и поддержание!) тест (даже в присутствии изменений кода) лучше быть тривиальным.

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

5
задан Adam Lear 16 December 2011 в 06:20
поделиться

10 ответов

In one word: NO!

Let's look at:

sub ucwords{
    $str = @_[0];
    $str = lc($str);
    $str =~ s/\b(\w)/\u$1/g;
    return $str;
}

First and foremost, you are not using strict. Use it. It is for your own good.

Second, you are not using warnings. Use it. It is for your own good. For example, the first element of @_ should be referred to using $_[0] and not @_[0].

Third, you should get in the habit of reading the FAQ list occasionally before re-inventing the wheel again: See How do I capitalize all the words on one line?

If you think this is harsh, consider the fact that when called as:

print ucwords("FRED AND BARNEY'S LODGE"), "\n";

your code outputs

Fred And Barney'S Lodge

which is the example given in that question.

Further, having a function that does more than one thing, chooses what it does on the basis of mystery numbers and does none of those things right is not a good design strategy.

You should instead have multiple functions, named in a way that can be understood by a casual reader of your code, each of which does only one thing and does that right.

Finally, the extended version of your function (without saying anything about the wisdom of writing such a function) can be better written as:

# untested code follows

use Carp;

{
    my %modes = map {$_ => undef} 0 .. 3;
    sub ucwords{
        croak 'No arguments passed' unless @_;

        my ($str, $mode) = @_;
        $mode = 0 unless defined $mode;

        croak "Invalid mode: '$mode'" unless exists $modes{$mode};

        if ( $mode == 0 ) {
            $str = lc($str);
            $str =~ s/\b(\w)/\u$1/g;
        }
        elsif ( $mode == 1 ) {
            $str =~ s/\b(\w)/\u$1/g;        
        }
        elsif ( $mode == 2 ) {
            $str = lc($str); 
            $str =~ s/(\w)\b/\u$1/g;        
        }
        else {
            $str =~ s/(\w)\b/\u$1/g;
        }

        return $str;
    }
}

See also Why use if-else if in C++?

25
ответ дан 18 December 2019 в 05:11
поделиться

Я категорически не люблю слишком умные функции. Чрезмерно умная функция - это функция, поведение которой полностью изменяется из-за ее параметров. Посмотрите на свой, они почти не разделяют никакого кода, кроме обработки параметров. В любом случае, если бы я сделал что-то подобное, я бы написал примерно следующее:

use Carp;

{
    my %ucwords = (
        0 => sub {
            my $str = lc( shift() );
            $str =~ s/\b(\w)/\u$1/g;
            return $str;
        },
        1 => sub {
            my $str = shift;
            $str =~ s/\b(\w)/\u$1/g;
            return $str;
        },
        2 => sub {
            $str = lc( shift() );
            $str =~ s/(\w)\b/\u$1/g;
            return $str;
        },
        3 => sub {
            my $str = shift;
            $str =~ s/(\w)\b/\u$1/g;
            return $str;
        }
    );

    sub ucwords {
        my ( $str, $mode ) = @_;
        croak "No overload method of ucwords() takes no arguments"
            unless defined $str;
        $mode = 0 unless defined $mode;
        my $code = $ucwords{$mode};
        croak "Invalid mode: '$mode'" unless defined $code;
        goto \&$code;
    }
}
2
ответ дан 18 December 2019 в 05:11
поделиться

Что-то, на что намекали, но прямо не упоминалось в других ответах, - это использование числовых режимов, соглашение, чуждое Perl, перенесенное из C. Быстрый, не глядя на код, что делает режим №3? Черт возьми, глядя на код, что делает режим №3?

Perl имеет эффективные и простые в использовании строки. Используй их. Дайте вашим режимам имена, которые имеют какое-то отношение к тому, что он делает. Что-то вроде ... first, last, recase_first, recase_last. Они не обязательно должны быть полностью описательными, lower_case_then_uc_last_letter будет слишком длинным для ввода, но достаточным для того, чтобы человеческий мозг мог зацепиться за него и связать его.

Но на самом деле это четыре подпрограммы. Флаги режима - это красный флаг, особенно когда большая часть вашего кода заключена внутри оператора if / else.

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

Но на самом деле это четыре подпрограммы. Флаги режима - это красный флаг, особенно когда большая часть вашего кода заключена внутри оператора if / else.

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

Но на самом деле это четыре подпрограммы. Флаги режима - это красный флаг, особенно когда большая часть вашего кода заключена внутри оператора if / else.

2
ответ дан 18 December 2019 в 05:11
поделиться

@_ Распаковка

Как правило, вы всегда хотите распаковать @_ перед выполнением любой другой обработки в вашей подпрограмме. Это делает гораздо более понятным пользователям, другим сопровождающим и вам в будущем, как использовать вашу подпрограмму. Используя напрямую @_ , очень трудно понять, что нужно передать, только на основе приведенных аргументов. У них нет никаких значимых имен, что еще больше усложняет определение их назначения, и у вас есть магические константы повсюду - обычно в целом это плохо!

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

Для подпрограмм с одним аргументом обычным решением является использование shift . Это снимает первый элемент массива, и возвращает его (что-то вроде противоположности pop .) Если не задан массив, а вы находитесь в подпрограмме, он извлекает его из массива @_. Итак, вы можете выполнить

sub mysub {
    my $foo = shift;
}

для любой подпрограммы с одним аргументом.

Но что, если у вас их больше? Назначение контекста списка, на помощь! Можно назначить сразу несколько переменных, используя назначение списка. Вы можете сделать

sub myothersub {
   my ($foo, $bar, $baz) = @_;
}

. И $ foo , $ bar и $ baz будут присвоены значения в индексах 0, 1 и 2 @ _ соответственно. Что произойдет, если в индексах 0, 1 или 2 ничего нет? Их все еще назначают - они становятся undef ! Затем вы можете проверить undef, как упоминалось в другом месте этого вопроса.

вы можете выполнить

sub mysub {
    my $foo = shift;
}

для любой подпрограммы с одним аргументом.

Но что, если у вас их больше? Назначение контекста списка, на помощь! Можно назначить сразу несколько переменных, используя назначение списка. Вы можете сделать

sub myothersub {
   my ($foo, $bar, $baz) = @_;
}

. И $ foo , $ bar и $ baz будут присвоены значения в индексах 0, 1 и 2 @ _ соответственно. Что произойдет, если в индексах 0, 1 или 2 ничего нет? Их по-прежнему назначают - они становятся undef ! Затем вы можете проверить undef, как упоминалось в другом месте этого вопроса.

вы можете выполнить

sub mysub {
    my $foo = shift;
}

для любой подпрограммы с одним аргументом.

Но что, если у вас их больше? Назначение контекста списка, на помощь! Можно назначить сразу несколько переменных, используя назначение списка. Вы можете сделать

sub myothersub {
   my ($foo, $bar, $baz) = @_;
}

. И $ foo , $ bar и $ baz будут присвоены значения в индексах 0, 1 и 2 @ _ соответственно. Что произойдет, если в индексах 0, 1 или 2 ничего нет? Их все еще назначают - они становятся undef ! Затем вы можете проверить undef, как указано в другом месте этого вопроса.

и $ baz будут присвоены значения в индексах 0, 1 и 2 @_ соответственно. Что произойдет, если в индексах 0, 1 или 2 ничего нет? Их по-прежнему назначают - они становятся undef ! Затем вы можете проверить undef, как упоминалось в другом месте этого вопроса.

и $ baz будут присвоены значения в индексах 0, 1 и 2 @_ соответственно. Что произойдет, если в индексах 0, 1 или 2 ничего нет? Их по-прежнему назначают - они становятся undef ! Затем вы можете проверить undef, как упоминалось в другом месте этого вопроса.

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

Don't use the $foo ne undef construct. Operators in Perl are what's known as "context sensitive". By using certain operators, you introduce certain contexts. ne, eq, lt, gt, le, ge are all "string" operators, treating the scalars on either side as strings, whereas ==, !=, <, >, <=, >= are numeric operators, treating the object on either side as a number.

However, if you're testing for undef, it really doesn't make sense that something undefined is a number or a string, so they have an operator just for that kind of test: defined

You can test if something is defined simply by doing

if (defined $foo) {
    # my cool logic on $foo here
}
11
ответ дан 18 December 2019 в 05:11
поделиться

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

use Carp;

sub ucwords {
    my $str = shift;
    defined($str) 
        or croak 'No overload method of ucwords() takes no arguments';
    #the rest of your logic
}
5
ответ дан 18 December 2019 в 05:11
поделиться

Может быть, вы найдете Params :: Validate полезным. Его можно использовать для проверки параметров по различным правилам. Вот как это может выглядеть в вашем случае:

## somewhere is the start of the module
use Params::Validate qw(:all);

sub ucwords {
    ## this line helps to undestand which parameter should be passed to function
    my ($string, $algorithm_id) = @_; 

    ## make sure that 2 scalar parameters passed
    validate_pos(@_, {'type' => SCALAR}, {'type' => SCALAR});

    ## main code here
}
4
ответ дан 18 December 2019 в 05:11
поделиться

Индексирование массива

Доступ к массиву, как и другие вещи в Perl, зависит от контекста. Думайте о сигиле, которая прикрепляется к имени, как о «напоминании» вам о том, что вы пытаетесь получить или использовать в данный момент. Каждый раз, когда вы видите $ , это означает, что вы пытаетесь получить одно скалярное значение. Всякий раз, когда вы видите @ , это означает, что вы обращаетесь к списку, а % , конечно, означает хэш-пару ключ / значение. Итак, когда вы обращаетесь к своему массиву следующим образом:

@_[1]

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

$_[1]
3
ответ дан 18 December 2019 в 05:11
поделиться

die

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

croak

Do:

use Carp;

, а затем

croak "My error here!";

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

4
ответ дан 18 December 2019 в 05:11
поделиться

Оператор переключения Perl: given / when

Perl, начиная с версии 5.10 и выше, имеет фантастический встроенный оператор переключения, называемый [ дано ]. Это примерно эквивалентно оператору switch в C, но гораздо более универсально. Чтобы включить эту функцию, вам нужно добавить строку вверху вашего скрипта:

use 5.010;

Это включает все функции perl 5.10, включая switch (и , скажем, , который работает как print ], но автоматически добавляет "\ n" в конце.) Вы можете использовать это так:

my $foo = get_foo();
my $nothing = 0;
given($foo) {
    when (undef)  { say "got an undefined value!"; }
    when ([1,3,5,6,8]) { say "was 1, 3, 5, 6, or 8"; }
    when (/^abc/) { say "was a string starting with abc"; }
    when ($_ == 4) { say "It was 4!!!"; }
    when ($_ > 100) { say "Greater than 100"; }
    default { $nothing = 1; }
}

Переменная, переданная в given, автоматически помещается в $ _ внутри данного кода, что позволяет сравнивать против этого. Затем конструкция when выполняет интеллектуальное сопоставление с $ _. Итак, в вашем случае это будет выглядеть так (исправление проблемы с @ [] до $ [] ):

given ($_[1]) {
    when (1) { $str =~ s/\b(\w)/\u$1/g }
    when (2) { $str = lc($str); $str =~ s/(\w)\b/\u$1/g }
    when (3) { $str =~ s/(\w)\b/\u$1/g; }
    default { croak "No overloaded method of ucwords() takes '$_'." }
}
5
ответ дан 18 December 2019 в 05:11
поделиться
Другие вопросы по тегам:

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