Over the years of programming practice I have made a number of observations on how to best write code in order to prevent bugs from appearing, multiplying and finally taking over your project. So here is my list. I strongly recommend these best practices to everyone concerned with the quality of code and the costs of development and further maintenance.

1. Think before coding

When you get to implement something, often you immediately think of code. You already see loops, data structures, algorithms, method calls etc. You want to get to code it immediately working out the details on the way. Hold it. Just stop and think about it for a minute. What goal do you need to achieve? How are you going to structure you code? How will it fit into the rest of the application? What parameters will it accept? What methods will it call? What dependencies is it going to have? Which cases will it handle and which situations will it not? How will it perform in different scenarios? Do you see the big picture now? Very well, now you can code it.

2. Fix bugs immediately

Whenever you discover a bug in your code fix it right now. Wherever you see a potential for a bug close it immediately. Don't put them into a long to-do list which you expect to work on the next day. Tomorrow you will find another issue and the list will grow. Soon you forget the context in which the bug appeared and you will miss something when you will be fixing it. In the meantime someone else could have discovered the bug, reworked the code or implemented a workaround to it, so that your fix will now break their code. If you will be leaving the issues open then after a while the list will get that long that it will scare and demotivate you. Nobody likes it to work on software known to have dozens of bugs. You will get the feeling of a messy place and you (unintentionally) will be leaving more mess around because it will feel like a norm.

3. Test individual functional elements

When you are done implementing some little code portion that has a recognizable function and is usable on its own, test it. Don't wait for all pieces of the puzzle to come in to test them all in one sweep. Think of your code as a reusable component. Try to abstract away for a moment from the current context and test this thing in other scenarios with other input data that this code is supposed to handle. If you skip this parts testing step, you may experience later numerous errors from one or another element when other operation scenarios get enacted. If you do pay attention to your building blocks, then you will get a stable foundation to build upon.

4. Test complete puzzle

When you have completed a large functional part that relies on many components to work, test it. Don't believe for an instant that because you have tested all the smaller parts and seen them work, the whole thing will work flawlessly. As unlikely as it may seem, there is still a bunch of small elements you have forgotten that will break your code. Not set default values, temporary development shortcuts and testing values you forgot to remove, some settings not prepared etc. The one true way to make sure the thing is working is to test it and clean up what has been forgotten, wrongfully assumed or not taken into consideration.

5. Write robust code components

Develop your code components to be robust and transactional. Prevent your code as much as you can from working half way, breaking and leaving the system in an inconsistent or corrupted state. Apply various checks to input parameters and required resource to make sure they are ready for the play. Use locks when necessary to make sure somebody won’t pull the carpet right from under you. As much as it is possible in the scope of the code without stretching out of its responsibilities, make sure everything is ready for the code to complete its work. If it is, then let it run, otherwise just fail. It is far easier to analyze exceptions raised by your own checking code than to debug through layers of API method calls and indirections to get to the cause of the problem.

6. Fail as early as possible

Many applications are designed as quite complex systems with many layers of abstractions, data processing and communication. With this organization it is easy to lose perspective on what component at which hierarchy level should be prepared to notice and handle an exceptional situation. This especially applies to working with frameworks. For many cases, a good rule of a thumb is to expect an exceptional situation as early as possible. Don't let an error get through your perimeter, enact costly operations in multiple layers and almost reach the core where the last line of defense will stop it, following of course raising an exception that will propagate back through all layers of indirection finally reaching the outer perimeter where it should never have made it though in the first place. This not only complicates problem analysis and debugging, it consumes time and wastes valuable system resources. Disk operations and database access are the first examples here. Design your system to expect and be ready for an exceptional situation in the upper layers where it would not cost much yet. Your core should also be ready for invalid data sent or an invalid operation requested, but that should really serve as the last line of defense. A good example here would be the database integrity checks. They will not let wrong data through and will not allow wrong operations to get executed, but the cost of the checks done at this level would be very high.

7. Avoid quick hacks

