Quick & Easy Elixir Refactorings — Part 3

Conditionals in the middle

Ville
4 min readNov 29, 2017

In the last part we looked at a technique for refactoring away conditionals at the end of your functions and before that we looked at using pattern matching to eliminate conditionals from the top of your functions.

If you’ve not checked them out yet I recommend you do

Part 1 — Functions starting with a conditional
Part 2 — Functions ending with a conditional

In this post we’ll be looking at how we can clean up some code where two or more functions need to have the same start and end but something slightly different in the middle.

Conditionals in the middle

There’s couple of variants to this kind of thing I can think of. These usually come up when we are requested to add a feature to something already in place but is something a little bit different. You know, that special case 😉

Lets say we have a function that looks something like this

defmodule Example do  def parse_name(email) do
email
|> String.split("@")
|> hd()
|> String.split(".")
|> Enum.join(" ")
end
end

We get an email address, take the bit before @ symbol, separate on a full stop to get first name and last name and join them back together to make a full name. There’s better ways of doing that but it’s good for our purposes here.

Say now an additional requirement came in and we need to sometimes capitalise the first name and last name.

First thing we could do is

def parse_pretty_name(email) do
email
|> String.split("@")
|> hd()
|> String.split(".")
|> Enum.map(&String.capitalize/1)
|> Enum.join(" ")
end

Identical to the existing one apart from the additional 4th line in the pipeline with call to String.capitalize/1

Doesn’t feel quite right duplicating all that other logic. So alternatively we could pass something like an option to the function

def parse_name(email, pretty \\ false) do
names = email
|> String.split("@")
|> hd()
|> String.split(".")
if pretty do
names = Enum.map(names, &String.capitalize/1)
end

Enum.join(names, " ")
end

Doesn’t look very nice and if we compile this the Elixir compiler gives us a warning about setting a variable inside a conditional and gives a recommendation to fix it to something like this

def parse_name(email, pretty \\ false) do
names = email
|> String.split("@")
|> hd()
|> String.split(".")
names =
case pretty do
true -> Enum.map(names, &String.capitalize/1)
_ -> names
end
Enum.join(names, " ")
end

Aaaaaaaa…it’s getting worse! No compiler errors though 😉

Let’s have a look at an alternative where we’ll extract the bit that is different away.

def parse_name(email) do
do_parse_name(email, &(&1))
end
def parse_pretty_name(email) do
do_parse_name(email, &capitalize_names/1)
end
defp do_parse_name(email, transform) do
email
|> String.split("@")
|> hd()
|> String.split(".")
|> transform.()
|> Enum.join(" ")
end
defp capitalize_names(names) do
Enum.map(names, &String.capitalize/1)
end

So we’ve gone back to having a pair of functions with prescriptive names rather than options, although we could have kept the same name and pattern matched for the option here instead.

We’ve created a new private function which takes as it’s arguments the email but also a transform function which it then calls in the pipeline.

Our first function parse_name/1 now just calls the new private “do” function and passes in an anonymous function which simply returns whatever argument it gets essentially not doing anything.

The second function calls the same “do” function but uses function capturing to pass in the new capitalize_names/1 private function as the transform function. Neat 👌

I’d probably leave it at that but we could go one step further if we wanted by having the “do nothing” anonymous function as default value for the transform argument 💥

def parse_name(email, transform \\ &(&1)) do
email
|> String.split("@")
|> hd()
|> String.split(".")
|> transform.()
|> Enum.join(" ")
end
def parse_pretty_name(email) do
parse_name(email, &capitalize_names/1)
end
defp capitalize_names(names) do
Enum.map(names, &String.capitalize/1)
end

What I like about the final solution is that we’ve kept the neat looking easy to follow pipeline, we’ve eliminated conditionals, we’ve eliminated duplicated code and we’ve extracted the part that is different to a function on it’s own with a nice descriptive name.

This technique is great if you have some conditionals in the middle of your functions ruining your beautiful pipelines or lot of code duplicated around the differentiating piece of code.

Please do click the 👏 icon below if you liked this post and follow me here or on Twitter @efexen to be notified of the next part where we’ll look at more simple tricks for leveling up your Elixir code 👍

Next: Quick & Easy Elixir Refactorings — Part 4

--

--

Ville

Freelance Developer • Founder of @CodeHalf • Rubyist • Elixir Alchemist • Wannabe Data Scientist • Dad