Tell, don't ask
robots.thoughtbot.com
robots.thoughtbot.com
class Post
def created
user.post_created(self)
end
def send_to_feed(feed)
feed.send(contents)
end
end
class TwitterUser
def post_created(post)
post.send_to_feed(twitter)
end
end
class EmailUser
def post_created(post)
# no-op.
end
end
The post merely tells its user that it was created. Then the user can decide to do something else. All users know about posts, but only TwitterUser knows about feeds.Honestly though, if I saw the not-so-good code, I'd leave it alone. It takes a pretty big justification to double the code for the same features.
Adding unnecessary inheritance is far worse than the original problem.
In my experience observing (pragmatically, never dogmatically) OO principles like SOLID reduces entropy. Thus are worth applying.
Personally I prefer polymorphism and null object pattern over conditionals to deal with edge cases, though if you have a leaky abstraction in the first place no principle or pattern is going to save you!
Every time I look at the code I have to 'recompile' it in my brain, just because it's doing such a simple thing in such a darn complex way.
class User
def post_created(post); end
end
class TwitterUser < User
def post_created(post)
post.send_to_feed(twitter)
end
end
class EmailUser < User
endCode should be simple and easy to understand. Observers add significant complexity and make your code vulnerable to unnecessary bugs because the code that acts on your objects is invisible to the normal control flow of the program and those who write or maintain it.
My stance is that things like sending email should be explicit calls so they are obvious. Sending email when registering a new user, for example, should be in the if user.save branch in your controllers or, if you have a more SOAish app, in the service that creates users.
def create user = User.create(params[:user])
if user.save
Emailer.new_user_email.deliver
render 'welcome'
else
render 'oh shit'
end
endOr, refactor that out a bit (ONLY IF NECESSARY!)
def create user = UserService.create_user_from(params[:user])
if user
render 'welcome'
else
render 'oh shit'
end
endI'm not saying it's possible to avoid conditionals completely, but this article gives several good examples of where it's possible. Someone should put together a similar set of examples in a functional language.
A quick tip: Any time you check an object's state to decide which method to call on it, you are breaking encapsulation. Call the method on the object and let it figure out what to do based on the state it is in.
Unless that object is self. But a good tip none the less.
Edit: to clarify, by "expensive" I mean expensive in terms of human hours to understand the code, not computer performance. Class hierarchies are much harder to understand than if statements.
Maintainability over the life of the code is more important than optimizing its present state to the most elegant solution. The code is going to grow, requirements will change, cases will be added not present in the current system.
If, and only if, my code is too slow is your concern relevant. That happens so rarely I find it not worth spending time on.
If the inheritance already exists and make sense, adding another overridden method may be a win. The thing I'm arguing against is introducing a new class hierarchy to remove an if statement. Inheritance is a big gun and you shouldn't use it unless it significantly cleans up the code.
In this particular case, extending User to get TwitterUser is a particularly bad example because it's adding an is-a relationship when has-a is better. After all, users may have both twitter accounts and email addresses, and these can change dynamically (they can edit their notification settings). Unthinking use of inheritance is far worse than extra if statements.
When you have a chunk of 50 consecutive lines with branches, then yes, that might be worth an abstraction.
Sadly too often, in ruby, I see people turning 8 consecutive lines into 4 layers of indirection...
As usual it's all about striking the balance. I wonder if one day we'll come up with a programming language that can enforce these things in a meaningful way.
When you learn OO in a procedural language like ruby or java, you tend to have a slightly odd idea of OO. Just because a language supports objects doesn't make it object oriented, it just allows object orientation.
I thought I knew OO until I learned Smalltalk, and discovered just how deep the rabbit hole can go. If's, foreach, while, unless, switches, these are all procedural constructs. You don't truly grok OO if you can't write a program without using procedural constructs (as an exercise).
And who knows, maybe someone will invent a language that nails the balance, I won't bet on it soon though.
In principle I agree with you, but in general procedural constructs are not harmful and should be used where appropriate.
500 lines is indeed a bit much, but I've seen through 100 line methods without an urge to refactor.
The best programs are those that have both; good use of patterns and the odd suspiciously long method if appropriate.
The worst programs are not only the classic spaghettis but also those that dogmatically stick to a pattern even where it makes no sense. Java is notorious for the latter, but I also often see ruby programs where the author religiously clinges to the belief that no method can be allowed to exceed 5 lines of code. That, in combination with misunderstood unit-testing (foo.MUST_RECEIVE :bar), often leads to ridiculously tight coupling and effectively a monolithic brick that is resilient to change.
I call these programs Gnocchi-code. A close relative of spaghetti, just higher density...
That's just crappy coding in any language. And I see it all too often, alas.
Having missed out on Smalltalk back in the 80s, I will venture to ask how one does conditional execution or terminates recursion without an "if", though? Even Lisp has its "(COND ...)" expression (yes, I know that's not OOP).
As for Smalltalk's conditional, of course it has them, but they're not procedural constructs of the language. Smalltalk implements its conditional behavior with an object model of course, the abstract class Boolean has two subclasses, true and false. The keywords true and false reference the single instance of each of those classes. Each class implements a set of methods like ifTrue:ifFalse: which take blocks (closures in modern smalltalks). True implements ifTrue by evaluating the block, False implements ifTrue with a no op, an empty method. Bam, Boolean logic implemented with objects and polymorphism.
Thus in Smalltalk, conditionals are method calls on booleans and come after the comparison rather than before.
1 = 2 ifTrue: [ 'boom' out ]Yes it's a good thing, because such programs are simple and pluggable allowing you to add features by adding new classes rather than modifying and potentially breaking existing ones.
I much prefer the functional way of doing things. The complexity is still minimized, but I can see where things are coming from, and how data is composed.
While it's harder to add "cases" to types in the functional style, I find myself wanting to add functions over types far more often, and therefore, I find that it works far better for me.
You clearly prioritize data over behavior, so naturally functional code fits your thought process better, but your though process is one among many. OO works well when your thoughts are behavior centric rather than data centric.
Functional and OO actually go very well together.
If you insist on thinking functionally or procedurally, well, then OO won't agree with you because you won't let it. You want to see everything in one place so you can see the big picture all at once; if that works well for you great.
But there's another way that we like, rather than seeing everything crammed into one spot in a complex way, we break it down into many small parts that are each individually stupidly simple. Each part assigned a responsibility in completing the overall task, and each part pluggable with any part having the same interface. The big picture is in the message names between the parts and the part names themselves, and the actual code implementing those messages is basically irrelevant. We find this simpler precisely because we can ignore everything but the one part we're working on, which is stupidly simple. And we can easily extend the system buy subclassing any part to change the behaviour of the program without touching the existing parts, but simply by adding new ones.
Quite simply, we don't want everything in one place, it's inflexible, brittle, not pluggable, and not simple in the way we define simple.
But it is. Smalltalk has no conditional statement, conditionals are implemented via polymorphism on the subclasses True and False (ignoring compiler optimizations).
Having said that, I like how many OOP languages implement loops as a special case of "visitor", though.
It doesn't just have objects, it's built out of them, Smalltalk "is" objects; library and language are the same thing. Your custom constructs are syntactically identical to core language constructs because it's all just library.
example 1, we're mixing data with UI labels. How do you handle localization ? by coupling your localization code with your user data/behavior ?
example 2 : you're simply coupling the system_monitor with the alarm, while in the worst case, the alarm should be linked to the system_monitor. Now if you want to add a "report_to_government_agency" method, you'd add that inside the code of every single one of your monitors (knowing that you don't want to report a broken light, but it might be a good idea for a melting nuclear core) ? Note that I'm not saying that the first code is good, it's just as bad... Also, the method becomes very poorly named (I want to know if the sensor went back to normal, but every time I query "check_for_overheating", it just rings the alarm and doesn't give me any info back ???)
example 3 is just a poor usage of pseudo inheritance (and an abuse of duck typing). you create a new type of user for every messaging service again ? and if a user uses more than one messaging service, you just create a type for every combination ? Not very scalable nor readable, IMO.
The last example is just as bad. useless inheritance, senseless object. the definition of street_name is wrong. the doc will be around "Street name returns a street name or an error message if there's no street name defined" How do you know if there's no street name, now ?
All those examples basically make all extension harder. They're everything that's wrong with OO. an object is not about one structure that contains data and does everything that can be done with it. An object is about giving organized access to pertinent data and/or pertinent behavior and (potentially) allowing to change them.
Incidentally, if this article blew your mind or if you're interested in Smalltalk, I can't recommend this book enough:
http://www.lulu.com/shop/andres-valloud/a-mentoring-course-o...
The author has a radical take on OO in Smalltalk and shows that going to absurd lengths to eliminate if-statements (well, ifTrue: et. al., because it's Smalltalk) can lead to both better readability and better performance.
Unit-testing the check_for_overheating inside SystemMonitor looks complicated... The "sound_alarms" call inside probably needs to be a reference to a "Speaker.sound_alarms", right? Why should the SystemMonitor be locked to the API of a Speaker? etc.
The point is more about putting the behavior itself in the object. For unit testing, you'd want to move the individual tests into their own classes, letting the SystemMonitor be the glue that calls them and routes responses to the appropriate system.
{{ user.address || "No address on file" }}
The "not so good" code is essentially this, but inside a wrapper in view code. What if you need different markup for a missing address, you either stuff it into a method or change the method's return value to nil... and what if only street_name is missing, not the whole address? It looks like a big mess to me. class User
delegate :street_name, to: :address, prefix: true, allow_nil: true
end
<%= user.address_street_name || "No street name on file" %>It makes me want to design a programming language that makes good code shorter than bad code...
Example 1: 5->1: It did get shorter.
Example 2: 5->8: One of the lines is showing a line calling the check_for_overheating method, which wasn't shown in the first example. The other two are declaring the SystemMonitor class, which was declared "off-screen" before.
Example 4: 7->13: Five lines for declaring and defining User.address, which was off-screen before. So it did get a line longer.
class SystemMonitor def check_for_overheating if temperature > 100 sound_alarms end end end
There are many cases where it's easier/lazier to have if-statements.
Going further, you can take the things learned in this article to make your general purpose code potentially faster, as well. For example, if a set of things that must be performed in order; and sometimes some of those steps are missing, instead of performing a null-check, you can have a default no-op case; that way, instead of:
if(firstAction != null) firstAction();
if(secondAction != null) secondAction();
if(thirdAction != null) thirdAction();
in your inner loop, you can have: firstAction();
secondAction();
thirdAction();
While whenever firstAction, secondAction, thirdAction are set, you can say: firstAction = newFirstAction ?? noop;
// etc.
All in all, I'm glad this sort of knowledge is getting out there. I'm just grateful for my having really, really good CS teachers back in highschool ( I don't remember my college talking about OO as a way of removing if-statements ).* Users of Java, of course, will just need to write a NoOp instance of their interface to take advantage of this.
The only way the second is even -as good- is if I'm constantly holding in my mind the various idioms you've used in the code, in this case that your function pointers are never null but will instead refer to an action that might do nothing. As far as I can tell, this is more work for me, for no gain.
I would love to hear your reasoning why this way of doing it is -better-, as opposed to just -more object oriented- (or -marginally less typing-).
When I'm writing my code, I personally find that being able to trust what my code is doing to be more readable. In the case I wrote above, more than likely, I would have arrived at that point by first writing whatever the first action was; and then coming to know that there could be two different actions that could have taken place (causing a method call/inheritance to occur) and then I realized that sometimes, nothing might happen. Now we have a nothing case. The nothing case did not negatively effect my code flow. I am not 100% sure I would write code like I had above in the first place, it would grow to that state organically; but, the advantage of trust later on, was worth noting.
I originally heard this concept a long, long, long time ago; and one of the interesting selling points that the person that told me it was that code could be faster run if it had no if-statements, reason being branching and branch prediction forces the processor to rewind; whereas a guaranteed jump is potentially less expensive, especially if the code is in the cache. Of course, this would be a premature optimization, but if the code occurred in the inner-most loop, there may be some gains to be had that otherwise wouldn't be.
I suppose, for me, it looks cleaner when you're dealing with larger projects. That said, as I contemplate it further, I could certainly see where it would slip up some people, especially newcomers to my code. This sort of creativity would probably primarily spring up in organic/fluid code where OO paradigms are already in place.
Thanks for making me think on this further :)
On the other hand, if you were actually extending a class to make a DoNothingClass version, then the overhead of dynamic binding plus the function call would make it somewhat slower (branch prediction on a NULL comparison will cost at most 5 clock cycles in a single-thread pipeline, or none if you predict right) on those checks where the DoNothingClass is the one you find. For instance, if you had a sparse array of Class* and wanted to iterate over them, the NULL check would probably be more efficient than pointers to a singleton NullClass, especially since branch prediction will start correctly predicting NULLs more often.
So, you know, trade-offs.
Being a little pedantic here: you can't safely say this part without knowing which kind of branch predictions the processor uses; and, more importantly, how deep the branch prediction can go. There are some processors that will branch predict once ... and then again, and again and again. The first one they get wrong, they have to roll back to that first one. But then, you're right. The more times that single method is called, depending on the implementation of branch prediction, the more often it's going to be right, and so as long as those values aren't changing often (I don't see why they would in the case we're trying to suggest), it may eventually fade into nothing.
Needs moar testing!
I really like the clean design of the site though, very fresh imo.
Read it. Learn it. Love it. Make it a habit that is so ingrained you do it automatically.
Extract method: do it reflexively.
I haven't done any RoR but I imagine a similar approach would work there.
If there's an on/off button to show additional information on the screen, it would seem weird to me to have the button (view) try and display the additional settings. The view has no idea what kind of environment it is in, how does it know if it can display the information without moving other things around. But a controller object managing all the views on the screen does. This also avoids subclassing or adding methods to the view.
If you want a developed vocabulary, Smalltalk is where you find it (and it's been there for thirty-odd years).
Because it's so old, Smalltalk does things very differently to other development environments.
The GUI is strange by today's standards (Smalltalk was why Xerox developed the GUI - you can see how much extra work Apple did to make the windows/icons/menus that we use today).
And it uses an image (which is sort of like developing in a VM, and then copying that VM to another machine to deploy).
So diving straight into a Smalltalk may leave you a bit lost - whereas the book is quite a good primer on OOP in general and where those OO patterns came from (whether Squeak, Pharo, GNU or one of the big expensive implementations).