← Назад к вопросам

Как разбить код проекта на модули?

2.0 Middle🔥 151 комментариев
#Soft Skills и рабочие процессы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как разбить код проекта на модули

Это фундаментальный вопрос о архитектуре и организации кода. Модульность напрямую влияет на масштабируемость, тестируемость и поддерживаемость проекта.

1. Принципы модульного дизайна

Single Responsibility Principle (SRP)

Каждый модуль должен иметь одну ответственность:

// Плохо - модуль делает много:
// src/user.js
export function getUser(id) { /* fetch */ }
export function updateUser(id, data) { /* update */ }
export function renderUserProfile(user) { /* render */ }
export function validateEmail(email) { /* validate */ }

// Хорошо - разделено по ответственности:
// src/api/userApi.js
export function getUser(id) { /* fetch */ }
export function updateUser(id, data) { /* update */ }

// src/utils/validators.js
export function validateEmail(email) { /* validate */ }

// src/components/UserProfile.jsx
import { getUser } from '@/api/userApi';
export function UserProfile({ userId }) { /* render */ }

Cohesion (Когезия)

Элементы в модуле должны быть тесно связаны:

// Высокая когезия - связанный код в одном модуле:
// src/features/auth/
// ├── hooks/
// │   └── useAuth.js
// ├── context/
// │   └── AuthContext.jsx
// ├── services/
// │   └── authService.js
// └── types/
//     └── auth.types.ts

// Все связано с аутентификацией - это хорошо

2. Структура проекта на примере React

// Рекомендованная структура:
project/
├── src/
│   ├── app/                    # Корневой уровень приложения
│   │   ├── App.jsx
│   │   └── Root.jsx            # Layout
│   │
│   ├── features/               # Функции (domain-driven)
│   │   ├── auth/
│   │   │   ├── components/
│   │   │   │   └── LoginForm.jsx
│   │   │   ├── hooks/
│   │   │   │   └── useAuth.js
│   │   │   ├── services/
│   │   │   │   └── authService.js
│   │   │   └── types/
│   │   │       └── auth.types.ts
│   │   │
│   │   └── posts/
│   │       ├── components/
│   │       ├── hooks/
│   │       ├── services/
│   │       └── types/
│   │
│   ├── shared/                 # Переиспользуемые элементы
│   │   ├── ui/                # UI компоненты
│   │   │   ├── Button.jsx
│   │   │   └── Input.jsx
│   │   ├── hooks/             # Утилита хуки
│   │   │   └── useMediaQuery.js
│   │   ├── utils/             # Утилиты
│   │   │   ├── cn.js
│   │   │   └── validators.js
│   │   ├── api/               # API клиент
│   │   │   └── client.js
│   │   └── types/             # Глобальные типы
│   │       └── common.types.ts
│   │
│   ├── pages/                 # Page компоненты для routing
│   │   ├── HomePage.jsx
│   │   └── ProfilePage.jsx
│   │
│   ├── main.jsx
│   └── index.css
│
├── public/
├── package.json
└── vite.config.js / webpack.config.js

3. Модульность на практике

Правильное разделение

// src/features/posts/services/postService.js
export async function fetchPosts(page = 1) {
  const response = await fetch(`/api/posts?page=${page}`);
  return response.json();
}

export async function createPost(data) {
  const response = await fetch('/api/posts', {
    method: 'POST',
    body: JSON.stringify(data),
  });
  return response.json();
}

// src/features/posts/hooks/usePosts.js
import { useState, useEffect } from 'react';
import * as postService from '../services/postService';

export function usePosts(page = 1) {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const load = async () => {
      try {
        const data = await postService.fetchPosts(page);
        setPosts(data);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };
    
    load();
  }, [page]);
  
  return { posts, loading, error };
}

// src/features/posts/components/PostsList.jsx
import { usePosts } from '../hooks/usePosts';
import { PostItem } from './PostItem';

export function PostsList({ page }) {
  const { posts, loading, error } = usePosts(page);
  
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  
  return (
    <ul>
      {posts.map(post => (
        <PostItem key={post.id} post={post} />
      ))}
    </ul>
  );
}

4. Типы модульности

CommonJS (Node.js)

// math.js
function add(a, b) {
  return a + b;
}

module.exports = { add };

// main.js
const { add } = require('./math');
console.log(add(2, 3)); // 5

ES Modules (современный стандарт)

// math.js
export function add(a, b) {
  return a + b;
}

export const PI = 3.14;

// main.js
import { add, PI } from './math.js';
console.log(add(2, 3)); // 5

// default export
// utils.js
export default function log(msg) {
  console.log(msg);
}

// main.js
import log from './utils.js';
log('Hello');

5. Path aliases для чистых импортов

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@features': path.resolve(__dirname, './src/features'),
      '@shared': path.resolve(__dirname, './src/shared'),
    },
  },
});

// tsconfig.json (для TypeScript)
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@features/*": ["src/features/*"],
      "@shared/*": ["src/shared/*"]
    }
  }
}

// Использование:
import { usePosts } from '@features/posts/hooks/usePosts';
import { Button } from '@shared/ui/Button';
// Вместо:
// import { usePosts } from '../../../../features/posts/hooks/usePosts';

6. Барьеры между модулями (Index.js pattern)

// src/features/posts/index.js - публичный API
export * from './components/PostsList';
export * from './hooks/usePosts';
export { fetchPosts, createPost } from './services/postService';
export * from './types/post.types';

// Использование:
import { PostsList, usePosts } from '@features/posts';

// Вместо:
// import { PostsList } from '@features/posts/components/PostsList';
// import { usePosts } from '@features/posts/hooks/usePosts';

7. Взаимозависимости между модулями

// Разрешено (стрелка показывает зависимость):
// pages -> features -> shared -> (ничего)

// Правило - зависимости должны идти только в одну сторону:
pages/ProfilePage.jsx
  ├─ imports -> features/auth
  │             ├─ imports -> shared/ui
  │             └─ imports -> shared/utils
  └─ imports -> shared/types

// Запрещено - циклические зависимости:
// features/posts <-> features/comments
const circularDependency = 'BAD!';

// Нарушение слоев:
// components должны быть in shared, не в features
// services должны быть in features, не в shared

8. Практический пример: функция экспорта и импорта

// src/features/auth/index.ts
export { LoginForm } from './components/LoginForm';
export { useAuth } from './hooks/useAuth';
export type { User, AuthState } from './types/auth.types';
export { authService } from './services/authService';

// src/pages/LoginPage.jsx
import { LoginForm, useAuth } from '@features/auth';

export function LoginPage() {
  const { user } = useAuth();
  
  return <LoginForm />;
}

9. Ленивая загрузка модулей

// Разбивка на chunks для оптимизации
import { lazy, Suspense } from 'react';

const PostsList = lazy(() => import('@features/posts/components/PostsList'));

export function App() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <PostsList />
    </Suspense>
  );
}

Правила для хорошей модульности

  1. Одна ответственность - каждый модуль решает одну задачу
  2. Минимальные зависимости - не импортируй ненужное
  3. Публичный API - через index.js определи, что экспортируется
  4. Направленные зависимости - pages -> features -> shared
  5. Переиспользование - shared модули используются везде
  6. Группировка по функциям - не по типам файлов
  7. Тестируемость - каждый модуль легко тестируется отдельно
  8. Читаемые имена - из названия должна быть ясна цель

Правильная модульность - это инвестиция в будущее проекта. Усложнение в начале окупается простотой масштабирования и поддержки.