Megaworkarounds (long)

Computing technology is still in its infancy with respect to durability, reusability and correctness of code, as evidenced by the fact that developers have to deal with system issues constantly in application code. We are always writing almost the same code over and over because the new code is running on a different language or platform, takes advantage of new system features, or needs different dependencies. We don’t have a space to construct abstracted data and operations where we are free from thinking about the machine boundaries, memory management and other concerns that detract from the purity of the logic. Therefore most of what we write is within the space bound by those limits.

Engineering today is therefore an art of tying together the abstract perfection of pure logic with the finesse of manipulating actual machines. That art – working around limitations – is essentially what’s difficult about the art. It’s hard mainly because the technology is immature.

To be highly effective, we have to stay focused on what’s coming and meanwhile work with what we have. That’s the concept of “megaworkarounds” – the collection of work patterns and techniques used to effectively work around the limitations of the state of technology. We need to be aware when we are doing a megaworkaround and why, and that’s what this article covers. It’s not about “best practices” (as if that characterizes some kind of end state) but it’s rather about ways to do things solidly despite being amidst chaos.

The layering megaworkaround

Layering is a side effect of dependencies, at any level of analysis. Considering deployable components, a server app may require Windows 7 with various installed components, each of which have further dependency chains. The app may have an add-on that depends on the app. The dependency graph is best visualized with all arrows pointing down; a lower level component cannot depend on a higher level one. Each vertical point on that graph is a layer. Within the app there may be optional parts that depend on other parts. At the compiler and linker levels of analysis, there are dependencies between libraries. At the class level, dependencies and encapsulation are the primary paradigm of object oriented programming. All of these levels of analysis exhibit the same thing: a dependency graph that can be visualized as layers with mainly down-pointing arrows.

Any two elements that both depend on each other map to the same layer. So in the visual layers, arrows point down if they are a regular dependency, and they point across if they are a cross-dependency. In the sample below, the pink ovals are elements (such as classes), black arrows are dependencies, and blue arrows are cross-dependencies.

dependencies1

With a large code base even exceptional developers can fall into a cross-dependency trap, in which too many elements inadvertently depend on each other. The result is fragile code that requires too many external things to be in place, or can’t be changed because too much depends on everything. In the extreme, the dependency chains can all map to a single layer. It’s a trap because once intertwined it becomes hard to extract them back into a state of low dependency.

A main reason we get stuck there is because the tools don’t properly support layering. For example in C#, the main layering technique is by making many assemblies: classes internal to an assembly can be friend classes to the others (meaning, in the same layer), allowing for cross-dependencies, but assemblies must be hierarchically mapped to each other with no cross-dependencies. So C# only directly supports the pattern at that one level, which is not sufficient. The question of deploying classes into exported assemblies should be completely separate from logical layering.

We need to have a conscious layering pattern that we apply, as a dependency management technique. A layering syntax in code would expand the private, public, and internal keywords to allow any number of arbitrarily nested layers with finely managed visibility of each member. For this paper, I’m leaving aside what the syntax might be to focus on it visually.

In the diagram below for a small part of a math library, rounded rectangles are classes (with private and public members) and rectangles are the layers – collections of one or more classes that are implicitly cross dependent. There are four layers shown – math, functions, values, and internal constructs – note the nesting. Arrows specify layer dependencies, and the thick lines specify visibility.

layers1

In this example, the class Expression with all its public members is visible in the values layer, and the values layer is visible in the math layer, so a member like Expression.Length is exposed by the math layer. The class Interval is exposed by the internal constructs layer, but only to classes in the functions layer. There is no thick blue line connecting internal constructs to the top of the math layer, so it isn’t exposed. Since there is no dependency line from values to internal constructs, none of the values classes can see Interval.

The classes Expression and Series presumably have references to each other, so they are at the same layer.

The diagram below shows how the math layer is composed with other layers to produce a hypothetical application that composes graphs and outputs them to web pages.

layers2

Of course not all layers are shown, but you can imagine each layer being filled with nested other layers, maybe 10 levels deep or more.

As you build classes in a layered system you should be forced to place them in the layering model: this forces the programmer to think about dependencies and code contracts as a primary design step. The default should be each class in its own layer, because you should make a conscious decision to write friend classes, which should be more rare than hierarchically arranged classes.

Ideally the development tool will also:

  • give you room to explain the purpose of each layer, so it becomes a documentation entry point to the code base
  • organize access to classes through a visual interface as shown here
  • allow layers to be used in many ways, such as: visible to many projects, be versioned, published separately, or imported from compiled sources

Since that kind of tool isn’t common, we have to still think through these questions and map it out on paper or in an ancillary file. Having to keep up that information outside the code base is the megaworkaround. I usually do this with bullet points in a Word file, and sometimes a graphic like the ones above. Also I’ve used folders within assemblies in C# with names like a_Containers, b_Session, and so on to simulate the ordering of the layers and keep myself from accidentally making an upward dependency.

The persistence definition megaworkaround

Persistent data include database columns or XML files or any other data structure persisted (saved) somewhere. It takes diligence to maintain documented persistence definitions, because the toolsets don’t encourage it. For example, SQL does not have comments as a standard or integral feature. Tools that generate persistence code might copy comments into the code, where it then lives in two places. You then might map data from generated or generic classes to simple objects, or view model objects, where having the comment again would be redundant.

The problem is that there is no “right” place to put persistence documentation and developers may skip it altogether.

But defining data structures is a central part of programming, and that includes coming up with the explanatory comments. Here are three examples of definitions in code:

  • string c;
  • string code;
  • string! code = “D”; //the customer code from C_CODES table

The first lacks any definition or hint and would be used only in places where the scope is very small. The second is a bit better only because the variable name acts as a hint. The third is the best because it tells you the exact range and that it may not be null (the string! type). If you wanted to go further you could make a CustomerCode class and type the variable as an instance of that; in that manner you can enforce the value’s allowed range in any way you need.

The code above is useful for variables scoped by a function, and that’s where programmers are more likely to write out definitions. But when the scope is persistent, all that goes to pieces; programmers will just put whatever value fits in a field and not think to include that in the definition of the range of the field. In other words, it promotes the kind of thinking that a database field is defined by whatever the code happens to write there. If you used a class like CustomerCode, there is no standard for making sure the database value is controlled only by instances of that class. Clearly a megaworkaround is needed.

As an aside, one of the touted reasons for no-schema databases is so that developers can skip the tedious step of defining schema outside code. They reason that you should be able to define a class like Customer and just “persist it” where it automatically maps to database fields; that way you only document the members once. That strategy fails when one app defined the persistent data structures and another one (possibly in a different language) needs to read them. The comments are buried in the first app’s source code.

The ideal solution to this is to define the types at the layer at which they persist (like a relational database), and flow those definitions to anything that references those types. This would allow arbitrarily complex definitions with features such as trees of referenced objects, collections, and inheritance. To do this we would define a “wire type”, a byte stream that strictly adheres to a type, defined independently of the programming language. This solves the conundrum of whether the app or the database is more controlling. The wire type is outside of and controlling them both.

In practice, you might define the wire type Customer with members like this:

type Customer {
  public string Name; //full name of company
  public CustomerCode Code; //display name, uppercase with no whitespace
}

Then in the application code, you might say:

class Customer {
  void SuggestCode() { Code = new CustomerCode(Name); }
}

and when writing that application code, you can view the comments defined in the wire type. The wire type is thus persistable as is (because the persistence mechanism would read that kind of syntax) and usable as a type in multiple languages as is.

Alas, we usually don’t have this type of system available, so we need discipline to maintain documentation on persistence outside the application and outside the database server. I usually use a word processor document or local wiki for this purpose and require that when making database changes, the master documentation must be updated. The documentation contains for each table the purpose of the table, the indexes, and then all the fields. For each field it contains the exact definition, type, range, and use as a foreign key. When I say “exact”, I mean it should literally specify the way composed fields are built, the reasons for variations in value, and anything else that would save some future person a reverse-engineering task.

Fortunately persistence definitions should flow directly from specifications, so once the spec is written, it can become the basis for the ongoing documentation.

A related workaround skill is to meticulously avoid duplicate definitions. You might have a data model document, and then in code you have:

int OrderStatus; //0=open, 1=incomplete, 2=shipped

Then you would have a duplicate definition (don’t repeat yourself!) so instead you should have

int OrderStatus; //defined by order.order_status

 The flow documentation megaworkaround

One of the troubles in documenting classes is failing to place a class in a larger context – how you get there, and who uses it. A class whose comment is just “screen options” doesn’t tell you enough. This gets very out of control quickly when the classes define web pages and the inputs cannot be strictly defined programmatically – because a web request might provide any inputs. So if you have

class CatalogDetail : Page {
  public override void Render() {
    if (Request["ShowDetail"] == "Y") {…}
  }
}

then you have used an input that isn’t strictly defined, but the compiler will never treat this as an error. The problem pattern can be found all over but is rampant in server side web code. Ideally code would be almost self-doumenting because it would be arranged in layers (see above), each class would be very single-purpose, it would have well-chosen identifiers, and it would define all its inputs and outputs in a compiler-checkable way.

