DEV Community

Cover image for 3 tips for clean & efficient code
Deepu K Sasidharan
Deepu K Sasidharan

Posted on • Edited on • Originally published at deepu.tech

3 tips for clean & efficient code

Originally published at deepu.tech.

One of the most important aspect of software engineering is code quality and one of the most important criteria should be readability and maintainability of your code, especially when working in teams where others have to read and understand your code. The best code in my opinion is that doesn't need any explanation or even documentation and that even a complete beginner can read and understand.

It is easy to write complex and cryptic piece of code. It is much harder to write simple and readable code.

It is easy to get carried away and write long and complex functions and code that shows off how smart you are and how you can write something in the smartest way, especially when you area beginner and you are trying impress your peers. But the smartest way might not be the most readable and maintainable way. It doesn't take a lot of skill to do that IMO, but writing simple and clean code that is easy to read and maintain takes skill. Its not a skill that you can acquire overnight, its something you develop over the years.

Below are some tips to make your code more efficient, readable, easy to navigate and more maintainable. This is not an exhaustive list, its just some of them that could help.

1. Fail fast and early

When writing any logic try to do the failure assertions as early as possible and fail fast. It makes programs feel faster as you wouldn't execute a lot of stuff first just to fail at the end for something that you could have checked to begin with. This approach also makes your code easier to understand as it makes intent much clearer.

Consider the below function, let us use JavaScript on NodeJS for simplicity. Note that i'm using the syncrnous methods from the NodeJS FileSystem API for simplicity.

function processFile(filePath) {
  if (fs.existsSync(filePath)) {
    // check if file exists
    var fileContent = fs.readFileSync(filePath, "utf8"); // read the file content

    if (fileContent !== "") {
      console.log("Content found");
      // do stuff
    } else {
      throw Error("No content found on file!");
    }
  } else {
    throw Error("File not found!");
  }
}
Enter fullscreen mode Exit fullscreen mode

We are fetching the given file and doing some processing on it, if the file is not found or if the content is empty we throw an error. There is nothing wrong with this method but if you have a lot of steps in processing the reader has to go through all that first to see the error handling hence the intent is not that clear, also the cyclomatic complexity of the function is higher due to multiple if/else. Consider the below version of the same function

function processFile(filePath) {
  if (!fs.existsSync(filePath)) {
    // check if file does not exist
    throw Error("File not found!");
  }

  var fileContent = fs.readFileSync(filePath, "utf8"); // read the file content

  if (fileContent === "") {
    throw Error("No content found on file!");
  }

  console.log("Content found");
  // do stuff
}
Enter fullscreen mode Exit fullscreen mode

This does the same logic but is less complex and more readable. It has less cyclomatic complexity and the intent is clear

2. Return early

Similar to failing early it is also much nicer to return early, i.e use the language's ability to flow code based on returns to reduce the complexity of code and improve readability and intent. Consider the below logic added to our function

function processFile(filePath) {
    ...
    console.log("Content found");

    // check if the file content is base64 encoded: Lib used https://github.com/miguelmota/is-base64
    if (isBase64(fileContent.trim())) {
        console.log("Decoding content");
        // check if the base64 content is image and return a placeholder
        if (isBase64(fileContent.trim(), { allowMime: true })) {
            return "Image content";
        } else {
            // decode to string
            var buffer = Buffer.from(fileContent, "base64");
            var decoded = buffer.toString("ascii");
            return decoded;
        }
    } else {
        return fileContent;
    }
}
Enter fullscreen mode Exit fullscreen mode

The logic checks if the content is base64 encoded, and if not decodes it. Again the function works fine but consider the below version as well

function processFile(filePath) {
    ...
    console.log("Content found");

    // check if the base64 content is image and return a placeholder
    if (isBase64(fileContent, { allowMime: true })) {
        return "Image content";
    }
    // check if the file content is not base64 encoded: Lib used https://github.com/miguelmota/is-base64
    if (!isBase64(fileContent.trim())) {
        return fileContent;
    }

    console.log("Decoding content");
    // decode to string
    var buffer = Buffer.from(fileContent, "base64");
    var decoded = buffer.toString("ascii");
    return decoded;
}
Enter fullscreen mode Exit fullscreen mode

