Code Smell | Generic Exceptions

Code Smell | Generic Exceptions

August 16, 2024

codequality
refactorit
code-smell
generic-exceptions

In software development, proper error handling is crucial for building robust and maintainable applications. However, one common pitfall that developers may encounter is the use of generic exceptions. This practice can lead to significant challenges in capturing and maintaining errors, ultimately resulting in a codebase that's harder to debug and extend.

What Are Generic Exceptions?

Generic exceptions are exceptions that don't carry specific information about the error that occurred. This often manifests as throwing a simple Error or a custom exception without meaningful context. Here's a basic example:

function performOperation(data: any) {
    if (!data) {
        throw new Error('Something went wrong')
    }
    // Perform the operation
}

In this example, the Error thrown is generic and doesn't provide much insight into what actually went wrong.

Why Avoid Generic Exceptions?

1. Lack of Context

Generic exceptions often lack the context needed to understand the root cause of an issue. When an error is thrown, the goal is to provide as much information as possible to help developers identify and fix the problem.

Example:

throw new Error('Failed to perform operation')

This message doesn't tell you why the operation failed, just that it did. Is it because of invalid input? A network issue? A file not found? The lack of detail forces anyone debugging the issue to spend extra time figuring out what went wrong.

2. Difficulty in Error Handling

When catching generic exceptions, it's difficult to differentiate between different types of errors and handle them accordingly. This often leads to broader catch blocks that don't adequately address specific issues.

Example:

try {
    performOperation(data)
} catch (error) {
    console.error('An error occurred:', error.message)
    // But what type of error was it?
}

In this case, the catch block captures all errors in the same way, without distinguishing between them. This can make it harder to implement appropriate error recovery strategies.

3. Challenges in Maintenance

As the codebase grows, the use of generic exceptions can lead to maintenance challenges. If a method throws a generic exception, anyone working on the code in the future may need to dig through the code to understand what types of errors could be thrown and under what conditions.

This becomes particularly problematic in larger applications where multiple layers of abstraction are involved.

Best Practices for Error Handling

To avoid the pitfalls of generic exceptions, consider the following best practices:

1. Create Specific Error Classes

Define custom error classes that extend the base Error class. These custom classes should include additional context about the error.

Example:

class InvalidDataError extends Error {
    constructor(message: string) {
        super(message)
        this.name = 'InvalidDataError'
    }
}
 
class NetworkError extends Error {
    constructor(
        message: string,
        public statusCode: number
    ) {
        super(message)
        this.name = 'NetworkError'
    }
}
 
function performOperation(data: any) {
    if (!data) {
        throw new InvalidDataError('Data is invalid or missing')
    }
    // Perform operation
}

2. Use Error Objects with Contextual Information

When throwing exceptions, include relevant data that can help diagnose the issue.

Example:

function fetchUserData(userId: string) {
    try {
        // Simulate a network request
        throw new NetworkError('Failed to fetch user data', 404)
    } catch (error) {
        if (error instanceof NetworkError) {
            console.error(
                `Error: ${error.message}, Status Code: ${error.statusCode}`
            )
        } else {
            console.error('An unexpected error occurred')
        }
    }
}

By including the statusCode in the NetworkError, you provide more context, making it easier to handle the error appropriately.

Conclusion

Generic exceptions may seem convenient, but they often lead to more significant problems down the road. By using specific error classes and providing detailed context when throwing exceptions, you make your code easier to debug and maintain. This practice aligns with the principles of clean code and will ultimately result in a more robust and reliable application.

Avoid the smell of generic exceptions and embrace clarity in your error handling strategy.


Thanks for reading me 😊