100 Tips to Write Clean Code
Clean Functions, Classes, Comments, Commits and everything you need to write Clean Code
CodeRabbit: Free AI Code Reviews in CLI (Sponsor)
CodeRabbit CLI is an AI code review tool that runs directly in your terminal. It provides intelligent code analysis, catches issues early, and integrates seamlessly with AI coding agents like Claude Code, Codex CLI, Cursor CLI, and Gemini to ensure your code is production-ready before it ships.
Enables pre-commit reviews of both staged and unstaged changes, creating a multi-layered review process.
Fits into existing Git workflows. Review uncommitted changes, staged files, specific commits, or entire branches without disrupting your current development process.
Reviews specific files, directories, uncommitted changes, staged changes, or entire commits based on your needs.
Supports programming languages including JavaScript, TypeScript, Python, Java, C#, C++, Ruby, Rust, Go, PHP, and more.
Offers free AI code reviews with rate limits so developers can experience senior-level reviews at no cost.
Flags hallucinations, code smells, security issues, and performance problems.
Supports guidelines for other AI generators, AST Grep rules, and path-based instructions.
Motivation
Every developer can write working code.
But not every developer can write clean code.
Clean code is code that:
tells the story of the business,
can be read like prose,
and can be changed with confidence.
Over the years, I’ve collected 100 practical tips that help you craft better software that doesn’t just work today, but stays readable, testable, and maintainable for years.
Here’s the full list 👇
✨ Clean Namings
Use intention-revealing names
Capture business knowledge
Avoid encoding and technical details
Use pronounceable names
Use searchable names
Avoid noise and redundant words
Use strong words
Don’t use abbreviations
Be consistent with concepts and vocabulary
Use
is/has/should/canprefix for booleansAvoid negative names for booleans
Use naming conventions within your project
Boolean names should be adjectives
Class names should be nouns or noun-phrases
⚡ Clean Functions
Function names should be verbs or verb-phrases
Keep functions small and concise
Avoid too many function arguments
Max number of lines in a function: 8–10
Avoid passing booleans as parameters
Strive for no side effects
Use enums as flags
Use empty lines to separate logic
If it takes more than 3 seconds to figure out what a function does, refactor it
🏗 Clean Classes
A class should have only one main responsibility
Avoid large classes (~100+ lines can be a smell)
Strive for one public function per class
Create small private functions for single tasks
Order functions based on execution flow
💬 Clean Comments
Avoid comments as much as possible
Don’t state the obvious
Don’t use them extensively (nobody will read them)
Replace comments with good naming
Use comments only for explaining why
Use comments for revealing implicit behavior
Use comments for API doc generation\
✅ Clean Tests
Use descriptive test names (scenario-based)
Use Given/When/Then or Should/When templates
Structure tests with Arrange / Act / Assert
Avoid logic (if, for, while) in tests
Couple a test with one behavior
Use meaningful test data
Hide irrelevant test data
Write clean assertions that describe domain behaviors
Create deterministic tests
Remove duplication with parameterized tests
Prefer fakes for mocking out 3rd party code
🔑 F.I.R.S.T Principle for Tests
Tests should be fast
Tests should be independent
Tests should be repeatable
Tests should be self-validating
Tests should be thorough (happy paths, edge cases, negative cases, security, illegal input)
🔀 Git Commits
Commit early & push often
Write meaningful commit messages explaining the reason
Use the imperative mood in commit messages
Use present tense
Add a link reference to the related story, task, or bug
🕳 Code Smells
Avoid magic numbers
Avoid magic strings
Avoid long conditions
Avoid global variables
Avoid long parameter lists
Don’t overuse primitives — model your domain with rich types
Avoid deeply nested logic
Reduce cyclomatic complexity
Hard-to-test logic is a smell to fix
🎨 Formatting
Set up team standards
Use automated formatters
Set a max width for lines
Don’t use horizontal alignment
Don’t break indentation
Declare variables close to their usage
📐 Core Principles
72. Don't repeat yourself (DRY): remove knowledge duplication
73. Keep it simple (KISS): simple code beats both complex and clever code
74. You ain't gonna need it (YAGNI): don't produce code that is not needed in your current context
75. Tell, don't ask: bundle data and functions together
76. Single Responsibility Principle (SRP): organize logic into modules with one reason to change
77. Liskov Substitution Principle (LSP): a subtype should be able to replace its base type without altering the program
78. Interface Segregation Principle (ISP): split large interfaces into small and more specific ones
79. Dependency Inversion Principle (DIP): high-level modules should not depend on low-level modules. Both should depend on abstractions.
80. Favor composition over inheritance: inheritance leads to tight coupling, composition gives more flexibility
81. Divide and Conquer: break down a problem into smaller sizes to manage complexity
82. High cohesion: group related code together. It helps you to find logic easier and makes your code easy to maintain
83. Low coupling: your modules should independent of other modules. By doing so, you can easier make changes to internals without breaking other modules
🛠 Extra Tips
Use a solid IDE with refactoring tools
Master your IDE hotkeys
Use feature-based folder structure
Pair programming helps keep code clean
Delete unused code — it’s a liability
Write code for humans, not just machines
Readability > cleverness
Favor readability over efficiency
Always leave the code cleaner than you found it
Test early, test often
Refactor early, refactor often
Do real-time code reviews instead of just PR reviews
Use the “rule of three” to remove duplication
Avoid using
NULL— it’s a code smellWorking code ≠ clean code
You can’t have clean code without tests
Write code that reads like well-written prose
Conclusion
Clean code is not a checklist. It’s a mindset.
It’s the discipline of choosing clarity over cleverness.
It’s the habit of leaving the codebase better than you found it.
It’s the commitment to future teammates, and to your future self.
Because in the end:
Working code delivers features.
Clean Code builds lasting software.








I added this for agents:
## 📏 Code Quality Rule
Don't apply SOLID as a rigid checklist. Judge code by whether it's **composable, does one thing, predictable, idiomatic Python/FastAPI, and mirrors the domain** — not by rule compliance. Before adding an abstraction, ask if it's earning its keep.
Concretely:
- **No premature abstractions.** Don't add a repository interface, base service class, or dependency-injected abstraction for a single DB implementation "in case we swap Postgres for MySQL someday." Write the concrete query/service first; abstract only when a second real implementation exists.
- **No over-splitting.** Don't break a service/endpoint into multiple classes just to satisfy "single responsibility" if a plain function or module-level set of functions is clearer. FastAPI route handlers calling straightforward service functions is idiomatic — don't wrap everything in unnecessary classes.
- **Favor Pydantic/FastAPI idioms over generic OOP patterns.** Use dependency injection (`Depends`) where FastAPI expects it, not homegrown DI containers. Use Pydantic models for validation instead of manual validator classes.
- **DB layer: prefer explicit, readable queries over deep ORM abstraction layers.** Don't hide simple queries behind multiple inheritance layers or generic repository patterns unless the project already has that pattern established and reuses it.
- **Predictability over cleverness.** A teammate should be able to trace a request from route → service → DB call without jumping through interfaces that exist only for theoretical substitutability.
- **Match the codebase's existing idiom.** If the project already uses SQLAlchemy models directly in services, don't introduce a new abstraction layer unilaterally — consistency with existing patterns beats "correct" architecture in isolation.
**Before finalizing:** if an abstraction, interface, or split has no second concrete use case today, cut it.
Very insightful Daniel. Great share.