Monday, March 24, 2008

Domain Specific Documentation

I've often lamented poor documentation in libraries I've had to use. Even more troubling is a lack of proper documentation in my own code. Many times have I vowed to start every project with a flow-chart of all methods with their contracts, only to have it spiral out of control.

A problem with Rails is that it's so productive, the time you spend documenting your code can literally be something like the cube of the time you spend writing to the spec. Some wouldn't call this a problem - but it can lead to you iterating out ahead of yourself to the point where, if you're not careful, you'll end up with an unmaintainable mess.

The solution? RDoc.

RDoc lets you comment as you go. This can be as simple and useless as "this method does stuff" or a more formal contract for each controller action, such as:
# requires:
# - sessions[:username] is_valid_username
# produces:
# - @items_in_cart = ELEMENTS(user = sessions[:username], items U sessions[:cart] )
# - @username = sessions[:username]
# renders:
# - view : 'store#cart_view'
# failure_states:
# - redirect_to view : 'store#index'
def cart_view
...
end

It only takes a second to sketch up, and there's absolutely no question what the operation does and doesn't do.

The requires/ensures clauses are adapted from a RESOLVE-C++ method of design-by-contract. Just because it's an in-house research language doesn't mean you can't learn from it, right? However, Rails' MVC structure as well as the conspicuous absence of pass-by-reference requires some modification. I propose 'requires (things like params), produces (instance variables created), writes (to database), renders (which view), and fails_to (what does it do when the precondition isn't met?).' Fails-to seems like a bad fit for contract-based-design that should never, in theory, fail... but this is the web, and if there's a way for some of your code to break, then it will. The best thing to do is to define it explicitly.

Once you have all of these contracts written, it's trivial to get the sweet, sweet documentation from your code. Add the following to your ${RAILS_ROOT}/Rakefile:
Rake::RDocTask.new do |rd|
rd.rdoc_files.include("app/**/*.rb")
end

Then just ${RAILS_ROOT}/rake rdoc

Voila! Instant domain documentation.

The rest of the week: doing this stuff automatically from the top-down.

2 Comments:

At March 26, 2008 8:24 AM , Anonymous Anonymous said...

Instant useless domain documentation, I'm afraid. Things like what view a method renders or what arguments it takes are the _second_ level of documentation a method needs. The first level, and one which is, as far as I can see, universally ignored by anyone using javadoc/rdoc/foodoc is "Why would I need to use this function".

 
At March 26, 2008 8:35 AM , Blogger legionnaired said...

While that's a good critique if you were designing something like a function library, you tend not to have that problem so much with Rails, considering the nature of the actions you're rendering.

Something like "cart#view" is so obvious in purpose that documentation isn't really needed. It's good practice to name all methods and actions this way, unless space prohibitive. In a virtual tabletop scenario, for example, I might have a method "table#receive_ajax_drag_drop." Obviously, this is going to be something that is called by the ajax in the view, and if I read the contract, I see that it renders a partial, updates the location information in the database for a given id with the location passed as a parameter.

Purpose-based-comments are good, and necessary, I agree. Just a bit redundant with this example.

 

Post a Comment

<< Home