Since we are not nearly at that point, we need a megaworkaround to organize flow. For web code I use an external file that must be kept up to date, and contains the inputs, behavior and outputs for each action. Here is an example:

  • CatalogDetail
    • URL arguments
      • ItemNo – numeric item number, required
      • ShowDetail – Y indicates to show catalog detail
    • outputs
      • the catalog detail page for the given item
      • ordering buttons (see OrderHTML class)

There is often a question of whether the caller or the receiving action controls the definitions. Having the external documentation solves that, because it becomes controlling, and the code does not need to comment its use of the URL argument names.

For simpler code that doesn’t interface with outside clients the way web server code does, there’s much less need for external documentation; comments can be with the code.

The block documentation megaworkaround

Here’s an example of a block with a block comment:

//get tax rate for this customer
var taxRates = getCachedRateTable();
var rateCode = customer.tax_rate_code;
double rate = taxRates.LookupRate(rateCode);

Here’s the same code after someone added a new feature:

//get tax rate for this customer
var taxRates = getCachedRateTable();

//if rates aren't set up, assume no tax
double rate = 0;
if (taxRates.Count != 0) {
  var rateCode = customer.tax_rate_code;
  double rate = taxRates.LookupRate(rateCode);
}

By adding the new check, they introduced a documentation bug. “Get tax rate for this customer” is a comment which is introducing a block that no longer does what it says.

Ideally we would have languages and editors that presented code and comments in a structured way so when you were working in a block that has a purpose, you stay in that purpose. Writing block-comments first is an efficiency technique, and the editor should support it. It might look like this:

Purpose Code
get tax rate for this customer; if rates aren’t set up, assume no tax  

var taxRates = getCachedRateTable();
double rate = 0;
if (taxRates.Count != 0) {
  var rateCode = customer.tax_rate_code;
  double rate = taxRates.LookupRate(rateCode);
}
find order totals

This way comments stay with their code. It could also support nesting and collapsing.

However, since we don’t have this, the megaworkaround is to be strict about block-comment policies. Coders should be required to use them for nontrivial functions, and it should be considered a coding error on review if the block comments don’t match their code lines.

The underexposed class megaworkaround

An underexposed class is one who documented public members don’t give you enough information to be able to use it fully and correctly. Consider this class fragment in C#:

//functions dealing with customer retention
public class CustomerManager {
  //check if a customer is active
  //days = how many days in history to check
  public string IsActive(Customer c, int days, bool checkOrders) {…}
}

I am assuming that this definitional part of the source code will be available for the downstream programmer to inspect when they need to use the class (including the comments shown). However, that programmer may have lots of unanswerable questions like (1) When is the class meant to be instantiated and what is its lifetime? Is it just a collection of functions or does it represent something? It’s unclear what private data it might have if any. (2) What does “active” mean for a customer? For example, one who’s placed an order in the last six months? What is it checking for? (3) When should you check orders and when not? (4) What is the return value’s definition and can it be null? (5) What exceptions might it throw?

With such limited exposure as in the example, the programmer would need to open up the implementation code and reverse-engineer it to find the answers to those questions – a big time-waster.

In duck-typed and otherwise looser languages like javascript, the problem is even worse, because you cannot tell from the definitions anything about the types of the arguments or returns, and everything is public.

Java originally required exceptions to be declared (so it is illegal to throw an exception type in a method that doesn’t declare that type as throwable), which helps with part of this problem.

Ideally the tools would encourage full commenting on the public members, and allow downstream programmers to view the class’s definitional skeleton as its documentation page.

The solution is to be rigorous about thinking about how the downstream programmer will understand the class, and include in comments:

  • What the class is – what it represents. (not just what it is about or what it does)
  • What is the expected lifetime of the instance, what layer creates it, and if there is supposed to be only one instance.
  • General usage notes, including any required sequence of calls or how to clean it up.
  • What exceptions the methods throw.
  • What are the meanings and range of all the methods, arguments and return values?

Here’s an example rewrite of the underexposed code from above:

