Refatorando na medida certa

     Vinícius Bail Alonso

Quem sou eu

  • Desenvolvedor na BairesDev                                                                                                                         
  • Graduado em Sistemas para Internet (UTFPR)                             
  • Mentor pelo Training Center

Mas afinal, o que é refatoração?

Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure.

 

Martin Fowler

Quando não devo refatorar?

  • Quando um trecho de código não é muito utilizado       
  • Quando o código será descartado em breve                  

Quando devo refatorar?

  • Quando um trecho de código é mudado constantemente                                                                      
  • Quando o código deverá ser mantido ao longo do tempo                  

Como identificar um trecho que precisa ser refatorado? 

  • Code Smells                                                                                                    
  • Complexidade ciclomática                                                   
  • Código duplicado                   

Como dizer se um código está bom?

Definições

"Clean code is simple and direct. Clean code reads like well-written prose. Clean code never obscures the designer’s intent but rather is full of crisp abstractions and straightforward lines of control."

 

Grady Booch, autor do livro Object Oriented Analysis and Design with Applications

Definições

 

"You know you are working on clean code when each routine you read turns out to be pretty much what you expected. You can call it beautiful code when the code also makes it look like the language was made for the problem."

Ward Cunningham, co-inventor do eXtreme programming

Por que devo me importar com isso?

Dívida Técnica

Profissionalismo

Evitar desperdício

Sumário

  • Nomes significativos                                                       
  • Funções bem escritas                                                     
  • Comentários                                                                     
  • Classes                                

Nomes Significativos

Nomes Significativos

  • Devem ser fáceis de pronunciar                                                                                      
  • Devem expressar a intenção                     
  • Não utilizar abreviações e siglas, exceto, se todos conhecerem (ex: API)                       
  • As classes devem usar substantivos em seus nomes e verbos em seus métodos

Nomes Significativos

$d      =  10;
$cust_n =  "Juca";
$brt    =  "1990-03-30 18:00:38";

Nomes abreviados e difíceis de pronunciar!

Nomes Significativos

$workedDays    =  10;
$customerName  =  "Juca";
$birthday      =  "1990-03-30 18:00:38";

Nomes Significativos

function calc(x, y) {
  return x / y;
}

O nome da função não expressa sua intenção!

Nomes Significativos

function division(dividend, divider) {
  return dividend / divider;
}

Nomes Significativos

class ProgrammerFL {
  private $h;
  private $v;
    
  public function __construct($h, $v) {
    $this->h = $h;
    $this->v = $v;
  }
    
  public function pay() {
    return $this->h * $this->v;
  }
}

A classe não expressa sua intenção e tem muita abreviação!

Nomes Significativos

class ProgrammerFreeLancerPayment {
  private $hoursWorked;
  private $valuePerHour;
    
  public function __construct($hoursWorked, $valuePerHour)
  {
    $this->hoursWorked = $hoursWorked;
    $this->valuePerHour = $valuePerHour;
  }
    
  public function calculateTotalValueOfWork()
  {
    return $this->hoursWorked * $this->valuePerHour;
  }
}

Nomes Significativos

  • Nomear não é uma tarefa fácil                                                                                        
  • Devemos escolher bem os nomes para facilitar o entendimento do código                                                                                
  • Lembre-se, outros desenvolvedores vão trabalhar naquela funcionalidade no futuro

Funções bem escritas

Funções bem escritas

  • Pequenas!                                                      
  • Devem fazer apenas uma coisa                                                                                      
  •  Poucos níveis de indentação (max: 1)                                                                            
  • Não devem ter efeitos colaterais

Funções bem escritas

À seguir um exemplo da fórmula Bhaskara

function quadratic_equation($a,$b,$c) {
 if($a === 0) {
  echo "Para o cálculo de uma equação quadrática, 
  \"a\" não pode ser igual a zero.";
 } else {
  $delta = pow($b,2) - ((4*$a)*$c);
  if($delta === 0) {
   $raiz = -($b)/(2*$a);
   echo "Delta igual a zero. A equação tem 1 raiz.
   <br>Raiz da equação: ".$raiz;
  } elseif($delta > 0) {
   $x1 = (-$b + sqrt($delta))/(2*$a);
   $x2 = (-$b - sqrt($delta))/(2*$a);
   $raiz = array($x1, $x2);
   echo "Delta maior que zero. A equação tem 2 raizes.
   <br>Raizes da equação: " .$raiz[0]. "," .$raiz[1];
  } else {
   echo "Delta menor que zero: equação sem solução.";
  }
 }
}

Funções bem escritas

  • A função anterior é muito grande                                                                                  
  • Possui muitos níveis de indentação                                                                                
  • Muitos fluxos de execução, aumentando a complexidade ciclomática
function calculate_bhaskara($a,$b,$c) {
 if(not_is_zero($a)) {
  $delta  =  calculate_delta($a, $b, $c);
  return calculate_root($a, $b, $delta);
 }
}
function not_is_zero($param) {
  return $param !== 0;
}
function calculate_delta($a, $b, $c) {
  return pow($b,2) - ((4*$a)*$c);
}
function calculate_root($a, $b, $delta) {
  if(is_zero($delta))
    return calculate_simple_root($b, $a);
  if(is_greater_than_zero($delta))
   return calculate_double_root($b, $a, $delta);
 
 return null;
}

Funções bem escritas

  • Agora temos várias funções pequenas                                                                          
  • Fazendo apenas uma coisa cada                                                                                    
  • Facilitando a manutenção                                                                                                
  • Isolando os bugs

Funções bem escritas

# Primeira opção
if ($delta === 0)
  return -($b)/(2*$a);

# Segunda opção
if(is_zero($a))
  return calculate_simple_root($b, $a);

Use suas funções para tentar formar uma narrativa

Funções bem escritas

function checkPassword($email, $password) {
  $credentials = ['email' => $email,
                'password' => $password];

  $userExists = User::check($credentials);

  if ($userExists) {
    session_start();
  }

  return $userExists;
}

O que há de errado com esse código ?

Funções bem escritas

function checkPassword($email, $password) {
  $credentials = ['email' => $email,
                'password' => $password];

  $userExists = User::check($credentials);

  if ($userExists) {
    session_start();
  }

  return $userExists;
}

Cuidado com funções que fazem mais do que se propõe a fazer!

Comentários

Comentários

  • Não devem explicar o código                                                                              
  • Atualizar o código e o comentário gera retrabalho                                                                                                                            
  • Podem ser usados para documentação e API's públicas                                                                                                                  
  • Também são úteis em arquivos de configuração

Comentários

O código deve ser autoexplicativo

# Não pode ser vazio e nem nulo
if (!is_empty($name) && !is_null($name))

Comentários

function notIsEmptyOrNull($param) {
  return !is_empty($param) && !is_null($param);
}

if (notIsEmptyOrNull($name))

Comentários

use Carbon\Carbon;

# Retorna a data atual 
# Formato Y-m-d
Carbon::now();

O comentário é mentiroso!

use Carbon\Carbon;
# Retorna a data atual 
# Formato Y-m-d

echo Carbon::now();
// 2016-06-24 15:18:34

Comentários

/**
     * @SWG\Post(
     *     path="/pets",
     *     operationId="addPet",
     *     description="Creates a new pet in the store.  Duplicates are allowed",
     *     produces={"application/json"},
     *     @SWG\Parameter(
     *         name="pet",
     *         in="body",
     *         description="Pet to add to the store",
     *         required=true,
     *         @SWG\Schema(ref="#/definitions/NewPet"),
     *     ),
     *     @SWG\Response(
     *         response=200,
     *         description="pet response",
     *         @SWG\Schema(ref="#/definitions/Pet")
     *     ),
     *     @SWG\Response(
     *         response="default",
     *         description="unexpected error",
     *         @SWG\Schema(ref="#/definitions/ErrorModel")
     *     )
     * )
     */
    public function addPet()
    {
    }

São úteis para gerar documentação

Comentários

/*
    |--------------------------------------------------------------------------
    | Application URL
    |--------------------------------------------------------------------------
    |
    | This URL is used by the console to properly generate URLs when using
    | the Artisan command line tool. You should set this to the root of
    | your application so that it is used when running Artisan tasks.
    |
    */
    'url' => env('APP_URL', 'http://localhost'),
/*
    |--------------------------------------------------------------------------
    | Application Timezone
    |--------------------------------------------------------------------------
    |
    | Here you may specify the default timezone for your application, which
    | will be used by the PHP date and date-time functions. We have gone
    | ahead and set this to a sensible default for you out of the box.
    |
    */
    'timezone' => 'Africa/Windhoek',

São úteis em arquivos de configuração

Arquivo config/app.php gerado pelo Laravel Framework

Comentários

private function updateListOrder($id, $listOrder, $parentId = 0)
{

  //$token = token($this->email);
  //if (self::where('confirmation_token', $token)->first()) {
  //  return $this->getUniqueConfirmationToken();
  //}


  $row = Testimonial::find($id);
  $row->list_order = $listOrder;
  $row->save();
  return $row;
}

Não deixe trechos de código comentados

Comentários

  • Podemos consultar códigos removidos pelo git                                                                                                                                 
  • Um comentário é útil se colocado no lugar certo                                                                                                                         
  • Comentários não devem explicar o código

Classes

Classes

  • Pequenas!                                                                                                                            
  • Devem ser coesas                                                                                                              
  • Os tópicos abordados em funções também servem para os métodos                                                                                  
  • Respeite o encapsulamento

Classes

  • Relembrando o que Grady Booch disse "ler como uma boa prosa"                         
  • O uso das classes deve ser próximo de uma narrativa

Classes

$hoursWorked = 30;
$valuePerHour = 50;

$programmerFreeLancer = 
new ProgrammerFreeLancerPayment($hoursWorked, $valuePerHour);

$programmerFreeLancer->calculateTotalValueOfWork();

Cuidado para não deixar a linha muito grande

Classes

# ...

public function buildJson() 
{
  $this->getUsersData();
  return $this->build();
}

private function getUsersData() {
  # ...
}

private function build() {
  # ...
}

Seu código deve ser lido de cima para baixo

COESÃO

X

ACOPLAMENTO

Classes

class Person {
  private $name;
  private $age;
  private $zipCode;
  private $street;
  private $number;
  private $neighborhood;
  private $state;
  private $city;
  private $cellphone;
  private $phone; 
  private $agency;
  private $accountNumber;
  # ...
}

A classe não está coesa! God Class!

Classes

class Address {
 
  private $zipCode;
  private $street;
  private $number;
  private $neighborhood;
  private $state;
  private $city;
}

class Contact {
  private $cellphone;
  private $phone; 
}

Agora temos classes mais coesas.

class CustomerAccount {
  private $agency;
  private $number;
}

class Person {
  private $name;
  private $age;
  private $account;
  private $contact;
  private $address;
}

Classes

$address = new Address();
$contact = new Contact();
$account = new CustomerAccount();


$person = new Person('Juca', 20, $address, $contact, $account);

Favorecendo a composição :)

ENCAPSULAMENTO

Classes

  • Será mostrado um exemplo com um carrinho de compras em uma loja virtual                                                                                                                                  
  • Com as classes Product, Item e Cart

Classes

class Product {
  private $value;
  private $name;
    
  public function __construct($name, $value) {
    $this->name  = $name;
    $this->value = $value;
  }
    
  public function getValue() {
    return $this->value;
  }
}

Classes

class Item {
  private $product;
  private $quantity;
    
  public function __construct($product, $quantity) {
    $this->product  = $product;
    $this->quantity = $quantity;
  }
    
  public function getProduct() {
    return $this->product;
  }

  public function getQuantity() {
    return $this->quantity;
  }
}

Classes

class Cart {
  private $owner;
  private $items;
    
  public function __construct($owner, $items) {
    $this->owner = $owner;
    $this->items = $items;
  }
    
  public function getTotalValueOfItems() {
    $total = 0;
    foreach ($this->items as $item) {
      $total += $item->getProduct()->getValue() * $item->getQuantity();
    }
    return $total;
  }
}

O que há de errado nessa classe?

Classes

class Cart {
  private $owner;
  private $items;
    
  public function __construct($owner, $items) {
    $this->owner = $owner;
    $this->items = $items;
  }
    
  public function getTotalValueOfItems() {
    $total = 0;
    foreach ($this->items as $item) {
      $total += $item->getProduct()->getValue() * $item->getQuantity();
    }
    return $total;
  }
}

Classes

  • A classe Cart está acessando dados da classe Product sem a conhecer, violando o encapsulamento                    
  • Quem deve retornar o valor de cada produto é a classe Item                                                                                                    

  • Para refatorar isso usamos a  Lei de Demeter

Classes

class Item {
  private $product;
  private $quantity;
    
  public function __construct($product, $quantity) {
    $this->product  = $product;
    $this->quantity = $quantity;
  }
    
  public function getProduct() {
    return $this->product;
  }
  public function getQuantity() {
    return $this->quantity;
  }
    
  public function totalValue() {
    return $this->product->getValue() * $this->quantity;
  }
}

Classes

class Cart {
  private $owner;
  private $items;
    
  public function __construct($owner, $items) {
    $this->owner = $owner;
    $this->items = $items;
  }
    
  public function getTotalValueOfItems() {
    $total = 0;

    foreach ($this->items as $item) {
      $total += $item->totalValue();
    }

    return $total;
  }
}

Classes

  • Com o uso correto da orientação a objetos evitamos Code Smells                                                                               
  • Devemos sempre conciliar coesão e acoplamento                                                                       

  • Nosso foco deve estar na troca de mensagens entre objetos

Conclusão

  • Escrevemos código para outros desenvolvedores, por isso deve ser claro                                                                                                                                      
  • Assim evitamos que nossos projetos de hoje sejam os legados horríveis de amanhã

Lembre-se

 

"Any fool can write code that a computer can understand. Good programmers write
code that humans can understand.
"

Martin Fowler

 

Entre em contato

vba321@hotmail.com

Referências

Refatorando na medida certa

By Vinícius Alonso

Refatorando na medida certa

  • 753