Slide 1

Slide 1 text

sobre code smells, refactoring e design como SOLID pode te ajudar no dia a dia

Slide 2

Slide 2 text

hello

Slide 3

Slide 3 text

1996

Slide 4

Slide 4 text

Elaine Naomi Watanabe Desenvolvedora de Software (Plataformatec) Mestre em Ciência da Computação (USP) twitter.com/elaine_nw speakerdeck.com/elainenaomi 2019

Slide 5

Slide 5 text

http://careers.plataformatec.com.br

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

analisar os conceitos básicos de orientação a objeto como identificar problemas na nossa base de código como melhorar o design do nosso código o que vamos ver?

Slide 8

Slide 8 text

orientação a objeto uma versão super resumida

Slide 9

Slide 9 text

Objeto Classe

Slide 10

Slide 10 text

class Pessoa def initialize(nome_recebido, email_recebido) @nome = nome_recebido @email = email_recebido end def contato @email end end exemplo de uma classe

Slide 11

Slide 11 text

p1 = Pessoa.new("Ana", "[email protected]") p1.contato => "[email protected]"

Slide 12

Slide 12 text

p1 = Pessoa.new("Ana", "[email protected]") p1.contato => "[email protected]" exemplo de um objeto

Slide 13

Slide 13 text

p2 = Pessoa.new("Bia", "[email protected]") p2.contato => "[email protected]" exemplo de outro objeto

Slide 14

Slide 14 text

Herança Polimorfismo Composição

Slide 15

Slide 15 text

exemplo de herança class PessoaFisica < Pessoa def diga_oi 'olar' end end

Slide 16

Slide 16 text

p1 = PessoaFisica.new("Ana", "[email protected]") p1.contato => "[email protected]" método herdado

Slide 17

Slide 17 text

p1 = PessoaFisica.new("Ana", "[email protected]") p1.contato => "[email protected]" p1.diga_oi => "olar"

Slide 18

Slide 18 text

p1 = Pessoa.new("Ana", "[email protected]") p1.contato => "[email protected]" p1.diga_oi => NoMethodError (undefined method `diga_oi' for #)

Slide 19

Slide 19 text

p1 = Pessoa.new("Ana", "[email protected]") p1.contato => "[email protected]" p1.diga_oi => NoMethodError (undefined method `diga_oi' for #)

Slide 20

Slide 20 text

class PessoaJuridica < Pessoa def contato @nome end end exemplo de polimorfismo

Slide 21

Slide 21 text

class PessoaJuridica < Pessoa def contato @nome end end método redefinido

Slide 22

Slide 22 text

p1 = PessoaFisica.new("Ana", "[email protected]") p1.contato => "[email protected]" p2 = PessoaJuridica.new("Umbrella Academy", "[email protected]") p2.contato => "Umbrella Academy"

Slide 23

Slide 23 text

[p1, p2].each do |pessoa| puts pessoa.contato end não importa qual pessoa estou chamando

Slide 24

Slide 24 text

class PessoaAnonima < Pessoa def contato '...' end end homenagem a talk da Cybelle

Slide 25

Slide 25 text

class Pessoa def initialize(nome_recebido, forma_de_contato_recebida) @nome = nome_recebido @contato_favorito = forma_de_contato_recebida end def contato @contato_favorito.identificador end end exemplo de composição

Slide 26

Slide 26 text

class Twitter def initialize(nome_do_usuario) @nome = nome_do_usuario end def identificador @nome end end exemplo de composição

Slide 27

Slide 27 text

twitter = Twitter.new("@ana") pessoa = Pessoa.new("Ana", twitter) pessoa.contato => "@ana"

Slide 28

Slide 28 text

class WhatsApp def initialize(numero_telefone_recebido) @numero_telefone = numero_telefone_recebido end def identificador @numero_telefone end end

Slide 29

Slide 29 text

zapzap = WhastApp.new(5511999999999) pessoa = Pessoa.new("Ana", zapzap) pessoa.contato => 5511999999999

Slide 30

Slide 30 text

Reúso Coesão Acoplamento Encapsulamento

Slide 31

Slide 31 text

class MeuChatBot def contato oi end private def oi 'olar' end end exemplo de encapsulamento

Slide 32

Slide 32 text

class MeuChatBot def contato oi end private def oi 'olar' end end

Slide 33

Slide 33 text

bot = MeuChatBot.new bot.contato => "olar" bot.oi => NoMethodError (undefined method `olar` for #) método oi está encapsulado/oculto

Slide 34

Slide 34 text

objetos + mensagens = app

Slide 35

Slide 35 text

e no dia a dia?

Slide 36

Slide 36 text

Frameworks MVC

Slide 37

Slide 37 text

View Model Controller apresentação intermediador dados + lógica de negócio

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

+ Funcionalidades

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

+ Funcionalidades + Modificações

Slide 42

Slide 42 text

+ Funcionalidades + Modificações + Bugs

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

Tamanho da base de código Tempo de entregas Nota: gráfico ilustrativo, mas baseado em fatos reais

Slide 46

Slide 46 text

"Esse código é difícil de entender"

Slide 47

Slide 47 text

"Joga isso fora e começa do zero"

Slide 48

Slide 48 text

Quando reescrever um sistema? Uso de tecnologia desatualizada Contratação de pessoas está difícil Existência de tecnologias mais vantajosas Fonte: http://blog.plataformatec.com.br/2016/07/key-points-to-consider-when-doing-a-software-rewrite/

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

que tal refatorar o código?

Slide 51

Slide 51 text

Refatoração processo de melhorar o design do código existente, alterando o software sem alterar o seu comportamento

Slide 52

Slide 52 text

Por que refatorar? Código fácil de ler Código fácil de entender Código fácil de manter Código limpo

Slide 53

Slide 53 text

precisamos mesmo refatorar?

Slide 54

Slide 54 text

Dívida Técnica Custo da mudança Tempo Fonte: How to Monetize Application Technical Debt, Gartner, 2011

Slide 55

Slide 55 text

Dívida Técnica Valor de negócio Custo da mudança Tempo Fonte: How to Monetize Application Technical Debt, Gartner, 2011

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

e como evitar isso?

Slide 58

Slide 58 text

Custo da mudança Tempo dívida técnica refatoração caso ótimo Fonte: An Empirical Model of Technical Debt and Interest, MTD' 11, Ariadni Nugroho et al.

Slide 59

Slide 59 text

Como medir a necessidade de refatoração? Quantidade de código duplicado Dificuldade para escrever testes Desempenho da suíte de testes Identificação dos code smells

Slide 60

Slide 60 text

Code smells Sintomas no código que indicam possíveis problemas de design em sistemas orientados a objeto

Slide 61

Slide 61 text

Code smells Indícios de que o código precisa ser refatorado

Slide 62

Slide 62 text

exemplos de code smells

Slide 63

Slide 63 text

Duplicated Code Código duplicado Regras de negócio duplicadas

Slide 64

Slide 64 text

Regra de negócio duplicada != Texto duplicado

Slide 65

Slide 65 text

Mesma alteração em vários arquivos, Buscas globais para toda alteração Shotgun Surgery

Slide 66

Slide 66 text

Vários comentários explicando o funcionamento do código Comments

Slide 67

Slide 67 text

# a é o valor total a ser cobrado def charge(a) # cartão com status = 3 é o cartão ativo if credit_card.status == 3 payment_gateway.charge(a) end end

Slide 68

Slide 68 text

def charge(total) # cartão com status = 3 é o cartão ativo if credit_card.status == 3 payment_gateway.charge(total) end end segundos de vida economizados! o/

Slide 69

Slide 69 text

def charge(total) # cartão com status = 3 é o cartão ativo if credit_card.status == 3 payment_gateway.charge(total) end end

Slide 70

Slide 70 text

def charge(total) # cartão com status = 3 é o cartão ativo if credit_card.status == 3 payment_gateway.charge(total) end end

Slide 71

Slide 71 text

def charge(total) # cartão com status = 3 é o cartão ativo if credit_card.active? payment_gateway.charge(total) end end

Slide 72

Slide 72 text

def charge(total) if credit_card.active? payment_gateway.charge(total) end end def active? status == 3 end mais segundos de vida economizados! o/

Slide 73

Slide 73 text

comentário != documentação

Slide 74

Slide 74 text

SECONDS_FOR_CONNECTION_TIMEOUT_IN_INTEGER = 60

Slide 75

Slide 75 text

# Public: Integer number of seconds to wait # before connection timeout. CONNECTION_TIMEOUT = 60

Slide 76

Slide 76 text

# Public: A summary of how much some user has consumed in a certain plan. # # Examples # plan_consumption_summary(contracted_plan) # # => '2.44% (500 MB of 20 GB)' # # Returns a String. def plan_consumption_summary(contracted_plan) total_contracted = contracted_plan.plan_storage_limit total_consumed = contracted_plan.total_consumed # ... Fonte: http://blog.plataformatec.com.br/2018/06/the-anatomy-of-code-documentation/

Slide 77

Slide 77 text

Mysterious Name Long Function Long Parameter List Global Data Mutable Data Divergent Change Outros code smells 2019

Slide 78

Slide 78 text

Feature Envy Data Clumps Primitive Obsession Repeated Switches Loops Lazy Element Speculative Generality Temporary Field 2019

Slide 79

Slide 79 text

Message Chains Middle Man Insider Trading Large Class Alternative Classes with Different Interfaces Data Class Refused Bequest 2019

Slide 80

Slide 80 text

e como evitar os code smells? reduzir

Slide 81

Slide 81 text

reconheça esses sintomas

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

Muitas regras de negócio nos controllers Métodos muito longos Classes muito grandes

Slide 84

Slide 84 text

Excesso de indireção/callbacks Dificuldade para escrever testes Classes de difícil manutenção

Slide 85

Slide 85 text

Ctrl + F / ⌘ + F para qualquer alteração Arquivos constantemente alterados

Slide 86

Slide 86 text

reconheci, e agora?

Slide 87

Slide 87 text

Para cada code smell, existe um conjunto de refatorações sugeridas

Slide 88

Slide 88 text

Martin Fowler e Kent Beck listam 22 code smells e sugerem como refatorá-los 1999

Slide 89

Slide 89 text

Martin Fowler e Kent Beck listam 22 code smells e sugerem como refatorá-los 2019 24 Refatoraram?

Slide 90

Slide 90 text

Mas vamos tentar olhar para o design do código

Slide 91

Slide 91 text

Finalmente, SOLID o/

Slide 92

Slide 92 text

No content

Slide 93

Slide 93 text

Single Responsibility Principle Open/Closed Principle Liskov Substitution Principle Interface Segregation Principle Dependency Inversion Principle

Slide 94

Slide 94 text

Single Responsibility Principle

Slide 95

Slide 95 text

O que a minha classe faz? Indicativo de problemas: usar "e" ou "ou" na explicação

Slide 96

Slide 96 text

Métodos relacionados? Uma única razão para mudar? Alterações com efeitos previsíveis?

Slide 97

Slide 97 text

class BooksUser < ApplicationRecord belongs_to: :book belongs_to: :user after_commit :send_notification_reservation_completed def send_notification_reservation_completed NotificationService.reservation_completed(user, book) end end

Slide 98

Slide 98 text

class BooksUser < ApplicationRecord belongs_to: :book belongs_to: :user after_commit :send_notification_reservation_completed def send_notification_reservation_completed NotificationService.reservation_completed(user, book) end end

Slide 99

Slide 99 text

Uma classe de persistência não deveria saber sobre notificações a um usuário, por ex. Que tal um Service Object para isso? Mais exemplos em: codeclimate.com/blog/7-ways-to-decompose-fat-activerecord-models/

Slide 100

Slide 100 text

class ReserveBookService attr_reader :user, :book, :notification_service def initialize(user, book, notification_service) @user = user @book = book @notification_service = notification_service end def confirm! user.books << book notification_service.reservation_completed(user, book) end end

Slide 101

Slide 101 text

class ReserveBookService attr_reader :user, :book, :notification_service def initialize(user, book, notification_service) @user = user @book = book @notification_service = notification_service end def confirm! user.books << book notification_service.reservation_completed(user, book) end end

Slide 102

Slide 102 text

Open/Closed Principle

Slide 103

Slide 103 text

Adicionar nova regra = modificar uma ou mais classes? Se sim, é um indicativo de problema

Slide 104

Slide 104 text

Aberto para extensão, fechado para modificação Defina interfaces/super classes Reduza o acoplamento

Slide 105

Slide 105 text

class FinancialReport def generate(account, file_format) case file_format when :csv file = FormatCSV.generate_file(account.transactions) when :xml file = XML.parse_list(account.transactions) end Mailer.send(account.email, file) end end

Slide 106

Slide 106 text

class FinancialReport def generate(account, file_format) case file_format when :csv file = FormatCSV.generate_file(account.transactions) when :xml file = XML.parse_list(account.transactions) when :pdf file = PDFGenerator.create(account.transactions) end Mailer.send(account.email, file) end end edição

Slide 107

Slide 107 text

class FinancialReport def generate(account, file_format) case file_format when :csv file = FormatCSV.generate_file(account.transactions) when :xml file = XML.parse_list(account.transactions) when :pdf file = PDFGenerator.create(account.transactions) end Mailer.send(account.email, file) end end

Slide 108

Slide 108 text

class FinancialReport def generate(account, file_creator) file = file_creator.create(account.transactions) Mailer.send(account.email, file) end end

Slide 109

Slide 109 text

class FileCreator def create(items) raise NotImplementedError end end contrato

Slide 110

Slide 110 text

class FileCreatorXML < FileCreator def create(items) XML.parse(items) end end class FileCreatorCSV < FileCreator def create(items) FormatCSV.generate_file(items) end end

Slide 111

Slide 111 text

class FileCreatorPDF < FileCreator def create(items) PDFGenerator.generate(items) end end adição

Slide 112

Slide 112 text

class FinancialReport def generate(account, file_creator) file = file_creator.create(account.transactions) Mailer.send(account.email, file) end end FinancialReport.new.generate(account, FileCreatorPDF.new)

Slide 113

Slide 113 text

Liskov Substitution Principle

Slide 114

Slide 114 text

Barbara Liskov Institute Professor from MIT The 2008 Turing Award winner liskov at csail.mit.edu

Slide 115

Slide 115 text

Liskov Substitution Principle (1987) Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true for objects y of type S where S is a subtype of T. Tradução em: https://speakerdeck.com/elainenaomi/hacking-evening-liskov-substitution-principle

Slide 116

Slide 116 text

Eita!

Slide 117

Slide 117 text

Design by contract respeitar os contratos definidos pela classe base

Slide 118

Slide 118 text

Pré-condições: dados de entrada classes derivadas só podem ser mais permissivas Pós-condições: dados de saída classes derivadas só podem ser mais restritivas Não podemos criar comportamentos inesperados ou incorretos! O comportamento da super classe precisa ser mantido

Slide 119

Slide 119 text

class CheckingAccount # ... def deposit(value) raise InvalidValueError if value <= 0 self.balance = self.balance + value end def compute_bonus self.balance = self.balance * 1.01 end end

Slide 120

Slide 120 text

class PayrollAccount < CheckingAccount class OperationNotAllowed < StandardError; end # ... def compute_bonus raise OperationNotAllowed end end

Slide 121

Slide 121 text

CheckingAccount.all.each do |account| account.compute_bonus end

Slide 122

Slide 122 text

CheckingAccount.all.each do |account| begin account.compute_bonus rescue PayrollAccount::OperationNotAllowed false end end

Slide 123

Slide 123 text

CheckingAccount.all.each do |account| begin account.compute_bonus rescue PayrollAccount::OperationNotAllowed false end end contrato quebrado

Slide 124

Slide 124 text

class PayrollAccount < CheckingAccount # ... def deposit(value) raise InvalidValueError if value <= 100 self.balance = self.balance + value end def compute_bonus self.balance = self.balance * 1.01 end end

Slide 125

Slide 125 text

class PayrollAccount < CheckingAccount # ... def deposit(value) raise InvalidValueError if value <= 100 self.balance = self.balance + value end def compute_bonus self.balance = self.balance * 1.01 end end contrato quebrado

Slide 126

Slide 126 text

Deveriam ser classes diferentes!

Slide 127

Slide 127 text

Interface Segregation Principle

Slide 128

Slide 128 text

Uma classe derivada não deveria ser obrigada a implementar métodos que ela não usa

Slide 129

Slide 129 text

class CoffeeMachine def brew_coffee # brew coffee logic end def fill_coffee_beans # fill coffee beans end end

Slide 130

Slide 130 text

class Person attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachine.new End def quero_cafe coffee_machine.brew_coffee end end

Slide 131

Slide 131 text

class Staff attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachine.new end def fill_coffee_beans coffee_machine.fill_coffee_beans end end

Slide 132

Slide 132 text

Várias interfaces específicas é melhor do que uma interface generalizada

Slide 133

Slide 133 text

class CoffeeMachineUserInterface def brew_coffee # brew coffee logic end end class CoffeeMachineServiceInterface def fill_coffee_beans # fill coffee beans end end

Slide 134

Slide 134 text

class CoffeeMachineUserInterface def brew_coffee # brew coffee logic end end class CoffeeMachineServiceInterface def fill_coffee_beans # fill coffee beans end end

Slide 135

Slide 135 text

class Person attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachineUserInterface.new end def quero_cafe coffee_machine.brew_coffee end end

Slide 136

Slide 136 text

class Person attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachineUserInterface.new end def quero_cafe coffee_machine.brew_coffee end end

Slide 137

Slide 137 text

class Staff attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachineServiceInterface.new end def fill_coffee_beans coffee_machine.fill_coffee_beans end end

Slide 138

Slide 138 text

class Staff attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachineServiceInterface.new end def fill_coffee_beans coffee_machine.fill_coffee_beans end end

Slide 139

Slide 139 text

Dependency Inversion Principle

Slide 140

Slide 140 text

Dependa de abstrações, não de implementações

Slide 141

Slide 141 text

class FinancialReport def generate(account, file_creator) file = file_creator.create(account.transactions) Mailer.send(account.email, file) end end

Slide 142

Slide 142 text

class FileCreatorCSV < FileCreator def create(items) FormatCSV.generate_file(items) end End FinancialReport.new.generate(account, FileCreatorCSV.new)

Slide 143

Slide 143 text

class FileCreatorCSV < FileCreator def create(items) FormatCSV.generate_file(items) NewCSVGenerator.parse(items, header: false) end End FinancialReport.new.generate(account, FileCreatorCSV.new)

Slide 144

Slide 144 text

use o encapsulamento e polimorfismo a seu favor

Slide 145

Slide 145 text

TL;DR

Slide 146

Slide 146 text

Princípios para a criação de um código mais flexível e adaptável a mudanças SOLID

Slide 147

Slide 147 text

No fundo, o que queremos? Alta coesão Baixo acoplamento Encapsulamento

Slide 148

Slide 148 text

No content

Slide 149

Slide 149 text

vamos refatorar tudo?

Slide 150

Slide 150 text

No content

Slide 151

Slide 151 text

esses conceitos nos ajudam a criar aplicações mais flexíveis

Slide 152

Slide 152 text

converse com seu time

Slide 153

Slide 153 text

analisem juntos os trade-offs

Slide 154

Slide 154 text

verifique a cobertura de testes

Slide 155

Slide 155 text

testes automatizados são essenciais para garantir a evolução do seu sistema

Slide 156

Slide 156 text

refatoração != revolução

Slide 157

Slide 157 text

cuidado com big design up front e overengineering

Slide 158

Slide 158 text

e não esqueçam:

Slide 159

Slide 159 text

codar é um processo de comunicação Donald Knuth. "Literate Programming (1984)" in Literate Programming. CSLI, 1992, pg. 99.

Slide 160

Slide 160 text

No content

Slide 161

Slide 161 text

Mais sobre design? Padrões de projeto TDD, DDD

Slide 162

Slide 162 text

minhas referências

Slide 163

Slide 163 text

No content

Slide 164

Slide 164 text

No content

Slide 165

Slide 165 text

No content

Slide 166

Slide 166 text

guidelines.plataformatec.com.br thoughtbot.com/upcase/clean-code refactoring.guru mundopodcast.com.br/podprogramar Mais referências:

Slide 167

Slide 167 text

Refactoring rails apps - Flavia Fortes http://bit.ly/2zDADhe Evitando o Jenga Driven Development - João Britto http://bit.ly/2DFvB3v Mais referências:

Slide 168

Slide 168 text

Por que (às vezes) você deve reinventar a roda - Paulo Silva https://youtu.be/kdNf2abcP5E?t=11640 Callbacks do ActiveRecord: o mal secreto ou apenas mal compreendidos? - Rondy https://youtu.be/kdNf2abcP5E?t=13214 Mais referências:

Slide 169

Slide 169 text

No content

Slide 170

Slide 170 text

Sororidade Empatia, solidariedade, companheirismo, respeito Juntas somos mais fortes

Slide 171

Slide 171 text

Até a próxima!

Slide 172

Slide 172 text

muito obrigada speakerdeck.com/elainenaomi