I believe that software development is design, either informed design or accidental design.
As a programmer is writing code, they are constantly and continuously making design decisions. For example, consider the questions a developer asks herself when writing a simple method:
- Should this code/business logic be in its own method?
- If yes, what is the visibility of the method?
- Should I add guard clauses and code with Design by Contract?
- If I need to return multiple things from this method, how should I do it?
- Use a generic Pair<> class?
- Use a generic Tupple<> class?
- Create my own tiny class to return the data?
- Could that class be a private class to the containing class?
- Just use some output parameters?
- The code in this method needs to do some <string manipulation / date logic / whatever> should I extract that code into a reusable library?
- Should I bother to check if the reusable library already contains that code somewhere?
- I need to program a value into the method, should that value come from:
- The database?
- The configuration file?
- A constant defined in the code somewhere?
- An inline value?
- What if I need to return a stream – how do I ensure the stream is cleaned up appropriately?
- Should I log creation of the stream and the stack-trace that lead to it in case it’s not properly cleaned up so we know where the offending code is?
- What type of exception should I throw if something goes wrong?
- Should I just reuse a framework exception, even though it’s not a very good fit?
- Should I write (and then unit test) my own exception class?
- How could I make that more reusable
- I need some data in this method, where should I get it from?
- Should I expect it to be passed in?
- How granular should I expect the data?
- If I need three or four related fields should I write a class to be passed in?
- Should I require the existing class that these two fields are on, or should I just ask for the two fields separately?
- Should I go directly to the database layer in this method to retrieve the data I need?
- Should I expect it to be passed in?
This is of course just a small sample, but it is very typical. Developers are constantly designing code! Moreover, all of the above decisions will add up to have a big impact on the final code. How changeable is the code when the business requirements change during the project? How reusable will the code be later in this project, and to other projects? How long will it take to finish writing and testing all of this code? How will the code perform? These are all questions that will be strongly impacted by the decisions the developer makes. The question is, are the developers thinking about these repercussions, or just how to finish the code as quickly as possible to satisfy their current project manager?
In my experience, the quickest path to a working solution for the perceived current set of requirements is all that actually informs the developer’s decision. Long term re-usability, maintenance, performance, etc. are not considered by the developer because they are not rewarded by the company. The company does not reward its developers years after she wrote some code because it was rock solid and reused by six other projects (helping those projects get into production faster). Instead, companies tend to reward developers for getting the current task done as quickly as possible (this is also much easier to measure). So the company rewards the lazy programmer. Let us answer the above design questions in this mindset:
Should this business logic be in its own method?
Probably not, because that will mean more documentation required to pass the code walk-through, which is more work and requires more time.
Should I add guard clauses and code with Design by Contract?
Probably not, because that will mean more unit testing which will slow me down, even though it will help catch future bugs immediately.
If I need to return multiple things from this method, how should I do it?
Just use an Tupple<> because that is the fastest even though it is completely confusing and requires intimate knowledge of the inner-workings of the method.
The code in this method needs to do some string manipulation, should I extract that code into a reusable library?
I might not find any existing code, and if I do it might not be exactly what I need. If I have to write the new utility method I’ll also have to write the method documentation, write unit tests for it, and promote the utility library which will just slow me down. I’ll just code it directly into the current method.
I need to program a value into the method, where should that value come from?
Probably just a constant, although accessing the database layer directly can be pretty quick too. It will corrupt the application layering and tie this code to the database implementation and schema representation, but I’ll be done faster…
This serious of small pseudo-“time-saving” optimization decisions almost certainly ends up costing the company a lot more time and money in the long run, when business requirements change and the code needs to be updated and supported – which will happen. Small design “optimizations” like this during development can destroy an organization’s IT agility in the large scale. On large projects, or projects with rapidly changing requirements, these kinds of short-signed development design decisions will hurt the project itself, but in those cases the developer is still familiar enough with the code that changing the code is usually not too time consuming. However, after about 2 months poorly written code is almost always difficult to maintain even by the original author, and long-term maintenance suffers.
It is also this short-sighted developer mindset that allows companies to outsource development to other countries. If the internal developers are producing low quality code, why not pay somebody in a 3rd world country a lot less money to produce low quality code? Of course, outsourced developers definitely don’t care about the long-term goals of the company, so they will almost always take the fastest development design alternative every time, resulting in consistently hard to maintain code. I suspect that this is why the industry folks I am talking with are seeing companies bring development back in house! However, without seriously addressing the short-term project based mindset developers will still be tempted to take the quick route and code quality will not increase.
It has been my unfortunate experience to observe several organizations that do not grasp the complexities of detailed software design and development. In extreme cases, I’ve seen software development life cycles the barely acknowledged any software design. At these companies, very high-level documents are given to the developers, and then the developers are expected to build elegant solutions. Unfortunately, (unless the developers are exceptional) the resulting code is usually a hodgepodge of GUI code intermixed with domain logic intermixed with data access and service access code – the fastest path to “solution”. The code has an accidental design. I’m not blaming the developer, I’m faulting the company’s software development life cycle that doesn’t allow or encourage detailed design and robust code.
For these reasons I strongly prefer domain driven development over test driven development. I have seen test-driven development produce well tested, but absolutely horrible code that was very hard to change in the short run as business requirements fluctuated even slightly. Of course it was very hard to maintain over the long run too, and slowed down the project and delayed the business goals.
Conversely, my experience with domain driven development over almost a decade has consistently produced elegant code that was reusable by other projects, as well as being easy to test and easy to maintain in the long run. It was also faster! It took maybe a day or two longer to program originally, but then benefited the project by easily accommodating changing requirements, and having fewer bugs. It had fewer bugs because overall there was less code! There was no business logic code duplication, and utility code was written and unit tested just once. Additionally, because it was well designed the code was easy to understand and maintain by any team member. Focusing on the domain, and implementing it by carefully considering separation of concerns and adherence to the single responsibility principle lead to small reusable easy-to-understand classes (easy to understand if you knew the domain).
Of course all of the domain driven design code was also unit tested! I am a huge fan of unit testing well designed code! Testability should be an important part of the code design, but test-driven development is not a path to good code; it is a path to well tested code; but that does not mean the code is well designed, reusable, flexible, layered, or maintainable.
To help developers choose the right path, companies need to have software development life cycles that allocates time to domain driven design, and have software architects that understand OOAD and the business domain. The software architect needs to work closely with the project analyst and the business, as well as the developers and testers. It means you need quality design tools that can both forward and reverse engineer code. And it means acknowledging that development is design.