Announcing Bito’s free open-source sponsorship program. Apply now

Let AI lead your code reviews

What is Code Smell Detection? A Practical Guide with Examples

Code Smell Detection

Table of Contents

If you’ve ever looked at a piece of code and thought, “This works… but something feels off,” you’ve probably encountered a code smell. Code smells don’t usually break your program, but they silently signal deeper problems — like poor design, unnecessary complexity, or maintainability risks.

This is where code smell detection comes in. By spotting these subtle signs early, you can refactor before the problem grows into a technical debt nightmare.

Before we dive deep, a quick tip: tools like Bito’s AI Code Review Agent can automatically detect code smells during your reviews. Instead of manually hunting for them, you can get AI-powered suggestions on refactoring, maintainability, security, and more — right inside your workflow.

Now, let’s explore how to detect code smells effectively, with real-world coding examples you can relate to.

What is a code smell?

A code smell is a surface indicator that your code might have a deeper issue. Think of it like a “bad odor” in the codebase—it doesn’t crash the system, but it makes maintenance harder.

Common signs of code smells include:

  • Bloated methods with too many responsibilities
  • Duplicate code blocks across the project
  • Overly complex conditional logic
  • Classes that know or do too much

The danger is not immediate failure—it’s the long-term cost of change.

Why code smell detection matters

Many developers underestimate the impact of code smells because their code still works. But here’s why detection matters:

  • Maintainability: Clean code is easier to update without breaking things.
  • Collaboration: New team members understand well-structured code faster.
  • Reliability: Smelly code often hides bugs and security risks.
  • Cost: Fixing smells early is cheaper than dealing with technical debt later.

In short, detection isn’t about nitpicking style—it’s about ensuring your codebase stays healthy over time.

Examples of code smells (and how to detect them)

Let’s make this practical. Here are some common smells and how you can spot them.

1. Long method (before → after)

Smell (long method):

public class OrderService {
    public void processOrder(Order order) {
        // validate
        if (order == null || order.getItems().isEmpty()) {
            throw new IllegalArgumentException("Invalid order");
        }
        // price calculation
        double subtotal = 0;
        for (OrderItem item : order.getItems()) {
            subtotal += item.getPrice() * item.getQuantity();
        }
        double discount = 0;
        if (order.getCustomer().isPremium()) {
            discount = subtotal * 0.10;
        }
        double tax = subtotal * 0.07;
        double total = subtotal - discount + tax;

        // update inventory
        for (OrderItem item : order.getItems()) {
            InventoryService.decreaseStock(item.getSku(), item.getQuantity());
        }

        // send confirmation
        EmailService.send(order.getCustomer().getEmail(), "Order confirmed: " + order.getId());

        // logging
        Logger.log("Processed order " + order.getId() + " total: " + total);
    }
}

Why it’s a smell: method mixes validation, pricing, inventory, email, and logging — hard to test and modify.

Refactor (extract methods):

public class OrderService {
    public void processOrder(Order order) {
        validate(order);
        double total = calculateTotal(order);
        updateInventory(order);
        sendConfirmation(order);
        log(order, total);
    }

    private void validate(Order order) {
        if (order == null || order.getItems().isEmpty()) {
            throw new IllegalArgumentException("Invalid order");
        }
    }

    private double calculateTotal(Order order) {
        double subtotal = order.getItems().stream()
            .mapToDouble(i -> i.getPrice() * i.getQuantity()).sum();
        double discount = order.getCustomer().isPremium() ? subtotal * 0.10 : 0;
        double tax = subtotal * 0.07;
        return subtotal - discount + tax;
    }

    private void updateInventory(Order order) {
        for (OrderItem item : order.getItems()) {
            InventoryService.decreaseStock(item.getSku(), item.getQuantity());
        }
    }

    private void sendConfirmation(Order order) {
        EmailService.send(order.getCustomer().getEmail(), "Order confirmed: " + order.getId());
    }

    private void log(Order order, double total) {
        Logger.log("Processed order " + order.getId() + " total: " + total);
    }
}

