Главная Веб-разработка Как читать чужой код – Как понимать не свой код – Tproger

Как читать чужой код – Как понимать не свой код – Tproger

от admin

Рассказываем, как поймать дзен, сесть за компьютер и спокойно разобраться в чужом коде. Ну, или в своем.

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()  		

Из него понимаем, что:

  1. Проект связан с библиотекой (судя по названию модуля);
  2. Есть тестовый сценарий, в котором видно основную функциональность;
  3. Код организован в пакеты и модули.

Если запустим тесты, увидим следующее:

			Тест выдачи книги: Книга успешно выдана Результаты поиска: ['Мастер и Маргарита'] Тест возврата книги: Книга успешно возвращена Просроченные книги: []  		

Теперь мы знаем, что система как минимум умеет:

  • Выдавать книги;
  • Искать книги по названию;
  • Показывать статус.

Шаг 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:

Как читать чужой код - Как понимать не свой код - Tproger

Как читать чужой код - Как понимать не свой код - Tproger

Здесь мы ищем метод borrow.book и видим, как он используется в тестах и других частях кода.

  • Go to Definition — видим, как создается объект:

Как читать чужой код - Как понимать не свой код - Tproger

Как читать чужой код - Как понимать не свой код - Tproger

  • Structure View — позволяет увидеть все методы и свойства классов в одном месте:

Как читать чужой код - Как понимать не свой код - Tproger

Как читать чужой код - Как понимать не свой код - Tproger

Шаг 7: Отладка и запуск — пошаговое выполнение кода

Один из лучших способов понять, как работает чужой код — пройтись по нему в отладчике.

С помощью отладчика можно видеть значения всех переменных на момент исполнения, проверять различные условия в реальном времени, а также понимать последовательность выполнения кода.

1. Установим breakpoint в методе borrow_book:

			def borrow_book(self, isbn, borrower, days=14):      if isbn not in self.books: # breakpoint        return "Книга не найдена" 		

2. Запустим тест и пошагово посмотрим:

Как читать чужой код - Как понимать не свой код - Tproger

Этот скриншот показывает значения переменных:

			borrower = 'Иван Петров' days = 14 isbn = '978-0451524935' Состояние каталога с книгами и их статусами  		

3. Затем можно посмотреть пошаговое выполнение кода с помощью функций Step Over, Step Into, Step Out:

Как читать чужой код - Как понимать не свой код - Tproger

4. Теперь можно попробовать выдать 2 раза одну книгу разным людям и запустить отладку:

			status = catalog.borrow_book("978-0451524935", "Петр Петров") print(f"Тест выдачи книги: {status}") status = catalog.borrow_book("978-0451524935", "Иван Иванов") print(f"Тест выдачи книги: {status}")  		

Как читать чужой код - Как понимать не свой код - Tproger

На этом скриншоте мы видим, что книга с этим id доступна для выдачи. Продолжим выполнять код, нажимаем на кнопку Resume Program:

Как читать чужой код - Как понимать не свой код - Tproger

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

Несколько советов, чтобы не сойти с ума, читая чужой код

Рисуйте диаграммы

Будущая версия вас обязательно скажет спасибо за то, что этот хаос переменных и циклов вы оформили в виде понятной схемы — что, зачем и куда ведет; а еще оставляли комментарии типа «Вот это не трогай, оно тебя сожрет». На самом деле, визуально представлять чужой код — огромный плюс, особенно если вам потом придется работать над ним вместе с коллегами.

Создайте мини-документацию для себя

Представьте, что ведете личный дневник по горячим следам. Если в коде вы наткнулись на нечто неочевидное, сделайте краткую запись: что это за модуль, зачем он тут, и как он связан с другими частями. Через неделю, когда вы снова откроете проект, сами себе скажете: «Ого, какой я молодец, всё прописал!».

Не пытайтесь понять весь код целиком

Если вы считаете, что сядете и поймете весь код за один присест — пересчитайте. Это наш код с библиотекой не такой мудреный, а бывают и проекты, где десятки файлов, подключение API и сотни циклов. Даже опытные разработчики начинают с малого: выбирают один модуль, функцию или файл, и разбираются в них, а уже потом переходят к остальному.

Оставайтесь в контексте

Если вы изучаете код без понимания, где и как он применяется, это всё равно что настраивать выключенный монитор. Понять контекст можно через вопросы, например: «Что за проект? Какая целевая аудитория? Какие задачи решает этот код?». Например, вы понимаете, что этот модуль связан с оплатами, значит, нужно разобраться, как работает система транзакций.

Не забывайте про библиотеки

Если в чужом коде много сторонних библиотек и фреймворков, о которых вы могли даже не слышать, обязательно их изучите — именно здесь появляется много багов. И в целом, узнавать новые инструменты — полезно и весело, поскольку они могут выполнять за вас большую часть работы.

Поздравляем, вы прошли игру. Читать чужой код — практически искусство. Самое важное помнить, что миллионы разработчиков по всему миру с вами в одной лодке. Рассказывайте в комментах, какие проблемы возникали у вас при чтении чужого кода.

Кстати! Хочешь разбираться в коде быстрее и эффективнее? Собрали для тебя полезные советы тут.

Похожие статьи