📜 Старый подход: наивный ввод
selected_index = int(input("Enter department number: "))
selected_department = departments[selected_index - 1]
print("You selected:", selected_department)
Что здесь происходит: программа ожидает корректный ввод. Если пользователь введёт буквы или номер вне диапазона, программа упадёт с ValueError или IndexError.
❌ Почему так не надо
- Любой текст вместо числа вызывает
ValueErrorи аварийное завершение. - Номер
0или999приводит кIndexError. - Пользователь не получает шанс исправить ошибку.
✅ Современный подход: цикл с валидацией
def read_department_number(departments):
while True:
try:
selected_index = int(input("Enter department number: "))
if 1 <= selected_index <= len(departments):
return selected_index
print("Invalid department number. Please try again.")
except ValueError:
print("Invalid input. Please enter a number.")
selected_index = read_department_number(departments)
selected_department = departments[selected_index - 1]
Что улучшилось: функция read_department_number отвечает только за ввод. Она проверяет и тип, и диапазон, и повторяет запрос при ошибке.
- Программа не падает на некорректном вводе.
- Логика ввода вынесена в отдельную функцию и легко тестируется.
- Главная функция остаётся короткой и читаемой.
📜 Старый подход: f-string SQL
condition = input("Enter condition: ").strip()
value = float(input("Enter salary: "))
query = f"""
SELECT first_name, last_name, job_title, salary
FROM employees
WHERE departments.department_name = %s AND salary {condition} %s
"""
cursor.execute(query, (selected_department, value))
Что здесь происходит: значение condition вставляется прямо в текст SQL. Если condition пришёл от пользователя, злоумышленник может подменить оператор на произвольный SQL-код.
✅ Современный подход: белый список операторов
valid_operators = {'>', '<', '=', '>=', '<='}
condition = input("Enter condition: ").strip()
if condition not in valid_operators:
print("Invalid condition.")
else:
value = float(input("Enter salary: "))
query = f"""
SELECT first_name, last_name, job_title, salary
FROM employees
WHERE departments.department_name = %s AND salary {condition} %s
"""
cursor.execute(query, (selected_department, value))
Что улучшилось: оператор проверяется на допустимость, а само число зарплаты передаётся через параметр %s. Это сочетает гибкость запроса и безопасность.
- SQL-инъекция через оператор невозможна.
- Поддерживаются только известные операторы сравнения.
- Значение зарплаты экранируется библиотекой PyMySQL.