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

583e920a7e9238a1c21e923025f8f641?s=128

Elaine Naomi

March 09, 2019
Tweet

Transcript

  1. 2.
  2. 3.
  3. 4.

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

    da Computação (USP) twitter.com/elaine_nw speakerdeck.com/elainenaomi 2019
  4. 6.
  5. 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?
  6. 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
  7. 18.

    p1 = Pessoa.new("Ana", "ana@email.com") p1.contato => "ana@email.com" p1.diga_oi => NoMethodError

    (undefined method `diga_oi' for #<Pessoa:0x00007fd9cb0f0760 @nome="Ana", @email="ana@email.com">)
  8. 19.

    p1 = Pessoa.new("Ana", "ana@email.com") p1.contato => "ana@email.com" p1.diga_oi => NoMethodError

    (undefined method `diga_oi' for #<Pessoa:0x00007fd9cb0f0760 @nome="Ana", @email="ana@email.com">)
  9. 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
  10. 31.
  11. 33.

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

    method `olar` for #<MeuChatBot:0x00007fd9cb0f0760>) método oi está encapsulado/oculto
  12. 38.
  13. 40.
  14. 43.
  15. 44.
  16. 45.

    Tamanho da base de código Tempo de entregas Nota: gráfico

    ilustrativo, mas baseado em fatos reais
  17. 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/
  18. 49.
  19. 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
  20. 54.

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

    Application Technical Debt, Gartner, 2011
  21. 55.

    Dívida Técnica Valor de negócio Custo da mudança Tempo Fonte:

    How to Monetize Application Technical Debt, Gartner, 2011
  22. 56.
  23. 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.
  24. 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
  25. 60.
  26. 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
  27. 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/
  28. 69.

    def charge(total) # cartão com status = 3 é o

    cartão ativo if credit_card.status == 3 payment_gateway.charge(total) end end
  29. 70.

    def charge(total) # cartão com status = 3 é o

    cartão ativo if credit_card.status == 3 payment_gateway.charge(total) end end
  30. 71.

    def charge(total) # cartão com status = 3 é o

    cartão ativo if credit_card.active? payment_gateway.charge(total) end end
  31. 75.

    # Public: Integer number of seconds to wait # before

    connection timeout. CONNECTION_TIMEOUT = 60
  32. 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/
  33. 77.

    Mysterious Name Long Function Long Parameter List Global Data Mutable

    Data Divergent Change Outros code smells 2019
  34. 78.

    Feature Envy Data Clumps Primitive Obsession Repeated Switches Loops Lazy

    Element Speculative Generality Temporary Field 2019
  35. 79.

    Message Chains Middle Man Insider Trading Large Class Alternative Classes

    with Different Interfaces Data Class Refused Bequest 2019
  36. 82.
  37. 85.

    Ctrl + F / ⌘ + F para qualquer alteração

    Arquivos constantemente alterados
  38. 88.

    Martin Fowler e Kent Beck listam 22 code smells e

    sugerem como refatorá-los 1999
  39. 89.

    Martin Fowler e Kent Beck listam 22 code smells e

    sugerem como refatorá-los 2019 24 Refatoraram?
  40. 92.
  41. 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
  42. 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
  43. 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/
  44. 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
  45. 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
  46. 103.
  47. 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
  48. 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
  49. 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
  50. 110.

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

    FileCreatorCSV < FileCreator def create(items) FormatCSV.generate_file(items) end end
  51. 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
  52. 116.
  53. 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
  54. 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
  55. 120.

    class PayrollAccount < CheckingAccount class OperationNotAllowed < StandardError; end #

    ... def compute_bonus raise OperationNotAllowed end end
  56. 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
  57. 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
  58. 129.

    class CoffeeMachine def brew_coffee # brew coffee logic end def

    fill_coffee_beans # fill coffee beans end end
  59. 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
  60. 133.

    class CoffeeMachineUserInterface def brew_coffee # brew coffee logic end end

    class CoffeeMachineServiceInterface def fill_coffee_beans # fill coffee beans end end
  61. 134.

    class CoffeeMachineUserInterface def brew_coffee # brew coffee logic end end

    class CoffeeMachineServiceInterface def fill_coffee_beans # fill coffee beans end end
  62. 145.
  63. 148.
  64. 150.
  65. 159.

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

    (1984)" in Literate Programming. CSLI, 1992, pg. 99.
  66. 160.
  67. 163.
  68. 164.
  69. 165.
  70. 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:
  71. 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:
  72. 169.