DEV Community

Cover image for JavaScript Clean Code Mastery: Part 3 - Modern JavaScript Features That Transform Your Code
sizan mahmud0
sizan mahmud0

Posted on

JavaScript Clean Code Mastery: Part 3 - Modern JavaScript Features That Transform Your Code

Welcome Back to Clean Code!

In Part 1, we conquered naming. In Part 2, we mastered functions. Today, we're unleashing the modern JavaScript features that will make your code shorter, cleaner, and more expressive.

I once reviewed code that had 15 lines of defensive null checking:

if (user && user.address && user.address.location && user.address.location.city) {
  console.log(user.address.location.city);
}
Enter fullscreen mode Exit fullscreen mode

With optional chaining, it became one line:

console.log(user?.address?.location?.city);
Enter fullscreen mode Exit fullscreen mode

Today's Arsenal:

  • Destructuring (unpack data cleanly)
  • Template Literals (readable string formatting)
  • Optional Chaining (safe property access)
  • Nullish Coalescing (better defaults)
  • Spread Operator (immutable operations)

Let's dive in!


Practice 1: Use Destructuring for Cleaner Code

The Problem: Accessing nested properties is verbose and repetitive.

❌ Bad: Repetitive Property Access

function displayUser(user) {
  console.log('Name: ' + user.name);
  console.log('Email: ' + user.email);
  console.log('City: ' + user.address.city);
  console.log('Country: ' + user.address.country);

  return {
    fullName: user.name,
    emailAddress: user.email,
    location: user.address.city + ', ' + user.address.country
  };
}

function getCoordinates(point) {
  const x = point[0];
  const y = point[1];
  const z = point[2];
  return { x, y, z };
}
Enter fullscreen mode Exit fullscreen mode

βœ… Good: Destructuring

function displayUser(user) {
  const { name, email, address: { city, country } } = user;

  console.log(`Name: ${name}`);
  console.log(`Email: ${email}`);
  console.log(`City: ${city}`);
  console.log(`Country: ${country}`);

  return {
    fullName: name,
    emailAddress: email,
    location: `${city}, ${country}`
  };
}

function getCoordinates([x, y, z]) {
  return { x, y, z };
}

// Function parameters destructuring with defaults
function createUser({ name, email, age = 18, role = 'user' }) {
  return { name, email, age, role };
}

createUser({ name: 'Alice', email: 'alice@example.com' });
// Returns: { name: 'Alice', email: 'alice@example.com', age: 18, role: 'user' }
Enter fullscreen mode Exit fullscreen mode

Advanced Destructuring Patterns:

// Rename while destructuring
const { name: userName, email: userEmail } = user;

// Rest operator to get remaining properties
const { name, email, ...otherDetails } = user;
console.log(otherDetails);  // { age, address, phone, etc. }

// Array destructuring with skipping
const [first, , third] = [1, 2, 3];  // Skip second element
console.log(first, third);  // 1, 3

// Swap variables
let a = 1, b = 2;
[a, b] = [b, a];  // a is now 2, b is now 1
Enter fullscreen mode Exit fullscreen mode

Practice 2: Use Template Literals for String Formatting

The Problem: String concatenation with + is error-prone and hard to read.

❌ Bad: String Concatenation

const user = { name: 'Alice', age: 25 };

const greeting = 'Hello, ' + user.name + '! You are ' + user.age + ' years old.';

const html = '<div class="user">' +
  '<h2>' + user.name + '</h2>' +
  '<p>Age: ' + user.age + '</p>' +
  '</div>';

const url = 'https://api.example.com/users/' + user.id + '/posts?page=' + page + '&limit=' + limit;
Enter fullscreen mode Exit fullscreen mode

βœ… Good: Template Literals

const user = { name: 'Alice', age: 25 };

const greeting = `Hello, ${user.name}! You are ${user.age} years old.`;

const html = `
  <div class="user">
    <h2>${user.name}</h2>
    <p>Age: ${user.age}</p>
  </div>
`;

const url = `https://api.example.com/users/${user.id}/posts?page=${page}&limit=${limit}`;

// Complex expressions work too!
const message = `You have ${unreadCount > 0 ? `${unreadCount} unread messages` : 'no unread messages'}`;

// Multi-line without weird escaping
const email = `
  Dear ${user.name},

  Thank you for joining our platform!
  Your account has been activated.

  Best regards,
  The Team
`;
Enter fullscreen mode Exit fullscreen mode

Pro Tip: Template literals preserve indentation and line breaksβ€”perfect for HTML/SQL strings.


Practice 3: Use Optional Chaining (?.) to Safely Access Properties

The Problem: Checking for null/undefined creates deeply nested conditions.

❌ Bad: Defensive Null Checks

function getUserCity(user) {
  if (user && user.address && user.address.city) {
    return user.address.city;
  }
  return 'Unknown';
}

function getFirstOrderTotal(user) {
  if (user && user.orders && user.orders.length > 0 && user.orders[0].total) {
    return user.orders[0].total;
  }
  return 0;
}

