Click here to Skip to main content
15,867,879 members
Articles / Programming Languages / C# 4.0

Writing Maintainable Code

Rate me:
Please Sign up or sign in to vote.
3.90/5 (13 votes)
13 Sep 2009CDDL9 min read 46.4K   27   21
Here I suggest some guidelines that all programmers, regardless of language, can follow to keep their code maintainable by others.

Introduction

The success rate for software development projects around the world is shameful. Projects are very often delivered late or over budget, which is acceptable because the alternative seems to be not delivering the project at all. Certainly there are things in the development lifecycle that are out of the developer's hands, such as the quality of the requirements. However, one cause of poor software delivery success rates is that applications with large amounts of code can quickly become unmanageable, tangled messes that no one wants to touch. While the steps in this article aren't the only answer to these problems, writing maintainable code will help keep projects moving to completion and through the support phase.

Write For the Person Who Will Maintain Your Code

Unless you work for yourself, the code you write will likely be touched by someone else at one time or another. Just because you felt like you were looking for a needle in a haystack when you were maintaining someone else's code doesn't mean you should force the next person to have the same experience. Instead, focus on things you can do to make your code more readable.

Variable/Method Names

I'm not going to advocate the use of any particular notation or convention. As long as the notation makes sense and is consistent, you're probably fine. What you want to avoid is changing notation dramatically during the course of development work. The reader should be able to get used to the notation, whatever it is, fairly quickly, and changing conventions mid-stream will only cause confusion.

One thing you should focus on is making readable variable, function, and object names. You might not think there's a problem with this:

C#
//s will be some type of saxophone
Object s = this.GetSaxophone();

The problem is that if you have a long function, you will have to remember what "s" is. Instead of doing that, try this:

C#
Object saxophone = this.GetSaxophone();

Now, no one needs to question what "saxophone" is supposed to be.

The same concept applies to method names. The method name should be a short description of the method's purpose, not some arbitrary set of letters. Also, since you are naming functions after what they do, you should ensure that your function names are verbs to reflect their purpose.

Short Methods

It can be difficult understanding what's going on inside many functions, but it's much worse when the function is 2000 lines long. If you're debugging such a monster, you're likely going to forget all of the assumptions that were in place when the function started. You're also likely going to have problems mentally breaking down the function into smaller bits of functionality for debugging. The solution is to explicitly break the function down into smaller ones, ideally groups no larger than your computer screen. (No, this does not give you an excuse to cram as much code into each line as possible.) Your parent function could call these child functions, giving the code reader an easy way to get a high level view of the code's purpose by looking at the parent function, while also allowing the reader to see each component individually by looking at the child functions.

Refactoring

Whenever you're making a change to code, you're probably either making it better or making it worse. If you're leaving code in that no longer works, or are working around code that may or may not work, or are leaving in code that works but has turned into spaghetti code due to all of the changes made, you're making it worse. Take the following example:

C#
//At this point, my application only supports alto saxophones
MySaxophone.DoSomething();

Let's add support for tenor saxophones:

C#
if (MySaxophone is AltoSaxophone)
	DoSomething();
else if (MySaxophone is TenorSaxophone)
	DoSomethingElse();

By this point, you should be thinking about refactoring this code. What happens if you add a BaritoneSaxophone type? How about SopranoSaxophone or BassSaxophone? If you're working in an object-oriented language, you should consider moving this method into the base Saxophone object and override it in the specific saxophone types when needed. Otherwise, you could run into a rather large if/else chain.

If you're thinking that this example isn't so bad, you're right. If your application is fifty lines long, having one awkward chain of if/else statements isn't going to make or break your application. When you apply this idea to an application that has several hundred thousand lines of code, though, it should be fairly obvious why it's important to keep on top of the little things before they become big problems.

Real Fixes vs. Quick Fixes

Related to refactoring, you as a developer should always be looking to fix the cause of the problem, not to fix the symptoms. For example, if an application blows up whenever it goes to the database and gets a string when it expects an integer, many developers I've encountered will trap the data and try to turn it into an integer the best they can. That will make the application worse. The correct solution would be to find out why the non-integers are being put into the database in the first place. Is this the cause of the bug? Do you misunderstand what this field is actually being used for? Or is the actual problem something else entirely? Without the answers to these questions, you're at best creating additional confusing code for the next person to clean up and at worst causing more bugs than you are fixing.

