DEV Community

Cover image for Take Your Code to The Other Side
Code of Relevancy
Code of Relevancy

Posted on • Edited on

Take Your Code to The Other Side

In today's adventure I will take you to the Other Side of code development. As ONE, we'll explore the art of code refactoring and see how it can revolutionize your codebase. Sounds marvelous!!! After going through this adventure, you'll learn how to transform your code from Good to Great or Great to Next level. You're a developer and it's your responsibility to take your application to new heights of performance, scalability and maintainability.

Take Your Code to The Other Side


As software projects grow, it's common for the codebase to become complex & difficult to manage. At this point, code refactoring comes into light from the darkness. It's the process of improving existing code by restructuring it in a more efficient and effective way without changing its behavior.

Refactoring does not add any new features or functionalities to the software or product. It can improve the quality, readability and maintainability of your codebase. Which makes it easier to work with in the long run.


Why Refactor Your Code?

Why Refactor Your Code

Improved Code Quality:

Refactoring improves code quality by reducing code complexity, removing duplication and improving the overall design of the code. This ends in code that is easier to read and understand, reducing the risk of introducing errors or bugs.

Increased Maintainability:

By reducing code complexity and improving the design of your code, refactoring makes it easier to maintain and update your code in the long term. This can save time and effort for developers working on the project and reduce the risk of introducing bugs when making changes.

Better Performance:

Refactoring can help improve the performance of your code by optimizing algorithms, removing unnecessary code and reducing resource usage. This can lead to faster execution times and improved efficiency.

Easier Collaboration:

By improving code quality and reducing complexity, refactoring can make it easier for developers to collaborate on a project. This can improve team productivity and reduce the risk of introducing errors or bugs when working on shared code.


By moving forward on our adventure, it's time to perform the code refactoring and break on through to the Other Side.


Code Formatting

It's important to consider code formatting when working on a team or maintaining a codebase. Different developers may have varying preferences for indentation, line breaks and quotation marks. That ends in an inconsistent code style. Inconsistent code style can make the code look cluttered and challenging to read. So it's crucial to maintain a consistent code style across the project.

It's beneficial to use refactoring tools such as Prettier. In case you're not familiar with Prettier, let's break it.. Prettier is a popular and user friendly tool that automates code formatting. Once added to the project, it will take care of formatting the code according to pre set style settings. Also you can customize the formatting rules to your preference by creating a .prettierrc file with below configs:

{
  "arrowParens": "always",
  "bracketSameLine": false,
  "bracketSpacing": true,
  "embeddedLanguageFormatting": "auto",
  "htmlWhitespaceSensitivity": "css",
  "insertPragma": false,
  "jsxSingleQuote": false,
  "printWidth": 80,
  "proseWrap": "preserve",
  "quoteProps": "as-needed",
  "requirePragma": false,
  "semi": true,
  "singleAttributePerLine": false,
  "singleQuote": false,
  "tabWidth": 2,
  "trailingComma": "es5",
  "useTabs": false,
  "vueIndentScriptAndStyle": false
}
Enter fullscreen mode Exit fullscreen mode

Class Component Without State or Lifecycle Methods in React

Original code:

import React, { Component } from "react";

