Best Code Rule: всегда разделяйте ввод, вывод и обработку данных
By Volodymyr Obrizan on Август 22, 2025 · Прочитать этот пост на других языках: English Ukrainian
Когда вы только начинаете, написание кода обычно означает «сделать так, чтобы работало». Поэтому вы быстро пишете скрипт, который запрашивает ввод пользователя, делает что-то умное и выводит результат. Раз-два-три и готово.
Но вот в чем дело: такой быстрый и грязный стиль навредит вам в тот момент, когда ваша программа должна будет расти, изменяться или переиспользоваться.
Есть одно простое правило, которое отделяет грязные скрипты от чистого, профессионального кода:
Всегда разделяйте ввод, вывод и обработку.
Это правило меняет игру. Оно помогает писать код, который легче: тестировать, отлаживать, переиспользовать и развивать (расширять) в будущем.
Вам не нужно быть старшим разработчиком, чтобы следовать ему. Нужно просто увидеть разницу. Давайте пройдемся по реальному примеру — обработке табличных данных — и вы больше никогда не напишете код, который «склеивает всё вместе».
В этом блоге:
- Шаг 1: Вы пишете наивный, но работающий скрипт
- Шаг 2: Теперь нужно читать из CSV-файла
- Шаг 3: Осознание: вы просто скопировали и подправили ту же логику
- Всегда разделяйте ввод, вывод и обработку
- Шаг 4: Вы выносите обработку из мешанины с вводом
- Шаг 5: Вы рефакторите и версию с файлом
- Шаг 6: Вы идёте до конца — разделяете ввод, вывод и обработку
- Шаг 7: Посмотрите, что вы приобрели
- Итог
Шаг 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 надежную функцию, которая делает настоящую работу. Один мозг. Ноль дублирования. Бесконечное облегчение.
Это не излишняя инженерия. Это минимальное, чистое разделение — и это всё, что нужно, чтобы избежать хаоса скриптов-склейок.
Итог
Если вы запомните из этого поста только одно, пусть это будет:
Никогда не смешивайте ввод, обработку и вывод. Никогда.
Даже в маленьких скриптах такое разделение экономит время и сохраняет гибкость кода. Особенно когда ваши проекты растут.