//Temporary cache of customers with functions to query various aspects of the customer status.
//This is meant to be used in business analytics contexts to load a target set of customers and then
//produce aggregate and customer data for the loaded set, which may be graphed.
//Caller should instantiate it for the duration of the filter criteria. First call FilterBy to set up the
//criteria, then call Load to load the data using the filter. Only after it is loaded, you can call
//CountBy or SumBy repeatedly to obtain the graphing points. It cannot be loaded again.
public class CustomerManager {
  //Check if a customer is active as defined by the c_status field and optionally by the
  //presence of recent orders. Throw NotFoundException if given customer was not loaded
  //in this CustomerManager instance. A customer is considered active if it has
  //a recent order, or if c_status is one of the active codes.
  //c = customer, which must be one loaded by this manager
  //checkOrders = if false, only checks is_active field; if true, also checks for recent orders
  //days = how many days in history to check for orders (only inspected if checkOrders is true)
  //Returns a two-character string: The first char is the active-status code as defined by c_status,
  //and the second char is "Y" if there are recent orders, or "N" if there are none or it wasn't
  //checked.
  public string IsActive(Customer c, int days, bool checkOrders) {…}
}

With all that commenting, it should never be necessary to inspect and reverse-engineer the implementation. Intentionally, I’ve used an imperfect, or “legacy” style interface because a lot of code like that exists, and it isn’t an excuse to fail to expose it. For example, it’s probably not a good practice to return a 2-character string in the manner stated, and if was upgraded to a class which properly wraps that concept (maybe called ActiveStatus), then less commenting would be necessary. But since it isn’t like that, it requires painstaking rigor to specify what happens. Because of knowing the interface semantics so well, the caller can be confident that it never returns a null or empty string, and it if doesn’t throw an exception, the customer was valid.

The “what you see is” megaworkaround

Database products like Foxpro, Access and FileMaker allowed you to script commands that would, for example, “open Customers where rep=’Sam'” and then it provided the user with the ability to edit and save changes to all those customers with features like filtering, and sorting. This embodies the “what you see is” paradigm. It’s derived from “what you see is what you get”, which refers to on-screen representation being true to the printed page. But “what you see is” is the paradigm of showing you everything, or at least the headers and categories so you can open and drill down to everything. It’s data-centric rather than by a wizard or menu-driven.

We haven’t gone forward with that concept very well, so now we have a disparate stack of tools and have to write a lot of middleware, just to do things that decades ago only required one line of code. Ideally we’d have the same kind of record-editing capability as before, but on any user device, with a true database server, and with the ability to apply any amount of business logic to it.

The user paradigm hasn’t changed over time: social media and web searches (among many other things) are exactly the same thing – ways to present the results of database queries and possibly allow editing. I think at least 80% of computing consists of filtering, sorting and formatting all the data in the world to show you the data you want to see.

Without a conscious effort to build the UI and data access layers to the “what you see is” paradigm, systems (web sites, apps, etc) degrade to be driven by commands invoking narrow sequences of behaviors, with too few options. This is often because it is easier to code it that way.

As a simple example, a game might list the completed and in-progress games and allow you to create, open, or delete from the list, rather than the standard “new game”, “quit game” and other commands. This allows the user much more flexibility in using the product, and it encourages more object oriented programming (the Game is a persitable object in memory).

This applies to nearly anything – point of sale systems, hotel web sites, or dating sites. They can give you a generalized means to query, sort, group, open, drill into, and delete items (sales, hotels, bookings, matches), then the user is never confused: they don’t have to wonder how they might go back and find another sale/booking/match/etc, or delete it, because that functionality can be generalized (as it was in Foxpro!)

Actually achieving this megaworkaround is unfortunately quite a bit more expensive on a web platform than coding for each use case.

The member ordering megaworkaround

Suppose someone adds these two members to the end of a C# class called Invoice:

//get the next unused detail line number
public int NextLineNo() { return ++lineNo; }
private int lineNo = 0;

There is nothing wrong with the code; the problem is where it is. Looking at the top of the class file, there may be declarations for the storage of things about the invoice – line items, customer, due date, and so on. Now we have a new data member declared at the end. So we may have inadvertently created confusion about what all the class really contains.

The more general problem is that in common languages, source code is just a text file and you can sequence and format as you like. You might group all your public methods as a way to help navigate the class, which introduces semantics to the file formatting. Different programmers don’t have the same tastes in all this, so there could be incompletely-maintained and misleading semantics in the file.

Ideally the tools would support intentional ordering semantics but prohibit unintentional formatting that could be interpreted as semantic. This would be a bit like the block comment megaworkaround, where the editor would allow you to specify named groups of members, but would not allow blank lines or other hints, such as long lines of hyphens as the separators.

The megaworkaround that we need, since the tools are not helpful, is rigor in the definition of the ordering established at the team level. So, it could require the order of all type declarations, all private data, all public data, all public methods, then all private methods. It would then be considered a bug to mislocate code.

I often further group class variables by labels such as “data set by constructor”, “data used only during exporting” and so on, so when you are working on the class, you know the lifetime and initialization status of everything easily.

The widget megaworkaround

Screen widgets tend to be a fairly time-intensive kind of programming that requires many iterations. With other kinds of code, stepthrough-style debugging is practical and fast, but for widgets, debugging tends to be more complex and slow.

Ideally we would be able to code screen widgets in an interpreted live environment. So we might be building a control for entering currency values, and while it is partially coded, we can see it as it will look, change the value displayed, and change the size or other visual properties. It would not be recompiled or restarted, and would not have to be embedded in a test app to be able to code and debug it.

The currency control is a simple example, but it gets worse with grids and complex user interactions – a lot of time can be soaked up with things like making a grid validate at the right time, displaying custom grid features and other complex binding and UI event interactions. Problems might only appear with real data.

I’m not a big fan of universal unit testing because the amount of time taken up by making test platforms can exceed the savings in debug time. But for widgets and UI in general, it is worth developing controls in an executable whose only purpose is to throw test data at the controls – test-driven programming. Then you import the controls into the actual program you are writing.

You can extend this test-bed philosophy to grids and whole screens (whether web or installed apps). However, to get this level of independence at that broader level, your test platform has to not know anything about your data system. It is tempting to use a class defined in your data system, like a persistent Customer class for example, as the input to a customer-editing control. That layers up the UI on top of the other layers. While that can work, it makes it impossible to unit test it without the test bed being essentially the whole system. To be able to separate it for faster UI development, the control and UI library has to re-define everything in its own terms and get its dependencies injected. So it would have a CustomerModel which is a “view-model” class that has the same fields as the persistent, or “model”, Customer.

The dogmatic insistence on separating all view-models from models, and dependency-injecting everything has its own costs though. In particular, the business logic doesn’t flow over and a lot of mapping has to be rewritten. In the extreme, if you want the customer code to be validated when the user keys each letter of the code, you would need to write a validate method in the logic tier and an event in the UI tier, and hook it up in a controller tier – so code for a fairly simple thing has to live in three places.

To summarize visually, here are the two paradigms discussed here:

widgets

Remember the problem is significant because we don’t have the ability to build controls while the program is running; if we did, we could use the vertical architecture more safely.

Some of the techniques for this situation include:

  • Make multiple layers of widget libraries, so the simpler layers can be unit tested easily, and the higher layers are composed, might not be unit tested or require a larger test bed.
  • Use dependency injection without copying between model and view-model. For example, make the customer editing control use an interface ICustomer, which is defined at a common lower layer, and make sure the customer model implements ICustomer.
  • or… whatever works but think this through in advance so you save on unit testing time.

The sinking ship megaworkaround

The “sinking ship” is the phenomenon of an enterprise data system getting bigger and bigger over time, until it outgrows itself – bugs and performance problems start to overwhelm it. You know you have a sinking ship when people start talking about “scrapping this whole thing”.

Here is a sampling of the many problems that can arise with supersized code base:

  • We don’t know what is where. A new programmer doesn’t know if there already a solution built for the situation they are tasked with solving. Worst case, they develop a new solution, and then the code base has multiple ways to do the same thing.
  • There is resistance to improve the foundations; it feels fragile and experience shows that when the foundations are messed with, things start breaking all over. This can go along with a sentiment that “we worked really hard to get to where we are now” which suggests that the value of some of the system primitives are sacred, or cannot be questioned. A team I was on had the rule imposed that we “may not refactor” – but refactoring is a name for a variety of necessary coding practices, so going to that extreme is an admission that the system is too fragile.
  • As it gets bigger, dependency walls are not built to suit the new size. It makes sense that a low-budget application would be single-tier and the programmer would not worry about principles like separation of concerns as much; consequently a small application might not have any hard walls prohibiting over-dependency between parts. But bigger applications need that.
  • Dependencies are added carelessly and then once added, additional code is written that depends on the same thing until it becomes very difficult to reverse the original mistake. For example a method that was designed to be private might be made public as a hack to get something to work; then it starts to be called all over and encapsulation is broken.
  • Performance degrades; it becomes hard to track everything that is happening. Seemingly simple things are bogged down in many layers of code that are not properly optimized out.
  • General classes creep towards specifics, reducing the reusability of the whole code base. For example, in a SQL library that I had written, I looked a year or so later and found that someone had added an if-statement like this: if (col_name==”customer_id”) {…} The problem here is that what was a general-purpose SQL library now has magic in it, which pertains only to one specific database schema.
  • General rules are added that should be specific. Someone once added a rule to a large data system that no field value anywhere in any table could start with a tilde. This solved (or maybe hacked) some problem, having to do with searching using specialized search syntax, but it was not really a general rule. A completely different part of the system needed a field to start with a tilde.
  • A single class keeps getting more features added. There is a tendency when designing new code to think about classes better, but when maintaining and adding relatively small features, there is a tendency to just add them to the existing class structure and fail to make new classes. I’ve seen classes grow beyond 30,000 lines of undocumented spaghetti, because every new line was just another case, another user option, another small thing.

