Sunday, July 31, 2011

From a Java/C# world to Ruby's: Writing modular code

For the past handful of months I've been dabbling in Rails in my free time to play with some projects.  It's a fun platform for creating applications and I (as has been mentioned) dig Ruby.  I'm starting to actually build up enough code to warrant a little more discipline than what I've had so far.  For the most part, I've been plowing through just trying to experience all the tools and tricks the platform has to offer.  I couldn't really be effective otherwise if I hadn't given myself any time to play in the sandbox.

One thing I'm trying to learn is how to modularize code in Ruby.  How and where do you draw lines of responsibilities?  Where should code live?  Should I use a module or should I use a class?  There hasn't been a straight forward answer.  Ruby being dynamic, open classes and being able to intercept/modify just about any behavior in the system...it feels like my first year away at college (well, not really but you catch my drift).  While it's a lot of fun, you have to make sure you don't run wild with your freedoms.  There's a limit and you have to govern yourself.

Again, where are the lines drawn?  What are the right things to do?  It's not apparent.  Look at models as they're known in Rails.  Models use the Active Record pattern where each instance constitutes a record in a persistent store.  So what is the typical Rails model responsible for?  Reading and writing itself from a persistent store at the least (along with any of its child models).  This alone will make members of the CQRS Illuminati grow faint.  But it doesn't stop there.  Also, you need to perform your validation there which makes sense for any stateful, data-driven creature to do.  

At first, I let everything pile into my models.  If I had 3 ways I wanted to query things from the DB?  Oh, hey, I need to query my associations too, what should I do with all that logic?  Put it in the model.  If I had extended validations dependent on certain conditions?  It undoubtedly went in the model.  If I add an authentication framework that needed to decorate the client classes?  Hey, I'll just add it to the model!

It never felt right and as I piled more functionality on, things became especially itchy.  Instead of trying to foresee how this would all pan out and try to apply some half-brained pattern of my own, I just went for it and made things a sloppy mess.  I really wanted to see what the wrong way to do things was so then the answer would be more apparent.  Just like my early days when I realized how tests benefited my code, I could learn from it.  Why?  Because Ruby isn't Java or C# and I'm a Ruby part-timer.  I've seen and read about trying to apply patterns from either of those languages that is inappropriate.  I decided to let mother nature dictate how I should proceed.

In the case of the rogue models, I found what makes me most comfortable.  First, anything related to the data and validation of a model stays in its class definition.  Second, any associations defined stay in the model's class definition.  Third, anything that demonstrates how that model behaves in its domain should stick around (if possible).  I want to be able to see and quickly digest what the model is and what it's related to.  

Last, everything else, provided that its a significant amount of code, is placed into modules.  Modules allow me to create meaningful, cohesive groups of methods and constants.  For example, the code to query the DB (in any number of ways) is pulled out and placed in some sort of data access module.  Modules, while not being the same as classes, act very much like a class in most senses.  What I don't have a feel for is how many includes is too many includes.  The models get this very facade-like feeling.  They do a lot.  It's still something I haven't quite gotten used to yet.  

So, the short of the long, modules are nifty and I can draw parallels with how I used interfaces (and ultimately their implementations) in Java/C#.  It's the same song, just a different dance.  Use them to decompose the larger objects and group logically related functionality.  

No comments: