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

С помощью чего можно добиться качественного и чистого кода?

2.0 Middle🔥 61 комментариев
#Python Core#Soft Skills#Архитектура и паттерны

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

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

Ответ: Инструменты и практики для чистого кода

Качественный код это не магия и не талант, это система и дисциплина. Расскажу как я это делаю на практике.

1. Линтеры — первая линия защиты

Линтер анализирует код и находит ошибки до runtime.

Ruff — самый быстрый

# Установка
pip install ruff

# Проверка
ruff check .

# Автоматическое исправление
ruff check --fix .

Конфиг (pyproject.toml):

[tool.ruff]
line-length = 79
target-version = "py311"

[tool.ruff.lint]
select = [
    "E",    # pycodestyle ошибки
    "W",    # pycodestyle предупреждения
    "F",    # Pyflakes
    "I",    # isort (импорты)
    "C4",   # flake8-comprehensions
    "B",    # flake8-bugbear
    "D",    # pydocstyle
]

ignore = [
    "D100", # Missing docstring in public module
    "D104", # Missing docstring in public package
]

[tool.ruff.lint.pydocstyle]
convention = "google"

Pylint — углублённый анализ

pip install pylint

pylint mymodule.py

Находит:

  • Неиспользуемые переменные
  • Нарушения SOLID принципов
  • Сложность циклов
  • Логические ошибки

2. Форматер кода — Zero friction

Форматер автоматически переписывает код в правильном стиле. Не думаешь о форматировании, просто пишешь.

Black — бескомпромиссный

pip install black

black .

Конфиг:

[tool.black]
line-length = 79
target-version = ['py311']

Пример:

# ДО (мой код)
result=calculate_total_price(user_id=123,items=[1,2,3],apply_discount=True,tax_rate=0.2)

# ПОСЛЕ (Black)
result = calculate_total_price(
    user_id=123,
    items=[1, 2, 3],
    apply_discount=True,
    tax_rate=0.2,
)

3. Type checking — ловим ошибки типов

Python динамический, но можно добавить типы и проверять их статически.

mypy

pip install mypy

mypy .

Конфиг:

