...but Something is too much
10 Jan 2019A new year, a new blog! What better way to start than with an appeal for minimalism in code (and perhaps life)?
A few days ago, I watched Sandi Metz’ great talk “Nothing is Something” based on a recommendation by Monica Lent.
In her talk, Sandi argues in favor of composition and to be wary of inheritance. She gives a typical example of what happens when inheritance is used to share behavior:
class RandomHouse < House
def data
@data ||= super.shuffle
end
end
class EchoHouse < House
def parts (number)
super.zip(super).flatten
end
end
# don't do this
class RandomEchoHouse < House
def data
@data ||= super.shuffle
end
def parts (number)
super.zip(super).flatten
end
end
Obviously, this leads to brittle code with lots of duplication. As a solution, Sandi advocates a combination of dependency injection and composition using multiple behavioral classes:
DATA = ['1', '2', '3', '4', '5', '6']
class DefaultFormatter
def format(parts)
parts
end
end
class EchoFormatter
def format(parts)
parts.zip(parts).flatten
end
end
class DefaultOrder
def order(data)
data
end
end
class RandomOrder
def order(data)
data.shuffle
end
end
class House
attr_reader :formatter, :data
def initialize(DefaultOrder.new, DefaultFormatter.new) # DI
@formatter = formatter
@data = orderer.order(DATA)
end
def parts(number)
formatter.format(data.last(number))
end
end
class RandomHouse
[...]
end
class RandomEchoHouse
[...]
end
Indeed, this makes the code simpler and more maintainable than before.
Still, I couldn’t help but think: Why not do away with this whole class structure? Why do we even have to create multiple classes for something that is a simple functional data transformation?
Here is my Clojure implementation for the same task:
(def data ["a" "b" "c" "d" "e"])
(defn echo [data]
(interleave data data))
(defn shuffle-it [data] ;; could be inlined
(shuffle data))
(defn echo-shuffle [data]
(echo (shuffle-it data)))
This is still more verbose than necessary in the interest of comparability.
One might argue that the class structure is useful for code organization in a real world use case; for that, we simply could have used namespaces.
This data-first approach isn’t limited to a purely functional language like Clojure. The same thing can be achieved with a few lines of Rust:
use std::fmt::Display;
fn main() {
let mut data = vec!["a", "b", "c", "d", "e"];
// "RandomEchoHouse"
shuffle(&mut data);
echo(&data);
}
fn shuffle<T>(data: &mut Vec<T>) {
[...]
}
fn echo<T: Display>(data: &Vec<T>) {
print!("[");
for v in data {
print!("{}, {}, ", v, v);
}
println!("]");
}
Perhaps I’m simply missing something here. Perhaps that’s a good thing.