Isn't this much simpler and easier to reason with. So try to return early and keep the code flow simple.

3. Write small focused functions

Finally do not write huge functions, even if you are doing imperative style of programming try to break it down into small functions as much as possible. The above example is not a huge function, but still you could break it logically into two as below

function decodeBase64(fileContent) {
    // check if the base64 content is image and return a placeholder
    if (isBase64(fileContent, { allowMime: true })) {
        return "Image content";
    }
    // check if the file content is not base64 encoded: Lib used https://github.com/miguelmota/is-base64
    if (!isBase64(fileContent.trim())) {
        return fileContent;
    }

    console.log("Decoding content");
    // decode to string
    var buffer = Buffer.from(fileContent, "base64");
    var decoded = buffer.toString("ascii");
    return decoded;
}

function processFile(filePath) {
    ...
    console.log("Content found");
    return decodeBase64(fileContent);
}
Enter fullscreen mode Exit fullscreen mode

Having smaller focused functions helps with following

  • Unit testing becomes easier.
  • Functions become reuseable and composable.
  • Easier to focus and read, without having to build a mental model of the entire logic
  • Easier to change something in a small function than in a huge one, as you will have less cases to worry about.
  • Easier to debug and easier to spot bugs.
  • reduces side effects as the function does something very focused.

Bonus: Write pure functions

This is not a functional programming thing, though it was made popular by FP. In general mutations are always tricky and could cause unwanted bugs. When we try to avoid side effects(includes external date mutations) by keeping small functions pure we also reduce the surface area for such bugs. So keep your pure functions separate from impure functions and try to write as much as pure functions. Do side-effects and external data/state mutations from within a unction only if its unavoidable. You can follow this regardless of programming paradigm you are following.


If you like this article, please leave a like or a comment.

You can follow me on Twitter and LinkedIn.


Cover image credit: Photo by Christopher Robin Ebbinghaus on Unsplash

Top comments (10)

Collapse
 
pinotattari profile image
Riccardo Bernardini

Another suggestion fairly easy to implement: use descriptive names. A variable with name Quantization_Step is much more readable than Quant_S or qs. There is a minimum risk of getting carried away using names like

Variable_With_A_Name_So_Long_That_It_Seems_A_Chapter_of_The_Betrothed

but, honestly, the risk is minimal :-)

Collapse
 
deepu105 profile image
Deepu K Sasidharan

Agree, well-written names also reduce the amount of documentation needed

Collapse
 
lexlohr profile image
Alex Lohr

Another thing to consider is the testability of your functions. Tests are making your life easier by detecting problems before you do. There are 3 flavors: unit tests (testing small parts in isolation), integration tests (testing parts against each other) and end to end tests (testing the whole stack). Unit tests are usually rather fast, easily written, and catch obvious errors early. End to end tests tend to get slow and complicated, which is why you should try to do as much of your testing with the other methods.

Collapse
 
deepu105 profile image
Deepu K Sasidharan

Great points

Collapse
 
antonioortizpola profile image
Antonio Ortiz Pola

I do not know if this can be important, but: Be aware of your business environment.

Part of making a good software is making easy to move the parts, but it is really hard to create a super flexible system and you can introduce a lot of accidental complexity.

So a good knowledge of what the organization does, what are the goals, the culture and the commercial opportunities can give you good tips about what parts of the system need more design, what parts can be extensible and what probably will stay as is.

This comes from the Conway's law

organizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations.

Personally, this has helped me a lot in the medium and long term.

Collapse
 
pinotattari profile image
Riccardo Bernardini

If the language allows you, declare pre- and post-condition for your function and type invariants for your types. They are powerful bug traps. If a post-condition gets triggered when a function return, you have quite a strong hint about where the bug lies.

Collapse
 
deepu105 profile image
Deepu K Sasidharan

could you provide a sample? and wouldn't those be considered side effects?

Collapse
 
giampietrigonzalo profile image
Giampietri Gonzalo

Nice post. An other tip may be:

  • Take a time to read your code after wrote it and ask yourself, Is this readable enough? or even better, ask it somebody else.
Collapse
 
deepu105 profile image
Deepu K Sasidharan

Good point

Collapse
 
deepu105 profile image
Deepu K Sasidharan

If you have more tips please write in comments