The ideal of course is that a growing feature set grows the code structurally as well as in pure lines of code. The size is not the true issue. The issue is that the structure has to match the size.

The tools impede meeting this ideal largely because of the lack of explicit layering (as shown in one of the megaworkarounds above). But the real culprit is simply that people are fallible and larger systems require more mastery to manage well.

The main megaworkaround for a sinking ship is rigor in working from the foundation up as the code grows. The metaphor I like to use is building a new second-story room onto an existing house. You might legitimately need a new room in that exact place, but you would not just glue it onto the outside of the house as shown in figure A below:secondstoryInstead you would build a foundation for it as in figure B, then build up the first and second stories as in figure C. Likewise, if the code base does not support a new feature, you have to first build up from the bottom, so that it does support it, then add the feature.

As an example, suppose you have a partially editable grid of invoices in your app, and it relies on a grid control which has been specialized for the application. The grid allows a property “CellStyle” to be set for each column, and the options for CellStyle are Plain, Editable, or Hyperlink. The customer column is not editable but it is a hyperlink used to drill down to the customer record. Now suppose the requirement comes in that they want to also be able to change the customer in this same grid. A very “second story” approach would be to write in exceptions just for the invoice grid to allow that – a hack. The cleaner solution relies on these insights: (1) Probably other grids will need this feature, so it should be in the lower layer; and (2) “Hyperlink” and “Editable” are not really mutually exclusive in the first place. Realizing that the original conception of the CellStyle property was wrong allows us to fix it. We need to overcome the hurdle of thinking “it’s always been that way” and also overcome the resistance to making a new system-wide feature just for one small customer requirement which only affects one screen. Over time, expanding the base to maintain its correctness allows us to build the layers on top in a non-hacky way, and the whole code base can remain beautiful.

Often the “bad” choice (conflating editing with linking in the definition of CellStyle) was a reasonable choice at the time because the requirements were smaller before. Even though it wasn’t a “mistake”, it does need to be made more correct. For example, it could be refactored into two properties – IsEditable and IsLink.

When a new requirement goes just a tiny bit outside the boundaries of the foundation, it is tempting to just hack it – and sometimes expedience should win (maybe because it is so unlikely to be a general purpose feature or is short-lived). But for all but trivial cases, going the extra mile to expand the base pays off in the end.

In this case of the editable customer cell, the refactoring means all references to CellStyle would need to be changed throughout the system to the new properties, leaving the code base in a clean, elegant state. Then the UI for the link might need to be changed: Since you cannot navigate away when a click lands in an editable cell, the hyperlink might be changed to an icon instead of an underlined cell value. Once all those features are accounted for, the specific case of the invoice grid can use them. If you have to build up a lot of new foundation, you often don’t change the code in question (invoices) until the very end after many hours of refactoring and improving the feature set system-wide.

