
Знание правил шахматной игры еще не делает человека гроссмейстером, знание языка программирования еще не делает человека программистом. А чего в обоих случаях недостает? Ищем ответы на оба вопроса у признанных мастеров и пытаемся иллюстрировать собственными примерами.
“- А что такое, товарищи, значит идея? Идея, товарищи, — это человеческая мысль, облеченная в логическую шахматную форму. Даже с ничтожными силами можно овладеть всей доской. Все зависит от каждого индивидуума в отдельности. Например, вон тот блондинчик в третьем ряду. Положим, он играет хорошо…
Блондин в третьем ряду зарделся.
— А вон тот брюнет, допустим, хуже.
Все повернулись и осмотрели также брюнета.
— Что же мы видим, товарищи? Мы видим, что блондин играет хорошо, а брюнет играет плохо. И никакие лекции не изменят этого соотношения сил, если каждый индивидуум в отдельности не будет постоянно тренироваться в шашк… то есть я хотел сказать — в шахматы… “
И.Ильф, Е.Петров “12 стульев”
Программирование подобно шахматам в том смысле, что знание “правил игры” (язык а программирования, стандартных библиотек) не гарантирует написания хорошего кода. Это, увы, очевидно всякому, кто провел хотя бы пару десятков интервью на программистскую позицию. Завет Великого Комбинатора (образование – ничто, главное опыт) работает не всегда – человек может иметь 10 лет однотипного опыта написания каких-нибудь форм (вряд ли силен шахматист, 10 лет подряд игравший одну и ту же партию).
А вот слова маэстро про идею мне кажутся важными. Ибо программа, товарищи, – это человеческая мысль, воплощенная в форму кода. Соответственно, качественный код возникает в результате воплощения качественной мысли (ну или, как минимум, хоть какой-нибудь мысли).
Я собираюсь показать на простом примере, как увидеть эту самую мысль за строками (или, скорее, между строк) программы и почему такое “видение” помогает писать качественные программы. Скажу сразу – описанной технике (связанной в первую очередь с именами Дейкстры и Хоара) десятки лет, и она, к сожалению, так и не стала “мэйнстримом” программистского образования. Но меня не оставляет надежда, что еще пара убедительных и доступных примеров — и лед тронется…
Редкая книга по введению в программирование обходится без задачи о ханойских башнях. Сейчас мы закодируем совершенно “боковой” фрагмент такой программы. Нам даже необязательно знать, в чем состоит “полная версия” игры в ханойские башни, а в нашем языке программирования будет всего две команды. Итак.
Дано. На трехцветном экране изображены три штыря (1, 2, 3), на первый штырь надето кольцо.

Штыри черные, кольцо серое, фон белый. Доступны две команды:
Штырь (номер, цвет)
Кольцо(номер, цвет)
Каждая команда заливает соответствующий прямоугольник соответствующим цветом. Например, если подать команду Кольцо(3, белый) в описанной начальной ситуации, то у правого штыря исчезнет нижняя часть.
Требуется написать программу, в результате работы которой кольцо переместится на средний штырь. Команды работают мгновенно, никакие “эффекты анимации” невозможны и не требуются.
Попробуйте самостоятельно написать нужный фрагмент. Слишком очевидно? Тем лучше.
Эта задача предлагалась людям с очень разной степенью подготовки. Справлялись все, выдавая одну из трех программ:
A | B | C |
Кольцо(1, белый) Штырь (1, черный) Кольцо(2, серый) |
Кольцо(2, серый) Кольцо(1, белый) Штырь (1, черный) |
Кольцо(1, белый) Кольцо(2, серый) Штырь (1, черный) |
В случайной аудитории решения A, B, C выдавались с приблизительно равной вероятностью. А вот сильные программисты писали исключительно вариант А, были удивлены, когда им сообщали, что есть другие варианты, и не могли объяснить, чем вариант А лучше. Попробуем разобраться. Для начала снабдим программу А комментариями:
// кольцо на штыре 1
Кольцо(1, белый)
Штырь (1, черный)
// все штыри пусты
Кольцо(2, серый)
// кольцо на штыре 2
Во всех комментариях мы описываем исключительно картинку, полученную на соответствующем этапе выполнения, то есть комментируются не действия программы, а состояния системы до/после соответствующих действий.
Чем хороша программа А? У нее есть простое, симметричное (по отношению к номеру штыря) и естественное (так было бы и в реальности) промежуточное состояние. Состояние между первой и второй командой (испорченный первый штырь) не имеет простого описания и не соответствует реальности. Возможно¸ поэтому опытный программист интуитивно стремится сперва уйти из этого подозрительного состояния и лишь потом двигаться дальше.
Оокей, скажет терпеливый читатель, мы уважаем верования автора, но какой во всем этом практический смысл, помимо абстрактной морали? Программы В и С чем-то хуже на практике?
Давайте внимательнее посмотрим на программу B, используя тот же прием – прокомментируем промежуточные состояния системы.
// кольцо на штыре 1
Кольцо(2, серый)
// кольцa на штырях 1 и 2 ???
Кольцо(1, белый)
Штырь (1, черный)
Как видим, и здесь можно описать предполагаемое промежуточное состояние. И это описание должно мгновенно зажечь тревожный сигнал для разработчика – по условию задачи у нас есть только одно кольцо, не два. Казалось бы, что за детские страхи – это software, сколько захотим, столько нарисуем. Почему опытный программист не выбирает этот путь? Возможно, потому, что экономит умственные усилия – программа А “списана” с реальности, именно так мы переставляли бы настоящее кольцо на настоящих штырях. Непротиворечивость и осуществимость плана А в каком-то смысле гарантирована природой, что позволяет сэкономить на формальных гарантиях. План B предполагает некую “виртуальную реальность”, непротиворечивость которой требует аккуратного анализа. Два кольца на соседних штырях – а они там поместятся? После такого вопроса провальный тест для программы B становится очевидным:

Программа С действует примерно в том же порядке, что и А, только она откладывает устранение мелких недоделок на потом. В ней нет понятного промежуточного состояния, вследствие чего внимание читающего постоянно перескакивает с первого штыря на второй и обратно – чтоб понять, что происходит, следует держать в голове одновременно все три команды и всю модифицируемую часть картинки. Возможно, опытный программист избегает этого пути опять-таки из экономии умственных усилий – лучше уж мы поскорее решим все проблемы со стартовым штырем и забудем о них.
Практические проблемы с программой C менее очевидны, чем в случае B. Представим себе, что позднее заказчик программы пожелал возможности переноса кольца с произвольного штыря s (предполагая, что оно там есть) на произвольный штырь d. Нам потребуется модификация первоначальной программы (рефакторинг, да). Естественной идеей кажется заменить везде константу 1 на переменную s и константу 2 на переменную d. Оказывается, что программа A такой рефакторинг прекрасно выдерживает, а программа C нет – она не будет работать корректно при s=d (проверьте!).
Наблюдение. Обратите внимание, что выбрать правильный вариант кодирования существенно легче, чем понять, где сломаются остальные варианты (если мы конечно, умеем читать программу между строк — осознанно или интуитивно). На наш взгляд, программистская квалификация в первую очередь состоит не в умении понять, где в программе ошибка, а в умении написать программу так, чтоб вероятность ошибки была минимальной. Вспомним поговорку про находчивого и мудрого: находчивый умеет выпутываться из сложных ситуаций, а мудрый в них просто не попадает.
В заключение, еще один вариант все той же программы, демонстрирующей некоторый водораздел между наукой и производством.
Кольцо(1, белый)
Кольцо(2, белый)
Кольцо(3, белый)
Штырь (1, белый)
Штырь (2, белый)
Штырь (3, белый)
// сплошной белый фон
Штырь (1, черный)
Штырь (2, черный)
Штырь (3, черный)
// три пустых штыря
Кольцо(2, серый)
// кольцо на штыре 2
Такое решение вряд ли уместно в университетском курсе или на интервью, но на производстве может быть пригодно. Автор программы честно признал, что не сумел вычислить правильное “инкрементное изменение” и нарисовал все с чистого листа. В отличие от авторов программ В и С, он, видимо, сумел разглядеть потенциальные трудности, но не имел времени или возможности справиться с ними более эффективно. Бесполезно расчитывать на то, что в ближайшее время найдется достаточно программистов, пишущих гениальный код. Но, может быть, мы хотя бы можем научить большинство кодеров писать “тупой” код вроде этого?
Автор: 2_bytes