Declarar campos em classes é realmente prejudicial no PHP?

12

Considere o código a seguir, no qual o setter é deliberadamente quebrado devido a um erro de programação mundano que cometi de verdade algumas vezes no passado:

<?php

    class TestClass {

        private $testField;

        function setField($newVal) {
            $testField = $newVal;
            // deliberately broken; should be `$this->testField = $newVal`
        }

        function getField() {
            return $this->testField;
        }

    }

    $testInstance = new TestClass();
    $testInstance->setField("Hello world!");

    // Actually prints nothing; getField() returns null
    echo $testInstance->getField(); 

?>

O fato de eu ter declarado $testFieldno topo da classe ajuda a esconder esse erro de programação. Se eu não tivesse declarado o campo, obteria algo semelhante ao seguinte aviso impresso no meu log de erros ao chamar este script, o que seria potencialmente valioso para ajudar na minha depuração - especialmente se eu cometesse um erro como esse em um aplicativo grande e complicado do mundo real:

Aviso do PHP: Propriedade indefinida: TestClass :: $ testField em /var/www/test.php na linha 13

Com a declaração, não há aviso.

Talvez esteja faltando alguma coisa, mas estou ciente de apenas duas razões para declarar campos de classe no PHP: primeiro, que as declarações atuam como documentação e, segundo, que sem as declarações não se pode usar os modificadores privatee protectedaccess, que são indiscutivelmente útil. Como o último argumento não se aplica a campos públicos - a atribuição a um campo não declarado de um objeto o torna público - parece-me que eu deveria pelo menos comentar todas as minhas declarações de campo público. Os comentários fornecerão exatamente o mesmo valor de documentação, mas vou me beneficiar de avisos se tentar ler um campo não inicializado.

Pensando melhor, porém, não parece fazer sentido parar por aí. Como, na minha experiência, tentar ler um campo não inicializado é uma causa de erro muito mais comum do que tentar ler ou modificar inadequadamente um campo privado ou protegido (já fiz o primeiro várias vezes na minha curta carreira de programação, mas nunca o último ), parece-me que comentar todas as declarações de campo - e não apenas públicas - seria uma prática recomendada.

O que me faz hesitar é que nunca vi ninguém fazer isso em seu código. Por que não? Existe um benefício em declarar campos de classe que eu não conheço? Ou posso modificar a configuração do PHP de alguma forma para alterar o comportamento das declarações de campo para que eu possa usar declarações de campo reais e ainda me beneficiar dos avisos de "Propriedade indefinida"? Ou há mais alguma coisa que eu tenha perdido na minha análise?

Mark Amery
fonte
1
Indiscutivelmente útil ? Os benefícios que você declarou são extremamente importantes. Eu provavelmente me recusaria categoricamente a trabalhar em códigos que não tivessem declarações explícitas de propriedade.
Michael
2
Realmente, a maneira de se proteger desses erros é com uma verificação cuidadosa e melhor, através de testes de unidade.
Michael
1
existe um relatório de erros php (um nível de aviso) embutido que informa se você usa uma variável sem declará-la primeiro.
Crayon Violent
3
@MarkAmery Eu acho que uma startup freneticamente está entre os lugares mais importantes para adotar testes de unidade estritos - como você está sempre alterando o código, provavelmente está sempre quebrando . Esse é realmente o objetivo exato de testes rigorosos e TDD simples. Sim, acho que usar sublinhados pode ajudar a lembrar que você está acessando coisas particulares, mas para mim a idéia de adotar padrões de codificação específicos para me proteger de cometer erros que não deveria estar cometendo é perturbadora. Como fazer TRUE === $variablepara impedir que você atribua acidentalmente, em vez de comparar.
Michael
1
@MarkAmery Observe também que as palavras-chave de visibilidade são analisadas por ferramentas de documentação automatizadas , para fornecer mais "valor de documentação" do que apenas um comentário que você colocaria manualmente.
Michael

Respostas:

14