class Adventure extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log("Migration happened");
  }

  render() {
    return (
      <div>
        <p>Break on Through To the Other Side</p>
        <button onClick={this.handleClick}>Migrate me</button>
      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Refactored code:

import React from "react";

function Adventure() {
  const handleClick = () => {
    console.log("Migration happened");
  };

  return (
    <div>
      <p>Break on Through To the Other Side</p>
      <button onClick={handleClick}>Migrate me</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

If the component needs state or lifecycle methods then use class component otherwise use function component.

From React 16.8 with the addition of Hooks, you could use state, lifecycle methods and other features that were only available in class component right in your function component. So, it is always recommended to use Function components, unless you need a React functionality whose Function component equivalent is not present yet, like Error Boundaries.


Avoid extra wrapping <div> in React

Original code:

import React from "react";

function Adventure() {
  return (
    <div>
      <p>Break on Through To the Other Side</p>
      <button>Migrate me</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Refactored code:

import React, { Fragment } from "react";

function Adventure() {
  return (
    <Fragment>{/* or use a shorter syntax <> </> */}
      <p>Break on Through To the Other Side</p>
      <button>Migrate me</button>
    </Fragment>
  );
}
Enter fullscreen mode Exit fullscreen mode

It's a common pattern in React which is used for a component to return multiple elements. Fragments let you group a list of children without adding extra nodes to the DOM.

Why fragments are better than container divs?

  • Fragments are a bit faster and use less memory by not creating an extra DOM node. This only has a real benefit on very large and deep trees.
  • Some CSS mechanisms like Flexbox and CSS Grid have a special parent-child relationships and adding divs in the middle makes it hard to keep the desired layout.

Naming Conventions

Naming is a important part of writing clean and maintainable code and it's required to take the time to choose descriptive and meaningful names for your functions and variables. When naming functions and variables, it's important to choose names that are self explanatory and convey the purpose and function of the code.

For React, a component name should always be in a Pascal case like UserDashboard, Dashboard etc.. Using Pascal case for components differentiate it from default JSX element tags.

Methods/functions defined inside components should be in Camel case like getUserData(), showUserData() etc.

For globally used Constant fields in the application, try to use capital letters only. As an instance,

const PI = 3.14;
Enter fullscreen mode Exit fullscreen mode

Remove Unnecessary Comments from the Code

Original code:

import React, { Fragment } from "react";

function Adventure() {
  console.log("test");

  return (
    <Fragment>
      {/* <h1>Adventure</h1> */}
      <p>Break on Through To the Other Side</p>
      <button>Migrate me</button>
    </Fragment>
  );
}
Enter fullscreen mode Exit fullscreen mode

Refactored code:

import React from "react";

function Adventure() {
  return (
    <>
      <p>Break on Through To the Other Side</p>
      <button>Migrate me</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Add comments only where it's required so that you do not get confused while changing code at a later time.

Also don't forget to remove statements like console.log, debugger, unused commented code.


Destructuring Props in React

Destructuring Props in React

Original code:

function Adventure(props) {
  return (
    <>
      <h1>Hello, {props.username}</h1>
      <button>Migrate me</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Refactored code:

function Adventure({ username }) {
  return (
    <>
      <h1>Hello, {username}</h1>
      <button>Migrate me</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Destructuring was introduced in ES6. This type of feature in the JavaScript function allows you to easily extract the form data and assign your variables from the object or array. Also, destructuring props make code cleaner and easier to read.


Respecting the Import Order

Original code:

import { formatCurrency, toNumber } from "@/utils";
import { useDrag } from "react-dnd";

import "./styleB.css";
import { useFormik } from "formik";
import React, { useEffect, useState } from "react";
import Button from "@/components/Button";
import TextField from "@/components/TextField";
import PropTypes from 'prop-types';
Enter fullscreen mode Exit fullscreen mode

Refactored code:

import React, { useEffect, useState } from "react";
import PropTypes from 'prop-types';

import { useDrag } from "react-dnd";
import { useFormik } from "formik";

import { formatCurrency, toNumber } from "@/utils";

import Button from "@/components/Button";
import TextField from "@/components/TextField";

import "./styleB.css";
Enter fullscreen mode Exit fullscreen mode

Organizing imports is a required part of writing clean and maintainable React code.

Import orders:

  1. React import
  2. Library imports (Alphabetical order)
  3. Absolute imports from the project (Alphabetical order)
  4. Relative imports (Alphabetical order)
  5. Import * as
  6. Import β€˜./.

Each kind should be separated by an empty line. This makes your imports clean and easy to understand for all the components, 3rd-party libraries and etc.


Split your code into multiple smaller functions. Each with a single responsibility.

One of the key principles of writing clean and maintainable code is to keep functions and components small and focused. This means splitting your code into multiple smaller functions, each with a single responsibility.

When a function or component has too many responsibilities, it becomes harder to read, understand and maintain. By breaking it down into smaller, more focused functions, you can improve the readability and maintainability of your code.

To show you what I mean:
Split your code into multiple smaller functions


Don't Repeat Yourself

Don't Repeat Yourself (DRY) is a fundamental principle of software development. Which encourages developers to avoid duplicating code or logic throughout their codebase. The DRY principle is a required aspect of writing maintainable and scalable code and it applies to both coding standards and code refactoring.

In terms of coding standards, the DRY principle suggests that you should avoid duplicating code or logic in your codebase. This means that you should strive to write reusable code that can be used in multiple places in your application. As an instance, instead of writing the same validation logic for every input field in your application, you could write a reusable function that performs the validation and use it in each input field.


It's Highly Recommended to Avoid Arrow Functions in Render

One way to improve the performance of your components is to avoid using arrow functions in the render() method.

What's the problem with below instance?
Every time a component is rendered, a new instance of the function is created. Actually, it's not a big deal in case the component is rendered one or two times. But in other cases, it can affect performance. Arrow functions in the render() method can cause unnecessary re renders of your components.

Original code:

function Adventure() {
  const [message, setMessage] = useState("Migration not started");

  return (
    <>
      <h1>Mesage: {message}</h1>
      <button onClick={() => setMessage("Migration happened")}>
        Migrate me
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

So if you care about performance, declare the function before using it in render as below instance:

Refactored code:

function Adventure() {
  const [message, setMessage] = useState("Migration not started");

  const onMessage = () => {
    setMessage("Migration happened");
  };

  return (
    <>
      <h1>Mesage: {message}</h1>
      <button onClick={onMessage}>Migrate me</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Instead of using arrow functions in the render() method, you can declare methods as instance methods outside of the render() method. This ensures that the method is only created once and is not recreated on each render.


Decrease React Bundle Size

When you're using third-party libraries in your React app, it's very important to keep the bundle size as small as possible. A large bundle size can negatively impact the performance of your application, especially on slower internet connections.

One way to reduce the bundle size is to only load the parts of the library that you actually need. Instead of importing the entire library, you can import only the specific method that you need using a technique called tree shaking. This can significantly reduce the size of your bundle.

Let's say you're using the popular library Lodash and you only need the pick() method to pick certain properties from an object. Instead of importing the entire Lodash library, you can import only the pick() method as below:

import pick from 'lodash/pick';

const pickedUserProps = pick(userProps, ['name', 'email']);
Enter fullscreen mode Exit fullscreen mode

instead of

import lodash form 'lodash';

const pickedUserProps = lodash.pick(userProps, ['name', 'email']);
Enter fullscreen mode Exit fullscreen mode

Use useMemo to cache expensive calculations

When building a React app, it's common to perform calculations or generate data that can be computationally expensive. This can negatively impact the performance of your app, especially if these calculations are performed frequently or in large amounts.

To kill the performance issues of your app, you can use the useMemo hook in React to cache expensive calculations. useMemo is a built in React hook that memoizes the result of a function, so that it is only recomputed when its dependencies have changed.

Let's say you have a component that generates a list of items based on some data that is fetched from an API. The list is generated using a function that performs some expensive calculations:

function generateList(data) {
  // Perform some expensive calculations here
  // ...
  return list;
}

function Adventure() {
  const [data, setData] = useState([]);

  useEffect(() => {
    // Fetch data from API
    // ...
    setData(data);
  }, []);

  const list = generateList(data);

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

With the above instance, the generateList function is called every time the component re-renders, even if the data hasn't changed. This can result in unnecessary computations and slow down the performance of your application.

To optimize this, you can use the useMemo hook to cache the result of generateList:

function Adventure() {
  const [data, setData] = useState([]);

  useEffect(() => {
    // Fetch data from API
    // ...
    setData(data);
  }, []);

  const list = useMemo(() => generateList(data), [data]);

  // ...
}
Enter fullscreen mode Exit fullscreen mode

Remove too many if-else conditions

If-else statements are a common feature in most programming languages and they are often used to control the flow of code execution based on certain conditions. Though, too many if-else statements can make your code difficult to read, maintain and debug..

One approach to removing too many if-else conditions is to use the "switch" statement. The switch statement is similar to an if-else statement, but it is designed to handle multiple cases in a more concise and efficient way. Let's see an instance:

function checkGrade(grade) {
  if (grade >= 90) {
    return "A";
  } else if (grade >= 80) {
    return "B";
  } else if (grade >= 70) {
    return "C";
  } else if (grade >= 60) {
    return "D";
  } else {
    return "F";
  }
}
Enter fullscreen mode Exit fullscreen mode

In above instance, we are using an if-else statement to check the grade of a student and return a letter grade. This code can be refactored using the switch statement:

function checkGrade(grade) {
  switch (true) {
    case (grade >= 90):
      return "A";
    case (grade >= 80):
      return "B";
    case (grade >= 70):
      return "C";
    case (grade >= 60):
      return "D";
    default:
      return "F";
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the switch statement is more concise and easier to read than the if-else statement. It also allows us to handle multiple cases in a more efficient way.

Another approach to removing too many if-else conditions is to use object literals. Object literals are a way of defining key and value pairs in JavaScript.

To show you what I mean:

function getAnimalSound(animal) {
  if (animal === "dog") {
    return "woof";
  } else if (animal === "cat") {
    return "meow";
  } else if (animal === "cow") {
    return "moo";
  } else {
    return "unknown";
  }
}
Enter fullscreen mode Exit fullscreen mode

In above instance, we are using an if-else statement to return the sound of an animal. This code can be refactored using object literals:

function getAnimalSound(animal) {
  const animalSounds = {
    dog: "woof",
    cat: "meow",
    cow: "moo"
  }
  return animalSounds[animal] || "unknown";
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the object literals approach is more concise and easier to read than the if-else and switch statement. It also allows us to define key and value pairs in a more flexible way..


End of an adventure

Code refactoring is all about breaking through the limitations of your current code and pushing towards a better, more efficient and effective version. It can be a daunting task, but with the right mindset, tools and best practices, you can take your code to the Other Side.

Code refactoring is not just about improving the quality of your code, but also about improving the overall user experience of your app. By putting in the effort to refactor your code, you can create a more responsive, intuitive and engaging app that users will love.

So, don't be afraid to break through to the other side. Hug the process of code refactoring and watch as your app rises to new heights..


Motivation

Motivation


πŸ€Support

Please consider following and supporting us by subscribing to our channel. Your support is greatly appreciated and will help us continue creating content for you to enjoy. Thank you in advance for your support!

YouTube
Discord
GitHub

Thank you in advance for your support

Top comments (27)

Collapse
 
thexdev profile image
M. Akbar Nugroho

Agree with the component should use PascalCase, but not with the file name. For the file name, I prefer KCD suggestion. He use lower-kebab-case for universal use OS.

So, you have component MagicButton and filename is magic-button.tsx. This will ensure you have no filename insensitive problem.

I have experienced coding on Mac, no problem happened. But when I pull the code from github and running on Linux, I forget to rename the import component from "Button" to "button" and the entire file that importing that component thow an error.

Collapse
 
codeofrelevancy profile image
Code of Relevancy

Thank you for your valuable feedback. Anyway, I prefer to use the PascalCase for the file name and Component Name. I don't think it can create any problems while coding on Mac, Linux or Windows.

Instances:

import Typography from "@material-ui/core/Typography"; 
import Navigation from "../components/Navigation";
Enter fullscreen mode Exit fullscreen mode

PascalCase for filenames is also recommended by Airbnb' React/JSX Style Guide.

React

Collapse
 
thexdev profile image
M. Akbar Nugroho

Yeah, this is a rare case. Usually it happens when you importing a file.

Imagine you have a component Navigation.tsx and import it like this:

import Navigation from 'components/navigation';
Enter fullscreen mode Exit fullscreen mode

On windows and Mac it shouldn't throw an error because they don't care about "case-sensitive".

The thing why this metter is when you use CI tools for example GitHub actions which use Linux based OS (you can also use windows or mac) for doing automated-test.

Linux cares about case-sensitive and of course the code above should make your actions fail.

Another case is when you use Vercel, Netlify, etc which provide CI/CD and of course their runner is use Linux based OS.

IMHO, this is metter because I don't want to lost a time because of simple problem and for me style guide is only a style guide. Like a cookbook. Meaning you can adjust it.

And the most important thing is to document your changes. Maybe you already work in CamelCase project, but you want another one. This is important for next devs, so they know "what happens" to the codebase.

Thread Thread
 
codeofrelevancy profile image
Code of Relevancy

Interesting, thank you for sharing detailed info..

Collapse
 
msmello_ profile image
Matheus Mello

I really like it, some things that worth mentioning:
Typescript and the usage of common variables.

enum Migrations {
"Migration not started",
"Migration happened"
}

function Adventure() {
  const [message, setMessage] = useState<keyof typeof Migrations>(Migrations."Migration not started");

  const onMessage = () => {
    setMessage(Migrations."Migration happened");
  };

  return (
    <>
      <h1>Message: {message}</h1>
      <button onClick={onMessage}>Migrate me</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Maybe also exporting all the components from a common index file.

Instead of:

import Button from "@/components/Button";
import TextField from "@/components/TextField";
Enter fullscreen mode Exit fullscreen mode

Something like this:

import {Button, TextField} from "@/components";
Enter fullscreen mode Exit fullscreen mode
Collapse
 
codeofrelevancy profile image
Code of Relevancy

Yes its looks good to me. There are so many ways we can go with it for the code refactoring.

I really like your approach to import the components:

import {Button, TextField} from "@/components";
Enter fullscreen mode Exit fullscreen mode

Thank you for sharing your insights..

Collapse
 
ant_f_dev profile image
Anthony Fung

A really good write-up!

For removing comments, I think this is a really good idea. I see lots of commented code too often. People usually say it's there just in case; it'll also be in source control.

For if statements, I generally stick to the rule of using if with up to three branches. After that, I'll either use switch, or the dictionary/object approach. One other point that might not be obvious is that it's possible to put arrow functions in there to run. This might be useful if implementing some type of command interpreter.

Collapse
 
codeofrelevancy profile image
Code of Relevancy

I completely agree with you. Thanks for sharing your insights.. Happy coding!!!

Collapse
 
wadecodez profile image
Wade Zimmerman

a lot of these suggestions are nothing more than preferred syntax. wouldn't bother messing with most of these because a senior programmer will just overrule you. sometimes it just isn't making code "more readable"

Collapse
 
codeofrelevancy profile image
Code of Relevancy

I completely agree with you. I had showed basics of code refactor. There are millions ways a developer can approach it.. Starting with basics, eventually we will end as a "Pro Developer". It's important to maintain consistency in while coding.. THnak you for reading and your valuable feedback..

Collapse
 
codeofrelevancy profile image
Code of Relevancy

Very excited to announce that we have reached 1K followers in our community. We couldn't have done it without your support and engagement. We want to thank each and every one of you for being a part of our adventure..

We look forward to continuing to grow together & providing even more value to our community.

Happy coding!!!

Collapse
 
fruntend profile image
fruntend

Π‘ongratulations πŸ₯³! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up πŸ‘

Collapse
 
codeofrelevancy profile image
Code of Relevancy

Thanks a ton man

Collapse
 
opendataanalytics profile image
The Open Coder

Such a informative aritcle! This does make me to reconsider my own coding practices. Good work!

Collapse
 
codeofrelevancy profile image
Code of Relevancy

Glad, you enjoyed the trip of Other Side of code development. Thank you for reading and feedback.

Collapse
 
kennedykeinstein profile image
KENNEDY

Just one word! Thank You

Collapse
 
codeofrelevancy profile image
Code of Relevancy

You're welcome my friend. I hope my article helped you to break on through the Other Side of code development. Thank you..

Collapse
 
msmello_ profile image
Matheus Mello

Really nice post CR, you are becoming an expert buddy. πŸš€

Collapse
 
codeofrelevancy profile image
Code of Relevancy

Thanks for your support, it means a lot! πŸ™πŸΌLet's keep pushing forward & reaching towards the stars πŸš€

Collapse
 
excalith profile image
Can Cellek

Great article, key concepts explained beautifully!

Collapse
 
codeofrelevancy profile image
Code of Relevancy

Thanks a million..