Best Code Rule: всегда разделяйте ввод, вывод и обработку данных

By Volodymyr Obrizan on Август 22, 2025 · Прочитать этот пост на других языках: English Ukrainian

Когда вы только начинаете, написание кода обычно означает «сделать так, чтобы работало». Поэтому вы быстро пишете скрипт, который запрашивает ввод пользователя, делает что-то умное и выводит результат. Раз-два-три и готово.

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

Есть одно простое правило, которое отделяет грязные скрипты от чистого, профессионального кода:

Всегда разделяйте ввод, вывод и обработку.

Это правило меняет игру. Оно помогает писать код, который легче: тестировать, отлаживать, переиспользовать и развивать (расширять) в будущем.

Вам не нужно быть старшим разработчиком, чтобы следовать ему. Нужно просто увидеть разницу. Давайте пройдемся по реальному примеру — обработке табличных данных — и вы больше никогда не напишете код, который «склеивает всё вместе».

В этом блоге:

Шаг 1: Вы пишете наивный, но работающий скрипт

Алгоритм довольно простой: считывать значение с клавиатуры — накапливать — повторять — делить на количество:

total = 0
count = 0

while True:
    line = input("Введите зарплату или 'done': ")
    if line == "done":
        break
    total += float(line)
    count += 1

print(f"Средняя зарплата: {total / count}")

Это просто, и при запуске мы получаем именно то, что хотели — среднюю зарплату для набора вводимых данных:

Введите зарплату или 'done': 100
Введите зарплату или 'done': 150
Введите зарплату или 'done': 200
Введите зарплату или 'done': 400
Введите зарплату или 'done': 300
Введите зарплату или 'done': done
Средняя зарплата: 230.0

Кажется, наша задача выполнена и выполнена хорошо.

Шаг 2: Теперь нужно читать из CSV-файла

На следующий день вы понимаете, что вводить данные о зарплатах с клавиатуры неудобно. Хотелось бы читать из широко используемого формата данных — CSV — например такого:

name,salary
Alice,50000
Bob,60000
Charlie,55000

Вы думаете: «Окей, я просто скопирую и адаптирую свой код, чтобы читать из файла вместо input()». Вот что у вас получается:

import csv

total = 0
count = 0

with open("employees.csv") as file:
    reader = csv.DictReader(file)
    for row in reader:
        total += float(row["salary"])
        count += 1

print(f"Средняя зарплата: {total / count}")

Это просто и работает! Но подождите...

Шаг 3: Осознание: вы просто скопировали и подправили ту же логику

Теперь ваша логика вычисления средней зарплаты существует в двух местах: один раз для ввода пользователя, один раз для CSV-файла. Та же математика. Та же структура. Дублирование. Немного разные источники.

Подумайте о следующих изменениях:

  • Что если вы захотите изменить способ вычисления среднего?
  • Что если завтра захотите получать данные из API?
  • Что если захотите сохранять результаты в базу данных?

С текущим подходом, где ввод, обработка и вывод слиты вместе, вы будете копировать-вставлять и адаптировать снова и снова, тонуть в похожих, но разных версиях кода.

И вот главный момент: вы не можете переиспользовать ни одну из частей. Вы не почувствуете мощь синергии, когда можно комбинировать разные функции, чтобы получить новое ценное действие почти мгновенно!

Это тупик. Вы написали код, привязанный к своему вводу. Вы не можете его протестировать, не можете расширить, и если хотите использовать где-то еще — начинаете с нуля.

Всегда разделяйте ввод, вывод и обработку

Когда вы только начинаете, заманчиво написать всё в одном большом блоке: считывать ввод, делать работу и выводить результат — всё переплетено. Это работает, конечно. Но становится проблемой, как только вы захотите изменить, откуда или как поступают данные, или что вы с ними делаете.

Давайте снова посмотрим на исходный пример. Этот код делает три вещи одновременно.

Ввод:

input("Введите зарплату или 'done': ")

Вы считываете данные от пользователя. Это ввод. Но ввод — это не только input() с клавиатуры. В реальных проектах данные могут поступать из: файла, JSON API, веб-формы, аргумента командной строки, запроса к базе данных, сенсора или аппаратного устройства, даже drag-and-drop в интерфейсе. Все это просто способы, которыми данные попадают в вашу программу. И ни один из них не должен менять логику обработки.

Если вы смешали ввод и логику, вам придется переписывать всё при смене источника — и это тот беспорядок, которого мы пытаемся избежать.

Обработка:

total += float(line) и total / count

Вы делаете работу: преобразуете строки в числа, суммируете, считаете, усредняете. Но это смешано с циклом, где вы читаете ввод.

Вывод:

print(f"Средняя зарплата: {total / count}")

Вы показываете результат. Это вывод. Он также зависит от того, что предыдущий код написан именно так. В данном случае программа выводит результат на экран — но вывод может принимать множество других форм: запись в файл (CSV, JSON, текст), отправка данных на веб-страницу или мобильное приложение, возврат значения из функции, вставка строк в базу данных, отрисовка графика или диаграммы, отправка сетевого ответа (например, API или сокет), обновление элемента интерфейса на экране.

Итак, золотое правило:

  • Ввод = как данные попадают в программу
  • Обработка = что ваша программа делает с этими данными
  • Вывод = как результаты доставляются (печать, сохранение, возврат и т.д.)

Держите их раздельно. Всегда. Каждый должен быть своей чистой частью кода — желательно функцией — которая выполняет только одну задачу. Разделяя ввод, обработку и вывод, вы получаете гибкость.

Давайте продолжим.

Вы только что осознали, что дублируете логику. Это работает... но вы пишете один и тот же код дважды, и любое изменение значит менять его дважды. Пора на поворот сюжета.

