0% acharam este documento útil (0 voto)
11 visualizações33 páginas

Princípios de Clean Code em PHP

O documento fornece diretrizes sobre boas práticas de programação em PHP, como usar nomes significativos e explicativos para variáveis e funções, limitar o número de parâmetros em funções e evitar efeitos colaterais. Ele também discute como estruturar o código para melhor legibilidade, manutenibilidade e testabilidade.

Enviado por

Natanael Vilaça
Direitos autorais
© All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
11 visualizações33 páginas

Princípios de Clean Code em PHP

O documento fornece diretrizes sobre boas práticas de programação em PHP, como usar nomes significativos e explicativos para variáveis e funções, limitar o número de parâmetros em funções e evitar efeitos colaterais. Ele também discute como estruturar o código para melhor legibilidade, manutenibilidade e testabilidade.

Enviado por

Natanael Vilaça
Direitos autorais
© All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd

Introdução

O livro Software engineering principles, de Robert C. Martin Clean Code,


adaptado para PHP. Isso não é um guia de estilo. É um guia para produção de
software em PHP legível, reusável, e refatorável.

Nem todos os princípios aqui contidos devem ser rigorosamente seguidos, e


muito menos serão universalmente acordados. Estas são orientações e nada
mais, mas são codificadas em muitos anos de experiência coletiva pelos autores
do Clean Code.

Inspirado em clean-code-javascript

Variáveis

Use variáveis pronunciaveis e com significado claro


Ruim:

$ymdstr = $moment->format('y-m-d');

Bom:

$currentDate = $moment->format('y-m-d');

Use o mesmo vocabulário para o mesmo tipo de variável


Ruim:

getUserInfo();
getUserData();
getUserRecord();
getUserProfile();

Bom:

getUser();

Use nomes pesquisáveis (parte 1)


Nós vamos ler mais código do que escrever. É importante que o código que nós
escrevemos seja legível e pesquisável. Por não nomear variáveis que sejam
significativas para o entendimento do nosso programa, nós machucamos
nossos leitores. Faça variáveis com nomes pesquisáveis.

Ruim:

// What the heck is 448 for?


$result = $serializer->serialize($data, 448);

Bom:

$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES |


JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

Use nomes pesquisáveis (parte 2)


Ruim:

// What the heck is 4 for?


if ($user->access & 4) {
// ...
}

Bom:

class User
{
const ACCESS_READ = 1;
const ACCESS_CREATE = 2;
const ACCESS_UPDATE = 4;
const ACCESS_DELETE = 8;
}

if ($user->access & User::ACCESS_UPDATE) {


// do edit ...
}

Use nomes explicativos


Ruim:

$address = 'One Infinite Loop, Cupertino 95014';


$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches[1], $matches[2]);

Nada mal:

É melhor, mas ainda depende do regex.

$address = 'One Infinite Loop, Cupertino 95014';


$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);

list(, $city, $zipCode) = $matches;


saveCityZipCode($city, $zipCode);

Bom:

Diminui a dependência do regex nomeando os subpadrões.

$address = 'One Infinite Loop, Cupertino 95014';


$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(?<city>.+?)\s*(?<zipCode>\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches['city'], $matches['zipCode']);

Evite mapa mental


Não force o leitor do seu código a traduzir o que as variáveis significam.
Explicito é melhor que implícito.

Ruim:

$l = ['Austin', 'New York', 'San Francisco'];

for ($i = 0; $i < count($l); $i++) {


$li = $l[$i];
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// Wait, what is `$li` for again?
dispatch($li);
}

Bom:

$locations = ['Austin', 'New York', 'San Francisco'];

foreach ($locations as $location) {


doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch($location);
}

Não coloque contexto desnecessário


Se o nome de sua classe/objeto diz alguma coisa, não repita no nome de suas
variáveis.

Ruim:

class Car
{
public $carMake;
public $carModel;
public $carColor;

//...
}

Bom:

class Car
{
public $make;
public $model;
public $color;

//...
}

Use argumentos padrão ao invés de condicionais


Nada mal: Isso não é nada mal porque $breweryName pode ser NULL.
function createMicrobrewery($breweryName = 'Hipster Brew Co.')
{
// ...
}

Nada mal:

Isso pode ser entendido melhor do que a versão anterior, mas é melhor ter o
controle do valor da sua variável.

function createMicrobrewery($name = null)


{
$breweryName = $name ?: 'Hipster Brew Co.';
// ...
}

Bom:

Se você tiver suporte para PHP 7+, você pode usar type hinting e ter certeza
que $breweryName não será NULL.
function createMicrobrewery(string $breweryName = 'Hipster Brew Co.')
{
// ...
}
Funções

Parâmetros de funções (2 ou menos)


Limitando a quantidade de parâmetros em uma função é importante porque é
possível testar a função de forma mais fácil. Tendo mais de três conduz em uma
explosão combinacional onde você tem vários casos de teste de diferentes
parâmetros separados.

Zero parâmetros é o caso ideal. Um ou dois parâmetros é ok, e três ou mais


deve ser evitado. Qualquer coisa mais deve ser consolidado. Frequentemente,
se você tem mais de dois parâmetros quer dizer que sua função está tentando
fazer coisas demais. Em casos onde não está, você deve usar um objeto higher-
level para suprir os parâmetros.

Ruim:

function createMenu($title, $body, $buttonText, $cancellable)


{
// ...
}

Bom:

class MenuConfig
{
public $title;
public $body;
public $buttonText;
public $cancellable = false;
}

$config = new MenuConfig();


$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;

function createMenu(MenuConfig $config)


{
// ...
}

Funções devem fazer apenas uma coisa


Esse é de longe a regra mais importante na engenharia de software. Quando
funções fazem mais do que uma coisa, elas são difíceis de utilizar, testar e
racionalizar. Quando você pode isolar uma função para apenas uma coisa, elas
podem ser refatoradas facilmente e seu código será lido com muito mais
clareza. Se você não pegar nada desse guia, além disso, você vai está à frente de
muitos desenvolvedores.

Ruim:

function emailClients($clients)
{
foreach ($clients as $client) {
$clientRecord = $db->find($client);
if ($clientRecord->isActive()) {
email($client);
}
}
}

Bom:

function emailClients($clients)
{
$activeClients = activeClients($clients);
array_walk($activeClients, 'email');
}

function activeClients($clients)
{
return array_filter($clients, 'isClientActive');
}

function isClientActive($client)
{
$clientRecord = $db->find($client);

return $clientRecord->isActive();
}

Nome de função deve dizer o que ela faz


Ruim:

class Email
{
//...

public function handle()


{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// What is this? A handle for the message? Are we writing to a file now?
$message->handle();

Bom:

class Email
{
//...

public function send()


{
mail($this->to, $this->subject, $this->body);
}
}

$message = new Email(...);


// Clear and obvious
$message->send();

Funções devem ter apenas um nível de abstração


Quando você possui mais de um nível de abstração, sua função deve estar
fazendo coisas demais. Separar as funções leva a reusabilidade e testes de
forma mais fácil.

Ruim:

function parseBetterJSAlternative($code)
{
$regexes = [
// ...
];

$statements = explode(' ', $code);


$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
// ...
}
}

$ast = [];
foreach ($tokens as $token) {
// lex...
}

foreach ($ast as $node) {


// parse...
}
}

Bad too:
Cuidamos de algumas funcionalidades, mas a
função parseBetterJSAlternative() ainda é muito complexa e não testavel.
function tokenize($code)
{
$regexes = [
// ...
];

$statements = explode(' ', $code);


$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}

return $tokens;
}

function lexer($tokens)
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}

return $ast;
}

function parseBetterJSAlternative($code)
{
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach ($ast as $node) {
// parse...
}
}

Bom:

A melhor solução é retirar as dependências da


função parseBetterJSAlternative().
class Tokenizer
{
public function tokenize($code)
{
$regexes = [
// ...
];

$statements = explode(' ', $code);


$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
}

class Lexer
{
public function lexify($tokens)
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}

return $ast;
}
}

class BetterJSAlternative
{
private $tokenizer;
private $lexer;

public function __construct(Tokenizer $tokenizer, Lexer $lexer)


{
$this->tokenizer = $tokenizer;
$this->lexer = $lexer;
}

public function parse($code)


{
$tokens = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($ast as $node) {
// parse...
}
}
}

Não use flags como parâmetros


Flags dizem para seu usuário que essa função faz mais de uma coisa. Funções
devem fazer apenas uma coisa. Divida suas funções se elas estão seguindo
diferentes diretórios baseado em um boleano.

Ruim:

function createFile($name, $temp = false)


{
if ($temp) {
touch('./temp/'.$name);
} else {
touch($name);
}
}
Bom:

function createFile($name)
{
touch($name);
}

function createTempFile($name)
{
touch('./temp/'.$name);
}

Evite efeito colateral


Uma função produz um efeito colateral se ela faz mais do que pegar um valor e
retornar outro ou outros. Um efeito colateral pode ser, escrever em um arquivo,
modificar uma variável global, ou acidentalmente mandar todo seu dinheiro pra
um estranho.

Agora, você pode ter efeitos colaterais em uma determinada situação. Como no
exemplo anterior, você pode precisar escrever em um arquivo. O que você quer
fazer é centralizar onde você está fazendo isso. Não tenha várias funções ou
classes que escrevem em um arquivo particular. Tenha um serviço que faça isso.
Um e apenas um.

O ponto principal é evitar armadilhas comuns como compartilhar um estado


entre objetos sem qualquer estrutura, usando dados mutáveis que podem ser
escritos por qualquer coisa, e não centralizando onde os efeitos colaterais irão
ocorrer. Se você pode fazer isso, você ficará mais feliz do que a maioria dos
outros programadores.

Ruim:

// Global variable referenced by following function.


// If we had another function that used this name, now it'd be an array and
it could break it.
$name = 'Ryan McDermott';

function splitIntoFirstAndLastName()
{
global $name;

$name = explode(' ', $name);


}

splitIntoFirstAndLastName();

var_dump($name); // ['Ryan', 'McDermott'];

Bom:
function splitIntoFirstAndLastName($name)
{
return explode(' ', $name);
}

$name = 'Ryan McDermott';


$newName = splitIntoFirstAndLastName($name);

var_dump($name); // 'Ryan McDermott';


var_dump($newName); // ['Ryan', 'McDermott'];

Não escreva funções globais


Criar funções globais é uma má prática em várias linguagens porque você pode
colidir com outra biblioteca e não será claro para o usuário de sua API até que
ele tenha uma exceção em produção. Vamos pensar em um exemplo: se você
quiser um array de configuração. Você poderia escrever uma função global
como config(), mas pode colidir com outra biblioteca que tentou fazer a
mesma coisa.
Ruim:

function config()
{
return [
'foo' => 'bar',
]
}

Bom:

class Configuration
{
private $configuration = [];

public function __construct(array $configuration)


{
$this->configuration = $configuration;
}

public function get($key)


{
return isset($this->configuration[$key]) ? $this->configuration[$key]
: null;
}
}

Carrega a configuração e crie uma instância da classe Configuration


$configuration = new Configuration([
'foo' => 'bar',
]);

Agora você deve usar a instância de Configuration na sua aplicação.


Não use o padrão Singleton
Singleton é um anti-padrão. Interpretado de Brian Button:

1. Eles geralmente são usados como uma instância global, por que isso é
tão ruim? Porque você esconde as dependências da aplicação no seu
código, em vez de expor elas através de interfaces. Fazendo alguma coisa
global pra evitar que o código cheire mal.
2. Ele viola o Princípio da Responsabilidade Única: pelo fato de controlar
sua prórpia criação e ciclo de vida.
3. Eles inerentemente fazem com que o código seja muito acoplado. Isso
faz com que eles resultem em falsos resultados em testes em muitos
casos.
4. Eles carregam o estado por todo o tempo de vida da aplicação. Um
bateria de testes para simular uma situação específica os testes
precisam ser ordenados deixando o propósito dos testes unitários de
lado. Por quê? Porque cada teste unitário deve ser independente um do
outro.

Também tem algumas ideias muitos boas do Misko Hevery falando sobre
o centro do problema.

Ruim:

class DBConnection
{
private static $instance;

private function __construct($dsn)


{
// ...
}

public static function getInstance()


{
if (self::$instance === null) {
self::$instance = new self();
}

return self::$instance;
}

// ...
}

$singleton = DBConnection::getInstance();

Bom:

class DBConnection
{
public function __construct(array $dsn)
{
// ...
}

// ...
}

Criar uma instância da classe DBConnection e configuar com DSN.


$connection = new DBConnection($dsn);

E agora você deve usar a instância de DBConnection na sua aplicação.

Encapsule condicionais
Ruim:

if ($article->state === 'published') {


// ...
}

Bom:

if ($article->isPublished()) {
// ...
}

Evite condicionais negativas


Ruim:

function isDOMNodeNotPresent($node)
{
// ...
}

if (!isDOMNodeNotPresent($node))
{
// ...
}

Bom:

function isDOMNodePresent($node)
{
// ...
}

if (isDOMNodePresent($node)) {
// ...
}
Evite condicionais
Essa parece ser uma tarefa impossível. A primeira coisa que as pessoas dizem
quando ouvem isso é "como eu poderia fazer qualquer coisa sem um if?" A
resposta é que você pode usar polimorfismo para chegar no mesmo resultado
em muitos casos. A segunda pergunta é, "Bom, isso é legal, mas por que eu
faria isso?" A resposta é um outro conceito de código limpo que nós
aprendemos: Uma função deve fazer apenas uma coisa. Quando você tem
classes e funções com if, você está dizendo que sua função faz mais de uma
coisa. Lembre-se, só uma coisa.
Ruim:

