Saturday, April 05, 2008

Writing Maintainable Code

I'm not a real programmer. Instead, I'm just some guy that's written lots of code. I've probably spent half of the past 13 years doing software development -- I've spent the rest of the time doing a hodge podge of various stuff -- managing people and projects, writing business plans, making sales calls, figuring out requirements, whatever.

The fact that I'm not a real programmer doesn't mean I haven't gotten to work on real programming problems (fortunately). I've gotten to write device drivers, hack on parts of the Linux kernel, write gobs of report generation and data analysis tools, build dynamic web sites, and a whole host of other things. But still, I'm not really a programmer.

Instead, I'm a problem solver -- some of the problems just happen to be solved with software. And the bulk of the software work I tend to get to do is maintenance work (finding and fixing bugs, and adding features). So I've gotten good at reading code. I suspect I'm a whole lot better at reading code than writing it. Which, is probably a good thing considering that maintenance work is mostly about reading code.

Which brings me to today's random musing: Writing Maintainable Code.

The code you write is only partially a set of instructions for the computer. Your code is also a letter to the maintenance programmer describing the solution to a problem. Clarity about the solution -- and the requisite (but usually missing) clarity about the problem -- goes a long way to making your code more maintainable.

Over time, I'll probably have lots of examples of code that starts out working (in that the computer can figure out what to do with it) but broken (makes things hard for the maintainer which translates into more pain for me).

Here's a simple example drawn from a common class of problems. The original author is setting up a dictionary of handlers for a Django based site.
urlpatterns = patterns('',
# Account Management
('^%s?$'%settings.LOGIN_URL[1:], login),
('^%s?$'%settings.LOGOUT_URL[1:], logout,
{'template_name':'registration/logged_out2.html'}),
('^%s'%settings.PROFILE_URL[1:], include('pycon.usermgr.urls')),

## Site Applications
('^%sproposals/'%settings.ROOT_URL[1:], include('pycon.propmgr.urls')),
('^%stalks/'%settings.ROOT_URL[1:], 'pycon.propmgr.views.accepted_talks'),
('^%sschedule/'%settings.ROOT_URL[1:], include('pycon.schedule.urls')),
('^%sregistration/'%settings.ROOT_URL[1:], include('pycon.registration.urls')),

# Django Admin:
('^%s' % settings.ADMIN_URL[1:], include('django.contrib.admin.urls')),

# root level site features
('^%s$' % settings.OPTIONS_URL[1:], 'pycon.views.set_option'),
)
This isn't an uncommon task -- a set of key to value mappings. It shows up ALL OVER your code. Think variable definitions, hash tables, tuples, lists of lists, nested arrays, function parameters (for some languages), etc.

If you think of this as a table mapping keys to values though, suddenly it becomes obvious what it should look like in code. At least, it should become obvious. Here's a rendition of the above that makes just as much sense to the compiler, but is easier to read and follow. It takes a bit longer to type (adding those extra spaces and alignment), but pays for itself each time somebody has to read it.
urlpatterns = patterns('',
# Account Management
('^%s?$'%settings.LOGIN_URL[1:], login),
('^%s?$'%settings.LOGOUT_URL[1:], logout, {'template_name': 'registration/logged_out2.html'}),
('^%s'%settings.PROFILE_URL[1:], include('pycon.usermgr.urls')),

# Site Applications
('^%sproposals/'%settings.ROOT_URL[1:], include('pycon.propmgr.urls')),
('^%stalks/'%settings.ROOT_URL[1:], 'pycon.propmgr.views.accepted_talks'),
('^%sschedule/'%settings.ROOT_URL[1:], include('pycon.schedule.urls')),
('^%sregistration/'%settings.ROOT_URL[1:], include('pycon.registration.urls')),

# Django Admin:
('^%s' % settings.ADMIN_URL[1:], include('django.contrib.admin.urls')),

# root level site features
('^%s$' % settings.OPTIONS_URL[1:], 'pycon.views.set_option'),
)
Wasn't that easier? Yes! Of course it was easier! That was the whole point of the exercise.
What changed? Just the layout. We made the code more consistent in its layout. This happened in two ways. First, since this is a table of data, we made it look like a table by separating the various columns. Secondly, we dropped the extra '#' in the Site Applications line.

Why should we care about consistent layout? It makes the code easier to read. Think about working with code being a two step process, the first is reading (akin to the lexer reading the source) and the second is understanding (sort of akin to the parser doing stuff with the tokens). Every neuron you force the maintainer to spend on lexing is one that isn't getting to focus on parsing.

Make lexing easier for the maintainer. They'll thank you. Or hate you less if nothing else. Sometimes that's all you can hope for.

No comments: