Anatoly Vorobey (avva) wrote,
Anatoly Vorobey
avva

Category:

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

Думаю, в программировании есть полезные шаблоны мышления. Только это не приевшиеся и по большей части бесполезные 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 раз быстрее набирает исходники и запускает компилятор? Нет. Лучшие программисты действительно отлаживают одну и ту же программу и чинят в ней баги намного быстрее средних и плохих, это верно. Но другой значительный вклад, а может и основной, в их эффективность - то, что они пишут намного меньше кода, и он намного проще, чем то, что выходит у простых смертных вроде нас с вами.

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

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

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 78 comments
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →