понедельник, 4 марта 2013 г.

о шаблонах в программировании

о шаблонах в программировании: Думаю, в программировании есть полезные шаблоны мышления. Только это не приевшиеся и по большей части бесполезные design patterns, а нечто более фундаментальное и одновременно аморфное. Такое, что трудно описать и непонятно, можно ли вообще научить.

Попробую объяснить на примере кода, который недавно писал - HTML5-версии игры Jelly No Puzzle.

В начальных уровнях игры по карте двигаются цветные блоки, и каждый раз, когда два блока одинакового цвета соприкасаются, они соединяются друг с другом. В исходниках для этого есть функция checkForMerges(), которая вызывается после каждого движения.

Кроме того, есть описание карты каждого уровня, начальной позиции. Оно сделано простым и наглядным - содержимое карты описывается построчно символами, пробел сооветствует пустой клетке, x - стенке, r/g/b - цветным клеткам red/green/blue соответственно. Например:
[ "xxxxxxxxxxxxxx",
    "x            x",
    "x       r    x",
    "x       b    x",
    "x       x    x",
    "x b r        x",
    "x b r      b x",
    "xxx x      xxx",
    "xxxxx xxxxxxxx",
    "xxxxxxxxxxxxxx", ],

В этом примере две голубые клетки находятся рядом друг с другом, и две красные тоже. В самой игре они должны с самого начала быть соединенными. Это достигается простым вызовом checkForMerges() в начале уровня. Понятно, что это куда проще и элегантней, чем пытаться внутри карты указывать, какая клетка должна соединиться с какой, и во время обработки карты это выполнять.

Но теперь программист доходит до более продвинутых уровней, в которых в игре появляются черные клетки. У них есть несколько особенностей, которые необходимо учесть при написании их поддержки. Это не просто еще один цвет. На карте их легко изобразить: ну, буква b у нас уже занята, так используем букву B. Но поведение у них другое.

Во-первых, черные блоки, если они соприкасаются друг с другом, не сливаются, как другие цвета. Что ж, думает программист, я добавлю в функцию doOneMerge(), главную рабочую лошадку соединения блоков, исключение: если клетки черные, не соединять.

Но тогда возникает проблема с начальной картой: в ней есть целые блоки черных клеток, которые формируются к началу игры уже готовыми. До сих их формировал вызов checkForMerges() в начале уровня, но черные клетки он теперь соединять откажется. Придется, видимо, делать исключение и тут. Пусть функция checkForMerges() пример дополнительный аргумент - если он true, то будет соединять черные, если false, то не будет. В начале уровня мы вызовем с true, а в течение игры после каждого движения - с false.

Но тут программист смотрит на карты следующих уровней, и ругается вслух:



Есть уровни, в которых черные блоки с самого начала стоят рядом, и при этом не соединены. Это означает, что checkForMerges() в начале уровня сработает неправильно и соединит их все.

Что же делать? Кажется, нет иного выхода, как указывать на карте, какие черные клетки должны соединяться, а какие нет. Но это же очень неприятно. Придется добавлять кучу информации, когда описываешь уровень. Эта информация не влезет удобным образом в один символ, поэтому карта перестанет быть простой ASCII-картинкой, придется как-то усложнять. Можно, например, оставить место вокруг каждого символа, и ставить черточки-соединения там, где нужно:
" x                         x",
      "                            ",
      " x b B B                   x",
      "   | |                      ",
      " x b B g-g           g     x",
      "   | |               |      ",
      " x b B B B           g B b x",
      "                            ",

В этом отрывке карты некоторые соседние 'B' соединены вместе, а некоторые нет. Но это будет довольно муторно выписывать, не говоря уж о том, что код, который считывает карту и строит из нее объекты, сильно усложнится.

Еще до того, однако, как программист пытается все это написать, ему приходит в голову одна идея. Это не гениальная идея, и надо специально отметить, что программист в этой истории (то есть я) не считает ее каким-то особенным достижением. С определенной точки зрения она буквально лежит на поверхности; но при этом верно и то, что есть много программистов, которые ее не заметят и будут продолжать примерно по указанному выше рецепту.

