Ruby Best Practices: large teams, performance, deployment, and more...

Hello Gregory, I wasn't able to see the table of contents in the online sample, so I hope these questions aren't redundant. I've been taking to Ruby (and RoR) for the past few months and I've certainly enjoyed a productivity gain as well as a fresh appreciation for development, as compared to some of the shackles that I wear when I develop in Java (and it's associated frameworks).

I recently read a great post by M. Fowler at ThoughtWorks on how Ruby (and RoR) has fit into the company. The article left me with some questions - especially since they are such a large organization who have been using Ruby (and RoR) for production projects. I'm curious if Ruby best practices can tackle the curiosities I had after going over ThoughtWorks experiences.

How can Ruby best practices help large teams create clean code bases?

Are there best practices that help with deployment, an area that still seems difficult?

Can best practices mitigate the comparatively slow performance of Ruby?

Is it possible, with best practices, to maintain an increased level of productivity with Ruby?

Metaprogramming attitudes at ThoughWorks seemed to trend towards: useful in small doses. Are there best practices to help developers understand how much and how often?

And finally, how do you see Ruby fitting into a Polygot programming model with other languages like Java, C#, shell scripting, etc?

Michael Sullivan wrote: How can Ruby best practices help large teams create clean code bases?

Ruby really isn't a 'large team' language, so it might be best to start with that

But I'm actually not ducking your question, a majorly important practice in Ruby is identifying separable parts and turning them into their own modules / projects / services (depending on level of abstraction). These can then be maintained respectively by small teams, making it much easier to coordinate things. However, since that code will likely be used by a lot of people, and maintained by different coders over its life cycle, it still isn't reasonable to think the only folks who will need to know how a bit of software works is the team that created it.

So, best practices and idioms can be applied to help make your codebase less surprising to others. Part of this is reducing complexity, but a bigger part of it is making the code work in a way similar to Ruby itself, along with some of the leading Ruby libraries, tend to operate. While Ruby offers us many different ways to do things, there are definitely better ways and worse ways to do a task in a given context. If you're aware of the common patterns other Ruby developers use, and adopt them yourself, your code will be instantly more understandable. This allows you to share responsibility with a larger group of developers, because your solutions will build on top of a common knowledgebase.

Because Ruby is very flexible and dynamic, it's possible to create a byzantine maze out of your source if you are not careful. Learning to balance that power with responsibility is key to most best practices, and it will take you far. I can tell you that as an open source developer, I find most well written Ruby code to be highly readable, even if it is uncommented. The language itself sets up a high standard for API design and interaction between objects, and if you just follow that, you'll find yourself writing code that pretty much anyone can work with.

Michael Sullivan wrote: Are there best practices that help with deployment, an area that still seems difficult?

I don't know. I don't do the sysadmin stuff at my work What I can say is that my deployment is just 'cap deploy', and I never need to think about it.

I'm assuming this is a rails specific question. The easiest answer to it is:

Capistrano, Apache, Phusion Passenger, Ruby Enterprise Edition. Combine all of that, and your deploy is probably not going to be too painful.

Michael Sullivan wrote: Can best practices mitigate the comparatively slow performance of Ruby?

Yes and no. You won't get around any of the hard limitations of the language without dropping down to something lower level. Ruby has about the worst execution speed of any language I've worked with, and once you begin to see all the functionality packed into Ruby, it starts to make sense why this is the case. However, a very important thing to realize is that slow code is often either sloppy or algorithmically inefficient. In that space, there is a *ton* that can be done.

Here are three things to know about Ruby. Method invocation is extremely slow because of all the interception that can happen when an object receives a message. So that means that doing straight recursive calls would be slow, calling lots of methods in tight loops would be slow, etc. Then, object creation and destruction is slow. In Ruby, you don't have any primitive objects, so everything is a big fat Object that consumes lots of memory and takes a while to build. Ruby's garbage collector uses a mark-and-sweep algorithm, which when dealing with a ton of objects, is really slow too! So if you create too many temporary objects in a tight loop (such as when using Enumerable#inject), you're going to see a major slowdown. Finally, eval() is slow, especially when you're evaling a string. It will be much faster to do 1.send(:+, 1) then it will be to do eval("1 + 1"), simply because the former does not require firing up a parser to work. There are about a million good reasons not to eval strings in addition to the performance issue, so just don't do that

These three things only scratch the surface, but they're a good foundation to look for when optimizing. Don't worry about them until you actually have a problem and then do some benchmarking and profiling to see where the problem really is, otherwise your code will be ugly, complicated, and um... ugly. However, these are good facts to remember when you are hunting a bottleneck.

Well optimized Ruby code might still be slow, and there are ways to mitigate that. You can drop down to a lower level, as mentioned before. I really like using tools like RubyInline or RubyFFI for that. You can background worker processes if your task is capable of running concurrently. You can buy bigger, faster computers. None of these are solutions people like to hear, but in practice, Ruby's performance is less of a problem than you might imagine.

Always check the algorithms first. Ruby is a lot slower when it is running dumb code than it is smart code

Michael Sullivan wrote: Is it possible, with best practices, to maintain an increased level of productivity with Ruby?

Absolutely. Not like a 10% difference, but an order of magnitude (or more). The initial investment might seem like hard work, but it pays off continuously once you start to focus on improving your processes. Every new skill you pick up that makes a certain task easier applies forever, and lets you uncover even more subtle levels of mastery. Just because I wrote a book on this stuff doesn't mean I'm done learning. I still read a ton of open source code, and keep up on my tech reading, discussions with other teams, etc. There is infinite potential for personal (and team) growth.

Michael Sullivan wrote: Metaprogramming attitudes at ThoughWorks seemed to trend towards: useful in small doses. Are there best practices to help developers understand how much and how often?

At the end of the sample chapter, I discuss my strategy.

RBP wrote: My general rule of thumb is to ignore all of these advanced Ruby features until my code
illustrates a need for them. If I write several method calls that appear to do almost the
same thing with a different name, I might be able to leverage method_missing. If I want
to endow certain objects with some handy shortcuts, but leave the option of
instantiating a simple, unadorned core object, I might look into mixing in some sin-
gleton methods using extend. By the end of the day, in a large or complicated applica-
tion, I may end up using a large subset of the techniques dicussed here. But if I started
out by thinking about what dynamic features my code needed rather than what re-
quirements it must satisfy, development would come to a confusing, grinding halt.

So here’s my advice about making use of the information in this chapter: just make a
mental note of what you’ve learned here, and then wait until some code jumps out at
you and seems to be begging to be cleaned up using one of the techniques shown here.
If it works out well, you’ve probably made a good decision. If it seems like more trouble
than it’s worth, bail out and wait for the next bit of code to alert you again. Keep
repeating this process and you’ll find a good balance for how dynamic your code really
needs to be.

So, the key is to not even think about metaprogramming until it is necessary. Then, make sure it *really* necessary before diving too deep.
Always build your meta-programmed features on top of a more basic interface, and use that basic interface internally for everything you do.

Meta-programmed code should be introduced as part of refactoring, or as a layer of sugar on top of a simple core. It should never be reached for until there is a need.
It's better to have a slightly more tedious object to work with than one that is so magical that it explodes if you don't treat it just right.

Michael Sullivan wrote: And finally, how do you see Ruby fitting into a Polygot programming model with other languages like Java, C#, shell scripting, etc?

Oh, I think Ruby is great for this. Spend a couple hours playing w. JRuby and you'll find that you can very easily work with Java, Ruby, and C code (via FFI) all in one project. As far as C# goes, I'm not closely following IronRuby, but it should have some solutions there. Ruby's integration with the shell is excellent, a trait it inherited from Perl. While I don't think it's a good idea to make a polyglot without considering the complexity that it adds, Ruby tends to work really well in the presence of other technologies. The fact that it works so well with the shell, and that it's very easy to build web services means that even if there isn't first order support that bridges any given language to Ruby, you can rely on generic approaches to great effect.

Great questions, hope my answers help

-greg

Michael Sullivan
Ranch Hand

Joined: Dec 26, 2003
Posts: 235

posted Jul 22, 2009 20:11:36

0

Gregory Brown wrote: Ruby really isn't a 'large team' language

That's an interesting statement. What do you think the optimum size is for a team that uses Ruby as it's primary language? Do you think Ruby has advantages/disadvantages if the team is distributed geographically?

Gregory Brown wrote: Ruby is a lot slower when it is running dumb code than it is smart code

Pretty funny, but true.

RBP wrote: My general rule of thumb is to ignore all of these advanced Ruby features until my code illustrates a need for them.

I've seen this thought in the Ruby arena before, perhaps it is an agile unique paradigm? I find in other spaces we get into complex architectures and designs before we've coded anything, or before we've discovered enough about the problem. I often find myself promoting simple solutions, and the use of test-cases to drive out the "next level" of complexity that is necessary.

I'm glad you find Ruby a comfortable peer to other languages, as I personally believe that's how it will make it's transition into "one language" shops, and cultures which still have a great deal of FUD.

@Michael,
I liked Phusion passenger with Apache. Its very easy to integrate and on top of that its also let you quickly hook other web applications on top of rails. Example. Integrating wordpress with rails.
You may like this video by Ryan Bates about Capistrano.

Michael Sullivan wrote: What do you think the optimum size is for a team that uses Ruby as it's primary language? Do you think Ruby has advantages/disadvantages if the team is distributed geographically?

As far as geographically distributed teams go, Ruby is at its core, an open source language. So teams that may never meet each other in person are already achieving great things together. I'm not sure how much the language lends itself to that or not, but the reality is that it's being done all the time, both commercially and by volunteers. FWIW, Nearly 100% of my commercial Ruby work for the last several years has been remote.

As far as team size goes, I'd say per-project, optimum team size might be 2-4 developers, + a designer, a project manager, possibly a sysadmin and someone to architect things a bit on larger projects. Often those latter four roles are done by the developers, especially in a startup setting. Of course, given adequate resources, it's nice to have people with dedicated roles. But if you have tens or hundreds or thousands of developers in your organization, things would work best if they could completely own a slice of work and use tests, continuous integration, and abstraction barriers so that focus stays in the right place while still allowing code interaction.

I really don't know though, I'm speaking primarily about my personal experience doing work for Brad Ediger (author of Advanced Rails) at Madriska Media Group. We do work with intermediate/junior developers, but only ones we've hand selected for their ability to learn fast and be highly responsible. There is a lot of direct apprenticeship going on, and daily code reviews. In an environment like that, we probably get a strong multiplier effect so that even though our teams are small, we are tackling absolutely giant projects (broken up properly, of course). Your mileage is really going to vary if you have a more traditional development environment, but even there, it's better to have a couple small, expert teams doing Ruby than it would be to convert your whole shop and have people struggling to 'get it'.

Anyway, like anything else you'll need to try a few things out and see what works for you. It's just difficult to apply a non-Ruby (or I guess more generally 'non-Agile') set of working practices to Ruby and get good results from it. You may still see a boost, but not nearly the kinds of gains you hear others talking about. To do that, you really need to be willing to look at things from a fresh angle. Of course, sometimes that's very hard to do, for any number of reasons. In that case, you just do the best you can with what you have.