Draft
Clean Code Principles
Clean code is code that is readable, maintainable, and easy to understand. Writing clean code helps avoid "spaghetti systems" that become too complex and unmaintainable.
Remember, nobody writes clean code at first. First, you make it work, then you make it clean, and finally, you optimize it for performance.
Meaningful Names
- Use clear, descriptive names for variables, functions, and classes. Good names reveal intent, making the purpose of code obvious.
- Avoid ambiguous or generic names like
temp,data, orstuff. - Avoid overly long or detailed names that are cumbersome to read, like
getDatabaseConnectionWithRetryMechanismOnFailure(). - For local variables in short functions, concise names like
ifor an index can be appropriate. - Guideline: Use verbs for function names (e.g.,
calculateTotal,sendEmail) and nouns for classes or objects (e.g.,Invoice,User).
Single Responsibility Principle (SRP)
- Each function, class, or module should have one responsibility or purpose, making the code modular and easier to test.
- Define "one purpose" in terms of business logic. For example, "processing a payment" is a single responsibility, while "processing and logging payments" is not.
- Avoid: Splitting responsibilities too finely, leading to "class explosion" or overly fragmented code.
- When to Relax: If a function is already clear and manageable, slight overlaps in responsibility may be acceptable.
Don't Repeat Yourself (DRY)
- Reuse code where possible. Refactor repeated logic into reusable functions or modules to reduce bugs and simplify maintenance.
- Avoid: Over-abstraction of small repeated snippets. Forcing reuse in unrelated contexts can make code harder to read and maintain.
- When to Relax: Repeating simple lines of code is fine if consolidating them would obscure intent or create unnecessary complexity.
Keep It Simple, Stupid (KISS)
- Strive for simplicity. Avoid overengineering or adding unnecessary complexity.
- Avoid premature optimization; write clear code first, then optimize only when necessary.
- Avoid: Oversimplification that results in generic code too inflexible to meet real-world requirements.
- Guideline: Some functions and processes are inherently complex. When simplicity isn't possible, prioritize clarity.
Encapsulate Conditionals
- Simplify complex conditionals by encapsulating them into well-named functions (e.g.,
isEligibleForDiscount()). - Avoid deeply nested conditionals; handle errors and exit early when possible.
- Avoid: Encapsulating trivial conditionals into functions, as this can make the logic harder to follow.
- When to Relax: Simple conditionals may be clearer inline than hidden in a function.
Error Handling and Exceptions
- Handle errors explicitly and gracefully. Use exceptions for exceptional cases, not regular control flow.
- Avoid: Overusing
try-catchblocks for every operation, which can clutter code and mask real issues. - When to Relax: In high-performance systems, it may be more efficient to handle errors at a higher level.
- Clarification: An error is an unexpected situation in normal operations, while an exception is a specific object or mechanism used to handle such errors.
Comments and Documentation
- Write comments to explain the why, not the what. Well-structured code should largely explain itself.
- Avoid: Over-commenting obvious code, which creates noise and may become outdated.
- When to Relax: Use comments for complex or unusual logic, or to clarify intent that naming conventions alone cannot express.
Favor Composition over Inheritance
- Use composition (combining existing components) instead of inheritance for flexibility and reduced coupling.
- Avoid: Excessive composition that results in too many small classes, obscuring the overall logic.
- When to Relax: Inheritance is suitable when a subclass is a type of the superclass, like
Doginheriting fromAnimal.
Limit Side Effects and Mutations
- Prefer pure functions (no side effects) for computational logic. Avoid changing states outside a function's scope unless intentional.
- Avoid: Over-avoiding state changes, which can lead to overly complex functionalized code in imperative programming.
- When to Relax: Functions interacting with external systems (e.g., logging, saving data) naturally have side effects.
Consistent Formatting
- Follow a consistent style guide for indentation, naming conventions, and spacing across the codebase.
- Avoid: Rigid adherence to style rules that reduces readability or efficiency in specific contexts.
- When to Relax: Minor deviations that improve clarity or meet contextual needs are acceptable.
Testing
- Focus tests on critical functionality and complex scenarios. Unit tests ensure parts of the code work independently.
- Avoid: Pursuing 100% test coverage at the cost of testing trivial code or creating high maintenance overhead.
- When to Relax: Skip testing for trivial functions or code already covered by integration or system tests.
Avoid Magic Numbers and Strings
- Replace hard-coded values with named constants to improve readability and reduce errors.
- Avoid: Excessive use of constants for universally understood values (e.g.,
0,1,60for seconds in a minute). - When to Relax: Hard-coding values is fine when they are obvious and contextually clear.
Code Reviews and Pair Programming
- Use code reviews to improve code quality and encourage knowledge sharing. Pair programming can catch errors early.
- Avoid: Reviewing every trivial task, which slows progress and adds overhead.
- When to Relax: For simple or low-risk changes, lighter reviews or automated checks may suffice.
Refactor Regularly
- Refactor code to improve readability and maintainability. Address technical debt before it accumulates.
- Avoid: Excessive refactoring ("refactor churn"), where frequent changes destabilize code.
- When to Relax: Refactor only when it meaningfully improves the code. Avoid refactoring for minor or subjective changes.
By following these principles thoughtfully and flexibly, you can strike a balance between clean code ideals and practical development needs.