Итоговое консольное приложение
Содержание курса
Типичные ошибки интеграции и способы их избежать
Когда три десятка строк кода, написанных по отдельности, объединяются в одно приложение, появляются специфические ошибки — не в синтаксисе, а в логике соединения частей. Разберём три из них, потому что каждая из них тихо ломает приложение, не вызывая очевидного сбоя.
Ошибка 1. Функция «обновляет» список, но изменения не видны снаружи
Эта ошибка возникает, когда внутри функции имя переменной переприсваивается новому объекту вместо изменения оригинала.
# Неправильно: notes_list внутри функции теперь указывает на новый объект
def add_note_broken(notes_list, notes_dict):
text = input("Текст заметки: ").strip()
note_id = f"note_{len(notes_list) + 1}"
note = {"id": note_id, "text": text}
notes_list = notes_list + [note] # создаёт новый список, не меняет оригинал
notes_dict[note_id] = note
После вызова add_note_broken словарь обновится, а список в main() останется прежним. Причина: notes_list + [note] создаёт новый объект списка и присваивает его локальному имени notes_list. Оригинальный список в main() эта операция не трогает.
Исправление простое — менять объект на месте через append:
# Правильно: append изменяет оригинальный список
def add_note(notes_list, notes_dict):
text = input("Текст заметки: ").strip()
note_id = f"note_{len(notes_list) + 1}"
note = {"id": note_id, "text": text}
notes_list.append(note) # изменяет тот же объект, что живёт в main()
notes_dict[note_id] = note
Правило: методы append, remove, pop, присваивание по ключу словаря — всё это работает с оригинальным объектом. Оператор + для списков и | для словарей — создают новый объект.
Ошибка 2. bare except скрывает настоящие ошибки
except без указания типа исключения перехватывает буквально всё, включая KeyboardInterrupt (Ctrl+C), SystemExit и ошибки самого кода.
# Неправильно: bare except поглощает всё
try:
with open(filename, "r", encoding="utf-8") as f:
for line in f:
note_id, text = line.strip().split("|")
...
except: # скрывает и FileNotFoundError, и ValueError, и любую другую ошибку
pass
Если в файле окажется строка без символа |, split вернёт список из одного элемента, и распаковка в note_id, text бросит ValueError. С bare except эта ошибка исчезнет в тишину — данные не загрузятся, но программа продолжит работу как ни в чём не бывало.
Исправление — перехватывать только ожидаемый тип исключения:
# Правильно: называем конкретный тип
try:
with open(filename, "r", encoding="utf-8") as f:
...
except FileNotFoundError:
print("Файл заметок не найден, начинаем с чистого листа.")
FileNotFoundError — единственное штатное исключение при первом запуске: файла ещё нет. Всё остальное пусть падает явно, чтобы баг было легко найти. Полная реализация load_notes с этим блоком уже показана в предыдущей секции.
Ошибка 3. Режим 'w' открывается после каждой операции
Режим 'w' при открытии файла немедленно обнуляет его содержимое — ещё до того, как будет записана хоть одна строка. Если вызывать save_notes после каждого добавления или удаления заметки, это означает постоянную перезапись файла.
Сама по себе перезапись — не ошибка, если список на момент вызова полный. Проблема возникает, когда запись ведётся не по всему списку, а только по текущей операции:
# Опасный вариант: в файл попадает только последняя заметка
def add_and_save_broken(notes_list, notes_dict, filename="notes.txt"):
text = input("Текст заметки: ").strip()
note_id = f"note_{len(notes_list) + 1}"
note = {"id": note_id, "text": text}
notes_list.append(note)
notes_dict[note_id] = note
with open(filename, "w", encoding="utf-8") as f:
f.write(f"{note['id']}|{note['text']}\n") # только что добавленная — остальные стёрты!
Режим 'w' должен использоваться один раз — когда нужно сохранить весь список целиком:
# Правильно: записываем весь список за один раз
def save_notes(notes_list, filename="notes.txt"):
with open(filename, "w", encoding="utf-8") as f:
for note in notes_list:
f.write(f"{note['id']}|{note['text']}\n")
print("Заметки сохранены.")
Эта функция вызывается только по явной команде «сохранить» или при выходе — не после каждой операции. Если нужно добавлять строки в файл без перезаписи, используют режим 'a', но в архитектуре этого приложения он не нужен: проще хранить актуальный список в памяти и сохранять его разом.
Все три ошибки объединяет одно: они не вызывают SyntaxError и не останавливают программу. Код «работает», но делает не то. Именно поэтому при отладке интеграционного кода стоит проверять не только «запустилось ли», но и «действительно ли данные там, где я ожидаю».
