Я занимаюсь программированием уже более 30 лет, начиная с машин, уже
устаревших (на процессорах Z80 и 6502) до современных, используя языки
BASIC, ассемблера, C, C++, Tcl, Perl, Lisp, ML, occam, arc, Ruby, Go и
многие другие.
Далее следует список того, чему я научился.
0. Програмиирование — это ремесло, а не наука или инженерия
Программирование гораздо ближе к ремеслу, чем к науке или инженерной
дисциплине. Это комбинация умения и опыта, выраженная с помощью
инструментов. Разработчик выбирает нужные инструменты (иногда создает
собственные) и учится использовать их в процессе создания.
Мне кажется, что это ремесло. Я думаю, что лучшие программисты похожи,
скорее, на часовщиков, чем на строителей или физиков. Конечно,
программирование похоже и на науку и на инженерную дисциплину из-за
использования логики и математики, но в основе это использование
инструментов и создание чего-то с их помощью.
Принимая во внимание, что это ремесло, нетрудно заметить, что важны опыт, инструменты и интуиция.
1. Честность — главное правило
В процессе написания кода иногда возникает соблазн поэкспериментировать,
чтобы узнать, будет ли это работать и запустить программу, не понимая,
что же, собственно, происходит. Классический пример — вызов API, который
вы решаете оставить потому, что, волшебным образом, он позволяет
избавиться от бага или добавление printf, в результате которого
программа перестает падать.
Оба примера — примеры нечестности. Спросите себя: «Понимаю ли я, почему
моя программа делает X?». Если не понимаете, позже у вас возникнут
проблемы. Знать, что происходит — ответственность программиста, потому
что компьютер выполняет точно то, что ему приказывают, а не то, что ему
бы хотелось.
Честность требует строгого отношения. Вы должны быть точно уверены в том, что знаете, что ваша программа делает и почему.
2. Упрощайте, упрощайте, упрощайте
Тони Хоар сказал: «Есть два способа спроектировать систему. Один —
сделать ее настолько простой, что в ней очевидно не будет недостатков, а
второй — сделать ее настолько сложной, что в ней не будет очевидных
недостатков. Первый способ намного более труден.»
Упрощайте, переписывайте, удаляйте.
Перефразируя Хоара скажу: «Внутри каждой большой и сложной программы
есть маленькая и элегантная программа, которая делает то же, что и
большая и делает это правильно.»
К этому имеет отношение подход «небольшие элементы, нежестко связанные
друг с другом». Лучше проектировать программу, состоящую из небольших
частей, взаимодействующих друг с другом, чем создавать огромное
монолитное целое. Вот почему UNIX оказалась успешной.
3. Отладчики иногда вредят, профилировщики — никогда
Я почти никогда не пользуюсь отладчиками. Мои программы создают журналы и
я знаю, что мои программы делают. Обычно, я могу понять, что не так с
моим кодом из журнала, не обращаясь к помощи отладчика.
Причина, по которой я не пользуюсь отладчиками — моя уверенность в том,
что отладчики заставляют лениться. Обнаружив ошибку, многие разработчики
запускают отладчик, выставляют точки останова и исследуют память или
значения переменных. Легко поддаться очарованию от такого мощного
инструмента, но недостаточные размышления приведут к большей потере
времени. А если ваша программа такая сложная, что отладчик вам
необходим, см. п.2
(Замечание: несмотря на все вышесказанное, один из уважаемых мной
программистов, Джон Остерхут, похоже, целыми днями сидит в отладчике
Windows).
С другой стороны, профилировщики необходимы, когда вам нужно разобраться
с производительностью. Вы никогда не перестанете удивляться тому, что
может поведать вам профилировщик.
4. Дублированный код подставит вам ножку
Не повторяйтесь. Делайте все однократно в вашем коде.
Этот урок имеет отношение к п.2, но это особый случай. Даже простой
кусочек дублированного кода причинит неприятности позже, когда вы
«пофиксите» его в одном месте, но забудете в другом.
5. Не привыкайте к языкам программирования
Некоторые люди одержимы конкретными языками программирования и вынуждены
пользоваться только ими. Это ошибка. Не существует языка, подходящего
для решения всех задач.
Важно знать, какой язык из вашего инструментария вы будете использовать
для конкретной проблемы. И лучше иметь побольше инструментов. Пробуйте
разные языки, применяйте их на практике.
Например, вам не понадобится Python или ML, но вы можете
поэкспериментировать со списочными выражениями и увидеть их силу. Или
«поиграйте» с Go и увидите, как он работает с многопоточностью. Или
используйте Perl и увидите как гибко он работает со строками. Или
используйте PHP, чтобы быстро создать динамическую веб-страницу.
Я не люблю языковые войны. Они — удел неудачников, потому что они спорят
о неверных вещах. Для меня PHP неуправляемый инструмент, а в руках
других он творит чудеса. То же можно сказать о C++.
6. Проще вырастить программу, чем построить ее
Это правило имеет отношение к п.2. Начните с малого и растите
постепенно. Когда решаете проблему, легче всего начать с небольшой ее
части (возможно, используя временные заглушки или эмулируя недостающие
части), чем сразу придумать целую огромную систему.
Если вы сразу создаете целую систему, то: (a) вы не понимаете ее, (b)
придумываете лабиринт, который очень трудно изменить. С другой стороны,
если у вас много небольших элементов, взаимодействующих друг с другом,
проведение рефакторинга будет легче, если вы поймете, что ошиблись
где-то в начале.
Причина в том, что вы никогда не узнаете какой должна быть действительно
правильная архитектура. Очень редко можно предугадать, какими будут
внешние воздействия на вашу программу. Вы можете быть уверены в том, что
знаете, как проходит TCP трафик через ваш почтовый сервер или
количество адресатов, а может, вы никогда не слышали о спаме. Всегда
найдется что-то, что нарушит ваши предположения, а если они образуют
огромную, запутанную, сложную программу, это значит — у вас серьезные
неприятности.
7. Изучите уровни приложения
Я думаю, что понимание того, что происходит в программе, начиная с
центрального процессора и заканчивая языком, который вы используете,
очень важно. Важно понимать уровни приложения (например, понимать, что
происходит со скомпилированным кодом C или как работает виртуальная
машина Java).
Это очень помогает, когда приходится решать проблемы производительности,
а также при отладке. Я помню случай, когда один из клиентов моей
компании прислал скриншот ошибки в Windows 2000, на котором были видны
состояние маленького участка памяти и регистров. Используя только эту
информацию и зная версию программы, мы смогли обнаружить проблему с
нулевым указателем и ее причину.
8. Я недостаточно молод, чтобы знать все
Мне все еще нужно многому научиться. Есть языки, с которыми я почти не
работал, но хотел бы (Erlang, Clojure). Есть языки, с которыми я
«поиграл», но не знаю их достаточно хорошо (JavaScript), а еще есть
идеи, которые я почти не понимаю (монады).
PS: мне указали на то, что я не упомянул о тестировании. Стоило бы
добавить, я уверен в том, что тестирование важно для любого кода,
который будет использоваться достаточно долго. Возможно, если я буду
заниматься программированием еще 30 лет, у меня будет ответ на вопрос
«улучшают ли юнит-тесты качество программ?». Я писал код и с тестами и
без тестов и до сих пор не уверен, что знаю точный ответ, хотя и
склоняюсь к тому, что юнит-тесты могут иметь положительный эффект.