Good design comes at a price, and it is important to understand these costs if we seek to build and maintain well designed code.
What are the Costs?
Up-Front Design Costs
It costs more to design code well from the outset than it does just to throw code at a problem until you have a solution. There is a tension between over-engineering in the name of good design and spaghetti coding in the name of just getting the job done as fast as possible.
As time goes by, good designs tend to rot. It can take considerable effort to maintain good design.
The counter to this is that poorly desined code becomes increasingly difficult to maintain, reducing its value and the value that can be added to it.
Increased Cost of Grokking a Code Base
The smaller your modules, the more there are and the higher your levels of abstraction. But more layers in your code mean that new developers will have a harder time understanding your code base.
In addition, a good design often maps onto a business domain. As a result, a developer may need deep domain knowledge in order to understand the reasoning behind many of the design descisions that have been taken. It takes time and effort (ie. investment) for developers to gain this knowledge.
Of course, developers are generally happier working on clean, well designed code, and happy are less likely to change jobs. Nevertheless, this can be a considerable cost to some organisations.
Higher Levels of Abstraction Lead to Slower Code
In general, the more modules you have, the higher the cost of those modules communicating with each other, the slower your code will run. Big balls of mud are often fast.
Of course, not all code needs to be especially fast. Moreover, well designed code is easier to optimize. Furthermore, it can be cheaper to add better hardware than reduce developer productivity: developer time is usually more expensive than CPU cycles.
Finally, good design often does involve more abstraction… but not always.
Better Design Results in More Lines of Code
Many langages require significant amounts of “boiler plate” (standard code) in the header and footer of each module, typicallly indicating such things as the module’s scope, namespace and name.
Better design often calls for more descriptive names, which are often longer than poorly thought through names.
It is arguable that “lines of code” is a poor measure of developer productivity, but even so, each additional character is a codebase has a cost associated with it.
Is Good Design Worth the Cost?
In my view, not always. I believe that the purpose of design is to maximise value delivered by a solution over the lifetime of a problem. So, if a solution is only intended to be used once, never changed, used by a single user and very short and simple then there is little to be gained by putting too much time into making its code base perfect. On the other hand, if a solution is intended to be with an organisation for any length of time, then overall costs can be reduced if the principles of good design are applied.
A common principle of good design is to avoid repetition, and this often involves increasing abstraction. I have often witnessed a pattern amongst developers, according to their experience: Novice developers apply little abstraction, more experienced tendwards over-abstraction (using, for example, all the design patterns they can think of on every project), whereas a true expert will apply appropriate, pragmatic levels of abstraction.
So, how do you know if good enough is really good enough? How can you tell if you are you guilding the lilly or adding real business value?
I don’t have all the answers, but then neither does anyone else!
Clearly, a great starting point is the recognition that this is an important question, but a hard one.
Most systems start off with a nice clean design, but over time software starts to rot.
Does Design Matter?
Software design is about managing the complexity of a system. Unmanaged complexity leads to difficulty in making changes to the system, which is important because all software development is change.
Poor design leads to lower quality systems, less functionality to clients, higher costs (for everyone), loss of business and misery for developers.
Why Do People Tolerate Poor Design?
Developers are often encourages to working software fast, so “quick and dirty” solutions go into production
Lack of knowledge:
Introductory courses / texts tend to focus on syntax rather than design
People learn from what they see, and there are a lot of poorly designed systems to learn from
Larger systems benefit from good design more than smaller systems
It takes more effort to design larger systems well
Fewer developers have experience developing larger systems
What are Design Principles?
Design principles help us to understand the “rules” about the best way to manage complexity, and therefore maintiain and increase the value that is delivered by our code. They are best viewed as guidelines. They shouldn’t be followed blindly, as there are costs involved in using them.
What is Design Rot?
Most systems start of with a nice clean design, but over time, “software starts to rot”. As “Uncle Bob” Martin says:
At first it isn’t so bad. An ugly wart here, a clumsy hack there, but the beauty of the design still shows through. Yet, over time as the rotting continues, the ugly festering sores and boils accumulate until they dominate the design of the application. The program becomes a festering mass of code that the developers find increasingly hard to maintain.Robert C Martin, Clean Code
What are the Symptoms of Design Rot?
Martin identifies 4 symptoms of rot:
Rigidity: Software becomes difficult to change, and changes cause other changes in dependent modules. Simple changes become expensive, developers become fearful, unknowns increase.
Fragility: The tendency of the software to break in many places every time it is changed, sometimes in areas that have no conceptual relationship with the area that was changed. Fixes introduce new bugs. Developer credibility is lost.
Immobility: The inability to reuse software from other projects or from parts of the same project. Drawing on existing modules is impossible because of they bring in too many dependencies.
Viscosity: Design Viscosity occurs when the design-preserving approaches are harder to use than hacks, i.e. it is easier to do the wrong thing than the right thing. Environmental Viscosity occurs when the development environment is slow and inefficient, leading to the temptation to take short cuts.
These issues tend to be more of a problem as the size of a software base increases.
What are the Causes of Design Rot?
Each of these symptoms is mainly caused by improper dependencies between the modules of the software. Therefore, managing dependencies between modules is at the core of good design. This applies at several levels: framework, library, package, class and method.
Where Does it Happen
At all levels of abstraction:
Function / method level
Class / module level
In the interfaces between systems
How Do We Avoid Design Rot?
Some of the general principles are:
Value design and pay attention to it
Iteratively improve design
Improve design at each layer of abstraction
Desgin modules that are small and focused (high cohesion)
Reduce dependencies between modules (low coupling)
Here is how I fixed my missing .Net target framework 4.0 issue in VS2010.
Visual Studio 2010 stopped detecting the .Net framework 4.0. Specifically, this framework version was missing from the “Target framework” drop-down on a project’s property page. Versions 2.0, 3.0 and 3.5 were present in the list, but not 4.0. Reinstalling the framework and rebooting had no effect.
The problem was that the file,”FrameworkList.xml” was missing from folder “C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\RedistList“. Copying this file from another machine solved the problem.
I am now able to target .Net 4.0 again:
I found this solution at the end of this discussion.
All of a sudden, odd things have stopped working and others are asking for admin permissions. Explorer won’t let me see things I can normally see, scripts aren’t running… This seems odd, as I’m already an admin on my box. What’s going on here?
This has happened to me several times now, and each time the circumstances have been the same:
I’m Windows on a network
My password has changed recently
I haven’t logged in recently on the box where I’m having problems
This is quite a common scenario for me at work because most of my programming work is done on a VM that I very rarely restart, or even log off. At the same time, whenever my password expires I change it on my physical box rather than the VM.
Log out and back in again.
Your credentials for your current session are stale.
I have been developing a rather complex map that includes various scripting functoids for manipulating dates. One of the in-line C# scripts started producing output that simply didn’t make sense. I ran the code in LINQPad, and it produces the expected output, but testing the map resulted in some bizarre behaviour.
My code looked like this:
public string ToOpenLinkDateTime(string param1)
if(DateTime.TryParse(param1, out dt))
Given an input node that contains:
I validated the map, which generated the XSLT that is actually run on the input. I was surprised to find that the C# code embedded in the XSLT looked like this (notice the difference in the string formatting on the 6th line):
public string ToOpenLinkDateTime(string param1)
if(DateTime.TryParse(param1, out dt))
Why is this different from the code in the scripting functoid?
Avoid using the same method signature more than once. When several Scripting functoids have the same method signature, BizTalk selects the first implementation and disregards the others.
It turned out that I’d created a similar functoid elsewhere in the map that uses the same method signature (name and parameters), but had the implementation above. It turns out that BizTalk recognized that more than one function was defined with the same name, and then silently ignored all but the first one.