Coding, Best Practices

Written by

Coding is not just about making magic happen; it’s about ensuring the code is maintainable, readable, and understandable to others.

Fortunately, you have many sets of tools to help you. For example, there's a book called Clean Code by Bob Martin (Uncle Bob). Look into it, it's great!

Before You Begin

As a developer, first you need to clearly understand all the requirements for the job. When building the model, this is the foundation.

From there, it’s best to abstract into sections to help you find the algorithms that are required. These sections become little pieces of the puzzle that will help with the eventual solution.

Make a Plan

Once you clearly understand the problem and can model it, you’re halfway home. At this point, think about the architecture of the solution, the application flow, the libraries that you will need, and how the data needs to be fetched, among other things.

Now it becomes like a “Lego” game: take all the pieces, put them together, and validate their consistency. Remember, when writing a piece of code, it’s good to practice DRY code or Do Not Repeat yourself. For example, when writing a function that logs the application warnings, you do not want to duplicate the log function for each type of warning.

SOLID

The SOLID principles are very important and should be part of the curriculum for every computer science degree. These principles are described by Robert C. Martin in his Agile Software Development book and cited by many other authors.

Single Responsibility Principle

The "S" from SOLID is for the "Single Responsibility Principle", which means that a class has to solve one problem and do it well. It also means that all the logic for this class needs to be isolated in another component.

For example, if you have a “Log” class, its only responsibility is to log text into a file or console. You shouldn’t assign validations or transformations. If you have an “Account” class, its only responsibility is managing accounts. Let’s see an example:

//  BAD
class Account {
    constructor(){}
    
    add(){
        // Logic to verify the payload
        // Logic to verify if exists
        // ... more logic        
        // Logic to add an account
    }
}

As you can see, all the logic for account creation is in the class. The code will grow as many lines as possible to try to verify the input, the existence of this account, and finally create it.

Now we are going to see how to solve this kind of issue in a piece of code:

// GOOD
class Account {
    constructor(){}
    
    add(){
        ValidationService.validate();
        AccService.validate();
        AccService.create();
    }
}

The code above shows us the same “add” method responsible for creating an account, but this time we can see some functionalities are being invoked inside the method. But why is the above code good and the first one example bad?

Suppose you are working in a requirement and the specification for an account validation has changed. 

In the “add” method, you have to affect a class with tons of lines of code and run tests for the entire code.

With the second option, the good code, let’s take a look at its benefits:

  • You only need to change one portion of the code in “ValidationService”
  • The test you need to run is smaller
  • You can reuse the validation method everywhere
  • The code is readable, understandable, and the class is cohesive

Open/Closed principle

The meaning of this principle is “Open” for extension and “Closed” for modification. As a software developer you probably know about the object-orientation paradigm, which says that we can extend properties from a parent using a feature called “inheritance.” 

In Javascript (ES6), we can extend a class with the reserved word “extends.” Let’s take a look at how it works.

class Person {  

	get name() {     
    	return this._name; 
    }  
    
    set name(newName){     
    	if(newName){         
        	this._name = newName;     
        } 
    }
}

A class “Person” has one property called “name”. In this class, we are using get and set to handle the modification of this property. But notice that the get and set methods are inside the class. We can modify this property by changing those two methods.

Now, we can create a class “Developer” that extends the “Person” class.

class Developer extends Person {     

	get favLang() {     
    	return this._favLang;   
    }   
    
    set favLang(newfavLang) {     
    	this._favLang = newfavLang;   
    }‍
    
}

We use the reserved word “extends” to inherit from a base class, in this case, we are extending the “Person” class because the “Developer” also has a name.

Indeed, if a class “Developer” needs to change its name, it will do so, extending the functionality of the base class.

Liskov Substitution Principle

“If it looks like a duck, quacks like a duck, but needs batteries, you probably have the wrong abstraction” – Robert C. Martin.

We discussed the “Open/Close” principle and now we have an idea about the importance of inheritance, but now it’s time to see how the Liskov Substitution Principle can help us handle it.

Sometimes things that we assume as logic in language doesn’t translate well in code. For example, think of how a “Rectangle” in mathematics is a “Square.” Implementing this abstraction, Rectangles are Squares, in code doesn’t work well. Let’s see why.

One of the pillars of the Liskov Substitution Principle is that when you have a class “X” who inherits from “Y,” the class “X”  needs to be consistent enough to substitute for the parent class.

Say the class “X” is the “Square” and “Y” is the “Rectangle”. You will see that the derived class cannot substitute for its parent because the behavior for setting the width and height are different.

We’ll go deeper in a future post, but for now it is enough to understand the importance of the abstraction and behaviors when you are modeling a solution.

Interface Segregation Principle

Maybe you heard something like “Clients shouldn’t be forced to depend on methods that they do not use.” Let’s talk about this.

Segregation is when you take something and separate (or isolate) it from others for better understanding. We are going to see an example of payment classes when we use two processors, one for Visa and another for MasterCard.

First, let’s take a look at the interface (written in TypeScript)

interface ProcessorType{   
	type: 'Visa' | 'Master Card',   
    process: (amount:Number) => any
}

This interface represents a payment type, which could be type “Visa” or “MasterCard” and has a method called “process” that will be responsible for processing the payment.

class PaymentBase {  

	private _processor:ProcessorType = null;  
    
    set processorEngine(p:ProcessorType){      
    	this._processor = p;  
    }  
    
    get processorEngine(){    
    	return this._processor;  
    }  
    
    processPayment(amount:Number){    
    	this._processor.process(amount);  
    }
}

Above, we have a piece of code with a class “PaymentBase” for the abstraction of payment. This processor type can be set and has a method to process the payment requiring a number as a parameter.

class VisaPayment extends PaymentBase{  
	processor: ProcessorType ={     
    	process: () => {},     
        type: 'Visa'  
    constructor(){     
    	super();     
        this.processorEngine = this.processor; 
    }
    
}

Now, let’s take a look at what we did. We made an interface to abstract how it looks at a processor type. Then we created a class to handle the payments for a processor type. Finally, we extended the “PaymentBase” class to create a new “PaymentType.” So far, if the client needs implementation for “MasterCard”, we have all we need to build it and the side effects are greatly reduced.

This is how it looks:

const payWithVisa = new VisaPayment();
payWithVisa.processPayment(1000);

Dependency Inversion Principle

This is one of my favorite principles in SOLID.  We have seen this in the above example of “Payments” and now we are going to talk a little bit more about how to approach it.

The goal of the “Dependency Inversion Principle” is to not have any instances inside classes. In Javascript, we can see a lot of examples of this principle, like callbacks.

In callbacks, you are not creating instances inside a function, you just pass the reference as an argument and it will be executed inside the target method.

Returning to the previous example, we had a class called “PaymentBase” and this accepts a “ProccessorType” for the payment. This property responds to an interface that has the shape of a processor.

First, we are not creating instances in the base class, and second, we are isolating the responsibility of the implementation to a derived class.

Benefits of SOLID

  • Increase the maintainability of the code
  • Makes the code much cleaner
  • Makes the code easier to read and understand
  • Is a guide, not mandatory
  • Gives you an understanding of design patterns
  • Allows you to put good practices in your code

What’s next?

This post is just a brief introduction to SOLID Principles and best practices, but the continuous pursuit of knowledge via self-learning is key.

Reading, practicing, and understanding the important topics of SOLID and clean code is the best way to keep on track. I suggest reading two books:  Clean Code and Agile Software Development by Robert C. Martin. 

Try it yourself by writing a couple of lines in your favorite code editor.

Cheers!

Frequently Asked Questions