Thursday, September 21, 2006

Duplication smells

Duplication. A programmer's worst nightmare. Does that sound over the top? Maybe it does, but I guess most programmers would agree that duplication is one of the worst code smells around. So, a programmer should do everything to prevent and reduce duplication, right? Well, I think that in the case of simple duplication we all agree on that. With simple I mean that two pieces of code are textually equal or very similar, e.g.:

patterns.Publisher().registerObserver(self.onMatchAllChanged,
eventType='view.taskcategoryfiltermatchall')
patterns.Publisher().registerObserver(self.onFilteredCategoriesChanged,
eventType='view.taskcategoryfilterlist')
patterns.Publisher().registerObserver(self.onAddCategoryToTask,
eventType='task.category.add')
patterns.Publisher().registerObserver(self.onRemoveCategoryFromTask,
eventType='task.category.remove')


This is actual code from Task Coach (blush) and represents a component registering itself as observer for different event types. Of course, this type of duplication is rather easy to fix, e.g.:

callbacks = {'view.taskcategoryfiltermatchall': self.onMatchAllChanged, ...}
publisher = patterns.Publisher()
for eventType, callback in callbacks.items():
publisher.registerObserver(callback, eventType)

But then there's also duplication that's not strictly textual, but more structural. For example, two for-loops looping over the same datastructure but with a different body. An example from Task Coach again. These two event handlers update a check-listbox (a list with items that can be checked and unchecked by the user) when categories are added to or removed from a task:

def onAddCategoryToTask(self, event):
for category in event.values():
if self._checkListBox.FindString(category) == wx.NOT_FOUND:
self._checkListBox.Append(category)
self.Enable(len(self.__taskList.categories()) > 0)

def onRemoveCategoryFromTask(self, event):
for category in event.values():
if category not in self.__taskList.categories():
self._checkListBox.Delete(self._checkListBox.FindString(category))
self.Enable(len(self.__taskList.categories()) > 0)


Removing this type of duplication is more tricky. Refactoring to something like the following should work (untested):

def updateCategoriesListbox(self, categories, updateNeeded, updateListbox):
for category in categories:
if updateNeeded(category):
updateListbox(category)
self.Enable(len(self.__taskList.categories()) > 0)

def onAddCategoryToTask(self, event):
def updateNeeded(category):
return self._checkListBox.FindString(category) == wx.NOT_FOUND

def updateListbox(category):
self._checkListBox.Append(category)

self.updateCategoriesListbox(event.values(), updateNeeded, updateListbox)

def onRemoveCategoryFromTask(self, event):
def updateNeeded(category):
return category not in self.__taskList.categories()

def updateListbox(category):
self._checkListBox.Delete(self._checkListBox.FindString(category))

self.updateCategoriesListbox(event.values(), updateNeeded, updateListbox)


But this raises the question: is the increase in lines of code and number of functions worth the reduction of duplication? Or did I miss a more simple solution?

Wednesday, September 20, 2006

Steering open source development

So far, steering the development of Task Coach has been very easy. Since I'm basically the only developer and it's a hobby project that I work on after hours, I've been adding functionality that I thought would be a good idea to add. Judging from reactions by users and on websites I think I did pick valuable features most of the time so far.

However, the list of feature requests has been growing much faster than I have been able to develop them. So, unless I get more people to work on Task Coach, I need to prioritize between features. I have been thinking about different possible ways to do that:
  • Developing a release plan with a different focus per release. For example, the focus for a 1.0 release could be to add all functionality that would make Task Coach a full fledged stand-alone task manager. Then the 2.0 release could focus on integrating Task Coach with e-mail and calendar software (e.g. using vCal).
  • Letting people vote on features. Features with the most votes get implemented first. Unfortunately, Source Forge doesn't support anything like this, as far as I know.
  • Donation driven development. The features that gets the most donations is implemented first.
Maybe a combination of the above could work too. I'll have to ponder this issue some more.

About this blog

Software development is fun and hard at the same time. This blog discusses my experience with developing an open source desktop application called Task Coach. Task Coach is a task manager that supports hierarchical tasks, budget and time registration, categories, and more. For me, Task Coach is also a vehicle to experiment with software development, tools, and techniques. For example, I try to use test-driven development to steer the development of the software. So far, I like the way test-driven development allows me to add new functionality in a safe and gradual manner. I also like how having an extensive set of automated unit tests (+/- 1700 at the moment, that run in about 30 seconds) allows me to refactor the source code without (too much) fear of seriously breaking the application. Anyway, the plan is to use this blog to talk about ideas about software development, and as much as possible, discuss experience with applying this ideas to Task Coach.