Commenting/Documentation

You might be surprised to see that I haven't mentioned comments or inline documentation yet. Comments can be useful at times, but one should never write a comment to explain an unclear block of code without first making some effort to make it readable.  Your goal should be allowing your reader to understand what you are doing the first time through your code.  Use comments when they get you further towards this goal. 

Keep Code Testable

There have been several studies done stating that the sooner an error is found, the cheaper it is to fix. Running the application and testing each possible scenario can be time-consuming, and regression testing large applications can be expensive and unpleasant. Fortunately, you can write methods to test your own code, helping to eliminate errors before they are found by testers or end-users.

Write Unit Tests

Unit tests are essentially bits of code that test individual components without needing to step through each line manually. Here is an example of a method that could use a unit test:

C#
Decimal CalculateDiscount(Int32 age, Decimal amount)
{
	// Code to calculate your sales discount here 
	// based on the age of the customer and the size of the purchase
}

Here are the skeletons of some potential unit tests:

C#
public void CalculateDiscount15And100Test()
{
	//I'm deliberately leaving class information out
	//so the code is readable to as many programmers as possible
	Decimal discount = CalculateDiscount(15, 100.00);
	Assert.AreEqual(.10, discount);
}

public void CalculateDiscount40And500Test()
{
	Decimal discount = CalculateDiscount(40, 500.00);
	Assert.AreEqual(.12, discount);
}

public void CalculateDiscount70And1000Test()
{
	Decimal discount = CalculateDiscount(70, 1000.00);
	Assert.AreEqual(.15, discount);
}

Assuming for the sake of example that there is an error in the code that calculates discounts for 70 -year-olds, your unit test results might look something like this:

CalculateDiscount15And100Test: PASSED
CalculateDiscount40And500Test: PASSED
CalculateDiscount70And1000Test: FAILED

There are two important advantages to being able to test your code like this. The first is that you don't actually have to start up your application to test this method. If this were contained within an e-commerce website, you might have to log in as three different users and make three different purchases in these amounts to test the same scenarios, which would be very time-consuming. Unit tests are much faster to run. The second advantage is that these unit tests would continue to exist in your project (though preferably within their own container so the code wouldn't be pushed to production). They could be run periodically, which greatly reduces the need for manual regression testing.

A further discussion of unit testing is beyond the scope of this article, but entire books have been written on the subject. There are also multiple frameworks available in multiple languages to help you get started.

Write Once, Use Most Places

Another benefit to breaking your code into smaller pieces is that the components can be reused. Using the example in the previous section, if your application needed to get the discount amount in multiple places, you can simply call your original function in each place and can reasonably expect it to work. Having said that, don't take this to extremes. There are scenarios in which you may be tempted to push the limits of code reuse. Create new functions or objects when necessary to keep your code simpler.

Use Exceptions Wisely

Exception handling (preventing the user from seeing their application crash) is, in general, a good thing. Most undesirable situations, such as the user entering inappropriate values, should be anticipated and handled properly. However, exception handling can be overdone. There are scenarios where it is appropriate for your user to see a generic error screen (which of course sends you, the developer, an e-mail telling you an error occurred and hopefully what caused it). To see why, let's look at another saxophone example:

C#
try
{
	MySaxophone.GetKeyByName("G").Press();
}
catch
{
	/* Code to handle your exception here */
}

Unfortunately, I've seen multiple applications where the exception handling code (in the "catch" section) does nothing whatsoever - the application just moves along without giving the user any indication that there was a problem. The reasoning behind that approach seems to be that if the user doesn't see an error screen, there will be fewer complaints. While this may be true, the result is that you (as the programmer) won't get helpful information to solve your problem. The user won't know a serious error occurred. If they notice anything at all, it will be that the application doesn't work as they expect, so you may get complaints about your user-interface, faulty data, or maybe 100 other things, none of which point directly to your problem.

In this case, a saxophone without a G key wouldn't be of much use to most players, so hiding the error from the user isn't a good idea. If saxophones often didn't have G keys, I would suggest using local error-handling code to show the error to the user and tell them ways they might solve the problem themselves. Since saxophones shouldn't be missing G keys, you should strongly consider skipping the localized error-handling logic completely. Let your global routine handle this error, since having a saxophone without a G key is serious enough for the user to take notice and for you to be notified immediately.

Focus on the Big Picture

Ultimately, the goal is to allow the person maintaining your code to focus on the problem at hand, rather than sorting through your mess. The usual methods of making code maintainable, such as naming conventions, patterns, etc., are only useful if they give the next person a way to understand what you were trying to do. So don't feel like you have to pick the "right" naming conventions, patterns, etc., as long as you are continually making an effort to make your code readable. And remember, if another programmer doesn't understand your code, it's probably your fault.

Further Reading

If you wish to read further on the subject, I highly recommend Steve McConnell's book "Code Complete", available from Microsoft Press. If you ignore the fact that the title is grammatically awkward, it is a very good book, filled with practical advice every programmer should consider while writing code.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Technical Lead
United States United States
Scott started his career in the working world as a band instrument repairman at a music store in the Chicago area. Not long after starting repairing, Scott started teaching himself various web technologies (C++, HTML, CSS, JavaScript, and eventually ASP.NET with C#) and soon discovered that programming matched his interests and abilities much better than repairing band instruments. After working in sales and web development for a music store, he started working for Adage Technologies, a web development consulting firm specializing in custom software solutions. He is currently a lead developer/project manager for a multi-phase, multi-year project for a non-profit firm. He spends most of his free time studying for his MBA at the Kelley School of Business at Indiana University.

Comments and Discussions

 
Generalvote of 5 Pin
Omar Gameel Salem20-May-10 3:15
professionalOmar Gameel Salem20-May-10 3:15 
GeneralAltzheimer's Law Pin
Brian Lowe15-Sep-09 3:52
Brian Lowe15-Sep-09 3:52 
General.DoSomething Pin
kg2v15-Sep-09 1:24
kg2v15-Sep-09 1:24 
GeneralRe: .DoSomething Pin
Scott Norberg15-Sep-09 3:32
Scott Norberg15-Sep-09 3:32 
GeneralShort variable names Pin
supercat914-Sep-09 9:02
supercat914-Sep-09 9:02 
RantRe: Short variable names Pin
John B Oliver17-Sep-09 1:40
John B Oliver17-Sep-09 1:40 
GeneralRe: Short variable names Pin
supercat917-Sep-09 5:36
supercat917-Sep-09 5:36 
GeneralMy vote of 2 Pin
FredericSivignonPro14-Sep-09 5:17
FredericSivignonPro14-Sep-09 5:17 
GeneralA few more tips Pin
Michael Frey13-Sep-09 20:19
Michael Frey13-Sep-09 20:19 
GeneralRe: A few more tips Pin
Tommy Carlier13-Sep-09 20:32
Tommy Carlier13-Sep-09 20:32 
RantRe: A few more tips Pin
Johann Gerell13-Sep-09 20:40
Johann Gerell13-Sep-09 20:40 
GeneralRe: A few more tips Pin
frugalmail28-Sep-09 23:41
frugalmail28-Sep-09 23:41 
AnswerRe: A few more tips Pin
Johann Gerell28-Sep-09 23:44
Johann Gerell28-Sep-09 23:44 
The problem with it is that it requires backwards thinking/reading, which makes it not easier to read.

--
Time you enjoy wasting is not wasted time - Bertrand Russel

GeneralRe: A few more tips Pin
PIEBALDconsult14-Sep-09 6:06
mvePIEBALDconsult14-Sep-09 6:06 
GeneralRe: A few more tips Pin
frugalmail28-Sep-09 23:43
frugalmail28-Sep-09 23:43 
General[My vote of 2] vote of 2 Pin
Donsw13-Sep-09 15:36
Donsw13-Sep-09 15:36 
GeneralMy vote of 1 Pin
Frans Bouma12-Sep-09 22:51
Frans Bouma12-Sep-09 22:51 
GeneralRe: My vote of 1 Pin
Squirrel Hacker14-Sep-09 1:30
Squirrel Hacker14-Sep-09 1:30 
GeneralDocumentation is for other purposes than to explain what the code does PinPopular
Frans Bouma12-Sep-09 22:51
Frans Bouma12-Sep-09 22:51 
GeneralRe: Documentation is for other purposes than to explain what the code does [modified] Pin
Scott Norberg13-Sep-09 12:38
Scott Norberg13-Sep-09 12:38 
GeneralOn commenting [modified] Pin
Tim Craig12-Sep-09 15:08
Tim Craig12-Sep-09 15:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.