13 min. read

Software design

What is it?

It’s a process where you take requierments and produce software blueprints.

Why do we need it?

Manage the risks and estimate the time to construct the implementation.
As builders don’t craft their buildings on the spot - programmers should not construct their code on raw ground.
A good way to think about it - it will cost you less to throw a piece of paper or better yet - shit+delete a document - instead of reconstructing your software every 3 monthes because something was missing.

The process

The design process is where all the thinking comes.
In a perfect world this process produces a blueprint which tells exactly how to implement the system.
From hardware choices to minor implementation details such as what system API to call.

Assessment

Since we are limited to our human form - time and cost are crucial.
The first step is to estimate how much time the design will cost and the accuracy of the design process will determine how much time the construction - implementation - will cost.

What if I don’t want to design?

It’s bad luck to have no design.
Maybe for short living code it ok, but for a long project or product it’s better to start with a design.

Minimal

The first goal to reach is to have a minimal blueprint - basic guidelines on how the interfaces should look like, how the solution implements different flows and business logic.

Company Policy

Usually companies that aren’t 5 day old startups have their own way of producing design documents.
I always thought of them not enough although they are the perfect start point of covering all the necessary major aspects of the software such as - critical components, configuration, security risks, patches, etc…

Good enough

This is the document we want to reach - good enough for us to use it as implementation guidance.
Most of the difficult dilemmas are answered, languages were choosed, frameworks were picked, products were tested, etc…
If you reach this step you will notice most of the design points you add are fixes to your original ideas.
Rarely you’ll need to throw everything away and that’s because the requierments have changed - in this case be relieved that you didn’t implement the system!

Perfect

That’s what we should strive for but we will never get there.
You need to stop thinking on how to produce perfect software and start managing the risks to produce high quality software!

Types of designs

  • Clean
  • Integrations
  • Dirty
  • Incremental design
  • Algorithm design

Clean

Clean start

A design which takes no existing code into account, everything is possible, everything is permited.
Languages and frameworks are the first choice to make - what kind of language will help us solve this solution or if you are considering an existing product, how will this product limit us or help us.
Furthermore it’s good to check rivals, if existing tools exist, etc…

Flows & Business

After languages are decided it’s time to go into business - what kind of flows we want, what kind of behavior is expected to be perfomed.
Usually I prefer to describe a list of features by priority, which are the most important - that are the core logic of the code - and which are “fun to have”.
If it’s a client side or GUI oriented, it should also contain specific details about the requirements of the GUI.
A GUI which isn’t design properly prior to implementation won’t be any good.

Requirements

The design step will come after gathering requierments with business/client.
Therefore we should have the general idea how it should act, the design step will extend these requierments to solve technical solutions or find loop-holes inside the original requierments.

For example, let’s take a website.
Maybe the user input wasn’t decided, should it fall on us or should it be a client choice? If you are working alongside the client - fast feedbacks are good to remain relevant and produce the best software the client want.

Software characteristics

According to the software expected behavior you can determine overall what goals and characteristics you need to meet.
Some of these general chracteristics may be: Robust, high performance, Visual appealing, etc…

Crafting interfaces / API endpoints

Thinking abstract make it easier to dive into details.
Treating complex details as a black box can mitigate the tangles created by coupled components.
So I always recommend by thinking how inner components interact between them.
For example:
Consider a simple game where you want to write your highscores into a storage.
It may be: A file, Memory location, Web location, etc…
If it’s stored in a server it’s going to be part of the networking system.

Inviting KISS (Keep it simple stupid) alongside abstractions may produce interfaces like so:

1
2
3
4
5
6
7
template <class TObj>
class BasicStorage
{
public:
virtual void Store(const TObj& obj) = 0;
virtual TObj Retrieve() = 0;
};

All we want is to store an object (Probably a list of highscores) and retrieve it.
The details are in the implementaion - do we save memory cache, is it a file, is is a socket, etc…

Thinking about implementation

The implementation should not lack in focus - this is finally where things are coming to play together, depending on your style of documenetation the implementation should be thought about and expressed in words or schemes.
People sometimes tend to add code or code they already implemented - this is a risk for additional fixes.
First think about the details then construct them.

I also tend to keep with me API references or empty projects to check if certain implementation will work - usually minor stuff like specific searches in container or API calls.

Wrapping things up

As far as the document is concerned, be sure all details that you think are necessary for the implementation or the documentation are in there in details so the implementation would be easy to understand.


Integrations

Integrations

This kind of design consists of integrating an existing system or a new system into an existing ecosystem.
This may be a small task of consuming a new endpoint,
Or may be a large scale project for example, integrating an existing business.

Importance of the document

Managing risks is an integral part of producing bugless integrations.
The risks and features should be described as thorough as possible.
Neglecting this side of the design will create unforseen bugs such as mismatch in protocols, SSL configurations, API parameter mismatches, etc…

Working as a team

Either you are the consumer or the producer of an API, you won’t work alone.
Therefore communication is important as much as the document itself.
If the sides don’t understand the needs of each other, it will create unpleasent working envinronment which will lead to missing deadlines and again, unforseen bugs.
From a 3 week project you may get a 6 monthes failure.

It’s important to state all relevant business/technical requirements in the document because that’s the “Why we did it like this”.

Guidance and fast communication

From my experience any change that isn’t communicated properly will lead to bugs.
At the first day a component may consume certain endpoint in a JSON format.
After 2 monthes the maintainer of this endpoint decides to tweak it to work only with XML since their JSON library causes issues, if this change isn’t communicated quickly, the component will fail and result in a bug.

Describing the system

Don’t assume that whoever reads the document has prior knowledge of the system.
Always describe your current system and how you want to change the state to fulfill the business requirements.
Not only it will clarify any missed detail by the reader, but will teach you better on the system.


Dirty

Dirt all over the place!

I call it dirty because it involves a risky solution that touches a long existing flow.
Changes that are required to make end-to-end.
A classic scenario is a new feature that takes user data from the client, and this data is needed in the server.

Risk

Because of all the dirt, this must be done with unusual amount of observation and code analysis.
The design should evolve around the existing code, taking into account all existing relevant flows.
Every piece counts as long as the assessment is correct, therefore accuracy is important.
Moreover, the better the accuracy the better is the time estimation for the project.

Document document document

If the risk managment is accurate, there should be no issues to estimate the changes and time requierments.
The document should contain all existing solutions and how they mutate to include the new features/changes.

Legacy & Testing

Creating new stuff on top of existing can be a pain especially if it involves legacy code.
The most subjective issue at stake is how to maintain existing functionality.
Depending on the kind of behavior you are adding/changing I suggest adding testing as part of the design document - how are you going to ensure that the current functionality is working, and also how the new functionality works properly.
This is why it’s important - adding code that is TESTABLE will change your design and how you integrate and refactor your code.


Incremental Design

Iterations!

It’s all about making smaller iterations with the intention that the code WILL change.
The main reason for doing incremental design is that you aren’t sure of the details or you explicitly know that the code will change.
The best example will be a new product that you want to develop as fast as possible and produce it to the market.

How to design the code

Details may change, interfaces may shift, whole libraries may be thrown away.
Therefore it’s important to couple loosley everything that you think may change and make the components that should stay robust.
Prefer making simpler decisions, the simpler the code the better next iteration will be.

Partial documentation

Don’t feel the necessaty to document every detail.
Instead document changes and intentions, because itentions are not clear at first, and down the road the project will either fail, or succeed and than the incremental design will become the design.
Only then you can start documenting everything else that is missing.

Startup development mode

The incremental design is best suited for a “startup” kind of programming.
When you want to progress quickly or iterate through different kind of designs to see what suits you better.
I still want to empathize that design is still a cruical step, it’s not just about creating a document - it’s the whole thinking process where you connect the dots between features and how they are implemented in the solution.


Algorithm design

Step by Step

Algorithms are all about describing how specific details work.
The description should focus on each step, what’s the input of that step and what is the output.
In terms of mathematical details, it’s better to include and document mathematical variables and equations.
Because as programmers know that mathematical equations look rough when writing them in code.
Example:
Let’s take the prabola equation x^3 + a*x^2 + bx + c = y
How it look in code:

1
2
3
4
public T SolveForY<T>(T x, T a, T b, T c)
{
return Math.Pow(x, 3) + a * Math.Pow(x, 2) + b*x + c
}

It’s neat but doesn’t look like the mathematical equation.
And this is a very simple case.
So if you have a difficult equation, take the time to document important details.
Especially configurable variables.
Better:

1
2
3
4
5
6
7
8
public T SolveForY<T>(T x, T a, T b, T c)
{
// Use variables and document them!
var x3 = Math.Pow(x,3);
var x2 = Math.Pow(x,2);
// Make it simpler for the eye:
return x3 + a * x2 + b*x + c
}

To create bugless code we need to start designing it to be bugless from the beginning.
Thanks for reading.


Is this helpful? You can show your appreciation here: Buy me a coffee