Padrões de codificação
Índice
- Formatação do arquivo
- Demarcação do código PHP
- Estilo de codificação
- Documentação de código
- 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 (&&,
É 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):
- Drupal coding standards
- Zend Framework's Appendix B - Zend Framework Coding Standard for PHP
- Symfony coding standards
- PEAR Manual::Coding Standards
- phpDocumentor manual
- How to use GPL licenses for your own software
