Hacker News new | ask | show | jobs
by richcollins 5933 days ago
Keyword arguments via hashes is certainly not a best practice for "Beautiful API Design". Sending messages is part and parcel to good OO design. Code that deconstructs hashes to emulate keyword arguments is invariably hideously complicated.
2 comments

I agree with you from a language design standpoint, but seriously, I doubt you have much Ruby experience if you're saying this. And if that's the case, you really don't have a basis to be commenting on my book in such a critical and dismissive way.

Using positional arguments for more than two or so things creates connascence of position, which quickly becomes annoying.

Using block based DSLs so that you can break things down into small functions that each do their part is a good way around this, but these tend to be hard to extend dynamically.

Typically well designed Ruby systems provide both: Pseudo keyword arguments for dynamic needs, and some DSL-type syntax for pleasing the eyes.

Yes, it sucks that we don't have real keyword arguments. But no, it's not nearly as bad as you think.

I've been using Ruby since 2005 and I am a top 100 contributor to Rails (http://contributors.rubyonrails.org/). Rails design suffers for its heavy use of "keyword arguments".

positional arguments for more than two or so things creates connascence of position

Which is precisely the reason that you shouldn't pass more than two arguments to a method (unless you are creating a list or hash for their intended purpose). If you have more than two arguments to a method, it's a sign that you should reify them into an object.

Using block based DSLs so that you can break things down into small functions that each do their part is a good way around this

Why do people love making things complicated by misusing blocks. Just create an object and set slots on it. Blocks are best used for delaying computation, not setting up state.

I think that Zed Shaw's essay on the "Master" and the "Expert" is applicable here: http://www.zedshaw.com/essays/master_and_expert.html

Wow, and this is exactly the kind of douchebaggery I was trying to get away from in my book. You know, I think on the first page I mention that the answer to "what the best way" to do something in Ruby is always "it depends". The notion that you can follow singular design principles in all situations is absurd, if you ask me.

Context is king, and what you've done is taken a single point from my book, ripped out its context, strawmanned it to death, and used it to bolster you're own "Top 100 rails" status.

I encourage readers to actually see what I had to say in the book by downloading the PDF before taking these arguments too seriously. If there are some constructive examples to be shown that seem promising and pass peer review, they'll definitely make it into the open source version.

It might even be worth it to write up a section describing the tradeoffs that richcollins has pointed out, but it's definitely not something as universal as he makes it sound.

used it to bolster you're own "Top 100 rails" status.

I've mostly stopped using Rails and Ruby in favor of Io, so I have no interest in bolstering my Rails status. I was just providing evidence to counter your assertion that I was a Ruby newb.

The notion that you can follow singular design principles in all situations is absurd, if you ask me.

I agree that "it depends", but using Hashes as arguments has become idiomatic Ruby, which is unfortunate in most cases.

I wasn't assuming you were a newb, just that you didn't have much exposure to what are commonly regarded as well designed Ruby libraries. It didn't occur to me that you'd have had that exposure and yet sound as if you were shocked and appalled about what is common practice.

I still would really like to see examples of well designed open source Ruby projects that follow what you consider to be decent design principles. I think that'd make a much stronger case for your argument, and might open my eyes to something that's been in a blind spot.

Personally, I feel like a certain amount of API design is bound to what consumers will expect. We can certainly stretch and shift their tendencies, but if we fly in the face of them, our perfection in a vacuum will never be appreciated.

Which libraries do you consider well designed? I'll let you know how I think that they could be improved.
Would you prefer a *rest argument? An options hash is the best way to make your ruby API flexible.
Plain old objects are the best way to make your API design flexible. You can't easily change the behavior of setting a key/value pair for an individual hash. Objects are designed to implement behavior, Hashs are meant to hold key value pairs.
Right, the keys/values need to have supporting logic. And yeah if you're sticking procs in a hash, just use an object; but if your object is just a bunch of fields, use a hash. And often that's all you need. I just want to override the marginWidth on this fooBar.

Remember you don't need to destructure the hash. You've got a local one initialized to sensible defaults, and you .merge! it with the param one, and then just use the hash values as your local vars.

If your object is a bunch of fields, you should still use an object. It might need behavior later.

I think the central issue is that there is too much friction to creating new classes in Ruby. It feels official and final. In Io, you just clone an existing object like you would send any other message.

There's also an embrace of YAGNI and "Do the simplest thing possible ..."

I'd rather start with a hash and then see if it later demands more, than start inventing objects that, based on existing use cases, need only be hashes or structs.

I'd also rather go through a number of pre-1.0 iterations to get real-world usage to shake out the painful API parts. Up until then I'm happy to break compatibility if experience says there's some bad code in there.

Monkeybars, for example, had some methods that began life using positional args, but they were changed to use hash values. I hated it.

Aside from the extra typing required for every call, with positional args I got used to reading code and seeing things in a fixed order, so when I had to use the method myself I knew exactly what needed to go where. (These methods had 2 arguments, with maybe an optional third or so.)

With the hash args the order could vary so I had to keep looking up what the possible argument key values could be.

(Thing about using hashes for args: with positional arguments you need only know what role an argument is playing in order to know what sort of value to pass in, but with a hash you have to know the exact key name. That ends up being more stuff to look up in the docs, especially if the API designer has a quirky way of naming things.)

You might think that at least for a code reader the use of hash keys would better document the call, but the method name and the names of items being passed in were almost always sufficient.

I suspect this particular change was a result of over-thinking these methods and expected usage instead of actually paying attention to what the code was doing right now, and how people were using it right now.

There are times when my gut tells me, "You know what's going to happen with this code later", and I try to take that into consideration, but (usually) simple wins for me.

With the hash args the order could vary so I had to keep looking up what the possible argument key values could be.

You can set slots on objects in any order that you want to:

    Foo.new.set_x(x).set_z(z) ...
The book is opensource, why not submit a <strike>patch</strike> pull request. :)
In all seriousness, all it takes is some code examples that make me and a few others say "whoa, cool" and it'll make its way in.

If RBP becomes "Gregory Brown's Best Practices", it'll be epic fail. I am encouraging folks to prove whatever they can wrong in this book.