I’ve compiled a list of 8 features that are inherent to all badly designed software. This list is in regards to code design — it’s about architecture.

1. High Coupling

All software you write depends on some other software. And parts of your own software depends on other parts of your software. Coupling is how much one part of your software depends on another part. In a highly coupled system, lots of parts all depend on each other. In a lowly coupled system, parts are independent from each other as much as possible.

Why It’s Bad

  • Changes made to one part of your system affect other parts of your system. This means making changes becomes difficult and error prone.
  • It becomes difficult or impossible to use even functionally unrelated modules separately because they depend on each other so heavily.
  • Due to the previous point, it is nearly impossible to write unit tests. This compounds the problem of making changes because you can’t properly test to see if your changes break things in other parts of your system.

Solution

Stop coupling! Write classes that are as separate from each other as you can. This may mean building utility classes, or classes that help “connect” bits of your system. The point is that Part A of your system should be completely separate from Part B of your system.

Further Reading

2. Premature Optimization

We’ve all heard the famous quote by Donald Knuth, the author of The Art of Computer Programming: Premature optimization is the root of all evil.

Developers often worry about the performance of their software before performance should be considered at all.

Why It’s Bad

  • A developer who writes code with performance as a first priority will produce code that is hard to use and maintain.
  • Code that is “optimized” prematurely rarely ever results in software that actually runs faster. Without proper testing/benchmarking, it is often hard to know which parts of your system is the bottleneck. Saving 15 milliseconds on a calculation routine does nothing for the data-fetching routine that takes 1500 milliseconds.
  • Due to the lack of proper testing, developers will spend too much time on menial performance optimizations while missing the important ones.

Solution

Write your code with performance in your mind — but don’t sacrifice usability and maintainability. Write your code first, and then only once you have identified real bottlenecks you can start optimizing.

3. Abusing Inheritance

Inheritance is great, but some developers try to force their software to fit into rigid inheritance trees. Inheritance is a tool, but it’s not the only way to write reusable software.

Why It’s Bad

  • Abusing inheritance results in classes that have functionality that they shouldn’t.
  • Subclasses make assumptions about their parent classes. This couples children to their parents. As we know, coupling is bad. The deeper our class tree gets, the more coupling, and the worse the design is.
  • You end up with huge families of classes. For example: A parent Animal class. Then a Dog child. Then a BlackDog, and a BlackBigDog, and BlackSmallDog, and BlackBigLoudDog and BlackSmallLoudDog… You can see how it quickly gets out of control!
  • You tie functionality/algorithms to a specific family of classes, making it harder to reuse. The only way to access such functionality is through inheritance — even when inheritance doesn’t exactly fit the bill.

Solution

The old maxim: Favor composition over inheritance.

Composition adds functionality at runtime — giving you much flexibility. Inheritance forces you to define all functionality at code-time, limiting possibilities.

Composition means your classes are built up (composed) of other classes for additional functionality. This reduces needless children, and decouples functionality into separate reusable classes of their own. Using the example mentioned previously, a Dog class might be made up of separate classes to define it’s appearance (AnimalSize class?) and loudness of it’s bark (AnimalSound class?). An example calls might then be, $dog->getSize()->display() and $dog->getSound()->makeSound().

In other words, you want “HAS A” (composition) relationships rather than “IS A” (inheritance) relationships.

4. Failure to separate responsibilities

Some developers fall into the trap of creating classes that do way too much — so called God Objects. Classes should do one thing only (see the Single responsibility principle).

Why It’s Bad

  • Other parts of your system depend too much on a single class, creating very high coupling. Any changes made will ripple out to all it’s dependants.
  • It is hard to reuse code written in these classes because they come with so much “baggage”. Instantiating such a monster object wastes resources with all of the unneeded functionality that has been added.

Solution

Refactor. The God Object needs to be properly deconstructed into smaller, more specialized classes that all do a single job.

5. Failure to refactor

Refactoring is the activity of switching around your softwares internal design, while keeping it’s functionality the same. For example, renaming a method is a small example of refactoring. A more intense example might be splitting one class into two separate classes (maybe you’re tearing apart a God Object?).

Why It’s Bad

  • As your product grows older and you add new features, classes will start to gain functionality they did not before. Over time, classes may become too big resulting in monster or God Objects.
  • As your product grows and you add features, some classes may start to become complex. Without refactoring to simplify the inner workings of your system, this complexity may start to get out of control. For example, a class constructor that takes 20 arguments.
  • The longer refactoring is put off, the worse the problem becomes and the harder the task will be in the future.

Solution

Refactor as soon as you encounter smelly code. The earlier you refactor, the easier it will be.

6. Complexity

Code should be simple to read and maintain. Functions that take 20 arguments, or classes that require too many helper objects are examples of complex designs that can be fixed.

Why It’s Bad

  • Complex designs are hard to understand and thus hard to maintain.
  • Complex designs will result in hard to find bugs.

Solutions

Simplify. If a function takes 20 arguments, maybe some of those arguments can be encapsulated into a class. Or maybe the one function can be replaced by an easier to use class. If a class requires lots of setup, consider using the Builder pattern. If you have a system of classes that all work together towards some goal, consider simplifying the interface by using a Facade.

7. Reinventing The Wheel

Some developers, especially amateurs, like to create everything from scratch and ignore all of the available free library code that exists on the internet.

Why It’s Bad

  • You waste time writing components when you could be actually building your product.
  • You get none of the free testing done against a popular library. Anything you write from scratch will contain bugs.
  • Not always true (but may especially apply to amateurs): Many libraries are written by developers who may be more experienced than you. These coders produce better organized code with more features.
  • Not always true: Developers of libraries are often experts in whatever task the library is meant to solve. For example, database abstraction. Because of this, the library developers will produce a better product than you could.

Solutions

Realize that writing everything in-house is almost always a bad thing. There will of course be times where there exists no suitable library. But almost everything has been written before, and written well. A good developer knows the tools at his disposal, and uses them.

Every time you consider writing something yourself, you should search for a good library first. Common difficult tasks such as database abstraction, web services, image manipulation, validation and parsing are examples of components where an abundance of free library code exists. Less well-known problems, or new problems, may not have many pre-built solutions.

8. Failure to apply known design patterns

Design patterns are well known solutions to architectural problems in software. I’ve mentioned a couple in this article already (Builder, Facade).

Why It’s bad

  • A developer who doesn’t know design patterns is more likely to design his software incorrectly.
  • Not knowing about design patterns will decrease your efficiency. Every problem has been solved before. By knowing which pattern to apply to a problem, half of your work is done for you. Otherwise, you will go through many revisions before you end up at the same solution anyway.
  • Design patterns help a developer communicate with other developers. It’s the language of the profession. You will not be able to convey ideas effectively if you don’t understand the terminology.

Solution

Read all material you can find on the topic of design patterns (see next section). Amateur programmers may not appreciate the value of these patterns at first — or, indeed, may not fully understand them — but as time goes on, this knowledge is priceless.

Further Reading

One Response to “8 Features of Badly Designed Software”

  1. Bragaadeesh Says:

    Awesome Article.. Really stunning dude.

Leave a Reply