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

Gardening An Elixir Application

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Gardening An Elixir Application

Avatar for Andrew Summers

Andrew Summers

February 19, 2019
Tweet

More Decks by Andrew Summers

Other Decks in Programming

Transcript

  1. whoami • Backend Software Engineer at albert.io • Wrote Dialyzer

    pretty printer • Maintainer of Dialyxir, Erlex, elixir-lsp packages • Contributor to many others! • Not a gardener
  2. What is this? • Opinionated patterns learned from • writing

    and refactoring a lot of Elixir • developer confusion • tricky bugs • my particular tastes
  3. • For many projects this may be enough • Don't

    over-engineer if unnecessary • Conceptual scale is often as important as code scale
  4. Control Growth! • Control where and how modules can grow

    • Maximize compiler help when patterns change • Minimize errors of forgetfulness • Write Assertive Code 
 http://blog.plataformatec.com.br/2014/09/writing- assertive-code-with-elixir/
  5. defstruct • Use structs in combination with behaviors • Avoid

    impromptu data structures, e.g. maps when something has a name • Especially useful at ingress/egress boundaries • Match and dispatch on struct module • Avoid creating too early • Do not make unnecessary structs
  6. @behaviour • See Alistair Cockburn Ports/Adapter pattern • Use @impl,

    avoid @impl true • Useful with Mox for stubbing mocks • Useful when passing around modules in generic pipelines
  7. Modules as data structures • See: Ecto's __schema__/1 function •

    Can be behaviourized for compile/refactor safety • Allows passing modules and asking them questions about how to execute generic pipelines, e.g. what are your attributes • Powerful in combination with Domain Specific Languages
  8. defprotocol • Useful when data structures may exist in other

    projects • Ability to extend behavior of existing modules • Inspect, Enumerable as examples in Elixir core
  9. Interrobang?! • Use the availability of ? and ! in

    variable, function names • Unless guard, or over network, avoid is_ prefix • Use ! when function may raise, but be consistent • If ! function is provided, have a non-! function that returns :ok/:error tuple
  10. Dependencies • Try to avoid wherever possible, but don't NIH

    them • Think of them like herbicides for plants - can help but can take over your entire garden if left unchecked • Keep roots away from main application with wrappers • Hard to rip out after they permeate app, especially when transitively included by other dependencies
  11. Good Root Structure • Use mix xref to examine how

    modules fit together • Use utility style modules when "leaf" functionality • Keep direction pointing down • Similar granularity layers should interact with each other • Prefer "deep modules" as in Philosophy of Software Design
  12. Optimize for greppability • File name should match module name

    • Avoid partial and multi aliases • Avoid alias :as when possible • alias > import • Prefer import :only • Try to not import in __using__ macros
  13. Macros • Only do necessary work in macros • Delegate

    to functions ASAP for debuggability • Use location: :keep • after_compile validation useful in DSLs • Disabling lexical tracker useful for preventing deadlocks • Avoid name generation • Clean up after yourself
  14. @spec All The Things • Dialyzer is an imperfect tool

    • But helps prevent classes of errors if used in CI • @spec helps humans as much as CI tooling • Credo rules exist to enforce @spec on all functions
  15. Wait For Pain Points • Always keep eye out for

    abstractions that need to exist to solve some problem • But make sure there's a problem first • Wrong abstractions are worse than code duplication
 
 https://www.sandimetz.com/blog/2016/1/20/the-wrong- abstraction
  16. Aggressively Refactor • Refactor for clarity • Remove unused functions

    • defp gives warnings when unused, can be inlined • When removing function call, check for dead code • Reevaluate code for unnecessary abstractions
  17. GenServers & Umbrellas • Processes are not for domain logic

    organization
 https://www.theerlangelist.com/article/spawn_or_not • Should call into application logic where appropriate • Umbrellas are not for code organization (ultimately) • Both are for runtime concerns