[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true  # строгая типизация

Пример:

from typing import Optional

def get_user_age(user_id: int) -> Optional[int]:
    """Get user age or None."""
    user = db.query(User).get(user_id)
    if user is None:
        return None
    return user.age

# mypy проверит что это правильно
age = get_user_age(123)
if age is not None:
    print(age + 1)  # OK
    
# mypy ошибка: age может быть None
print(age + 1)  # Error: unsupported operand type(s)

4. Тесты — коллега который смотрит за тобой

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

pytest

pip install pytest pytest-cov

pytest
pytest --cov=app tests/  # с покрытием

Пример теста:

import pytest
from app.services import calculate_total_price

def test_calculate_total_price_no_discount():
    result = calculate_total_price(
        base_price=100,
        quantity=2,
        tax_rate=0.2,
        discount=0
    )
    assert result == 240  # 100 * 2 * 1.2

def test_calculate_total_price_with_discount():
    result = calculate_total_price(
        base_price=100,
        quantity=2,
        tax_rate=0.2,
        discount=0.1
    )
    assert result == 216  # 100 * 2 * 1.2 * 0.9

def test_invalid_quantity():
    with pytest.raises(ValueError):
        calculate_total_price(
            base_price=100,
            quantity=-1,  # ошибка!
            tax_rate=0.2,
            discount=0
        )

Цели по coverage:

  • Минимум 80% покрытие
  • 90%+ для критического кода
  • 100% для business logic

5. Code Review и CI/CD

GitHub Actions pipeline

name: Code Quality

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - run: pip install ruff black mypy pytest
      - run: ruff check .
      - run: black --check .
      - run: mypy .
      - run: pytest --cov=app

На pull request запускается:

  1. Линтер (ruff)
  2. Форматер (black)
  3. Type checker (mypy)
  4. Тесты (pytest)

Если что-то не прошло — PR не мержится.

6. SOLID принципы — архитектура

S — Single Responsibility (одна ответственность)

# ❌ Плохо: UserService делает слишком много
class UserService:
    def create_user(self, name, email):
        user = User(name=name, email=email)
        db.add(user)
        db.commit()
        # Отправляет email
        send_email(email, "Welcome!")
        # Логирует
        logger.info(f"User {name} created")
        # Обновляет кэш
        cache.set(f"user:{user.id}", user)
        return user

# ✓ Хорошо: разделили ответственность
class UserRepository:
    def create(self, name: str, email: str) -> User:
        user = User(name=name, email=email)
        db.add(user)
        db.commit()
        return user

class NotificationService:
    def send_welcome_email(self, user: User) -> None:
        send_email(user.email, "Welcome!")

class UserService:
    def __init__(self, repo: UserRepository, notifier: NotificationService):
        self.repo = repo
        self.notifier = notifier
    
    def create_user(self, name: str, email: str) -> User:
        user = self.repo.create(name, email)
        self.notifier.send_welcome_email(user)
        return user

D — Dependency Inversion (зависимости от интерфейсов, не реализации)

# ❌ Плохо: зависит от конкретной БД
class UserService:
    def __init__(self):
        self.db = PostgreSQL()  # жёсткая зависимость
    
    def get_user(self, user_id: int):
        return self.db.query(User).get(user_id)

# ✓ Хорошо: зависит от интерфейса
from abc import ABC, abstractmethod

class IUserRepository(ABC):
    @abstractmethod
    def get(self, user_id: int) -> User:
        pass

class UserService:
    def __init__(self, repository: IUserRepository):
        self.repository = repository  # зависимость от интерфейса
    
    def get_user(self, user_id: int) -> User:
        return self.repository.get(user_id)

# Можно использовать любую реализацию
postgres_repo = PostgresUserRepository()
mongo_repo = MongoUserRepository()
memory_repo = MemoryUserRepository()  # для тестов!

service = UserService(memory_repo)  # легко подменить

7. DRY (Don't Repeat Yourself)

# ❌ Плохо: код повторяется
def get_user_page(page_num):
    users = db.query(User).filter(User.is_active == True)
    total = users.count()
    offset = (page_num - 1) * 10
    items = users.offset(offset).limit(10).all()
    return {"items": items, "total": total, "page": page_num}

def get_posts_page(page_num):
    posts = db.query(Post).filter(Post.is_published == True)
    total = posts.count()
    offset = (page_num - 1) * 10
    items = posts.offset(offset).limit(10).all()
    return {"items": items, "total": total, "page": page_num}

# ✓ Хорошо: извлекли общий паттерн
def paginate(query, page_num, page_size=10):
    total = query.count()
    offset = (page_num - 1) * page_size
    items = query.offset(offset).limit(page_size).all()
    return {"items": items, "total": total, "page": page_num}

def get_user_page(page_num):
    query = db.query(User).filter(User.is_active == True)
    return paginate(query, page_num)

def get_posts_page(page_num):
    query = db.query(Post).filter(Post.is_published == True)
    return paginate(query, page_num)

8. KISS (Keep It Simple, Stupid)

# ❌ Плохо: переусложнено
class UserFilterBuilder:
    def __init__(self):
        self.filters = []
    
    def add_name_filter(self, name):
        self.filters.append(lambda u: u.name == name)
        return self
    
    def add_age_filter(self, min_age, max_age):
        self.filters.append(lambda u: min_age <= u.age <= max_age)
        return self
    
    def build(self):
        def combined_filter(user):
            return all(f(user) for f in self.filters)
        return combined_filter

filter = UserFilterBuilder()
filter.add_name_filter("John")
filter.add_age_filter(25, 35)
results = [u for u in users if filter.build()(u)]

# ✓ Хорошо: просто
results = [
    u for u in users
    if u.name == "John" and 25 <= u.age <= 35
]

Мой production stack

# pyproject.toml dependencies
[tool.poetry.group.dev.dependencies]
ruff = "^0.1.0"
black = "^23.0.0"
mypy = "^1.0.0"
pytest = "^7.0.0"
pytest-cov = "^4.0.0"
pytest-asyncio = "^0.21.0"

# Скрипты
[tool.poetry.scripts]
lint = "ruff check . && black --check ."
format = "black . && ruff check --fix ."
type-check = "mypy ."
test = "pytest --cov=app"

Workflow перед коммитом:

make format   # черный форматер
make lint     # ruff проверка
make type-check  # mypy
make test     # pytest

Итог

Чистый код это не талант, это система:

  1. Линтеры (ruff) — находят ошибки
  2. Форматеры (black) — унифицируют стиль
  3. Type checking (mypy) — ловят ошибки типов
  4. Тесты (pytest) — гарантируют корректность
  5. Code review — второй взгляд
  6. CI/CD — автоматическая проверка
  7. SOLID — архитектура
  8. DRY/KISS — простота

Главное: эти инструменты работают, только если их использовать в pipeline'е. Один ruff не поможет, нужна вся система. Я использую их все, каждый день на работе.

С помощью чего можно добиться качественного и чистого кода? | PrepBro