Besides learning Elixir and ‘getting things done’ with it, I hope to also learn how to write code that is highly readable – and therefore also easy to grasp. I still have a lot to learn in that regard, I’m sure. I try not to rush to a new problem to write a solution for and first clean up some of the mess I have left behind. The code might work completely, but it’s never fun to having to come back to my own code a while later and feeling tempted to rewrite the code, rather than to understand what I wrote in the first place because of the mess.
I have noticed that I often find Elixir code more readable than JavaScript code (given being familiar with both). The pipeline pattern in particular. Nevertheless, I am interested in any of your personal advice about writing readable code.
To give you an example. I wrote this function today. I didn’t think it would be this long and involved. I want to clean it up further (I made a tiny start already).
I love pipelines that consists of highly descriptive code. However, in this case that would mean writing a lot of helper function, possibly.
I have also seen people put comments behind pipe elements, to provide description. Often, that results in a lot of info located in one place, though.
alias AppWeb.Component.Helpers
defp histogram_frequencies(responses, variable, bucket_size, start_value, bucket_count) do
end_bucket = start_value + bucket_count * bucket_size
max_value =
responses
|> Enum.map(fn %{^variable => value} -> value end)
|> Enum.max
bucket_number_list = Enum.to_list(1..bucket_count)
zero_list = Enum.map(bucket_number_list, fn b -> {b, 0} end)
sums =
responses
|> Helpers.frequencies(variable)
|> Enum.filter(fn {age, _count} -> age < end_bucket end)
|> Enum.map(fn {age, count} -> {trunc(age/bucket_size), count} end)
|> Kernel.++(zero_list)
|> Enum.group_by(fn {key, _value} -> key end)
|> Enum.map(fn {_key, value} -> Enum.map(value, fn {_x, e} -> e end) end)
|> Enum.map(fn p -> Enum.sum(p) end)
lower_limits_buckets = Helpers.lower_limits_buckets(bucket_size, start_value, bucket_count)
upper_limits_buckets = Helpers.upper_limits_buckets(bucket_size, start_value, bucket_count)
buckets =
[lower_limits_buckets, upper_limits_buckets]
|> Enum.zip_with(fn [x, y] -> "#{x}-#{y}" end)
# If any values greater than range of last bucket,
# put them into the last bucket
# and change that bucket's name accordingly (e.g. "60-70" becomes "60+").
case max_value < end_bucket do
true ->
Enum.zip(buckets, sums)
false ->
last_bucket = "#{start_value + bucket_count * bucket_size}+"
last_sum =
responses
|> Helpers.frequencies(variable)
|> Enum.filter(fn {key, _value} -> key >= end_bucket end)
|> Enum.map(fn {_age, count} -> count end)
|> Enum.sum
sums = sums++[last_sum]
buckets = buckets++[last_bucket]
Enum.zip(buckets, sums)
end
end