Other code-size-related skills and workarounds are as follows:

  • Watch class size. If it exceeds 1,000 lines in a class, the question should be asked: is this class too big? If it exceeds 4,000 lines, the answer has to be “yes”. Is the class both an entity and a container of some other kind, and maybe also a controller? Separate out concerns.
  • Watch for narrow feature requests. Often, the new feature as stated by the user might be “I need to edit the customer on this one screen”, but that has to be seen as an example of a more general need (as in the case above). “Allow for two tax rates” might really mean there could be zero or more tax rates. “Use white text on black” could really mean they want customizable themes.
  • Wall off madness. When you have code confusion, sometimes you can resequence and rename – actions that don’t affect the compilation, but help you wall off aspects and see dependencies better. One time I had a large UI class that was tracking many kinds of mouse operations including dragging toolbar items and multiple context menus. It was buggy in ways that suggested poor interaction between the various mouse operations, and it wasn’t sufficiently encapsulated. Some of the variables were named things like ClickPosition and LastMenu, and so, not knowing whether those variables were used for some or all the operations, I gave names to the operations. One was Lindsey, one was Jodie, and so on. Programming is to named abstractions as welding is to steel, and we need to be able to bend them at will. Once I walled off “Lindsey” and all her variables (LindseyClickPosition and so on) as the operation dealing with dragging certain screen elements, it was possible through the naming scheme to eliminate the buggy side effects. This technique isn’t a substitute for better object oriented techniques, but it is a step to isolate code madness.
  • Keep doing things the bad way. Sometimes there is a technique used all over the code base, and it isn’t very inspired. For example, every time we use a dropdown control, we have the same 5 lines of code to set up the options. A new programmer faced with coding a new form with a dropdown control should continue to use the bad way, rather than make a better way for the new form. The pattern may involve code duplication, which is bad, but inconsistency in technique is worse.
  • Improve globally. As a counterpoint to the previous point, when someone has that flash of insight – there is a better way! we don’t need all this duplicate code! – then all cases should be brought forward into the new way in one project. From the previous example, the 5 lines of code should be condensed into 1 in all the places where it occurs. Avoid supporting multiple ways to do things.
  • Make foundations sacred. Beauty is most important at the core, and many competing artists cannot paint a beautiful picture. If someone codes a wacky hacked up dialog box or controller code that nothing depends on, it is not so important that it be treated like sacred artwork, but the foundations need to be. Don’t allow entry level programmers into the sacred area; they may change it to suit whatever they are doing with no concept of durable correctness. They might not care.
  • Schedule infrastructure improvements. You can wait until new needs are identified to do the work of expanding the foundation as described above with the second-story picture, but you can also proactively do improvements just because it’s a general investment in the future. You might look and say, “we could eliminate these dependencies” and go ahead and do that just to make better code. You might look at a bloated API surface area and try to streamline the interface to fewer calls.
  • Break things on purpose. When you discover a logical problem, break it. For example, suppose a method is defined like this: ParseDate(string userEntry) and a new requirement or a missed bit of logic is exposing a bug in which the entry might be a date-only or a date-time, but the parsing method doesn’t know which to expect. The correct way to handle it, which is more tedious in the short term but saves hours in the end, is to change the syntax to cause all calls to break. The new syntax might be ParseDate(string userEntry, bool dateAndTime), or multiple methods with different names. Then when compiling, you have to determine for each broken call whether it needs a date-only or a date-time in that context. Programmers might shy away from this, feeling it is too big or risky, and they might put in a heuristic or optional second way to call it, so they don’t break anything already coded. But the probability is great that the existing code has bugs, so it is better to find and expose them than to leave it be.
  • Watch what it feels like. Look at the classes and documentation as a whole. Is it a beautiful work of art or does it feel messy? (Hire a girl coder if necessary to answer this question!) If it just feels uninspired, it probably is riddled with bugs.

The nonconfiguration megaworkaround

A scenario that eats up a lot of developers’ time is that a system doesn’t work the same (or at all) in production as it does in development. Something is different about the machine, operating system, the deployed disk image or some configuration, and it can take a long time to isolate what the difference is.

The root cause of all this is the many decades and layers of options, or moving parts in all the deployables. One example: The tool may need PHP, but only some versions work. PHP has configuration, and you can decide what directory to install it in. Then you need to tell the other component where PHP is installed. There could be an add-on to the product or a customized web style. And so on – a potentially long list of things.

There has not been an adequate effort to rationalize the storing of configuration and options across platforms and products. In Windows for example, you can change the application folder, use the “app data” folder by user, store options in user-defined target files or in a database, or even use the central Windows registry.

I think that this happens because products are rarely really finished; they just get released when they work well enough. So a programmer puts in some experimental options or tries something a couple different ways, and leaves all the choices intact while it’s being tested, in case one way doesn’t work. Then it goes out the door like that.

A web system that combines function, content and configuration in the same folder tree is another variant of this problem. It is possible to change content after deployment, making it non-identical to the released product.

Ideally tools and deployable components would be atomic, immutable, and versioned, so they can be relied on to work identically in all cases. Components would reference each other by version, and the idea of an “install location” would be abstracted out of the application realm.

While we can’t fix the platforms yet, we can rationalize our own products to have as few moving parts as possible. The megaworkaround details include things like:

  • Write code to test the presence and version of components that it relies on, and issue nice errors.
  • Document the exact file list in a deployment. Don’t design a product that can be installed in many ways and might work differently depending on what gets put in the deployed folder.
  • Control optional functionality through licensing, not by removing files.
  • Centralize configuration. Don’t use the built-in configuration mechanism of the system components when that results in options being read from multiple sources. Instead write code that reads from the central configuration and forces the behavior of the components. (The endpoint configuration in Windows Communication Foundation is a very guilty party in this regard.)
  • Separate site-modifications from the product itself.

The integration megaworkaround

