Как говорится, если тебе не стыдно за свой старый код, значит, ты не растешь как программист — и я согласна с таким мнением. Я начала программировать для развлечения более 40 лет назад, а 30 лет назад и профессионально, так что ошибок у меня набралось очень много. Будучи профессором информатики, я учу своих студентов извлекать уроки из ошибок — своих, моих, чужих. Думаю, пришло время рассказать о моих ошибках, чтобы не растерять скромность. Надеюсь, кому-то они окажутся полезны.
Третье место — компилятор C от Microsoft
Мой школьный учитель считал, что «Ромео и Джульетту» нельзя считать трагедией, потому что у героев не было трагической вины — просто они вели себя глупо, как и положено подросткам. Тогда я с ним не согласилась, но сейчас вижу в его мнении рациональное зерно — особенно в связи с программированием.
К моменту окончания второго курса MIT я была молода и неопытна, как в жизни, так и в программировании. Летом я проходила практику в Microsoft, в команде компилятора C. Поначалу я занималась рутиной вроде поддержки профилирования, а потом мне доверили работу над самой веселой (как я считала) частью компилятора — оптимизацией бэкенда. В частности, я должна была улучшить код x86 для операторов ветвления.
Настроенная написать оптимальный машинный код для каждого возможного случая, я бросилась в омут с головой. Если плотность распределения значений была высока, я вносила их в таблицу переходов. Если у них имелся общий делитель, я использовала его, чтобы сделать таблицу плотнее (но только если деление можно было выполнить при помощи битового сдвига). Когда все значения представляли собой степени двойки, я проводила еще одну оптимизацию. Если множество значений не удовлетворяло моим условиям, я разбивала его на несколько оптимизируемых случаев и использовала уже оптимизированный код.
Это был кошмар. Через много лет мне рассказывали, что унаследовавший мой код программист меня ненавидел.
Вынесенный урок
Как пишут Дэвид Паттерсон и Джон Хеннесси в книге «Архитектура компьютера и проектирование компьютерных систем», один из главных принципов архитектуры и разработки в том, чтобы в общих случаях все работало как можно быстрее.
Ускорение общих случаев повысит производительность более эффективно, чем оптимизация редких случаев. Как ни иронично, общие случаи часто проще редких. Этот логичный совет подразумевает, что вы знаете, какой случай считать общим — а это возможно только в процессе осторожного тестирования и измерения.
В свою защиту могу сказать, что я пыталась разобраться, как выглядели операторы ветвления на практике (например, сколько существовало веток и как были распределены константы), но в 1988 году эта информация была недоступна. Тем не менее, мне не стоило добавлять специальные случаи всякий раз, когда действующий компилятор не мог сгенерировать оптимальный код для придуманного мной искусственного примера.
Мне нужно было позвать опытного разработчика и вместе с ним подумать, какие были общие случаи, и разобраться конкретно с ними. Я написала бы меньше кода, но это даже хорошо. Как писал основатель Stack Overflow Джефф Этвуд, злейший враг программиста — сам программист:
Я знаю, что у вас самые добрые намерения, как и у всех нас. Мы создаем программы и любим писать код. Так уж мы устроены. Мы думаем, что любую проблему можно решить при помощи изоленты, самодельного костыля и щепотки кода. Как бы ни было больно кодерам признавать это, но лучший код — тот код, которого нет. Каждая новая строчка нуждается в отладке и поддержке, ее нужно понимать. Добавляя новый код, вы должны делать это с неохотой и отвращением, потому что все другие варианты оказались исчерпаны. Многие программисты пишут слишком много кода, делая его нашим врагом.
Если бы я написала более простой код, покрывавший общие случаи, то при необходимости его было бы куда проще обновить. Я же оставила беспорядок, с которым никто не хотел связываться.
Второе место: реклама в соцсетях
Когда я работала в Google над рекламой для соцсетей (помните Myspace?), я написала на C++ что-то вроде этого:
for (int i = 0; i < user->interests->length(); i++) {
for (int j = 0; j < user->interests(i)->keywords.length(); j++) {
keywords->add(user->interests(i)->keywords(i)) {
}
}
Программисты, возможно, сразу увидят ошибку: последним аргументом должен быть j, а не i. Модульное тестирование не выявило ошибку, не заметил ее и мой проверяющий.
Был проведен запуск, и как-то ночью мой код отправился на сервер и обвалил все компьютеры в дата-центре.
Ничего страшного не произошло. Ни у кого ничего не сломалось, потому что перед глобальным запуском код тестировался в пределах одного дата-центра. Разве что SRE-инженеры ненадолго бросили играть в бильярд и сделали небольшой откат. Наутро я получила email с аварийным дампом, исправила код и добавила модульных тестов, которые выявили бы ошибку. Поскольку я следовала протоколу — иначе мой код бы просто не прошел запуск — других проблем не возникло.
Вынесенный урок
Многие уверены, что подобная крупная ошибка обязательно стоит виновнику увольнения, но это не так: во-первых, все программисты ошибаются, во-вторых, они редко совершают одну ошибку дважды.
На самом деле, у меня есть знакомый программист — блестящий инженер, которого уволили за одну-единственную ошибку. После этого его взяли на работу в Google (и вскоре повысили) — он честно рассказал о допущенной ошибке на интервью, и ее не сочли фатальной.
Вот что рассказывают о Томасе Уотсоне, легендарном главе IBM:
Был объявлен госзаказ на сумму около миллиона долларов. Корпорация IBM — а точнее, лично Томас Уотсон-старший — очень хотела его получить. К сожалению, торговый представитель не смог этого сделать, и IBM проиграла тендер. На следующий день этот сотрудник пришел в кабинет к мистеру Уотсону и положил ему на стол конверт. Мистер Уотсон даже не стал в него заглядывать — он ждал сотрудника и знал, что это заявление об уходе.
Уотсон спросил, что пошло не так.
Торговый представитель подробно рассказал о ходе тендера. Он назвал допущенные ошибки, которых можно было избежать. Наконец, он сказал: «Мистер Уотсон, спасибо вам за то, что дали мне объясниться. Я знаю, насколько мы нуждались в этом заказе. Я знаю, как он был важен», — и собрался уходить.
Уотсон подошел к нему у двери, посмотрел ему в глаза и вернул конверт со словами: «Как я могу дать тебе уйти? Я ведь только что вложил миллион долларов в твое образование.
У меня есть футболка, на которой написано: «Если на ошибках и правда учатся, то я уже магистр». На самом деле по части ошибок я доктор наук.
Первое место: App Inventor API
По-настоящему страшные ошибки затрагивают огромное количество пользователей, становятся достоянием общественности, долго исправляются и допускаются теми, кто мог бы их не допускать. Моя крупнейшая ошибка удовлетворяет всем этим критериям.
Чем хуже, тем лучше
Я читала эссе Ричарда Гэбриела об этом подходе в девяностые, будучи аспирантом, и оно мне так нравится, что я задаю его своим студентам. Если вы плохо его помните, освежите память, оно небольшое. В этом эссе желание «сделать как надо» и подход «чем хуже, тем лучше» противопоставляются по многим параметрам, включая простоту.
Как надо: дизайн должен быть простым в реализации и интерфейсе. Простота интерфейса важнее простоты реализации.
Чем хуже, тем лучше: дизайн должен быть простым в реализации и интерфейсе. Простота реализации важнее простоты интерфейса.
Забудем об этом на минутку. К сожалению, я забыла об этом на долгие годы.
App Inventor
Работая в Google, я входила в команду App Inventor, онлайновой среды разработки с поддержкой перетаскивания объектов для начинающих Android-разработчиков. Шел 2009 год, и мы спешили вовремя выпустить альфа-версию, чтобы летом провести мастер-классы для учителей, которые могли бы пользоваться средой при обучении уже осенью. Я вызвалась реализовать спрайты, ностальгируя по тому, как в свое время я писала игры на TI-99/4. Для тех, кто не в курсе: спрайт — это двумерный графический объект, который может перемещаться и взаимодействовать с другими программными элементами. Примеры спрайтов — космические корабли, астероиды, шарики и ракетки.
Мы реализовали объектно-ориентированный App Inventor в Java, так что там просто куча объектов. Поскольку шарики и спрайты ведут себя очень похоже, я создала абстрактный sprite-класс со свойствами (полями) X, Y, Speed (скорость) и Heading (направление). Они обладали одними и теми же методами выявления столкновений, отскока от границы экрана и т.д.
Главное отличие между шариком и спрайтом в том, что именно нарисовано — заполненный круг или растр. Поскольку сначала я реализовала спрайты, логично было указать x- и y-координаты верхнего левого угла места, где располагалось изображение.
Когда спрайты заработали, я решила, что можно реализовать объекты-шарики очень небольшим количеством кода. Проблема была лишь в том, что я пошла самым простым путем (с точки зрения реализатора), указав x- и y-координаты верхнего левого угла контура, обрамляющего шар.
На самом деле нужно было указать x- и y-координаты центра круга, как этому учит любой учебник математики и любой другой источник, упоминающий круги.
В отличие от моих прошлых ошибок, от этой пострадали не только мои коллеги, но и миллионы пользователей App Inventor. Многие из них были детьми или совсем новичками в программировании. Им приходилось выполнять много лишних действий при работе над каждым приложением, в котором присутствовал шар. Если остальные свои ошибки я вспоминаю со смехом, то эта бросает меня в пот и сегодня.
Я наконец-то запатчила это ошибку лишь недавно, десять лет спустя. «Запатчила», а не «исправила», ведь как говорит Джошуа Блох, API вечны. Не имея возможности внести изменения, которые повлияли бы на существующие программы, мы добавили свойство OriginAtCenter со значением false в старых программах и true во всех будущих. Пользователи могут задать закономерный вопрос, кому вообще пришло в голову расположить точку отсчета где-то, кроме центра. Кому? Одному программисту, который десять лет назад поленился создать нормальный API.
Вынесенные уроки
Работая над API (что иногда приходится делать почти каждому программисту), вам стоит следовать лучшим советам, изложенным в видео Джошуа Блоха «Как создать хороший API и почему он так важен» или в этом кратком списке:
- API может принести вам как огромную пользу, так и огромный вред. Хороший API создает постоянных клиентов. Плохой становится вашим вечным кошмаром.
- Общедоступные API, как и бриллианты, вечны. Выложитесь на все сто: другого шанса сделать все, как надо, больше не представится.
- Наметки для API должны быть краткими — одна страница с сигнатурами классов и методов и описаниями, занимающими не больше строчки. Это позволит вам легко реструктуризировать API, если с первого раза он выйдет не идеальным.
- Распишите сценарии использования, прежде чем реализовывать API и даже работать над его спецификацией. Таким образом вы избежите реализации и спецификации полностью нефункционального API.
Если бы я написала хотя бы небольшой конспект с искусственным сценарием, скорее всего, я бы выявила ошибку и исправила бы ее. Если и нет, то это точно бы сделал кто-то из моих коллег. Любое решение, которое имеет далеко идущие последствия, необходимо обдумывать хотя бы день (это касается не только программирования).
Название эссе Ричарда Гэбриела «Чем хуже, тем лучше» указывает на преимущество, которое получает тот, кто первым вышел на рынок — пусть даже с несовершенным продуктом — пока кто-то другой целую вечность гонится за идеалом. Размышляя над кодом спрайтов, я понимаю, что мне даже необязательно было писать больше кода, чтобы сделать все, как надо. Как ни крути, я грубо ошиблась.
Заключение
Программисты совершают ошибки каждый день — будь то написание кода с багами или нежелание попробовать что-то, что повысит их мастерство и продуктивность. Конечно, можно быть программистом и не допуская столь серьезных промахов, какие делала я. Но стать хорошим программистом, не осознавая своих ошибок и не извлекая из них уроков, невозможно.
Мне постоянно встречаются студенты, которым кажется, что они допускают слишком много ошибок и потому не созданы для программирования. Я знаю, насколько распространен в IT синдром самозванца. Надеюсь, вы усвоите перечисленные мной уроки — но помните главный из них: каждый из нас допускает ошибки — стыдные, смешные, страшные. Я буду удивлена и расстроена, если в будущем у меня не наберется материала на продолжение статьи.