Проверка кода на нейросеть: три ошибки в функции из трёх строк
Проверка кода на нейросеть находит то, что не видно на правильных данных. Разбор функции среднего: пустой список, строка вместо числа, генератор — и как исправить.
Андрей
Преподаватель, научный редактор LibraChat
7 мин чтения
Функцию для среднего арифметического я написал за 2 минуты и был уверен, что она работает. На моих примерах работала. Потом я отдал её на проверку нейросети, и она нашла три способа её сломать: пустой список, строка среди чисел и генератор вместо списка. Проверка кода на нейросеть ценна именно этим: она смотрит не на правильные данные, на которых ты всё проверил сам, а на неправильные, о которых ты не подумал. Ниже моя ошибка, что я из неё вынес и как теперь проверяю свой код.
Как я написал «работающую» функцию
Я учу Python несколько месяцев. Задача из учебника: функция, которая считает среднее значение списка чисел. Я написал так:
```python
def avg(nums):
total = 0
for n in nums:
total += n
return total / len(nums)
```
Проверил на `[1, 2, 3]`, получил 2.0. Проверил на `[10, 20]`, получил 15.0. Всё правильно. Я отметил задачу как сделанную.
Через неделю я использовал эту функцию в маленькой программе для своих курсов: она считала средний балл по заданиям. Один студент не сдал ни одного задания, и список его оценок оказался пустым. Программа упала на середине таблицы, и я потратил 40 минут, чтобы понять, что случилось. Тогда я и вспомнил про совет знакомого программиста проверять странные входы.
Как преподаватель я хорошо знаю эту ошибку у студентов. Проверить решение на тех примерах, на которых оно точно сработает. Это называется проверкой «счастливого пути». Сам я её сделал, не задумываясь.
Что нашла проверка кода на нейросеть
Я добавил к функции два вызова, которые мне посоветовал знакомый программист: пустой список и список со строкой. И отправил всё в LibraChat:
Проверь этот код на Python: найди ошибки и слабые места, по каждому объясни, что случится и как исправить. def avg(nums): total = 0 for n in nums: total += n return total / len(nums) print(avg([])) print(avg(["5", 3]))
Ответ начался без церемоний:
Код падает на обоих вызовах и ещё на нескольких типичных входах. Ниже — что именно ломается и как починить.
Дальше шли три ошибки и отдельно слабые места. Я свёл их в таблицу:
Что подали · Что случится · Почему
пустой список — деление на ноль — длина равна 0
`["5", 3]` — ошибка типа — `0 + "5"` в Python недопустимо
генератор — ошибка, у генератора нет длины — `len` нельзя вызвать после цикла
Третий случай я не проверял вовсе, и даже не подумал бы о нём. Я даже не знал, что генератор после прохода цикла «съеден» и второй раз его не прочитать. В ответе это объяснено двумя предложениями, и я понял с первого раза.
Слабые места, которые не падают сразу
Самое интересное было в разделе, где ничего не ломается. Там перечислено то, что даёт тихо неверный результат:
`True` и `False` в Python ведут себя как 1 и 0, и функция молча их посчитает;
`None` или вложенный список вызовут ошибку в неожиданном месте;
ручной цикл повторяет то, что уже умеет встроенная `sum()`;
функция не говорит, что должно быть на пустом входе: ошибка или ничего.
Первый пункт меня поразил. Список `[True, True, False]` даст среднее 0.666… без единой жалобы. Если такие значения попадут в расчёт оценок студентов, никто и не заметит, что посчитано не то.
Что я вынес: проверять надо неправильные данные
Первый принцип. Код проверяют не на том, что должно работать, а на том, что может сломать. Пустое, нулевое, слишком большое, не того типа. Студентам я всегда говорил: проверяйте выводы на крайних случаях. А в собственном коде забыл.
Второй принцип. Ошибка должна быть понятной. В ответе об этом сказано очень точно: при пустом списке исключение само по себе логично, но сообщение будет про деление, а не про смысл ошибки. Человек, который увидит «деление на ноль», не поймёт, что дело в пустом списке.
Третий принцип. Функция должна прямо говорить, что она принимает. Исправленная версия из ответа сначала превращает вход в список, потом проверяет, что он не пустой, потом проверяет тип каждого элемента и выдаёт понятное сообщение: какой тип ожидался и что пришло.
Почему я не нашёл это сам
Честно разобрал свою ошибку, как разбираю студенческие. Причина не в незнании Python. Я знал, что на ноль делить нельзя. Причина в том, как я проверял: я придумывал примеры, на которых функция работает, потому что хотел, чтобы она работала. Это обычная человеческая слепота к собственному тексту, редакторы знают её лучше всех. Свою статью вычитать труднее, чем чужую.
Нейросеть в этой ситуации — посторонний читатель. У неё нет желания, чтобы код оказался правильным. Поэтому она идёт туда, куда автор не смотрит: в пустое, в чужое, в странное. Ровно так работает хороший рецензент.
Как теперь выглядит моя проверка кода
Сначала я пробую свой код сам, на трёх группах данных:
Обычные данные, на которых код должен работать.
Крайние случаи: пусто, один элемент, ноль, очень большое число.
Чужие данные: строка вместо числа, `None`, логическое значение.
Потом отправляю код в LibraChat с просьбой найти ошибки и слабые места и объяснить каждое. Сравниваю его список со своим. Если в нём есть пункт, которого у меня не было, записываю его в тетрадь ошибок. За месяц таких пунктов набралось одиннадцать, и последние две недели новых почти нет.
И одно правило, которое я добавил после этого случая. Исправленный код из ответа я не копирую сразу. Сначала пишу исправление сам, по объяснению, а потом сравниваю с вариантом из ответа. Так ошибка запоминается, а не проходит мимо головы. Если сразу вставить готовое, через неделю напишешь ту же функцию с той же дырой.
Проверка кода на нейросеть: что писать в запросе
Мой первый запрос был коротким, и он сработал. Но со временем я понял, что ответ становится точнее, если добавить несколько вещей. Сейчас мой запрос на проверку обычно содержит:
сам код, целиком, а не кусок из середины;
два-три вызова, которые я уже проверил, и что они должны вернуть;
что за данные придут в функцию в реальной жизни: оценки, суммы, имена;
просьбу разделить ответ на «падает» и «работает, но неправильно».
Третий пункт важнее, чем кажется. Если написать, что в функцию приходят оценки от 1 до 5, нейросеть может обратить внимание на то, что важно именно для оценок. Без этого она проверяет всё подряд, и полезное тонет среди общих советов.
Четвёртый пункт я взял из своего же первого ответа. Разделение на ошибки и слабые места оказалось очень удобным. Ошибки чинишь сразу. Слабые места обдумываешь: нужно ли это в твоей программе. Иногда честный ответ «нет, не нужно», и это тоже решение, которое должен принять ты, а не кто-то за тебя.
Где проверка кода на нейросеть не заменит тебя
Проверка ограничена тем, что попало в сообщение. Какие данные придут в функцию в настоящей программе, нейросеть не видит. Может быть, в твоей программе строк никогда не бывает, и проверка типа лишняя. А может, бывают, и тогда исправление из ответа обязательно. Решать тебе: в ответе прямо сказано, что если строки-числа надо принимать, проверку типа нужно заменить приведением к числу.
И второе. Найти ошибку ещё не значит понять её. Если объяснение в ответе непонятно, переспроси в том же чате. Мне, например, пришлось перечитать абзац про генераторы дважды, прежде чем он лёг в голову.
Если ты пишешь код профессионально и хочешь посмотреть на другие задачи, есть статья нейросеть для программиста.
Маленькое задание
Возьми функцию, которую ты написал недавно и считаешь рабочей. Придумай к ней три вызова: обычный, пустой и с данными не того типа. Отправь код с этими вызовами в чат LibraChat и попроси найти ошибки и слабые места, объясняя каждое. Попробуй сначала угадать, что найдётся. Для такой проверки подписка не нужна, условия тарифов — на странице тарифов.
проверка кода на нейросетьpythonошибки в кодеграничные случаиучёбасамообучение