GoReleaser: lessons learned so far
I’ve started GoReleaser almost 2 years ago. This is a summary of (some) things I’ve …
booleans
, am I right? What a wonderful piece of technology! They help us solve so many problems…
I just need this method to behave slightly different when some condition is true.
Nice, what’s the problem in that?
We had a method like this:
class Coffee
def initialize(size)
@size = size
end
def make
# do stuff to make coffee with the given @size
end
end
Coffee.new(:large).make
Now, we have something like this:
class Coffee
def initialize(size)
@size = size
end
def make(milk)
# do stuff to make coffee with the given @size
if milk
# actually make that previous coffee be a latte
end
end
end
Coffee.new(:large).make(true)
So, what’s wrong?
At the first sight, you might think it is OK: “Just a single flag. This wouldn’t hurt anybody…”
The problem is that, later on, someone else will like to add another feature, for example, whether to add or not sugar…
class Coffee
def initialize(size)
@size = size
end
def make(milk, sugar)
# do stuff to make coffee with the given @size
if milk
# actually make that previous coffee be a latte
end
if sugar
# actually make that previous coffee or latte, a sweet
# coffee or sweet latte
end
end
end
Coffee.new(:large).make(true, false)
Now the method is slightly more complicated than before (imagine that real code will be in there instead of comments).
What happens next?
Of course, another feature:
class Coffee
def initialize(size)
@size = size
end
def make(milk, sugar, cream)
# do stuff to make coffee with the given @size
if milk
# actually make that previous coffee be a latte
end
if sugar
# actually make that previous coffee or latte, a
# sweet coffee or sweet latte
end
if cream
# actually make that coffee, sweet coffee, latte or
# sweet latte a coffee with cream, sweet coffee with
# cream, latte with or sweet latte with cream
end
end
end
Coffee.new(:large).make(true, true, true)
I dare you to guess what happens next…
At this point, the code is already a mess. Maybe the developers will attempt to apply some clean code and put the contents of each if
statement in separated functions… but, is this good design, at all?
Of course not. This is imperative, flag-oriented programming.
Also: look at the method call. Who will know in 3 months from now what each one of those booleans are?
Exactly: nobody.
Maybe this problem could be better solved with decorators, for example:
class Coffee
def initialize(size)
@size = size
end
def make
# make a coffee with the given @size
end
end
class Latte
def initialize(coffee)
@coffee = coffee
end
def make
@coffee.make
# adds milk to the given @coffee
end
end
class SweetCoffee
def initialize(coffee)
@coffee = coffee
end
def make
@coffee.make
# adds sugar to the given @coffee
end
end
class WhippedCreamCoffee
def initialize(coffee)
@coffee = coffee
end
def make
@coffee.make
# adds whipped cream to the given @coffee
end
end
# just plain old black coffee
Coffee.new(:small).make
# a latte
Latte.new(
Coffee.new(:large)
).make
# a latte with whipped cream
WhippedCreamCoffee.new(
Latte.new(
Coffee.new(:medium)
)
).make
# a sweet latte with whipped cream
WhippedCreamCoffee.new(
Latte.new(
SweetCoffee.new(
Coffee.new(:large)
)
)
).make
Yes, it is a little more verbose, but in the other hand it is more readable, maintainable, extendable, testable, etc… and, of course, more object oriented.
I know it feels like a lot of work (and of classes), but that is how Objects are intended to be: small, doing one thing very well, composable, and that’s it.
Looking back at the beginning of the post, one can argue that the second programmer shouldn’t have added more flags.
Yeah, maybe… but… humans… right?
We often do what we know is not the right thing because… reasons. I have no intend to approach the psychology aspect of this, but I do recommend this book, if you want to learn something about it.
The big question is: will you trust that, given a method with one flag already, the next programmer right away fix the mess and do the right thing?
I’m sorry, but I believe it is more likely to happen this way:
next = n
), arrives late at the office because of a flat tire - not very happy already;Coffee.rb
file, takes a look around and start cursing someone - because he couldn’t stand to look at that hundred if
statements;git blame coffee.rb
). And he knows where you live.Why is he mad?
Because he spent 3 hours refactoring the code to finally being able to implement his feature in a decent way, while he could have done that in 30 minutes if the previous guys weren’t lazy.
Do you want to be the “Programmer X” in this case? Do you want to be one of the lazy guys?
I know I don’t.
This is my point of view on this subject, if you disagree, please, comment bellow, let’s discuss how to write better code.
And, just to be clear, there could be N other implementations, decorators are not a silver bullet. Use them wisely.