Integrating data between software systems is a major cost and potentially major point of confusion and failure. Here’s some common scenarios with some of the problems spelled out:

  • A company allows for each employee to have a domain login, an account in the in-house data system, and an account on the help forum software, email lists, .. and the list goes on. As each new system is introduced, the IT people develop scripts or other means to try to keep the account details in sync. They might get the new-account procedure working but it fails if an employee changes her name, or it may have no facility for deleting accounts. There may be multiple ways to create each of the accounts and not all of those ways invoke the scripts.
  • A hospital acquires an oncology lab with its own data system, operating as a service – so it doesn’t have access to the raw database. It also has a general patient database. There are common data points, as well as separate oncology data and separate general data. It’s not practical to rewrite the systems into one, so there may be procedures forced onto the staff – for example, they must enter patient data into the main system first then use a utility to copy that record into the oncology system. Staff therefore have to spend a lot more time dealing with the systems.

Where these scenarios break down is not so much in the capabilities of development tools but rather in the operating systems and the whole architecture of the internet and how systems are written to rely on specific databases.

It may not be obvious what the ideal solution is that prevents problems like this from occurring, because to envision that we will have to see the whole process of developing data systems differently. Here are a few of the philosophical points that would describe a more ideal world for integration:

  • Software development is the art of providing tools that act on data, rather than the business of developing complete siloed systems. (Even if we build in rich APIs for controlling behavior, that doesn’t make it recomposable.)
  • People and organizations should own and control our own data; not just have “our” data spread around on facebook, gmail, and dozens of other server farms that we cannot access except through the service.
  • Platforms (operating systems) should work in abstractions so there is no system boundary.
  • We should have a standard for robust communications of structured type-safe data.
  • We should be able to redefine the persistence layer of applications and insert layers as needed.

Alas, since we are not there yet, integration continues to require careful attention to detail. The megaworkaround is to pick the best and most practical out of the following three approaches:

  1. No copies – There is only one database and all integrated systems access the same data through glue code, modifications to systems, or other means to prevent duplication.
  2. Master data and read-only copies – The master copy is the only one that gets updates, and it exports changes to the other systems (nightly or in response to changes). The other systems are configured or modified to disallow changes to the data in question.
  3. Multiple read/write copies with synchronization – This is the worst case scenario, relying on two-way synchronization, or – the worse than worst case – one-way synchronization with no controls guarding against changing the target copy.

Partial integration can be worse than no integration; when partial integration fails (or just wasn’t developed to cover some cases) users are left in an undefined state. If the undefined state cannot be fixed by users, then the whole integration is broken. So it is a good idea to document the target data points with integration in mind. For example the customer_name column in a synchronization-target database could be defined as any one of the following:

  • “The read-only customer name from the X system; may be obsolete depending on the time of the last data copy”
  • “The customer name. The initial value is imported from system X, but it is editable thereafter and no longer connected to system X”.

Yes, I literally write out definitions like this in permanent living documentation!

Whenever the integration relies on user compliance, then it’s critical to make it a forced part of the UI, not some semi-auto-magical thing that sometimes happens. It must force failures when it is done wrong, and allow users to redo it.

The bug tracking megaworkaround

The nature of “bug tracking” is that it also includes feature tracking and there is no strict line between the two. In a fantasy world of pure engineering, we would carefully determine whether the observed unwanted behavior is “by design” or not and classify it accordingly. In the real world, “that’s not what I meant originally” equals “bug”. To make matters more complex, really large things (which might be perceived as small by the reporter) are lumped in with actually small things. The result is that the list of things to do on the code base is fractal-like: large things to do are really composed of small things, and in the process of reporting a “bug” as fixed, the fix itself may have nested and related bugs.

An example: A bug is that the system didn’t have a way to search for old orders. The fix introduces essentially a new feature. The feedback on the fix is that the search results should be sortable by different columns. Upon fixing that, further feedback is that it should search on any part of the customer name instead of the beginning of the name. In short, the communication about what to do can be a big time vortex, as bugs swirl around with requirements clarifications.

Because of the grayness of it all, new features can creep in under the guise of a bug fix without having gone through proper vetting. As an example: The bug is reported that the system emails all customers when it should optionally only email active customers; a mediocre programmer would just add in an if-then with a dialog box and not think about the fact that it is really a new requirement and what that requirement means.

I’ve not seen any tool that handles this well. Ideally the tool would allow programmers to split and merge bug records, and it would separate discussion threads about user needs from discussion threads about engineering.

The megaworkaround that I suggest for this is to have an experienced gatekeeper monitoring the bug database. Anything that is called “wrong” should be analyzed very critically – is it a new requirement and does it make sense? Does it require a full design cycle to avoid the sinking ship phenomenon? Does it reveal a general problem in the system or is just literally an isolated bug?

 

Until there are perfect tools, there are megaworkarounds!