Как читать чужой код и понимать его: гайд, как не разбить экран компьютера

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

1К открытий6К показов
Как читать чужой код и понимать его: гайд, как не разбить экран компьютера

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

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

В статье расскажем, как читать чужой код без стресса, а еще рассмотрим типичные ошибки и посоветуем крутые лайфхаки.

Разбираемся на практике

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

Структура проекта:

Library_project/

├── 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:
Как читать чужой код и понимать его: гайд, как не разбить экран компьютера 1
Как читать чужой код и понимать его: гайд, как не разбить экран компьютера 2

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

  • Go to Definition — видим, как создается объект:
Как читать чужой код и понимать его: гайд, как не разбить экран компьютера 3
Как читать чужой код и понимать его: гайд, как не разбить экран компьютера 4
  • Structure View — позволяет увидеть все методы и свойства классов в одном месте:
Как читать чужой код и понимать его: гайд, как не разбить экран компьютера 5
Как читать чужой код и понимать его: гайд, как не разбить экран компьютера 6

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

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

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

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

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

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

Как читать чужой код и понимать его: гайд, как не разбить экран компьютера 7

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

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

		

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

Как читать чужой код и понимать его: гайд, как не разбить экран компьютера 8

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

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

		
Как читать чужой код и понимать его: гайд, как не разбить экран компьютера 9

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

Как читать чужой код и понимать его: гайд, как не разбить экран компьютера 10

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Следите за новыми постами
Следите за новыми постами по любимым темам
1К открытий6К показов