DEV Community

Cover image for 5 Code Refactoring Techniques to Improve Your Code
Suraj Vishwakarma for Documatic

Posted on • Edited on

5 Code Refactoring Techniques to Improve Your Code

Introduction

Writing code is fun and we enjoyed it a lot. Until an error pops out from nowhere which takes quite a time to solve it. Sometimes, the error is not visible as the code runs as intended but in production can cause an error. There can be performance and accessibility-based errors which can cause a bad user experience. This kind of error can be reduced by refactoring the code.

Code refactoring involves making improvements to existing code without altering its external functionality. It is one of the core parts of programming, which can not be overlooked otherwise, you will not achieve a better version of your code. Code refactoring can enhance the readability, maintainability, and scalability of the code. It is also intended to increase performance and increase developer productivity.

Today, we are going to look into some techniques that can help you in refactoring your code in a better way.

Now, let’s get started.

How to integrate Refactoring

Before looking for techniques to improve refactoring, let’s see how you can integrate code refactoring into your coding process. The below suggestion you can use for the purpose:

  • You should allot time specifically for refactoring the code.
  • Break down the larger refactoring problem into smaller ones for management.
  • Try to involve the whole team in the refactoring process.
  • Use automated tools that can help you in finding common refactoring errors.

Now, let’s start with the techniques to use for refactoring.


Extract Method

This method involves converting code blocks into a separate method/function. This is done to improve the structure and readability of the code. It is achieved by extracting code blocks that are long, and complex into smaller and more manageable methods.

To use this technique, we first need to find a code block that performs particular tasks which are a little complex. After identification, we then extract the code and place it into a new method. Also, make sure to give a meaningful name to the method. Now, where we need the code we call them.

Example:

Before Refactoring

    function calculateInvoiceTotal(items) {
      let total = 0;
      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        if (!item.quantity || !item.price) {
          console.error('Invalid item', item);
          continue;
        }
        const itemTotal = item.quantity * item.price;
        total += itemTotal;
      }
      return total;
    }
Enter fullscreen mode Exit fullscreen mode

After Refactoring:

    function calculateInvoiceTotal(items) {
      let total = 0;
      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        const itemTotal = calculateItemTotal(item);
        total += itemTotal;
      }
      return total;
    }

    function calculateItemTotal(item) {
      if (!item.quantity || !item.price) {
        console.error('Invalid item', item);
        return 0;
      }
      return item.quantity * item.price;
    }
Enter fullscreen mode Exit fullscreen mode

You can see how we converted the complex code that runs inside the for loop into another method for simplicity and readability.


Replace Magic Number with a Symbolic Constant

This code refactoring is for writing cleaner and more readable code. Magic numbers simply mean the hard-coded numerical value. Writing hardcoded numbers cause confusion for others as their purpose is not defined. Converting hard-coded values into a variable with a meaningful name will definitely help others to understand it. Also, you can add comments to it for further explanation. It can also help in debugging and reducing the risk of having an error in the future.

Example:

Before

    if (temperature > 32) {
        // Do something if temperature is above freezing
    }
Enter fullscreen mode Exit fullscreen mode

After

    const int FREEZING_POINT = 32;

    if (temperature > FREEZING_POINT) {
        // Do something if temperature is above freezing
    }
Enter fullscreen mode Exit fullscreen mode

Merge Duplicated Code

duplication or identical codes can appear in the code from different places. This code does not need to be totally identical but it can perform similar tasks or extend a little bit further from the original code. Duplicated code can lead to several problems, including increased maintenance costs, difficulty in making changes to the codebase, and a higher risk of introducing bugs.

While refactoring your code, you have to look out for duplicate codes. When finding such code, one method to deal with this is converting such codes into a single reusable function/method.

Example:

Before

    function calculateTotal(numbers) {
      let total = 0;
      for (let i = 0; i < numbers.length; i++) {
        total += numbers[i];
      }
      return total;
    }

    function calculateAverage(numbers) {
      let total = 0;
      for (let i = 0; i < numbers.length; i++) {
        total += numbers[i];
      }
      const average = total / numbers.length;
      return average;
    }
Enter fullscreen mode Exit fullscreen mode

After

    function calculateSum(numbers) {
      let total = 0;
      for (let i = 0; i < numbers.length; i++) {
        total += numbers[i];
      }
      return total;
    }

    function calculateTotal(numbers) {
      return calculateSum(numbers);
    }

    function calculateAverage(numbers) {
      const total = calculateSum(numbers);
      const average = total / numbers.length;
      return average;
    }
Enter fullscreen mode Exit fullscreen mode

In the before code example, we were doing the sum and doing it again for finding the average. In the after, we have replaced that with the function that provides a sum to both of them.


Simplifying Methods

It is quite similar to identifying as you are looking for methods/functions to optimize. Simplifying methods can be done for logic or to make it readable and cleaner. This technique can help you in reducing lines of code.

