Good Coding Practices: Codesphere Version

Adhering to good coding practices and maintaining a consistent codebase is the differentiating factor when it comes to developer experience and efficiency.

Best Coding practices for software engineers
Best Coding practices for software engineers

There is a huge difference between programming and software engineering. Programming is more like working on a hobby project where you know the entirety of the codebase. On the other hand, software engineering is a collaborative process where a team works together on a big project, and not a single person is familiar with the entire codebase.

In collaborative projects, the actual codebase is the main determinator of developer experience, because that is what developers work with every day. No surprise here that if your codebase is low quality and inconsistent, development is going to be slower and annoying. So, adopting consistent and good coding practices is a key differentiator that enables teams to produce high-quality software at scale

We at Codesphere (subtle hint: code in the name) are very passionate about writing good code and empowering other software engineers to work seamlessly in their teams as well. So we want to share our take on good coding practices. The below-mentioned recommendations are based on Codesphere’s internal guidelines crafted by our architects (a big loud thank you to them). These coding style suggestions come from significant programming experience and an understanding of human psychology and physiology. 

However, first things first, what constitutes a well-written code? Let’s take a look at it first, and then we will jump to common human limitations and good coding practices.

What is Meant by Well-written Code?

 Well-written code is clear, efficient, and maintainable. It is structured in a way that is easy to understand by utilizing meaningful variable names and clear comments. By easy it does not mean you have to put in an insignificant amount of work but rather create code that is low risk and will not introduce bugs and cause confusion for the reader about what needs to be changed or how an API is being used. In addition to that, maintainability is also a key aspect of good code Ensuring that the code can be easily modified without introducing unintended side effects. 

What are the Factors that Affect Our Coding Practices?

Several factors affect how we read and perceive code. We are going to discuss some of those factors and limitations in detail.

Human Limitations

Since code is primarily written by humans and for humans there are significant limitations. Here are some of those limitations 

  1. A small short-term memory capacity.
  2. Being prone to distraction.
  3. Often making trivial mistakes.
  4. Having a limited visual field and acuity.
  5. Easily missing things in plain sight.
  6. Limited ability to imagine being in a state different from the current state.
  7. Being unable to imagine how much we don't know.
  8. Being prone to overestimating our abilities and having a strong optimistic bias.
  9. Being unaware of all these and other limitations.

Reading Code

It is important to notice that people don't read code like they read a book. it requires you to jump around and between areas to understand what's happening in the code. It means you are reading one line at a time but instead, you will have a small elliptical visual area in focus which allows you to reach short parts of multiple lines at a time. 

In general, it means that things at the start of lines are easier to see and understand than things written at the end of the line.  It also means the reader will see text on the below line before the end of the current line they are concentrating on. This is especially relevant when you are leaving comments in the code.

Familiarity Biases

You need to understand that you might prefer writing the code the way you are familiar with. You will prefer to stick to the familiar style over it being written differently irrespective of the value of one or the other approach. This bias usually recedes over time as you become familiar with the new approach.

Examples of  Good Coding Practices?

Code communicates at least two things, how something is being done and what the code is attempting to do. The how is just the set of instructions and it is generally obvious from the code itself. However, the meaning may not be obvious but is more important. When a programmer is reading code, they are almost always trying to understand what it is trying to accomplish, not necessarily how. So here are some good coding practices for clean, well-written, and organized code.

Variable Names

Variable naming conventions vary depending on context and scope. It's advisable to opt for descriptive, longer names for global variables, as they may be accessed across modules, reducing the risk of confusion. Conversely, concise names suffice for local variables, especially if their lifespan is short, enhancing readability and reducing cognitive load. Longer names can be harder to compare and may exceed visual field capacity. 

You should also take into account the names that are already being used in the same context it should avoid having multiple similar names in the same context to avoid confusion. 

Example

Prefer:

metrics.map((m) => …);

Avoid:

metrics.map((metric) => …);

Function Names

Function names should clearly indicate what a function does when called, providing a helpful summary for anyone reading the code. Ideally, function names should focus on the function's purpose rather than when it's called. There are exceptions, like when a function implements an interface or when it's abstract and lacks a specific purpose. In such cases, it's acceptable to name it based on when it's called. However, this practice doesn't give much insight into what they actually do in the code. 

Consider the following:

on('click', handleClick)  // what happens on click?

Compare to this:

on(‘click’, closeForm)

The latter gives a clearer idea of what happens when the click event occurs.

Example

Prefer:

dispatchCloseEvent

Avoid:

handleClick

Meaningless Literals

When passing boolean or numeric values directly to functions, it might not be clear what they represent. This lack of clarity can make the code harder to understand. It's recommended to add comments or give them meaningful names unless their meaning is obvious from the surrounding code. Comments should follow a specific format, indicating the parameter name before the value. Another option is to assign these values to local variables or constants if they're used more than once in the function. 

Example

Prefer:

e.addEventeListener(‘click’, closeForm, /*useCapture=*/true);

Avoid:

e.addEventeListener(‘click’, closeForm, true); 

Splitting Lines: Binary Operators & Continuations

When you are breaking lines in your code try to put the continuation or operated at the start of the next line and not the end of the current line.  This includes: 

  • all binary operators: =, &&, ||, +, *, etc.
  • each part of the ternary operator ?: 
  • spaces at the end of strings

One exception might be commas. Placing operators at the beginning of the line makes them more visible and prominent, which helps readers quickly grasp the expression. Additionally, this practice helps prevent certain bugs, like missing spaces within strings.

Example

Prefer:

 return 'appId' in args
        && typeof args.appId === 'number’
        && 'domainName' in args
        && typeof args.domainName === 'string’
        && !isEmpty(args.domainName);

Avoid:

 return 'appId' in args &&
            typeof args.appId === 'number' &&
            'domainName' in args &&
            typeof args.domainName === 'string' &&
            !isEmpty(args.domainName);

Block Statements & Braces ({})

It's best to always use curly braces {} after if, for, while, and similar statements. Additionally, make sure to add a new line after the opening curly brace. This consistent practice ensures clarity and makes modifications easier. It also simplifies debugging tasks like adding or removing debug logging and setting breakpoints.

Example

Prefer:

if (!has(x)) {
        return;
     }

Avoid:

if (!has(x)) return;

TODOs

Todos are comments about the functionality you plan on implementing or correcting in the future. It is helpful to know the author of every todo so more details about it can be attained from the relevant person if needed. It is advised to use these comments sparingly and instead implement the requisite functionality straight away.

The format for TODOs is strictly as follows:

// TODO(username): text of the todo

Lazy Operator Side Effects

Using binary operators like && and || or the ternary operator ?: for conditional operations can be confusing because it doesn't clearly express the intention of the code. Consider the following:

if (foo) {
bar();
}

reads as "if foo, call bar", which is clear, while

foo && bar();

reads as  “full and call bar”, which makes the code’s meaning less clear.

It's recommended not to use these short forms unless the returned value is being used directly. In those cases, it's acceptable and often preferred.

Example

Prefer:

if (condition) {
doSomething();
}

Avoid:

condition && doSomething();

Use Exceptions for error handling

Exception throwing and catching is the standard mechanism for error handling in many languages. It is also always possible to handle errors by returning an error of some sort (e.g. a Reply object) and checking for the error at the call site. However, there are two problems. It doesn't preserve the stack trace and may require error checks at every point in the call chain, leading to redundancy. Additionally, it results in two different error-handling methods, complicating code maintenance. So, It is recommended to:

Use exceptions to signal errors within functions or methods.

Employ the Reply type for inter-service communication, especially when dictated by existing libraries.

Example

Prefer:

function foo(): string {
    throw new Error('bar');
 	}


const s = foo();

Avoid:

function foo(): Reply<string> {
  return errReply('bar');
}


const res = foo();
const s = res.getValue();

Use existing libraries for string format data

There are several libraries to read and write many string-based data formats including JSON, HTML, path, SQL, URL, YML, etc. Some of these may be augmented with wrappers and/or utility functions. Instead of trying to create string format data manually, use a library specifically designed for converting data and standard data structures to a specific format. If you already have existing code that does this then refactor the code before making new changes. It is advised because directly creating string data formats can lead to the mixing of data and code. Moreover, this can reduce readability and cause security issues that are not understood by the vast majority of developers.

Example

Prefer:

const url = buildUrl(‘https://localhost/’, {
  		variant: ‘dark’,
}).toString();

Avoid:

const url = `https://localhost/?variant=dark’;

Wrapping up: Why You Should Know These Good Coding Practices?

Being a good software engineer and a team player goes beyond just solving a complex problem. It is highly emphasized that you adhere to established coding conventions and best practices, promoting consistency across the codebase. This will directly impact the developer experience. A clean and consistent codebase will enhance the efficiency of the team and help produce high-quality software at scale. However, the above-mentioned practices are based on our experience and might differ for other teams.

We still hope this article helps you understand why adhering to good coding practices is important and enables you to write more consistent, clean, and maintainable code.