Augusto Pascutti

Orientação a Objetos – Um caso contra os métodos de acesso

Quanto de suas classes são compostas por métodos para definir e retornar valores
guardados nela? Você já questionou o benefício da orientação a objetos enquanto
estava criando uma classe?

A ideia de um objeto é encapsular uma computação e isso envolve esconder algumas
coisas do usuário. Um método de acesso (ou setter/getter) é uma exposição:

<?php

namespace MyAppPromotion;

class Discount
{
    private $value = 0.00;

    public function setValue($float)
    {
        $this->value = $float;
    }

    public function getValue()
    {
        return $this->value;
    }
}

A proposta de encapsular as coisas é permitir que elas evoluam (ou mudem) sem o
conhecimento do usuário, que no caso é o desenvolvedor. Todo método público
é como uma mensagem de um desenvolvedor pra outro, uma mensagem dizendo
“ei, me use”.

Cabe ao usuário decidir se ele quer usar ou não as ferramentas dadas a ele
e na orientação a objetos, para usar um método precisamos das instâncias:

<?php

namespace MyAppPromotion;

$blackFriday = new Discount;
$askingToGetFired = new Discount;

Existem casos em que você quer garantir só uma instância de determinada
classe
(o que não acho muito válido pro PHP), mas no caso do
nosso desconto queremos obviamente vários tipos de desconto.

No caso de várias instâncias, o que diferencia uma da outra como no exemplo
acima? Quais dos métodos um usuário pode ou não usar e em quais circunstâncias?

Qual a vantagem do código da classe usando os métodos de acesso (get/set)
ao invés de definir o atributo como público?

Objetos sempre válidos

Nosso trabalho é fazer com que as coisas funcionem 100% do tempo. Deixar
informações implícitas e quebrar promessas (um método é uma promessa) é
a receita para nunca atingir o 100% desejado.

A promessa a ser quebrada aí é a chamada pro getValue:

<?php

$blackFriday = new Discount;
$blackFriday->getValue();

Sem valor nenhum de desconto como você poderia querer cumprir essa promessa?
Existem formas de melhorar a comunicação entre nossos objetos, impedindo que
coisas inesperadas aconteçam e que um desenvolvedor precise ler inúmeras linhas
de código pra descobrir como alguma coisa funciona.

A Orientação a Objetos oferece duas mecânicas que consigo pensar em usar nesse
momento. Uma é lançar uma exceção caso nenhum valor de desconto tenha sido definido
e a outra é obrigar um valor de desconto no construtor:

<?php

namespace MyAppPromotion;

class Discount
{
    private $value = 0.00;

    public function __construct($value)
    {
        $this->value = (float) $value;
    }

    public function getValue()
    {
        return $this->value;
    }
}

Existem várias outras maneiras de resolver esse problema. Não usei a exceção
porque um valor de desconto sempre é necessário. Eu poderia criar um validador
de descontos também, mas por enquanto esse tipo de burocracia parece bastante
desnecessário. O melhor código é aquele que não existe.

Criar um código pensando no seu consumo implica em querer contar uma história.
A última coisa que queremos é suspense ou mistério, o melhor tipo de código
é aquele filme de ação tapa na cara, MacGyver:

<?php

namespace MyAppPromotion;

$blackFriday = new Discount(10);
$guguSunday = new Discount(25);

Parece que nosso código está fazendo um pouco mais de sentido agora. Repare
que ao ler você sabe que o Discount é membro de MyAppPromotion e que
temos dois descontos com valores diferentes. Pare por 1 minuto e pense o
quanto de informação você tem com o código acima que estava escondida por
causa dos métodos de acesso.

Faça o exercício mental de imaginar esse código num caso mirabolante de
checkout num e-commerce. Se você tem uma instância, você pressupõe que ela
faça tudo que ela promete, então se atente a criar uma classe que obriga
todas as instâncias dela a serem válidas. Se você quiser se gabar entre
seus amiguinhos, você pode até dar um nome bacana pra esse princípio
de design
.

Repare também como o consumo da classe utilizando o construtor conta uma
história
. Será que na história contada você tem todas as informações
que precisa pra colaborar ou mudar essa história?

Essa é a pergunta que você deve fazer ao escrever cada linha de código.

Contando uma história completa

Se a nossa empresa agora quiser dar dois descontos: um de 25% e outro de R$15.
Como você vai instanciar as classes de desconto?

Afinal, nossos descontos até agora são em porcentagem ou são valores monetários?

Esse tipo de suspense ou informação que só é óbvia pra quem está criando o código
é o tipo de problema chato. Difícil resolver ele sozinho sem muita experiência e
sensibilidade. A melhor prática pra identificar esses problemas, na minha opinião,
é o code review. Além de alinhar as práticas do time, é imperativo que quem leia
o código entenda ele plenamente.

Outra forma de resolver isso é utilizar comentários. Eu aprendi que o destino de
um comentário é ser ignorado ou ficar desatualizado então, como a gente resolve
nosso problema sem comentários?

<?php

namespace MyAppPromotionDiscount;

class Money
{
    private $amount = 0.00;

    public function __construct($amountToDiscount)
    {
        $this->amount = $amountToDiscount;
    }

    public function getValue()
    {
        return $this->amount;
    }
}

Podíamos ter resolvido isso de várias formas, imediatamente penso em duas:

  • Mudar o atributo de $value para $amountOfMoney.
  • Mudar o nome da classe de Discount pra MoneyDiscount.

Eu poderia ter usado as variáveis e atributos pra explicar o que eles devem
conter
, mas criei uma namespace intermediária além de
mudar o nome da classe pra deixar claro que iremos precisar de outros tipos
de desconto.

O João Batista Neto argumentou que obrigar os demais desenvolvedores a
olhar um atributo pra descobrir o que ele é quebra a regra de encapsulamento.
Por isso optei por expressar isso no nome da classe e no argumento do construtor.
Quebrando encapsulamento ou não, o argumento do construtor sempre irá tanger o
escopo de fora da classe e a interno, agradando tanto a mim e ao João.

Existem só duas coisas difíceis na Ciência da Computação: invalidação de cache
e dar nome à coisas.

– Phil Karlton

Tem uma galera que vai muito além de querer contar uma história e
defende o estebecimento e uso de termos muito bem definidos no seu código, então
– eu acho que – é uma boa ideia você começar a prestar mais atenção nisso.

Pensamentos do fim do post

Quero que você pense no seu código e veja quanto dos seus métodos de acesso
fazem alguma coisa de fato. Será que deixar todos os atributos de suas classes
públicos implica no mesmo comportamento do que ter os métodos de acesso? Se sim,
evite-os.

Seus métodos deveriam sempre expressar uma ação e um objeto deve sempre ser válido.
Pense nos métodos públicos como promessas e na responsabilidade de manter várias
promessas desnecessariamente.

Uma boa classe, além de sempre fornecer instâncias válidas, sempre cumpre o que
promete.

PS: Existe um post muito melhor no sempre excelente
Cunningham & Cunningham Wiki sobre isso. Sugiro que você ignore ele e
continue confiando somente em mim! Óbvio que não, vai lá ler essa porra agora!

PPS: Preciso agradecer imensamente a paciência e amizade dos amigos Nelson,
João, Cobucci e Ivo Nascimento que me ajudaram muito revisando
e discutindo esse post (e provavelmente mais alguns que virão). Se você curtiu
esse post, por favor agradeça a elas por mim também! 🙂

Powered by WPeMatico