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

O que você precisa saber sobre tipagem estática

Lucas Uyezu
November 28, 2019

O que você precisa saber sobre tipagem estática

Lucas Uyezu

November 28, 2019
Tweet

More Decks by Lucas Uyezu

Other Decks in Programming

Transcript

  1. • Mais de 800 mil lojas em 175 países •

    RoR 6.1.0.alpha • Monolito Majestoso com serviços satélite • Centenas de devs • 50+ deploys por dia
  2. Ruby 3 • Vai chegar no natal de 2020. •

    Performance (Ruby 3x3) • Concorrência • Análise estática
  3. • Definiu o que é uma linguagem fortemente tipada em

    1974. • Um dos princípios SOLID é nomeado com o seu nome.
  4. Barbara Liskov "... sempre que um objeto é passado de

    uma função chamadora à uma função chamada, o seu tipo deve ser compatível com o tipo declarado na função chamada." -- 1974
  5. Tipagem Forte lucasuyezu@Lucass-MBP ~/s/g/l/r/src> irb irb(main):001:0> 1 + '1' TypeError:

    String can't be coerced into Fixnum from (irb):1:in `+' from (irb):1 from /usr/bin/irb:11:in `<main>' irb(main):002:0>
  6. WAT

  7. Tipagem Estática lucasuyezu@Lucass-MBP ~/s/g/l/rubyconfbr2019> cargo build Compiling rubyconfbr2019 v0.1.0 (file:///Users/lucasuyezu/src/github.com/lucasuyezu/rubyconfbr2019)

    error[E0308]: mismatched types --> main.rs:6:27 | 6 | println!("{:?}", adiciona(1, '1')); | ^^^ expected usize, found char error: aborting due to previous error For more information about this error, try `rustc --explain E0308`. error: Could not compile `rubyconfbr2019`. To learn more, run the command again with --verbose.
  8. Tipagem Dinâmica def adiciona(a, b) a + b end lucasuyezu@Lucass-MBP

    ~/s/g/l/rubyconfbr2019> ruby main.rb Traceback (most recent call last): 2: from main.rb:5:in `<main>' 1: from main.rb:2:in `adiciona' main.rb:2:in `+': String can't be coerced into Integer (TypeError)
  9. • Nível 1: Sem assinaturas. Reporta bugs possíveis. • Há

    a possibilidade de reportar falsos positivos. • Nível 2: Necessita assinaturas. Verifica se o código cumpre com as assinaturas. • Steep. • Sorbet. • RDL.
  10. • Ruby 3 virá com um formato padrão de assinaturas

    de tipos. • O formato oficial ainda é um trabalho em progresso.
  11. .rbs • Assinatura de tipos em Ruby: github.com/ruby/ ruby-signature •

    Devs de bibliotecas poderão adicionar arquivos .rbs. • Devs de aplicativos poderão escrever as suas próprias definições.
  12. # -- exemplo.rbs -- class Array[A] include Enumerable def []:

    (Integer) -> A? def each: { (A)->void } -> self ... end
  13. Sorbet • Checador de tipos nível 2, com checagem estática

    e em tempo de execução. (Nós fazemos checagem em tempo de execução só em modo de desenvolvimento) • Escalável: 100 mil LoC/ segundo. 100x mais rápido que rubocop.
  14. Sorbet • Usa Language Server Protocol o que permite utilizar

    com editores de texto e IDEs. • Beta privado de 6 meses no Shopify.
  15. Por quê? • Elimina uma categoria inteira de errors. •

    Já se provou eficaz em outras linguagems (TypeScript, mypy). • Há benefícios só de se instalar.
  16. Sorbet Por que: Gradual | Todos os erros ignorados Todos

    os erros reportados | |--------------------------|--------------|-------------|---------------|---------------------------| | typed: ignore | typed: false | typed: true | typed: strict | typed: strong |
  17. Sorbet Por que: Código inalcançável if Random.rand foo = 1

    else foo = 2 end editor.rb:5: This code is unreachable https://srb.help/7006 5 | foo = 2 ^ Errors: 1
  18. Sorbet Por que: Erros de digitação begin data = JSON.parse(File.read(path))

    rescue JSON::ParseError => e raise "Got invalid JSON: #{e}" end json.rb:6: Unable to resolve constant ParseError 6 | rescue JSON::ParseError => e ^^^^^^^^^^^^^^^^ shims/gems/json.rbi:319: Did you mean: ::JSON::ParserError? 319 |class JSON::ParserError < JSON::JSONError ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  19. Exemplo sig do params( url: T.nilable(String), id_busca: String ).returns(T.nilable(String)) end

    def adicionar_id_busca(url, id_busca) return nil if url.nil? url + "?_id_busca=#{id_busca}" end
  20. • Não pare o seu projeto para adicionar anotações de

    tipo. • Continue entregando funcionalidades e consertando bugs, adicione tipos conforme for alterando os arquivos. • Adicione um passo no seu pipeline de CI, e ligue checagem de tipos em tempo de execução no ambiente de desenvolvimento.
  21. Obsessão por primitivos O uso de primitvos como Strings e/ou

    números para objetos que possuem lógica diferenciada. Conhecem with_indifferent_access?
  22. Exemplo sig do params( url: T.nilable(String), id_busca: String ).returns(T.nilable(String)) end

    def adicionar_id_busca(url, id_busca) return nil if url.nil? url + "?_id_busca=#{id_busca}" end
  23. Exemplo sig do params( url: T.nilable(String), id_busca: String ).returns(T.nilable(String)) end

    def adicionar_id_busca(url, id_busca) return nil if url.nil? separador = url =~ /\?/ ? '&' : '?' url + "#{url}#{separador}_id_busca=#{id_busca}" end
  24. sig do params( url: T.nilable(String), id_busca: String ).returns(T.nilable(String)) end def

    adicionar_id_busca(url, id_busca) return nil if url.nil? separador = url =~ /\?/ ? '?' : '&' id_busca = URI.encode(id_busca) url + "#{url}#{separador}_id_busca=#{id_busca}" end
  25. Use um objeto URI sig do params( url: T.nilable(String), id_busca:

    String ).returns(T.nilable(String)) end def adicionar_id_busca(url, id_busca) return nil if url.nil? url = Addressable::URI.parse(url) parametros = url.query_values || {} parametros['_id_busca'] = id_busca url.query_values = parametros url.to_s end
  26. Receba e retorne um objeto URI sig do params( url:

    T.nilable(Addressable::URI), id_busca: String ).returns(T.nilable(Addressable::URI)) end def adicionar_id_busca(url, id_busca) return nil if url.nil? url = Addressable::URI.parse(url) parametros = url.query_values || {} parametros['_id_busca'] = id_busca url.query_values = parametros url end
  27. Objetos que não devem ser primitivos • URIs • IPs

    • Dinheiro • Temperatura • Medidas • Dica: (De)serialize nas bordas do seu sistema.
  28. Sir Tony Hoare "Eu chamo de meu erro de um

    bilhão de dólares. Foi a criação do null em 1965. Isso provocou inúmeros erros, vulnerabilidades e falhas, o que provavelmente causou um bilhão de dólares em danos e sofrimento nos últimos quarenta anos." -- QCon Londres, 2009
  29. Motivos contra o uso de null • Null explode quando

    você o utiliza, não quando você o retorna ou o gera. • Polui a sua base de código com checagens de null por todo lugar, disfarçado de .to_s, .to_i, .to_f, etc. • Linguagens modernas como Rust foram projetadas sem null de propósito. • C# introduziu Nullable Reference Types, uma forma mais restrita de se lidar com null.
  30. sig do params( url: T.nilable(Addressable::URI), id_busca: String ).returns(T.nilable(Addressable::URI)) end def

    adicionar_id_busca(url, id_busca) return nil if url.nil? url = Addressable::URI.parse(url) parametros = url.query_values || {} parametros['_id_busca'] = id_busca url.query_values = parametros url end
  31. # sugestao_restaurante.rb class SugestaoRestaurante URL_IMAGEM_PADRAO = "https://example.com/image_padrao.png" attr_accessor :url_string end

    url = adicionar_id_busca(sugestao_restaurante.url_string, id_busca) url ||= URL_IMAGEM_PADRAO
  32. # sugestao_restaurante.rb class SugestaoRestaurante URL_IMAGEM_PADRAO = "https://example.com/image_padrao.png" attr_accessor :url_string def

    url Addressable::URI.parse(url_string || URL_IMAGEM_PADRAO) end end ... url = adicionar_id_busca(sugestao_restaurante.url, id_busca)
  33. Exemplo sig do params( url: Addressable::URI, id_busca: String ).returns(Addressable::URI) end

    def adicionar_id_busca(url, id_busca) parametros = url.query_values || {} parametros['_id_busca'] = id_busca url.query_values = parametros url end
  34. Resultado: Depois sig do params( uri: Addressable::URI, id_busca: String ).returns(Addressable::URI)

    end def adicionar_id_busca(uri, id_busca) parametros = uri.query_values || {} params[:_id_busca] = id_busca uri.query_values = parametros uri end
  35. • Checagem estática de tipos e anotações de tipos podem

    te ajudar a encontrar conceitos implícitos e anti-patterns na sua base de código. • Tornar esses conceitos explícitos e consertar esses anti-patterns é mais importante do que só adicionar assinaturas e anotações.
  36. • Evite usar null sempre que possível. • Evite usar

    primitivos sempre que possível. • Não interrompa o progresso do seu projeto para implementar sorbet. O ponto chave é tipagem gradual. Adicione um passo na sua suíte de CI e siga em frente.
  37. Referências • EuRuKo 2019 - A Plan towards Ruby 3

    Types by Yusuke Endoh • Sorbet Homepage • Strange Loop 2018 Stripe's Sorbet Talk • Null Object Design Pattern • Primitive Obsession Anti-pattern • WAT