Recomendações para programação em Perl

Nilson Santos Figueiredo Júnior
Publicado em 20/08/2006

r3 - 20 Aug 2006 - JoenioCosta

Recomendações para programação em Perl

----

* Recomendações gerais
* Estética do código
* Filosofia de desenvolvimento
* Nomenclatura em geral
* Recursos interessantes da linguagem
* Considerações finais

----

Essas recomendações foram fortemente baseadas no "perlstyle" - a manpage que é o guia de estilo de programação "oficial" de Perl. Além disso uma outra apresentação com sugestões de estilo para garantir a legibilidade do código também foi utilizada como base. Essa apresentação pode ser gerada a partir do script contido no endereço:

* http://www.cpan.org/modules/by-authors/id/TOMC/scripts/perlstyle-talk.gz

Recomendações gerais

Cada programador terá as suas próprias preferências relativas à formatação do código. Porém, existem algumas sugestões gerais que tornarão os programas mais fáceis de ler, entender e manter.

A primeira e mais importante providência a se tomar é executar os programas sob a flag -w ou então garantir que exista um "use warnings" no início de cada arquivo. Os programas também devem sempre rodar sob "use strict". Essas duas recomendações garantem com que muitos problemas nem cheguem a aparecer no futuro. Caso você tenha algum problema, um "use diagnostics" tentará dar uma explicação do que significa um warning e os motivos usuais dele estar sendo disparado. Às vezes é interessante que os warnings se tornem fatais, para que o programa se recuse a executar caso existam warnings. Nesse caso, utilize:

   use warnings FATAL => 'all';

Estética do código

Esses são apenas guias de estética. As recomendações mais importantes estão marcadas com um astericos. Um "bloco" é definido por uma abertura e fechamento de chaves.

* Código indentado (preferencialmente, com indentação de 4 colunas)

* Abrir as chaves na mesma linha do comando. Caso não seja possível (no caso de uma condição muito grande, por exemplo) garanta com que as chaves de abertura e fechamento do bloco estão alinhadas
* Espaço em branco antes do início de um bloco
* Um bloco de uma linha pode ser colocado na mesma linha do comando, incluindo as chaves ( ex: "if ($x) { $y = $z }" ), sem utilizar ponto-e-vírgula
* Sem espaço antes do ponto-e-vírgula
* Espaço em volta da maioria dos operadores
* Linhas em branco entre pedaços de código que fazem coisas diferentes

* Sem espaço antes da chamada de uma função ("funcao()" ao invés de "funcao ()")
* Espaço depois de cada vírgula
* Linhas grandes são quebradas após um operador
* Alinhas itens correspondentes verticalmente. Exemplo:
        @animais = ( 'cachorro', 'gato', 'avestruz',
                     'urubu',    'leão', 'girafa'    );

* Omita pontuação redundante.

Essas foram algumas sugestões mais superficiais. Bem ligadas à estética tradicional do código. Algumas sugestões mais substanciais são:

* Só porque você PODE fazer alguma coisa de uma maneira específica não quer dizer que você DEVA fazer dessa forma. Perl foi feito para dar várias maneiras de se fazer qualquer coisa, então tente escolher a alternativa mais legível. Por exemplo:
        open(my $fh, 'arq') or die "Não foi possível abrir 'arq': $!";

é melhor que

        die "Não foi possível abrir 'arquivo': $!" unless open(my $fh, 'arq');

porque a segunda forma esconde o ponto principal da sentença em um modificador. Por outro lado

         print "Iniciando análise\n" if $verbose;

é melhor que

         $verbose && print "Iniciando análise\n";

porque o ponto principal não é se o usuário está em modo verbose ou não.

* Não faça contorções bobas para sair de um loop no seu topo ou no seu fim, uma vez que Perl provê o operador "last" para que você possa sair no meio. Apenas garanta que ele fique claramente visível de alguma forma. Por exemplo:
        for my $i (1..10) {

            my $resultado = funcao($i);

            # termina o loop se o resultado for verdadeiro
            last if $resultado;

            processa_inteiro($i);
        }

  • * Não tenha medo de utilizar labels para os loops, já que eles existem para melhorar a legibilidade e permitir quebras em loops de vários níveis. Por exemplo:
                 LINE: while (<$fh>) {
                           next LINE if /xxx/;
                 CHAR:     for my $char (split '') {
                                next CHAR if /[A-F]/;
                                next LINE if /[0-9]/
                                print $char;
                           }
                       }

* Use identificadores (nomes de variáveis, de funções, etc) que tenham algum significado aparente. Use underscores para separar as palavras. Geralmente é mais fácil ler $var_names_like_this do que $VarNamesLikeThis ou alguma outra forma, principalmente considerando um código em inglês escrito por pessoas que não falam a língua nativamente. Essa é uma regra simples que funciona de forma consistente com $VAR_NAMES_LIKE_THIS.
* Pode ser útil utilizar letras maiúsculas ou minúsculas para representar o escopo ou a natureza das variáveis:
      $ALL_CAPS_HERE   constantes (cuidado pois existem nomes reservados)
      $Some_Caps_Here  variáveis globais
      $no_caps_here    variáveis de escopo local ( my() ou local() )

Nomes de funções e métodos normalmente ficam melhores sempre em letras minúsculas.

Você pode utilizar um underscore como caracter inicial de um identificador para indicar que uma variável ou função não deve ser utilizada fora do pacote em que ela foi definida (variáveis / métodos privados).

  • * Use os operadores "and" e "or" ao invés de "&&" e "||" para reduzir o número de caracteres de pontuação. Porém, tome cuidado pois eles possuem precedências diferentes ao se relacionar com outros operadores. Veja mais detalhes na manpage "perlop".
* Ao invés de vários print()s ou atribuições / concatenações repetidas, utilize os "here documents". Por exemplo:

Ao invés de

        $output .= "\n";
        $output .= "  \n";
        $output .= "    \n";
        $output .= "  \n";
        $output .= "\n";

Utilize

        $output .= <<'ENDOFHTML';
        
          
            
          
        
        ENDOFHTML

Note que a string de fim do documento deve estar sozinha numa linha, sem caracteres (incluindo espaços) antes ou depois. A indentação observada acima é do documento e não do código.

* Módulos bem escritos quase nunca requerem o uso de "::" para a chamada de funções. As funções devem ser importadas ou utilizadas como métodos da classe.

Escreva módulos que aceitam a forma

        Modulo->funcao(@parâmetros);

Ao invés de

        Modulo::funcao(@parâmetros);

Filosofia de desenvolvimento

O primeiro ponto a ser levantado é um dos mais importantes: sempre que fizer sentido, pense em reusabilidade. Porque desperdiçar seu esforço com uma solução específica se depois você provavelmente precisará de fazer algo similar novamente? Pense em generalizar o seu código. Pense em escrever um módulo ou uma classe.

Sempre lembre que você está programando em Perl. Como foi citado na seção anterior, "só porque você PODE fazer alguma coisa de uma maneira específica não significa que você DEVA fazer dessa forma". Se você se encontrar escrevendo linhas de programa que se pareçam com C, Java ou Pascal, você não está no caminho correto. Você precisa aprender a programar o "Perl nativo". Não evite certas formas de escrever o código só porque ela utiliza um recurso pouco conhecido e/ou inexistente em outras linguagens. Quem for manter seu código no futuro deve ser alguém que conhece a linguagem. Você não escreve português para que um alemão ou francês entenda, você escreve português para que pessoas que entendam português leiam.

Procure sempre escrever programas que são funcionais, minimalísticos, flexíveis e compreensíveis (não necessariamente nessa ordem). Pense primeiro, depois comece a programar. Se as coisas parecerem muito ruins, jogue tudo fora e comece do zero de novo. Nunca tente "remendar" algo que está ruim: sempre que possível, refaça do zero. Isso melhora o seu entendimento, aprimora a criatividade e produz um produto final mais refinado. Lembre-se: o bom senso é o que reina nas boas práticas de programação. Algumas vezes, um código menor o torna mais mantível, outras vezes não.

Faça comentários relevantes, sem parafrasear o código. Fique longe de grandes blocos de comentários com pouco conteúdo e caracteres de pontuação em volta "desenhando" o seu contorno. Porém, não faça comentários de linhas específicas mas, sim, comentários relativos a um bloco de código inteiro. Normalmente, é mais importante que suas estruturas de dados estejam bem comentadas que os algoritmos propriamente ditos. Isso não é uma regra geral, mas, normalmente, um código que precisa de comentários para ser entendido por um programador fluente na linguagem é um indício de um código mal escrito.

Quebre tarefas complexas em subrotinas. Quebre subrotinas em pedaços que são mais facilmente gerenciáveis. Não tente colocar tudo em uma só expressão regular. Quando você vê funcionalidade similar em dois lugares, unifique o código em uma subrotina mais genérica.

Nomenclatura em geral

Para evitar os comentários é fundamental que a escolha dos nomes de variáveis, funções, procedimentos, etc. seja feita de forma pensada. Não basta seguir uma convenção: novamente, o bom senso é muito importante.

Como regra geral, os nomes de procedimentos (subs que não retornam nada) devem refletir o que elas fazem. Os nomes de funções devem refletir o que elas retornam. Dê nome às coisas de forma que elas leiam bem na linguagem na em que o código é escrito. Caso estejamos usando inglês para nomear uma função que retorna verdadeiro ou falso, "is_ready" é um nome melhor que "ready". E assim por diante, utilizando os predicados "is", "does", "can", "has", etc. Assim sendo, teremos "canonize" como um procedimento, "canonical_version" com uma função que retorna um valor e "is_canonical" como uma verificação booleana. Para funções de conversão ou de mapeamento, "abc2xyz" ou "abc_to_xyz" são nomes normalmente utilizados. Hashes normalmentes especificam uma propriedade das suas chaves e são usadas como uma relação de possessividade. Em outras palavras, dê nome às hashes pelos seus valores não pelas suas chaves. Por ex:

* CERTO:
        %color = ('apple' => 'red', 'banana' => 'yellow');
        print $color{'apple'};          # Prints `red'

* ERRADO:
        %fruit = ('apple' => 'red', 'banana' => 'yellow');
        print $fruit{'apple'};          # Prints `red'

Ao nomear alguns tipos de variáveis, muitas vezes é interessante utilizar algumas convenções relativas ao seu tipo. Abaixo estão algumas sugestões para prefixos de nomes de variáveis retiradas diretamente da manpage do "DBI":

    $dbh    Database handle object
    $sth    Statement handle object
    $fh     A filehandle
    \%attr  Reference to a hash of attribute values passed to methods

Recursos interessantes da linguagem

Algumas seções atrás, quando falávamos sobre filosofia de desenvolvimento, citamos que existem recursos que fazem com que seu código deixe de se parecer com outras linguagens e se torne "Perl nativo". Aqui serão listadas algumas dessas técnicas.

Quando trabalhando com expressões regulares é comum precisar de copiar valores e modificar somente a cópia. Com Perl, você pode copiar e mudar de uma vez só. Alguns exemplos:

      # ex1
      chomp($answer = );

      # ex2
      ($a += $b) *= 2;

      # ex3
      # strip to basename
      ($progname = $0)        =~ s!^.*/!!;

      # ex4
      # Make All Words Title-Cased
      ($capword  = $word)     =~ s/(\w+)/\u\L$1/g;

      # ex5
      # /usr/man/man3/foo.1 changes to /usr/man/cat3/foo.1
      ($catpage  = $manpage)  =~ s/man(?=\d)/cat/;

      # ex6
      @bindirs = qw( /usr/bin /bin /usr/local/bin );
      for (@libdirs = @bindirs) { s/bin/lib/ }
      print "@libdirs\n";
      # saída: "/usr/lib /lib /usr/local/lib"