Detection tip: long methods that touch many different domains are candidates for Extract Method and unit tests.

2. Duplicated Code (before → after)

Smell (duplicate logic):

public class GeometryUtils {
    public double areaCircle(double radius) {
        return 3.14159 * radius * radius;
    }

    public double surfaceAreaSphere(double radius) {
        return 4 * 3.14159 * radius * radius;
    }
}

Why it’s a smell: magic constant and formula repeated — risk of inconsistency.

Refactor (single source of truth):

public class GeometryUtils {
    private static final double PI = 3.14159;

    public double areaCircle(double radius) {
        return PI * radius * radius;
    }

    public double surfaceAreaSphere(double radius) {
        return 4 * areaCircle(radius);
    }
}

Detection tip: search for identical expressions or constants; consolidate into constants or helper methods.

3. God class (before → after)

Smell (God class):

public class AppManager {
    public void createUser(User u) { /*...*/ }
    public void deleteUser(String id) { /*...*/ }
    public void backupDatabase() { /*...*/ }
    public void sendEmail(String to, String body) { /*...*/ }
    public void generateReports() { /*...*/ }
    public void deploy() { /*...*/ }
}

Why it’s a smell: one class owns unrelated responsibilities—deployment, reporting, user management. Hard to maintain and test.

Refactor (split responsibilities):

public class UserService {
    public void createUser(User u) { /*...*/ }
    public void deleteUser(String id) { /*...*/ }
}

public class EmailService {
    public void sendEmail(String to, String body) { /*...*/ }
}

public class BackupService {
    public void backupDatabase() { /*...*/ }
}

public class ReportService {
    public void generateReports() { /*...*/ }
}

Detection tip: if a class name contains “Manager” or “Service” and methods span domains, split by SRP (Single Responsibility Principle).

4. Large conditional / complex if-else (before → after)

Smell (complex conditional):

public class DiscountCalculator {
    public double getDiscount(Customer c, int daysWithCompany, boolean hasCoupon) {
        if (c.isVIP() && daysWithCompany > 365) {
            return 0.20;
        } else if (c.isVIP() && hasCoupon) {
            return 0.15;
        } else if (!c.isVIP() && daysWithCompany > 365 && hasCoupon) {
            return 0.10;
        } else if (!c.isVIP() && daysWithCompany > 365) {
            return 0.05;
        } else {
            return 0;
        }
    }
}

Why it’s a smell: nested conditions become unreadable and error-prone.

Refactor (use small strategy objects or a map of rules):

public interface DiscountRule {
    double apply(Customer c, int days, boolean coupon);
}

public class VipLongTermRule implements DiscountRule {
    public double apply(Customer c, int days, boolean coupon) {
        if (c.isVIP() && days > 365) return 0.20;
        return 0;
    }
}

// ... other rule implementations ...

public class DiscountCalculator {
    private final List<DiscountRule> rules;

    public DiscountCalculator(List<DiscountRule> rules) {
        this.rules = rules;
    }

    public double getDiscount(Customer c, int days, boolean coupon) {
        return rules.stream()
            .mapToDouble(r -> r.apply(c, days, coupon))
            .max() // choose highest applicable discount
            .orElse(0);
    }
}

Detection tip: long if/else chains or switch blocks usually mean a strategy/policy pattern fits better.

5. Feature envy (object knows too much about another)

Smell (feature envy):

public class InvoicePrinter {
    public void printInvoice(Order order) {
        double total = 0;
        for (OrderItem item : order.getItems()) {
            total += item.getPrice() * item.getQuantity();
        }
        System.out.println("Total: " + total);
    }
}

Why it’s a smell: InvoicePrinter computes order totals — logic belongs to Order.

Refactor (move behavior to the data owner):

public class Order {
    public double total() {
        return items.stream().mapToDouble(i -> i.getPrice() * i.getQuantity()).sum();
    }
}

public class InvoicePrinter {
    public void printInvoice(Order order) {
        System.out.println("Total: " + order.total());
    }
}

Detection tip: if a class frequently accesses another object’s internals to compute values, move the logic to that object.

Manual vs. automated code smell detection

Traditionally, developers rely on:

  • Code reviews → Peer reviews catch smells, but humans can miss subtle issues.
  • Static analysis tools → Tools like SonarQube flag common patterns.
  • Personal experience → Senior developers develop a “smell sense” over time.

But with modern workflows, AI-assisted detection takes this further. Tools like Bito’s AI Code Review Agent don’t just flag smells — they also explain why it’s a problem and suggest fixes, saving hours in review cycles.

How to fix code smells

Finding a code smell is just the first step. The real improvement comes from refactoring—changing the structure of the code without altering its behavior. Here are some proven strategies:

  • Extract method → Break long methods into smaller, well-named methods to make the code easier to read and test.
  • Apply DRY (Don’t Repeat Yourself) → Remove duplicated logic by consolidating it into a single reusable function or constant.
  • Encapsulation → Keep implementation details hidden and expose only what other parts of the code need to know.
  • Single Responsibility Principle (SRP) → Ensure each class or module focuses on one responsibility instead of handling unrelated tasks.
  • Simplify conditionals → Replace long if/else or switch statements with clearer patterns like strategy, polymorphism, or lookup maps.

The goal isn’t to chase “perfect” code. It’s to make code clean, maintainable, and adaptable so your team can work faster and with fewer errors in the long run.

Best practices for staying ahead of code smells

  • ✅ Run regular automated code reviews
  • ✅ Invest in team-wide refactoring culture
  • ✅ Pair junior devs with mentors to build “code smell detection instincts”
  • ✅ Use tools like Bito for AI-powered, real-time feedback

Final thoughts

Code smells may not crash your program, but they silently build up technical debt that slows down every release. Detecting and refactoring them early is one of the smartest investments you can make in your codebase.

With modern AI code review tools, you don’t need to rely on just intuition or manual reviews — you get scalable, consistent code quality checks across your projects.

If you’ve ever struggled with bloated methods, duplicate code, or God classes, now you know: those aren’t just annoyances — they’re code smells. Start detecting them today and keep your codebase healthy for the long run.

Picture of Nisha Kumari

Nisha Kumari

Nisha Kumari, a Founding Engineer at Bito, brings a comprehensive background in software engineering, specializing in Java/J2EE, PHP, HTML, CSS, JavaScript, and web development. Her career highlights include significant roles at Accenture, where she led end-to-end project deliveries and application maintenance, and at PubMatic, where she honed her skills in online advertising and optimization. Nisha's expertise spans across SAP HANA development, project management, and technical specification, making her a versatile and skilled contributor to the tech industry.

Picture of Amar Goel

Amar Goel

Amar is the Co-founder and CEO of Bito. With a background in software engineering and economics, Amar is a serial entrepreneur and has founded multiple companies including the publicly traded PubMatic and Komli Media.

Written by developers for developers

This article was handcrafted with red heart icon by the Bito team.

Latest posts

What is Code Smell Detection? A Practical Guide with Examples

Jira Integration | What Shipped This Week | 09.18.25 

12 Best Replit Alternatives for Developers in 2025

PR and Issue Level Analytics | What Shipped | 09.09.25

How Bito Offers Codebase Aware AI Code Reviews

Top posts

What is Code Smell Detection? A Practical Guide with Examples

Jira Integration | What Shipped This Week | 09.18.25 

12 Best Replit Alternatives for Developers in 2025

PR and Issue Level Analytics | What Shipped | 09.09.25

How Bito Offers Codebase Aware AI Code Reviews

From the blog

The latest industry news, interviews, technologies, and resources.

Code Smell Detection
placeholder bito

What is Code Smell Detection? A Practical Guide with Examples

JIRA Integration
placeholder bito

Jira Integration | What Shipped This Week | 09.18.25 

Best Replit Alternatives for Developers
placeholder bito

12 Best Replit Alternatives for Developers in 2025