【问题标题】:Is this the way to go about building Perl subroutines?这是构建 Perl 子例程的方法吗?
【发布时间】:2010-12-07 02:05:14
【问题描述】:

所以我有一个简单的 Perl ucwords 函数,我已经有一段时间了,并且想扩展它,这就是我想出的,这是我应该构建我的函数来处理的方式可选参数?

原文:

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

扩展:

sub ucwords{
    if(@_[0] ne undef){#make sure some argument was passed
        @overloads = (0,1,2,3);
        $str = @_[0];
        if(@_[1] eq undef || @_[1] eq 0){ #default is to lowercase all but first
            $str = lc($str);
            $str =~ s/\b(\w)/\u$1/g;
            return $str;
        }else{ #second parameters
            if(!grep $_ eq @_[1], @overloads){ die("No overload method of ucwords() takes ".@_[1]." as second parameter."); }
            if(@_[1] eq 1){ $str =~ s/\b(\w)/\u$1/g;} #first letter to upper, remaining case maintained
            if(@_[1] eq 2){ $str = lc($str); $str =~ s/(\w)\b/\u$1/g;} #last letter to upper, remaining to lower
            if(@_[1] eq 3){ $str =~ s/(\w)\b/\u$1/g;} #last letter to upper, remaining case maintained
            return $str;
        }
    }else{
        die("No overload method of ucwords() takes no arguments");
    }
}

心理

标签: perl function parameters user-defined-functions


【解决方案1】:

一句话:不!

我们来看看:

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

首先,您没有使用strict。用它。这是为了你好。

其次,你没有使用warnings。用它。这是为了你自己好。例如,@_ 的第一个元素应该使用 $_[0]not @_[0] 来引用。

第三,在重新发明轮子之前,你应该养成偶尔阅读常见问题列表的习惯:见How do I capitalize all the words on one line?

如果您认为这很苛刻,请考虑以下事实:当被称为:

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

你的代码输出

弗雷德和巴尼的小屋

这是该问题中给出的示例。

此外,拥有一个功能不止一件事,根据神秘数字选择它所做的事情并且没有做对这些事情,这不是一个好的设计策略。

相反,您应该拥有多个函数,并以普通读者可以理解的方式命名,每个函数只做一件事,而且做得对。

最后,你的函数的扩展版本(不用说编写这样一个函数的智慧)可以更好地写成:

# 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;
    }
}

另见Why use if-else if in C++?

【讨论】:

  • 我认为在这种情况下你应该回答两次。一次使用您的原始答案,一次使用当前版本。在那种情况下,你会赢得我的两次赞成票。
  • 我同意这应该是另一个答案,但我不知道我是否会赞成第一个答案,因为只是说使用严格和警告并没有真正指出其他问题功能。无论哪种方式,这个+1。 :)
  • 应该不用说...但是作为在我的工作中维护脚本的人,我要说...如果您要使用上面的扩展功能之类的东西-文档它。命名你的模式,如果只在 cmets 中。
  • 另外,如果没有人阅读我提到的常见问题解答条目,请使用 Text::Autoformat search.cpan.org/perldoc/Text::Autoformat 来满足此和其他格式需求。
  • @Robert P:我回滚了你的更改。 3 是有效模式。
【解决方案2】:

也许你会发现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
}

【讨论】:

    【解决方案3】:

    不要使用$foo ne undef 构造。 Perl 中的运算符是所谓的“上下文敏感”。通过使用某些运算符,您可以引入某些上下文。 neeqltgtlege 都是“字符串”运算符,将两边的标量视为字符串,而 ==!=、@987654331 @、><=>= 是数字运算符,将两边的对象视为数字。

    但是,如果您正在测试 undef,那么未定义的东西是数字或字符串真的没有意义,所以他们有一个专门用于这种测试的运算符:defined

    你可以通过做来测试某些东西是否被简单地定义了

    if (defined $foo) {
        # my cool logic on $foo here
    }
    

    【讨论】:

      【解决方案4】:

      这可能只是我的意见,您的编码风格完全取决于您,但我个人发现立即将参数分配给变量而不是包装您的“业务”部分很有价值if 块中的子程序,在此之前我会使用 croak 函数。例如:

      use Carp;
      
      sub ucwords {
          my $str = shift;
          defined($str) 
              or croak 'No overload method of ucwords() takes no arguments';
          #the rest of your logic
      }
      

      【讨论】:

      • 这很好。我会编辑原件,但这会让你的评论看起来毫无意义。我将对其进行编辑。
      • @NateDSaint:我冒昧地编辑了您的帖子并删除了我的评论。如果您不喜欢这些更改,请随时回滚。
      • 这种方式误导性可能较小。谢谢!
      • 同意;我总是怀疑远距离动作,所以我总是抓取特殊变量的本地副本,例如$1, $_ 立即。几乎我唯一直接使用 $_ 的地方是在单行地图或 grep 中。
      【解决方案5】:

      die

      die 和其他 perl 内置函数一样,不需要,通常也不应该有括号。不过,die 有个大哥,现在大部分人都在用,叫

      croak

      做:

      use Carp;
      

      然后

      croak "My error here!";
      

      Croak 的工作方式与 die 类似,但通常比 die 添加更多有用的信息到错误消息中,例如相对于调用者发生错误的行。

      【讨论】:

        【解决方案6】:

        数组索引

        数组访问和 Perl 中的其他东西一样,是上下文相关的。将附加在名称上的印记想象为“提醒”您当前尝试访问或使用的内容。每当您看到$ 时,这意味着您正在尝试获取单个标量值。每当您看到@ 时,这意味着您正在访问一个列表,而% 当然意味着一个键/值哈希对。所以,当你像这样访问你的数组时:

        @_[1]
        

        您要的是一个包含单个元素的列表。此功能允许您一次从一个数组中获取多个值,但是当只访问一个值时,它会在某些上下文中导致问题,例如赋值。因此,当访问单个数组元素时,您希望始终使用标量上下文:

        $_[1]
        

        【讨论】:

          【解决方案7】:

          Perl 的 switch 语句:given/when

          Perl 在 5.10 及更高版本中内置了一个很棒的 switch 语句,称为 [given]。这大致相当于 C 中的 switch 语句,但用途更广泛。要启用此功能,您需要在脚本顶部添加一行:

          use 5.010;
          

          这将启用所有 perl 5.10 功能,包括 switch(和 say,其工作方式类似于 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; }
          }
          

          传递给给定的变量会自动放入给定代码内的$_,让您可以与它进行比较。然后,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 '$_'." }
          }
          

          【讨论】:

            【解决方案8】:

            @_拆包

            通常,您总是希望在子例程中进行任何其他处理之前解压缩@_。这让用户、其他维护者和你自己在未来如何使用你的 sub 变得更加清晰。通过直接使用@_,很难仅从给定的参数中找出需要传递的内容。它们没有任何有意义的名称,因此更难确定它们的用途,而且到处都有魔法常数 - 通常总体上是一件坏事!

            最好的办法是在执行任何其他操作之前立即将变量转换为有意义命名的标量。

            对于一个参数子例程,一个常见的解决方案是使用shift。这会拉出数组的第一个元素,并返回它(有点像 pop 的反面。)如果没有给定数组,并且您在子例程中,它会从 @_ 数组中拉出它。所以,你可以这样做

            sub mysub {
                my $foo = shift;
            }
            

            对于任何一个参数子程序。

            但是,如果你有更多呢?列出上下文分配,救援!可以使用列表分配一次分配多个变量。你可以这样做

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

            $foo$bar$baz 将分别在@_ 的0、1 和2 索引中分配值。那么,如果 0、1 或 2 索引中没有任何内容会发生什么?他们仍然被分配 - 他们成为undef!然后你可以检查这个问题中其他地方提到的undef。

            【讨论】:

              【解决方案9】:

              我非常不喜欢过于智能的功能。过度智能的函数是其行为完全由其参数改变的函数。看看你的,除了参数处理之外,它们几乎不共享任何代码。无论如何,如果我会做一些类似的事情,我会写这样的东西:

              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;
                  }
              }
              

              【讨论】:

                【解决方案10】:

                在其他答案中已经暗示但未直接解决的问题是数字模式的使用,这是 Perl 与 C 相异的约定。快速,不看代码,模式 #3 有什么作用?见鬼,看代码模式#3是做什么的?

                Perl 具有高效且易于使用的字符串。使用它们。给你的模式名称与它所做的事情有关。有点像......第一个,最后一个,recase_first,recase_last。它们不必是完全描述性的,lower_case_then_uc_last_letter 会太长而无法输入,但足以让人类大脑能够连接和联想。

                但实际上这是四个子程序。模式标志是危险信号,尤其是当您的大部分代码都包含在 if/else 语句中时。

                【讨论】:

                  猜你喜欢
                  • 2011-01-31
                  • 1970-01-01
                  • 2012-06-24
                  • 2010-09-16
                  • 1970-01-01
                  • 2017-04-29
                  • 2011-12-28
                  • 1970-01-01
                  • 2018-05-06
                  相关资源
                  最近更新 更多