Design Principles in Software Development Part 1

2021/2/20·9 min read

This blog is talking about design principles in software development. It includes DRY, Soc, DIE, SOLID and few more wildly used design principle and provide example code. 

Following is the list of design principles I can find and are very helpful during the process of software development. If you're experienced developer even you have not heard about it, you might already doing it. 

DRY Design Principle

DRY, Don't Repeat Yourself. This is pretty much easy to understand and I think everyone is doing whenever is possible. This is the principle don't repeat the same things over and over again, if you see some functions, code are used at multiple places, it's better to move to a common place otherwise you'll have to maintain two different places for the same code. This can apply any area of software development, wether you're structure the front-end's scss file, create javascript functions of develop the back-end SQL. Following is one example I'm using using Node.js with TypeScript.

Following is pretty much easy scenario, for example, if you start develop a page A and create a class and that class need a readonly string for blog detail URL, so you defined that at the page A. Later, you noticed you also need that blog detail URL at page B, base on the DRY principle, you should move to a common area which both class can't have access. Perhap you create constant file so both page A and page B can access. 

// page A
private readonly BLOG_DETAIL: string = "/blog/post/";

// page B
private readonly BLOG_DETAIL: string = "/blog/post/";

This DRY can also apply when you create SCSS file, imagine if declear one variable for you primary color in one of your SCSS file, if that file gonna use at any other SCSS file then it's better to declear to a global variable SCSS file and all the SCSS file just import the variable. 

Same thing can apply to function, I have seen sometime DEV create one file for localstage in Javascript, another almost exactly the same file for sessionStorage in Javascript, and the only difference is localStorage vs sessionStorage but somehow it end up create two same copy. That's just something sad to see. 

This DRY seems simple, but if you continue keep this principle in mind I think lots of thing can improve. For instance, how can I create two exact same UI component to two different system? Let's say one for public UI, another for internal UI, but this UI component are looks exactly the same. There might be many way to achieve this, Micro Front-End could be one of the solution to this, Micro Front-End could be something difference, but the idea might be the same, DRY, you don't want to repeat to create 2 same component everywhere. 

SoC Design Principle

SoC, Separation of Concern is for separating software program into different unique section or unit, so each has its own purpose to address its own thing, so the end of this, it is better for maintainability, code refactoring, testable and more. Imagaine if one class has to take care everything, it'll be very hard to see. 

Let's take a look of following code, this snippet of the code is written in Express.js, the route is get the querystring key and use that value to access MongoDB to get local data, then bind the data to view 'local-view'. This code is not too long, and looks like is following DRY principle, and looks like is working, so what's wrong? 

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

let local = new Schema({
        key: {
            type: String
        },
        value: {
            type: String
        },
        culture: {
            type: String
        }
});

async getLocalText(key) {
        if (!key) {
            return '';
        }

        try {
            return await localModel.findOne({key: key});

        } catch (error) {
            console.error(error);
            return '';
        }    
    }

exports.all = async function(req, res) {
   
    const model = await getLocalText(req.params.key);
    
    res.render('local-view', model);
    
}

This code can actually break down into following section, database model, database repository, view model and controller, and each has its own purpose to responsibility need to archive. It is better not mix all these purpose into one file, so my understanding of SoC is breaking each software into smaller piece as possible, so you can just focus on that only. 

What happen if you don't do that? Well, then it'll make your code become a God class, a class can do anything, which hard to maintain, hard to add features and hard to debug, hard to do everything.  

  • Database Model - Define MongoDB Schema Model
  • Database Repository - Repository function to access MongoDB Database
  • View Model - Model to bind for View
  • Controller -  Check Request Query, get model to bind for View

So instead, if you create a file for database model, you can add validation, define schema at the model and can share with other data repository. If you create database repository file, then that file only need to worry about database access, database connect, same as View Model and Controller each has it's own purpose. 

DIE Design Principle

DIE, Duplicate Is Evil, this seems like same as DRY but just saying in different way. The duplicate is bad as same as DRY, I would think when you write new code, you should try to follow DIE or DRY to remove duplicate things as much as possible, however there's might be few case you might not have follow if you have to give it up other things.

For example, if you come to work on a system, you have seen so many duplicate constant, or functions, are you going to change it? 

I would think it might be depend, you need to consider your own error rate, what's percentage you break the code? Do you have a truste QA automation or not? Does your boss trust when you do refactoring? If you just come to a company saw the bad code and send PR to your boss and says this is bad, we shouldn't do this, if everything works that's great but if you break something it might take time to get trust back. 

KISS Design Principle

KISS, Keep It Simple Stupid is a design principle which mean keep the software design as simple as possible and don't make it complicate. This sounds simple, but I think in reality might take lots effort to write simple code. You'll have to review again and again to see which way is better, than reduce code, test again. 

 One of the moment I think KISS principle really apply was, assume you need to fix a bug in software, seems complicate, one dev A goes there fix the issue but end up create few new functions, another dev B go to the same code and fix the issue with one line of code. Which is better? 

But, how do you do to keep it simple? That's great question to ask, perhaps first is, 

Findout what's the real problem, this process might take more time, the reason why dev B only use one line to solve the problem perhaps he spend more time to under the issue. 

Another is keep your code smaller as possible, I always think if the fiel become bigger you have mixed responsibility in the same file. So if you can break them into different pieces, that's might help to keep it simple. 

Another way is do code review with your coworker, I remember I review many time with my coworker just to rewrite the setup for the Unit-Test in C#. We discussed how to write the code so easy for people to understand. If you have a great cowork can do this with you that'll be great way to help you write simple, clearn code for starter. 

YAGNI Design Principle

YAGNI, You Ain't Gonna Need It. This could be similar to KISS principle, to design software and make it simple, don't add anything if you don't need it. 

TDA Design Principle

TDA, Tell Don't Ask Principle. This is the principle that suggest the object is responsible for itself's data not the caller should be the one ask the question. Let's take a look of following simple code. 

You have class to holde price and product quantity and total amount,  when you initiate the LineItem Object, you have to do some kind of calculation ot get total, but ideally shouldn't the LineItem tell you the answer?

class LineItem {
         
         ItemPrice: number;
         Quantity: number;
         Total: number;

}

const myItem = new LineItem();
myItem.ItemPrice = 30;
myItem.Quantity = 2;
myItem.Total = 2 * 30;

Now, we change the code to following, which is just simply ask this LineItem, tell me the total and I know the price and quantity. In reality this LineItem might getting bigger and bigger, it might be make more sense to separate the LineItem's logic and properties to different file.

class LineItem {
         
         constructor(itemPrice: number, quantity: number) {
         } 
         get Total(): number {
              return this.itemPrice * this.quantity;
         }
}

const myItem = new LineItem(30, 2);
console.info(myItem.Total);