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

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

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

Talk apresentada no Women Dev Summit 2019

Elaine Naomi

March 09, 2019
Tweet

More Decks by Elaine Naomi

Other Decks in Programming

Transcript

  1. sobre code smells, refactoring e design como SOLID pode te

    ajudar no dia a dia
  2. hello

  3. 1996

  4. Elaine Naomi Watanabe Desenvolvedora de Software (Plataformatec) Mestre em Ciência

    da Computação (USP) twitter.com/elaine_nw speakerdeck.com/elainenaomi 2019
  5. http://careers.plataformatec.com.br

  6. None
  7. 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?
  8. orientação a objeto uma versão super resumida

  9. Objeto Classe

  10. class Pessoa def initialize(nome_recebido, email_recebido) @nome = nome_recebido @email =

    email_recebido end def contato @email end end exemplo de uma classe
  11. p1 = Pessoa.new("Ana", "[email protected]") p1.contato => "[email protected]"

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

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

    objeto
  14. Herança Polimorfismo Composição

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

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

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

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

    (undefined method `diga_oi' for #<Pessoa:0x00007fd9cb0f0760 @nome="Ana", @email="[email protected]">)
  19. p1 = Pessoa.new("Ana", "[email protected]") p1.contato => "[email protected]" p1.diga_oi => NoMethodError

    (undefined method `diga_oi' for #<Pessoa:0x00007fd9cb0f0760 @nome="Ana", @email="[email protected]">)
  20. class PessoaJuridica < Pessoa def contato @nome end end exemplo

    de polimorfismo
  21. class PessoaJuridica < Pessoa def contato @nome end end método

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

    Academy", "[email protected]") p2.contato => "Umbrella Academy"
  23. [p1, p2].each do |pessoa| puts pessoa.contato end não importa qual

    pessoa estou chamando
  24. class PessoaAnonima < Pessoa def contato '...' end end homenagem

    a talk da Cybelle
  25. 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
  26. class Twitter def initialize(nome_do_usuario) @nome = nome_do_usuario end def identificador

    @nome end end exemplo de composição
  27. twitter = Twitter.new("@ana") pessoa = Pessoa.new("Ana", twitter) pessoa.contato => "@ana"

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

    @numero_telefone end end
  29. zapzap = WhastApp.new(5511999999999) pessoa = Pessoa.new("Ana", zapzap) pessoa.contato => 5511999999999

  30. Reúso Coesão Acoplamento Encapsulamento

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

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

    end end
  33. bot = MeuChatBot.new bot.contato => "olar" bot.oi => NoMethodError (undefined

    method `olar` for #<MeuChatBot:0x00007fd9cb0f0760>) método oi está encapsulado/oculto
  34. objetos + mensagens = app

  35. e no dia a dia?

  36. Frameworks MVC

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

  38. None
  39. + Funcionalidades

  40. None
  41. + Funcionalidades + Modificações

  42. + Funcionalidades + Modificações + Bugs

  43. None
  44. None
  45. Tamanho da base de código Tempo de entregas Nota: gráfico

    ilustrativo, mas baseado em fatos reais
  46. "Esse código é difícil de entender"

  47. "Joga isso fora e começa do zero"

  48. 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/
  49. None
  50. que tal refatorar o código?

  51. Refatoração processo de melhorar o design do código existente, alterando

    o software sem alterar o seu comportamento
  52. Por que refatorar? Código fácil de ler Código fácil de

    entender Código fácil de manter Código limpo
  53. precisamos mesmo refatorar?

  54. Dívida Técnica Custo da mudança Tempo Fonte: How to Monetize

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

    How to Monetize Application Technical Debt, Gartner, 2011
  56. None
  57. e como evitar isso?

  58. 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.
  59. 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
  60. Code smells Sintomas no código que indicam possíveis problemas de

    design em sistemas orientados a objeto
  61. Code smells Indícios de que o código precisa ser refatorado

  62. exemplos de code smells

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

  64. Regra de negócio duplicada != Texto duplicado

  65. Mesma alteração em vários arquivos, Buscas globais para toda alteração

    Shotgun Surgery
  66. Vários comentários explicando o funcionamento do código Comments

  67. # 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
  68. 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/
  69. def charge(total) # cartão com status = 3 é o

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

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

    cartão ativo if credit_card.active? payment_gateway.charge(total) end end
  72. def charge(total) if credit_card.active? payment_gateway.charge(total) end end def active? status

    == 3 end mais segundos de vida economizados! o/
  73. comentário != documentação

  74. SECONDS_FOR_CONNECTION_TIMEOUT_IN_INTEGER = 60

  75. # Public: Integer number of seconds to wait # before

    connection timeout. CONNECTION_TIMEOUT = 60
  76. # 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/
  77. Mysterious Name Long Function Long Parameter List Global Data Mutable

    Data Divergent Change Outros code smells 2019
  78. Feature Envy Data Clumps Primitive Obsession Repeated Switches Loops Lazy

    Element Speculative Generality Temporary Field 2019
  79. Message Chains Middle Man Insider Trading Large Class Alternative Classes

    with Different Interfaces Data Class Refused Bequest 2019
  80. e como evitar os code smells? reduzir

  81. reconheça esses sintomas

  82. None
  83. Muitas regras de negócio nos controllers Métodos muito longos Classes

    muito grandes
  84. Excesso de indireção/callbacks Dificuldade para escrever testes Classes de difícil

    manutenção
  85. Ctrl + F / ⌘ + F para qualquer alteração

    Arquivos constantemente alterados
  86. reconheci, e agora?

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

  88. Martin Fowler e Kent Beck listam 22 code smells e

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

    sugerem como refatorá-los 2019 24 Refatoraram?
  90. Mas vamos tentar olhar para o design do código

  91. Finalmente, SOLID o/

  92. None
  93. Single Responsibility Principle Open/Closed Principle Liskov Substitution Principle Interface Segregation

    Principle Dependency Inversion Principle
  94. Single Responsibility Principle

  95. O que a minha classe faz? Indicativo de problemas: usar

    "e" ou "ou" na explicação
  96. Métodos relacionados? Uma única razão para mudar? Alterações com efeitos

    previsíveis?
  97. 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
  98. 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
  99. 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/
  100. 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
  101. 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
  102. Open/Closed Principle

  103. Adicionar nova regra = modificar uma ou mais classes? Se

    sim, é um indicativo de problema
  104. Aberto para extensão, fechado para modificação Defina interfaces/super classes Reduza

    o acoplamento
  105. 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
  106. 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
  107. 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
  108. class FinancialReport def generate(account, file_creator) file = file_creator.create(account.transactions) Mailer.send(account.email, file)

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

  110. class FileCreatorXML < FileCreator def create(items) XML.parse(items) end end class

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

  112. 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)
  113. Liskov Substitution Principle

  114. Barbara Liskov Institute Professor from MIT The 2008 Turing Award

    winner liskov at csail.mit.edu
  115. 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
  116. Eita!

  117. Design by contract respeitar os contratos definidos pela classe base

  118. 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
  119. 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
  120. class PayrollAccount < CheckingAccount class OperationNotAllowed < StandardError; end #

    ... def compute_bonus raise OperationNotAllowed end end
  121. CheckingAccount.all.each do |account| account.compute_bonus end

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

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

    contrato quebrado
  124. 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
  125. 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
  126. Deveriam ser classes diferentes!

  127. Interface Segregation Principle

  128. Uma classe derivada não deveria ser obrigada a implementar métodos

    que ela não usa
  129. class CoffeeMachine def brew_coffee # brew coffee logic end def

    fill_coffee_beans # fill coffee beans end end
  130. class Person attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachine.new End

    def quero_cafe coffee_machine.brew_coffee end end
  131. class Staff attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachine.new end

    def fill_coffee_beans coffee_machine.fill_coffee_beans end end
  132. Várias interfaces específicas é melhor do que uma interface generalizada

  133. class CoffeeMachineUserInterface def brew_coffee # brew coffee logic end end

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

    class CoffeeMachineServiceInterface def fill_coffee_beans # fill coffee beans end end
  135. class Person attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachineUserInterface.new end

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

    def quero_cafe coffee_machine.brew_coffee end end
  137. class Staff attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachineServiceInterface.new end

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

    def fill_coffee_beans coffee_machine.fill_coffee_beans end end
  139. Dependency Inversion Principle

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

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

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

    FileCreatorCSV.new)
  143. class FileCreatorCSV < FileCreator def create(items) FormatCSV.generate_file(items) NewCSVGenerator.parse(items, header: false)

    end End FinancialReport.new.generate(account, FileCreatorCSV.new)
  144. use o encapsulamento e polimorfismo a seu favor

  145. TL;DR

  146. Princípios para a criação de um código mais flexível e

    adaptável a mudanças SOLID
  147. No fundo, o que queremos? Alta coesão Baixo acoplamento Encapsulamento

  148. None
  149. vamos refatorar tudo?

  150. None
  151. esses conceitos nos ajudam a criar aplicações mais flexíveis

  152. converse com seu time

  153. analisem juntos os trade-offs

  154. verifique a cobertura de testes

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

    sistema
  156. refatoração != revolução

  157. cuidado com big design up front e overengineering

  158. e não esqueçam:

  159. codar é um processo de comunicação Donald Knuth. "Literate Programming

    (1984)" in Literate Programming. CSLI, 1992, pg. 99.
  160. None
  161. Mais sobre design? Padrões de projeto TDD, DDD

  162. minhas referências

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

  167. Refactoring rails apps - Flavia Fortes http://bit.ly/2zDADhe Evitando o Jenga

    Driven Development - João Britto http://bit.ly/2DFvB3v Mais referências:
  168. 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:
  169. None
  170. Sororidade Empatia, solidariedade, companheirismo, respeito Juntas somos mais fortes

  171. Até a próxima!

  172. muito obrigada speakerdeck.com/elainenaomi