Промпты для разбора кода: от регулярки до стек-трейса
Шестнадцать готовых вопросов к объяснителю: в каждом уже лежит фрагмент кода, регулярка, команда или стек-трейс и сказано, кому и насколько подробно объяснять. Подставьте свой кусок, нажмите «Применить» и читайте разбор.
Разобрать чужой код
Объяснить чужой код приходится чаще, чем писать свой: функция без единого комментария, построчный разбор каждой строки, код на незнакомом языке программирования и причина, почему написано именно так, а не иначе.
Функция без комментариев
Переводит доставшийся по наследству код в одну фразу о том, что он на самом деле считает.
def recalc(items, rate): total = 0 for it in items: if it.status != "cancelled": total += it.price * it.qty if total > 10000: total = total * (1 - rate) return round(total, 2)# достался от прошлого разработчика, комментариев нет — объясни, что эта функция считает и при каких условиях
Функция считает сумму заказа со скидкой.1. Складывает цену на количество по всем позициям, пропуская отменённые: позиция со status "cancelled" в сумму не попадает.2. Если набралось больше 10000, к итогу применяется rate: значение 0.1 означает минус десять процентов.3. Результат округляется до двух знаков, то есть до копеек.Важная деталь, которую видно только из кода: скидка считается от всей суммы, а не от превышения над порогом. Заказ на 10001 после скидки окажется дешевле заказа на 9999.
Код на незнакомом языке
Объясняет чужую конструкцию через ту, которую вы уже знаете в своём языке.
import functoolsdef retry(times): def decorator(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): for attempt in range(times): try: return fn(*args, **kwargs) except TimeoutError: continue raise RuntimeError("all attempts failed") return wrapper return decorator# я пишу на PHP, декораторы вижу впервые — объясни через привычные мне понятия
Это декоратор с параметром. В PHP ближайший знакомый вам аналог — функция, которая возвращает замыкание, обёрнутое вокруг другой функции.Читается изнутри наружу:- retry(3) возвращает decorator;- decorator получает вашу функцию и возвращает wrapper;- строка @retry(3) над функцией означает ровно fn = retry(3)(fn) — после этого имя функции указывает на wrapper.Сам wrapper вызывает исходную функцию до трёх раз и молча глотает TimeoutError, а после последней неудачи бросает RuntimeError. functools.wraps нужен, чтобы обёртка сохранила имя и докстринг оригинала, иначе в трейсбеке и в отладчике будет везде видно wrapper.
Построчный разбор
Идёт по фрагменту строка за строкой — для случая, когда непонятно не целое, а конкретное место.
def top_customers(orders, n): totals = dict() for order in orders: totals[order.customer_id] = totals.get(order.customer_id, 0) + order.total ranked = sorted(totals.items(), key=lambda pair: -pair[1]) return ranked[:n]# разбери построчно, особенно строку с sorted и минусом перед pair
1. totals = dict() — пустой словарь, куда собираются суммы по клиентам.2. Цикл идёт по заказам. totals.get(id, 0) возвращает накопленное или ноль, если клиент встретился впервые — поэтому отдельная проверка на первый заказ не нужна.3. sorted по items() сортирует пары «клиент — сумма». Минус перед pair[1] переворачивает порядок: вместо возрастания получается убывание, и самые крупные клиенты оказываются сверху.4. ranked[:n] отрезает первые n пар.Возвращаются именно пары, а не только идентификаторы: сумма нужна вызывающему коду, чтобы её показать, и второй раз считать её не придётся.
Почему написано именно так
Объясняет не что делает код, а зачем автор выбрал эту конструкцию.
_cache = None_lock = threading.Lock()def get_config(): global _cache if _cache is None: with _lock: if _cache is None: _cache = load_config_from_disk() return _cache# зачем проверка на None написана дважды, до блокировки и внутри неё
Это приём под названием double-checked locking, и обе проверки нужны по разным причинам.Первая — быстрый путь. После того как конфиг однажды загружен, все следующие вызовы видят готовое значение и уходят сразу, не трогая блокировку. Захват блокировки стоит времени на каждом вызове, а нужен ровно один раз за жизнь процесса.Вторая — про гонку. Пока первый поток держит блокировку и читает файл, второй уже прошёл внешнюю проверку и стоит в очереди. Без внутренней проверки он загрузил бы конфиг ещё раз и затёр чужой результат.Строка global нужна, чтобы присваивание меняло переменную модуля, а не создавало новую локальную.
Ошибка и стек-трейс
Красный текст сначала читают и только потом чинят: что означает ошибка, из-за которой падает приложение, почему не проходит сборка и как разобрать стек-трейс из боевого лога, не гадая по строчкам.
Падение приложения
Читает стек-трейс: где оборвалось, какая строка виновата и что означает сама ошибка.
Traceback (most recent call last): File "app/orders.py", line 42, in create_order total = calc_total(items) File "app/pricing.py", line 17, in calc_total return sum(item.price * item.qty for item in items)AttributeError: 'dict' object has no attribute 'price'что здесь произошло и в какой строке искать причину
Ошибка AttributeError означает: у объекта нет атрибута price.Трейс читается сверху вниз по пути вызова и снизу вверх по причине:- create_order в orders.py, строка 42, позвал calc_total;- calc_total в pricing.py, строка 17, обратился к item.price и упал.Виновата строка 17, но причина не в ней. Сообщение говорит, что в items лежат словари, а не объекты, — а у словаря обращение через точку не работает. Значит вызывающая сторона передала данные прямо из JSON, не собрав их в модель. Чинить надо там, где формируется items, либо читать поле как item["price"].
Сборка не проходит
Переводит лог установки зависимостей с языка пакетного менеджера на человеческий.
ERROR: Could not find a version that satisfies the requirement pandas==2.9.1 (from versions: 2.0.0, 2.1.4, 2.2.2)ERROR: No matching distribution found for pandas==2.9.1не могу поставить зависимости, что означает это сообщение
Такой версии библиотеки не существует.pip сходил в индекс пакетов, нашёл pandas и перечислил доступные версии — самая свежая из них 2.2.2. Вы просите 2.9.1, её нет ни у кого, поэтому «no matching distribution».Почти всегда это одно из трёх: опечатка в номере версии, requirements.txt, переехавший из более нового проекта, или версия, собранная только под другую версию Python. Второе сообщение — не отдельная проблема, а итог первого.
Строка из лога прода
Объясняет, о чём именно сообщает сервер, когда в логе тайм-аут и три цифры подряд.
2026-09-12T03:14:07Z ERROR db pool: timeout acquiring connection after 30s, active=20 idle=0 waiting=1372026-09-12T03:14:07Z ERROR api POST /orders 503 in 30012ms request_id=8c1f2aчто сервер пытается мне сказать этими двумя строками
Кончились соединения с базой.Первая строка: пул ждал свободное соединение 30 секунд и сдался. active=20 — заняты все двадцать, idle=0 — свободных нет, waiting=137 — в очереди сто тридцать семь запросов.Вторая строка — следствие первой: запрос POST /orders провисел те же 30 секунд и вернул 503.Читается это так: запросы приходят быстрее, чем база успевает отпускать соединения. Сам пул не сломан — он честно сообщает, что упёрся в потолок. Смотреть дальше стоит на долгие запросы, которые держат соединение, и на размер пула относительно числа воркеров.
Регулярки и команды
Строки, которые работают, пока их не трогаешь: что делает регулярное выражение из валидации формы, что проверяет выражение, вписанное в конфиг, и чем закончится команда из чужой инструкции.
Что проверяет регулярка
Разбирает шаблон на части и говорит, какие адреса он пропустит, а какие отвергнет зря.
^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z][A-Za-z]+$это выражение стоит в валидации формы — что именно оно проверяет и что пропустит лишнего
Шаблон проверяет форму адреса, а не его существование.- ^ и $ требуют, чтобы совпала вся строка целиком, а не кусок внутри неё.- Часть до собаки: буквы, цифры, точка, подчёркивание, процент, плюс и дефис — один символ или больше.- @ — ровно одна собака.- Домен: буквы, цифры, точки и дефисы.- В конце точка и зона минимум из двух букв.Что пройдёт зря: две точки подряд, точка в начале имени, несуществующий домен. Что отвергнется напрасно: адреса с кириллицей и национальные доменные зоны. Для формы этого обычно достаточно — существование адреса всё равно подтверждается письмом.
Выражение из конфига
Показывает, какие части строки лога попадут в группы и в каком порядке.
^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) - - \[([^\]]+)\] "([A-Z]+) ([^ ]+) [^"]+" ([0-9]+) ([0-9]+)$это разбор строки access.log — что попадёт в каждую группу
Выражение разбирает строку журнала веб-сервера на шесть групп.1. IP-адрес: четыре числа через точки. Экранированная точка означает именно точку, а не «любой символ».2. Время в квадратных скобках: внутри разрешён любой символ, кроме закрывающей скобки, — благодаря этому группа не съедает остаток строки.3. Метод запроса: заглавные буквы.4. Путь: всё до первого пробела.5. Код ответа.6. Размер ответа в байтах.Порядок групп и есть порядок полей в результате: первая группа — адрес, пятая — статус. Строка, в пути которой встретился пробел, под шаблон целиком не подойдёт и будет просто пропущена.
Команда из чужой инструкции
Разбирает цепочку из терминала до того, как вы нажмёте Enter на боевом сервере.
tar -czf - ./data | ssh backup@nas "cat > /backups/data-$(date +%F).tar.gz"# нашёл в инструкции по бэкапу — объясни, что произойдёт, прежде чем я это запущу
Команда делает резервную копию папки data на другой машине, ничего не сохраняя на этой.- tar -czf - собирает архив и сжимает его, но вместо файла пишет в стандартный вывод: на это указывает одинокий дефис.- Вертикальная черта отдаёт получившийся поток следующей команде.- ssh подключается к машине nas под пользователем backup и запускает там cat, который укладывает поток в файл.- $(date +%F) подставляет сегодняшнюю дату в имя файла: получится data-2026-09-13.tar.gz.Архив нигде не приземляется целиком — он течёт по сети на ходу, поэтому свободное место на диске исходной машины не нужно. Опасное место одно: если файл с таким именем уже есть, он будет перезаписан молча.
SQL и запросы к базе
SQL на пол-экрана разбирается по частям: что именно считает отчётный SELECT, как работает оконная функция и почему запрос медленно работает на боевой базе, хотя на тестовой всё было в порядке.
Отчётный запрос
Переводит длинный SELECT в одну фразу о том, какие строки окажутся в отчёте.
SELECT c.name, date_trunc('month', o.created_at) AS month, count(*) AS orders, sum(o.total) AS revenueFROM orders oJOIN customers c ON c.id = o.customer_idWHERE o.created_at >= now() - interval '1 year'GROUP BY 1, 2HAVING sum(o.total) > 1000ORDER BY revenue DESC-- объясни, что вернёт этот запрос и почему часть клиентов в отчёт не попадёт
Запрос считает выручку по клиентам в разрезе месяцев за последний год.- JOIN связывает заказ с клиентом. Он обычный, не LEFT, поэтому клиенты без единого заказа в результат не попадают вовсе.- WHERE отсекает всё старше года от текущего момента.- date_trunc обрезает дату до первого числа месяца — именно так отдельные заказы собираются в месячные группы.- GROUP BY 1, 2 группирует по первым двум выражениям списка: имени клиента и месяцу.- HAVING отбрасывает группы с выручкой до 1000. В отличие от WHERE этот фильтр применяется уже после подсчёта суммы.Одна строка результата — это один клиент в одном месяце, а не клиент целиком.
Оконная функция
Объясняет, что считает OVER и чем окно отличается от обычной группировки.
SELECT customer_id, created_at, total, sum(total) OVER (PARTITION BY customer_id ORDER BY created_at) AS running_total, row_number() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rnFROM orders-- объясни, что считают эти два OVER и почему строки не схлопываются
Оконная функция считает по группе, но строки не схлопывает: на выходе их столько же, сколько на входе, просто у каждой появляются два новых столбца.- PARTITION BY customer_id делит таблицу на пачки по клиентам. Пачка и есть окно.- В первом столбце сумма считается нарастающим итогом: ORDER BY created_at задаёт порядок внутри окна, и по умолчанию учитываются строки от начала окна до текущей. Для третьего заказа клиента это сумма трёх первых.- Во втором OVER порядок другой — по убыванию суммы, а row_number проставляет номера: 1 достаётся самому крупному заказу клиента.Именно это отличает окно от GROUP BY: группировка оставила бы по одной строке на клиента и потеряла бы отдельные заказы.
Чем запрос рискует
Объясняет запрос и заодно показывает, обо что он споткнётся на боевых данных.
SELECT *FROM orders oLEFT JOIN order_items i ON i.order_id = o.idWHERE DATE(o.created_at) = '2026-09-01' AND o.status != 'cancelled'ORDER BY o.created_at DESC-- на тестовой базе работает мгновенно, на бою висит — объясни запрос и на что он тут напорется
Запрос забирает заказы за первое сентября вместе со всеми их позициями. LEFT JOIN означает, что заказ без позиций тоже останется в выдаче — с пустыми полями таблицы order_items.Где он спотыкается на настоящих данных:- DATE(o.created_at) — это функция поверх столбца, и индекс по created_at из-за неё не работает: базе приходится вычислить дату у каждой строки таблицы. На сотне тестовых записей незаметно, на миллионах — полное сканирование.- SELECT * при соединении «один ко многим» возвращает заказ столько раз, сколько в нём позиций. Строк на выходе заметно больше, чем заказов, и приложение обязано их схлопывать само.- Столбцы обеих таблиц называются одинаково (id, created_at), и в результате они перемешаны — по имени уже не понять, чьё это поле.
Объяснить новичку и на собеседовании
Когда объяснить код простыми словами важнее, чем написать его: первая программа студента, разбор задачи с собеседования по программированию и рассказ про скрипт нетехническому руководителю.
Первая программа
Объяснение для того, кто пишет код вторую неделю: что делает каждая строка и почему именно так.
nums = [4, 8, 15, 16, 23, 42]total = 0for n in nums: if n % 2 == 0: total += nprint(total / len(nums))# только начал учить Python — объясни по шагам, что тут происходит
Программа складывает чётные числа списка и делит результат на количество ВСЕХ чисел.1. nums — список из шести чисел.2. total = 0 — коробка, куда будем складывать. Ноль нужен, чтобы было с чего начать.3. for n in nums — цикл: n по очереди становится каждым числом списка.4. n % 2 == 0 — остаток от деления на два равен нулю, то есть число чётное.5. total += n — прибавить найденное к накопленному.6. Деление на len(nums) — на длину списка, то есть на шесть.Похоже, здесь ошибка замысла: чётных чисел в списке четыре, их сумма 70, а делится она на шесть. Если нужно среднее чётных, делить надо на количество чётных.
Задача с собеседования
Разбор алгоритма так, чтобы его можно было объяснить вслух у доски.
def search(nums, target): lo, hi = 0, len(nums) - 1 while hi >= lo: mid = (lo + hi) // 2 if nums[mid] == target: return mid if nums[mid] > target: hi = mid - 1 else: lo = mid + 1 return -1# спросили на собеседовании — объясни идею алгоритма и почему он быстрый
Это двоичный поиск по отсортированному массиву. Идея одна: на каждом шаге отбрасывается половина оставшегося отрезка.- lo и hi — границы отрезка, где ещё может лежать ответ.- mid — середина. Двойной слэш это целочисленное деление, поэтому получается индекс, а не дробь.- Если в середине искомое, ответ найден.- Если в середине больше — ответ левее, сдвигается hi.- Иначе правее, сдвигается lo.- Цикл заканчивается, когда границы разошлись: элемента нет, возвращается -1.Сложность логарифмическая: миллион элементов означает двадцать шагов вместо миллиона. Обязательное условие — массив уже отсортирован, иначе алгоритм молча вернёт неверный ответ.Частая придирка на собеседовании: в языках с ограниченным int сумма lo + hi может переполниться, поэтому там пишут lo + (hi - lo) // 2.
Объяснить руководителю
Пересказ скрипта без единого термина — для разговора с тем, кто код не читает.
import csv, smtplibrows = list(csv.DictReader(open("orders.csv")))late = [r for r in rows if r["shipped_at"] == ""]body = "Не отгружено заказов: " + str(len(late))smtplib.SMTP("mail.local").sendmail("bot@shop.local", "ops@shop.local", body)# нужно объяснить нетехническому руководителю, что делает этот скрипт
Скрипт проверяет, какие заказы ещё не отгружены, и сообщает об этом письмом.По шагам, без терминов:1. Открывает таблицу заказов — обычный файл, который открывается и в Excel.2. Отбирает строки, где дата отгрузки пустая: заказ оформлен, но не уехал.3. Считает, сколько таких строк набралось.4. Отправляет письмо на адрес отдела с одной цифрой в тексте.Что важно знать руководителю: скрипт ничего не меняет и никому не мешает, он только читает и сообщает — запускать его можно хоть каждый час. Слабое место одно: данные он берёт из файла, а значит показывает картину на момент последней выгрузки, а не прямо сейчас.