This guide covers code styles and engineering best practices to maintain the code and architecture as simple as possible while maintaining performance and maintainability.
Immutability First
- Prefer
constoverlet, never usevar - Use immutable data structures and operations (map, filter, reduce)
- Avoid mutating function arguments or external state
- Return new objects/arrays instead of modifying existing ones
Pure Functions
- Functions should always return the same output for the same input
- Avoid side effects (API calls, DOM manipulation, logging) in business logic
- Side effects should be isolated to dedicated functions and clearly marked
- Keep functions small and focused on a single responsibility
Function Composition
- Build complex operations from simple, reusable functions
- Use higher-order functions to create abstractions
- Prefer function composition over class inheritance
Clear and Descriptive Names
- Use verb-noun pattern for functions:
getUserById,calculateTax,validateEmail - Use nouns for classes and objects:
User,OrderProcessor,PaymentGateway - Boolean variables should sound like yes/no questions:
isActive,hasPermission,canEdit - Avoid abbreviations unless universally understood
Consistency
- Use consistent terminology across the codebase
- Follow existing patterns in the project
- Document domain-specific terms in a glossary
- Red: Write a failing test that defines the desired behavior
- Green: Write the minimum code to make the test pass
- Refactor: Improve the code while keeping tests green
Test Structure
- Use the Arrange-Act-Assert (AAA) pattern
- One assertion per test when practical
- Test file names should mirror source files:
user.js→user.test.js
What to Test
- Public interfaces and APIs
- Edge cases and boundary conditions
- Error handling and validation
- Business logic and calculations
- Do not test implementation details
- Do not test third-party libraries
Test Coverage Goals
- Aim for 80%+ coverage on business logic
- 100% coverage on critical paths (payments, security, data integrity)
- Coverage is a guide, not a goal—focus on meaningful tests
Test Isolation
- Each test should be independent and runnable in any order
- Use mocks/stubs for external dependencies
- Reset state between tests
- Avoid test interdependencies
- folder: tests/
Unit Tests (70% of tests)
- Test individual functions and modules in isolation
- Fast execution (milliseconds)
- No external dependencies
Integration Tests (20% of tests)
- Test interactions between modules
- Verify contracts between components
- May use real databases/services in test mode
End-to-End Tests (10% of tests)
- Test complete user workflows
- Run against production-like environment
- Slower but high confidence
Single Responsibility
- Each module/function should have one reason to change
- Separate concerns: business logic, data access, presentation
Dependency Inversion
- Depend on abstractions, not concrete implementations
- Use dependency injection for better testability
Avoid Premature Optimization
- Write clear, simple code first
- Optimize only when performance issues are measured
- Profile before optimizing
Minimize Dependencies
- Evaluate if you really need that library
- Prefer standard library solutions
- Consider bundle size and maintenance burden
Progressive Complexity
- Start with the simplest solution that works
- Add complexity only when requirements demand it
- Refactor when patterns emerge, not before
What to Look For
- Does the code follow TDD (are there tests)?
- Are functions pure and side effects isolated?
- Are names clear and consistent?
- Is the code simple and readable?
- Are edge cases handled?
- Is error handling appropriate?
What to Avoid
- Nitpicking style issues (use automated formatters)
- Asking for changes without clear rationale
- Blocking on personal preferences
- Rewriting code in your own style
Code Should Be Self-Documenting
- Clear names reduce need for comments
- Write comments for "why", not "what"
- Document complex algorithms or business rules
- Keep README files updated with setup and architecture decisions
API Documentation
- Document public interfaces with JSDoc or similar
- Include examples for complex functions
- Document error cases and return types
When to Optimize
- After measuring with profiling tools
- When performance impacts user experience
- When dealing with large datasets or high frequency operations
Functional Performance
- Be mindful of unnecessary array iterations
- Use appropriate data structures (Map, Set vs Arrays)
- Consider lazy evaluation for expensive operations
- Memoize pure functions when appropriate
- Refactor regularly to pay down technical debt
- Review and update this guide as the team learns
- Share knowledge through pair programming and code reviews
- Celebrate when complexity is removed
Remember: Simple code is maintainable code. When in doubt, choose clarity over cleverness.