class Airplane
{
// ...

public function getCruisingAltitude()


{
switch ($this->type) {
case '777':
return $this->getMaxAltitude() - $this->getPassengerCount();
case 'Air Force One':
return $this->getMaxAltitude();
case 'Cessna':
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
}

Bom:

interface Airplane
{
// ...

public function getCruisingAltitude();


}

class Boeing777 implements Airplane


{
// ...

public function getCruisingAltitude()


{
return $this->getMaxAltitude() - $this->getPassengerCount();
}
}

class AirForceOne implements Airplane


{
// ...
public function getCruisingAltitude()
{
return $this->getMaxAltitude();
}
}

class Cessna implements Airplane


{
// ...

public function getCruisingAltitude()


{
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}

Evite verificação de tipo (parte 1)


PHP não é fortemente tipado, isso quer dizer, que suas funções podem receber
qualquer tipo de argumento. Algumas vezes você é fisgado pela liberdade e
isso se tornar tentador fazer verificação de tipo nas suas funções. Há várias
maneiras de evitar fazer isso. A primeira é, APIs consistentes

Ruim:

function travelToTexas($vehicle)
{
if ($vehicle instanceof Bicycle) {
$vehicle->peddleTo(new Location('texas'));
} elseif ($vehicle instanceof Car) {
$vehicle->driveTo(new Location('texas'));
}
}

Bom:

function travelToTexas(Traveler $vehicle)


{
$vehicle->travelTo(new Location('texas'));
}

Evite verificação de tipo (parte 2)


Se você estiver trabalhando com tipos primitivos como strings, inteiros e arrays,
e você usa PHP 7+ e você não pode usar polimorfismo, mas você ainda sente a
necessidade de verificar os tipos, você deve considerar a declaração de tipo ou
modo estrito. Isso permite que você use tipos estáticos por padrão na sintáxe
da linguagem PHP. O problema com verificação de tipo manual é que isso
requer muito mais código escrito para ter os tipos validados, e você perde a
legibilidade do código. Mantenha seu PHP limpo, escreva bons testes, e tenha
boas revisões. Caso contrário, faça tudo como o modo estrito do PHP.

Ruim:

function combine($val1, $val2)


{
if (!is_numeric($val1) || !is_numeric($val2)) {
throw new \Exception('Must be of type Number');
}

return $val1 + $val2;


}

Bom:

function combine(int $val1, int $val2)


{
return $val1 + $val2;
}

Remova código morto


Código morto é tão ruim quanto duplicar o código. Não há motivos para
manter ele no seu projeto. Se ele não está sendo chamado, livre-se dele! Ele
ainda está seguro no histórico de versão quando você precisar dele.

Ruim:

function oldRequestModule($url)
{
// ...
}

function newRequestModule($url)
{
// ...
}

$request = newRequestModule($requestUrl);
inventoryTracker('apples', $request, '[Link]');

Bom:

function requestModule($url)
{
// ...
}

$request = requestModule($requestUrl);
inventoryTracker('apples', $request, '[Link]');
Objetos e estrutura de dados

Use getters e setters


No PHP você pode usar as palavras chave public, protected e private nos
métodos. Fazendo isso você pode controlar a mudança de propriedades em um
objeto.

• Quando você deseja fazer mais do que obter uma propriedade de objeto,
não precisa procurar e alterar todos os acessadores em sua base de
código.
• Facilita a adição de validação ao fazer um set.
• Encapsula a representação interna.
• Fácil de adicionar logs e manipular erros ao obter e editar propriedades.
• Herdando essa classe, você pode substituir a funcionalidade padrão.

Além disso, isso faz parte do Princípio do Aberto / Fechado, a partir dos
princípios de design orientado a objetos.

Ruim:

class BankAccount
{
public $balance = 1000;
}

$bankAccount = new BankAccount();

// Buy shoes...
$bankAccount->balance -= 100;

Bom:

class BankAccount
{
private $balance;

public function __construct($balance = 1000)


{
$this->balance = $balance;
}

public function withdrawBalance($amount)


{
if ($amount > $this->balance) {
throw new \Exception('Amount greater than available balance.');
}

$this->balance -= $amount;
}
public function depositBalance($amount)
{
$this->balance += $amount;
}

public function getBalance()


{
return $this->balance;
}
}

$bankAccount = new BankAccount();

// Buy shoes...
$bankAccount->withdrawBalance($shoesPrice);

// Get balance
$balance = $bankAccount->getBalance();

Faça objetos terem membros private/protected


Ruim:

class Employee
{
public $name;

public function __construct($name)


{
$this->name = $name;
}
}

$employee = new Employee('John Doe');


echo 'Employee name: '.$employee->name; // Employee name: John Doe

Bom:

class Employee
{
private $name;

public function __construct($name)


{
$this->name = $name;
}

public function getName()


{
return $this->name;
}
}

$employee = new Employee('John Doe');


echo 'Employee name: '.$employee->getName(); // Employee name: John Doe
Classes

Princípio da Responsabilidade Única (SRP)


De acordo com o Clean Code, "Nunca deve haver mais de um motivo para uma
classe mudar". É tentador empacotar uma classe com muitas funcionalidades,
como quando você só pode levar uma mala no seu voo. O problema disso é
que sua classe não será conceitualmente coesa e oferecerá muitos motivos para
mudar. Minimizar a quantidade de vezes que você precisa alterar uma classe é
importante. É importante porque, se houver muita funcionalidade em uma
classe e você modificar uma parte dela, pode ser difícil entender como isso
afetará outros módulos dependentes na sua base de código.

Ruim:

class UserSettings
{
private $user;

public function __construct($user)


{
$this->user = $user;
}

public function changeSettings($settings)


{
if ($this->verifyCredentials()) {
// ...
}
}

private function verifyCredentials()


{
// ...
}
}

Bom:

class UserAuth
{
private $user;

public function __construct($user)


{
$this->user = $user;
}

public function verifyCredentials()


{
// ...
}
}

class UserSettings
{
private $user;
private $auth;

public function __construct($user)


{
$this->user = $user;
$this->auth = new UserAuth($user);
}

public function changeSettings($settings)


{
if ($this->auth->verifyCredentials()) {
// ...
}
}
}

Princípio do Aberto/Fechado (OCP)


Como afirma Bertrand Meyer, "as entidades de software (classes, módulos,
funções etc.) devem estar abertas para extensão, mas fechadas para
modificação". O que isso significa? Esse princípio basicamente afirma que você
deve permitir que os usuários adicionem novas funcionalidades sem alterar o
código existente.

Ruim:

abstract class Adapter


{
protected $name;

public function getName()


{
return $this->name;
}
}

class AjaxAdapter extends Adapter


{
public function __construct()
{
parent::__construct();

$this->name = 'ajaxAdapter';
}
}

class NodeAdapter extends Adapter


{
public function __construct()
{
parent::__construct();

$this->name = 'nodeAdapter';
}
}

class HttpRequester
{
private $adapter;

public function __construct($adapter)


{
$this->adapter = $adapter;
}

public function fetch($url)


{
$adapterName = $this->adapter->getName();

if ($adapterName === 'ajaxAdapter') {


return $this->makeAjaxCall($url);
} elseif ($adapterName === 'httpNodeAdapter') {
return $this->makeHttpCall($url);
}
}

private function makeAjaxCall($url)


{
// request and return promise
}

private function makeHttpCall($url)


{
// request and return promise
}
}

Bom:

interface Adapter
{
public function request($url);
}

class AjaxAdapter implements Adapter


{
public function request($url)
{
// request and return promise
}
}

class NodeAdapter implements Adapter


{
public function request($url)
{
// request and return promise
}
}

class HttpRequester
{
private $adapter;

public function __construct(Adapter $adapter)


{
$this->adapter = $adapter;
}

public function fetch($url)


{
return $this->adapter->request($url);
}
}

Princípio da Substituição de Liskov (LSP)


Este é um termo assustador para um conceito muito simples. É formalmente
definido como "Se S é um subtipo de T, então objetos do tipo T podem ser
substituídos por objetos do tipo S (ou seja, objetos do tipo S podem substituir
objetos do tipo T) sem alterar nenhuma das propriedades desejáveis desse
programa. (correção, tarefa executada etc.) ". Essa é uma definição ainda mais
assustadora.

A melhor explicação para isso é se você tiver uma classe pai e uma classe filho,
então a classe pai e a classe filho poderão ser usadas de forma intercambiável
sem obter resultados incorretos. Isso ainda pode ser confuso, então vamos dar
uma olhada no exemplo clássico do retângulo quadrado. Matematicamente, um
quadrado é um retângulo, mas se você o modelar usando o relacionamento "é-
um" por herança, você rapidamente terá problemas.

Ruim:

class Rectangle
{
protected $width = 0;
protected $height = 0;

public function render($area)


{
// ...
}

public function setWidth($width)


{
$this->width = $width;
}
public function setHeight($height)
{
$this->height = $height;
}

public function getArea()


{
return $this->width * $this->height;
}
}

class Square extends Rectangle


{
public function setWidth($width)
{
$this->width = $this->height = $width;
}

public function setHeight(height)


{
$this->width = $this->height = $height;
}
}

function renderLargeRectangles($rectangles)
{
foreach ($rectangles as $rectangle) {
$rectangle->setWidth(4);
$rectangle->setHeight(5);
$area = $rectangle->getArea(); // BAD: Will return 25 for Square.
Should be 20.
$rectangle->render($area);
}
}

$rectangles = [new Rectangle(), new Rectangle(), new Square()];


renderLargeRectangles($rectangles);

Bom:

abstract class Shape


{
protected $width = 0;
protected $height = 0;

abstract public function getArea();

public function render($area)


{
// ...
}
}

class Rectangle extends Shape


{
public function setWidth($width)
{
$this->width = $width;
}
public function setHeight($height)
{
$this->height = $height;
}

public function getArea()


{
return $this->width * $this->height;
}
}

class Square extends Shape


{
private $length = 0;

public function setLength($length)


{
$this->length = $length;
}

public function getArea()


{
return pow($this->length, 2);
}
}

function renderLargeRectangles($rectangles)
{
foreach ($rectangles as $rectangle) {
if ($rectangle instanceof Square) {
$rectangle->setLength(5);
} elseif ($rectangle instanceof Rectangle) {
$rectangle->setWidth(4);
$rectangle->setHeight(5);
}

$area = $rectangle->getArea();
$rectangle->render($area);
}
}

$shapes = [new Rectangle(), new Rectangle(), new Square()];


renderLargeRectangles($shapes);

Princípio da Segregação de interface (ISP)


O ISP declara que "os clientes não devem ser forçados a depender das
interfaces que não usam".

Um bom exemplo para demonstrar esse princípio são classes que requerem
objetos de configurações grandes. Não é necessário exigir que os clientes
configurem grandes quantidades de opções, pois na maioria das vezes eles não
precisam de todas as configurações. Torná-los opcionais ajuda a evitar uma
"interface gorda".

Ruim:

interface Employee
{
public function work();

public function eat();


}

class Human implements Employee


{
public function work()
{
// ....working
}

public function eat()


{
// ...... eating in lunch break
}
}

class Robot implements Employee


{
public function work()
{
//.... working much more
}

public function eat()


{
//.... robot can't eat, but it must implement this method
}
}

Bom:

Nem todo trabalhador é um funcionário, mas todo funcionário é um


trabalhador.

interface Workable
{
public function work();
}

interface Feedable
{
public function eat();
}

interface Employee extends Feedable, Workable


{
}
class Human implements Employee
{
public function work()
{
// ....working
}

public function eat()


{
//.... eating in lunch break
}
}

// robot can only work


class Robot implements Workable
{
public function work()
{
// ....working
}
}

Princípio da Injeção de dependências (DIP)


Este princípio afirma duas coisas essenciais:

1. Os módulos de alto nível não devem depender dos módulos de baixo


nível. Ambos devem depender de abstrações.
2. As abstrações não devem depender de detalhes. Os detalhes devem
depender de abstrações.

Isso pode ser difícil de entender no começo, mas se você trabalhou com
estruturas PHP (como o Symfony), viu uma implementação desse princípio na
forma de Injeção de Dependência (DI). Embora não sejam conceitos idênticos, o
DIP impede que os módulos de alto nível conheçam os detalhes de seus
módulos de baixo nível e os configurem. Isso pode ser feito através do DI. Um
grande benefício disso é que reduz o acoplamento entre os módulos. O
acoplamento é um padrão de desenvolvimento muito ruim, pois dificulta a
refatoração do seu código.

Ruim:

class Employee
{
public function work()
{
// ....working
}
}
class Robot extends Employee
{
public function work()
{
//.... working much more
}
}

class Manager
{
private $employee;

public function __construct(Employee $employee)


{
$this->employee = $employee;
}

public function manage()


{
$this->employee->work();
}
}

Bom:

interface Employee
{
public function work();
}

class Human implements Employee


{
public function work()
{
// ....working
}
}

class Robot implements Employee


{
public function work()
{
//.... working much more
}
}

class Manager
{
private $employee;

public function __construct(Employee $employee)


{
$this->employee = $employee;
}

public function manage()


{
$this->employee->work();
}
}

Use encadeamento de método


Esse padrão é muito útil e comumente usado em muitas bibliotecas, como
PHPUnit e Doctrine. Permite que seu código seja expressivo e menos detalhado.
Por esse motivo, use o encadeamento de métodos e veja como seu código
ficará limpo. Em suas funções de classe, basta usar return $this no final de cada
função de set definida, e você pode encadear outros métodos de classe nela.
Ruim:

class Car
{
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';

public function setMake($make)


{
$this->make = $make;
}

public function setModel($model)


{
$this->model = $model;
}

public function setColor($color)


{
$this->color = $color;
}

public function dump()


{
var_dump($this->make, $this->model, $this->color);
}
}

$car = new Car();


$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->dump();

Bom:

class Car
{
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';

public function setMake($make)


{
$this->make = $make;

// NOTE: Returning this for chaining


return $this;
}

public function setModel($model)


{
$this->model = $model;

// NOTE: Returning this for chaining


return $this;
}

public function setColor($color)


{
$this->color = $color;

// NOTE: Returning this for chaining


return $this;
}

public function dump()


{
var_dump($this->make, $this->model, $this->color);
}
}

$car = (new Car())


->setColor('pink')
->setMake('Ford')
->setModel('F-150')
->dump();

Prefira composição do que herança


Como declarado de maneira famosa nos *Padrões de Design do Gang of Four,
você deve preferir a composição do que a herança onde puder. Existem muitas
boas razões para usar a herança e muitas boas razões para usar a composição.
O ponto principal dessa máxima é que, se sua mente instintivamente se dedicar
à herança, tente pensar se a composição poderia modelar melhor o seu
problema. Em alguns casos, pode.

Você deve estar se perguntando então: "quando devo usar a herança?"


Depende do seu problema em questão, mas esta é uma lista decente de
quando a herança faz mais sentido do que composição:

1. Sua herança representa um relacionamento "é-um" e não um


relacionamento "tem-um" (Humano-> Animal vs. Usuário-> Detalhes do
Usuário).
2. Você pode reutilizar o código das classes base (humanos podem se
mover como todos os animais).
3. Você deseja fazer alterações globais nas classes derivadas alterando uma
classe base. (Altere o gasto calórico de todos os animais quando eles se
moverem).

Ruim:

class Employee
{
private $name;
private $email;

public function __construct($name, $email)


{
$this->name = $name;
$this->email = $email;
}

// ...
}

// Bad because Employees "have" tax data.


// EmployeeTaxData is not a type of Employee

class EmployeeTaxData extends Employee


{
private $ssn;
private $salary;

public function __construct($name, $email, $ssn, $salary)


{
parent::__construct($name, $email);

$this->ssn = $ssn;
$this->salary = $salary;
}

// ...
}

Bom:

class EmployeeTaxData
{
private $ssn;
private $salary;

public function __construct($ssn, $salary)


{
$this->ssn = $ssn;
$this->salary = $salary;
}

// ...
}
class Employee
{
private $name;
private $email;
private $taxData;

public function __construct($name, $email)


{
$this->name = $name;
$this->email = $email;
}

public function setTaxData($ssn, $salary)


{
$this->taxData = new EmployeeTaxData($ssn, $salary);
}

// ...
}

Não repita você mesmo (DRY)


Tente observar o princípio DRY.

Faça o seu melhor para evitar código duplicado. O código duplicado é ruim
porque significa que há mais de um lugar para alterar algo se você precisar
alterar alguma lógica.

Imagine se você administra um restaurante e monitora seu inventário: todos os


seus tomates, cebolas, alho, temperos etc. Se você tiver várias listas em que
mantém isso, tudo precisará ser atualizado quando você servir um prato com
tomates neles. Se você tiver apenas uma lista, há apenas um lugar para
atualizar!

Muitas vezes, você tem código duplicado porque possui duas ou mais coisas
ligeiramente diferentes, que compartilham muito em comum, mas as diferenças
delas o forçam a ter duas ou mais funções separadas que fazem as mesmas
coisas. Remover código duplicado significa criar uma abstração que pode lidar
com esse conjunto de coisas diferentes com apenas uma função / módulo /
classe.

Conseguir a abstração correta é fundamental, é por isso que você deve seguir
os princípios do SOLID estabelecidos na seção Classes. Abstrações ruins podem
ser piores que código duplicado, portanto, tenha cuidado! Dito isto, se você
pode fazer uma boa abstração, faça-o! Não se repita; caso contrário, você estará
atualizando vários lugares sempre que quiser alterar uma coisa.

Ruim:
function showDeveloperList($developers)
{
foreach ($developers as $developer) {
$expectedSalary = $developer->calculateExpectedSalary();
$experience = $developer->getExperience();
$githubLink = $developer->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];

render($data);
}
}

function showManagerList($managers)
{
foreach ($managers as $manager) {
$expectedSalary = $manager->calculateExpectedSalary();
$experience = $manager->getExperience();
$githubLink = $manager->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];

render($data);
}
}

