Ruby Best Practices (full book available for download)(blog.rubybestpractices.com) |
Ruby Best Practices (full book available for download)(blog.rubybestpractices.com) |
http://sandal.github.com/rbp-book/pdfs/rbp_1-0.pdf
(But please read the article, it provides context :)
So... here's the tl;dr; for the book itself:
"Ruby provides many ways to do things, many of them decent. But some are usually better than others, depending on the context."
Marketing-wise, it was a gamble, because the open source release date wasn't contingent on sales or anything like that. We launched the book and gave a 9 month exclusive rights period to O'Reilly.
This at least takes into account the fact that most books sell the majority of their copies in the first month or two, but I didn't have like a minimum sales number before I could open source the book or anything like that. In fact, the print book says right in the front cover that it'd be open sourced in March.
The book did okay sales wise, not great, but not horrible. It definitely did worse than it should given that (at least from the looks of it) people really, really like it. But this isn't really up to the readers, it's up to the distributor. Basically, much of our sales come from wholesale, and with paper book stores on the decline, and the saturation of the market with Ruby books over the last couple years, it was hard to convince them to pick up a lot of copies of the book.
As of right now, I am just about at the point of clearing my advance. I will share specific numbers at some point, but let's just say that the advance probably works out to minimum wage or less when compared to the amount of effort put in.
Interestingly enough, the gradual week by week release of chapters really led to a nice spike in sales, coming from what was essentially a steady decline except for a spike around the holidays. So actually, I think open sourcing the book was good for sales in that regard. Since it might be hard to find the book on the shelves in a book store, this gives people a chance to "try before they buy".
Unfortunately, because my book was selling above average as an ebook, I've probably effectively killed those sales today. Then again, maybe people will still buy the Kindle and Iphone editions, who knows.
At the end of the day, I think neither O'Reilly nor myself expected this book to be a big bread winner. I love the book because it embodies the most interesting things I've learned from the smartest people I know, and I think that comes through in the writing. I wrote the book because the idea popped into my head fully formed and demanded to be put out on paper. So that was my biggest win.
But from an authors perspective, RBP has given me a great boost in exposure. I already had name recognition in the inner circles of Ruby community, but after writing the book, I find more opportunities from folks I probably wouldn't have been able to reach otherwise. This has been good for my open source projects, and good for my work.
I'm glad I did this, and it worked out well. I'd recommend it to others, for sure. Of course, buying the book will make O'Reilly happy, so go ahead and do that if you want :)
I've talked to a few people who did things along this line, and they are of two minds. One camp says it kills sales. The other says it boosts sales, but in the latter case I don't think there was a PDF version even offered, and they made the book available as a Web site.
I encourage people who think this book is valuable to pay for it in some form, to encourage publishers to do more of the same, and to reward the author.
Even though you may not have been paid a "fair wage" for the work that you put into this book, it sounds like you are very happy with the outcome and the experience and the future benefits that you will undoubtedly reap from this project.
Congratulations and thanks again!
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.
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
That gives the book some extra credibility in my eyes.
EDIT: OK, now that I've read the foreward, I see Matz himself mentions Gregory's work on Prawn. Doh.
I'm still active in the project as a maintainer. But really, these days you have Brad Ediger, James Healy, and Daniel Nelson, along with about 60+ contributors to thank for where Prawn is heading.
My job is mostly to sit around naming things, and warning people about the fragility of the early code I wrote. But we will have a 1.0 plan soon, and no matter who does the work, that'll be good for everybody.
Maybe this will keep RBP moving forward, and keep it alive.
http://wiki.github.com/sandal/rbp-book/questions-for-gregory
https://docs.google.com/fileview?id=0B8mB_WI1jRkCMzgyZWY5MzY...
Looks like it loses some quality on translation to Google docs, though...
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.
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 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.
If you're just looking to get your ideas across, you'll almost certainly get more mileage out of writing articles, doing talks, etc.
And if you're looking to make money, unless you get very lucky, you won't make it directly off your book. It will almost certainly lead you towards other opportunities, but really all it creates is potential, things won't fall into your lap.
Still not discouraged? Then you might really, really want to write a book. That alone is what got me through the two I wrote. So then, go for it!
Here are some rough tips:
0) Get to know publishers a bit before submitting a proposal. I've had friends who got their book accepted cold, but really, there's little to differentiate you from the giant stack of proposals publishers receive daily if you come in without them knowing who you are first. This will also help you quickly identify the sort of people you don't want to work with. :)
1) Have a vision for your book. Get a sense of what you want it to be before you start writing it. This can be something very flexible, and doesn't need to button down any specifics, but know what the structure will be and test it out by attempting to write a chapter against it. If that doesn't work, reiterate. If you are working with a publisher that enforces a particular style, make sure you love it. But avoid those sort of restrictions if at all possible. I've written chapters on books where I was forced to write in a way I didn't like, and it was terrible.
2) Get together a good advisory group. Find the people you look up to in your topic area and ask them to discuss your chapters as you write them. We had a private mailing list for this in RBP, and it was amazingly effective. While tech reviewers can turn up a lot in isolation, you uncover the core issues when you ask them to discuss things as a group.
3) Expect your topics to change somewhat. If you find a chapter extremely hard to write, but there is a suitable replacement, consider introducing it. Completeness is a virtue, but I rather leave something out than cover it poorly. Some chapters will be hard due to necessary complexity, and that's okay. But there are others that are hard just because there is no reasonable way to write about them given your resources. Avoid those.
4) Write steadily, and try to make sure you're producing new content every week. Don't obsess over individual chapters, keep the whole of the book in mind. Leave yourself time to go back and breathe life into your more anemic content, but be sure to keep on rolling, even if its against your better judgment.
5) When forming a chapter, write out a bunch of code and examples first. When you think you have a cohesive set, sit down and write as if you're explaining the code you wrote to a friend or co-worker. Make sure your examples can flow together nicely, where possible. I found this approach to work well with RBP. If I couldn't come up with a great set of examples that went well together, I went back to the drawing board without ever writing a line of prose.
6) Ignore all of the above, and find a flow that works for you.
This stuff is hard, it's okay for it to be hard, and really, all you need to keep in mind is pretty much every other published author has been through the same hell that you'll find yourself in.
But you get a big reward in the end. You (eventually) get your sanity back.
When I was co-authoring Pro VB 6 & XML I was always coming across topics that could be books in themselves. Database details, designing COM objects, stuff like that.
I hated the idea that a chapter would leave the reader n limbo over some key aspect, but also didn't want to book to be even larger than it was likely to be, and explaining stuff can get hard.
So there were lots of places where I simply had to point and refer the reader to some other book or resource. There's not much you can do about that other than try to be mindful of when it is the right choice.
I think there was a post here on HN about that recently, but I can't remember the link. The author who wrote it described pretty much the exact process I went through, even though I think he was working with another publisher...
http://macournoyer.com/blog/2010/03/01/promote-cyopl/
But for me, I didn't want to spend a ton of time and effort on marketing, and my topic was a niche within a niche at the time (I wrote the Ruport Book with Mike Milner -- ruportbook.com).
After a year or so, instead of being thousands in the black, we were hundreds in the red, even though the book sold a few hundred copies. So we just made it available at cost and closed the doors at that point.
This may be because the HTML version was available for free when we started. It may be because we set price points very low (I think our PDF was like $8), it may be because I started an LLC and wasted tons of money on absurd taxes just for keeping the doors open. It was a bunch of epic fail on the business side of things, and that at least taught me a lesson.
I talked to O'Reilly and though they were very inflexible about certain things (due dates, typesetting, etc), they were open to change pretty much everything that mattered to me, and did. As a result, my book shipped early and at a much higher quality level than I could imagine doing myself.
Of course, I could tell you stories about other publishers I either pitched ideas for, did tech editing with, or wrote chapters for that'd make your head spin. So I wouldn't say that professional publishers are good across the board, just O'Reilly pretty much let me run with my ideas and ended up being very accomodating.
This may have something to do with the fact that I blogged and wrote articles with them for a while before writing a book, and because my editor is also a friend of mine from the Ruby community. But I know folks who have pitched O'Reilly cold and had similarly positive experiences, so I don't think that's the case.
Ultimately, publishing is going to be what you make of it. Self-publishing gives you full control, but it also gives you full responsibility. I think you can make money on it if you market effectively, and I think it can be a worthwhile if you really want to do something unique.
But for me, someone who just wanted to get some ideas out there and maybe make a buck or two in the process, doing all of the work of self publishing really was horrible.
I had a mixed blessing with my first book effort, Pro VB 6 & XML. I started out as one of about 10 authors, expected to contribute one or two chapters, and ended up as one of two authors and writing a little over half the book.
I got to write about stuff I was already quite familiar with, so it went pretty quick. And the book did OK, so I made some decent money, and the time spent was well worth it.
However: It gave me the impression this was a repeatable event. :)
Aside from the odd chapter for Wrox (most of which never saw the light of day because books got canceled, though I still got paid since I was not on royalties for them), my next book effort was Beginning Ruby for Wrox, around 2001 or so.
Way more work, the tech market was turning to shit, and Ruby was still an uber-niche area. Wrox then went bankrupt, and I never saw my final check for work completed. I got, I think, a grand for several months work.
Just before Wrox burned I had some discussions with my editor who suggested various ways to monetize what I had already written. Mostly it was "have a Web site".
Thing is, there is so much free quality info already available that drumming up eyeballs and getting people to part with cash is quite tricky.
If you're thinking of writing a book you really need to have your eyes open and keep realistic expectations, and be comfortable with what you can expect. If the goal is to build a rep, then sales may not matter. But you are still competing for attention.
Greg has the Ruby Best Practices blog; I'm quite honored to be allowed to publish there (and I wish I could get my ass in gear to write more). I'd be all for having ads on the site and finding a way to get some cash back to Greg & O'Reilly so long as it didn't spoil the reader's experience.
But I think that's quite doable.
I also think E-books can do better by exploiting their E-nature. I've had some ideas for E-books that weren't simply paper books turned PDF. There is lot's you can do with them, and perhaps there are ways to hook them into a recurring revenue service (say, a subscription to Best Practices screencasts or something.)
I can't emphasize enough, though, that people who find these things valuable should see about remunerating the author. If you want to see more things like this, help out.
People like Greg deserve a lot of credit for trying things out and trying to provide quality material in an affordable way. He is one of the reasons I'm proud to be part of the Ruby community.
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.
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.
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.
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.
Anyway, point being, I was pretty obsessive about not letting the book balloon. It forced me to put the knife to a lot of good content that wasn't quite great.
Until then, stop acting like you're some sort of authority (NOTE: This is not to claim I am -- because I'm not and no one is.) I just think it'd make sense to put your ideas into a bit of context so people can see that you're not just being critical for the sake of being critical.
#from your book
def distance(*points)
case(points.length)
when 2
x1,y1,x2,y2 = points.flatten
when 4
x1,y1,x2,y2 = points
else
raise ArgumentError, "Points may be specified as [x1,y1], [x2,y2] or x1,y1,x2,y2"
end
Math.hypot(x2 - x1, y2 - y1)
end
puts distance(1, 2, 5, 6)
puts distance([1, 2], [5, 6])
# why would you choose to make a complicated interface that requires a complicated implementation?
# simple, clear interfaces that allow for simple implementations
# are almost always preferrable to complicated ones that save a
# keystrokes through syntax
class Point
attr_accessor :x
attr_accessor :y
def initialize
self.x = 0
self.y = 0
end
def self.with(x, y)
new.set(x, y)
end
def set(x, y)
self.x = x
self.y = y
self
end
def distance_to(a_point)
Math.hypot(a_point.x - x, a_point.y - y)
end
end
puts Point.with(1, 2).distance_to(Point.with(5, 6))You can set slots on objects in any order that you want to:
Foo.new.set_x(x).set_z(z) ...It would seem you only see the surface beauty of things. [redacted -- good point]
To be clear, I think that it is great that you created this book. I just responded as I did because I'm often frustrated when I use Ruby APIs that sacrifice good design (imo) for syntactic terseness.
Point.with(1, 2).distance_to(Point.with(5, 6))
Over this?
distance [1,2], [5,6]
I guess it depends on how many interesting things you wanted to do with points. As things get more complicated, maybe something like this is worthwhile, but I don't feel that making everything an explicit object is categorically better.
But the section you're quoting is among the most contrived examples that RBP gives, it's mainly just part of a list of the different sorts of argument processing Ruby offers and the trade-offs betweeen them.
Maybe you skimmed and missed that, or maybe you read through and chose to ignore that detail.
0. It breaks the fundamental OO convention and doesn't encapsulate logic and data.
1. It's not clear what it means unless you look up the docs.
2. It's implementation is complex, making it more difficult to understand and maintain
3. It fails to separate concerns, making it less extensible/modifiable. e.g. What if you wanted to override the setting behavior? You couldn't do it in one place.
Is that how you defend an API? Insult the user?
Holy shit that's stupid.
def to_hash
{:x => @x, :y => @y}
end
def to_ary
[@x,@y]
end
def [] i
case i
when 0,:x
@x
when 1,:y
@y
end
end
Should work if the receiving method interprets the arguments properly.