This method can be broken down into smaller code blocks that you can find in a function to optimize. Here are those:

  • Remove unnecessary variables and expressions: There can be some variables or expressions that you leave out for debugging but forget to remove such as console.log in JavaScript. Remove those and also
  • Using built-in function: Sometimes using an in-built function of the library or language will be better. As the same functionality can be achieved with less code.
  • Simplify conditional statements: If a method has complex conditional statements, consider simplifying them by combining conditions or using the ternary operator.

Using Lazy Load

It is a technique where objects are loaded only when they are needed. This can improve the performance of your application by reducing the amount of memory used. This results in faster loading of the application.

This technique is quite popular in web development. Especially with JavaScript frameworks like React where you can import different components through lazy loading. This can also be used to load images as per requirement.

Example:

Before

    import React from 'react';
    import MyComponent from './MyComponent';

    const App = () => {
      return (
        <div>
          <h1>My App</h1>
          <MyComponent />
        </div>
      );
    }

    export default App;
Enter fullscreen mode Exit fullscreen mode

After:

    import React, { lazy, Suspense } from 'react';

    const MyComponent = lazy(() => import('./MyComponent'));

    const App = () => {
      return (
        <div>
          <h1>My App</h1>
          <Suspense fallback={<div>Loading...</div>}>
            <MyComponent />
          </Suspense>
        </div>
      );
    }

    export default App;
Enter fullscreen mode Exit fullscreen mode

In the updated version, we use the lazy function to import the MyComponent component asynchronously. This means that the component is only loaded when it is actually needed, improving the overall performance of our app. We also use the Suspense component to display a fallback UI while the component is being loaded.

Conclusion

Refactoring is an essential practice for any developer who wants to improve the quality, performance, and maintainability of their code. By taking the time to analyze and optimize your code, you can eliminate redundancies, reduce complexity, and create a more efficient and scalable application.

By continuously reviewing and improving your code, you can create a more robust and resilient application. I hope this article has helped you to understand some refactoring techniques. Thanks for reading the article.

Top comments (22)

Collapse
 
ant_f_dev profile image
Anthony Fung

Great article.

I might not always replace magic numbers with symbolic constants. If the number is used in more than one place, then it definitely deserves to be replaced.

But if it's used only in one place, I might leave a comment to describe what it is instead. Taking the example above:

if (temperature > 32) { // freezing point
  // Do something if temperature is above freezing
}
Enter fullscreen mode Exit fullscreen mode

results in less code and doesn't seem harder to read than:

const int FREEZING_POINT = 32;

if (temperature > FREEZING_POINT) {
  // Do something if temperature is above freezing
}
Enter fullscreen mode Exit fullscreen mode

In addition to extracting methods, I sometimes also move these methods into different classes during refactoring. This groups common functions and helps promote single-responsibility within classes.

This in turn helps when wanting to share code between many modules. A further positive is that it makes code more testable because we think more about what functions can/should be public vs private.

Collapse
 
richardforshaw profile image
Richard Forshaw

I have had many years development experience. As a writer and reader of code, and with a basic understanding of compilers, if faced with this example I will always chose readability over lines of code for these reasons:

  • It is only 'more code' in terms of written code. After compiling (if a compiled language), there is no difference in code. So this is really an invalid argument.
  • Comments do not break code and fail tests, so they are subject to a different set of rules; thus they will often be ignored, abused and left out-of-sync with code changes (esp with copy-pasta code). So having the literal meaning form part of the compiled code is always better.
  • As another commenter posted, comments are often written for the writer to understand, and may not be understood by all readers. code that is able to be compiled, on the other hand, must be understood by all parties out of necessity.
Collapse
 
basman profile image
Adam.S

You might want to consider the case that 32°F equates to 0°C. Your comment works well. But might confuse some.

Collapse
 
ant_f_dev profile image
Anthony Fung

Hi Adam

You make a good point. Comments are only useful if read and understood. It's important to use units/conventions that the team will understand.

For example, °C is predominantly used in UK, and °F in US. If the team is UK based, it's probably best to use °C. Likewise use °F if the team is US based. It the codebase is shared between multiple teams, the best choice might be to either use both, or discuss and choose the one that everyone can agree on.

Collapse
 
ekeijl profile image
Edwin • Edited

I would prefer to use constants in most cases, because it communicates the meaning (to other developers) of the number 32. You also get the opportunity to include a unit in there: FREEZING_POINT_C to indicate the temperature is in degrees Celsius (or K or F). When the next developer comes in and looks at this piece of code, you have saved them time because they don't need to waste brain cycles on what the 32 could mean.

IDEs can help you easily look up the value of the constant (hover/ctrl-click), if you're worried about the amount of code.

Collapse
 
ant_f_dev profile image
Anthony Fung

Hi Edwin

I'm not quite sure how else // freezing point could be interpreted in this one-use context. It wouldn't refer to temperature as that would have been named freezingPoint if that was the case. Those are the only two non-keywords on that line.

