Что у программы между строк

в 12:30, , рубрики: Алгоритмы, Программирование, программирование для начинающих, программировать с нуля, программист
image

Знание правил шахматной игры еще не делает человека гроссмейстером, знание языка программирования еще не делает человека программистом. А чего в обоих случаях недостает? Ищем ответы на оба вопроса у признанных мастеров и пытаемся иллюстрировать собственными примерами.

“- А что такое, товарищи, значит идея? Идея, товарищи, — это человеческая мысль, облеченная в логическую шахматную форму. Даже с ничтожными силами можно овладеть всей доской. Все зависит от каждого индивидуума в отдельности. Например, вон тот блондинчик в третьем ряду. Положим, он играет хорошо…
Блондин в третьем ряду зарделся.
— А вон тот брюнет, допустим, хуже.
Все повернулись и осмотрели также брюнета.
— Что же мы видим, товарищи? Мы видим, что блондин играет хорошо, а брюнет играет плохо. И никакие лекции не изменят этого соотношения сил, если каждый индивидуум в отдельности не будет постоянно тренироваться в шашк… то есть я хотел сказать — в шахматы… “

И.Ильф, Е.Петров “12 стульев”

Программирование подобно шахматам в том смысле, что знание “правил игры” (язык а программирования, стандартных библиотек) не гарантирует написания хорошего кода. Это, увы, очевидно всякому, кто провел хотя бы пару десятков интервью на программистскую позицию. Завет Великого Комбинатора (образование – ничто, главное опыт) работает не всегда – человек может иметь 10 лет однотипного опыта написания каких-нибудь форм (вряд ли силен шахматист, 10 лет подряд игравший одну и ту же партию).

А вот слова маэстро про идею мне кажутся важными. Ибо программа, товарищи, – это человеческая мысль, воплощенная в форму кода. Соответственно, качественный код возникает в результате воплощения качественной мысли (ну или, как минимум, хоть какой-нибудь мысли).

Я собираюсь показать на простом примере, как увидеть эту самую мысль за строками (или, скорее, между строк) программы и почему такое “видение” помогает писать качественные программы. Скажу сразу – описанной технике (связанной в первую очередь с именами Дейкстры и Хоара) десятки лет, и она, к сожалению, так и не стала “мэйнстримом” программистского образования. Но меня не оставляет надежда, что еще пара убедительных и доступных примеров — и лед тронется…

Редкая книга по введению в программирование обходится без задачи о ханойских башнях. Сейчас мы закодируем совершенно “боковой” фрагмент такой программы. Нам даже необязательно знать, в чем состоит “полная версия” игры в ханойские башни, а в нашем языке программирования будет всего две команды. Итак.

Дано. На трехцветном экране изображены три штыря (1, 2, 3), на первый штырь надето кольцо.

Что у программы между строк - 2

Штыри черные, кольцо серое, фон белый. Доступны две команды:
    Штырь (номер, цвет)
    Кольцо(номер, цвет)
Каждая команда заливает соответствующий прямоугольник соответствующим цветом. Например, если подать команду Кольцо(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 становится очевидным:

Что у программы между строк - 3

Программа С действует примерно в том же порядке, что и А, только она откладывает устранение мелких недоделок на потом. В ней нет понятного промежуточного состояния, вследствие чего внимание читающего постоянно перескакивает с первого штыря на второй и обратно – чтоб понять, что происходит, следует держать в голове одновременно все три команды и всю модифицируемую часть картинки. Возможно, опытный программист избегает этого пути опять-таки из экономии умственных усилий – лучше уж мы поскорее решим все проблемы со стартовым штырем и забудем о них.

Практические проблемы с программой 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

Источник

Поделиться

* - обязательные к заполнению поля