Você sempre deve declarar suas propriedades de classe antes do tempo. Embora o PHP seja uma linguagem dinâmica e, com toda a certeza, funcione ao criar suas propriedades em tempo de execução, existem várias desvantagens nesse caminho.

  • O compilador pode otimizar propriedades declaradas. Quando você declara dinamicamente uma propriedade, isso é um impacto no desempenho, pois o compilador precisa criar uma tabela de hash dinâmica para armazenar suas propriedades dinâmicas.
  • Não sou 100% dessa, mas acredito que otimizadores de bytecode como a APC não serão tão úteis, pois não terão uma imagem completa da sua turma. (E otimizadores como a APC são obrigatórios )
  • Torna seu código 1000x mais difícil de ler. As pessoas vão te odiar.
  • Torna 1000x mais provável que você cometa um erro. (Foi getId ou getID? Espere, você usou os dois. Falha.)
  • Não há preenchimento automático de IDE ou dica de tipo.
  • Os geradores de documentação não verão sua propriedade.

O problema que você descreveu é realmente menor quando comparado ao problema de não declarar suas propriedades em sua definição de classe. Aqui está uma boa solução.

Acostume-se a declarar padrões para suas propriedades.

private $testField = null;
private $testField = '';
private $testField = 0;
private $testField = []; //array()
private $testField = false;

Exceto pelas propriedades que armazenarão objetos, isso cobrirá a maioria dos tipos de base que você armazenará. As propriedades que armazenarão objetos podem ser definidas no seu construtor.

Uma boa regra para o design de classe é que, após seu objeto ter sido criado e seu construtor executado, você não deverá ter nenhuma propriedade "indefinida".

Jarrod Nettles
fonte
Em resposta às suas 5 desvantagens: 1 (Desempenho): Você tem uma fonte para isso? 2 (Legibilidade): Não tenho certeza se entendi; a proposta era comentar as declarações, não apenas removê-las, então que legibilidade é perdida? Destaque de sintaxe um pouco menos amigável e dificuldade potencial de distinguir dos comentários vizinhos, eu acho? 3: Este eu não entendo nada; você pode esclarecer? 4 (IDEs): É justo - não tenho experiência em codificar PHP com um IDE e não sabia sobre as dicas de tipo do Eclipse. 5 (geradores de documentos): É justo - nunca usei um deles.
Mark Amery
Não vi sua fala sobre comentá-los. Independentemente disso, os pontos acima ainda se aplicam - como você sabe quais estão ativos ou comentados legitimamente?
Jarrod Nettles
Em resposta à sua solução proposta: é exatamente isso que quero evitar - a atribuição de padrões, mesmo quando eles não têm sentido e os valores padrão nunca devem ser usados. O problema com esses padrões (e com declaração sem atribuição, que apenas atribui nulo) é que se, devido a um erro de programação, eu não atribuo um valor a algo no construtor quando devo, em vez de o PHP dar uma aviso quando tento usar esse valor mais tarde - ajudando-me a detectar meu erro - tudo parecerá funcionar bem, mesmo que a classe esteja quebrada.
Mark Amery
2
@ MarkAmery Seu erro de programação, no entanto, é essencialmente um erro de digitação. Todos nós já os tivemos - mas você está tentando detonar uma bomba para matar uma mosca aqui.
Jarrod Nettles
Tu podes estar certo. Passei algumas horas rastreando um bug hoje que acabou sendo causado inteiramente por esse erro de digitação e fiquei frustrado depois pela súbita percepção de que, se eu não tivesse usado declarações de campo, teria um aviso e soube imediatamente onde estava o meu erro (o que eu sempre achei que aconteceria nessas circunstâncias; não percebi que as declarações inicializavam os campos como nulos). A acessibilidade imediata dessa experiência provavelmente está distorcendo meu julgamento sobre a probabilidade de recorrência ou o quanto vale a pena se defender.
Mark Amery
2

Eu trabalhei em algum código que usava propriedades de objetos criadas dinamicamente. Eu pensei que usar propriedades de objetos criadas dinamicamente era bem legal (é verdade, na minha opinião). No entanto, meu programa levou 7 segundos para ser executado. Eu removi as propriedades dinâmicas do objeto e as substitui, declaradas como parte de cada classe (pública neste caso). O tempo de CPU passou de 7 segundos para 0,177 segundos. Isso é bastante substancial.

É possível que eu estivesse fazendo algo errado na maneira como estava usando propriedades dinâmicas de objetos. Também é possível que minha configuração esteja quebrada de alguma forma. Obviamente, devo dizer que tenho uma configuração simples de baunilha em PHP na minha máquina.

user328304
fonte