if (temperature > 32) { // freezing point
Enter fullscreen mode Exit fullscreen mode

As units are mentioned in your reply, it's also possible to add units to the comment:

if (temperature > 32) { // freezing point Fahrenheit (F)
Enter fullscreen mode Exit fullscreen mode

It's also important to remember that IDEs aren't always used to look at code. Some people might prefer using simple text editors; some people might be reviewing a Pull Request online.

Thread Thread
 
ekeijl profile image
Edwin • Edited

It's not about misinterpreting the comment. If code can be self-documenting (in this case using constants), it should be written that way. It makes comments superfluous. The reality is that people primarily read code and sometimes skip the comments or forget to update comments when refactoring code. Redundant comments that repeat the code 1:1 only waste peoples time.

Furthermore, I'm assuming the context of a professional team that works with this code on a regular basis, where IDEs are the norm. Even VCS services like Github have features to annotate variables/constants and instantly show their definitions, check out their new code search functionality.

Thread Thread
 
ant_f_dev profile image
Anthony Fung • Edited

I think we’ll agree to disagree on this one.

I can see the Clean Code background that this is coming from, and I respect that. I’m not convinced by every principle of Clean Code, but if someone’s coding style fits that of the repository that it’s contributing to (whether it's their own, their company's, or the OS project), that’s all that matters really.

I completely agree on redundant comments, e.g.

// Do work
DoWork();
Enter fullscreen mode Exit fullscreen mode

Is a redundant comment. In the example given, there is only one occurrence of ‘freezing point’. This makes the comment not redundant.

As for professional teams, I’ve been in teams that favour VI over offerings from Microsoft, JetBrains, and others. I could be wrong on this one, but I don’t think VI or the PR diff viewer on GitHub (and PRs will be done often in professional contexts) offers tooltips that show a variable’s declaration/type on mouse-hover. I’m sure in some contexts it’s possible to search/jump to the declaration, but I personally find doing that takes more effort than looking upward/rightward. That said, I appreciate and respect that different people have different optimal workflows.

Thread Thread
 
delacombo profile image
Jasond_Official

"The reality is that people primarily read code and sometimes skip the comments or forget to update comments when refactoring code."

...is not an excuse. If there are comments, you update (or remove) the comments when refactoring. Now, I know things get inherited, but it's all part of being a developer.

Collapse
 
mergerg profile image
Raphael

I'm confused as to how "it communicates the meaning to other developers" doesn't apply to comments. That's literally the point of comments. You can also include the units in the comment, pretty easily.

Collapse
 
developerdoran profile image
Jake Doran

I'd like to advocate that refactoring is an essential part of development and should be a consistent process. However, it is worth noting the risk posed by refactoring code that isn't covered by adequate tests.

That said, one of my favourite patterns (especially on legacy codebases) is return early. It really does help tidy up the code and make it easier to parse and understand 😋

Collapse
 
brense profile image
Rense Bakker

This ☝️

Collapse
 
elijahtrillionz profile image
Elijah Trillionz

Really great read.
Refactoring codes can be a big relief for developers. I feel like most developers don't venture into it because they feel it's complex and time consuming but with articles like this it just proves how simple it is to refactor.
Thanks for sharing

Collapse
 
satriopamungkas profile image
Muhammad Raihan Satrio Putra Pamungkas

Nice article.

Actually, there is a practical approach to determine which code should be refactored. Many people agree that the part of the code that needs to be refactored is called "code smells". The common way is to scan and look for potential code smells, and then proceed to the refactoring step.

In his book, Fowler has categorized several code smells, along with the corresponding refactoring techniques. To find out more, you can check his book "Refactoring".

Additionally, refactoring is also a part of the Test-Driven Development process. There is a step called "red-green-refactor", which I actually like.

Collapse
 
maame-codes profile image
Maame Afia Fordjour

Saving this for later!

Collapse
 
iamspathan profile image
Sohail Pathan

It's inspiring to see how refactoring can help elevate code to a higher level of quality and maintainability. Refactoring is an invaluable tool for every software developer

Collapse
 
surajondev profile image
Suraj Vishwakarma

How you refactor code?

Collapse
 
ekeijl profile image
Edwin
  1. Make a plan. You need to know what parts of the code are affected by your refactoring. For example, if you replace a library by another, you need to identify all the places in your code base where it is used and check if it can be replaced. Which parts have edge cases? Which parts should be done first? Can we skip refactoring parts and remove that whole piece of code alltogether? Having a plan allows you to estimate the time it takes, which you can communicate to your team. If it takes too much time to do a big-bang refactor, split it into smaller tasks and do module-by-module if that makes it easier to manage.
  2. Write tests for features you are refactoring. This part is missing from your article. You need to be sure that you do not change the functionality behind the refactored code. Writing unit/integration/e2e tests before you refactor will give you confidence that nothing will break.
  3. Refactoring should be a part of implementing new functionality, not as dedicated user stories. Please read this article. In short, when doing a planning session for new functionalities, identify parts of existing code that need to be refactored and allot time to clean that up.
Collapse
 
howtomakeaturn profile image
尤川豪 • Edited

only 4 techniques in the article .... ?

where is the 5th technique ... 🥹🥹🥹

Collapse
 
tspuujee profile image
Пүрэвжаргал

5th technique is be do nothing. (:

Collapse
 
bantangzm profile image
Tang Ziming

testing is important if you refactor

Collapse
 
mayronsouza profile image
Mayron_Souza

I'll thank you