Live data from Hacker News

Tell, don't ask

robots.thoughtbot.com

61–70 of 88 posts

Re: Tell, don't ask

#61

Example 3 adds a nonsense method, EmailUser#send_to_feed. What does that mean? Email users don't have feeds. If we're going to evangelize OO purity, let's do it right. 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 te…

carrying on, why make all user models define a no-op? class User def post_created(post); end end class TwitterUser

A better solution would be to just use Observers. One for email, one for twitter. The user shouldn't care about how to talk with these services.

Re: Tell, don't ask

#62
post #11

For me, this technique is bigger than objects or encapsulation. It's about reusing existing "branch points" that a language gives you (whether it is polymorphism, method dispatch, namespacing) instead of explicit conditionals at a level higher than the language. My general take is that explicit conditionals in a high-level language are a smell. Sometimes they're necessary, but if you tell yourself that they mostly ar…

> I'm not saying it's possible to avoid conditionals completely But it is. Smalltalk has no conditional statement, conditionals are implemented via polymorphism on the subclasses True and False (ignoring compiler optimizations).

That smells of a parlor trick -- passing one or two blocks / objects / functions to a boolean object to execute or not.

Having said that, I like how many OOP languages implement loops as a special case of "visitor", though.

Re: Tell, don't ask

#63
post #58

Earlier quoted context omitted.

My example is not newbie programmers, rather, much code written by experienced OO programmers who don't have a Smalltalk background. I can't say what it looks like now, but I recall Rails active record implementation being heavily procedural in nature when I first looked at it way back and DHH is hardly a newbie. Decompile most classes in the dot net framework, and you'll find a fuck ton of procedural code even thoug…

Well, this is leading a bit astray. 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…

Of course, that's where taste comes in. And I did say "as an exercise", I wasn't suggesting not using them ever, but as a kata to see how much you can do with only objects.

Re: Tell, don't ask

#64

Earlier quoted context omitted.

My example is not newbie programmers, rather, much code written by experienced OO programmers who don't have a Smalltalk background. I can't say what it looks like now, but I recall Rails active record implementation being heavily procedural in nature when I first looked at it way back and DHH is hardly a newbie. Decompile most classes in the dot net framework, and you'll find a fuck ton of procedural code even thoug…

What's so "procedural" about 500 line routines (procedures, methods, functions)? 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).

Sequential thinking. Long methods tend to come from thinking only about accomplishing a task step by step rather than building a machine that can solve many problems (an object model).

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 ]

Re: Tell, don't ask

#65

Earlier quoted context omitted.

I'm not clear on how that makes your code more maintainable though. The first case makes it explicit that any of the actions can fail to occur, whereas the second one, on first glance, seems to have all the actions occuring. I, as a newcomer to this code, will almost definitely make that mistake, which will make debugging or maintenance harder. The only way the second is even -as good- is if I'm constantly holding in…

I may expand on this later; but, whenever you're going to a new code-base, you're going to have to learn the various idioms that are at work in that code base. This is especially true when you're working with some more complicated languages where no one uses the whole set of it (see: C++). 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 wro…

It's an interesting point about the speed. In this case, since it's just a function pointer, there's no polymorphic overhead, so avoiding branches is a no-brainer.

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.

Re: Tell, don't ask

#66

Earlier quoted context omitted.

> I'm not saying it's possible to avoid conditionals completely But it is. Smalltalk has no conditional statement, conditionals are implemented via polymorphism on the subclasses True and False (ignoring compiler optimizations).

That smells of a parlor trick -- passing one or two blocks / objects / functions to a boolean object to execute or not. Having said that, I like how many OOP languages implement loops as a special case of "visitor", though.

It's not a parlor trick, rather it's what it means to be object oriented. Traditional language constructs are built with object and methods as library rather than special magic keywords. Whatever the problem, Smalltalk builds the solution using objects, thus it is oriented towards objects.

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.

Re: Tell, don't ask

#67

Disclaimer: not a Ruby user. I'm confused by example 4. Why would you return a message from either of them? Shouldn't messages in general belong to the view? {{ 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…

Your example is not functionally equivalent as address is a model instance with many address-related properties. Though I agree with your general premise. This, to me, seems like the Tell, Don't Ask solution:

    class User
      delegate :street_name, to: :address, prefix: true, allow_nil: true
    end

    

Re: Tell, don't ask

#68

Earlier quoted context omitted.

It's a poor example of a reasonable technique. Reusing branch points is great. Adding an expensive branch point (inheritance to the User class) to replace a cheap one (if statement) is not a win, particularly when it screws up the model. 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 st…

That depends on what you're optimizing for. If you care about making your code smaller (which reduces the occurrence of bugs), making it more readable, etc., and performance isn't much of an issue, then it probably is a win. If you care about getting maximum performance... well, then, you need to get a good knowledge of the target system's performance characteristics and how language features are implemented in order…

> If you care about making your code smaller (which reduces the occurrence of bugs), making it more readable, etc., and performance isn't much of an issue, then it probably is a win.

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.

Re: Tell, don't ask

#70
post #43

Earlier quoted context omitted.

Is that supposed to be a good thing? I always found that style extremely hard to follow.

You have to change how you read code. Stop worrying about implementation details and see the objects API, and stop digging into every method, you don't need to see the implementation all the time. Step back, look at the classes and the messages between them and ignore the implementation whenever possible. When you understand how the parts work together, then you tend to know which part is broken for any given bug, an…

But they're not simple. The complexity is still there, it's just distributed and difficult to trace.

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.

Post reply on HN