Home‎ > ‎

Program Architecture Tips

  1. Err on the side of less abstraction
    If you haven't got a reason to make an abstraction, then you won't know which kind of abstraction to use

  2. Don't generalize until after you've done it thrice
    As with abstraction, don't create a generalized function until you've done it three different ways, or you won't know what needs to be generalized

  3. Capture complex commands in a single class / record
    If your Advanced Search or Print Job parameters are properties of a dedicated class (called SearchParameters or PrintJobParameters, for example), then you can pass that as the sole parameter to the search function, the printing function, and so-on

  4. Have complex functions return a class with a report of its work
    Rather than "void", have it return a class/record instance that summarizes its work instead, and that includes the parameters, too. Then dispatch and serialize the instance to your logging system, UI, remote monitors, etc. ValidateRequest() should return an instance of ValidationReport, for example, complete with error message, a copy of the exception, a reference to the original RequestParameters object, and as much detail as you want. This is the complement of tip #3

  5. Bubble progress reports out of deep modules with events, not Writeline() to the console
    There'll be no more re-writing when you turn your console program into a GUI, or your GUI into a web app, or your web app into a distributed application

  6. Summarize late
    When chaining together functions (making a pipeline), assume you will always need the most esoteric piece of data from the original input somewhere at the very end of the pipeline. Don't truncate, summarize or serialize complex data to a string until you absolutely have to. Moving pointers and references is free

  7. Assume you don't have to maintain your own cache
    Something else probably does anyway, like the database or the operating system. Add a cache only after you've found out that you need one, and try to add it someplace outside of your program. Eg: make your interfaces between components REST-style, and then use an off-the-shelf caching proxy like Squid

  8. Binary between modules, text between systems, text when saving to the disk
    Let the parts of a tightly-focused subsystem communicate between themselves with the protocol of greatest efficiency, but make everything else exchange data in the form of greatest transparency

  9. Chose an inferior native function over a superior third-party equivalent if it will suffice
    If it isn't the defining function of the program, or isn't related to security, then the maintenance benefits outweigh any advantage of a third-party component (ie: don't use third-party "Super DataGrids" and other crap)

  10. Chose the simplest third-party system over the most popular
    Log4Net is popular, but NLog is simpler. Unless it doesn't support a real requirement (and not an "I wanna pony" requirement) always chose the component that will be easier to learn and maintain

  11. Don't straddle systems
    You have to write a credit card processing module, but you think it'd save effort if you wrote it to work with both of the Order Fulfillment systems the company uses. It won't: your abstraction layer will be harder to debug than you thought. Instead, look for efficiencies further upstream in code that's naturally system-agnostic, like the interface to the credit card network

  12. Prefer to use the richest, most independent data type
    Data types are meant to preserve meaning when the data travels around its world. This means you shouldn't store timestamps as integers or strings if you have a DateTime type available, for example. This data should also be as independent of context as possible, so store those timestamps in UTC rather than a local timezone. Give your display code the ability to convert and format these values to "user friendly" representations where necessary

Observations About Program Architectures

  1. The easiest code to develop and maintain had no more and no less than one layer of abstraction above what the language already provided
    Unless the abstraction was baked into the language or the framework, it became a liability. And a program that didn't have at least one abstraction of its business purpose was too hard to see the programmer's intent behind the symbols

  2. The best frameworks and libraries were the ones with lots of types and type converters, but very few "Managers"
    The classes that abstracted data and provided methods to convert them into other forms were used productively everywhere, but the "xxxxManager" classes were black boxes that had to be prepped with mysterious incantations and prodded to perform something magical. Programmers avoided them because they had behaviors of their own, clumsy failure modes, and took too long to customize the behavior they wanted