Чистый код на TypeScript: правила, примеры и лайфхаки

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему чистый код на TypeScript — это не роскошь, а необходимость

Каждый разработчик хотя бы раз сталкивался с ситуацией: открываешь проект, написанный пару месяцев назад, и не понимаешь, что там происходит. Переменные называются a, b, data, функции занимают по 200 строк, а типы расставлены через раз. Знакомо?

TypeScript даёт нам мощный инструмент — статическую типизацию. Но сам по себе он не делает код чистым. Можно написать ужасный код и на TypeScript, и на любом другом языке. Чистый код — это дисциплина, стиль и уважение к себе и коллегам.

В этой статье мы разберём, как писать чистый код на TypeScript, который будет легко читаться, поддерживаться и масштабироваться. Вы узнаете про принципы SOLID, строгую типизацию, именование, работу с интерфейсами и многое другое. Поехали!

1. Именование — лицо вашего кода

Правильное именование переменных, функций и классов — это 50% успеха. Хорошее имя должно отвечать на вопросы: "Что это?" или "Что делает эта функция?".

Переменные и константы

Используйте существительные и camelCase. Избегайте однобуквенных имён (кроме циклов).

// Плохо
const d = new Date();
const u = users.find(u => u.id === 5);

// Хорошо
const currentDate = new Date();
const activeUser = users.find(user => user.id === 5);

Функции

Имена функций должны начинаться с глагола. Это сразу даёт понять, какое действие выполняется.

// Плохо
function data(id: number) { ... }

// Хорошо
function fetchUserData(id: number): Promise<UserData> { ... }

Классы и интерфейсы

Классы — существительные в PascalCase. Интерфейсы — тоже, но часто с префиксом I (хотя в сообществе идут споры). Лучше просто давать понятное имя без префикса.

// Плохо
interface IUser {
  name: string;
}

// Хорошо
interface User {
  name: string;
  email: string;
}

2. Строгая типизация — ваш лучший друг

TypeScript создан для того, чтобы типы работали на вас. Не используйте any без крайней необходимости. Каждый раз, когда вы пишете any, вы выключаете защиту компилятора.

Избегайте any

// Плохо
function process(data: any) {
  return data.name;
}

// Хорошо
interface Item {
  name: string;
  price: number;
}

function process(data: Item): string {
  return data.name;
}

Используйте Union и Intersection типы

Когда переменная может принимать несколько типов, используйте Union (|). Для комбинации — Intersection (&).

type Status = 'active' | 'inactive' | 'pending';
type AdminUser = User & { role: 'admin' };

Generic — универсальные функции

Generic позволяют писать гибкий код без потери типизации.

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

const num = firstElement([1, 2, 3]); // number
const str = firstElement(['a', 'b']); // string

3. SOLID — принципы для взрослых

Принципы SOLID помогают строить гибкую и поддерживаемую архитектуру. Рассмотрим каждый на примере TypeScript.

Single Responsibility (SRP)

Класс или функция должны иметь только одну причину для изменения.

// Плохо: класс делает всё
class UserService {
  createUser() { ... }
  sendEmail() { ... }
  generateReport() { ... }
}

// Хорошо: разделяем ответственность
class UserCreator { ... }
class EmailService { ... }
class ReportGenerator { ... }

Open/Closed (OCP)

Классы открыты для расширения, но закрыты для модификации. Используйте интерфейсы и наследование.

Liskov Substitution (LSP)

Подклассы должны заменять базовый класс без изменения поведения программы.

Interface Segregation (ISP)

Не заставляйте классы реализовывать методы, которые им не нужны. Лучше несколько маленьких интерфейсов, чем один большой.

// Плохо
interface Worker {
  work(): void;
  eat(): void;
  sleep(): void;
}

// Хорошо
interface Workable {
  work(): void;
}
interface Eatable {
  eat(): void;
}
interface Sleepable {
  sleep(): void;
}

Dependency Inversion (DIP)

Зависимости должны быть от абстракций, а не от конкретных реализаций.

4. Работа с функциями: коротко и ясно

Функции должны быть небольшими (до 20-30 строк). Одна функция — одно действие.

Избегайте side-эффектов

Чистые функции проще тестировать и понимать. Они не меняют внешнее состояние.

// Плохо
let counter = 0;
function increment() {
  counter++;
}

// Хорошо
function increment(counter: number): number {
  return counter + 1;
}

Деструктуризация параметров

Если функция принимает объект с множеством полей, используйте деструктуризацию.

// Плохо
function createUser(user: User) {
  console.log(user.name, user.email);
}

// Хорошо
function createUser({ name, email }: User) {
  console.log(name, email);
}

5. Комментарии — минимум, но по делу

Хороший код говорит сам за себя. Комментарии нужны только для объяснения почему, а не что.

Плохие комментарии

// Увеличиваем счётчик на 1
counter++;

Хорошие комментарии

// Используем Math.floor, потому что API ожидает целое число
const page = Math.floor(total / limit);

6. Обработка ошибок и null-безопасность

TypeScript помогает бороться с null и undefined. Используйте строгие проверки.

Optional chaining и Nullish coalescing

const userName = user?.profile?.name ?? 'Гость';

Создавайте кастомные ошибки

class ValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'ValidationError';
  }
}

7. Паттерны проектирования в TypeScript

Используйте проверенные паттерны, но не усложняйте там, где это не нужно.

Фабрика (Factory)

interface Button {
  render(): void;
}

class PrimaryButton implements Button {
  render() { console.log('Primary'); }
}

class ButtonFactory {
  static create(type:                

Блоги

Рекомендаций по книгам