Re: [Cascavel-pm] Boas práticas de programação em Perl

Nilson Santos Figueiredo Junior acid06 em gmail.com
Quarta Janeiro 25 11:10:04 PST 2006


On 1/25/06, Solli Moreira Honorio <shonorio em terra.com.br> wrote:
> > vazio existente: material de qualidade sobre Perl em português.
> Isto sim que é modéstia, hein :) !!! Brincadeira a parte, mesmo com a publicação do texto no Tekneeq, tenho
> interesse em publicar no SPPM (http://sao-paulo.pm.org/).

Eu pensei sobre esse trecho antes de escrever. ;-)

Mas é que eu fiz mais o trabalho de tradutor e "consolidador" (existe
isso?) do que de um autor propriamente dito. Então por pior que eu
fizesse meu trabalho, continuaria sendo no mesmo nível de qualidade
das fontes. E as fontes são de boa qualidade. Eu cortei alguma coisa
dos originais que eu julguei não ser tão importante assim.

Bom, vou enviar pra lista o arquivo e você podem reformatá-lo à
vontade para publicação, uma vez que ele não se encontra em nenhuma
formatação padrão, é somente texto quebrado em 80 colunas (sim, eu
sei, eu deveria ter feito em POD). Quem quiser adicionar alguma coisa
(como, por exemplo, algumas das coisas que eu cortei), também sinta-se
à vontade.

-Nilson Santos F. Jr.
-------------- Próxima Parte ----------
RECOMENDAÇÕES PARA PROGRAMAÇÃO EM PERL
       Nilson Santos Figueiredo Júnior
--------------------------------------

    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 .= "<td>\n";
        $output .= "  <tr>\n";
        $output .= "    <input type=submit>\n";
        $output .= "  </tr>\n";
        $output .= "</td>\n";
 
    Utilize

        $output .= <<'ENDOFHTML';
        <td>
          <tr>
            <input type=submit>
          </tr>
        </td>
        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(@parametros);

    Ao invés de:

        Modulo::funcao(@parametros);

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 = <TTY>);

      # 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<<FONT COLOR="\U$color\E">@_</FONT>> };
      }

    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.




Mais detalhes sobre a lista de discussão Cascavel-pm