Сейбел: А вас о чем спрашивали на собеседовании?
Фицпатрик: Например, был вопрос: представьте, что у вас есть несколько компьютеров, подключенных через коммутатор, которые занимают целую стойку. Напишите алгоритм, чтобы каждая машина в стойке знала статус любой другой машины — включена та или нет. То есть задача в целом сводилась к определению присутствия. Это было серьезным ограничением. В основном они описали схему работы сети Ethernet: вы можете послать широковещательное сообщение всем или посылать сообщение по конкретному МАС-адресу. Надо было проанализировать множество различных стратегий для минимизации полосы пропускания и времени определения того, что один из компьютеров вырубился. Это была интересная задача.
Сейбел: Какую из найденных вами ошибкок вы считаете самой серьезной?
Фицпатрик: Я стараюсь их не запоминать. Ненавижу, когда предположения столь сильно расходятся с реальностью. Недавно (это явно не пример самой плохой ошибки в моей жизни) я потратил полтора часа на отладку, потому что писал в один файл, а читал другой — с таким же точно именем, только путь к нему был на один элемент короче. Я продолжал перезапускать этот огромный MapReduce, наблюдая за выходными данными, и наконец запустил GDB для пошаговой отладки. «Какого хрена? — говорил я себе. — Ничего не меняется!» В конце концов я глянул на пути и воскликнул: «Бог ты мой!», — не знаю, как я мог потратить на это полтора часа. Я был так одержим, что даже не вернулся, чтобы проверить корректность командной строки.
И так бывает нередко. Мы постоянно сталкиваемся с подобными вещами в Perl, например, когда переменная $_ не определена в лексической области видимости. Возишься с $_ в сортировке, а на самом деле используешь значение, определенное где-то очень далеко. Эта ошибка доставала нас постоянно, создавая немало проблем. Когда мы наконец выяснили в чем дело, я провел аудит всего нашего кода, и мы ввели правило «никогда не делай этого».
Сейбел: Какие инструменты вы используете для отладки? Отладчики? Операторы print In? Еще что-то?
Фицпатрик: Я использую операторы print In, если среда позволяет это. Если в среде есть хорошие отладчики, использую отладчик. GDB хорошо поддерживается в Google и порой просто незаменим. Стараюсь использовать его пореже. Я в нем не такой уж большой специалист, но могу осмотреться и представить положение вещей в целом. Если приходится забираться в дебри, то я всегда могу как-нибудь выпутаться. Я люблю утилиту strace, просто не представляю жизни без нее. Если я не знаю, что делает моя или чья-то программа, то запускаю ее под strace и вижу, что конкретно в ней происходит. Если бы мне пришлось выбрать только одну утилиту, я бы выбрал именно ее. Все инструменты, вроде Valgrind и Callgrind, очень хороши.
Но в последнее время, если происходит что-то странное, я поступаю так: «Хорошо, вот эта функция слишком велика; давайте разобьем ее на части поменьше и напишем модульные тесты, чтобы проверить работоспособность каждой части независимо и найти место, в котором мои предположения оказались ошибочными, а не втыкать операторы print In где попало».
Бывает позже, в процессе рефакторинга, я начинаю думать о коде больше, и проблема становится очевидной. Тогда я могу вернуться к той огромной уродливой функции и исправить ее, но половину исправлений я уже внес; я могу продолжить, чтобы облегчить работу того, кто будет поддерживать код после меня.
Сейбел: Как вы используете инварианты в своем коде? Одни программисты добавляют специальные утверждения, другие добавляют инварианты на каждом шагу, что позволяет им проверить формальные свойства своей программы. Между этими двумя крайностями много промежуточных вариантов.
Фицпатрик: Я не сторонник чисто формального подхода. Мое основное правило: если некорректные данные могут приходить от конечного пользователя, то это не ошибка времени выполнения. Но если взаимодействие осуществляется между двумя кусками моего кода, я прерываю выполнение настолько жестко, насколько это возможно, — чем раньше упадет программа, тем лучше.
Я стараюсь мыслить главным образом в терминах предусловий и проверяю данные в конструкторе и в начале функции. При этом, если возможно, я использую условные проверки, чтобы компилятор мог их впоследствии выкинуть. Тут есть много подходов, я недостаточно подкован, чтобы говорить о том, какой из них самый верный. Есть языки, в которых все это является частью самого языка. Но почти во всех языках, которыми я пользуюсь, это оставлено на усмотрение программиста.