Common Code Smells
Common code smells to watch for during code reviews with examples and fixes.
Long Methods
Problem: Methods that do too many things are hard to understand, test, and maintain.
1// β Bad: 200-line method doing everything
2function processOrder(order) {
3 // validate order
4 if (!order.id) throw new Error("Invalid order");
5 if (!order.items || order.items.length === 0) throw new Error("No items");
6
7 // calculate totals
8 let subtotal = 0;
9 for (const item of order.items) {
10 subtotal += item.price * item.quantity;
11 }
12 const tax = subtotal * 0.08;
13 const shipping = subtotal > 100 ? 0 : 10;
14 const total = subtotal + tax + shipping;
15
16 // update database
17 db.orders.update(order.id, { total, status: 'processing' });
18
19 // send email
20 emailService.send(order.email, 'Order Confirmation', `Total: $${total}`);
21
22 // log
23 logger.info(`Order ${order.id} processed`);
24
25 // ... 180 more lines
26}
27
28// β
Good: Broken into smaller, focused functions
29function processOrder(order) {
30 validateOrder(order);
31 const total = calculateTotal(order);
32 updateDatabase(order, total);
33 sendConfirmationEmail(order, total);
34 logOrderProcessed(order);
35}
36
37function validateOrder(order) {
38 if (!order.id) throw new Error("Invalid order");
39 if (!order.items || order.items.length === 0) throw new Error("No items");
40}
41
42function calculateTotal(order) {
43 const subtotal = order.items.reduce((sum, item) =>
44 sum + (item.price * item.quantity), 0);
45 const tax = subtotal * TAX_RATE;
46 const shipping = subtotal > FREE_SHIPPING_THRESHOLD ? 0 : SHIPPING_COST;
47 return subtotal + tax + shipping;
48}
Rule of Thumb: If a function is longer than your screen, it's probably too long.
Too Many Parameters
Problem: Functions with many parameters are hard to call, test, and remember.
1// β Bad: 7 parameters
2function createUser(
3 name: string,
4 email: string,
5 age: number,
6 address: string,
7 phone: string,
8 role: string,
9 department: string
10) {
11 // ...
12}
13
14// Calling it is error-prone
15createUser("John", "john@example.com", 30, "123 Main St", "555-1234", "admin", "IT");
16
17// β
Good: Use object parameter
18interface UserData {
19 name: string;
20 email: string;
21 age: number;
22 address: string;
23 phone: string;
24 role: string;
25 department: string;
26}
27
28function createUser(userData: UserData) {
29 // ...
30}
31
32// Much clearer
33createUser({
34 name: "John",
35 email: "john@example.com",
36 age: 30,
37 address: "123 Main St",
38 phone: "555-1234",
39 role: "admin",
40 department: "IT"
41});
Rule of Thumb: More than 3 parameters? Consider using an object.
God Objects
Problem: Classes that know too much or do too much violate Single Responsibility Principle.
1// β Bad: One class doing everything
2class OrderManager {
3 validateOrder(order) {}
4 calculateTotal(order) {}
5 calculateTax(amount) {}
6 calculateShipping(order) {}
7 processPayment(order) {}
8 chargeCard(card, amount) {}
9 refundPayment(orderId) {}
10 sendEmail(to, subject, body) {}
11 sendSMS(phone, message) {}
12 generateInvoice(order) {}
13 generatePDF(invoice) {}
14 updateInventory(items) {}
15 checkStock(itemId) {}
16 reserveStock(items) {}
17 logTransaction(order) {}
18 logError(error) {}
19 // ... 50 more methods
20}
21
22// β
Good: Separate responsibilities
23class OrderValidator {
24 validate(order) {}
25}
26
27class OrderCalculator {
28 calculateTotal(order) {}
29 calculateTax(amount) {}
30 calculateShipping(order) {}
31}
32
33class PaymentProcessor {
34 processPayment(order) {}
35 chargeCard(card, amount) {}
36 refundPayment(orderId) {}
37}
38
39class NotificationService {
40 sendEmail(to, subject, body) {}
41 sendSMS(phone, message) {}
42}
43
44class InvoiceGenerator {
45 generate(order) {}
46 generatePDF(invoice) {}
47}
48
49class InventoryManager {
50 updateInventory(items) {}
51 checkStock(itemId) {}
52 reserveStock(items) {}
53}
54
55class TransactionLogger {
56 logTransaction(order) {}
57 logError(error) {}
58}
Rule of Thumb: If your class has more than 10 methods, it's probably doing too much.
Magic Numbers
Problem: Unexplained numbers in code make it hard to understand and maintain.
1// β Bad
2if (user.age > 18 && user.score > 100 && user.accountAge > 365) {
3 grantPremiumAccess(user);
4}
5
6// What do these numbers mean?
7const discount = price * 0.15;
8const shipping = weight > 5 ? 25 : 10;
9
10// β
Good: Named constants
11const LEGAL_AGE = 18;
12const MINIMUM_SCORE = 100;
13const DAYS_IN_YEAR = 365;
14const PREMIUM_DISCOUNT_RATE = 0.15;
15const HEAVY_PACKAGE_THRESHOLD_KG = 5;
16const STANDARD_SHIPPING_COST = 10;
17const HEAVY_SHIPPING_COST = 25;
18
19if (user.age > LEGAL_AGE &&
20 user.score > MINIMUM_SCORE &&
21 user.accountAge > DAYS_IN_YEAR) {
22 grantPremiumAccess(user);
23}
24
25const discount = price * PREMIUM_DISCOUNT_RATE;
26const shipping = weight > HEAVY_PACKAGE_THRESHOLD_KG
27 ? HEAVY_SHIPPING_COST
28 : STANDARD_SHIPPING_COST;
Rule of Thumb: Any number other than 0, 1, or -1 should probably be a named constant.
Nested Conditionals
Problem: Deep nesting makes code hard to read and reason about.
1// β Bad: Arrow of doom
2if (user) {
3 if (user.isActive) {
4 if (user.hasPermission('write')) {
5 if (user.balance > 0) {
6 if (!user.isSuspended) {
7 // do something
8 return result;
9 }
10 }
11 }
12 }
13}
14
15// β
Good: Early returns (guard clauses)
16if (!user) return;
17if (!user.isActive) return;
18if (!user.hasPermission('write')) return;
19if (user.balance <= 0) return;
20if (user.isSuspended) return;
21
22// do something
23return result;
24
25// β
Also good: Extract to function
26function canUserWrite(user) {
27 return user &&
28 user.isActive &&
29 user.hasPermission('write') &&
30 user.balance > 0 &&
31 !user.isSuspended;
32}
33
34if (canUserWrite(user)) {
35 // do something
36 return result;
37}
Rule of Thumb: More than 2 levels of nesting? Refactor with early returns or extract functions.
Commented-Out Code
Problem: Dead code clutters the codebase and creates confusion.
1// β Bad
2function processData(data) {
3 // const oldWay = data.map(x => x * 2);
4 // return oldWay.filter(x => x > 10);
5
6 // const anotherWay = data.filter(x => x > 5).map(x => x * 2);
7
8 return data.map(x => x * 2).filter(x => x > 10);
9}
10
11// β
Good: Remove it (it's in git history if you need it)
12function processData(data) {
13 return data.map(x => x * 2).filter(x => x > 10);
14}
Rule of Thumb: Delete commented-out code. If you need it later, use git history.
Inconsistent Naming
Problem: Inconsistent names make code harder to understand and search.
1// β Bad: Inconsistent naming
2const usr = getUser();
3const userInfo = getUserData();
4const u = fetchUserDetails();
5const user_profile = loadUserProfile();
6
7function get_user_by_id(id) {}
8function fetchUserByEmail(email) {}
9function UserByName(name) {}
10
11// β
Good: Consistent naming
12const user = getUser();
13const userDetails = getUserDetails();
14const userProfile = getUserProfile();
15const userSettings = getUserSettings();
16
17function getUserById(id) {}
18function getUserByEmail(email) {}
19function getUserByName(name) {}
Rule of Thumb: Pick a naming convention and stick to it throughout the project.
Boolean Parameters
Problem: Boolean parameters make function calls unclear.
1// β Bad: What does true mean here?
2sendEmail(user, true);
3processOrder(order, false, true);
4createUser(data, true, false, true);
5
6// β
Good: Use options object or separate functions
7interface EmailOptions {
8 urgent: boolean;
9}
10
11sendEmail(user, { urgent: true });
12
13// Or use separate functions
14sendUrgentEmail(user);
15sendRegularEmail(user);
16
17// Or use enums
18enum Priority {
19 URGENT,
20 NORMAL
21}
22
23sendEmail(user, Priority.URGENT);
Rule of Thumb: Avoid boolean parameters. Use options objects or enums instead.
Primitive Obsession
Problem: Using primitives instead of small objects loses type safety and meaning.
1// β Bad: Primitives everywhere
2function createUser(
3 email: string,
4 phone: string,
5 zipCode: string
6) {
7 // Easy to mix up parameters
8 // No validation
9}
10
11createUser("555-1234", "john@example.com", "12345"); // Oops, wrong order!
12
13// β
Good: Use value objects
14class Email {
15 constructor(private value: string) {
16 if (!this.isValid(value)) {
17 throw new Error("Invalid email");
18 }
19 }
20
21 private isValid(email: string): boolean {
22 return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
23 }
24
25 toString(): string {
26 return this.value;
27 }
28}
29
30class PhoneNumber {
31 constructor(private value: string) {
32 if (!this.isValid(value)) {
33 throw new Error("Invalid phone number");
34 }
35 }
36
37 private isValid(phone: string): boolean {
38 return /^\d{3}-\d{3}-\d{4}$/.test(phone);
39 }
40
41 toString(): string {
42 return this.value;
43 }
44}
45
46function createUser(
47 email: Email,
48 phone: PhoneNumber,
49 zipCode: ZipCode
50) {
51 // Type safe, validated, clear intent
52}
53
54// Can't mix up the order - compiler error!
55createUser(
56 new Email("john@example.com"),
57 new PhoneNumber("555-1234"),
58 new ZipCode("12345")
59);
Rule of Thumb: If a primitive has validation rules or behavior, make it a class.
Long Parameter Lists in Constructors
Problem: Classes with many constructor parameters are hard to instantiate.
1// β Bad
2class User {
3 constructor(
4 name: string,
5 email: string,
6 age: number,
7 address: string,
8 phone: string,
9 role: string,
10 department: string,
11 manager: string
12 ) {
13 // ...
14 }
15}
16
17// β
Good: Use builder pattern
18class UserBuilder {
19 private name: string;
20 private email: string;
21 private age: number;
22 private address: string;
23 private phone: string;
24 private role: string;
25 private department: string;
26 private manager: string;
27
28 setName(name: string): this {
29 this.name = name;
30 return this;
31 }
32
33 setEmail(email: string): this {
34 this.email = email;
35 return this;
36 }
37
38 // ... other setters
39
40 build(): User {
41 return new User(this);
42 }
43}
44
45const user = new UserBuilder()
46 .setName("John")
47 .setEmail("john@example.com")
48 .setAge(30)
49 .build();
Rule of Thumb: More than 3 constructor parameters? Consider builder pattern or options object.
Feature Envy
Problem: A method that uses more features of another class than its own.
1// β Bad: OrderProcessor is envious of Order's data
2class OrderProcessor {
3 calculateTotal(order: Order) {
4 let total = 0;
5 for (const item of order.items) {
6 total += item.price * item.quantity;
7 }
8 const tax = total * order.taxRate;
9 const shipping = order.shippingCost;
10 return total + tax + shipping;
11 }
12}
13
14// β
Good: Move the method to where the data is
15class Order {
16 items: Item[];
17 taxRate: number;
18 shippingCost: number;
19
20 calculateTotal(): number {
21 const subtotal = this.items.reduce(
22 (sum, item) => sum + (item.price * item.quantity),
23 0
24 );
25 const tax = subtotal * this.taxRate;
26 return subtotal + tax + this.shippingCost;
27 }
28}
Rule of Thumb: Methods should primarily use data from their own class.
Quick Reference
Immediate Red Flags
- π© Functions > 50 lines
- π© Classes > 500 lines
- π© More than 3 levels of nesting
- π© More than 5 parameters
- π© Commented-out code
- π© Magic numbers
- π© Inconsistent naming
- π© No error handling
Refactoring Priorities
- Security issues - Fix immediately
- Bugs - Fix before merge
- Code smells - Fix if time permits or create tech debt ticket
- Style issues - Use automated tools (linters)
Related Snippets
- C/C++ Code Smells
Common code smells in C/C++ and how to fix them. Memory Leaks 1// β Bad 2void β¦ - C/C++ Secure Coding
Secure coding practices for C/C++ applications. Buffer Overflow Prevention 1// β β¦ - C/C++ Vulnerability Checks
Tools for checking vulnerabilities in C/C++ code. Valgrind 1# Install 2sudo apt β¦ - Common Antipatterns
Common software antipatterns to avoid across all languages and architectures. β¦ - Developer Pre-Submission Checklist
Comprehensive checklist for developers before submitting a pull request. Code β¦ - Go Code Smells
Common code smells in Go and how to fix them. Ignoring Errors 1// β Bad 2result, β¦ - Go Secure Coding
Secure coding practices for Go applications. SQL Injection Prevention 1// β β¦ - Go Vulnerability Checks
Tools for checking vulnerabilities in Go code. Govulncheck 1# Install 2go β¦ - Haskell Code Smells
Common code smells in Haskell and how to fix them. Partial Functions 1-- β Bad: β¦ - Haskell Secure Coding
Secure coding practices for Haskell applications. SQL Injection Prevention 1-- β β¦ - Haskell Vulnerability Checks
Tools for checking vulnerabilities in Haskell code. Cabal Outdated 1# Check β¦ - Python Code Smells
Common code smells in Python and how to fix them. Mutable Default Arguments 1# β β¦ - Python Secure Coding
Secure coding practices for Python applications. SQL Injection Prevention 1# β β¦ - Python Vulnerability Checks
Tools for checking vulnerabilities in Python code. Safety - Dependency Scanner β¦ - Reviewer Checklist
Comprehensive checklist for code reviewers to ensure thorough and constructive β¦ - Rust Code Smells
Common code smells in Rust and how to fix them. Unwrap/Expect Abuse 1// β Bad β¦ - Rust Secure Coding
Secure coding practices for Rust applications. SQL Injection Prevention 1// β β¦ - Rust Vulnerability Checks
Tools for checking vulnerabilities in Rust code. Cargo Audit 1# Install 2cargo β¦ - TypeScript Code Smells
Common code smells in TypeScript and how to fix them. Using any 1// β Bad β¦ - TypeScript Secure Coding
Secure coding practices for TypeScript applications. XSS Prevention 1// β β¦ - TypeScript Vulnerability Checks
Tools for checking vulnerabilities in TypeScript/JavaScript code. npm audit 1# β¦