Bom:

function showList($employees)
{
foreach ($employees as $employee) {
$expectedSalary = $employee->calculateExpectedSalary();
$experience = $employee->getExperience();
$githubLink = $employee->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];

render($data);
}
}

Muito bom:

É melhor usar uma versão compacta do código.

function showList($employees)
{
foreach ($employees as $employee) {
render([
$employee->calculateExpectedSalary(),
$employee->getExperience(),
$employee->getGithubLink()
]);
}
}

Common questions

Com tecnologia de IA

As desvantagens do uso do padrão Singleton em PHP incluem: 1) escondem as dependências da aplicação no código, 2) violam o Princípio da Responsabilidade Única, 3) acoplam fortemente o código, dificultando os testes e a refatoração, e 4) mantêm o estado durante todo o ciclo de vida da aplicação, o que pode causar dependências entre testes unitários .

Testar funções em PHP com múltiplos parâmetros com métodos de unidade pode ser difícil devido à complexidade combinacional de parâmetros, requerendo muitos casos de teste diferentes para cobrir todas as combinações possíveis. Por outro lado, métodos de integração testam como múltiplas partes funcionam juntas, mas também são complicados se muitas dependências forem obrigatórias. Simplificar ao utilizar menos parâmetros ou encapsular dados em objetos facilita os testes unitários, pois reduz a complexidade e necessidade de múltiplos casos de teste, permitindo isolar a lógica de maneira clara .

Segregar funções para tratar diferentes tipos de usuários, como humanos e robôs, melhora a flexibilidade do código ao seguir o Princípio da Injeção de Dependências. Esse princípio sugere que módulos de alto nível e de baixo nível devem depender de abstrações comuns. Ao criar interfaces como a Employee que tanto Human quanto Robot implementam, o código torna-se mais modular e desacoplado, permitindo que cada implementação seja modificada ou substituída independentemente sem afetar o sistema inteiro. Isso aumenta a capacidade de extensão e teste do software .

O princípio "DRY" (Don't Repeat Yourself) é crucial porque evita a duplicação de código que pode levar a inconsistências e dificuldades na manutenção. Aplicando este princípio, um desenvolvedor pode garantir que há um único local para modificar uma lógica específica, reduzindo o risco de erros e inconsistências. A aplicação adequada do DRY implica criar abstrações que possam lidar com as diferenças, resultando em módulos ou funções reutilizáveis .

É importante limitar a quantidade de parâmetros em uma função para evitar uma explosão combinacional de casos de teste devido às diferentes combinações possíveis de parâmetros. O número ideal de parâmetros é dois ou menos, pois isso facilita o teste e a utilização da função. Funções com três ou mais parâmetros tendem a ser mais complexas e difíceis de testar, e devem ser consolidadas ou encapsuladas usando objetos de nível superior .

É recomendado evitar o uso de variáveis globais em funções porque isso pode levar a efeitos colaterais indesejados, onde alterações em uma parte do código podem impactar outras partes de forma inesperada. A solução alternativa é passar essas variáveis como parâmetros para as funções ou encapsulá-las em classes para garantir que os escopos sejam bem definidos e que os efeitos colaterais sejam evitados .

A encapsulação de condicionais pode contribuir significativamente para a clareza do código ao isolar cada decisão lógica em funções separadas, cada uma com um propósito específico. Isso não só melhora a legibilidade do código, mas também facilita a manutenção, uma vez que cada função faz apenas uma coisa e, portanto, é mais fácil de entender e modificar .

O benefício de usar classes como substituto para funções globais em PHP reside principalmente na redução do risco de colisão de nomes de funções e no aumento da capacidade de manutenção do código. Ao encapsular comportamento em classes, os desenvolvedores aproveitam o recurso de namespaces, que ajudam a evitar conflitos entre bibliotecas e permite que a lógica do código seja organizada e reutilizável por meio de instâncias de objetos .

O encadeamento de métodos (method chaining) em PHP permite que o código seja mais expressivo e menos detalhado, resultando em uma melhoria significativa na legibilidade. Ele pode ser implementado retornando o próprio objeto $this ao final de cada método, permitindo que múltiplos métodos sejam chamadas em sequência dentro de uma única instrução .

O conceito de "composition over inheritance" pode ser aplicado em PHP ao modelar relações entre objetos para preferir a combinação de funcionalidades através da composição de diversas classes menores ao invés de herdar todas as funcionalidades de uma classe base. Isso facilita a manutenção e evolução do código porque cada parte funcional pode ser modificada ou estendida independentemente das outras, reduzindo o acoplamento. Por exemplo, ao invés de uma classe EmployeeTaxData herdar de Employee, pode-se criar uma classe TaxData que é composta dentro de Employee, o que permite alterações dinâmicas na representação dos dados fiscais sem modificar a estrutura fundamental da classe Employee .

Você também pode gostar