Рассказываем, как поймать дзен, сесть за компьютер и спокойно разобраться в чужом коде. Ну, или в своем.
132 открытий137 показов
Представьте себе ситуацию: вы пришли в новую компанию, и вам сказали переписать чужой код. Ну, по крайней мере, сделать так, чтобы он работал. И вот вы сидите, ухватившись руками за голову, и пытаетесь понять, что делает эта функция и зачем нужна эта кнопка. Еще хуже — если нет документации. Рано или поздно с этим сталкиваются почти все прогеры, но без паники: прочесть, а, главное, понять чужой код — задача сложная, но выполнимая.
Разбираться в чужом коде — очень крутой навык, поскольку в нем вы можете найти новые приемы и подходы, посмотреть на логику решения конкретной задачи (можете сравнивать со своей), плюс быстрее адаптироваться к процессам в компании.
В статье расскажем, как читать чужой код без стресса, а еще рассмотрим типичные ошибки и посоветуем крутые лайфхаки.
Разбираемся на практике
Поскольку автор этой статьи — филолог по образованию и айтишник в душе, мы не придумали ничего лучше, чем создать код для библиотечной системы. Проект довольно сложный, поскольку в нем много файлов. В общем, давайте копаться.
Структура проекта:
Library_project/
Названы топ-10 проектов и сервисов, которые Google убила в 2024 годуtproger.ru
├── Library/
│ ├── models/
│ │ ├── book.py
│ │ └── catalog.py
│ ├── utils/
│ │ ├── search.py
│ │ └── reports.py
│ └── tests/
│ └── test_catalog.py
└── main.py
Код проекта:
# models/book.py from datetime import datetime class Book: """Класс, представляющий книгу в библиотеке""" def __init__(self, title, author, isbn): self.title = title self.author = author self.isbn = isbn self.is_available = True self.due_date = None self.borrower = None self.created_at = datetime.now() self.last_updated = datetime.now() def __str__(self): status = "доступна" if self.is_available else f"выдана {self.borrower}" return f"{self.title} ({self.author}) - {status}"
# models/catalog.py from datetime import datetime, timedelta from Library.models.book import Book class LibraryCatalog: """Управление библиотечным каталогом""" def __init__(self): self.books = {} self.borrowed_books = {} self.late_returns = set() self._load_initial_data() def _load_initial_data(self): """Загрузка начальных данных (в реальном проекте могла бы быть из БД)""" self.add_book("1984", "Джордж Оруэлл", "978-0451524935") self.add_book("Мастер и Маргарита", "Михаил Булгаков", "978-0141180144") def add_book(self, title, author, isbn): """Добавление новой книги в каталог""" if isbn in self.books: return False self.books[isbn] = Book(title, author, isbn) return True def borrow_book(self, isbn, borrower, days=14): """ Выдача книги читателю Args: isbn (str): ISBN книги borrower (str): Имя читателя days (int): Количество дней, на которое выдается книга Returns: str: Статус операции """ if isbn not in self.books: return "Книга не найдена" book = self.books[isbn] if not book.is_available: return "Книга уже выдана" # Устанавливаем дату возврата до изменения статуса книги book.due_date = datetime.now() + timedelta(days=days) book.is_available = False book.borrower = borrower book.last_updated = datetime.now() self.borrowed_books[isbn] = book return "Книга успешно выдана" def return_book(self, isbn): """ Возврат книги в библиотеку Args: isbn (str): ISBN возвращаемой книги Returns: str: Статус операции """ if isbn not in self.borrowed_books: return "Эта книга не была выдана" book = self.borrowed_books[isbn] current_time = datetime.now() # Сначала проверяем, не просрочили ли возврат if book.due_date and current_time > book.due_date: self.late_returns.add(isbn) # Затем обновляем статус книги book.is_available = True book.last_updated = current_time book.borrower = None book.due_date = None # Сбрасываем дату возврата после всех проверок del self.borrowed_books[isbn] return "Книга успешно возвращена" def get_borrowed_books(self): """ Получение списка всех выданных книг Returns: list: Список словарей с информацией о выданных книгах """ return [ { 'title': book.title, 'borrower': book.borrower, 'due_date': book.due_date } for book in self.borrowed_books.values() ] def find_book(self, search_term): """ Поиск книги по названию или автору Args: search_term (str): Поисковый запрос Returns: list: Список найденных книг """ results = [] search_term = search_term.lower() for book in self.books.values(): if (search_term in book.title.lower() or search_term in book.author.lower()): results.append(book) return results
# tests/test_catalog.py from Library.models.catalog import LibraryCatalog from Library.utils.search import BookSearch from Library.utils.reports import ReportGenerator def run_basic_tests(): """Базовое тестирование функциональности каталога""" catalog = LibraryCatalog() # Тест выдачи книги status = catalog.borrow_book("978-0451524935", "Иван Петров") print(f"Тест выдачи книги: {status}") # Тест поиска search_results = BookSearch.search_by_title(catalog, "Мастер") print(f"Результаты поиска: {[book.title for book in search_results]}") # Тест возврата книги return_status = catalog.return_book("978-0451524935") print(f"Тест возврата книги: {return_status}") # Тест генерации отчёта overdue_report = ReportGenerator.generate_overdue_report(catalog) print(f"Просроченные книги: {overdue_report}")
# utils/reports.py from datetime import datetime class ReportGenerator: """Генератор отчётов по библиотечному каталогу""" @staticmethod def generate_overdue_report(catalog): """Создание отчёта о просроченных книгах""" overdue_books = [] current_time = datetime.now() for book in catalog.borrowed_books.values(): if current_time > book.due_date: overdue_books.append({ 'title': book.title, 'borrower': book.borrower, 'days_overdue': (current_time - book.due_date).days }) return overdue_books
# utils/search.py class BookSearch: """Утилиты для поиска книг""" @staticmethod def search_by_title(catalog, title): """Поиск книг по названию""" title = title.lower() return [book for book in catalog.books.values() if title in book.title.lower()] @staticmethod def search_by_author(catalog, author): """Поиск книг по автору""" author = author.lower() return [book for book in catalog.books.values() if author in book.author.lower()]
# main.py from tests.test_catalog import run_basic_tests if __name__ == "__main__": run_basic_tests()
Представление о проекте есть, а теперь давайте по шагам читать чужой код.
Шаг 1: Поймите, что в принципе делает код
Первое, что нужно сделать при анализе чужого кода — понять его цель. Вместо того, чтобы сразу погружаться в реализацию, начните с конца: что делает программа и какую проблему она решает?
Давайте посмотрим на файл main.py:
# main.py from tests.test_catalog import run_basic_tests if __name__ == "__main__": run_basic_tests()
Из него понимаем, что:
- Проект связан с библиотекой (судя по названию модуля);
- Есть тестовый сценарий, в котором видно основную функциональность;
- Код организован в пакеты и модули.
Если запустим тесты, увидим следующее:
Тест выдачи книги: Книга успешно выдана Результаты поиска: ['Мастер и Маргарита'] Тест возврата книги: Книга успешно возвращена Просроченные книги: []
Теперь мы знаем, что система как минимум умеет:
- Выдавать книги;
- Искать книги по названию;
- Показывать статус.
Шаг 2: Изучите общую структуру кода
Когда вы поняли, что делает программа, нужно разобраться в ее архитектуре, и здесь важно задавать правильные вопросы:
Вопрос 1: Как организованы данные?
Помним, что у проекта есть четкая структура:
Library_project/
├── Library/
│ ├── models/
│ │ ├── book.py # Данные о книгах
│ │ └── catalog.py # Управление каталогом
│ ├── utils/
│ │ ├── search.py # Поиск
│ │ └── reports.py # Генерация отчетов
│ └── tests/
│ └── test_catalog.py # Тесты
└── main.py # Точка входа в приложение
Она говорит нам о том, что:
- Данные о книгах и каталоге разделены;
- Дополнительные функции лежат в отдельном модуле;
- Есть модуль для тестов;
- Система в целом следует принципам модульности.
Вопрос 2: Как взаимодействуют компоненты?
Посмотрим на импорты в catalog.py
:
from datetime import datetime, timedelta from Library.models.book import Book
Это показывает, что:
- Каталог зависит от класса Book;
- Приложение подтягивает даты (чтобы проверять сроки).
Вопрос 3: Какие главные операции выполняются в системе?
В классе LibraryCatalog
видим:
def borrow_book(self, isbn, borrower, days=14): def return_book(self, isbn): def find_book(self, search_term):
Отсюда понятно, что система может:
- Выдавать книги на конкретный срок;
- Возвращать книги;
- Делать поиск по каталогу.
Шаг 3: Изучите документацию и комментарии
При чтении чужого кода документация и комментарии — ваши лучшие друзья. Например:
class Book: """Класс, представляющий книгу в библиотеке""" def __init__(self, title, author, isbn): self.title = title self.author = author self.isbn = isbn self.is_available = True self.due_date = None self.borrower = None self.created_at = datetime.now() self.last_updated = datetime.now()
Здесь мы видим docstring, который кратко описывает назначение класса. Более наглядный пример — метод borrow_book
из файла /models/catalog.py:
def borrow_book(self, isbn, borrower, days=14): """ Выдача книги читателю Args: isbn (str): ISBN книги borrower (str): Имя читателя days (int): Количество дней, на которое выдается книга Returns: str: Статус операции """
Шаг 4: Проанализируйте главную точку входа в программу
Точка входа — место, с которого начинается выполнение программы. В нашем случае это main.py, но давайте посмотрим внимательнее, на что он ссылается:
#tests/test_catalog.py from Library.models.catalog import LibraryCatalog from Library.utils.search import BookSearch from Library.utils.reports import ReportGenerator def run_basic_tests(): """Базовый тест функциональности каталога""" catalog = LibraryCatalog() # Тест выдачи книги status = catalog.borrow_book("978-0451524935", "Иван Петров") print(f"Тест выдачи книги: {status}") # Тест поиска search_results = BookSearch.search_by_title(catalog, "Мастер") print(f"Результаты поиска: {[book.title for book in search_results]}") # Тест возврата книги return_status = catalog.return_book("978-0451524935") print(f"Тест возврата книги: {return_status}") # Тест генерации отчёта overdue_report = ReportGenerator.generate_overdue_report(catalog) print(f"Просроченные книги: {overdue_report}")
Из этого кода видно, что первым делом импортируются классы: LibraryCatalog
из файла /models/catalog.py, BookSearch
из файла /utils/search.py и класс ReportGenerator
из файла /utils/reports.py.
Далее — функция run_basic_tests
, в которой создается экземляр класса LibraryCatalog. В переменную status
записывается значение вызова метода borrow_book, который возвращает статус выдачи книги.
Затем выводится информация о тесте выдачи книги. По аналогии, в переменные search_results
, return_status
, overdue_report
записываются значения вызова методов search_by_title
, return_book
и generate_overdue_report
соответственно.
Шаг 5: Определите ключевые функции и классы
В любом проекте есть основные компоненты, вокруг которых строится вся логика. В нашем случае это:
LibraryCatalog — центральный класс. В нем реализованы основные методы управления библиотекой, такие как: добавление новой книги (add_book
), выдача книги читателю (borrow_book
), возврат книги в библиотеку (return_book
), получение списка всех выданных книг (get_borrowed_books
) и поиск книги (find_book
).
#models/catalog.py class LibraryCatalog: """Класс для управления библиотечным каталогом""" def __init__(self): self.books = {} self.borrowed_books = {} self.late_returns = set() self._load_initial_data()
BookSearch — утилитный класс. В нем реализованы методы поиска книги по автору и по названию.
# utils/search.py class BookSearch: """Утилиты для поиска книг""" @staticmethod def search_by_title(catalog, title): """Поиск книг по названию""" title = title.lower() return [book for book in catalog.books.values() if title in book.title.lower()] @staticmethod def search_by_author(catalog, author): """Поиск книг по автору""" author = author.lower() return [book for book in catalog.books.values() if author in book.author.lower()]
Шаг 6: Используйте инструменты IDE для навигации и анализа
В современных IDE есть мощные инструменты для анализа кода. Рассмотрим несколько основных на примере нашего «чужого» кода.
- Find Usages:
Здесь мы ищем метод borrow.book и видим, как он используется в тестах и других частях кода.
- Go to Definition — видим, как создается объект:
- Structure View — позволяет увидеть все методы и свойства классов в одном месте:
Шаг 7: Отладка и запуск — пошаговое выполнение кода
Один из лучших способов понять, как работает чужой код — пройтись по нему в отладчике.
С помощью отладчика можно видеть значения всех переменных на момент исполнения, проверять различные условия в реальном времени, а также понимать последовательность выполнения кода.
1. Установим breakpoint в методе borrow_book:
def borrow_book(self, isbn, borrower, days=14): if isbn not in self.books: # breakpoint return "Книга не найдена"
2. Запустим тест и пошагово посмотрим:
Этот скриншот показывает значения переменных:
borrower = 'Иван Петров' days = 14 isbn = '978-0451524935' Состояние каталога с книгами и их статусами
3. Затем можно посмотреть пошаговое выполнение кода с помощью функций Step Over, Step Into, Step Out:
4. Теперь можно попробовать выдать 2 раза одну книгу разным людям и запустить отладку:
status = catalog.borrow_book("978-0451524935", "Петр Петров") print(f"Тест выдачи книги: {status}") status = catalog.borrow_book("978-0451524935", "Иван Иванов") print(f"Тест выдачи книги: {status}")
На этом скриншоте мы видим, что книга с этим id доступна для выдачи. Продолжим выполнять код, нажимаем на кнопку Resume Program:
Теперь мы можем увидеть, что книга уже была выдана. То же самое появляется и в выводе при завершении программы. Так мы пошагово отслеживаем проверку выдачи книги и изменение статуса ее доступности. В общем, полезная штука, если в коде сложная логика, плюс можно найти потенциальные проблемы.
Несколько советов, чтобы не сойти с ума, читая чужой код
Рисуйте диаграммы
Будущая версия вас обязательно скажет спасибо за то, что этот хаос переменных и циклов вы оформили в виде понятной схемы — что, зачем и куда ведет; а еще оставляли комментарии типа «Вот это не трогай, оно тебя сожрет». На самом деле, визуально представлять чужой код — огромный плюс, особенно если вам потом придется работать над ним вместе с коллегами.
Создайте мини-документацию для себя
Представьте, что ведете личный дневник по горячим следам. Если в коде вы наткнулись на нечто неочевидное, сделайте краткую запись: что это за модуль, зачем он тут, и как он связан с другими частями. Через неделю, когда вы снова откроете проект, сами себе скажете: «Ого, какой я молодец, всё прописал!».
Не пытайтесь понять весь код целиком
Если вы считаете, что сядете и поймете весь код за один присест — пересчитайте. Это наш код с библиотекой не такой мудреный, а бывают и проекты, где десятки файлов, подключение API и сотни циклов. Даже опытные разработчики начинают с малого: выбирают один модуль, функцию или файл, и разбираются в них, а уже потом переходят к остальному.
Оставайтесь в контексте
Если вы изучаете код без понимания, где и как он применяется, это всё равно что настраивать выключенный монитор. Понять контекст можно через вопросы, например: «Что за проект? Какая целевая аудитория? Какие задачи решает этот код?». Например, вы понимаете, что этот модуль связан с оплатами, значит, нужно разобраться, как работает система транзакций.
Не забывайте про библиотеки
Если в чужом коде много сторонних библиотек и фреймворков, о которых вы могли даже не слышать, обязательно их изучите — именно здесь появляется много багов. И в целом, узнавать новые инструменты — полезно и весело, поскольку они могут выполнять за вас большую часть работы.
Поздравляем, вы прошли игру. Читать чужой код — практически искусство. Самое важное помнить, что миллионы разработчиков по всему миру с вами в одной лодке. Рассказывайте в комментах, какие проблемы возникали у вас при чтении чужого кода.
Кстати! Хочешь разбираться в коде быстрее и эффективнее? Собрали для тебя полезные советы тут.