Clean Code: пишем код, который легко читать
Принципы чистого кода от дядюшки Боба и практические примеры
"Любой дурак может написать код, который понимает компьютер. Хорошие программисты пишут код, который понимают люди." — Мартин Фаулер. Код читается в 10 раз чаще, чем пишется. Clean Code — инвестиция в будущее. Принципы Clean Code 1. Meaningful Names Плохо: const d = new Date(); const x = users.filter(u => u.a); function getData() { /* ... */ } Хорошо: const currentDate = new Date(); const activeUsers = users.filter(user => user.isActive); function fetchUserProfile() { /* ... */ } Правила naming: Используйте pronounceable names — можно произнести вслух Используйте searchable names — легко найти Избегайте mental mapping — не нужно помнить что значит переменная Classes: существительные — User, Account, Parser Methods: глаголы — getUserById, calculateTotal 2. Functions должны быть маленькими Плохо: function processUser(user) { // Validation (20 lines) if (!user.email) throw new Error('Email required'); if (!user.name) throw new Error('Name required'); // ... // Save to database (30 lines) const connection = await db.connect(); // ... // Send email (25 lines) const emailService = new EmailService(); // ... // Log analytics (15 lines) analytics.track('user_created'); // ... } Хорошо: function processUser(user) { validateUser(user); const savedUser = await saveUser(user); await sendWelcomeEmail(savedUser); trackUserCreation(savedUser); return savedUser; } function validateUser(user) { if (!user.email) throw new Error('Email required'); if (!user.name) throw new Error('Name required'); } async function saveUser(user) { return db.users.create(user); } async function sendWelcomeEmail(user) { await emailService.send({ to: user.email, template: 'welcome' }); } function trackUserCreation(user) { analytics.track('user_created', { userId: user.id }); } Правила функций: Do one thing — одна функция = одна задача One level of abstraction — не смешивайте уровни Small — 5-15 строк идеально Few arguments — максимум 3, лучше меньше 3. DRY: Don't Repeat Yourself Плохо: function createUser(data) { if (!data.email) throw new Error('Email required'); if (!data.email.includes('@')) throw new Error('Invalid email'); // ... } function updateUser(id, data) { if (!data.email) throw new Error('Email required'); if (!data.email.includes('@')) throw new Error('Invalid email'); // ... } Хорошо: function validateEmail(email) { if (!email) throw new Error('Email required'); if (!email.includes('@')) throw new Error('Invalid email'); } function createUser(data) { validateEmail(data.email); // ... } function updateUser(id, data) { validateEmail(data.email); // ... } 4. Comments: код должен говорить сам за себя Плохо: // Check if user is active if (user.status === 1 && user.deletedAt === null) { // ... } Хорошо: const isUserActive = user.status === UserStatus.ACTIVE && !user.deletedAt; if (isUserActive) { // ... } Когда комментарии нужны: TODO комментарии Объяснение почему, не что Warning о последствиях Documentation для публичного API // TODO: Оптимизировать этот запрос после миграции на PostgreSQL // Используем setTimeout вместо setInterval потому что: // setInterval не ждет завершения предыдущего вызова setTimeout(function poll() { fetch().then(() => setTimeout(poll, 1000)); }, 1000); // WARNING: Эта функция модифицирует исходный массив function sortInPlace(array) { /* ... */ } 5. Error Handling Плохо: function getUser(id) { try { const user = db.users.findById(id); return user; } catch (error) { return null; // Ошибка теряется! } } Хорошо: async function getUser(id) { try { return await db.users.findById(id); } catch (error) { logger.error('Failed to fetch user', { id, error }); throw new UserNotFoundError(`User ${id} not found`); } } Custom Errors: class UserNotFoundError extends Error { constructor(message) { super(message); this.name = 'UserNotFoundError'; this.statusCode = 404; } } class ValidationError extends Error { constructor(message, fields) { super(message); this.name = 'ValidationError'; this.statusCode = 400; this.fields = fields; } } 6. SOLID Principles S - Single Responsibility Principle Класс должен иметь одну причину для изменения. Плохо: class User { constructor(data) { this.data = data; } save() { db.save(this.data); // Database logic } sendEmail() { emailService.send(this.data.email); // Email logic } validate() { // Validation logic } } Хорошо: class User { constructor(data) { this.data = data; } } class UserRepository { save(user) { return db.save(user.data); } } class UserEmailService { sendWelcome(user) { return emailService.send(user.data.email); } } class UserValidator { validate(user) { // Validation } } O - Open/Closed Principle Открыт для расширения, закрыт для модификации. Плохо: function calculatePrice(product) { if (product.type === 'book') { return product.price * 0.9; // 10% discount } else if (product.type === 'electronics') { return product.price * 0.8; // 20% discount } return product.price; } Хорошо: class PricingStrategy { calculate(price) { return price; } } class BookPricing extends PricingStrategy { calculate(price) { return price * 0.9; } } class ElectronicsPricing extends PricingStrategy { calculate(price) { return price * 0.8; } } function calculatePrice(product, strategy) { return strategy.calculate(product.price); } L - Liskov Substitution Principle Подтипы должны заменять базовые типы. I - Interface Segregation Principle Не заставляйте реализовывать ненужные методы. D - Dependency Inversion Principle Зависьте от абстракций, не от конкретных реализаций. // Плохо class UserService { constructor() { this.db = new MySQLDatabase(); // Жесткая зависимость } } // Хорошо class UserService { constructor(database) { this.db = database; // Dependency injection } } 7. Code Organization Folder structure: src/ features/ users/ UserController.js UserService.js UserRepository.js UserValidator.js user.types.js products/ ProductController.js ... shared/ utils/ types/ constants/ Import order: // 1. External dependencies import React from 'react'; import { useState } from 'react'; // 2. Internal modules import { UserService } from '@/services'; import { Button } from '@/components'; // 3. Relative imports import { validateEmail } from './utils'; import styles from './styles.module.css'; 8. Magic Numbers и Constants Плохо: if (user.age > 18) { /* ... */ } setTimeout(callback, 86400000); Хорошо: const ADULT_AGE = 18; const DAY_IN_MS = 24 * 60 * 60 * 1000; if (user.age > ADULT_AGE) { /* ... */ } setTimeout(callback, DAY_IN_MS); 9. Guard Clauses Плохо: function processPayment(user, amount) { if (user) { if (user.balance >= amount) { if (amount > 0) { // Process payment return true; } } } return false; } Хорошо: function processPayment(user, amount) { if (!user) return false; if (user.balance 10. Avoid Flags в аргументах Плохо: function render(isAdmin) { if (isAdmin) { renderAdminPage(); } else { renderUserPage(); } } Хорошо: function renderAdminPage() { /* ... */ } function renderUserPage() { /* ... */ } // Или function render(user) { if (user.isAdmin) { renderAdminPage(); } else { renderUserPage(); } } Code Review Checklist ✅ Meaningful names ✅ Functions are small and focused ✅ No code duplication ✅ Comments explain why, not what ✅ Proper error handling ✅ SOLID principles followed ✅ No magic numbers ✅ Early returns used ✅ Consistent formatting ✅ Tests included Refactoring Tools ESLint rules: // .eslintrc.js module.exports = { rules: { 'max-lines-per-function': ['error', 50], 'max-params': ['error', 3], 'complexity': ['error', 10], 'max-depth': ['error', 3], 'no-magic-numbers': 'warn' } }; Практика Рефакторинг legacy кода: Добавьте тесты перед рефакторингом Маленькие шаги — по одному изменению Проверяйте тесты после каждого изменения Commit часто — можно откатить Заключение: Clean Code — это навык, который развивается с практикой. Начните с meaningful names и small functions. Применяйте DRY и SOLID принципы. Используйте guard clauses и избегайте глубокой вложенности. Рефакторьте постоянно — не ждите "подходящего момента". Помните: код пишется один раз, но читается десятки раз. Инвестируйте в чистоту кода — это окупится через легкость maintenance и меньше багов.