Dev/1.X/PadroesDeCodificacao

Padrões de codificação

Índice

  1. Formatação do arquivo
    1. Identação
    2. Comprimento da linha
    3. Quebra de linha
    4. Encoding dos arquivos
  2. Demarcação do código PHP
    1. Delimitação
    2. Strings
      1. Delimitação
      2. Concatenação
  3. Estilo de codificação
    1. Estruturas de controle (if, for, while, switch)
      1. Switch
    2. Operadores &&/AND e ||/OR e sintaxe alternativa
    3. Instrução return
    4. Arrays
    5. Funções
    6. Classes
    7. Declaração de classe
      1. Propriedades
      2. Métodos
    8. Conformidade com níveis de erro E_ALL e E_STRICT
      1. E_ALL
      2. E_STRICT
  4. Documentação de código
    1. Docblock do arquivo
    2. Docblock para classes ou interfaces
    3. Docblock de métodos/funções
    4. Propriedades/constantes
    5. Observações gerais sobre os docblocks
    6. Observações gerais sobre as tags
  5. Referências

Este é um trabalho em progresso para estabelecer padrões de codificação para o i-Educar. Atualmente o código fonte do i-Educar não possui uma padronização, o que dificulta o trabalho de desenvolvimento coordenado que o software livre possui.

Estes padrões devem ser seguidos a medida que novo código for escrito e durante a refatoração de código já existente (seja para a correção de bugs ou desenvolvimento de nova funcionalidade), com o objetivo de tornar a manutenção menos dispendiosa.

Dica: refatore apenas o código que esteja diretamente relacionado com o a correção/funcionalidade em que esteja trabalhando. Assim, evita-se a introdução de novos bugs por falta de atenção.

Os padrões de codificação para o i-Educar são baseados em padrões utilizados em projetos PHP bem sucedidos como o Drupal, Zend Framework, Symfony e PEAR. Este documento assume que o leitor tenha alguma familiaridade com a linguagem de programação PHP.

A versão do i-Educar ao qual estes padrões se aplicam é a 1.X.

Formatação do arquivo

Arquivos que contenham apenas código PHP não devem ter o bloco de terminação ?>, assim, evita-se problemas como injeção de espaço em branco na resposta (origem de warnings como Cannot send session cookie - headers already sent by).

Identação

A identação deve ser composta de 2 espaços, nunca caracteres tabs (\t). Nenhum espaço em branco ao final da linha é permitido pois atrapalham na hora de visualizar de  diff entre revisões.

<?php
function soma($a, $b)
{
  $c = $a + $b;
  return $c;
}

Dicas:

  • Configure o seu IDE preferido para inserir automaticamente dois espaços toda vez que você teclar tab. Geralmente essa característica é conhecida como soft tabs. As IDEs Aptana, Eclipse, Zend Studio e os editores de texto EditPlus, GEdit e TextMate possuem essa funcionalidade;
  • Configure também para que toda vez que salvar um arquivo, todos os espaços brancos ao final das linhas sejam removidos.

Comprimento da linha

O comprimento da linha deve estar de preferência entre 75 a 84 colunas. Em alguns casos, um comprimento maior de até 120 colunas é razoável. Linhas mais curtas melhoram a capacidade de leitura e de entendimento por parte do programador.

Quebra de linha

A quebra de linha deve ser representada por um caractere LF (linefeed) do estilo Unix. Evite os estilos do MacOS (CR, carriage return) e do Windows (CR+LF). IDEs modernos e editores de texto geralmente possuem uma opção para determinar o caractere de quebra de linha.

Encoding dos arquivos

Todos os arquivos de código (PHP, Javascript e HTML) devem ser salvos em ISO-8859-1 (Latin 1) por razões de compatibilidade.

Demarcação do código PHP

Delimitação

As tags de abertura e fechamento do PHP devem sempre ser a forma padrão não-abreviada:

<?php
...
?>

Strings

Delimitação