Идея следующая: черные блоки, которые стоят рядом и не соединяются - на самом деле блоки разных цветов; это просто обман зрения, что они все черные.

Немедленно после этого открытия все исключения, которые нужно было вносить в код, все усложнения и дополнительная информация - все это исчезает.

Во-первых, описание уровня остается простой ASCII-картинкой, в которой мы просто пользуемся (например) цифрами 0,1,2... для обозначения разных цветов "черный-0", "черный-1", "черный-2" итд.
"x            x",
      "xb01         x",
      "xb0gg     g  x",
      "xb023     g4bx",


Во-вторых, не нужно отдельного исключения в функции doOneMerge(): когда два черных блока встречаются, они не соединяются по той же причине, по которой не соединяются красный и зеленый.

В-третьих, не нужно особого поведения в начале уровня и дополнительного аргумента к checkForMerges(). В начале уровня клетки '0' все соединяются вместе, а клетки '1', '2' и '3', стоящие рядом с ними, остаются обособленными, как и требуется.

В-четвертых, даже в функции, проверяющей, закончил игрок уровень или нет, не нужно дополнительного кода. Я о ней раньше не упоминал, но эта функция проверяет, что для каждого цвета все его клетки соединены вместе; очевидно, для черного цвета нужно было делать исключение, потому что черные блоки для прохождения уровня соединять не надо (да и невозможно). Теперь это исключение можно убрать, потому что каждый из цветов черный-0, черный-1 и так далее, оказывается "законченным" уже в начале уровня, и не мешает проверке. Мелочь, а приятно.

Единственный дополнительный код, который вообще нужно писать - это в том месте, где мы собственно названию 'red' сопоставляем реальный HTML-цвет, и так же всем другим цветам, нам нужно черному-0, -1, -2 итд. всем прописать одинаковый черный цвет. И все. Вот и вся поддержка "черных блоков", которая, как думал поначалу программист, потребует немало строк кода.

Эта идея, которая пришла в голову программисту - пример определенного шаблона, паттерна мышления. Этот шаблон очень и очень полезен, потому что без него пришлось бы писать лишний код в четырех разных местах. Конечно, этот конкретный пример - игрушечный, и пришлось бы написать, скажем, 30 лишных строчек кода, и он был бы немного более сложен и неудобен. Но игрушечный пример хорошо иллюстрирует общий принцип. Та огромная разница в эффективности между лучшими программистами и худшими (или средними), о которой часто говорят и упоминают разницу то ли в 10, то ли в 25 раз (хотя исследования, которые якобы это доказали, сомнительны) - откуда она берется? Из того, что супер-программист в 10 раз быстрее набирает исходники и запускает компилятор? Нет. Лучшие программисты действительно отлаживают одну и ту же программу и чинят в ней баги намного быстрее средних и плохих, это верно. Но другой значительный вклад, а может и основной, в их эффективность - то, что они пишут намного меньше кода, и он намного проще, чем то, что выходит у простых смертных вроде нас с вами.

Если это полезный шаблон, то как его описать, определить, научить им пользоваться? Вот этого я как раз и не знаю. Какая абстрактная идея скрывается за решением представить разные черные блоки объектами разных цветов? Может быть, скажем, "вещи, которые выглядят одинаково на поверхности, могут быть совсем разными внутри". Но такая сентенция, с таким уровнем общности - это трюизм. Никому не поможет, если она будет написана в учебнике или высказана лектором. Почему мне пришла в голову эта идея? Может, потому, что я видел что-то похожее, когда читал другие исходники черт знает чего черт знает сколько лет назад, и вот этот шаблон отложился где-то в памяти: вот какой полезный трюк, типа. А сейчас всплыл. Но точно не потому, что меня этому кто-то пытался научить.

Может быть, полезные и важные шаблоны мышления в программировании есть, но они существуют главным образом как неясные метафоры у нас в голове, трудно определимые и не поддающиеся емкому описанию в словах; их трудно или невозможно преподать, ими можно лишь заразиться в процессе чтения исходников, своих и чужих, решения проблем, накопления опыта.

Комментариев нет:

Отправить комментарий