Best Code Rule: завжди розділяйте введення, виведення та обробку

By Volodymyr Obrizan on Серпень 22, 2025 · Прочитати цю публікацію іншими мовами: English Russian

Коли ви тільки починаєте, написання коду зазвичай означає «зробити так, щоб працювало». Тож ви швидко складаєте скрипт, який запитує у користувача вхідні дані, робить щось розумне і виводить результат. Раз-два-три і готово.

Але ось у чому справа: цей швидкий і брудний стиль зашкодить вам у той момент, коли ваша програма повинна буде рости, змінюватися або повторно використовуватися.

Існує одне просте правило, яке відокремлює заплутані скрипти від чистого, професійного коду:

Завжди розділяйте вхідні дані, вивід і обробку.

Це правило змінює гру. Воно допомагає писати код, який легше: тестувати, налагоджувати, повторно використовувати і розширювати пізніше.

Вам не потрібно бути senior-розробником, щоб його дотримуватися. Просто потрібно побачити різницю. Давайте пройдемося по реальному прикладу — обробці табличних даних — і ви ніколи більше не напишете код, який склеює все разом.

У цьому блозі:

Крок 1: Ви пишете наївний, але робочий скрипт

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

total = 0
count = 0

while True:
    line = input("Enter salary or 'done': ")
    if line == "done":
        break
    total += float(line)
    count += 1

print(f"Average salary: {total / count}")

Він простий, і запуск цього коду дає нам саме те, що ми хотіли — середню зарплату для набору введених даних:

Enter salary or 'done': 100
Enter salary or 'done': 150
Enter salary or 'done': 200
Enter salary or 'done': 400
Enter salary or 'done': 300
Enter salary or 'done': done
Average salary: 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"Average salary: {total / count}")

Це просто і працює! Але зачекайте...

Крок 3: Усвідомлення: ви просто скопіювали і трохи змінили ту ж логіку

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

Розгляньте такі зміни:

  • Що, якщо ви захочете змінити спосіб обчислення середнього?
  • Що, якщо завтра ви захочете отримувати дані з API?
  • Що, якщо ви захочете зберігати результати в базу даних?

З поточним підходом, де вхід, обробка і вивід склеєні разом, ви будете копіювати, вставляти і адаптувати знову і знову, тонути в схожих, але різних версіях коду.

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

Це глухий кут. Ви написали код, який привʼязаний до свого вхідного джерела. Ви не можете його тестувати, не можете розширювати, і якщо хочете використати десь ще — починаєте з нуля.

Завжди розділяйте вхід, вивід і обробку

Коли ви тільки починаєте, спокуса написати все в одному великому блоці: зчитати вхід, зробити роботу і вивести результат — все заплутано разом. Це працює, звісно. Але це стає проблемою, як тільки ви хочете змінити, звідки або як приходять дані, або що ви з ними робите.

Давайте знову поглянемо на оригінальний приклад. Цей код робить три речі одночасно.

Вхід:

input("Enter salary or 'done': ")

Ви зчитуєте дані від користувача. Це вхід. Але вхід — це не лише input() з клавіатури. У реальних проектах дані можуть надходити з: файлу, JSON API-відповіді, веб-форми, аргументу командного рядка, запиту до бази даних, сенсора чи апаратного пристрою, навіть drag-and-drop у UI. Всі ці способи — це просто шляхи, якими дані потрапляють у вашу програму. І жоден з них не повинен змінювати вашу логіку обробки.

Якщо ви змішали вхід і логіку, доведеться переписувати все, коли зміниться джерело — і це той безлад, якого ми намагаємося уникнути.

Обробка:

total += float(line) and total / count

Ви робите роботу: конвертуєте рядки в числа, сумуєте, рахуєте, обчислюєте середнє. Але це змішано в циклі, де ви зчитуєте вхід.

Вивід:

print(f"Average salary: {total / count}")

Ви показуєте результат. Це вивід. Він також залежить від того, що попередній код написаний саме так. У цьому випадку програма виводить результат на екран — але вивід може мати багато інших форм: запис у файл (CSV, JSON, текст), відправка даних на веб-сторінку або мобільний додаток, повернення значення з функції, вставка рядків у базу даних, відображення графіка чи діаграми, відправка мережевої відповіді (наприклад, API або сокет), оновлення елемента UI на екрані.

Отже, ось золоте правило:

  • Вхід = як дані потрапляють у програму
  • Обробка = що ваша програма робить з цими даними
  • Вивід = як результати доставляються (друк, збереження, повернення тощо)

Завжди тримайте їх окремо. Кожен має бути чистою частиною вашого коду — ідеально функцією — яка робить лише одну роботу. Розділяючи вхід, обробку і вивід, ви отримуєте гнучкість.

Продовжимо.

Ви щойно усвідомили, що дублюєте логіку. Вона працює... але ви пишете той самий код двічі, і будь-яка зміна означає змінювати його двічі. Час для повороту сюжету.

Крок 4: Ви виділяєте обробку з вхідного безладу

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

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

Тепер ви оновлюєте оригінальну версію з введенням, щоб використовувати цю функцію:

salaries: list[float] = []

while True:
    line = input("Enter salary or 'done': ")
    if line == "done":
        break
    salaries.append(float(line))

print(f"Average salary: {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"Average salary: {calculate_average(salaries)}")

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

Ви починаєте розділяти обовʼязки, навіть не використовуючи термін «розділення обовʼязків».

Крок 6: Ви йдете ва-банк — розділяєте вхід, вивід і обробку

Ось найчистіша версія на сьогодні:

import csv

def get_salaries_from_input() -> list[float]:
    salaries = []
    while True:
        line = input("Enter salary or '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 salary: {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

Коментарі

  • Ще немає коментарів.

Увійти щоб залишити коментар.

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