As strings devem utilizar aspas simples e a concatenação é preferível a expansão de variáveis, comumente utilizada quando as aspas duplas são utilizadas. O uso da função  sprintf() é encorajado para casos em que há múltiplas substituições.

<?php
// Forma aceita
$string = 'O i-Educar é um software de gestão escolar';
$string = $string . ' e foi desenvolvido pela prefeitura de Itajaí-SC.';

// Também aceita, quando for o caso de múltiplas substituições
$nome    = 'i-Educar';
$tipo    = 'gestão escolar';
$criador = 'prefeitura de Itajaí-SC';
$string  = sprintf('O %s é um software de %s e foi desenvolvido pela %s.', $nome, $tipo, $criador);

// Evitar expansão de variáveis com aspas duplas
$string = 'O i-Educar é um software de gestão escolar';
$string = "$string e foi desenvolvidor pela prefeitura de Itajaí-SC.";

Concatenação

A concatenação deve sempre conter um espaço único entre a variável/aspa e o operador de concatenação:

<?php
$tipo = 'software livre';
$string = 'O i-Educar é um ' . $tipo . ' licenciado pela GPLv2 e ' . 'disponibilizado gratuitamente no Portal do Software Público Brasileiro.';

Caso a string a ser concatenada fique muito longa, a concatenação pode continuar na próxima linha. Nesse caso, o operador ponto deve ficar logo abaixo do sinal de igual:

<?php
$string = 'O i-Educar é um ' . $tipo . ' licenciado pela GPLv2 e ' . 'disponibilizado gratuitamente no'
        . 'Portal do Software Público Brasileiro.';

Estilo de codificação

Estruturas de controle (if, for, while, switch)

Todas as estruturas de controle (isso inclui if, for, while, switch, entre outras) devem seguir a seguinte formatação:

<?php
if ((condition1) || (condition2)) {
  action1;
}
elseif ((condition3) && (condition4)) {
  action2;
}
else {
  defaultaction;
}

Estruturas de controle devem ter um espaço para o parêntese de abertura, facilitando a distinção com as chamadas de funções/métodos. Após fechar os parênteses, um espaço para a abertura de chaves deve ser utilizado. As condições não devem ter espaço entre a abertura e o fechamento dos parênteses mas devem ter entre os operadores booleanos (&&, , AND, OR e XOR).

É encorajado o uso de chaves mesmo quando o caso é opcional. Isso facilita a inclusão de novo código e evita ao máximo erros de parsing.

<?php
if (condition) {
  action1;
}

Switch

Uma declaração de switch segue as mesmas recomendações de if. Para blocos case extensos, recomenda-se uma quebra de linha entre a palavra-chave break e a próxima instrução case ou default, para melhorar a leitura. default não precisa de uma instrução break quando for a última instrução de um switch:

<?php
switch (condition) {
  case 1:
    action1;
    break;
  case 2:
    action2;
    // mais instruções
    break;

  default:
    defaultaction;
}

Em casos onde mais de uma seção case com código deva ser executada, coloque um comentário de uma linha deixando claro que é intencional:

<?php
switch (condition) {
  case 1:
  case 2:
    action12;
    break;

  case 3:
    action3;
    // Essa ação continuará em case 4 porque 3 age como um pré-processamento

  case 4:
    action4;
    break;

  default:
}

Recomenda-se cuidado com esse tipo de caso. Crie testes de unidade? para testar as possibilidades de seu switch e evitar bugs obscuros.

Operadores &&/AND e ||/OR e sintaxe alternativa

Não misture os dois estilos de operadores pois a  diferença de precedência pode causar confusão e comportamentos inesperados de difícil compreensão. Utilize AND e OR apenas quando estiver lidando com código HTML (dentro de algum arquivo de template) e não utilize a outra forma mesmo que seja em uma estrutura de controle diferente.

<?php if (isset($errors) AND count($errors) > 0): ?>
  <div class="error">
    <?php print createList($errors); ?>
  </div>
<?php endif; ?>

Observe no exemplo anterior que foi utilizado a  sintaxe alternativa. Utilize-a somente quando estiver lidando com templates, assim como o uso de AND e OR.

Instrução return

É desejável que a instrução return tenha uma quebra de linha antes em blocos de código extensos. Para casos simples, a instrução pode ser colocada logo após a última operação.

<?php
function soma($a, $b)
{
  $c = $a + $b;
  return $c;
}

Arrays

Os arrays devem ser formatados com um espaço entre os seus elementos e o sinal de atribuição (quando utilizado):

<?php
$frutas = array('morango', 'banana', 'citrica' => 'limão');

Quando um array tornar-se relativamente longo, os itens podem ser quebrados em linhas individuais, aninhados um nível abaixo. Alinhe os sinais de atribuição para melhorar a legibilidade do código:

<?php
$node = array(
  'id'     => 5,
  'title'  => 'Por que padrões de codificação?'
  'status' => 'active',
  'tags'   => array(
    'desenvolvimento', 'programação'
  ),
  'created_at' => '2009-03-23',
  'updated_at' => '2009-03-11',
);

Note a última vírgula no último elemento do array. Não é um erro de digitação, é válido para o PHP e ajuda na inclusão de novos elementos caso seja necessário posteriormente.

Funções

A chamada de funções não devem conter espaço entre o nome da função e o abre parêntese:

<?php
$soma = soma($a, $b);

A declaração de uma função não deve conter espaço entre o nome da função e o abre parêntese. A chave de abertura deve ser colocada na próxima linha:

<?php
function select($query)
{
  ...
}

Todos os argumentos opcionais de uma função devem vir por último na assinatura:

<?php
function select($query, $where = '', $order = '')
{
  ...
}

Dica: utilize o  type hinting do PHP. Isso ajuda no desenvolvimento de sua API, já que parte da validação é tratada pelo PHP:

<?php
function printStrings(array $arr)
{
  foreach ($arr as $item) {
    print is_string($item) ? $item : continue;
  }
}

printStrings(array(1, 'maçã', 'morango', 2));

Classes

Veja a classe CoreExt_Controller_Dispatcher_Strategy_PageStrategy como um exemplo real e completo das seções a seguir.

Declaração de classe

O abre chave deve estar sempre após uma quebra de linha após o nome da classe:

<?php
class A
{
}

Caso a classe estenda outra e implemente uma interface, a ordem deve ser extends e implements:

<?php
class A extends B implements C
{
}

Quando a declaração da classe passar do número de caracteres limite, quebre a linha após o nome da classe e idente as declarações extends e implements:

class A
  extends B
  implements C, D
{
}

Propriedades

Todas as propriedades devem ser declaradas antes dos métodos e devem ter um modificador de acesso. Propriedades private e protected devem ter seus nomes iniciados por um underscore único "_":

<?php
class A
{
  protected $_b = NULL

  public function setB(B $b)
  {
    $this->_b = $b;
    return $this;
  }

  public function getB()
  {
    return $this->_b;
  }
}

Métodos

A declaração de métodos segue as mesmas regras das funções mas possui algumas próprias a sua natureza. O uso de um modificador é obrigatório (com exceção das classes legadas). Métodos private e protected devem ter seus nomes iniciados por um underscore único "_":

<?php
class A
{
  public function doA()
  {
    return $this->_doA();
  }

  protected function _doA()
  {
    return 'did A: ' . rand(0, 999);
  }

  public static function getA(A $instance)
  {
    return $a->doA();
  }
}

Conformidade com níveis de erro E_ALL e E_STRICT

E_ALL

O código atual do i-Educar emite alguns avisos do nível E_NOTICE (e alguns do tipo E_WARNING não relatados). Apesar de não interferir necessariamente no bom funcionamento do sistema, é inegável que algo está sendo feito de forma incorreta.

Devido a quantidade de código, pode ser impossível remover todos os alertas de nível E_NOTICE mas devemos sempre programar para que o alterado (seja por correção de bug ou inclusão de funcionalidade) emita nenhum alerta, garantindo uma conformidade E_ALL. Um dos erros mais comuns é utilizar uma variável ou índice de array não inicializada em uma condição ou qualquer outra operação:

<?php
error_reporting(E_ALL);

// Notice: Undefined variable: a in /Users/eriksencosta/Sites/spikes/eall-variable.php on line 6
if ($a) {
  print $a;
}

// Notice: Undefined index: item2 in /Users/eriksencosta/Sites/spikes/eall-array.php on line 9
$array = array('item1' => 1, 'item3' => 3);
if ($array['item2']) {
  print $array['item2'];
}

Para evitar esse tipo de erro, precisamos verificar a variável ou o índice de array com a função isset() ou !empty():

<?php
error_reporting(E_ALL);

if (isset($a)) {
  print $a;
}

$array = array('item1' => 1, 'item3' => 3);
if (!empty($array['item2'])) {
  print $array['item2'];
}

Atenção: isset() e !empty() possuem comportamento diferentes:

  • isset() irá retornar TRUE mesmo quando o valor da variável for 0 ou (string vazia) e FALSE quando a variável tiver o valor NULL;
  • !empty() irá retornar TRUE apenas quando o valor da variável for diferente de 0 ou (string vazia).

E_STRICT

Para arquivos e classes novos, uma aproximação mais rigorosa deve ser tomada. Desde o PHP 5.1.4, um novo nível de erro, o  E_STRICT, foi disponibilizado para auxiliar os desenvolvedores a programarem suas aplicações com compatibilidade futura. Com a base de código atual, dificilmente teremos um código totalmente E_STRICT mas isso não nos impede de programarmos seguindo esta boa prática.

Este nível alerta para estilos de codificação depreciados, como o uso da declaração var para variáveis de instância e do uso da função  is_a() para checar se uma classe é subclasse de outra (para PHP nas versões >= 5.0.0 e < 5.3.0).

O nível E_STRICT não está ativado por padrão quando usamos o E_ALL. Para usá-lo, utilize o operador bit-a-bit | junto com E_ALL:

Em um arquivo php:

<?php
error_reporting(E_ALL | E_STRICT);

No arquivo  php.ini:

error_repoting = E_ALL | E_STRICT

Documentação de código

A documentação do código é feita através de docblocks do PHPDocumentor. Recomenda-se utilizar a ordem alfabética no uso das tags toda vez que a ordem da tag não estiver estabelecida, seja por ser de uso opcional (no caso dos docblocks de arquivo e de classe) ou no caso de não haver uma ordem pré-estabelecida (como no caso dos métodos).

Veja a classe CoreExt_Controller_Dispatcher_Strategy_PageStrategy como exemplo real e completo de documentação de código.

Docblock do arquivo

Todo arquivo deve conter o cabeçalho do quadro a seguir, que atribui direitos autorais à Prefeitura de Itajaí, além de reforçar o caráter de software livre, licenciado pela  CC GNU GPL v2. As tags PHPDoc  @author,  @category,  @license,  @package,  @since e  @version são obrigatórias e devem estar nessa mesma sequência.

Caso esteja refatorando um arquivo existente, aproveite para atualizar o cabeçalho do arquivo, para remover os caracteres de tabulação existentes e deixar o código mais padronizado. Mantenha a autoria original, você já ganha créditos nas mensagens de commit.

Dica: configure o seu editor ou IDE para incluir automaticamente esse cabeçalho para novos arquivos.

<?php

/**
 * i-Educar - Sistema de gestão escolar
 *
 * Copyright (C) 2006  Prefeitura Municipal de Itajaí
 *                     <ctima@itajai.sc.gov.br>
 *
 * Este programa é software livre; você pode redistribuí-lo e/ou modificá-lo
 * sob os termos da Licença Pública Geral GNU conforme publicada pela Free
 * Software Foundation; tanto a versão 2 da Licença, como (a seu critério)
 * qualquer versão posterior.
 *
 * Este programa é distribuí­do na expectativa de que seja útil, porém, SEM
 * NENHUMA GARANTIA; nem mesmo a garantia implícita de COMERCIABILIDADE OU
 * ADEQUAÇÃO A UMA FINALIDADE ESPECÍFICA. Consulte a Licença Pública Geral
 * do GNU para mais detalhes.
 *
 * Você deve ter recebido uma cópia da Licença Pública Geral do GNU junto
 * com este programa; se não, escreva para a Free Software Foundation, Inc., no
 * endereço 59 Temple Street, Suite 330, Boston, MA 02111-1307 USA.
 *
 * Descrição do arquivo (opcional).
 *
 * @author    Mister Tux <mister.tux@example.com>
 * @category  i-Educar
 * @license   @@license@@
 * @package   Namespace
 * @since     Arquivo disponível desde a versão X
 * @version   $Id$
 */

O resumo da licença  CC GNU GPL v2 é obrigatório para todos os arquivos.

A descrição do arquivo é opcional. Se for um arquivo contendo uma classe ou interface, é recomendado não colocar descrição, já que o docblock da classe possui um espaço para descrição também. Caso nenhuma descrição seja fornecida, utilize apenas uma linha em branco para separar o resumo da licença das tags do PHPDocumentor.

Repare que o arquivo vem com duas tokens: @@license@@ e $Id$.

  • @@license@@: essa token é substituída durante o processo de build pelo Phing, durante a criação de uma release para distribuição. O valor da token está configurado na variável project.conf.license do arquivo de build do Phing;
  • $Id: token do SVN. Durante o checkout, o valor é substituído por uma combinação formada pelo nome do arquivo, número de versão do SVN, data e hora da substituição e o autor da última mudança no arquivo. Mais informações na documentação do SVN ( Keywords substitution).

Docblock para classes ou interfaces

O docblock de uma classe/interface contém o nome da classe/interface seguido de espaço e um dos seguintes termos:

  • class: para uma classe concreta
  • abstract class: para uma classes abstrata
  • interface: para uma interface

Após o nome da classe/interface, pode ser definido uma descrição sobre o que a classe/interface faz. Essa descrição é recomendada mas não é obrigatória. Caso não informe uma descrição, deixe apenas uma linha em branco entre o nome da classe/interface e as tags.

As tags obrigatórias para documentar classes/interfaces são a  @author,  @category,  @license,  @package,  @since e  @version.

<?php

// Docblock do arquivo

/**
 * Example class.
 *
 * Descrição da classe. (opcional)
 *
 * @author    Mister Tux <mister.tux@example.com>
 * @category  i-Educar
 * @license   @@license@@
 * @package   Namespace
 * @since     Classe disponível desde a versão X
 * @version   @@package_version@@
 */
class Example
{
}

Nesse docblock, uma token diferente é utilizado para a tag @version. Diferentemente da versão do arquivo, essa token será substituída pelo número da release durante o processo de build do Phing. Uma release poderia ser 1.1.1 ou 1.1.0-beta3, por exemplo. A definição do número da release está configurada na variável project.conf.version do arquivo de build do Phing.

Docblock de métodos/funções

O docblock para métodos e funções são mais flexíveis já que o seu conteúdo depende muito do código. Basicamente será necessário prover uma descrição do que o método faz, o tipo do parâmetro recebido pelo método e o tipo de retorno do método.

Caso o método seja um sobrescrição de uma herança ou interface, você pode usar apenas a tag @see, para indicar o método sendo sobrescrito (que deverá estar documentado).

Quando a descrição se estender a mais de uma linha e/ou você utilizar mais de 2 tags do PHPDocumentor, insira uma quebra de linha entre a descrição e as tags para melhorar a legibilidade.

<?php

// Docblock do arquivo

// Docblock da classe
class Example implements Printable
{
  /**
   * O conteúdo armazenado por este container.
   * @var mixed
   */
  protected $_content = NULL;

  /**
   * Setter.
   * @param mixed
   * @return Example Provê interface fluída
   */
  public function setContent($content)
  {
    $this->_content = $content;
    return $this;
  }