Шаг 4: Вы выносите обработку из мешанины с вводом

Вместо того, чтобы запутывать вычисление средней зарплаты в вводе пользователя или чтении файла, вы выносите это в отдельную функцию. Здесь мы используем аннотации типов, чтобы подчеркнуть, что функция обработки принимает аргумент (список чисел с плавающей точкой list[float]) и что она возвращает (float):

def calculate_average(salaries: list[float]) -> float:
    return sum(salaries) / len(salaries)

Теперь вы обновляете исходную версию с вводом, чтобы использовать эту функцию:

salaries: list[float] = []

while True:
    line = input("Введите зарплату или 'done': ")
    if line == "done":
        break
    salaries.append(float(line))

print(f"Средняя зарплата: {calculate_average(salaries)}")

Гораздо чище. Ваш ввод всё еще немного грязный, но логика обработки теперь — переиспользуемый строительный блок. Прогресс.

Шаг 5: Вы рефакторите и версию с файлом

Теперь вы переиспользуете ту же функцию с данными из CSV:

import csv

salaries: list[float] = []

with open("employees.csv") as file:
    reader = csv.DictReader(file)
    for row in reader:
        salaries.append(float(row["salary"]))

print(f"Средняя зарплата: {calculate_average(salaries)}")

Готово! Теперь и скрипт с вводом, и скрипт с файлом используют одну и ту же основную логику.

Вы начинаете разделять ответственность, даже не используя термин «разделение ответственности».

Шаг 6: Вы идёте до конца — разделяете ввод, вывод и обработку

Вот самая чистая версия на данный момент:

import csv

def get_salaries_from_input() -> list[float]:
    salaries = []
    while True:
        line = input("Введите зарплату или 'done': ")
        if line == "done":
            break
        salaries.append(float(line))
    return salaries

def get_salaries_from_csv(filename) -> list[float]:
    with open(filename) as file:
        reader = csv.DictReader(file)
        return [float(row["salary"]) for row in reader]

def calculate_average(salaries: list[float]) -> float:
    return sum(salaries) / len(salaries)

def print_average(salaries: list[float]) -> None:
    average = calculate_average(salaries)
    print(f"Средняя зарплата: {average}")

Обратите внимание, что get_salaries_from_input и get_salaries_from_csv обе возвращают список чисел с плавающей точкой (list[float]), то есть по сути делают одно и то же (получают входные данные), но разными способами: ввод с клавиатуры и чтение из файла.

Теперь вы можете выбрать источник ввода:

# Ввод данных вручную:
salaries = get_salaries_from_input()
print_average(salaries)

# Чтение из файла:
salaries = get_salaries_from_csv("employees.csv")
print_average(salaries)

Шаг 7: Посмотрите, что вы приобрели

Теперь вы можете:

  • Менять источники ввода без изменения обработки
  • Тестировать обработку отдельно
  • Переиспользовать обработку для API, веб-форм, GUI и т.д.
  • Избегать дублирования логики
  • Писать чище и более поддерживаемый код

Например, посмотрите, как теперь легко тестировать ваш код с помощью pytest:

import pytest
from your_module import calculate_average

def test_calculate_average_basic():
    data = [50000, 60000, 55000]
    result = calculate_average(data)
    assert result == 55000

def test_calculate_average_single_entry():
    assert calculate_average([42000]) == 42000

def test_calculate_average_zero_values():
    assert calculate_average([0, 0, 0]) == 0

def test_calculate_average_raises_on_empty_list():
    with pytest.raises(ZeroDivisionError):
        calculate_average([])

Иначе, пытаться тестировать код, где смешаны ввод и обработка — это как пытаться отладить работающий блендер. Когда ваша логика переплетена с input(), чтением файлов или кликами пользователя, вы не можете легко подать тестовые данные — приходится каждый раз симулировать реальный ввод. Это значит писать неудобные обходные пути, мокать встроенные функции или, что хуже, вручную вводить значения при каждом запуске теста. Это хрупко, медленно и раздражает. И поскольку вы не можете изолировать «мыслящую» часть кода, ошибки проходят незамеченными, крайние случаи упускаются, а рефакторинг становится рискованным. Тестирование должно быть хирургическим — но с «спагетти» так не получится.

Подумайте о сложности кода: допустим, ваша программа поддерживает 4 источника ввода — например, ввод пользователя, CSV-файлы, JSON API и базу данных — и 3 формата вывода — например, печать в консоль, сохранение в файл или отправка веб-ответа. Если вы смешаете логику с вводом и выводом, у вас теперь 4 × 3 = 12 разных путей кода для управления. Двенадцать мест, где могут прятаться ошибки. Двенадцать мест, где мелкие изменения превращаются в большие головные боли по поддержке. А теперь представьте, что добавили еще один источник ввода — теперь у вас 15 комбинаций. Еще два вывода? Теперь 20. Такой рост — экспоненциальный, а не линейный. Но если вы чисто разделите ввод, вывод и обработку, вам нужно написать всего 4 адаптера ввода, 3 обработчика вывода и 1 надежную функцию, которая делает настоящую работу. Один мозг. Ноль дублирования. Бесконечное облегчение.

Это не излишняя инженерия. Это минимальное, чистое разделение — и это всё, что нужно, чтобы избежать хаоса скриптов-склейок.

Итог

Если вы запомните из этого поста только одно, пусть это будет:

Никогда не смешивайте ввод, обработку и вывод. Никогда.

Даже в маленьких скриптах такое разделение экономит время и сохраняет гибкость кода. Особенно когда ваши проекты растут.

Telegram
Viber
LinkedIn
WhatsApp

Комментарии

  • Комментариев пока нет.

Войти чтобы оставить комментарий.

← Назад к блогу