Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Object Calisthenics

Object Calisthenics

Como ter códigos melhores com o uso de Object Calisthenics

Avatar for Gilberto  Bueno

Gilberto Bueno

February 15, 2018

More Decks by Gilberto Bueno

Other Decks in Programming

Transcript

  1. O que é Object Calisthenics? Inventado por Jeff Bay. Faz

    parte do livro The ThoughtWorks Anthology. São 9 exercícios de programação. Não se trata de regras mas sim de um guia.
  2. Quais os benefícios? A prática desses exercícios ajudará a ter

    um código melhor. De fácil manutenção. Facilidade de se testar. Código legível e de fácil compreensão.
  3. public function createPurchaseOrder($parameters) { $order = $this->orderService->create($data); // ... if

    (isset($parameters['products']) && count($parameters['products'])) { $products = $parameters['products']; foreach ($products as $product) { $productVariation = $this->productVariation->find($product['id']); if (!$productVariation->product->unitType->virtual) { // ... if (isset($product['lead_time'])) { $orderRowEtaDays = $product['lead_time']; } else { $orderRowEtaDays = 0; } // ... $orderRow->save($orderRowData); } } } return $order; } EXEMPLO 1
  4. public function createPurchaseOrder($parameters) { $order = $this->orderService->create($data); // ... if

    (isset($parameters['products']) && count($parameters['products'])) { $orderRow = $this->orderRowService->create($parameters['products'], $order); $order->eta_date = $orderRow['order_eta']; } return $order; } EXEMPLO 1
  5. public function productsWithDiscount($products) { $productsWithDiscount = []; if (isset($products) &&

    count($products)) { foreach ($products as $product) { if ($product['has_discount']) { $productsWithDiscount[] = $product; } } } return $productsWithDiscount; } EXEMPLO 2
  6. public function productsWithDiscount($products) { $productsWithDiscount = []; if (isset($products) &&

    count($products)) { foreach ($products as $product) { if ($product['has_discount']) { $productsWithDiscount[] = $product; } } } return $productsWithDiscount; } EXEMPLO 2 remover condições desnecessárias
  7. public function productsWithDiscount($products) { $productsWithDiscount = []; foreach ($products as

    $product) { if ($product['has_discount']) { $productsWithDiscount[] = $product; } } return $productsWithDiscount; } EXEMPLO 2
  8. public function productsWithDiscount($products) { return array_filter($products, function ($product) { return

    $product['has_discount']; }); } EXEMPLO 2 apenas 1 nível de indentação
  9. EXEMPLO 1 public function authenticate($username, $password) { if ($this->isValid($username, $password))

    { $user = $this->userService->authenticate($username, $password); if ($user) { $token = $this->generateToken(); $result = [ 'success' => true, 'token' => $token ]; } } else { $result = [ 'success' => false, 'message' => 'Please check your username and password' ]; } return new JsonResponse($result); }
  10. EXEMPLO 1 public function authenticate($username, $password) { if ($this->isValid($username, $password))

    { $user = $this->userService->authenticate($username, $password); if ($user) { $token = $this->generateToken(); $result = [ 'success' => true, 'token' => $token ]; } } else { $result = [ 'success' => false, 'message' => 'Please check your username and password' ]; } return new JsonResponse($result); } 3 níveis de indentação
  11. EXEMPLO 1 public function authenticate($username, $password) { if ($this->isValid($username, $password))

    { $user = $this->userService->authenticate($username, $password); $result = [ 'success' => true, 'token' => $user['token'] ]; } else { $result = [ 'success' => false, 'message' => 'Please check your username and password' ]; } return new JsonResponse($result); }
  12. Early return Sair o mais rápido de um bloco de

    código. Abordagem defensiva ou otimista.
  13. EXEMPLO 1 public function authenticate($username, $password) { // optimistic approach

    if ($this->isValid($username, $password)) { $user = $this->userService->authenticate($username, $password); return new JsonResponse([ 'success' => true, 'token' => $user['token'] ]); } return new JsonResponse([ 'success' => false, 'message' => 'Please check your username and password' ]); }
  14. EXEMPLO 1 public function authenticate($username, $password) { // defensive approach

    if (!$this->isValid($username, $password)) { return new JsonResponse([ 'success' => false, 'message' => 'Please check your username and password' ]); } $user = $this->userService->authenticate($username, $password); return new JsonResponse([ 'success' => true, 'token' => $user['token'] ]); }
  15. Quais os benefícios? Evita o anti-pattern conhecido como primitive obsession.

    Evita duplicidade de código. Evita consumo de memória no PHP.
  16. EXEMPLO 1 class Product { public static function find($id) {

    if ( ! is_string($id) || trim($id) === '') { throw new \InvalidArgumentException( 'The product ID must be a non-empty value' ); } // do something to find ... } public static function update($id, array $data) { if ( ! is_string($id) || trim($id) === '') { throw new \InvalidArgumentException( 'The product ID must be a non-empty value' ); } // do something to update ... } } Id possui um comportamento duplicidade de código
  17. EXEMPLO 1 final class Id { private $value; public function

    __construct($value) { if ( ! is_string($value) || trim($value) === '') { throw new \InvalidArgumentException( 'The ID must be a non-empty string' ); } $this->value = $value; } public function getValue() { return $this->value; } }
  18. EXEMPLO 1 class Product { public static function find(Id $id)

    { // do something to find ... } public static function update(Id $id, array $data) { // do something to find ... } }
  19. EXEMPLO 2 class User { private $name; private $email; public

    function __construct($name, $email) { $this->name = $name; $this->email = $email; } } $user = new User('Dwight Lyman Moody', '[email protected]'); // ok $user = new User('Charles Spurgeon', '19061834-31011892'); // ok
  20. EXEMPLO 2 final class Email { private $value; public function

    __construct($value) { if ( ! filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new \InvalidArgumentException( 'E-mail must be a valid format' ); } $this->value = $value; } public function getValue() { return $this->value; } }
  21. EXEMPLO 2 // ok $user = new User('Dwight Lyman Moody',

    new Email(‘[email protected]')); // not ok $user = new User('Charles Spurgeon', new Email(‘19061834-31011892'));
  22. EXEMPLO 1 class User { private $name; private $albumList; public

    function getPublicAlbumList() { $filteredAlbumList = []; foreach ($this->albumList as $album) { if ($album->isPublic()) { $filteredAlbumList[] = $album; } } return $filteredAlbumList; } }
  23. EXEMPLO 1 class AlbumList extends Collection { public function getPublic()

    { $filteredAlbumList = []; foreach ($this->albumList as $album) { if ($album->isPublic()) { $filteredAlbumList[] = $album; } } return $filteredAlbumList; } }
  24. EXEMPLO 1 class AlbumList extends Collection { public function getPublic()

    { return new ArrayCollection( array_filter($this->value, function ($album) { return $album->isPublic(); }) ); } }
  25. EXEMPLO 1 class User { private $name; private $albumList =

    new AlbumList(); // ... } $publicAlbumList = $user->getAlbumList()->getPublic(); $albumList = $user->getAlbumList(); $albumList->add(new Album($name, $privacy));
  26. Chamadas de métodos não devem ser encadeadas. Com excessão de

    classes fluentes com método do mesmo nome.
  27. EXEMPLO 1 public function attach($callback, $priority = self::DEFAULT_PRIORITY) { //

    ... $this->filters->insert($callback, $priority); return $this; }
  28. Quais os benefícios? Facilita o entendimento. Facilita o debug. Facilidade

    ao testar (criar mocks). Segue a Lei de Demeter.
  29. EXEMPLO 2 if ( ! is_null($checkInEvent->order)) { if ( !

    is_null($checkInEvent->order->closedOrder)) { $supInvoiceNo = $checkInEvent->order->closedOrder->sup_reference_no; } }
  30. EXEMPLO 2 $supInvoiceNo = ''; $closedOrder = $checkInEvent->getClosedOrder(); if (

    ! is_null($closedOrder)) { $supInvoiceNo = $closedOrder->sup_reference_no; }
  31. EXEMPLO 3 /** * @param $variations * @param $discountCode *

    @return bool */ private function hasBrand($variations, $discountCode) { $attachments = $discountCode->attachments; // ... foreach ($variations as $variation) { $brands = array_column($$attachments[‘brands'], 'id'); if (in_array($variation->product->manufacturer->id, $brands)) { $this->errorMessages[] = $variation->sku; } } // … }
  32. EXEMPLO 3 /** * @param $variations * @param $discountCode *

    @return bool */ private function hasBrand($variations, $discountCode) { $attachments = $discountCode->attachments; // ... foreach ($variations as $variation) { $brands = array_column($$attachments[‘brands'], 'id'); if (in_array($variation->product->manufacturer->id, $brands)) { $this->errorMessages[] = $variation->sku; } } // … }
  33. EXEMPLO 3 $manufacturer = \Mockery::mock(Manufacturer::class); $manufacturer->shouldReceive('getAttribute')->once()->with('id')->andReturn(1); $product = \Mockery::mock(Product::class); $product->shouldReceive('isBundle')->andReturn(false);

    $product->shouldReceive('getAttribute')->once()->with('manufacturer')->andReturn($manufacturer); $variation = \Mockery::mock(Variation::class); $variation->shouldReceive('getAttribute')->once()->with('product')->andReturn($product); $variation->shouldReceive(‘getAttribute’)->once()->with('sku')->andReturn('3000001');
  34. EXEMPLO 3 $p = $e instanceof HttpExceptionInterface; $q = $e

    instanceof MethodNotAllowedHttpException; $r = $e instanceof NotFoundHttpException; if ($p || $q || $r) { // ... }
  35. EXEMPLO 3 if ( $e instanceof HttpExceptionInterface || $e instanceof

    MethodNotAllowedHttpException || $e instanceof NotFoundHttpException ) { // ... }
  36. EXEMPLO 3 private function isInvalidException(Exception $e) { return ($e instanceof

    HttpExceptionInterface) || ($e instanceof MethodNotAllowedHttpException) || ($e instanceof NotFoundHttpException) } if ($this->isInvalidException($e)) { // ... }
  37. EXEMPLO 3 private function isInvalidException(Exception $e) { if ($e instanceof

    HttpExceptionInterface) { if ($e->getStatusCode() == 500) { $this->logger->error('My message log.'); } return true; } if ($e instanceof MethodNotAllowedHttpException) { return true; } if ($e instanceof NotFoundHttpException) { return true; } return false; }
  38. class MyRegistrationService { protected $userService; protected $passwordService; protected $logger; protected

    $translator; protected $entityManager; protected $imageCropper; } EXEMPLO 1 Interação com BD deve estar em $userService Pode ser usado um event listener
  39. LARAVEL: TIPS & TRICKS HOW TO IMPROVE YOUR APPLICATION USING

    THE S.O.L.I.D. PRINCIPLES https://goo.gl/y4bkzx
  40. EXEMPLO 1 class BankAccount extends Model { protected $fillable =

    [ 'balance' ]; } Gera uma propriedade publica class BankAccount extends Model { public $balance; }
  41. EXEMPLO 1 class BankAccountService { public function deposit($accountId, $amount) {

    $account = BankAccount::findOrFail($accountId); $account->balance = $account->balance + $amount; $account->save(); // do something to notify the client } public function withdraw($accountId, $amount) { $account = BankAccount::findOrFail($accountId); $account->balance = $account->balance - $amount; $account->save(); // do something to notify the client } }
  42. Tell, don’t ask Não peça informações para fazer o seu

    trabalho, apenas diga para o objeto que possui a informação e ele fará o trabalho por você.
  43. EXEMPLO 2 class BankAccount extends Model { protected $fillable =

    [ 'balance' ]; public function deposit($amount) { $this->balance += $amount; } public function withdraw($amount) { $this->balance -= $amount; } }
  44. EXEMPLO 2 class BankAccountService { public function deposit($accountId, $amount) {

    $account = BankAccount::findOrFail($accountId); $account->deposit($amount); $account->save(); // do something to notify the client } public function withdraw($accountId, $amount) { $account = BankAccount::findOrFail($accountId); $account->withdraw($amount); $account->save(); // do something to notify the client } }