Share
Embed
Gardening An Elixir Application
Andrew Summers
February 19, 2019
Transcript
26 slides
-
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 -
What is this? • Opinionated patterns learned from • writing
and refactoring a lot of Elixir • developer confusion • tricky bugs • my particular tastes -
• For many projects this may be enough • Don't
over-engineer if unnecessary • Conceptual scale is often as important as code scale -
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/ -
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 -
@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 -
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 -
defprotocol • Useful when data structures may exist in other
projects • Ability to extend behavior of existing modules • Inspect, Enumerable as examples in Elixir core -
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 -
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 -
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 -
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 -
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 -
@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 -
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 -
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 -
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