jvdb.org Blog

Organizing maintenance 2: Modifying

on Tuesday 6 March 2007 @ 18:10 in Software Maintenance

This is the second part in a small series on organizing maintenance activities. The first part dealt with understanding the system to maintain. This part is about actually performing the modifications. How you go about this has a lot to do with the stage the system you’re modifying is in, as I wrote before, there’s a big difference between the so-called evolution and servicing stages in a system’s lifecycle. Although unfortunately, you’ll often be expected to evolve an application when it’s only realistically possible to service it. I will focus on this particular type of maintenance in this post.

The defining characteristic of software evolution as opposed to software servicing is the implementation of new user requirements. If the application is expected to actually change or enhance its functionality, it’s still evolving as opposed to just being kept running or serviced. Evolution is only really possible if the original development team is still available (and preferably involved in doing the changes) or if you’ve had enough time to study the application’s architecture in such a way that you fully understand all design choices and their implications. If this is the case it should be fairly straight-forward to modify the system, given that the requested modifications are possible in the context of the application’s architecture. If they’re not, then there’s no trick to make this work: you can either attempt to reject the request, explain that implementing this in such a way that the application remains evolvable in the future will be a very large change (at the architectural level) or implement it anyway knowing that this will damage the architecture and push the system into the servicing stage.

In the third and unfortunately usual case, there are some important things to pay special attention to, since they’ll make your work easier in the future when you need to perform additional maintenance. Note though that these are not necessarily good tips when doing initial development where you can still make fairly large changes to the general design. In those cases there are often more elegant solutions. (Also note that component can refer to class, object, assembly, package, etc.)

  • Keep the abstractions consistent. When modifying a component, make sure you understand what it is hiding: don’t add a method that somehow exposes everything the rest of the component was encapsulating to quickly add something new. The problem is that exposing for instance a private member that was never exposed before will suddenly allow users of the component to mess with its internal state. Future modifications can turn it into a source of bugs.

  • Do not depend on state. When adding a public method (or anything else that’s public), do not depend on the state of the system outside the component. Instead, let callers provide everything it needs in parameters so that you can use guard clauses to check their validity. For instance, sometimes a method may need to serialize some of its data to a file. Unfortunately, assuming that it’s always possible to create a file in the current directory and write to it is a dangerous assumption. Instead, let the caller provide an object the method can write to.

    This ofcourse means you’ll have to make more modifications outside the new method than you might have hoped. However, this is a good thing since it will force you to consider the impact of what the method is doing at every location you’re calling it from. And more importantly, future callers will have to do the same.

  • Update to current components. I recently modified a .NET application that was developed when the framework came out in around 2002. There was quite some code dedicated to multi-threading through the use of a Thread instance and a mutex for communicating status information. Since .NET 2.0 however, there’s the BackgroundWorker component that simplifies this type of work immensely. So instead of maintaining the old code, it was much easier to migrate it to use a BackgroundWorker. Don’t modify things that are working correctly, but if you have to modify it anyway, update it if there’s an easier way to achieve the same thing.

  • Consider wrapping components. If a component has a large amount of callers (a high fan in) and modifications are to be performed that are only relevant to a couple of these callers , consider wrapping it for them in another component so as not to disturb the relationship of the component in question with its other callers. This doesn’t make the system any prettier, but if you have no way to easily verify all the interactions (which is often the case in large maintenance projects), then this will be a relatively safe solution.

  • Adapt to the used standards. This applies to both the functionality of the code as to the style: if an entire system doesn’t throw exceptions but rather returns values to indicate success, you should either change it everywhere or use it as well in the newly added code. The same is true for coding standards: code written using a crappy coding standard consistently is easier to read than code that has several coding standards, including an exceptionally elegant one. This is in line with the consistency code quality attribute.

These are some of the things to remember when modifying a system that’s either too large to fully comprehend or too complicated to properly evolve (or both). Another important technique is using metrics, which I’ll discuss in a future post in some more detail.

Leave a Reply