Boas práticas na prática

Boas práticas na prática

Slides originais: https://viniciuscampitelli.com/slides/boas-praticas/

Para garantirmos a melhor arquitetura de código possível, precisamos aplicar diversos conceitos de qualidade que são difíceis de serem aplicados na prática. E isso afeta muito a manutenibilidade de nosso sistema. Iremos ver exemplos reais de como utilizar Object Calisthenics, SOLID e outros bons princípios para termos códigos mais limpos e evitarmos problemas futuros.

375596b28a94ecfaec5d63ff64c7f948?s=128

Vinícius Campitelli

October 06, 2018
Tweet

Transcript

  1. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 1/60 BOAS BOAS PRÁTICAS

    PRÁTICAS NA PRÁTICA NA PRÁTICA 1
  2. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 2/60 QUEM SOU EU?

    QUEM SOU EU? Vinícius Campitelli • MT4 Tecnologia • @MediaPost • Curseduca vcampitelli.github.io 2
  3. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 3/60 SOLID SOLID 3

    . 1
  4. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 4/60 Single Responsibility Principle

    Open Closed Principle Liskov Substitution Principle Interface Segregation Principle Dependency Inversion Principle 3 . 2
  5. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 5/60 SINGLE RESPONSIBILITY PRINCIPLE

    SINGLE RESPONSIBILITY PRINCIPLE uma classe só pode ter uma única razão para mudar 3 . 3
  6. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 6/60 class Report {

    public function fetch(); public function asHtml(); public function asJson(); } 3 . 4
  7. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 7/60 COMO RESOLVER? COMO

    RESOLVER? Especialize suas classes dividindo-as até que tenham somente um objetivo 3 . 5
  8. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 8/60 class Report {

    public function fetch(); } class ReportWriter { public function asHtml(Report $report); public function asJson(Report $report); } 3 . 6
  9. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 9/60 OPEN CLOSED PRINCIPLE

    OPEN CLOSED PRINCIPLE entidades devem ser abertas para extensão mas fechadas para modi cação 3 . 7
  10. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 10/60 É possível implementar

    uma nova funcionalidade em seu sistema somente adicionando novas classes ao invés de modi car as existentes? 3 . 8
  11. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 11/60 class ReportExporter {

    public function toCsv(Report $report) { $tempFile = $this->createNewFile(); $handler = fopen($tempFile, 'w'); foreach ($report as $row) { fputcsv($handler, $row); } fclose($handler); return $tempFile; } public function toXml(Report $report) { /* ... */ } } 3 . 9
  12. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 12/60 COMO RESOLVER? COMO

    RESOLVER? Crie estruturas polimór cas. Em PHP, utilize interfaces! 3 . 10
  13. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 13/60 class ReportExporter {

    public function export(Report $report, WriterInterface $writer) { $writer->init(); foreach ($report as $row) { $writer->write($row); } return $writer->finish(); } } interface WriterInterface { public function init(); public function write($row); public function finish(); } class CsvWriter implements WriterInterface { /* ... */ } class XmlWriter implements WriterInterface { /* ... */ } // Em meu sistema, posso trocar o Writer sem medo! $reportExporter->export($report, new CsvWriter()); $reportExporter->export($report, new XmlWriter()); 3 . 11
  14. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 14/60 LISKOV SUBSTITUTION PRINCIPLE

    LISKOV SUBSTITUTION PRINCIPLE objetos em um programa deveriam ser substituíveis por instâncias de seus subtipos sem alterar a exatidão do programa 3 . 12
  15. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 15/60 Meu sistema precisa

    funcionar corretamente ao trocar o tipo do Writer $writer = new CsvWriter(); $writer->write(/* ... */); $writer = new FileWriter(); $writer->write(/* ... */); 3 . 13
  16. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 16/60 Pré-condições não podem

    ser fortalecidas em uma subclasse class DatabaseAdapter { public function query(Statement $statement); } class MyPdoStatement extends Statement { /* ... */ } class PdoDatabaseAdapter extends DatabaseAdapter { // Errado! public function query(MyPdoStatement $statement); } 3 . 14
  17. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 17/60 Pós-condições não podem

    ser enfraquecidas em uma subclasse class Retangulo { public function setAltura($altura) { $this->altura = (float) $altura; } public function setLargura($largura) { $this->largura = (float) $largura; } public function getAltura() { return $this->altura; } public function getLargura() { return $this->largura; } } 3 . 15
  18. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 18/60 function area(Retangulo $retangulo)

    { return $retangulo->getAltura() * $retangulo->getLargura(); } $retangulo = new Retangulo(); $retangulo->setAltura(5); $retangulo->setLargura(4); if (area($retangulo) != 20) { throw new UnexpectedValueException("Algo não está certo"); } 3 . 16
  19. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 19/60 class Quadrado extends

    Retangulo { public function setAltura($altura) { $this->altura = (float) $altura; $this->largura = (float) $altura; } public function setLargura($largura) { $this->altura = (float) $largura; $this->largura = (float) $largura; } } 3 . 17
  20. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 20/60 O que acontece

    ao executar o mesmo código do cálculo de área, mas passando um objeto Quadrado? function area(Retangulo $retangulo) { return $retangulo->getAltura() * $retangulo->getLargura(); } $quadrado = new Quadrado(); $quadrado->setAltura(5); $quadrado->setLargura(4); if (area($quadrado) != 20) { throw new UnexpectedValueException("Algo não está certo"); } 3 . 18
  21. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 21/60 COMO RESOLVER? COMO

    RESOLVER? Herança não deve ser utilizada quando uma subclasse restringe a liberdade imposta na classe principal, mas sim quando adiciona mais detalhes. Ela só é necessária quando há similaridade de comportamento. Senão, use composição. 3 . 19
  22. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 22/60 INTERFACE SEGREGATION PRINCIPLE

    INTERFACE SEGREGATION PRINCIPLE é preferível ter várias interfaces mais especí cas do que uma genérica 3 . 20
  23. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 23/60 interface AveInterface {

    public function bicar(); public function voar(); } 3 . 21
  24. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 24/60 Mas nem toda

    ave voa! class Pinguim implements AveInterface { public function bicar() { /* ... */ } public function voar() { return false; } } 3 . 22
  25. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 25/60 COMO RESOLVER? COMO

    RESOLVER? Segrege suas interfaces ao invés de criar uma que atenda todos os casos interface AveInterface { public function bicar(); } interface VoadorInterface { public function voar(); } class Papagaio implements AveInterface, VoadorInterface { /* ... */ } class Pinguim implements AveInterface { /* ... */ } class Esquilo implements VoadorInterface { /* ... */ } 3 . 23
  26. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 26/60 DEPENDENCY INVERSION PRINCIPLE

    DEPENDENCY INVERSION PRINCIPLE dependa de abstrações (interfaces) ao invés de classes concretas 3 . 24
  27. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 27/60 PSR-15 php- g.org/psr/psr-15

    use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Response\JsonResponse; class ActionHandler implements RequestHandlerInterface { public function handle(ServerRequestInterface $request) : ResponseInterface { return new JsonResponse(['status' => true]); } } 3 . 25
  28. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 28/60 Depender de interfaces

    nos fornece grande exibilidade para mudar partes do sistema sem afetar o funcionamento do todo Design by contract 3 . 26
  29. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 29/60 OBJECT CALISTHENICS OBJECT

    CALISTHENICS 4 . 1
  30. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 30/60 Only One Level

    Of Indentation Per Method Don't Use The ELSE Keyword Wrap All Primitives And Strings First Class Collections One Dot Per Line Don't Abbreviate Keep All Entities Small No Classes With More Than Two Instance Variables No Getters/Setters/Properties 4 . 2
  31. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 31/60 ONLY ONE LEVEL

    OF INDENTATION ONLY ONE LEVEL OF INDENTATION PER METHOD PER METHOD 4 . 3
  32. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 32/60 public function minify(array

    $files) { foreach ($files as $file) { if (!is_file($file)) { throw new RuntimeException("{$file} não é válido"); } switch (pathinfo($file, PATHINFO_EXTENSION)) { case 'js': // ... break; case 'html': // ... break; case 'css': // ... break; } } } 4 . 4
  33. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 33/60 public function minify(array

    $files) { foreach ($files as $file) { $this->parseFile($file); } } protected function parseFile($file) { if (!is_file($file)) { throw new RuntimeException("{$file} não é válido"); } $this->getParserForExtension(pathinfo($file, PATHINFO_EXTENSION)) ->parse($file); } protected function getParserForExtension($extension) { if (isset($this->parsersByExtension[$extension])) { return $this->parsersByExtension[$extension]; } throw new DomainException("Nenhum minificador para {$extension} foi encontrado"); } 4 . 5
  34. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 34/60 DON'T USE THE

    ELSE KEYWORD DON'T USE THE ELSE KEYWORD 4 . 6
  35. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 35/60 public function login($username,

    $password) { $row = $this->dbh->fetch('SELECT * FROM user WHERE username = ?', $username); if (!empty($row)) { if ($row['active']) { if (password_verify($password, $row['password'])) { $_SESSION['loggedIn'] = true; header('Location: /dashboard.php'); return true; } else { header('Location: /login.php?error=invalid'); } } else { header('Location: /login.php?error=inactive'); } } else { $attempts = $this->dbh->fetch('SELECT attempts FROM login_attempts WHERE username = ?', $username); if ($attempts >= self::MAX_LOGIN_ATTEMTPS) { $this->dbh->query('UPDATE login_attempts SET attempts = attempts + 1 WHERE username = ?', $username header('Location: /login.php?error=blocked'); } else { header('Location: /login.php?error=invalid'); 4 . 7
  36. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 36/60 Early return public

    function login($username, $password) { $user = $this->repository->findByUsername($username); if (empty($user)) { $this->checkLoginAttempts($user); return false; } return $this->checkUser($user); } protected function checkUser(User $user) { if (!$user->active) { header('Location: /login.php?error=inactive'); return false; } if (!password_verify($password, $user->password)) { header('Location: /login.php?error=invalid'); return false; } 4 . 8
  37. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 37/60 WRAP ALL PRIMITIVES

    AND STRINGS WRAP ALL PRIMITIVES AND STRINGS 4 . 9
  38. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 38/60 public function parse($email)

    { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Email inválido'); } if (!EmailValidator::checkExistingDomain($email)) { throw new InvalidArgumentException('Email inválido'); } // ... } 4 . 10
  39. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 39/60 Se houver comportamento,

    crie um objeto! class Email { public function __construct($email) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Email inválido'); } // outras validações } } class Example { public function parse(Email $email) { // ... } } 4 . 11
  40. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 40/60 FIRST CLASS COLLECTIONS

    FIRST CLASS COLLECTIONS 4 . 12
  41. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 41/60 Se a classe

    possui uma coleção, ela não deve ter outras propriedades class FilterApplier { protected $data = []; protected $index; public function add(callable $filter); public function remove(callable $filter); public function run(); } 4 . 13
  42. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 42/60 Crie collections! class

    FilterApplier { public function __construct(FilterCollection $filterCollection); public function run(); } class FilterCollection implements Iterator { protected $data = []; public function add(callable $filter); public function remove(callable $filter); } 4 . 14
  43. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 43/60 ONE DOT PER

    LINE ONE DOT PER LINE 4 . 15
  44. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 44/60 Se você precisa

    passar por diversos objetos para chamar um método, signi ca que você precisa saber muito sobre como eles funcionam class Controller { public function run() { if ($this->getRequest()->getHeaders()->get('Accept') == 'application/json') { header('Content-type: application/json'); return json_encode(['status' => true]); } } } 4 . 16
  45. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 45/60 Law of Demeter

    e Tell, Don't Ask class Controller { public function run() { if ($this->getRequest()->needsJson()) { header('Content-type: application/json'); return json_encode(['status' => true]); } } } class Request { public function needsJson() { return $this->getHeaders()->needsJson(); } } class Headers { public function needsJson() { return $this->get('Accept') === 'application/json'; } } 4 . 17
  46. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 46/60 NO GETTERS/SETTERS/PROPERTIES NO

    GETTERS/SETTERS/PROPERTIES 4 . 18
  47. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 47/60 class Login {

    public function login(User $user) { // ... $attempts = $this->loginAttempts->getByUser($user); $this->loginAttempts->setByUser($user, ++$attempts); if ($attempts >= self::MAX_LOGIN_ATTEMTPS) { $this->loginAttempts->blockUserLogin($user); } } } 4 . 19
  48. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 48/60 “Tell, Don't Ask”

    class Login { public function login(User $user) { // ... $this->loginAttempts->touchUser($user); } } 4 . 20
  49. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 49/60 PHPCS PHPCS github.com/squizlabs/PHP_CodeSni

    er Plugin de Object Calisthenics para o PHPCS 5 . 1
  50. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 50/60 Veri ca violações

    do código de acordo com um conjunto de regras 5 . 2
  51. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 51/60 PHPCBF PHPCBF Automaticamente

    corrige algumas violações no código 5 . 3
  52. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 52/60 PHPMD PHPMD phpmd.org

    6 . 1
  53. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 53/60 Equivalente à ferramenta

    PMD do Java, analisa código para detectar diversas anomalias, como: Nomes muito pequenos ou grandes; Códigos muito longos ou complexos; Parâmetros e variáveis inutilizados; e muitos outros 6 . 2
  54. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 54/60 CYCLOMATIC COMPLEXITY CYCLOMATIC

    COMPLEXITY 6 . 3
  55. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 55/60 Complexidade determinada de

    acordo com o número de pontos de decisão (if, while, for e case) + 1 6 . 4
  56. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 56/60 public function minify(array

    $files) { // 1 foreach ($files as $file) { // 2 if (!is_file($file)) { // 3 throw new RuntimeException("{$file} não é válido"); } switch (pathinfo($file, PATHINFO_EXTENSION)) { case 'js': // 4 // ... break; case 'html': // 5 // ... break; case 'css': // 6 // ... break; } } } 6 . 5
  57. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 57/60 Graus de Complexidade

    1-4 — baixa 5-7 — moderada 8-10 — alta 11+ — muito alta 6 . 6
  58. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 58/60 MAIS TÓPICOS MAIS

    TÓPICOS DRY KISS YAGNI Code Smells 7
  59. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 59/60 REFERÊNCIAS REFERÊNCIAS Livro

    "Clean Code", do Uncle Bob imasters.com.br/back-end/solid-com-php eduardopires.net.br/2015/01/solid-teoria-e-pratica hackernoon.com/solid-principles-530b2cc2badf netguru.co/codestories/solid-principles-1-single-responsibility- principle williamdurand.fr/2013/06/03/object-calisthenics eltonminetto.net/2016/06/24/como-melhorar-seus-codigos- usando-object-calisthenics slideshare.net/guilhermeblanco/php-para-adultos-clean-code-e- object-calisthenics github.com/jupeter/clean-code-php 8
  60. 6/13/2019 Boas práticas na prática https://viniciuscampitelli.com/slides/boas-praticas/?print-pdf#/3 60/60 OBRIGADO! OBRIGADO! vcampitelli.github.io

    9