When you have some specific situation that you code is not ready for, it may often be tempting to quickly implement a workaround, a patch or simply a hack to solve the issue right now for only this particular case. Set a specific value to a variable, change some data format in-place, stop call to some function etc. Usual reasons for doing this include shortage of time, desire to roll out a feature as soon as possible or simply lack of understanding that this approach will hit hard in the long run. This lazy tactic has ruined countless projects. Now you do one hack, tomorrow another one, the day after tomorrow one more. The next week you add a new feature that breaks those hacks and you need to implement hacks for those hacks. Then some other part of code breaks because it was relying on the original implementation of a hack which is now working in cooperation with another hack, so you update your code after which some other hack breaks because it was not ready for changes in this particular code piece. It won't be long until you application turns into a cards house. You touch one and the whole collapses. This results in unmaintainable code where it becomes cheaper to rewrite it from scratch than to try to accommodate a new feature within it. This gets especially noticed when you've been promising new features to customers only to cancel them shortly after because it is just not possible to implement it without jeopardizing the whole software. This phenomenon is known as technical bankruptcy inevitably following the critical mass of accumulated technical debts, in other words, those small and seemingly insignificant hacks.

8. Use defensive programming

Many errors and potentially dangerous situations in code are produced because something has been neglected, forgotten, wrongfully assumed or just misunderstood. It is normal. Software is written by people and to err is very human. It is all right and has to be accepted as fact. Now what can we do to protect ourselves from ourselves? Use defensive programming techniques. If some property is not supposed to be changed, make it read-only. If once initialized object should not be modified, make it immutable. If an object has to derive from some base class, use type restrictions (the "where" clause in C#). When a class or a method depends on some other element, force it to be passed as a parameter so that it won't be forgotten. These little safeguards will help compiler pick up a huge amount of errors caused by a typo or for a moment lost attention focus. Leverage the power of compiler to help you find automatically at least code-level errors, leaving you time to deal with challenges of run-time and conceptual errors you are yet going to have.

9. Prefer strong typing over dynamic binding

Languages that support dynamic typing allow for a simple solution in many situations where otherwise the code would have been quite complex. As great as this option can often be, there is a danger hidden. One can get used to dynamic typing and use it abusively everywhere because it's just simpler. It is a never-ending source of hard-to-trace errors. You assign an initial value that was treated as one type, but you expect the other type later so type casting won't work. Or some code has changed the value along the way so that it turns into a different type in certain cases. Again, the type casting either won't work or it will produce a wrong value because cast from a wrong type. It is advisable in most cases to use strong typing, even at cost of extra work, only resorting to dynamic typing where it would bring significant advantages in terms of flexibility and code simplicity otherwise unattainable with strong typing. Strong typing allows for static code analysis by compiler that will raise many errors at a very early stage not letting faulty code to run. Think of it as of one more technique of defensive programming.

10. Write self-explanatory code

To the discussion about how much commenting to put into code, I can offer one simple answer. Write self-explanatory code. Name your variables, methods, classes and libraries in a clear and self-explanatory fashion. Pay attention that the names are not ambiguous in the context where they are used. Use unique names. Don't reuse names when they go out of their usage context. Avoid very simple names that have no meaning on their own. Do this and you won't need comments except for the cases when you'll feel compelled to explain some complex thinking process that resulted in this particular code. Comments can be very useful but they have two significant disadvantages. First of all, comments are like documentation. It is often write-only. Documentation is written but nobody likes to read it afterwards. It's easier with comments because they are shorter but still, developers only read them as the last resort. Besides that, comments are desynchronized from code. Once you introduce changes in code, you need to update comments. If you forget it, not only will comments become useless, but they will get misleading. Therefore, the best option is to write self-explanatory code that doesn't need comments at all. And don't worry about the length of self-explanatory names. It's easier for you to write and read such code. It is indifferent to the compiler how long the names are. It will compile the code to the same executable format.

11. Avoid sophisticated code

Programmers often like to show off their skills and knowledge of the frameworks they use by writing code that uses some not obvious language options or framework features put together in a very unique and unexpected fashion. Often it is used to put as much logic as possible in the shortest code passage conceivable. Exactly these code gems prove to be the source of bugs because nobody can understand them except for the programmer who originally wrote them. Not rare that the programmer themselves will not comprehend them after a few months least the years have elapsed. This code style is better to avoid in practice, especially when working in teams. Software projects are not programming contests. They have to be developed further and they have to be maintained. It happens regularly that there are blocks of code that the current team avoids to touch afraid of breaking them, because the only person who understood them is long since gone having left no documentation or even comments. It is a no-go for long lasting projects with a team larger than one person.

12. Avoid dense code blocks

In order to keep code clean and readable it is important to follow certain best practices of code style. These include before all appropriate formatting and usage of white space. White space is what is often sacrificed by programmers in order to minimize the number of code lines. Multiple expressions on a single line, saving on open and close brackets to spare two extra lines for a code block, passing long method calls directly as function arguments, this practice is what makes code very dense and very unreadable. It takes more time to analyze and understand before proceeding on to work with it. I often had to reformat the code, unwind it to many more lines and put all those omitted brackets in place to get a clear overview of the code. It only wastes time. It serves no purpose to spare on white space. More space occupied by code won't mean anything to the compiler. But it will to a human who will read this code. Think of other people who will work with your code and maintain it. Write clean, understandable and easily readable code from the beginning. You may lose a few more minutes doing this, but it will save hours later and will benefit the code base in the long run.

13. Avoid code duplication

One very popular coding technique has long been copying code portions and adjusting them to perform a similar but slightly different function. The obvious benefit of rapid development is quickly shadowed by exponentially decreasing maintainability of the code base. Once the shared code gets changed somewhere the update has to be passed on to all copied code instances. Since the copied code portions are not structurally connected but just represent text blocks, this update will need to be done manually which is very slow and error-prone. One could try using the search and replace feature present in most development environments, but in many cases the code to be updated would contain commonly used names, so the operation will not stop after replacing the needed code sections but will proceed to damage many other project files. The best way to avoid this hassle is to refrain from copying code. Extract the commonly used portions into a property, method or a class that can be used by as many clients as needed. Not only will it be easier to maintain, but it will also expose all code dependencies on this shared element.

14. Avoid magic constants

Do not put string constants and other fixed values directly in your source code. Your code defines how your application functions and how it processes the information. String constants and other inline values represent either this information or configuration settings. Neither belongs directly to the source code. Putting these elements all over your source code has two significant disadvantages. The constants directly in code have no meaning as to why they have these particular values and where they came from. It is difficult to maintain the code that has such magic elements with their meaning pretty much unknown to everyone else except for the original developer. The other issue is the one with code duplication. If you put the same value in several places in code, it will become more difficult to update them all at once, especially if the value coincides with that of other constants that have to stay unchanged. The solution is to remove these constants from the code. Put them in resources (.NET thing), in the database or in some external settings file and reference them from code. If it is something that is supposed to be changed very rarely and mainly by developers only, it is okay to declare these constants in code but just once. Give these elements sensible names, maybe provide a description what this is and why it has this particular value for now, then reference these elements in your code. This way you can easily and safely update these constants and always see what other code components depend on these elements.

15. Strive for loose coupling

While designing your application, think clear of dependency pathways and build it with accordance to this structure. Understand which modules have what responsibilities, what data they need, what operation they have to invoke and what higher-level interface they provide to their clients. Code it appropriately and do not take shortcuts. Imagine you have a page that displays some data from the database. The page has to ask the business layer what data to display. The business layer will decide which data can be displayed and ask the data layer for those data. In no case should the page grab directly to the database to pull out something small because it feels easier than update the business layer and the data layer appropriately. Let your modules have the minimum of dependencies, only those that are absolutely needed. This way, the changes introduced in one module can be contained there and probably encapsulated in neighboring dependent modules until they hit all those countless code sections that decided to circumvent the command structure for convenience purpose. Here at the cost of some extra work for keeping the clear dependency structure, we win much more on further development and maintenance costs.

16. Avoid hidden dependencies

Develop your modules to be clear in how they operate and transparent in what they need to function properly. Do not design by implication, but design by exposure. If your code component depends on some value, require it as a parameter. If it depends on some other element or interface to be initialized prior to working with your object, force a valid instance of them to be passed along when the object is constructed or its methods are invoked. One quite widely adopted programming technique is using name-value collections that are shared within application. Speaking of ASP.NET the first candidate would be the Session collection. Don't rely in your code on a particular key in such a collection to be set "by agreement". It is not possible to know it in advance or guess. Don't keep it silent by expecting the caller know what you need and have it stand by. They won't. People using your code won't be excited about having to read your documentation or browse through your source code to find out what you need. Let them know explicitly. This will eliminate asking around why this thing wouldn't work and will speed up development now when programmers do not have to solve puzzles you prepared for them.

17. Strive for flat responsibility distribution

Keep your code structure evenly weighted and balanced. Let the code elements be transparent in what functions they perform and what responsibilities they have. Avoid creation of centers of weight. In other words, prevent nodes (code elements such as methods, classes or libraries) from growing far beyond their functional scope and taking over responsibilities of adjacent elements and gradually turning into a giant code mass. Methods that are thousands of lines long, classes that contain hundreds of methods, source files that contain dozens of related classes, they all emit signals of bad-practices commonly known as anti-patterns. Refactor such elements. Extract common or repeated functionality into single components. Look if some code is working below or above its supposed level of abstraction and then move the according code portions into appropriate layers. Eliminate occasionally introduced but not absolutely needed dependencies. Split massive classes into a family or hierarchy of smaller ones. Try to follow the rule of one class per code file. Aim at that code structure where it consists of a possibly small number of lightweight transparent components that can be put to interoperate in various combinations to achieve the needed functionality. A code base with a smaller number of components is easier to maintain. Smaller lighter components are easier to work on. Reduced number of dependencies will limit the amount of extra work needed to adjust the related components and will keep side effects to a minimum.

18. Keep related code close together

Regardless of what code style you follow keep the related code together. It is easier to keep an eye on a group of neighboring elements just as it is more likely to lose an overview of elements standing far apart. This is due to the human thinking abilities and we must code to embrace them. Declare variables just before you intend to use them. Where possible, initialize variables and properties right there where they are declared. Avoid old-school practices advising to declare all needed elements in the beginning before you use them. Naturally, you won't know what exactly you are going to need and when you do you will constantly be losing focus when scrolling to the top to look at the declaration of an element of your current attention. Avoid all big initialization methods where you prepare your complex class to be ready for all possible usage scenarios. Initialize it on-demand. Perform this initialization right there in the respective methods so that other developers would clearly see the necessary steps in one place rather than guessing and browsing through your code structure to find hidden hints. The code written in this manner will be much easier to maintain. You will less likely forget to turn things that are needed. The code base will suffer less from orphan elements no longer in use that were forgotten to be removed there where they were introduced in some place far away from their usage environment.

19. Build a house, not an empire

Think straight when designing your application. Focus on what you really need and code it. Don't think of all possible scenarios you may possibly need tomorrow. Don't design for all imaginable customer wishes that have not been expressed yet. It is easy to strand into the forest of imaginary requirements and spend your time trying to find your way out implementing them rather then simply following your current route that is clearly defined. Do what is necessary to complete the project but do not play architecture astronaut. Be pragmatic. Embrace the K.I.S.S. principle (Keep it simple, stupid) and the YAGNI principle (You aren't gonna need it). Negligence to obey these best practices that reflect the vast practical experience often leads to a huge time loss while trying to create an empire when what's only needed is to build a house. This endangers a project far beyond its intended deadline and turns it into a never ending adventure.

20. Redesign when needed

While developing your project, observe how it's going on at all times. Keep your fingers on the pulse of the project architecture, code structure, functional division and overall quality. Perform regular code reviews. When you notice something nasty is starting to develop, stop coding. Look at what issue you have and find a way to fix it. Make necessary changes to deal with this issue and prepare for the others of the same kind should they arrive. Don't be afraid to redesign the code that you believe has been stable and perfect for a long time. Remember, the project quality does not grow with time on its own. It will only decay unless you maintain it properly and perform reconstructions when needed. It is of course much better if you not only keep it on the line but apply efforts to improve it. The more time is lost not fixing broken stuff, the more difficult it will become in the future to perform all those pending repairs in one sweep. If you have a project of significant complexity, you may arrive at a point where you can't go forward any more without repairing the engine, but it is now so expensive to repair it that it is just cheaper to get a new one.

21. Do not sacrifice quality

Regardless of what kind of project you are working on and what your time restrictions are, never ever sacrifice quality to the momentary benefits of saving time and efforts. Build your software to have a decent architecture and quality code base. Design it to embrace best practices, be flexible to changes, resilient to challenges and always ready for future. Before everything, avoid quick hacks and inconsistencies. Hacks will accumulate with time and will finally take over your project. When that happens you won’t be able to introduce changes without the rest of the code falling apart. Agree on your code style and programming practices within your team and force all team members to strictly follow them. When the code is clear, familiar and understandable, opportunities for bugs will present themselves less frequently. Always remember, the quality of your project is very easy to lose but very difficult to restore. On the other hand it is not that difficult at all to keep it stable from the very beginning of the development. Simply speaking, the quality of a project can either be its own capital or its own baggage. Be smart and choose the first one.

The ideas presented here summarize the best practices I have come up with in my programming experience. The list does not pretend to be complete or present an absolute truth. It is an opinion and is therefore subjective. Still, I hope my tips and tricks will prove useful to my readers and will be supported by them.