// Calling methods safely
if (user && user.getProfile && typeof user.getProfile === 'function') {
  const profile = user.getProfile();
}
Enter fullscreen mode Exit fullscreen mode

βœ… Good: Optional Chaining

function getUserCity(user) {
  return user?.address?.city ?? 'Unknown';
}

function getFirstOrderTotal(user) {
  return user?.orders?.[0]?.total ?? 0;
}

// Safely call methods
const profile = user?.getProfile?.();

// Works with arrays
const firstPost = user?.posts?.[0];
const secondComment = user?.posts?.[0]?.comments?.[1];

// Combine with destructuring
const { address: { city, country } = {} } = user ?? {};
Enter fullscreen mode Exit fullscreen mode

How it works:

  • ?. stops evaluation if left side is null or undefined
  • Returns undefined instead of throwing error
  • Can chain indefinitely: a?.b?.c?.d?.e

Practice 4: Use Nullish Coalescing (??) for Better Defaults

The Problem: || operator treats 0, '', false as falsy (often unwanted).

❌ Bad: Using || for Defaults

const config = {
  timeout: 0,        // Valid value!
  retries: null,
  maxItems: '',      // Valid empty string!
  showAds: false     // Valid boolean!
};

const timeout = config.timeout || 5000;  // BUG: 0 is falsy, so becomes 5000!
const retries = config.retries || 3;     // OK: null becomes 3
const maxItems = config.maxItems || 10;  // BUG: '' is falsy, so becomes 10!
const showAds = config.showAds || true;  // BUG: false becomes true!

console.log(timeout);   // 5000 (WRONG!)
console.log(maxItems);  // 10 (WRONG!)
console.log(showAds);   // true (WRONG!)
Enter fullscreen mode Exit fullscreen mode

βœ… Good: Nullish Coalescing

const config = {
  timeout: 0,
  retries: null,
  maxItems: '',
  showAds: false
};

// ?? only checks for null/undefined, not all falsy values
const timeout = config.timeout ?? 5000;  // 0 (correct!)
const retries = config.retries ?? 3;     // 3 (null defaults to 3)
const maxItems = config.maxItems ?? 10;  // '' (correct!)
const showAds = config.showAds ?? true;  // false (correct!)

console.log(timeout);   // 0 βœ“
console.log(maxItems);  // '' βœ“
console.log(showAds);   // false βœ“
Enter fullscreen mode Exit fullscreen mode

Key Differences:
| Operator | Treats as falsy | Use when |
|----------|-----------------|----------|
| \|\| | 0, '', false, null, undefined, NaN | You want to replace ALL falsy values |
| ?? | Only null and undefined | You want to keep 0, '', false |

Real-World Example:

// User preferences with valid falsy values
function getUserSettings(user) {
  return {
    theme: user?.preferences?.theme ?? 'light',
    fontSize: user?.preferences?.fontSize ?? 16,        // 0 is valid!
    showNotifications: user?.preferences?.notifications ?? true,  // false is valid!
    customCSS: user?.preferences?.customCSS ?? '',      // empty string is valid!
  };
}
Enter fullscreen mode Exit fullscreen mode

Practice 5: Use Spread Operator for Immutability

The Problem: Mutating objects/arrays causes bugs and makes debugging hard.

❌ Bad: Mutation

const user = { name: 'Alice', age: 25 };

function updateUserAge(user, age) {
  user.age = age;  // Mutates original object!
  return user;
}

const updatedUser = updateUserAge(user, 26);
console.log(user.age);  // 26 - original was changed! 😱

// Array mutation
const numbers = [1, 2, 3];
numbers.push(4);  // Mutates array
const sorted = numbers.sort();  // Mutates original array!
Enter fullscreen mode Exit fullscreen mode

βœ… Good: Immutable Updates with Spread

const user = { name: 'Alice', age: 25 };

function updateUserAge(user, age) {
  return { ...user, age };  // Returns new object
}

const updatedUser = updateUserAge(user, 26);
console.log(user.age);  // 25 - original unchanged! βœ“

// Immutable array operations
const numbers = [1, 2, 3];
const withNew = [...numbers, 4];        // New array
const sorted = [...numbers].sort();     // Sort copy, not original
const combined = [...arr1, ...arr2];    // Merge arrays

// Object operations
const defaults = { theme: 'dark', fontSize: 16 };
const userPrefs = { fontSize: 18 };
const settings = { ...defaults, ...userPrefs };  // { theme: 'dark', fontSize: 18 }

// Deep updates (nested objects)
const state = {
  user: {
    name: 'Alice',
    address: { city: 'NYC' }
  }
};

const updated = {
  ...state,
  user: {
    ...state.user,
    address: {
      ...state.user.address,
      city: 'LA'
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Why Immutability Matters:

  • Predictable code (no side effects)
  • Easier debugging (can trace state changes)
  • Time-travel debugging possible
  • Essential for React/Redux

Combining Modern Features: Real-World Example

Let's refactor a user profile update function using all these techniques.

Before: Old JavaScript

function updateUserProfile(userId, updates) {
  var user = getUser(userId);

  if (!user) {
    return { error: 'User not found' };
  }

  if (updates.name) {
    user.name = updates.name;
  }

  if (updates.email) {
    user.email = updates.email;
  }

  if (updates.address) {
    if (!user.address) {
      user.address = {};
    }
    if (updates.address.city) {
      user.address.city = updates.address.city;
    }
    if (updates.address.country) {
      user.address.country = updates.address.country;
    }
  }

  var preferences = user.preferences || {};
  if (updates.theme) {
    preferences.theme = updates.theme;
  }
  if (updates.fontSize !== undefined) {
    preferences.fontSize = updates.fontSize;
  }
  user.preferences = preferences;

  saveUser(user);

  return {
    success: true,
    message: 'Profile updated for ' + user.name
  };
}
Enter fullscreen mode Exit fullscreen mode

After: Modern JavaScript

function updateUserProfile(userId, updates) {
  const user = getUser(userId);

  if (!user) {
    return { error: 'User not found' };
  }

  // Immutable update with spread
  const updatedUser = {
    ...user,
    ...updates,
    address: {
      ...user.address,
      ...updates.address
    },
    preferences: {
      theme: updates.theme ?? user?.preferences?.theme ?? 'light',
      fontSize: updates.fontSize ?? user?.preferences?.fontSize ?? 16
    }
  };

  saveUser(updatedUser);

  return {
    success: true,
    message: `Profile updated for ${updatedUser.name}`
  };
}
Enter fullscreen mode Exit fullscreen mode

Improvements:

  • βœ… Used const instead of var
  • βœ… Spread operator for immutable updates
  • βœ… Optional chaining for safe access: user?.preferences?.theme
  • βœ… Nullish coalescing for defaults: fontSize ?? 16
  • βœ… Template literal for message: `Profile updated for ${name}`
  • βœ… 15 lines instead of 35 (57% reduction!)

Modern Features Cheat Sheet

Destructuring:

const { name, age } = user;                    // Object
const [first, second] = array;                 // Array
const { name, ...rest } = user;                // Rest
const { name: userName } = user;               // Rename
Enter fullscreen mode Exit fullscreen mode

Template Literals:

`Hello, ${name}!`                              // Interpolation
`Line 1
Line 2`                                        // Multi-line
Enter fullscreen mode Exit fullscreen mode

Optional Chaining:

user?.address?.city                            // Object
user?.orders?.[0]                              // Array
user?.getProfile?.()                           // Function
Enter fullscreen mode Exit fullscreen mode

Nullish Coalescing:

value ?? defaultValue                          // Only null/undefined
value || defaultValue                          // Any falsy
Enter fullscreen mode Exit fullscreen mode

Spread Operator:

{ ...obj, newProp: value }                     // Copy object
[...array, newItem]                            // Copy array
{ ...defaults, ...overrides }                  // Merge objects
Enter fullscreen mode Exit fullscreen mode

Quick Wins Checklist for Part 3

Modernize your code with these checks:

βœ… Am I repeating object property access? (Use destructuring)
βœ… Am I using + for strings? (Use template literals)
βœ… Do I have nested if checks for null? (Use optional chaining)
βœ… Am I using || for defaults? (Check if ?? is better)
βœ… Am I mutating objects/arrays? (Use spread for immutability)


Part 3 Conclusion: Modern JavaScript = Less Code, More Clarity

These modern features aren't just syntactic sugarβ€”they fundamentally change how we write JavaScript:

Before Modern JS:

  • 50 lines of defensive null checking
  • String concatenation soup
  • Mutation bugs everywhere
  • Verbose object/array operations

After Modern JS:

  • 20 lines of clean, safe code
  • Self-documenting operations
  • Immutable by default
  • Expressive and concise

Browser Support: These features work in all modern browsers (Chrome 80+, Firefox 75+, Safari 13.1+, Edge 80+). For older browsers, use Babel to transpile.

Challenge: Find a function with nested if-else null checks. Refactor it using ?. and ??. Post the before/after line count! πŸ“‰


Coming Up in Part 4: Async Patterns & Error Handling πŸš€

In the next installment, we'll conquer asynchronous JavaScript:

  • Async/Await - Escape callback hell forever
  • Promise.all() - Parallel execution mastery
  • Error Handling - Stop swallowing errors
  • Try/Catch Best Practices - Handle failures gracefully

No more callback pyramids of doom. No more .then().then().then() chains. Just clean, readable async code.


Did modern features blow your mind? πŸ‘ Clap for the spread operator! (It deserves all 50!)

Don't miss async mastery! πŸ”” Follow me - Part 4 drops in 3 days with async magic!

What's your favorite modern feature? πŸ’¬ Comment below! Optional chaining? Nullish coalescing? Spread operator? Let me know!

Spread the knowledge! πŸ“€ Share with your team - everyone should write modern JavaScript!


This is Part 3 of the 7-part "JavaScript Clean Code Mastery" series.

← Part 2: Functions & Arrow Functions | Part 4: Async Patterns & Error Handling β†’

Tags: #JavaScript #ES6 #ModernJavaScript #Destructuring #TemplateString #OptionalChaining #CleanCode #WebDevelopment #Programming

Top comments (0)