A problem I was investigating today led me to a two-line Ruby method much like this:
class App # ... def write_file_if_configured file_writer = FileWriter.new(@configuration.options) file_writer.write if file_writer.can_write? end end
This method definitely looks nice and object-oriented, and satisfies many code quality rules: it’s shorter than 10 lines, contains no branches, no Boolean parameters (unless there are any hiding in that
options object?), indeed no parameters at all.
It also conforms to the Law of Demeter: it calls a method on one of its fields, it creates a local object and calls methods on that objects, and it doesn’t send messages to any other object.
In fact there’s significant Feature Envy in this method. The method is much more interested in the
FileWriter than in its own class, only passing along some data. Moreover, it’s not merely using the writer, it’s creating it too.
That means that there’s no way that this method can take advantage of polymorphism. The behaviour, all invoked on the second line, can only be performed by an instance of
classglobal variable invoked on the first line.
FileWriter has no independent existence, and therefore is not really an object. It’s a procedure (one that uses
@configuration.options to discover whether a file can be written, and write that file if so), that’s been split into three procedure calls that must be invoked in sequence. There’s no encapsulation, because nothing is hidden: creation and use are all right there in one place. The
App class is not open to extension because the extension point (the choice of writer object) is tightly coupled to the behaviour.
Unsurprisingly that makes this code harder to “reason about” (a fancy phrase meaning “grok”) than it could otherwise be, and that with no additional benefit coming from encapsulation or polymorphism. Once again, the failure of object-oriented programming is discovered to be that it hasn’t been tried.
No branches? Isn’t the second line a branch? Nice post overall, though I fail to see the difficulty in reasoning what this code does (except for the logic behind can_write).