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. Elaine Naomi Watanabe Desenvolvedora de Software (Plataformatec) Mestre em Ciência

    da Computação (USP) twitter.com/elaine_nw speakerdeck.com/elainenaomi 2019
  2. 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?
  3. class Pessoa def initialize(nome_recebido, email_recebido) @nome = nome_recebido @email =

    email_recebido end def contato @email end end exemplo de uma classe
  4. 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]">)
  5. 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]">)
  6. 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
  7. bot = MeuChatBot.new bot.contato => "olar" bot.oi => NoMethodError (undefined

    method `olar` for #<MeuChatBot:0x00007fd9cb0f0760>) método oi está encapsulado/oculto
  8. Tamanho da base de código Tempo de entregas Nota: gráfico

    ilustrativo, mas baseado em fatos reais
  9. 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/
  10. Por que refatorar? Código fácil de ler Código fácil de

    entender Código fácil de manter Código limpo
  11. Dívida Técnica Custo da mudança Tempo Fonte: How to Monetize

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

    How to Monetize Application Technical Debt, Gartner, 2011
  13. 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.
  14. 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
  15. # 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
  16. 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/
  17. def charge(total) # cartão com status = 3 é o

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

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

    cartão ativo if credit_card.active? payment_gateway.charge(total) end end
  20. # Public: Integer number of seconds to wait # before

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

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

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

    with Different Interfaces Data Class Refused Bequest 2019
  25. Ctrl + F / ⌘ + F para qualquer alteração

    Arquivos constantemente alterados
  26. Martin Fowler e Kent Beck listam 22 code smells e

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

    sugerem como refatorá-los 2019 24 Refatoraram?
  28. 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
  29. 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
  30. 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/
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. class FileCreatorXML < FileCreator def create(items) XML.parse(items) end end class

    FileCreatorCSV < FileCreator def create(items) FormatCSV.generate_file(items) end end
  37. 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
  38. 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
  39. 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
  40. class PayrollAccount < CheckingAccount class OperationNotAllowed < StandardError; end #

    ... def compute_bonus raise OperationNotAllowed end end
  41. 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
  42. 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
  43. class CoffeeMachine def brew_coffee # brew coffee logic end def

    fill_coffee_beans # fill coffee beans end end
  44. class Staff attr_reader :coffee_machine def initialize @coffee_machine = CoffeeMachine.new end

    def fill_coffee_beans coffee_machine.fill_coffee_beans end end
  45. class CoffeeMachineUserInterface def brew_coffee # brew coffee logic end end

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

    class CoffeeMachineServiceInterface def fill_coffee_beans # fill coffee beans end end
  47. codar é um processo de comunicação Donald Knuth. "Literate Programming

    (1984)" in Literate Programming. CSLI, 1992, pg. 99.
  48. Refactoring rails apps - Flavia Fortes http://bit.ly/2zDADhe Evitando o Jenga

    Driven Development - João Britto http://bit.ly/2DFvB3v Mais referências:
  49. 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: