Tuesday, March 15, 2005

How To Write Unmaintainable Code

Miscellaneous Techniques

If you give someone a program, you will frustrate them for a day; if you teach them how to program, you will frustrate them for a lifetime.
- Anonymous

1. Don't Recompile
: Let's start off with probably the most fiendish technique ever devised: Compile the code to an executable. If it works, then just make one or two small little changes in the source code...in each module. But don't bother recompiling these. You can do that later when you have more time, and when there's time for debugging. When the hapless maintenance programmer years later makes a change and the code no longer works, she will erroneously assume it must be something she recently changed. You will send her off on a wild goose chase that will keep her busy for weeks.

2. Foiling Debuggers
: A very simple way to confound people trying to understand your code by tracing it with a line debugger, is to make the lines long. In particular, put the then clause on the same line as the if. They can't place breakpoints. They can't tell which branch of an if was taken.

3. S.I. vs American Measure
: In engineering work there are two ways to code. One is to convert all inputs to S.I. (metric) units of measure, then do your calculations then convert back to various civil units of measure for output. The other is to maintain the various mixed measure systems throughout. Always choose the second. It's the American way!

: Constant And Never-ending Improvement. Make "improvements" to your code often, and force users to upgrade often - after all, no one wants to be running an outdated version. Just because they think they're happy with the program as it is, just think how much happier they will be after you've "fixed" it! Don't tell anyone what the differences between versions are unless you are forced to - after all, why tell someone about bugs in the old version they might never have noticed otherwise?

5. About Box
: The About Box should contain only the name of the program, the names of the coders and a copyright notice written in legalese. Ideally it should link to several megs of code that produce an entertaining animated display. However, it should never contain a description of what the program is for, its minor version number, or the date of the most recent code revision, or the website where to get the updates, or the author's email address. This way all the users will soon all be running on different versions, and will attempt to install version N+2 before installing version N+1.

6. Ch ch ch Changes
: The more changes you can make between versions the better, you don't want users to become bored with the same old API or user interface year after year. Finally, if you can make this change without the users noticing, this is better still - it will keep them on their toes, and keep them from becoming complacent.

7. Put C Prototypes In Individual Files
: instead of common headers. This has the dual advantage of requiring a change in parameter data type to be maintained in every file, and avoids any chance that the compiler or linker will detect type mismatches. This will be especially helpful when porting from 32 -> 64 bit platforms.

8. No Skill Required
: You don't need great skill to write unmaintainable code. Just leap in and start coding. Keep in mind that management still measures productivity in lines of code even if you have to delete most of it later.

9. Carry Only One Hammer
: Stick with what you know and travel light; if you only carry a hammer then all problems are nails.

10. Standards Schmandards
: Whenever possible ignore the coding standards currently in use by thousands of developers in your project's target language and environment. For example insist on STL style coding standards when writing an MFC based application.

11. Reverse the Usual True False Convention
: Reverse the usual definitions of true and false. Sounds very obvious but it works great. You can hide:
#define TRUE 0
#define FALSE 1 somewhere deep in the code so that it is dredged up from the bowels of the program from some file that no one ever looks at anymore. Then force the program to do comparisons like:
if ( var == TRUE )
if ( var != FALSE ) someone is bound to "correct" the apparent redundancy, and use var elsewhere in the usual way:
if ( var ) Another technique is to make TRUE and FALSE have the same value, though most would consider that out and out cheating. Using values 1 and 2 or -1 and 0 is a more subtle way to trip people up and still look respectable. You can use this same technique in Java by defining a static constant called TRUE. Programmers might be more suspicious you are up to no good since there is a built-in literal true in Java.

12. Third Party Libraries
: Include powerful third party libraries in your project and then don't use them. With practice you can remain completely ignorant of good tools and add the unused tools to your resumé in your "Other Tools" section.

13. Avoid Libraries
: Feign ignorance of libraries that are directly included with your development tool. If coding in Visual C++ ignore the presence of MFC or the STL and code all character strings and arrays by hand; this helps keep your pointer skills sharp and it automatically foils any attempts to extend the code.

14. Create a Build Order
: Make it so elaborate that no maintainer could ever get any of his or her fixes to compile. Keep secret SmartJ which renders make scripts almost obsolete. Similarly, keep secret that the javac compiler is also available as a class. On pain of death, never reveal how easy it is to write and maintain a speedy little custom java program to find the files and do the make that directly invokes the sun.tools.javac.Main compile class.

15. More Fun With Make
: Have the makefile-generated-batch-file copy source files from multiple directories with undocumented overrwrite rules. This permits code branching without the need for any fancy source code control system, and stops your successors ever finding out which version of DoUsefulWork() is the one they should edit.

16. Collect Coding Standards
: Find all the tips you can on writing maintainable code such as the Square Box Suggestions and flagrantly violate them.

17. IDE, Not Me!
: Put all the code in the makefile: your successors will be really impressed how you managed to write a makefile which generates a batch file that generates some header files and then builds the app, such that they can never tell what effects a change will have, or be able to migrate to a modern IDE. For maximum effect use an obsolete make tool, such as an early brain dead version of NMAKE without the notion of dependencies.

18. Bypassing Company Coding Standards
: Some companies have a strict policy of no numeric literals; you must use named constants. It is fairly easy to foil the intent of this policy. For example, one clever C++ programmer wrote:
#define K_ONE 1
#define K_TWO 2
#define K_THOUSAND 999

19. Compiler Warnings
: Be sure to leave in some compiler warnings. Use the handy "-" prefix in make to suppress the failure of the make due to any and all compiler errors. This way, if a maintenance programmer carelessly inserts an error into your source code, the make tool will nonetheless try to rebuild the entire package; it might even succeed! And any programmer who compiles your code by hand will think that they have broken some existing code or header when all that has really happened is that they have stumbled across your harmless warnings. They will again be grateful to you for the enjoyment of the process that they will have to follow to find out that the error was there all along. Extra bonus points: make sure that your program cannot possibly compile with any of the compiler error checking diagnostics enabled. Sure, the compiler may be able to do subscripts bounds checking, but real programmers don't use this feature, and neither should you. Why let the compiler check for errors when you can use your own lucrative and rewarding time to find these subtle bugs?

20. Combine Bug Fixes With Upgrades
: Never put out a "bug fix only" release. Be sure to combine bug fixes with database format changes, complex user interface changes, and complete rewrites of the administration interfaces. That way, it will be so hard to upgrade that people will get used to the bugs and start calling them features. And the people that really want these "features" to work differently will have an incentive to upgrade to the new versions. This will save you maintenance work in the long run, and get you more revenue from your customers.

21. Change File Formats With Each Release Of Your Product
: Yeah, your customers will demand upwards compatibility, so go ahead and do that. But make sure that there is no backwards compatibility. That will prevent customers from backing out the newer release, and coupled with a sensible bug fix policy (see above), will guarantee that once on a newer release, they will stay there. Extra bonus points: Figure out how to get the old version to not even recognise files created by the newer versions. That way, they not only can't read them, they will deny that they are even created by the same application! Hint: PC word processors provide a useful example of this sophisticated behaviour.

22. Compensate For Bugs
: Don't worry about finding the root cause of bugs in the code. Simply put in compensating code in the higher-level routines. This is a great intellectual exercise, akin to 3D chess, and will keep future code maintainers entertained for hours as they try to figure out whether the problem is in the low-level routines that generate the data or in the high-level routines that change various cases all around. This technique is great for compilers, which are inherently multi-pass programs. You can completely avoid fixing problems in the early passes by simply making the later passes more complicated. With luck, you will never have to speak to the little snot who supposedly maintains the front-end of the compiler. Extra bonus points: make sure the back-end breaks if the front-end ever generates the correct data.

23. Use Spin Locks
: Avoid actual synchronization primitives in favor of a variety of spin locks -- repeatedly sleep then test a (non-volatile) global variable until it meets your criterion. Spin locks are much easier to use and more "general" and "flexible " than the system objects.

24. Sprinkle sync code liberally
: Sprinkle some system synchronization primitives in places where they are not needed. I came across one critical section in a section of code where there was no possibility of a second thread. I challenged the original developer and he indicated that it helped document that the code was, well, "critical!"

25. Graceful Degradation
: If your system includes an NT device driver, require the application to malloc I/O buffers and lock them in memory for the duration of any transactions, and free/unlock them after. This will result in an application that crashes NT if prematurely terminated with that buffer locked. But nobody at the client site likely will be able to change the device driver, so they won't have a choice.

26. Custom Script Language
: Incorporate a scripting command language into your client/server apps that is byte compiled at runtime.

27. Compiler Dependent Code
: If you discover a bug in your compiler or interpreter, be sure to make that behaviour essential for your code to work properly. After all you don't use another compiler, and neither should anyone else!

28. A Real Life Example
: Here's a real life example written by a master. Let's look at all the different techniques he packed into this single C function.
void* Realocate(void*buf, int os, int ns)
temp = malloc(os);
memcpy((void*)temp, (void*)buf, os);
buf = malloc(ns);
memset(buf, 0, ns);
memcpy((void*)buf, (void*)temp, ns);
return buf; }

* Reinvent simple functions which are part of the standard libraries.
* The word Realocate is not spelled correctly. Never underestimate the power of creative spelling.
* Presume malloc will always return successfully.
* Make a temporary copy of input buffer for no real reason.
* Cast things for no reason. memcpy() takes (void*), so cast our pointers even though they're already (void*). Bonus for the fact that you could pass anything anyway.
* Never bothered to free temp. This will cause a slow memory leak, that may not show up until the program has been running for days.
* Copy more than necessary from the buffer just in case. This will only cause a core dump on Unix, not Windows.
* It should be obvious that os and ns stand for "old size" and "new size".
* After allocating buf, memset it to 0. Don't use calloc() because somebody might rewrite the ANSI spec so that calloc() fills the buffer with something other than 0. (Never mind the fact that we're about to copy exactly the same amount of data into buf.)

29. How To Fix Unused Variable Errors
: If your compiler issues "unused local variable" warnings, don't get rid of the variable. Instead, just find a clever way to use it. My favorite is...
i = i;

30. It's The Size That Counts
: It almost goes without saying that the larger a function is, the better it is. And the more jumps and GOTOs the better. That way, any change must be analysed through many scenarios. It snarls the maintenance programmer in the spaghettiness of it all. And if the function is truly gargantuan, it becomes the Godzilla of the maintenance programmers, stomping them mercilessly to the ground before they have an idea of what's happened.

31. A Picture is a 1000 Words; A Function is 1000 Lines
: Make the body of every method as long as possible - hopefully you never write any methods or functions with fewer than a thousand lines of code, deeply nested, of course.

32. One Missing File
: Make sure that one or more critical files is missing. This is best done with includes of includes. For example, in your main module, you have
#include "stdcode.h" Stdcode.h is available. But in stdcode.h, there's a reference to
#include "a:\\refcode.h" and refcode.h is no where to be found.

33. Write Everywhere, Read Nowhere
: At least one variable should be set everywhere and used almost nowhere. Unfortunately, modern compilers usually stop you from doing the reverse, read everywhere, write nowhere, but you can still do it in C or C++.