четверг, октября 14, 2010

Что писать в лог


Как-то получается, что когда есть о чем писать нет времени, а когда есть время, писать не о чем. Зато есть проверенные временем посты на RSDN, на которые изредка требуется ссылаться. Вот некрокросспостингом я сейчас и займусь.


Регулярно люди спрашивают как и что писать в логи. Могу рассказать как это сделано в одном реальном проекте. Способов применения логов ровно два: а) помогать инженерам отвечать на вопросы пользователей в стиле "Что я только сделал не так?", и б) помогать разработчикам воспроизводить баги. Для этого критично знать, что, как и в какой последовательности происходило в системе. Если по логу не удается локализовать баг с точностью до строчки кода -- не страшно. Задача лога примерно очертить место поиска.


Уровней логгирования используется всего четыре:

  • DEBUG

  • INFO

  • WARNING

  • ERROR


  • Уровень CRITICAL не используется потому что изнутри системы невозможно обрушить ее целиком и навсегда. Поэтому вместо log.crit() вставлен код восстанавливающий систему после ошибки. Внешние сбои, конечно, могут обрушить что угодно, но они фиксируются внешними средствами. В данном случае в роли внешних средств выступает довольно серьезная система мониторинга на основе cacti. На практике не очень интересно, как именно система упала, если отвалился жесткий диск или рухнула сеть.


    На продакшене всегда включен уровень INFO. Очень редко, когда требуется воспроизвести особо злобный баг или проверить правильность алгоритма логгирование переключается на DEBUG. Снижать уровень до WARNING или ERROR не имеет смысла, потому что теряется необходимая информация. Если какая-то подсистема (чаще всего демон) забивает общий лог своими сообщениями, то для нее выделяется отдельный лог файл. Разные части системы пишут в собственные лог файлы. Все логи ротируются раз в сутки, а также при каждом перезапуске системы. Это полезно при апгрейдах, чтобы четко различать какие ошибки относятся к старой версии, а какие к новой.


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


    ERROR


    Логгирование с уровнем ERROR пишется в следующих местах:


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

      
      if (phaseOfMoon.isIncorrect()) {     
          log.error("Incorrect phase of moon at " + someEnvironment);     
          throw new AstronomyException("Incorrect phase of moon"); 
      } 
      
    • там, где мы преобразуем исключение в более общее с потерей информации. Обычно это происходит на границы системы, когда мы не хотим отдавать наружу специфичную информацию.
      
      } catch (SQLException ex) {     
          log.error("Databse error", ex);
          throw new SomeInterfaceException("Шеф, все пропало, шеф.") 
      } 
      
    • там, где исключение пробрасывается выше границы нашей системы
    • там, где прекращается обработка исключения
      
      } catch (SomeException ex) { 
          log.error("Не шмогла, ну и ладно", ex); 
      }
      
    • в случае ошибок, не связанных с исключениями
      
      if (positiveValue < 0) {
          log.error("Sanity check: positive is negative");
          return;
      }
      

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


    WARNING

    Общее правило: если какая-то неприятность произошла, но ее появление ожидаемо, неприятность не критична для работы или status quo может быть восстановлен автоматически, то это WARNING. Он пишется в момент обнаружения если неприятность невосстановима, или же в момент автоматического восстановления. Под warning попадают попытки восстановить соединение с сервером, перечитать файл, и т.п.


    INFO

    На уровне INFO логгируются события на уровне точек входа в систему: действия пользователя, вызовы API, запуск и остановка демонов и т.п.


    DEBUG

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


    Ну и напоследок несколько метрик. На 1000 строк кода приходится 5 записей в лог. Из них:

    • 51% — Error
    • 21% — Warning
    • 13% — Info
    • 15% — Debug

    понедельник, июня 28, 2010

    MarginCon'10

    В субботу прошел очередной MarginCon'10 (потомок RuPy.ru). На этот раз начало было в 0900, так что прогуляться по городу в поисках еды и лулзов не удалось. Ну и ладно, тоже неплохо. По дороге выяснилось, что билет на поезд Новосибирск - Омск можно купить онлайн, при этом его даже забирать не обязательно. Есть электронный check in, как в аэропорту. В следующий раз обязательно воспользуюсь.

    На самой конференции было шесть полновесных докладов, некоторое количество микро-докладов на 5 минут и 1 час свободного общения. Затем желающие собрались в заведении под вывеской "Колчак" на afterparty. Увы, билеты на поезд помешали в полной мере насладиться восточной кухней и живым общением, так что засвидетельствовав свое почтение, пришлось отправляться восвояси.

    Что было хорошо

    • Рекламная кампания. Я узнал о MarginCon'е за месяц, как миминиум из трех источников.
    • Полновесные доклады были очень интересные. Практически не было пересказов мануалов.
    • Видео-доклады были на высоте. Если в нашем углу нет мало интересных людей, их можно приглашать выступать онлайн. Снимаю шляпу перед блестящей идеей. Отдельное спасибо Льву Валкину за рассказ о собственно опыте.
    • Twitter в качестве СМИ показал себя великолепно. Завел себе.

    Что не очень

    • Мало обсуждений. Скорее всего из-за того, что люди рассказывали о своем использовании чужих технологий. У них нет уникального знания, которого нельзя нагуглить самому. Разработчики, рассказывающие о собственных технологиях, вызывают гораздо больший интерес.
    • Lighting talks. Мало, и большей частью ни о чем. Хотя рассказ о Common Lisp очень понравился.
    • Open Spaces. Опять же общих тем для обсуждения не было. В результате все скатилось в привычное "Образование", "Gnome vs. KDE", "emacs vs. vim" и т.п. Идея с выбором тем, которые потом будут организованно обсуждать радостно провалилась. Народ просто не оставил достаточно заявок.

    Что можно изменить

    • Сменить акцент конференции с собственно маргинальных языков на разработку на маргинальных языках. Тогда под формат попадут вещи связанные с языком, вроде: "Какие новые либы вы пропустили", "Куда движется Python", "Что будет в новой версии Rail", "Когда наконец выйдет Scala 2.8". Понятно, что большинство из этих тем недостойны полновесных докладов и в любом случае будут пересказом чьих-то слов, но Lighting Talks ими вполне можно интересно заполнить.
    • Выкинуть Open Spaces. После конференции можно сразу переместиться на afterparty (возможно более организованно) и уже там, под пиво, обсуждать. Ресторан для этого мало подходит. Скорее придется снять какое-нибудь кафе. Под такое дело, я даже готов сдать взнос.
    • Ну и, я надеюсь, слайды и записи будут выложены на сайт.

    среда, марта 10, 2010

    Versions

    Я уже как-то писал про нумерацию версий на RSDN. Возникла нужда сослаться, так что выношу тот пост сюда с некоторыми изменениями.

    Для чего вообще нужна нумерация версий? Самый простой и очевидный ответ: для идентификации сборок выходящих из рук разработчиков. Номер версии должен помогать отвечать на вопросы: Это одинаковые версии или разные? Какая из них была выпущена раньше? Что поменялось со времени предыдущей версии? Что нужно сделать, чтобы собрать точно такую же версию?

    Не стоит путать внутренний и маркетинговый номера версий. Если меркетинг хочет чтобы какая-то версия называлась Gold Enterprise Platinum Edition 7.0 то это никак не влияет на внутренний номер версии.

    Простая и в то же время достаточная система именования версий используют комбинацию из четырех чисел, записанных через точку: major.minor.hotfix.build

    Первые три числа это уникальный номер той версии, которая выкатывается пользователю. Они нужны для того, чтобы одним взглядом оценивать различия между версиями и степень их совместимости. Эти числа назначаются по определенным правилам.

    major — основной номер версии. Переход от одной основной версии к другой означает существенные изменения, часто вызывающие несовместимость с предыдущей версией без дополнительных ухищрений. Смена формата данных, глобальный рефакторинг, редизайн, существенное изменение функционала — достаточные основания для увеличения версии. Исправления багов, даже самых злокозненных, нет. Это число может только инкрементироваться. Увеличение major версии на единицу сбрасывает minor и hotfix в ноль. Традиционно, до релиза major версии равен нулю, с тем, чтобы в момент релиза выкатить пользователям 1.0.0.xxxx и в дальнейшем считать от него.

    minor — дополнительный номер версии. Это номер небольшого изменения в рамках версии. Это может быть появление нового функционала, совместимое изменение формата данных и т.п.

    hotfix — порядковый номер хотфикса. Часто бывает, что все изменения в версии связаны с исправлением багов, а не с добавлением функционала. В этом случае изменяется номер хотфикса. Очень часто, такая версия отдается в виде патча для обновления с предыдущей версии.

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

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

    понедельник, марта 01, 2010

    Bug theory - Initialization flow bug

    С простыми багами вроде покончено. Перейдем к более сложным.

    Initialization flow bug

    Симптомы:

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

    Критические места:

    Инициализация модулей, добавление новых зависимостей, изменения порядка инициализации.

    Проблема в том, что инициализация отдельных компонентов происходит позже, чем их первое использование. В многопоточном случае все может быть осложнено возможным race condition.

    Профилактика:

    Локализовать инициализацию в одно месте, практиковать Dependency injection, писать интеграционные тесты, при добавлении зависимости анализировать порядок инициализации для разных случаев.

    среда, февраля 24, 2010

    Bug theory - Redundant expectations

    Redundant expectations

    Симптомы:

    В определенных условиях система делает не то, что ожидается. Баг проявляется только в частных случаях, все остальное время система работает нормально. При воспроизведении бага могут возникнуть проблемы, но если воспроизводить тестовый сценарий в точности баг всегда проявляется.

    Критические места: любые межмодульные взаимодействия, особенно слабо изученными сторонними модулями.

    Это типичный баг "на границе". В определенных условиях сторонний модуль ведет себя не так, как ожидалось. Чаще всего это происходит когда документация на интерфейс была прочитана невнимательно или изначально была неполна. Хороший пример, это NullPointerException при нарушении принципа Деметры.

    person.getLeftHand().getRingFinger();

    Для большинства людей это сработает, но на первом же одноруком мы получим NullPointerException.

    Профилактика:

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

    среда, февраля 17, 2010

    Bug theory - TODO: Implement

    TODO: Implement

    Симптомы:

    Не происходит то, что ожидалось. Выбрасывается NullPointerException, OperationNotSupportedException, InvalidArgumentException или любое другое похожее исключение. Такие баги легко обнаруживаются и легко воспроизводятся. При анализе в коде обнаруживается комментарий "TODO: Implement" или просто отсутствие необходимого кода.

    Критические места:

    Код после рефакторинга, новые фичи, реализации интерфейсов.

    Тоже простой и очевидный баг, который появляется когда разработчик по какой-то причине оставляет часть контракта нереализованной. Это может происходить по разным причинам. Лень заниматься пустой работой, противоречивый или слишком детальный интерфейс, отсутствие времени.

    Профилактика:

    Избегать противоречивых и слишком сложных интерфейсов. После рефакторинга обязательно проверять появившиеся TODO/FIXME.

    вторник, февраля 16, 2010

    Bug theory

    bugzilla logo

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

    Ну и для затравки:

    Misprints

    Симптомы:

    Что-то происходит не так, как ожидается. Выбрасываются неожиданные исключения, возвращаются не те данные. Будучи обнаруженным, такой баг легко воспроизводится. Почти всегда опечатка легко обнаруживается и легко исправляется, но в тяжелых случаях она может прятаться годами.

    Критические места:

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

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

    Профилактика:

    Использовать строгую типизацию там, где это возможно. Избегать магических чисел. Избегать арифметики, а если избежать не удалось, изолировать ее и покрывать юнит-тестами. Писать сложные алгоритмы в стиле literate programming. Избегать похожих имен функций и переменных в одном контексте.

    пятница, января 30, 2009

    The validation problem

    Сложно найти программиста, который бы никогда не сталкивался с задачей проверки пользовательских данных. Буквально на первой же лабе в институте, преподаватель "ломает" программу некорректным вводом. Почти все через это прошли и признают, что относится к входным данным нужно со здоровой паранойей. Вопрос лишь в том, где именно проверять входные данные. Возбужденные сторонники ООП поднимают на щит инкапсуляцию и провозглашают немедленную валидацию на уровне классов бизнес-сущностей, бородатые DBA ратуют за проверки на уровне БД, очкастые архитекторы стоят на вынесении валидаторов в отдельную сущность, аппелируя к SRP. Разумеется, все они правы по-своему.

    Есть такая штука, которая называется инвариантом системы. Это такой набор утверждений, истинных в любом ее состоянии. Хороший пример такого инварианта -- бухгалтерский баланс. Сумма активов равна сумме пассивов. Если это не так, значит где-то произошла серьезная ошибка. Для отдельных компонентов могут быть свои инварианты, попроще. Например, для любого счета всегда известна валюта или метод никогда не возвращает null. В первую очередь инварианты нужны внутри самой системы. Если мы знаем, что для всех счетов всегда известна валюта, мы можем обращаться к ней без проверки не рискуя получить NullPointerException. Кто же ответсвенен за консистентное состояние системы? Очевидно, сама система. Технически это можно сделать разными способами. Годятся простые проверки, вынос обязательных полей в конструкторы, констрейнты на базе, ассерты, современные средства контрактного программирования и все прочее, что придет в голову. Однако у инварианта есть side effect в том, что каждое дополнительное утверждение снижает гибкость системы. Если какое-то состояние системы нарушает инвариант, оно невозможно, независимо от желаний программиста. Избыточность в инварианте системы приводит к тому, что вместо простых действий устраиваются танцы вприсядку с целью обойти инвариант.

    Чтобы избежать этой опасности, инвариант следует тщательно оберегать от распухания. Для того утверждения не влияющие на целостность системы целесообразно выделить в отдельный слой. Например, в системе никому не требуется номер паспорта клиента, но с точки зрения бизнеса это поле относится к обязательным, потому что по закону положено. Более того, часто возникают ситуации, когда в одних случаях поле является обязательным, а в других опциональным. Например, паспортные данные требуется от клиента клиент собирается получить деньги и небязательны если он принес деньги отдать.

    Проверки, которые не входят в инвариант стоит собирать в одном месте, что значительно упрощает внесение изменений. Тут хорошо годятся и отдельные классы *Validator, валидаторы на основе XML правил и т.п. Если правила меняются неприлично часто, хорошо помогает вообще отдать настройку правил валидации админу. Пусть он в рантайме поднимает галочки. Пользователи будут довольны.

    Почему бы инвариант системы тоже не оформить в виде набора отдельных валидаторов? Для внешних данных это имеет смысл, но проверки только внешних данных недостаточно. Любое изменение данных крайне желательно проверять на соответствие инварианту. Поэтому от дополнительных средств контроля целостности никуда не деться. Надеюсь, проблема валидации стала немного яснее.

    воскресенье, января 11, 2009

    Chinese classification

    Среди людей занимающихся семантикой распространен мем "китайская классификация" -- таксономия с "плавающим" основанием. Корни этого термина растут из эссе Борхеса "Аналитический язык Джона Уилкинса" в котором он упоминает некую китайскую энциклопедию под названием "Небесная империя благодетельных знаний". На ее древних страницах написано, что животные делятся на:

    1. принадлежащих императору;
    2. бальзамированных;
    3. домашних;
    4. молочных поросят;
    5. сирен;
    6. сказочных;
    7. бродячих псов;
    8. включённых в эту классификацию;
    9. бешеных;
    10. бессчетных;
    11. нарисованных тончайшей кистью из верблюжьей шерсти;
    12. прочих;
    13. только что разбивших кувшин;
    14. издалека кажущихся мухами.

    С точки зрения современного образованного человека китайская классификация выглядит дико. С другой стороны такие классификации встречаются повсеместно, достаточной вспомнить классификатор жанров FB2, библиотечный каталог, или классификатор МКБ-10. ailev вообще высказал мысль о том, что все онтологии -- это замаскированные китайские классификации, ибо именно таковые классификации лежат в основе мира, именно к таким классификациям привыкли люди. Если это так, возникает вопрос, откуда берутся "китайские классификации" и почему люди охотнее создают и пользуются ими вместо строгих таксономий?

    Не смотря на кажущуюся сумбурность, "китайская классификация" подчинена определенной внутренней логике. Строгая таксономия делит мир на классы согласно некоторого рационального критерия: принадлежит животное императору или нет; домашнее оно или дикое; существует оно в реальности или же оно вымышленное и т.п. Для "китайской" классификации в качестве основания выступает не рациональный критерий а степень важности некоторого класса. В каноническом примере, создателя классификации равно интересуют, как животные императора, так и домашние животные, независимо от того, кому они принадлежат. Если бы автор пожелал создать на тех же критериях строгую рациональную классификацию, она получилась бы в несколько раз больше и многие ее классы оказались бы невостребованными. "Китайские классификации" стоит рассматривать как оптимизации строгих таксономий под конкретные способы использования.

    Очевидно, что любую строгую иерархическую классификацицию можно сделать китайской. Достаточно присвоить классам разные веса и сгруппировать на одном уровне классы с одинаковым весом. Некоторый аналог такого приема в дизайне интерфейсов -- быстрые закладки для часто используемых функций. К сожалению, простого способа обратного преобразования не существует, поскольку невозможно автоматически выделить рациональные критерии из мешанины классов. Единственный достоверный способ на сегодня, это кропотливая ручная обработка. Неудивительно, что инициатива semantic web продвигается с таким скрипом. "Китайские" классификации это объективная часть природы человека, и хотим мы этого или нет, нам придется учиться с ними справляться.

    пятница, декабря 19, 2008

    Data about data

    Если открыть спецификацию какого-нибудь формата данных, в ней скорее всего обнаружится раздей посвященный метаданным. Иногда, такой раздел превышает остальную спецификацию в несколько раз по объему и по сложности. В то же время, на практике метаданные используют весьма и весьма нечасто. Существуют даже форматы, которые никто так полностью и не имплементировал, по причине невостребованности. Например, тот же ID3v2 (метаданные для MP3) с его несколькими сотнями тегов начиная от автора песни и заканчивая переводами лирики. Или EXIF, которым пользуются ровно потому, что все современные камры пишут в него параметры съемки. Еще ни разу не видел, чтобы из него кто-нибудь использовал разделы описания или копирайта. Или FB2, по поводу метаданных которого регулярно возникают споры на форумах о том, как их заполнять. Или MARC, с правильным заполнением которого проблемы даже у профессиональных библиотекарей, хотя им-то сам бог велел прекрасно разбираться в предметной области. И какой формат ни возьми, с метаданными будет плохо. Или мало, или много, или в самый раз, но совсем не то, что нужно.

    Для того чтобы понять почему так происходит, нужно определить, что такое метаданные. Лаконичное определение из заголовка, конечно, прекрасно, но оно ничего не говорит о том, какие это данные. Почти все факты о данных можно представить в виде утверждений <субъект, свойство, значение>. А набор свойств из предметной области и доменов для значений суть онтология. Например, фокусное расстояние это свойство, которое может принимать значение типа длина (а не угол или скорость). В большинстве форматов онтология жестко зафиксирована, так что не бросается в глаза, но она всегда присутствует.

    Вернемся к проблеме. Разработчик формата всегда держит в голове какой-то способ использования этого формата. Иначе он в принципе не смог бы разработать ничего полезного. Однако, как только он фиксируеется на способе он начинает представлять себе предметную область с одной, узкой, точки зрения. В результате получается онтология, описывающая каую-то узкую область, которую разработчик считает верной. Однако, у пользователей совсем другое мнение на этот счет. Почти наверняка кто-то будет использовать формат по другому. ID3 созвался для музыки, но его можно использовать для кучи разных вещей: от записи телефонных разговоров, до калибровочных сигналов дефект-детекторов. EXIF хорошо описывает метаданные съемки, но не годится для описания преобразований, проделанных с оригинальным изображением. Проблема в том, что невоможно предусмотреть все способы использования метаданных и разработать всеобъемлющую онтологию. Более того, такие попытки почти всегда приводят либо к излишней общности либо к излишней сложности. И то и другое отталкивает пользователей от использования метаданных. Хуже, когда появляются несколько стандартов метаданных для одного формата. В итоге каждый софт какие-то форматы метаданных поддерживает, а какие-то нет, что еще сильнее отвращает пользователей. Никому не хочется описывать файл, если при следующей обработке или конвертации эти сведения потеряются.

    Как можно с этой проблемой справиться? Если вы разработчик формата, не пытайтесь думать за пользователей -- все равно не выйдет. Дайте им возможность определять собственные онтологии и снабдите их парой убедительных примеров для конкретных узких областей. Хороший пример движения в нужном направлении Adobe XMP основанный на RDF. Нельзя сказать, что этот формат идеален, но хоть что-то.

    пятница, ноября 28, 2008

    Time to live

    У каждого предмета есть определенное время жизни. Рано или поздно он устареет, износится и потребует замены новым. Или просто исчезнет проблема, ради решения которой этот предмет создавался, как исчезли мундштуки, подстаканники, пресс-папье и буфеты. Это касается не только материальных объектов, но и информации, в том числе и информационных систем. У каждой строчки кода, у каждого килобайта данных есть собственное время жизни. Время жизни есть даже у таких неосязаемых сущностей, как идеи или соглашения. Для того, чтобы научиться оценивать характерное время жизни, нужно хорошо понимать механизмы разрушения объектов. Например, автомобиль может разрушится в результате аварии, физического износа или морального устаревания. Если известно, что автомобиль морально устареет через 10 лет, нецелесообразно обеспечивать его надежную работу в течение большего срока. Все равно к тому времени он уже окажется под прессом. Знание характерного времени жизни позволяет поддерживать качество на необходимом уровне, не больше и не меньше. Зачем делать автомобиль надежным, если через 10 лет он все равно окажется на помойке, где не важно работает он или нет.

    Данные

    Оставим за скобками временные данные, которые на то и временные, что их время жизни невелико. Для данных постоянных время жизни составляет от 25 лет до бесконечности. Данные редко становятся не нужны, a накопленные за долгие годы данные невозможно ничем заменить. Например снимки допоптопных аппаратов советской серии Венера, несмотря на отвратительное качество будут жить еще долго. Сперва в качестве основного источника информации, затем для сравнения с современными снимками и анализа изменений. Обычно, вменяемые люди стараются не уничтожать данные, мало ли что еще пригодится. Вдруг завтра из них удастся извлечь новую информацию. Мне доводилось работать с базами, которые пережили три поколения бизнес-кода и переживут еще как минимум столько же. Поэтому к проектированию баз данных нужно относиться особо внимально. Любое ваше сиюминутное решение в угоду производительности или простоте кодирования будут с матюками вспоминать через много лет. Изменить что-то в унаследованной базе, в которой уже накопились гигабайты данных нереально сложно.

    Протоколы и стандарты

    Две причины, по которым устаревают стандарты и протоколы: они перестают отвечать изменившимся требованиям или появляется способ сделать то же самое проще. Поскольку технологии непрерывно меняются и то, что раньше требовало дестятка PhD, сегодня доступно школьнику, редкий стандарт проживет больше 10 -- 15 лет. Затем, если представится возможность, он отправится на помойку истории. К сожалению это происходит не всегда. Очень часто изменения уже внедренного стандарта, процесс настолько болезненный, что стандарт активно используется не смотря на то, что они окончательно устарели. В качестве вопиющих примеров можно вспомнить Z39.50, SMTP, FTP и HTTP. К проектированию стандартов имеет смысл относиться даже более внимательно, чем к проектированию баз данных. "Внимательно" в данном случае означает не "предусмотреть все возможные случаи", а обеспечить возможность изменений. Все равно предусмотреть все не удастся, а если и удастся, то этим все равно не станут пользоваться из за непомерной сложности.

    Код

    Срок жизни кода определяется сроком жизни требований и сроком жизни платформы. В любом случае, срок жизни прикладного кода составляет 5 -- 10 лет. К этому моменту он оказывается практически полнотью переписанным с нуля. Часто, код преписывают весь сразу, одновременно с переходом на современную платформу. Конечно, бывают и исключения. Чаще всего они связаны либо с неизменными в течение десятилетий требованиями, либо с очень высокой ценой апгрейда. Например, связанного с высокой ценой миграции данных. Код ядра живет дольше в силу того, что зависит не от зыбких пользовательских требований, а от технических ограничений. Однако и технические ограничения меняются: если раньше несколько процессоров встречались только в серьезных серверах, то теперь многоядерные процессоры добрались до ноутбуков. Даже если завтра ваши алгоритмы и не изменятся, не исключено, что им придется работать совсем в другой среде, что влечет за собой переписывание кода.

    Абстракции и модели данных

    Абстракции живут от нескольких минут до тех пор, пока их помнят или пока не придумают что-то получше. Двойная бухгалтерская запись была изобретена в XV веке, а используется по сей день, несмотря на то, что в эпоху компьютеров без нее легко можно обойтись. Даже если абстракция хреновая, она все равно не умирает. Слишком много всего от нее зависит. Тут и код, и привычки людей, и их внутрення картина мира. Если вы предложите бухгатерам отказаться от двойной записи, они вас съедят. Будьте осторожны, если ваша абстракция получит распространение, вы ее уже не остановите.

    Пользовательский интерфейс

    Пользовательский интерфейс протухает за 3-5 лет. Он подвержен тем же изменениям требований что и код, вдобавок новые интерфейсные технологии появляются ничуть не реже программных. Даже отличный интерфейс пятилетней давности выглядит малость устаревышим, а десятилетним интерфейсом современному человеку пользоваться неудобно, да и глаз режет. Метафоры за 10 лет успели сильно измениться. Если пользователи не могут сбежать, устаревший интерфейс удается сохранить два-три срока. Затем начинаются проблемы уже с поиском операторов, способных работать на таком старье.

    четверг, сентября 25, 2008

    ICFP Contest '08 Final results

    На официальной странице ICFP Contest выложены результаты, которые так долго ждали большевики. Было проведено 11 заездов, в результате которых был определен один победитель (Team Smartass) и 297 неудачников, которым не так повезло. К сожалению финальные карты еще не выложены, поэтому трудно сказать что пошло не так, но финалисты предыдущего чекпойнта "вылетели за ограждение". Как в прошлый раз, я подготовил таблицу с суммарными результатами. Команды отсортированы по количеству раундов, в которых они приняли участие независимо от результата. Команды, прошедшие одинаковое число раундов отсортированы по рейтингу. Напоминаю, чем меньше рейтинг, тем круче. Для каждой команды приведена статистика исходов по всем пяти заездам каждого раунда. В общем, можно меряться. Get it and use it well.

    Моя команда называется Mosquito. Каким чудом она попала на 11 место, оставив позади страшных монстров, сам не понимаю. Видимо, новичкам везет.

    #TeamRoundsScoreHomeMartianCraterTimeoutError
    1Team Smartass11512510505
    2shinh115153704951
    3Error 404115419605032
    4UBE11546000487
    5LazyBear115568705122
    6SzM1160863040555
    7souris aveugle1161162040123
    8frank3241164361039655
    9kstm.org1167453040132
    10fireinthedisco2116894004582
    11Mosquito1173004041851
    12Om Nom Nom Nom1174314037153
    13Giantslalom1174855038161
    14epriestley1176509033175
    15intelliarts1060905037814
    16cw106170704010
    17The B-Team: 25/26ths as good as the A-Team1061928040145
    18Blue Iris106268603713
    19Spore Sept 7th, only $49.99! Play Bejeweled @ popcap.com!10631580381011
    20team_bruno10649780419
    21Hands Solo106522004082
    22jabber.ru106543103713
    23Celestial Dire Badger10659590446
    24Side Effects May Include...106607403812
    25Apathy Kings10665890361211
    26CNaml106728103713
    27Cup<'t>1069525036131
    28TC2107033104010
    29Team Meh107104303812
    30Yeahbut10712870437
    31td107169504271
    32Purple Pointer Eaters107195603515
    33TeamDJ1072120036131
    34ur 67P1072335032171
    35NMSU107327703911
    36Actua107390203416
    37Eger a Marson1073954035132
    38EdFred1074258038102
    39Arekuma1074803035132
    40little lemon1078496034142
    41Order of the Fridge108053103317
    42HackerDom_194421503510
    43ais5239442380387
    44ILL martians94483003411
    45Sir Bedevere the Wise94507803492
    46Kee9455360369
    47Urxae946507034101
    48Heklers94657903384
    49Malfunctional Programmer94702503510
    5042947790033111
    51Slow9478020369
    52LUHPI94842903672
    53tidder948657034101
    54elbereth@utmc9487230378
    55Wile E.94923003591
    5612a094941803114
    57PurelyFunctionalInfrastructure95015203411
    58RHZ95015703672
    59m1-speedygonzalez95151803411
    60Five of Six951680030762
    61Bobry9543260294111
    62a1k0n955337028134
    63Team Sampou957250030141
    64taxi driver839880029101
    65BugbearR84110603451
    66Dee Mon8428540346
    67ayatuki84333502695
    68HackerDom84342102812
    69Too Simplicity and Beyond!843892026941
    70I-cons-a-lot84487702992
    71The Dibbies844886023116
    72Hacking in the Rain845433024781
    73Altair84625502992
    74lazymonkeys846802030631
    75'a infvec84712902875
    76protocolocon847215031414
    77Kharkiv MIND84798102785
    78Begot85010702515
    79Lazy elf8509630231061
    80astsmtl7313950278
    81shitamachi no ue73195602771
    82kounoike7323950287
    83Java Jones and the Kingdom of the Crystal Crater73315202672
    84TBD73319802510
    85lepidum7333640278
    86ktkr-cpp733909026621
    87miyu73412203041
    88Gcup73415602681
    89Epsilon73532502942
    90nanndemoiidesu73556702591
    91pirapira73572002510
    92barry, our driver, is reckless73665102591
    93NAU ACM73724902195
    94TheRoverers73732102411
    95DotWeb74033302096
    96SwtPl740343021104
    977-15741668022121
    98Beer:Thirty626351024321
    99Team DumbAss62785302181
    100inforfun6278540246
    101Team Lolemnity628437021522
    102ntua 67P62855002343
    103jan nasa62952702352
    104Pear Programmers62958502442
    105Christoph Breitkopf6297470246
    106spotrover629798021351
    107cashto62984502253
    108The Weyburn Expatriates62999102235
    109ORBIT63013202424
    110Rhope Burn63018202325
    111aruhimorinonaka63020202271
    112green_tea63035702442
    113Bob's Store63058702226
    114WindowMakers 63076002271
    115The Blind Hen63079902262
    116bmm team63095702424
    117Grasshoppers63098602163
    118The Tcler632061020361
    119ksk63215002127
    120MIMUW 406063252602262
    121Florida Institute of Technology63254102253
    122D&D632740018372
    123wjdogs63282601758
    124Kerochan63291802073
    125The Unchecked Exceptions63299302019
    126In dumb we trust63350401974
    127Curry On!633519017364
    128KFL633554021252
    129hibi@utmc.or.jp633664021351
    130YBuzz633863020361
    131Dragons633921018381
    132Q42/Xopus634184018471
    133Team Guyford63420301812
    134Arsbit63512101857
    135one canuck63545101776
    136Chouser635591018372
    137uguu.org63566901848
    138midoj636782019641
    139The Tweezer Minstrels63709601578
    140bens63730001785
    141apple2gs63787902154
    142yarunee638188016383
    143The Usual Suspects63820802136
    144Ramen Holiday638355019461
    145The Code Alchemists63847902181
    146Team Rocket63893201947
    147nekoneko 639315017571
    148The Inmates63937702073
    149camping gaz international63977902226
    150solo r6640265020811
    151GRoboCode64033401938
    152ktkr-ocaml64119901857
    153LazyBottoms644571016671
    154spbsunt5176870214
    155It's all about the Pentiums.51832901933
    156Emory PChem51842701951
    157Toasted Monkeys518614019222
    158Le club des 551887501942
    159suihan_jar519344017431
    160Wobbla Team51939802041
    161__ufo42520548013264
    162Dis Functional52095501546
    163POSTECH-PL52100901654
    164The Higher Order Of Zeuxis521291017332
    165Unary Operator52141701843
    166POSCAT@POSTECH522283014254
    167suzumiyaharuhiko522405011743
    168Hidden Agenda4147770146
    169Incompetent Design414821011531
    170OCamlCore41530501253
    171Knights of Cydonia41590501253
    172Marcin Simonides4160540128
    173td241606201334
    174efg416122013331
    175AntiCylonDefenseTeamImpersonator41616501262
    176ikon416273010343
    177o(ry41644701451
    178bucchigiri41652301325
    179Dylan Hackers41653101352
    180Team Gales416570011252
    181The Rover Routers416690013511
    182Anairiats416719014321
    183Tigerfunk41708406284
    184dot-emacs41737807283
    185Fly (Yaroslavl)41758001271
    186K&R41758401334
    187Mostly Harmless41784901271
    188AyaCFP4178530137
    189putzen along41787401271
    190Enoch Root417978011531
    191futurelabs41809901244
    192Molotov Mocktail41864309722
    193Servants of Barsoom4187290128
    194NetHack41875501262
    195Ruby Team4187930128
    196Almaya418813012521
    197gassa & toxa41930001172
    198(defun friends)31071801032
    199The Lone TeXnician31077008232
    200hogelog3108370663
    201Insane Closure Posse3111800114
    202Macrott31133109321
    203Blazing Saddles31153709312
    204yet another team31155507413
    205honey_rooibos311614096
    206Evil Geniuses For a Better Tomorrow31173601041
    207TeamNewshamBros3117630924
    208Illudium Q-36 Explosive Space Modulator31180609411
    209Giver3118500924
    210rmathew31191205253
    211SIMAP31195709321
    212eugene_jeff31222406423
    213polka31243008421
    214Func-n-stein31251407242
    215EddieHasNoPreferenceForName 3126480852
    216Xebia31281108331
    217Bad Science3129430861
    218Stern Types 31304007323
    219siti31324707611
    220panard_groumpf31343805334
    221SugarSun3134660861
    222mfp31358604515
    223NCSU ARC Alums3136960933
    224The Bobcat Hackers31389806522
    225Knights of the Crescent Moon31402505424
    226isobe31406607431
    227EarthFusion31443108511
    228Epic Fail31467506522
    229plai31476205622
    230ozy4dm315737078
    231wheezards257930811
    232Soul Trader2775804213
    233404 NOT FOUND2784703151
    234WE NEED A TEAM NAME2785406211
    235Not a Number2789105212
    236Stanfy279440712
    237Bloody Camls27950073
    238Team Sparrow279740631
    239Too much free time280430622
    240Linkage Failure28066073
    241kt3k28123073
    242Suzumiya Haruhiko wo Ooini Shizumeru Fullbocco-dan281960631
    243University of Western Australia283980622
    244Littlechina2848305311
    245Drifter's Revenge2851505212
    246Erraen285860631
    247cthulhuivore2869402134
    248ktkr-java2893904213
    249Ocamlman2895005221
    250Team Cow290130622
    251Rhinos290200118
    252Cup<T>292040631
    253The Chun Council29234064
    254FUN292460451
    255ICSkies of Blue and F(P)2925504231
    256Felspar292560631
    257Happy Camels292670631
    258The Teddyborg2928502314
    259drunken ants2928605221
    260foognostic292950127
    261Marti293070631
    262StreetMagic294650622
    263Cal Poly Pomona Computer Science Society29753064
    264nishiohirokazu29851064
    265United Coding Team29888055
    266KuntbucketsAndKuntrags2100630136
    267Turn Right Left210067055
    268mazeSolver@UTMC2102330343
    269Schwarzschild2107120532
    270gpfault id-alex 2107250244
    271aalto high school21153201513
    272The Dudes2115510352
    273Engineers Without Boulders21167104411
    274The Al-Gore-Rhythms AKA The Doug Boat21177001315
    275Team_XIII 211920055
    276The Armstrongs2124310514
    277casillero del diablo2125570154
    278gzoluble213069055
    279Chvex Stucture2148470145
    280SATiriker136160131
    281The unmoving notogawa1423005
    282Monkeyget1423005
    283Sim1423005
    284P Squared1423005
    285gb1423005
    286mokehehe 1423005
    287ocaml users anonymous1423005
    288LISA~HackLab-FI1423005
    289zetafish1423005
    290Puddingforce1423005
    291Chicago Blubs1423005
    292Nodal Asymmetry1840005
    293incognito1840005
    294Monadic Cows1840005
    295Late Ocamler1840005
    296Rufus21840005
    297greedy1840005
    298The Shiniest of Heisenbugs1840005

    понедельник, сентября 08, 2008

    Закон больших чисел

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

    На большой выборке даже маловероятные события становятся наблюдаемыми.

    На практике этот закон означает полную неприменимость «наивной» аналитики и, как следствие, «наивного» тестирования при проектировании информационных систем. Если не предусмотреть редкие случаи ещё при проектировании они больно стукнут на стадии эксплуатации, причем далеко не сразу, а лишь по прошествии некоторого инкубационного периода. Это чревато хотя бы тем, что багфиксинг на внедренной системе, в которой уже накоплен значительный объем данных — отдельное развлечение.

    Ещё одно проявление Закона больших чисел в том, что невозможно построить информационную систему гарантированно не содержащую ошибок в данных. Простой пример: Оператор вводит данные вручную. Оператор не идеален, поэтому он делает одну ошибку на 100 записей. Это означает, что на 1 000 000 введенных записей будет порядка 10 000 ошибок. Поскольку такое количество ошибок нас не устраивает, мы садим поверх оператора контроллера, который будет проверять правильность ввода. Пусть контроллер более внимателен и продуктивен, однако и он не идеален, поэтому допускает одну ошибку на 1000 записей. Тогда вероятность ошибки снижается до 10-5 на запись. Однако, на миллионе записей это даст примерно 10 ошибок. Если количество данных неограниченно растет, а так бывает почти всегда, никаким контролем не добиться гарантированного отсутствия ошибок. Даже оценить вероятность ошибки и то не всегда возможно. Люди не механические приборы, к ним паспортов точности не полагается.

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

    • Признают, что качество данных не имеет значения для бизнеса;
    • Контролируют качество собственными силами;
    • Выбирают один из наборов данных и признают его эталонным.

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

    понедельник, августа 11, 2008

    ICFP '08 Results

    Опубликованы результыты первых восьми заездов. Для удобства фаллометрии свел все в одну табличку. Get it and use it well.

    Score — сумма по всем восьми заездам.

    Команды не допущенные до девятого заезда в табличку не включены.

    UPD: Добавил восьмой заезд. Плюс организаторы вернули еще 16 команд, дисквалифицированных по техническим причинам.

    TeamScore
    1UBE327090
    2td329520
    3Celestial Dire Badger330350
    4Hands Solo330560
    5Blue Iris331800
    6Team Meh332900
    7shinh333270
    8NMSU333390
    9team_bruno333590
    10Team Smartass334470
    11CNaml334680
    12Error 404334770
    13cw336130
    14HackerDom_1336740
    15Actua337670
    16ILL martians337870
    17Yeahbut338630
    18fireinthedisco2340290
    19ais523340450
    20Arekuma341070
    21TC2341190
    22elbereth@utmc341290
    23Spore Sept 7th, only $49.99! Play Bejeweled @ popcap.com!341510
    24Apathy Kings342900
    25LazyBear347940
    26SzM348090
    27Wile E.352650
    28Sir Bedevere the Wise353750
    29TeamDJ353980
    30Kee355170
    31jabber.ru356170
    32frank324356270
    33Urxae358030
    34Malfunctional Programmer358770
    35Heklers359580
    36PurelyFunctionalInfrastructure361340
    37Cup<'t>362370
    38Side Effects May Include...362960
    39Purple Pointer Eaters363780
    40intelliarts365870
    41Slow366260
    42EdFred367150
    4342367510
    44The B-Team: 25/26ths as good as the A-Team371520
    45a1k0n373610
    46RHZ373950
    47souris aveugle374140
    48LUHPI374310
    49little lemon375620
    50kstm.org376070
    51m1-speedygonzalez377810
    52Team Sampou378210
    53Five of Six378920
    54Mosquito381240
    55Eger a Marson381530
    56ur 67P385050
    57tidder385790
    5812a0389440
    59Giantslalom390270
    60epriestley408520
    61Om Nom Nom Nom409070
    62Bobry412030

    вторник, июля 29, 2008

    Nomen est omen

    Представим себе гипотетическую ситуацию: некий юноша-с-косичкой подрядился писать программу. Ну хотя бы учет посетителей в библиотеке. (Ещё ни одной приличной не видел) И вот доходят у него руки до учета пользователей и он радостно создает в базе табличку visitors {firstName, lastName, middleName}. А что, у всех буквально есть фамилия, имя и отчество. Вот у его знакомых в родном Крыжополе точно у всех. Да и девочка-библиотекарь, которая заместо аналитика, говорит что так правильно. И вот поступает такая система в эксплуатацию. А там кого только нет: и Александров-Погорельский, Ли Син Цин, и Хосе Луис Мария Педро Фернандос. И вот мучаются библиотекари, стараясь всунуть чуждые имена в прокрустово ложе программы, сыплются проклятья и летят в сторону незадачливого юноши лучики поноса.

    Произошло это потому, что незадачливый программист не разобрался в сути моделируемых явлений. На больших объемах данных такой подход очень редко сходит с рук — всегда найдутся выпадающие данные, даже если на первый взгляд их быть не должно. Хороший пример: тройные фамилии вроде бы как запрещены законодательно, по крайней мере просто так её взять не получится. Возникает соблазн добавить проверку, и соблазн довольно опасный. Вероятность того, что системе попадется человек с тройной фамилией довольно велика на большой выборке. Аллах знает, как у них это получается, но сменить человеку фамилию можно только через суд.

    Ещё одна распространенная ошибка — трактовать отчество как middle-name. Мне попадался даже один декан, который подписывался Boris A. Gladkih, видимо, не подозревая, что читается это как Борис Афанасий Гладких, что суть нонсенс. Второе имя это именно самостоятельное имя, которое можно использовать отдельно, пусть даже оно и дается в честь отца или деда. Отчество же без имени употребляется исключительно в качестве прозвища и только отдельно от фамилии: «Кузмич — за пивом пойдет». На эти грабли качественно наступили создатели формата fb2.

    В наш век всеобщей глобализации правильнее не задумываться над традициями присвоения имен, а признать, что человек может сам выбрать как ему называться. Технологически, ничего сложного в этом нет, если только вы не хотите поддерживать все возможные традиции именования. Зато, если завтра к вам придет посетитель, у которого в паспорте написано «БОЧ рВФ 260602 Вячеславович Воронин-Фролов» вы будете к этому готовы.

    среда, июля 16, 2008

    ICFP '08 Contest

    Контест завершился, завалы на работе разобраны, настало время небольшого отчета, куда-ж без него.

    Preambula

    Про ICFP contest я узнал начитавшись отчетов и сразуже захотел попробовать. Этот контест выгодно отличается от ненавистных мне «академических» олимпиад и их идиотскими заданиями, не имеющими ни малейшего отношения к реальности. Поскольку по одним субъективными отчетам трудно достоверно оценить явление было решено потрогать воду лапкой прежде чем готовиться к участвовать всерьез. Что и было проделано с величайшим удовольствием. Однако, некоторую подготовку я все же провел: настроил NetBeans с Ruby, создал необходимую структуру папок для проекта, поднял на всякий случай Linux под виртуальной машиной, скачал и запустил LiveCD

    Day 0. Landing

    За час до начала вылезаю на канал #icfp-contest и наблюдаю там больше 200 человек. С трудом дожидаюсь публикации задания. Большого смысла в этом нет, но хочется уже прочесть его, составить план штурма и отправится спать. Время из-за разницы во времени довольно позднее. В час «X» вся эта толпа, коей набралось уже за 300 человек радостно тянется качать задание и на несколько минут роняет сервер. Наконец я добираюсь до нужного документа и погружаюсь в чтение. Итак, задача этого года, в моем кратком изложении

    Есть квадратное поле заданных размеров, представляющее поверхность Марса. На поверхности имеются:
    1. кратеры, в которые можно упасть, насмерть, много;
    2. валуны, о которые можно стукнуться, больно, тоже много;
    3. марсиане, которые норовят сожрать, самоходные, много;
    4. база, дружественная, одна;
    5. марсоход, свой, один.
    На поверхность в произвольной точке высаживают марсоход (с довольно реалистичной физической моделью) для которого нужно написать программу управления (общающуюся с сервером по TCP/IP), приводящую его к базе в обход кратеров, валунов и марсиан. Соревноваться в итоге будут программы между собой, у кого лучше, тот и победитель. В деталях, оригинал на 12 страницах.

    Прочитав задание, понимаю, что Ruby тут не при делах. Марсоход — железка глупая, не станет ждать пока досчитается, укатится к черту на рога. Решаю применить запасной вариант и использовать Java. Пока разворачивается IDE, лезу качать симулятор, но его ещё не выложили. Основных проблем в задании видится три: 1) Сетевой протокол; 2) Алгоритм управления; 3) Визуализатор для отладки. За пару часов реализую сетевой протокол, описываю необходимые структуры данных и с чистой совестью отправляюсь спать

    Day 1. Doctrine: Mobility

    Пока я спал, организаторы выложили симулятор, а участники в чате и рассылке развели невообразимый флейм. Быстро скачав симулятор, проверяю свой код. Сетевой протокол работает без отладки. В симуляторе оказывается встроенный визуализатор, так что от одной задачи я на некоторое время избавлен. Несколько часов уходит на то, чтобы научить марсоход ехать в заданную точку. Дело продвигается медленно, потому как тригонометрию я напрочь забыл, уж слишком давно не делал ничего подобного. Когда первая карта проходит отправляюсь кататься на велосипеде, а вечером принимаюсь за объезд препятствий. Лобовое решение, отворачивать от препятствия расположенного в опасном угле по курсу работает плохо. Объехав первый кратер, марсоход сослепу ухает во второй. Мне подкидывают идею, предсказывать положение марсохода на несколько ходов. Решаю заняться этим завтра и отправляюсь спать.

    Day 2. Doctrine: Flexibility

    Со вчерашнего дня обнаружилась ещё одна проблема: перерегуляция, приводящая к том, что марсоход пролетает мимо базы. Решил сперва побороть её и ввел эвристики, запрещающие крутые повороты вдали от базы для наведения на цель. С предсказанием все плохо, марсоход едет довольно паршиво, часто падает в кратеры и стукается о валуны. Приседания с параметрами дают немного. Это продолжается до тех пор, пока я не начинаю проверять точность предсказаний в реальном времени. Сперва обнаруживается, что я забыл учесть трение и предсказания скорости не слишком точны. Добавив систему оценки трения и ускорения, получаю точность предсказания скорости до четвертого знака. Выглядит хорошо, но точность предсказания расстояния не изменилась. Проверяю расчеты калькулятором и обнаруживаю, что java.lang.Math принимает аргументы синусов и косинусов в радианах, а я считаю углы в градусах. Правлю и точность hgtlcrfpfybq достигает требуемой. Поскольку нет необходимости в играх с параметрами, устанавливаю предсказания на 5 секунд вперед. Все три тестовые карты проходятся, так что я начинаю искать, на чем бы ещё протестировать. Поскольку возможности достать и померяться прямо в процессе организаторы не предусмотрели народ во всю обсуждает результаты и выкладывает собственные карты. Пытаюсь протестировать на чужих картах и обнаруживаю два факта: а) на рандомных картах сходных с тесовыми все работает идеально; б) стенки, «рюмки», «бутылки», лабиринты и прочие непрерывности вводят марсоход в ступор. На этой прекрасной ноте отправляюсь спать.

    Day 3. Doctrine: Initiative

    Пенедельник — день рабочий, поэтому за марсоход удается приняться только к вечеру. Проверять на каждом шаге полноценное дерево решений чтобы выбрать оптимальное мне влом. Судя по отзывам не меньше половины участников этим не заморачивались, уповая на отсутствие в реальных тестах стенок. Так что я решаю посвятить оставшееся время отладке и доводке. На марсинан также решено забить. Мой марсоход едет быстро и непредсказуемо, так, что даже я не всегда могу предполагать куда он свернет. Перехватить его марсианином, задача гораздо менее тривиальная, чем объезд препятствий, так что с марсианином он может столкнуться только по чистой случайности. Вычисление радиуса поворота в эвристиках для определения необходимого положения руля значительно помогло. Теперь марсоход едет быстро, оптимально и опасно. Решаю оставить как есть в надежде, что он не свалится в кратер больше двух раз. Пробую ещё пару идей, но они не работают. Сперва пытаюсь объединить пересекающиеся препятствия в одно большое, но точности это не увеличивает, зато существенно замедляет работу. Затем пытаюсь настроить москитный маневр (вместо движения прямо, рулить попеременно влево-вправо для затруднения перехвата), но это ухудшает точность предсказаний, которые на него не рассчитаны. Допиливать предсказания не хочется, поэтому сабмичу, как есть. Выложат результаты, будет видно насколько мои предположения оправдались. 

    Conclusions

    Участвовать в этом можно, но одному не это делать не слишком приятно. Кучу побочных задач я пропустил, так, а они могли существенно улучшить результат. Если бы у меня был собственный рендерер выводящий внутренне представление, ошибка с предсказаниями была бы отловлена сразу же и хватило бы времени на более изощренный алгоритм. Генератор карт существенно облегчил бы отладку. Даже ознакомиться с достижениями конкурентов не хватило времени. В следующий раз, я планирую собрать команду и подойти к делу серьезно.

    четверг, июля 03, 2008

    Date, Time, Interval, and Duration

    С представлением времени в большинстве систем творится полный бардак. Ладно, RDF с OWL’ем, там и без этого все плохо, но даже в стандартных библиотеках самых что ни на есть мейнстримных языков полный бардак. Ну что нужно курить чтобы в стандартный класс Date добавить преобразование к Юлианскому календарю? Ни разу не встречал человека, которому бы это потребовалось. Зато вычислять интервал между двумя событиями приходится вручную. Давайте попробуем спроектировать библиотеку для работы со временем правильно. Может хоть кому-нибудь живой пример вправит мозги.

    Итак, что такое время для программиста? Это одна координатная прямая, на которой определена выделенная точка «сейчас», делящая прямую на «прошлое» и «будущее». Примитивные племена на этом и останавливаются, оперируя тремя понятиями: «было», «есть» и «будет». Современному человеку требуется больше: время должно быть измеримо. Это позволит производить вычисления (до сеанса осталось полчаса) и учитывать порядок событий ([сначала]он мне в глаз, а [затем] я ему в ухо) от которого и до причинно-следственной связи недалеко. Для того чтобы сделать время измеримым на прямую произвольным образом добавляют фиксированную выделенную точку «0». Исторически сложилось так, что в разных культурах эту точку добавляли по-своему. Это мог быть день коронования императора, начало определенного года, произвольно выбранный момент времени и даже время «сотворения мира». В мире UNIX, например, это полночь 1 января 1970 года.

    С другой стороны люди привыкли к довольно неудобной для вычислений системе предстваления времени, связанной с циклами вращения Земли. Опять же, в разных культурах к этой проблеме подходили по разному. В результате год начинается в разное время и по разному делится на месяцы в зависимости от культуры. Более того, в разных культурах привычные способы записи времени сильно отличаются, даже если системы счисления времени совпадают. Та же американская ММ/ДД/ГГ и русская ДД.ММ.ГГ.

    Из-за привычки людей к солнечному времени, в один и тот же момент время отличается в разных часовых поясах. Как будто этого мало, многие страны используют летнее/зимнее время для экономного использования светового дня. В результате, вычисление времени в определенной локации становится довольно неудобной задачей, по крайней мере для человека.

    Поскольку продолжительность суток растет, каждые несколько лет к UTC добавляют секунду, чтобы компенсировать замедление. Это тоже усложняет точные расчеты, однако, пока не вызывает проблем, поскольку единой системы точного времени затрагивающую все — от GPS приемников до кофеварок не создано.

    Теперь, когда требования ясны, займемся проектированием. Будет удобно, если внутри системы время будет хранится единообразно, в собственной системе координат, а преобразоваться в нужную будет только на входе и выходе. По крайней мере это избави программиста от ручных расчетов и преобразований форматов.

    Первое, что нам требуется, это момент времени (Timestamp) заданный с максимальной возможной точностью в некоторой системе отсчета, хоть UNIX time. Этот класс является платформозависимым, поскольку умеет работать с системным таймером. Никаких преобразований или вычислений этот класс делать не должен. Очень часто требуется работать с моментами времени с точностью до минуты, дня или года, в этом случае максимальное разрешение тут только мешает. Для некоторой частых случаев можно определить классы Date и DateTime и точностью, соответственно, до дня и до секунды. Timestamp должен корректно поддерживать сравнение отметок времени с максимальной и ограниченной точностью.

    Второе — интервал между двумя моментами времени (Interval) Интервал можно представить двумя моментами времени: начало интервала и конец интервала. Интервал должен уметь вычислять свою продолжительность. Кроме того, нужна возможность зная начало или конец интервала и продолжительность, получить интервал.

    Третье — продолжительносить (Duration) Внутри продолжительность хранится в максимально возможной системной точностью.

    Для каждой из трех сущностей потребуется два интерфейса: *Parser и *Formatter имплементации которых служат для преобразования сущностей из строки и к строке (или прочим типам). Кроме форматирования, их зона ответственности — преобразование к другим системам представления времени, например к тому же юлианскому календарю.

    В основном, этого достаточно. Естественно, в некоторых языках можно добавить собственные удобности, например профайлинг в Ruby:

    duration = Duration.measure do
    ...
    end
    

    вторник, июня 24, 2008

    RuPyRu 2008. Конференция

    Пару слов о самой конференции. В целом RuPyRu оправдала мои ожидания: хорошая, добротная провинциальная конференция.

    Техническая организация была на высоте. Обошлось без критических задержек, технических перерывов и прочих накладок. Во всяком случае я таковых не заметил.

    Качество докладов оставляет желать лучшего. Было очень много рассказов своими словами об использовании чужих технологий, разбавленных собственным опытом. По большому счету такие доклады бесполезны, поскольку такую информацию легко почерпнуть в мануале, а все, кто «в контексте» давно прочитали обзорные статьи в блогах и форумах. Вопросы в стиле «Почему это сделано именно так?» таким докладчикам тоже задавать бессмысленно, не они технологию разрабатывали, откуда им знать.

    Познать механизм деления докладов на «общий» и «специальный» потоки мне не удалось. Интересные доклады были и там и там. Я бы предложил деление на «пользовательский» и «авторский» потоки. В первом — краткие обзорные доклады, посвященные чужим технологиями, во втором — доклады авторов о собственных разработках и доклады экспертов. Понравилось, что Ruby и Python не были разнесены на отдельные потоки, что позволило познакомиться с конкурентными технологиями.

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

    Идея круглых столов с едой, хороша, но реализация не слишком хорошо удалась. Стоило составить физические столы так, чтобы народ сидел лицом друг к другу, обозначить вопросы на обсуждение и как-то организовать дискуссию. Темы круглых столов тоже не слишком удались. Здесь бы стоило разнести Ruby и Python на разные столы, поскольку в таком формате обычно обсуждаются довольно тонкие вопросы, пользователям конкурентной технологии не интересные.

    Из интересного для меня оказались совершенно не раскрыты темы развертывания Rails и Django приложений, использования динамических языков для кодогенерации, использования Ruby и Python в качестве скриптовых языков внутри приложения и создания DSL на базе динамических языков.

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

    RuPyRu 2008. Омск

    памятник Ленину

    Дожидаясь начала конференции удалось прогуляться по Омску. Увы, было ранее субботнее утро, поэтому оценить вид города в час пик не удалось. Утром выходного дня все города выглядят одинаково хорошо. На вокзале нас встретил традиционный бронзовый Ленин, твердой рукой указующий почему-то на здание вокзала. Видимо, это должно означать: "Убирайтесь, откуда пришли".

    памятник Оленям

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

    памятник Сантехнику

    В центре города нам встретился памятник Говночерпию Сантехнику, из вечно актуальной серии памятников рабочему классу (памятники Электрику и Дворнику в Томске) Расположенный прямо на тротуаре, он символизирует близость рыцаря труб и задвижек к народу, а умиротворенное выражение лица убеждает в чистоте его намерений.

    граффити

    Из современных форм искусства нам встретилось вот такое трафаретное граффити в стиле Fallout. Сложно сказать, что оно должно означать, но прикольно. Я всегда считал, что за трафаретным граффити будущее уличной живописи.

    мотоцикл

    Омские байкеры настолько суровы, что способны ездить даже на таком странном звере. К сожалению застать его едущим нам не удалось.

    Старый центр Омска похож на все прочие старые города Сибири. Те же трех-четырехэтажные здания не разваливающиеся на глазах только за счет многовекового слоя лепнины, штукатурки и краски. Те же узкие улочки. Что в Омске догадались сделать хорошо, так это отгородили тротуар с деревьями от проезжей части железной решеткой. И припарковаться на тротуаре не дает и народ где попало через улицу не бегает.

    Первые этажи зданий, заняты магазинами, которые, иногда, вполне гармонично вписываются в окружающую лепнину и белую штукатурку.