Hey, Elixir novice here coming from Typescript, Go, Ocaml.
What are your favorite techniques to get the most of your compile time checks, code readability and maintainability, and even testing strategies?
I just read this https://ulisses.dev/elixir/2020/02/19/elixir-style-for-maintanability.html, and I actually hadn’t thought to do more pattern matching on structs. Noteworthy to me because the code generated for Ecto’s CRUD functions often just pass along a generic map as the sole argument for parameters. Looking back at my project now, it would’ve served me better if I had used the specific struct as the argument.
I just posted a blog post on how I scope my use of typespecs, trying to find a a type safe balance while enjoying the dynamic nature of Elixir:
I also am really starting to fall into a groove with pattern matching and use it more and more – specifically I like making sure the variables I’m binding values into are the only ones required for a test or function. Trying not to make too many assumptions about the data (so it can hopefully evovle without breaking things).
Other final note, is I’m being much more mindful of how entities cross context boundaries. Still finding my way with LARGE code bases, but I feel like I’m starting to ask the right questions.
Elixir has guard clauses, that I like to use heavily in conjunction with pattern matching:
defp _todo_hash(%{
title: title, # pattern matching
user_uid: user_uid,
date: date,
} = _attrs,
action
)
when is_binary(title) # guard clauses
and byte_size(title) > 0
and is_binary(user_uid)
and byte_size(user_uid) === 64
and is_binary(date)
and byte_size(date) === 10
do
# your code here
end
Now that I discovered the Domo library I am using it to have typed structs and then pattern match on them:
defmodule Tasks.Todos.Types.Event do
# @link https://hexdocs.pm/domo/Domo.html
use Domo
typedstruct do
field :type, :todo | :backlog
field :target, :todo | :backlog | :all
field :action, :add | :update | :move | :duplicate | :delete
field :origin, atom()
field :broadcast_topics, list(), default: []
field :context, map(), default: %{}
end
end
that are then used like this:
def broadcast_change(
{:ok, data} = result,
%Tasks.Todos.Types.Event{} = event
) do
# your code here
do
my number one rule is to just go back tomorrow and read your code. Is there any doubt in your mind that you’ll enjoy reading this code in 2 years? Or worse yet - is there a remote possibility that this code is clear only because of memory?
I would imagine they mean 7 operations/assignments and for example a list that is split over n lines (for formatting sake) would just count as 1. Also comments and line breaks would not count, those are just there for context/formatting.
If your function is doing more than 7 things/operations inside of it, it becomes harder to keep all that in your head at once, and thus more prone to bugs…