Para pegar o último elemento de uma array, dê preferencia a $array-1 ao invés de $array$#array. Um bom efeito colateral disso é que utilizando a forma sugerida, você consegue pegar o último elemento de uma lista também. Ex:

      print( (1, 2, 3, 4)[-1] ); # imprime "4"

Lembre-se que substr, index, rindex e splice também aceitam índices negativos para contar de trás pra frente. Lembre-se que você pode atribuir valores a uma substring ou modificá-la de qualquer outra forma:

      substr($s, -10) =~ s/ /./g;

Você só estará começando a pensar em Perl quando pensa seus algoritmos em termos de hashes. Utilize uma hash sempre que você quiser representar um conjunto, uma relação, uma tabela, uma estrutura ou um registro.

Use loops foreach. Seu poder de localização das variáveis locais é bastante útil. Supondo que você tenha duas arrays de números e queira multiplicar todos os seus elementos por pi. Faça dessa maneira:

      foreach my $e (@a, @b) { $e *= 3.14159 }

Quando você modifica a variável $e dentro do loop, o elemento da array original é modificado e não uma cópia dele. Lembre-se que você pode copiar e modificar de uma vez só. Portanto, como exemplo, caso você queira criar uma array com os elementos de outra elevados ao quadrado, faça da seguinte maneira:

      foreach my $n (@square = @single) { $n **= 2 }

Atente para o fato de que as funções também são estruturas de dados. Então você pode utilizar referências para funções como argumentos para outras funções ou em estruturas de dados. Por exemplo:

      %State_Table = (
          Initial  => \&show_top,
          Execute  => \&run_query,
          Format   => \&get_format,
          Login    => \&resister_login,
          Review   => \&review_selections,
          Sorting  => \&get_sorting,
          Wizard   => \&wizards_only,
      );

      foreach my $state (sort keys %State_Table) {
          my $function = $State_Table{$state};
          my $how      = ($action == $function)
                          ? SCREEN_DISPLAY
                          : SCREEN_HIDDEN;
          $function->($how);
      }

Existe um recurso que Perl incorporou das linguagens de programação funcionais (como Lisp, Haskell, etc) que é bastante útil em alguns casos. Esse recurso são os closures. Você pode clonar funções similares utilizando-os:

      no strict 'refs';
      for my $color (qw[red yellow orange green blue purple violet]) {
          *$color = sub { qq<@_> };
      }

Perl não possui uma função de switch como a de C. Porém, você pode ter a mesma funcionalidade utilizando um for. Aprenda fazer um switch utilizando for:

     SWITCH: for ($where) {
                 /In Card Names/     && do { push @flags, '-e'; last; };
                 /Anywhere/          && do { push @flags, '-h'; last; };
                 /In Rulings/        && do {                    last; };
                 die "unknown value for form variable where: `$where'";
             }

Mais recomendações para garantir um código mantível

* Use hashes de registros, não estruturas de dados paralelas. Não faça isso:
      $idade{"João"} = 23;
      $pai{"João"}   = "Joaquim";

Quando você deveria fazer isso

      $pessoas{"João"}{idade} = 23;
      $pessoas{"João"}{pai}   = "Joaquim;

* Evite o uso de barras de escape ("\") em expressões regulares ou outros operadores quote-like. Perl deixa com você a escolha dos delimitadores, então tente usar esse poder:
      m#^/usr/spool/m(ail|queue)#

* Retire código repetido de dentro de loops ou condicionais.

Antes

      if (...) {
          X;  Y;
      } else {
        X;  Z;
      }

Depois

      X;
      if (...) {
          Y;
      } else {
          Z;
      }

Considerações finais

Como foi dito várias vezes ao longo desse texto: o mais importante de tudo é utilizar o bom senso. Tente pensar se o código escrito estaria claro caso outra pessoa fosse entendê-lo e tivesse que corrigir algum problema, por exemplo. Seja sincero consigo mesmo e saiba reconhecer os seus vícios que não são "saudáveis" para o código.

----

AUTHOR

Nilson Santos Figueiredo Júnior

blog comments powered by Disqus