  /**
   * Getter.
   * @return mixed
   */
  public function getContent()
  {
    return $this->_content;
  }

  /**
   * Retorna as informações para uma chamada XMLRPC do método, executando
   * operações adicionais.
   *
   * @author  Mister ElePHPant <mister.elephpant@example.com>
   * @link    http://www.xmlrpc.com/  Especificações do XMLRPC
   * @param   WebServiceFormatter  $formatter
   * @return  string  Uma string contendo XML formatado para o XMLRPC
   * @see     WebService#getXmlRpc
   * @since   Método disponível desde a versão X
   * @throws  WebServiceFormatterException
   */
  public function getXmlRpcInfo(WebServiceFormatter $formatter = NULL)
  {
    $info = array('example.getcontent' => 'Example::getContent');

    try {
      $xml = $formatter->formatArray($info);
    }
    catch (WebServiceFormatterException $e) {
      throw $e;
    }

    return $xml;
  }

  /**
   * @see Printable#toString()
   */
  public function toString()
  {
    return $this->__toString();
  }

  /**
   * Implementa o método mágico __toString.
   * @link http://br2.php.net/manual/pt_BR/language.oop5.magic.php#language.oop5.magic.tostring Documentação do método __toString
   * @return string
   */
  public function __toString()
  {
    return $this->getContent();
  }

  /**
   * Retorna os valores originais das propriedades da classe.
   */
  public function reset()
  {
    $this->_content = NULL;
  }
}

Repare que novas tags foram utilizadas para os métodos:

  •  @author: veja mais informações em "Observações gerais sobre as tags";
  •  @link: veja mais informações em "Observações gerais sobre as tags";
  •  @param: define o tipo do parâmetro sendo passado ao método. Use uma tag @param por parâmetro;
  •  @return: define o tipo de retorno do método;
  •  @see: cria um link referenciando o método original ao qual o atual sobrescreve. O uso dessa tag é mais abrangente, consulte a documentação do PHPDocumentor para mais informações;
  •  @throws: quando a classe lançar uma exceção verificável, defina-a com essa tag.

Propriedades/constantes

O uso da tag  @var é obrigatório para propriedades. Uma descrição breve é opcional. Para constantes, nenhum dos dois é obrigatório:

<?php

// Docblock de arquivo

// Docblock de classe
class Example
{
  /**
   * @var int
   */
  const EXAMPLE_FACTOR = 1.500067;

  /**
   * A instância singleton de Example.
   * @var Example|NULL
   */
  protected $_instance = NULL;
}

Observações gerais sobre os docblocks

  • Sempre use ponto final de uma descrição breve ou estendida;
  • Não use ponto final para as descrições de uma tag @param;

Observações gerais sobre as tags

  • @author: nunca altere o autor original de um arquivo ou classe. Se o arquivo ou classe for co-autorado, pode-se colocar mais de uma tag @author, quantas forem necessárias para listas todos os autores. Você pode definir um autor diferente em um método que você criou em uma classe já existente. Como o controle de versão é capaz de listar todas as alterações individuais feitas nos arquivos, o uso dessa tag para esse caso é desencorajado por adicionar informação extra irrelevante;
  • @link: Útil para prover informações extras como um link para uma especificação pública (padrões ISO, ABNT) ou para qualquer documentação extra relevante;
  • @package: Veja a documentação de  Namespaces para mais informações;
  • @since: Obrigatória para os docblocks de arquivo e de classe, opcional para os de métodos/propriedades. Esse é o número da release estável ao qual for disponibilizado. Por exemplo, se um método foi introduzido em uma classe durante o desenvolvimento para a release 1.1.0, esse valor deverá ser 1.1.0. Para métodos, apesar de bem vindo, não é nunca solicitado já que é possível saber quando um método foi introduzido pelo controle de versão (é um caso diferente da tag @author!).

Referências

Os seguintes documentos serviram de base para a definição desses padrões iniciais (todos em inglês):