This article identifies and addresses common mistakes in JavaScript and TypeScript codebases that gradually degrade code quality and increase maintenance costs. It’s the first part of a two-part series, focusing on foundational aspects.
Type Safety
- Lack of Strict Mode: TypeScript’s strict mode flags are crucial for preventing runtime errors. Enabling
strictNullChecksandnoImplicitAnyis highly recommended. - Abuse of
any: Usinganydisables type checking and can propagate unchecked types throughout the codebase. Useunknownand type guards instead. - Improper Discriminated Unions: Discriminated unions help represent impossible states, improving type safety and reducing the need for null checks.
- Missing Explicit Return Types: Explicit return types on exported functions act as contracts, preventing silent breaking changes when the implementation is modified.
Error Handling
- Swallowing Errors: Catch blocks should either rethrow, recover, or transform errors. Logging and continuing without handling the error hides bugs.
- Unhandled Promise Rejections: Promises should always be awaited or have a
.catch()handler to prevent unhandled rejections that can crash the application. - Ignoring the Result Pattern: Use the Result pattern to encode success and failure in the return type, forcing callers to handle potential errors explicitly.
Architecture & Design
- Lack of Dependency Injection: Hardcoded dependencies make unit testing difficult. Use dependency injection to pass in fakes during testing.
- Premature Microservices: Start with a modular monolith to establish clear boundaries before extracting services. Extract to services only when scaling bottlenecks are identified.
- Long Functions: Extract blocks of code into named functions for readability, even if they are not reused.
- Business Logic in Controllers: Controllers should only parse input, call a service, and format output. Business rules should be in separate services.
- Circular Dependencies: Circular imports can lead to runtime errors and difficult-to-debug issues. Break circular dependencies by fixing the dependency direction or extracting shared contracts into a third module.
The article concludes by outlining the topics to be covered in Part 2, including runtime and quality mistakes related to code hygiene, async & performance, and testing & validation.