-
No circular dependencies between components (keep your modules orthogonal, and employ a top down dependency scheme so that changes in a layer only affect the layer directly below it if need be).
-
Refactor as often as you can (and as often as time/budget/etc permit) to keep code duplication to a minimum. We’ve all had to maintain projects that use the same code block repeatedly throughout the application, and we all know what a pain in the ass it is to have to fix something in multiple places. Move repeatedly used code into a base class and set up an inheritance scheme. When you’re done refactoring, do it some more.
-
Move frequently used “utility” methods (i.e. string manipulation, process launching, etc) into a static utility class. Most of all, no nested classes…one class per class file, and name the class the same as the class file.
-
Keep your inheritance chains as shallow as the design spec will allow…I’ve worked on projects with chains much deeper than they need to be (simply because they could I guess), and it’s a maintanence nightmare, not to mention bug-prone.
-
No magic strings/numbers. In versions 1.x of Visual Studio I configured the IDE to highlight strings red so I could quickly scan through my code and get rid of as many strings as possible (meaning either moving them to a config file or a resource file). VS 2.0 does this by default (well, more of a maroon color). Granted there are cases where it’s unavoidable to have strings in your code, but keep them to a minimum.
-
Keep your classes under 1000 lines of code. Generally speaking if they are longer than that, a new class is more than likely warranted as the class is probably attempting to do more than it should.
-
Of course this goes without saying, but I’ll say it anyways. Keep data access stuff out of all layers of your application except for the DAL. On top of that, make judicious use of stored procedures…inline/dynamic SQL is horrific performance wise as the SQL Server query optimizer won’t be used. Not to mention that inline SQL is another maintenance nightmare.
-
Mark your business objects as serializable so they can easily be passed between application contexts/domains/etc. If you are positive something won’t need to be serialized, mark the class as serializable anyway and use the NotSerialized attribute on members that need it.
-
Judicious use of overloaded constructors (and constructor chaining). The .Net framework does this quite a bit, so should you. The same thing applies to methods in your business/data access layers. Flexibility is key, and it will lead to better code reuse down the line.
-
Use design patterns sparingly, i.e. don’t implement the pattern du jour “just because”…patterns are great, but if overused can create maintanence issues down the line. That being said, use factory patterns wherever possible (see orthogonality above). If you’re not familiar with this pattern, it’s time to get acquainted. Singleton objects are also good candidates for large amounts of static data that needs to remain in memory and is used by the entire application. I probably use these 2 patterns more than the other “popular” ones combined.
-
Keep reflection to a minimum. Reflection is extremely powerful in what it can do (application frameworks justifiably should use reflection), but the overhead involved is on the order of hundreds of times more expensive than using local object instantiation. Avoid Activator.CreateInstance at all costs.
-
Develop a variable/member naming scheme as early as possible in the design of the application, and most of all, stick to it. It’s worthless if not adhered to (create rules in FxCop if need be to have it yell at folks who break the naming convention). I usually try and mimic the .Net framework on this one; methods as verbs, properties as adjectives, attributes as adverbs, classes as nouns. Some purists will argue with this, doesn’t bother me though.
-
Don’t swallow exceptions (i.e. no empty catch blocks)…an exception means something exceptional is happening, so it needs to be dealt with. Also, no control of flow using try/catch blocks, and always perform cleanup duty in the finally block. If an object implements IDisposable you can wrap it up in a using statement.
-
If you have to jump through hoops to get a member to do what its supposed to do, then something is wrong. If it looks hackey, it is hackey and needs to be rewritten. If I’m getting frustrated with a code block, I either take a break or work on something else for a bit…it’s amazing how the mind works stuff out when you’re focused on something else. Give it time, it will come to you.
-
Try to bear in mind that code is 10x harder to read than it is to write. Somewhere down the line (and it’s usually someone else), your code will need maintanence so judicious use of comments/documentation is most definitely needed. I see this one broken more often than not (and am most definitely guilty of this myself). Keep routines concise and simple, and most of all the routine should do what it says its going to do, no more and no less.
I’m not saying I’m the world’s greatest coder (far from it actually…I studied chemistry in college believe it or not), but the above points have definitely made my life (and hopefully those who touch my code) as a coder much easier, and it’s by no means an exhaustive list.