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

Evitando o Jenga® Driven Development @ Elixir Brasil 2018

João Britto
February 03, 2018

Evitando o Jenga® Driven Development @ Elixir Brasil 2018

Mover e remover código. Algo que fazemos praticamente todos os dias da nossa vida como programadores. O que podemos aprender a respeito de algo aparentemente tão mundano? Quais detalhes são frequentemente negligenciados e nos causam, silenciosamente, uma diversidade de problemas?

João Britto

February 03, 2018
Tweet

More Decks by João Britto

Other Decks in Programming

Transcript

  1. –Grant Ammons “A product with lots of features does not

    make a great product. A great product is one that solves the customer’s problem in the simplest way possible. Great products deliver value, not features.”
  2. –Sandi MacPherson “Whenever you build a new feature, you’re entering

    into a contract to keep that code up-to-date and compatible with all other features you’ll choose to add in the future.”
  3. –Jeff Atwood “Every new line of code you willingly bring

    into the world is code that has to be debugged, code that has to be read and understood, code that has to be supported. Every time you write new code, you should do so reluctantly, under duress, because you completely exhausted all your other options.”
  4. –Edsger W. Dijkstra “…if we wish to count lines of

    code, we should not regard them as ‘lines produced’ but as ‘lines spent’.”
  5. –Mike Perham “No code runs faster than no code. No

    code has fewer bugs than no code. No code uses less memory than no code. No code is easier to understand than no code.”
  6. –Thomas Figg “The easiest code to delete is the code

    you avoided writing in the first place.”
  7. Coisas mudam • Features nascem, crescem e eventualmente morrem. •

    Ainda faz sentido no contexto atual do meu produto? • Tem usuários suficientes para justificar sua existência? • Representa uma vulnerabilidade na stack? ou seja… • Está mais atrapalhando do que ajudando?
  8. –Sandi Metz “The problem with poorly designed small applications is

    that if they are successful they grow up to be poorly designed big applications.”
  9. Código pouco “removível” defmodule Animals do @animals ~w(cat dog fox)a

    def say(animal) when animal in @animals do apply(__MODULE__, animal, []) end def say(animal) do "What does the #{animal} say?" end def cat, do: "MEOW!" def dog, do: "WOOF!" def fox, do: "WHAT!" end Animals.say(:dog) # "WOOF!"
  10. Código pouco “removível” defmodule Animals do @animals ~w(cat dog fox)a

    def say(animal) when animal in @animals do apply(__MODULE__, animal, []) end def say(animal) do "What does the #{animal} say?" end def cat, do: "MEOW!" def dog, do: "WOOF!" def fox, do: "WHAT!" end Animals.say(:dog) # "WOOF!"
  11. Código pouco “removível” defmodule Animals do @animals ~w(cat dog fox)a

    def say(animal) when animal in @animals do apply(__MODULE__, animal, []) end def say(animal) do "What does the #{animal} say?" end def cat, do: "MEOW!" def fox, do: "WHAT!" end Animals.say(:dog) # ** (UndefinedFunctionError) function Animals.dog/0 # is undefined or private
  12. Código pouco “removível” defmodule Animals do @animals ~w(cat dog fox)a

    def say(animal) when animal in @animals do apply(__MODULE__, animal, []) end def say(animal) do "What does the #{animal} say?" end def cat, do: "MEOW!" def fox, do: "WHAT!" end Animals.say(:dog) # ** (UndefinedFunctionError) function Animals.dog/0 # is undefined or private
  13. Código pouco “removível” defmodule Animals do @animals ~w(cat fox)a def

    say(animal) when animal in @animals do apply(__MODULE__, animal, []) end def say(animal) do "What does the #{animal} say?" end def cat, do: "MEOW!" def fox, do: "WHAT!" end Animals.say(:dog) # "What does the dog say?"
  14. Código mais “removível” defmodule Animals do @animals %{ cat: "MEOW!",

    dog: "WOOF!", fox: "WHAT!" } def say(animal) when is_atom(animal) do @animals[animal] || "What does the #{animal} say?" end end Animals.say(:dog) # => "WOOF!" Animals.say(:cow) # => "What does the cow say?"
  15. Código mais “removível” defmodule Animals do @animals %{ cat: "MEOW!",

    dog: "WOOF!", fox: "WHAT!" } def say(animal) when is_atom(animal) do @animals[animal] || "What does the #{animal} say?" end end Animals.say(:dog) # => "WOOF!" Animals.say(:cow) # => "What does the cow say?"
  16. Código mais “removível” defmodule Animals do @animals %{ cat: "MEOW!",

    dog: "WOOF!", fox: "WHAT!" } def say(animal) when is_atom(animal) do @animals[animal] || "What does the #{animal} say?" end end Animals.say(:dog) # => "WOOF!" Animals.say(:cow) # => "What does the cow say?"
  17. –Thomas Figg “Good code isn’t about getting it right the

    first time. Good code is just legacy code that doesn’t get in the way.”
  18. Deveria ser tão simples quanto 1. Apagar o código 2.

    Arrumar os testes 3. Partir pro abraço $
  19. –Ned Batchelder “Most developers don't like getting rid of stuff.

    They want to keep chunks of code around in case they need them again. They worked hard to write that chunk of code. They debugged it, it works. They don't want to just throw it away.”
  20. –Ned Batchelder “If you have a chunk of code you

    don't need any more, there's one big reason to delete it for real rather than leaving it in a disabled state: to reduce noise and uncertainty.”
  21. Remoção Incorreta defmodule Animals do @animals ~w(cat dog fox)a def

    say(animal) when animal in @animals do apply(__MODULE__, animal, []) end def say(animal) do "What does the #{animal} say?" end def cat, do: "MEOW!" def fox, do: "WHAT!" end Animals.say(:dog) # ** (UndefinedFunctionError) function Animals.dog/0 # is undefined or private
  22. Sacolejo Automático • Reduzir código entregue às máquinas • Passo

    de compilação/empacotamento • Código morto permanece no repositório
  23. Sacolejo Manual • Reduzir código entregue às pessoas • Parte

    do fluxo de desenvolvimento • Código removido em definitivo
  24. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  25. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  26. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  27. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  28. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  29. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  30. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  31. Removendo dependências defmodule CatchEmAll.Games do import Ecto.Query, warn: false alias

    CatchEmAll.Repo alias CatchEmAll.Games.Creature def get_creature!(id), do: Repo.get!(Creature, id) def delete_creature(%Creature{} = creature) do Repo.delete(creature) end end
  32. Removendo dependências defmodule CatchEmAll.Games do import Ecto.Query, warn: false alias

    CatchEmAll.Repo alias CatchEmAll.Games.Creature def get_creature!(id), do: Repo.get!(Creature, id) def delete_creature(%Creature{} = creature) do Repo.delete(creature) end end
  33. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  34. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  35. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  36. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  37. Removendo dependências defmodule CatchEmAll.Games do import Ecto.Query, warn: false alias

    CatchEmAll.Repo alias CatchEmAll.Games.Creature def get_creature!(id), do: Repo.get!(Creature, id) def delete_creature(%Creature{} = creature) do Repo.delete(creature) end end
  38. Removendo dependências defmodule CatchEmAll.Games do import Ecto.Query, warn: false alias

    CatchEmAll.Repo alias CatchEmAll.Games.Creature def get_creature!(id), do: Repo.get!(Creature, id) def delete_creature(%Creature{} = creature) do Repo.delete(creature) end end
  39. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  40. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  41. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  42. Removendo dependências defmodule CatchEmAllWeb.CreatureController do def delete(conn, %{"id" => id})

    do creature = Games.get_creature!(id) {:ok, _creature} = Games.delete_creature(creature) :ok = Notifications.creature_deleted(creature) conn |> put_flash(:info, "Creature now rests in peace.") |> redirect(to: creature_path(conn, :index)) end end
  43. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  44. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  45. Removendo dependências ## default.pot msgid "" msgstr "" #: lib/catch_em_all_web/templates/creature/index.html.eex:23

    msgid "Are you sure?" msgstr "" #: lib/catch_em_all_web/templates/creature/index.html.eex:20 msgid "Sacrifice %{creature} </3" msgstr ""
  46. Removendo dependências ## default.pot msgid "" msgstr "" #: lib/catch_em_all_web/templates/creature/index.html.eex:23

    msgid "Are you sure?" msgstr "" #: lib/catch_em_all_web/templates/creature/index.html.eex:20 msgid "Sacrifice %{creature} </3" msgstr ""
  47. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  48. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  49. Removendo dependências defmodule CatchEmAllWeb.Router do use CatchEmAllWeb, :router pipeline :browser

    do # ... end scope "/", CatchEmAllWeb do pipe_through :browser resources "/creatures", CreatureController end end
  50. Removendo dependências defmodule CatchEmAllWeb.Router do use CatchEmAllWeb, :router pipeline :browser

    do # ... end scope "/", CatchEmAllWeb do pipe_through :browser resources "/creatures", CreatureController end end
  51. Removendo dependências defmodule CatchEmAllWeb.Router do use CatchEmAllWeb, :router pipeline :browser

    do # ... end scope "/", CatchEmAllWeb do pipe_through :browser resources "/creatures", CreatureController, except: [:delete] end end
  52. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  53. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  54. Removendo dependências defmodule CatchEmAllWeb.CreatureView do use CatchEmAllWeb, :view def delete_creature_class(%{active:

    true}), do: "btn-warn" def delete_creature_class(%{active: false}), do: "btn-info" end
  55. Removendo dependências defmodule CatchEmAllWeb.CreatureView do use CatchEmAllWeb, :view def delete_creature_class(%{active:

    true}), do: "btn-warn" def delete_creature_class(%{active: false}), do: "btn-info" end
  56. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  57. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  58. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  59. Removendo dependências <%= link gettext("Sacrifice %{creature} </3", creature: creature.nickname), to:

    creature_path(@conn, :delete, creature), method: :delete, data: [confirm: gettext("Are you sure?")], class: "btn btn-delete #{delete_creature_class(creature)}" %>
  60. Checklist das dependências • CSS, JavaScript, Imagens, assets em geral

    • Chaves de cache de longa expiração • Tarefas assíncronas • mix.exs • Arquivos de configuração, I18n • Documentação, README, etc. • Receitas de provisionamento • Banco de dados: migrações, tabelas, colunas, views, triggers, etc. • Testes: cases compartilhados, helpers, etc.
  61. Checklist das dependências • CSS, JavaScript, Imagens, assets em geral

    • Chaves de cache de longa expiração • Tarefas assíncronas • mix.exs • Arquivos de configuração, I18n • Documentação, README, etc. • Receitas de provisionamento • Banco de dados: migrações, tabelas, colunas, views, triggers, etc. • Testes: cases compartilhados, helpers, etc.
  62. Fontes e referências • The Pink Panther in “Pink Outs”

    • https:/ /youtu.be/1q2hu-mDtKs • Jenga® • https:/ /en.wikipedia.org/wiki/Jenga • Information is beautiful - Codebases • http:/ /www.informationisbeautiful.net/visualizations/million-lines-of-code/ • Feature Creep • https:/ /twitter.com/wakeupmrkim/status/925427903352922114 • Grant Ammons - Killing features — just as important as building them • https:/ /medium.com/@gammons/killing-features-just-as-important-as-building- them-7f4d64223585
  63. Fontes e referências • Sandi MacPherson - Tough tradeoffs: Removing

    product features is hard, but often necessary • https:/ /thenextweb.com/dd/2015/01/18/tough-tradeoffs-removing-product-features- hard-often-necessary/ • Jeff Atwood - The Best Code is No Code At All • https:/ /blog.codinghorror.com/the-best-code-is-no-code-at-all/ • Edsger W. Dijkstra - EWD 1036 • https:/ /www.cs.utexas.edu/~EWD/transcriptions/EWD10xx/EWD1036.html • Mike Perham - Kill Your Dependencies • http:/ /www.mikeperham.com/2016/02/09/kill-your-dependencies/ • Thomas Figg - Write code that is easy to delete, not easy to extend. • https:/ /programmingisterrible.com/post/139222674273/write-code-that-is-easy-to- delete-not-easy-to
  64. Fontes e referências • Sandi Metz - Practical Object-Oriented Design

    in Ruby • http:/ /www.poodr.com/ • Maslow’s Trapezoid of Code Cleaniness • https:/ /twitter.com/KellySutton/status/847875703383117824 • https:/ /twitter.com/compooter/status/847881828224684032 • Refactoring code • https:/ /twitter.com/olarclara/status/928802983130927104 • Ned Batchelder - Deleting code • https:/ /nedbatchelder.com/text/deleting-code.html • Tree Shaking • http:/ /gph.is/2pPEHXx
  65. Fontes e referências • Rollup.js • https:/ /rollupjs.org/ • Unused

    • https:/ /unused.codes/ • Exercism • http:/ /exercism.io/ • Google Chrome Dev Tools Code Coverage Tab • https:/ /developers.google.com/web/updates/2017/04/devtools-release- notes#coverage • Programming skills • https:/ /twitter.com/stephentyrone/status/930825774818455553 • Imagens de arquivo pessoal