Как документируешь свои доработки или разработки?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Подход к документированию кода и разработок
Документация — это критический элемент качественной разработки. Я применяю многоуровневый подход документирования, в зависимости от контекста и аудитории.
1. Docstrings для функций и классов
Использую Google-style docstrings с указанием типов, параметров, возвращаемого значения и примеров:
def calculate_discount(price: float, discount_percent: float) -> float:
"""Рассчитать цену с учётом скидки.
Args:
price: Исходная цена товара в рублях.
discount_percent: Процент скидки (0-100).
Returns:
Финальная цена с учётом скидки.
Raises:
ValueError: Если цена отрицательная или скидка вне диапазона.
Example:
>>> calculate_discount(1000, 10)
900.0
>>> calculate_discount(500, 50)
250.0
"""
if price < 0:
raise ValueError("Цена не может быть отрицательной")
if not 0 <= discount_percent <= 100:
raise ValueError("Скидка должна быть от 0 до 100%")
return price * (1 - discount_percent / 100)
2. Type hints для самодокументирования
Полная типизация — это уже часть документации:
from typing import Protocol, Iterator, Optional
from dataclasses import dataclass
from datetime import datetime
@dataclass
class User:
"""Пользователь системы."""
id: int
name: str
email: str
created_at: datetime
is_active: bool = True
class UserRepository(Protocol):
"""Интерфейс репозитория пользователей."""
def find_by_id(self, user_id: int) -> Optional[User]:
"""Найти пользователя по ID."""
...
def find_all_active(self) -> Iterator[User]:
"""Получить всех активных пользователей."""
...
def save(self, user: User) -> None:
"""Сохранить пользователя в БД."""
...
3. Комментарии для сложной логики
Комментарии описывают ПОЧЕМУ, а не ЧТО:
def find_optimal_partition(items: list[int], target_sum: int) -> Optional[list[int]]:
"""
Найти подмножество элементов с суммой, максимально близкой к целевой.
Используется dynamic programming вместо brute force (O(2^n))
для производительности O(n*sum).
"""
n = len(items)
# dp[i][j] = True если можно достичь суммы j используя первые i элементов
dp = [[False] * (target_sum + 1) for _ in range(n + 1)]
# Base case: сумма 0 всегда достижима (пустое подмножество)
for i in range(n + 1):
dp[i][0] = True
# Fill DP table
for i in range(1, n + 1):
for j in range(target_sum + 1):
# Либо не берём текущий элемент
dp[i][j] = dp[i-1][j]
# Либо берём (если возможно)
if j >= items[i-1]:
dp[i][j] = dp[i][j] or dp[i-1][j - items[i-1]]
# Backtrack чтобы найти само подмножество
if not dp[n][target_sum]:
return None
result = []
i, j = n, target_sum
while i > 0 and j > 0:
# Если элемент был использован
if not dp[i-1][j]:
result.append(items[i-1])
j -= items[i-1]
i -= 1
return result
4. Архитектурные диаграммы и README
Для крупных проектов создаю документацию с диаграммами:
# Project Architecture
## Layered Architecture
┌─────────────────────────────────┐ │ Presentation (API) │ │ FastAPI routes, responses │ └──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐ │ Application (Use Cases) │ │ Business logic, orchestration │ └──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐ │ Domain (Models) │ │ Entities, value objects │ └──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐ │ Infrastructure (Database) │ │ Repositories, migrations │ └─────────────────────────────────┘
## Class Diagram
Repository <|-- UserRepository Repository <|-- ProductRepository
UseCase <|-- CreateUserUseCase UseCase <|-- GetUserUseCase
5. Inline примеры использования
class EmailService:
"""Сервис отправки email.
Example:
service = EmailService(smtp_host="smtp.gmail.com")
# Простое письмо
service.send(
to="user@example.com",
subject="Добро пожаловать",
body="Спасибо за регистрацию"
)
# С HTML и вложениями
service.send_html(
to="user@example.com",
subject="Счёт",
html="<h1>Счёт</h1>",
attachments=["invoice.pdf"]
)
"""
def send(self, to: str, subject: str, body: str) -> None:
"""Отправить простое текстовое письмо."""
pass
def send_html(self, to: str, subject: str, html: str,
attachments: list[str] | None = None) -> None:
"""Отправить HTML письмо с вложениями."""
pass
6. Тесты как документация
Хорошие тесты показывают, как использовать код:
import pytest
from app.services import PaymentService, PaymentError
class TestPaymentService:
"""Тесты для сервиса платежей."""
def test_successful_payment(self, payment_service):
"""Успешный платёж уменьшает баланс пользователя."""
result = payment_service.charge(
user_id=1,
amount=1000.0,
currency="RUB"
)
assert result.status == "success"
assert result.transaction_id is not None
def test_insufficient_funds(self, payment_service):
"""Платёж отклоняется если недостаточно средств."""
with pytest.raises(PaymentError) as exc_info:
payment_service.charge(
user_id=1,
amount=999999.0,
currency="RUB"
)
assert "insufficient_funds" in str(exc_info.value)
def test_concurrent_payments(self, payment_service):
"""Система обрабатывает конкурентные платежи корректно."""
# Это тест как документация параллельного поведения
pass
7. API документация (OpenAPI/Swagger)
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(
title="User API",
description="API для управления пользователями",
version="1.0.0"
)
class User(BaseModel):
"""Модель пользователя."""
id: int
name: str
email: str
@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: int):
"""
Получить пользователя по ID.
- **user_id**: ID пользователя в системе
Returns:
User объект с информацией о пользователе
Raises:
404: Пользователь не найден
"""
pass
8. Commit messages как документация
fix: исправить race condition в find_opponent
Проблема: при одновременном запросе от двух пользователей
они могут быть сопоставлены друг с другом несколько раз.
Решение: использовать SELECT FOR UPDATE SKIP LOCKED
для атомарного выбора и блокировки соперника.
Тест: test_concurrent_find_opponent проверяет это
9. Change logs и migration guides
# CHANGELOG.md
## v2.0.0 (Breaking Changes)
### Removed
- `UserService.get_user_by_email()` → используй `UserRepository.find_by_email()`
### Changed
- `User.created_at` теперь DateTime вместо Unix timestamp
### Migration
```sql
ALTER TABLE users
ALTER COLUMN created_at TYPE TIMESTAMPTZ USING
to_timestamp(created_at);
## Инструменты документации
- **Pydantic** — автоматическая валидация и документация
- **Sphinx** — для больших проектов
- **MkDocs** — для вики-стиля документации
- **Swagger/OpenAPI** — для API
- **Type hints + mypy** — для типизации
## Best Practices
- ✅ Docstrings для ВСЕХ public функций
- ✅ Type hints везде
- ✅ Примеры в docstrings
- ✅ Тесты как живая документация
- ✅ Комментарии для ПОЧЕМУ, не ЧТО
- ❌ Устаревшие комментарии (синхронизируйте с кодом)
- ❌ Очевидные комментарии
- ❌ Излишняя документация для простого кода