| name | clean-code |
| description | Clean Code principles for readable, maintainable software. Trigger: When writing code, refactoring, or reviewing pull requests.
|
| license | Apache-2.0 |
| metadata | {"author":"mrp4sten","version":"1.0","scope":["root"],"auto_invoke":["Writing new code","Refactoring existing code","Code review"]} |
| allowed-tools | Read, Edit, Write, Glob, Grep, Bash |
Clean Code Principles
Based on Robert C. Martin's "Clean Code" book, focusing on readability, maintainability, and simplicity.
Meaningful Names
Names should reveal intent, avoid disinformation, and be pronounceable.
❌ Bad Names
const d = 86400;
const yyyymmdstr = new Date().toISOString().split('T')[0];
let list = getUsers();
✅ Good Names
const SECONDS_PER_DAY = 86400;
const currentDateString = new Date().toISOString().split('T')[0];
let activeUsers = getActiveUsers();
Naming Conventions
| Type | Convention | Example |
|---|
| Variables | Descriptive noun | activeUserCount, totalPrice |
| Functions | Verb + noun | calculateTotal(), getUserById() |
| Classes | Singular noun | UserRepository, PaymentProcessor |
| Constants | UPPER_SNAKE_CASE | MAX_RETRY_COUNT, API_BASE_URL |
| Booleans | is/has/can prefix | isActive, hasPermission, canEdit |
Avoid Encodings
let strName: string;
let iAge: number;
let name: string;
let age: number;
Functions
Functions should be small, do one thing, and do it well.
Small Functions (One Level of Abstraction)
function processOrder(order: Order): void {
if (!order.items || order.items.length === 0) {
throw new Error('Order must have items');
}
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
}
if (order.coupon) {
const discount = total * 0.1;
total -= discount;
}
db.insert('orders', { ...order, total });
emailService.send(order.user.email, 'Order confirmed');
}
function processOrder(order: Order): void {
validateOrder(order);
const total = calculateTotal(order);
saveOrder(order, total);
notifyUser(order);
}
function validateOrder(order: Order): void {
if (!order.items || order.items.length === 0) {
throw new Error('Order must have items');
}
}
function calculateTotal(order: Order): number {
const subtotal = calculateSubtotal(order.items);
return applyDiscount(subtotal, order.coupon);
}
function calculateSubtotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
function applyDiscount(amount: number, coupon?: Coupon): number {
return coupon ? amount * 0.9 : amount;
}
function saveOrder(order: Order, total: number): void {
db.insert('orders', { ...order, total });
}
function notifyUser(order: Order): void {
emailService.send(order.user.email, 'Order confirmed');
}
Function Arguments (Limit to 2-3)
function createUser(
name: string,
email: string,
age: number,
address: string,
phone: string,
role: string
): User {
}
interface CreateUserDto {
name: string;
email: string;
age: number;
address: string;
phone: string;
role: string;
}
function createUser(dto: CreateUserDto): User {
}
Avoid Flag Arguments
function render(isAdmin: boolean): void {
if (isAdmin) {
renderAdminDashboard();
} else {
renderUserDashboard();
}
}
function renderAdminDashboard(): void {
}
function renderUserDashboard(): void {
}
Comments
Good code is self-documenting. Use comments sparingly.
❌ Bad Comments
if (user.status === 'active') {
}
for (const item of items) {
processItem(item);
}
function add(a: number, b: number): number {
return a + b;
}
✅ Good Comments (When Necessary)
const items = response.items || [];
const hashedPassword = await bcrypt.hash(password, 12);
const data = await fetchAllRecords();
Self-Documenting Code
if (user.age >= 18 && user.country === 'US') {
allowVoting(user);
}
const isEligibleToVote = user.age >= 18 && user.country === 'US';
if (isEligibleToVote) {
allowVoting(user);
}
Error Handling
Use exceptions, not error codes. Don't return null.
❌ Bad (Error Codes)
function getUser(id: string): User | null {
if (!userExists(id)) {
return null;
}
return db.findUser(id);
}
const user = getUser('123');
if (user === null) {
}
✅ Good (Exceptions)
function getUser(id: string): User {
if (!userExists(id)) {
throw new UserNotFoundError(id);
}
return db.findUser(id);
}
try {
const user = getUser('123');
} catch (error) {
if (error instanceof UserNotFoundError) {
}
}
Don't Return Null
function getActiveUsers(): User[] | null {
const users = db.findActiveUsers();
return users.length > 0 ? users : null;
}
function getActiveUsers(): User[] {
return db.findActiveUsers() || [];
}
Extract Try/Catch
function deleteUser(id: string): void {
try {
const user = db.findUser(id);
db.delete(user);
cache.invalidate(id);
logger.info(`Deleted user ${id}`);
} catch (error) {
logger.error('Failed to delete user', error);
throw error;
}
}
function deleteUser(id: string): void {
try {
performDeleteUser(id);
} catch (error) {
handleDeleteError(id, error);
}
}
function performDeleteUser(id: string): void {
const user = db.findUser(id);
db.delete(user);
cache.invalidate(id);
logger.info(`Deleted user ${id}`);
}
function handleDeleteError(id: string, error: Error): void {
logger.error(`Failed to delete user ${id}`, error);
throw error;
}
Code Formatting
Consistency matters more than specific style.
Vertical Formatting
- Newspaper metaphor: Most important info at top
- Blank lines: Separate concepts
- Related code: Keep close together
class UserService {
async createUser(dto: CreateUserDto): Promise<User> {
this.validateDto(dto);
const hashedPassword = await this.hashPassword(dto.password);
return this.repository.save({ ...dto, password: hashedPassword });
}
async deleteUser(id: string): Promise<void> {
await this.repository.delete(id);
this.cache.invalidate(id);
}
private validateDto(dto: CreateUserDto): void {
if (!dto.email.includes('@')) {
throw new ValidationError('Invalid email');
}
}
private async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 12);
}
}
Horizontal Formatting
- Line length: Max 80-120 characters
- Indentation: 2 or 4 spaces (consistent)
- Alignment: Avoid horizontal alignment
const name = 'John';
const email = 'john@example.com';
const age = 30;
const name = 'John';
const email = 'john@example.com';
const age = 30;
DRY (Don't Repeat Yourself)
Avoid code duplication. Extract common logic.
❌ Bad (Duplication)
function calculateAdminDiscount(price: number): number {
const tax = price * 0.1;
const discount = price * 0.2;
return price + tax - discount;
}
function calculateUserDiscount(price: number): number {
const tax = price * 0.1;
const discount = price * 0.1;
return price + tax - discount;
}
✅ Good (Extracted Logic)
function calculateFinalPrice(
price: number,
discountRate: number
): number {
const tax = price * 0.1;
const discount = price * discountRate;
return price + tax - discount;
}
function calculateAdminDiscount(price: number): number {
return calculateFinalPrice(price, 0.2);
}
function calculateUserDiscount(price: number): number {
return calculateFinalPrice(price, 0.1);
}
YAGNI (You Aren't Gonna Need It)
Don't add functionality until it's needed.
❌ Bad (Premature Abstraction)
class UserService {
constructor(
private db: Database,
private cache: Cache,
private logger: Logger,
private emailService: EmailService,
private smsService: SmsService,
private pushService: PushService,
private analyticsService: AnalyticsService
) {}
}
✅ Good (Add When Needed)
class UserService {
constructor(
private db: Database,
private cache: Cache,
private logger: Logger
) {}
}
Boy Scout Rule
"Leave the code cleaner than you found it."
Always improve code slightly when touching it.
Examples
- Rename unclear variable
- Extract magic number to constant
- Split long function
- Add missing type annotation
- Remove unused import
function calc(x: number): number {
return x * 1.1 + 50;
}
const TAX_RATE = 0.1;
const BASE_FEE = 50;
function calculateTotalPrice(price: number): number {
const tax = price * TAX_RATE;
return price + tax + BASE_FEE;
}
Clean Code Checklist
Before committing code, verify:
Resources
- Book: "Clean Code" by Robert C. Martin
- Book: "The Pragmatic Programmer" by Hunt & Thomas
- Tool: ESLint, Prettier, SonarQube (automated checks)