*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

вторник, 22 декабря 2009 г.

Wiki2blog: онлайновый wiki-редактор для Blogspot

Как я уже писал, что из-за неудобства онлайнового редактора Blogspot'а я перешел на использование Wiki на Google Code для написания и хранения постов. То есть я пишу пост, используя язык разметки Wiki, отлаживанию разметку, заливаю картинки и т.д., а затем просто конвертирую wiki-файл с помощью скрипта на php в html. Фишка тут в том, что скрипт должен учитывать некоторые "особенности" Blogspot'а — необходимость убирать разделители строки после блочных тэгов типа blockquote, pre, списков и т.д., чтобы не появлялись ненужные отступы. В результате выходной html становится практически нечитаемым.

Удобство же wiki-разметки в том, что исходник поста выглядит красиво и пригоден для последующего редактирования.

Но в таком подходе цикл работы над постом несколько длинноват: редактор, push через Mercurial на Google Code, просмотр как выглядит на Wiki, затем прогон через скрипт, и если все хорошо, то "cut-paste" в онлайн-редактор Blogspot и финальный отсмотр там. А если обнаруживаются шероховатости, что цикл повторяется сначала. И еще меня раздражал сам скрипт — корявый и непонятный.

Хотелось чего-нибудь легкого и более менее WYSIWYG.

Порыскав в сети на наткнулся на wiki2html. И это оказалось то, что нужно. Я немного подкорячил это под свой формат wiki-разметки и, в итоге получилось это: онлайновый редактор с препросмотром для подготовки постов в Blogspot.

Теперь цикл такой: набиваешь пост в этом редакторе, а он автоматически отображает preview по мере набора вместе с htmlем. Затем cut-paste html-я в Blogspot, и с большой вероятностью форматирование должно выглядеть как задумано.

Ни разу не претендую на возможные странности моего диалекта Wiki. Желающие могут изменить под себя, ибо форматировщик крайне простой.

Редактор состоит из одного файла wiki2html.html. Его можно просто сохранить локально и поиграться.

Под занавес привожу поддерживаемые команды wiki-разметки (таблиц пока нет).

Ссылки и картинки

[http://example.com/test] — ссылка: http://example.com/test

[http://example.com/test ссылка с текстом]ссылка с текстом

[http://github.com http://github.com/images/icons/public.png] — картинка со ссылкой

[http://github.com/images/icons/public.png] — картинка

[http://github.com/images/icons/public.png картинка по ссылке]картинка по ссылке

Текст

'''bold'''bold

''italic''italic

@@[http://google.com]@@ — экранирование любой wiki-разметки

моно`ширин`ный текст — моноширинный текст

Заголовки

= Заголовок1 =

== Заголовок2 ==

=== Заголовок3 ===

==== Заголовок4 ====

===== Заголовок5 =====

Цитирование

Цитата начинается с двух пробелов.

Разделитель

---


Ненумерованный список

Код:

* это
** ненумерованный
* список

Как выглядит:

  • это
    • ненумерованный
  • список

Нумерованный список

Код:

# это
## нумерованный
# список

Как выглядит:

  1. это
    1. нумерованный
  2. список

Исходники

Код:

{{{

#include <io.h>

int main() {

  int a = 1;

  return a;

}

}}}

Как выглядит:

#include <io.h>
int main() {
  int a = 1;
  return a;
}

Видео

[http://www.youtube.com/watch?v=FrufJFBSoQY]

Собственно, этот пост я подготовил по описанной технологии. Можно посмотреть как его исходник выглядит в wiki-формате.

Посты по теме:

среда, 16 декабря 2009 г.

Тренинг Steve Dewhurst'а "C++ Common Knowledge"

Побывал на тренинге Steve Dewhurst'а "C++ Common Knowledge". У меня уже давно есть его книга:



и в целом конкретно этот тренинг посвящен темам из нее.

Очень прикольный дядька. Нескучно, разбавлено подколами аудитории и шутками типа что ребята из Boost'a не иначе как курят шаблоны и т.д. Мне очень понравилось.

Стив сказал, что С++ - это практически все, что делал и делает в жизни. Писал компиляторы, утилиты, разбирался в стандартах, а сейчас вот дает тренинги.

Не могу сказать, что я узнал что-то особенно новое — это было бы странно, так как его книжку (см. выше) читал от корки до корки и периодически к ней возвращаюсь. Хотя, пожалуй, одна мысль меня зацепила: правильное написание конструктора копирования или оператора присваивания для класса, в иерархии которого есть виртуальный базовый класс с данными является весьма запутанной задачей. Тут однозначно нарушается принцип логической независимости уровней иерархии наследования, так как надо точно знать, от каких классов ты унаследован и как их правильно инициализировать при множественном наследовании.

Рекомендация такая: сначала задаешь себе вопрос: а нужно ли мне тут множественное наследование? а нужно ли мне виртуальное множественное наследование?? а нужно ли мне виртуальное множественное наследование с данными в базовом виртуальном классе??? И даже после долгого раздумья лучше сказать "нет". Лично я не имею ничего против множественного наследования. Но мне не очень нравится как это сделано в С++. И мне не очень нравится как это сделано в Java. Мне очень нравится, как сделано в Go. А именно: в Go полностью разнесены понятия структур данных и интерфейсов. Структуры данных не могут быть унаследованы. Они могут только реализовывать интерфейсы. А наследовать можно только интерфейсы. Поэтому в принципе нельзя при наследовании подцепить чужие данные, а только методы. А нет данных, не и проблемы их инициализации.

Итак, могу просто собрать общие рекомендации от Стива:
  • стараться использовать виртуальные функции и полиморфизм в целом вместо "if" и "case"
  • стараться использовать алгоритмы STL/Boost и функторы вместо циклов
  • использовать только "умные" указатели при работе с динамической памятью
  • не использовать классические массивы, а контейнеры STL (так как, например, std::vector гарантирует линейное размещение элементов, то можно смешивать "старый" код, работающий с указателями, с использованием контейнеров)
  • тщательно продумывать операции копирования сложных классов (лучше всего реализовать конструктор копирования и метод swap, а оператор присваивания реализовать через них)
  • всегда объявлять в коде класса конструктор копирования и оператор присваивания, и даже если они не используются, то просто закомментировать их объявление с пояснением, почему они не нужны
  • никогда не использовать приведения типов в стиле С, только С++ (static_cast, const_cast и т.д.), так как они длинные, их нудно набивать и они уродуют вид программы - в общем, все, что нужно для минимизации их наличия
  • помнить, что наследование - это re-use интерфейсов, а не кода как такового
  • не слишком верить компилятору ;-) (Стив их писал и знает, что они могут и подставить)

вторник, 15 декабря 2009 г.

Peter Seibel, "Coders at Work"

Неспешно дочитал "Coders at Work".

Peter Seibel, Coders at Work



Книга состоит из интервью с десятком известных программистов. Тут есть создатели UNIX, Netscape, JavaScript, Smalltalk, Haskell, Erland, Ghostscript, ЖЖ и также есть просто Дональд Кнут. В общем, не самые последние люди.

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

Многие ссылаются на разные книги - я значительно пополнил свой список на "прочитать".

Забавно, что почти никто не отозвался однозначно положительно о С++. Либо было однозначное нет типа "очень перегружено, сложно и т.д.", либо "ну раз уж более ничего пока лучше нет для создания native кода промышленной сложности, то пусть будет".
Лирическое отступление. Я тут покуриваю Go, и чем дальше, тем больше меня прет. Могу сказать, что я почти для всех своих плюсовых привычек нашел альтернативу в Go. Ну а его врожденная мультипотоковость и ультра быстрая компиляция довершают все.
Также интересно мнение на тему обязательно ли для всем уважающим себя программистам прочитать "Искусство программирования" Кнута или хотя бы иметь в своей библиотеке. Многие признали, что не читали от корки до корки, но как справочник используют.

Как всегда убедился, что я даже и не слышал о некоторых крайне известных вещах. Например, Literate programming от Дональда Кнута или фильтр Блума.

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

суббота, 28 ноября 2009 г.

Электронная записная книга TiddlyWiki

Лично я очень люблю задавать вопросы, но я не люблю задавать один и тот же вопрос дважды. А иногда случается, что неожиданно сваливается большой поток новой информации (например, начинается проект), и голове все не удержать. Естественно, обычный бумажный блокнот позволяет производить начальную фиксацию информации, но когда объем записей, к которым необходимо часто возвращаться, переваливает за 8-10 исписанных страниц, то начинается каша.

Какой логичный выход? Использовать для записей компьютер.

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

Так как информации много, то ее хочется как-то логично организовывать - главы, разделы, подразделы и т.д. Но со временем я заметил, что основной способ навигации впоследствии - это ни разу не оглавление, а просто поиск по словам. Например, мне нужно вспомнить, что мне там объясняли на тему как вбивать расходы или где смотреть отчеты по ночным сборкам. Я просто нажимаю CTRL-F и ввожу слово "расходы" или "ночные". Как говориться, "Don't sort. Search!"

Наличие всего в одном файле упрощает техническую сторону вопроса - хочешь в Ворде работай, хочешь в Vi. И бэкапить удобно или в контроль версий засунуть.

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

Что это такое? Поясню кратко, для тех кто не слышал. TiddlyWiki - это Wiki движок, который представляет собой один единственный html-файл. Как это работает: вы просто открываете этот файл в браузере, и движок на JavaScript позволяет создавать записи, редактировать их, ссылать записи друг на друга, удалять и, что важно, искать Но еще этот движок умеет делать самое интересное - сохранять все, что вы ввели в этот же файл. То есть когда вы откроете этот html-файл завтра, в нем будет все, что вы ввели раньше.

Я обычно работаю так: есть закладка в браузере, которая открывает локальный файл с TiddlyWiki. А так как браузера практически всегда открыт - запуск занимает мгновения. Так как этот Wiki, то доступны все удобства Wiki-разметки и взаимных ссылок (кстати, сам сайт www.tiddlywiki.com построен на TiddlyWiki). Создание новой заметки (tiddler'а) происходит мгновенно. Для навигации можно сделать удобное меню слева, но просто пользуюсь поиском. Когда данных много - это единственный способ что-то найти.

Что лично мне нравится TiddlyWiki-подходе в целом:

  • Нужен только браузер с установленым плагином Java и малюсенький файл "Saver.jar", который поставляется вместе с TiddlyWiki, и который надо просто положить в тот же каталог. Поэтому с wiki-файлом можно работать хоть в Windows, хоть в UNIX, хоть на Маке. Я лично проверял в Chrome, Firefox и IE).
  • Файл можно выложить на web-сервер, например в интранете, и таким образом публиковать записи. Конечно, для просматривающих его через web файл будет только для чтения.
  • Так как файл по сути своей текстовый (обычный html), то в случае чего, можно выдрать из него данные простым текстовым редактором, хотя у меня такой необходимости еще не было.
  • Движок TiddlyWiki при каждом самосохранении умеет делать копию текущего состояния. Я эти файлы обычно архивирую раз в неделю, и solid-архивация в один архив позволяет хранить всю историю с минимальным увеличением архива при каждом новом сохранении. И всегда можно вернуться к определенной старой версии.
  • И главное: удобный поиск!
Небольшой трюк для пользователей Хрома. Для нормальной работы TiddlyWiki нужно, чтобы были включены cookie. По умолчанию в Хроме при работе с локальными файлами cookie выключены, поэтому Хром следует запускать с ключом --enable-file-cookies.

Не спрашивайте второй раз одно и то же. Лучше запишите ответ в первый раз, а спросите что-нибудь новое.

четверг, 19 ноября 2009 г.

Google Chromium OS теперь open source

http://blog.chromium.org/2009/11/hello-open-source-developers-would-you.html

Удивительное дело, что еще остались люди, которые сомневаются, что open source - это единственная модель, с которой можно выживать при современном объеме и сложности софта.

Но вернемся к теме.

Chromium OS - загрузка за 4 секунды до получения браузера. Ну а параллельно можно грузить винды в виртуальную машину.



Вобщем, понеслась.

суббота, 14 ноября 2009 г.

Git для борьбы с CVS

По долгу работы мне приходится участвовать процедуре следующего толка: есть ветка исходников, стабильность которой имеет чудовищную важность. Там не то, что сломать билд нельзя, там каждый коммит проходит несколько стадий автоматических проверок (компиляция на разных платформах разными компиляторами, прогон разнообразных анализаторов и т.д.) плюс надо получить подтверждение у как минимум четырех/пяти человек, которые должны проверить твой коммит. Процедура мучительная и долгая даже с технической точки зрения. Более того, процедура выстраивалась годами и основана на очень древней системе контроля версий (не будем называть ее всуе), поэтому возможности слияния и разрешения конфликтов в основном ручные. Она умеет нормально делать только check-out и check-in.

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

Так как я только недавно в этой теме, то после второго же коммита решил упростить себе жизнь в плане слияния при конфликте.

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

В плане распределенных SCM я сейчас в основном работаю с mercurial, так как Google Code его поддерживает. Но все таки git - это невероятно мощный инструмент (правда когда в рабочем процессе не фигурирует Windows, ибо виндовая версия git'а портирована крайне криво).

По началу многое запутывает. Меня по первости крайне сбивала с толку идея staging area (или его еще называют - index). Эдакое промежуточное звено между локальными изменениями и самим репозиторием. Получается, что git diff может показывать три разные вещи: разницу между локальными изменениями и индексом (а не репозиторием - это будет по умолчанию, что обычно и вводит новичков в ступор), разницу между индексом и репозиторием и наконец разницу между локальными изменениями и репозиторием. Индекс (или staging area) позволяет при коммите выбирать, что именно из локальных файлов надо закоммитить, а не все подряд. В коммите участвуют только файлы, находящиеся в индексе. Причем самое интересное, что можно включать в индекс куски измененных файлов (например, я добавил в файл два новых класса, но закоммитить могу только выборочно один из них).

Вам уже нравится?

Или например, откат всех локальных изменений (очень частая операция) может быть выполнена как минимум двумя способами (через git checkout и через git reset), или откат уже сделанного коммита можно также провернуть минимум двумя путями (git reset или git revert) в зависимости от того, хотите ли вы видеть потом этот откат в истории.

Обилие функций и их некоторая непохожесть на общепринятые стандарты команд SCM немного обескураживают сначала. Но немного въехав в тему начинаешь ощущать всю мощь. Например, наличие staging area и git stash (когда можно временно заморозить текущее состояние, сделать какую-то быструю минутную работу и вернуться к основной теме) - весьма уникальные возможности git'a.

В плане GUI - gitk дает все необходимое.

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

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

Надеюсь, мне удалось привлечь в ряды пользователей git еще пару тройку энтузиастов.

Однажды привыкнув с постоянному наличию под рукой контроля версий, хочется, чтобы она была везде. Даже при починке автомобиля.



Посты по теме:

четверг, 12 ноября 2009 г.

Приглашения в Google Wave

Есть десяток приглашений в Google Wave.

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

Как показывает практика, реально после высылки приглашение приходит не сразу (может занять пару дней), так что надо потерпеть.

И еще -- делитесь сами приглашениями, если они у вас есть или будут. Wave хорош, когда там есть люди.

среда, 11 ноября 2009 г.

Code review

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

Из лично опыта могу сказать, что весьма часто вторая пара незамутненных многочасовым смотрением на данный исходник глаз моментально замечает лажу, и в очередной раз удается избежать ночной fuck up, когда начинают валить письма от системы ночного тестирования, что туча QА тестов сломана из-за глупого недосмотра.

Perforce - отличная система для работы с реально большимы объемами репозиториев и кодовой базы в целом, но в ней нет встроенного механизма для code review. Google решили эту проблему сами.

В данном видео небезызвестный Гвидо ван Россум рассказывает о системе Mondrian, построенной на основе Perforce, которая применяется в Google для процесса code review.

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

воскресенье, 8 ноября 2009 г.

Вести с полей эмулятора Радио-86РК на JavaScript

Когда есть немного времени, неспешно, с удовольствием, смакуя и присвистывая, развиваю эмулятор на JavaScript'е винтажного компьютера Радио-86РК.

Все как в старые добрые времена, только прямо в браузере (нажмите на картину ниже).



Текущая версия 0.6. Уже помимо самой эмуляции и набора игр есть встроенный ассемблер, на котором можно прямо в окне эмулятора писать и компилировать код для Intel 8080, и почти интерактивный дизассемблер, которым можно просматривать не только код, но и данные.

Несколько картинок (на них можно кликнуть).

Собственно, эмулятор (игра Volcano):



Ассемблер:



Дизассемблер:



Также постепенно дополнется список игр.

В плане браузеров я делаю в основном только под Хром, но говорят, что в Firefox и Safari тоже работает с разной степенью мини-глюков.

Удовольствие от этого проекта очень сложно объяснить. Тут что-то глубинное.

четверг, 29 октября 2009 г.

codepad.org

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

Искусственная типизация однородных параметров в C++

Допустим есть вот такой класс:
class Date {
public:
Date(int year, int month, int day) {
...
}
};
К сожалению, не весь мир пользуется логичной нотацией Год/Месяц/День или День/Месяц/Год. Иногда люди пишут Месяц/День/Год. Хотя и первые два легко перепутать. Вот к чему я веду: где-то в далеком от описания класса коде кто-то пишет:
Date d(2009, 4, 5);
Что он этим хотел сказать? 4-е Мая или 5-е Апреля? Сложно быть уверенным, что пользователь такого класса когда-нибудь не перепутает порядок аргументов.

Можно улучшить дизайн? Да.

Например, так:
class Year {
public:
explicit Year(int year) : year_(year) {}
operator int() const { return year_; }
private:
int year_;
};
И аналогично:
class Month { ... };
class Day { ... };
Интерфейс самого класса Date может быть таким:
class Date {
public:
Date(Year year, Month month, Day day);
Date(Month month, Day day, Year year);
Date(Day day, Month month, Year year);
}
И использовать класс надо так:
Date d(Year(2010), Month(4), Day(5));
или
Date d(Month(4), Day(5), Year(2010));
Результат будет всегда предсказуем и виден в вызывающем коде. Тут все inline'овое, так что эти три "лишние" класса никакого замедления не дадут.

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

Возражения есть?

четверг, 22 октября 2009 г.

Коварный printf()

Вчера подорвался на ерунде как ребенок.

Сижу, отлаживаю новый онлайновый ассемблер в своем эмуляторе Радио-86РК. Под отладкой понимается ёрзанье с применением html'я.

Для сборки финального html-файла из кучи мелких у меня написана примитивная программа. Вот ее фрагмент:
...
while (!feof(f)) {
char line[1024];
*line = 0;
fgets(line, sizeof(line), f);
printf(line);
}
...
Подразумевается, что данный код прострочно копирует данные из файла f на стандарный вывод.

Даже если отставить в сторону использование буфера с константной длиной и прочих "штучек" языка С, этот код имеет одну проблему, которая стоила мне сомнений в наличии сознания. До каких-то пор все работало отлично, но как только я начал использовать процентные значения для широт и высот в html, начались странности. Получалось, что вместо, например:
<table width="100%">
на выходе было:
<table width="100">
Вы, наверное, уже догадались, в чем тут дело. Но, признаюсь, я искал проблему минут тридцать.

Вместо:
printf(line);
надо писать:
printf("%s", line);
А иначе все процентные символы будут расцены как указатели форматов, ибо первый параметр printf() - это не просто строка, а формат, и в случае их неэкранирования будут уделены, что и происходило в моем случае.

Вывод (который следует после начального "сам дурак"): Лучше писать на С++ и использовать потоки для форматного вывода.

Лирическое отступление. Кстати, онлайновый ассемблер очень огранично вписался в эмулятор. Спасибо Вячеславу Славинскому за оригинальный код этого ассемблера. Особенно меня радует возможность автоматической фоновой компиляции. Теперь можно, прямо не отходя от эмулятора, переключиться в ассемблер, написать что-нибудь на диалекте Intel 8080 (КР580), скомпилировать и загнать прямо в эмулятор.

четверг, 15 октября 2009 г.

Введение в Google Mock (видео)

Данное видео будет хорошим дополнением к предыдущему посту про Google Mock для желающих освоить эту библиотеку.

вторник, 13 октября 2009 г.

Google C++ Mocking Framework для начинающих

В рамках проекта популяризации культуры разработки софта с активным использованием тестирования выложил перевод Google C++ Mocking for Dummies на русский язык - Google C++ Mocking Framework для начинающих.

Использование Mock-объектов является очень интересной темой. И владение ей позволяет перевести unit-тестирование на принципиально иной уровень.

Как рассказано в статье, языки программирования типа Python или Java благодаря встроенному механизму Reflection позволяют строить Mock-объекты почти автоматически. С++ не дает такой роскоши, но гугловцы проделали отличную работу, создав Google Mock. Практически все, что можно как-то упростить или автоматизировать в плане mock-дел в С++, сделано и сделано добротно.

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

Посты по теме:

пятница, 9 октября 2009 г.

Клонирование проектов в Google Code

Около суток Mercurial на Google Code был в read-only режиме. Проводили какую-то профилактику.

Захожу сегодня и что вижу:



Появился новый пункт в закладке Source — Clone (обведено красным). Это то, чего так долго ждали большевики — клонирование проектов.

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

Клон может быть в свою очередь тоже склонирован.

Больше открытых проектов, хороших и разных!

Посты по теме:

среда, 7 октября 2009 г.

Презентация от авторов Google C++ Testing Framework

В догонку к посту про новую версию Google Test, желающие могут заценить небольшую презентацию от ее авторов:



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

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

вторник, 6 октября 2009 г.

Закомментированные куски кода и TODO

Когда я вижу в production коде, в котором мне надо по какой-то причине разобраться, что-то типа:
...
int x = (i << 8) | (i >> 2) | ((i & 0x06) ^ 0xAA);

// if (x >= 0x12345)
// x = x >> 3;

calc_something_complicated(x);
...
мне хочется рвать и метать. Расчехлить blame, найти автора и заглянуть ему в глаза.

Иначе, что мне остается: только думать, что автор этих строк, видимо, бился с формулой, пытался подогнать результат под что-то (возможно, какой-то тест). Достиг ли он результата? Или может оно так и продолжает глючить... Кто знает. Единственное, о чем этот код однозначно говорит, что автор не был уверен в том, что пишет. Потому, что если он был уверен, то удалил бы этот фрагмент или раскомментировал бы его навсегда.

На втором месте у меня стоит отладочная печать, навеки оставленная в коде:
...
int x = (i << 8) | (i >> 2) | ((i & 0x06) ^ 0xAA);

// std::cerr << "На этот раз x = " << x << std::endl;

calc_something_complicated(x);
...
Снова получается, что автор сомневался и так и не отладил все до конца.

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

Ну на третьем месте нашего хит-парада - блоки TODO.
...
int x = (i << 8) | (i >> 2) | ((i & 0x06) ^ 0xAA);

// TODO: Заменить тип int на int64, так как на носу эра 64-разрядных машин.
// Программист Вася, 06.10.2009. Если что, вы знаете где меня искать.

calc_something_complicated(x);
...
Тут еще куда ни шло. Куски TODO можно найти автоматическим поиском при подготовке релиза и перед ответственным слиянием. Но каждый TODO должен быть подписан и датирован, а лучше еще и детально объяснен. Ни что так не помогает оценить "нужность" куска кода, как дата его последней модификации.

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

суббота, 3 октября 2009 г.

Google C++ Testing Framework 1.4.0

Вчера гугловцы анонсировали новую версию моей любимой библиотеки тестирования Google C++ Testing Framework - 1.4.0.

Что новенького?

Одна из главных новых возможностей - это "The event listener API". А попросту говоря, возможность полностью контролировать процесс печати результатов тестирования. Это позволяет формировать отчеты по тестированию в нужном формате без изменения кода библиотеки.

Например, стандартный вывод при выполнении элементарного теста (файл runner.cpp):
#include "gtest/gtest.h"

TEST(One, Simple) {
EXPECT_EQ(1, 2);
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
будет таким:
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from One
[ RUN ] One.Simple
runner.cpp(4): error: Value of: 2
Expected: 1
[ FAILED ] One.Simple (15 ms)
[----------] 1 test from One (15 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (31 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] One.Simple

1 FAILED TEST
Для задания иного формата вывода нужно реализовать свой event listener (назовем его сервис печати). Например (файл runner.cpp):
#include "gtest/gtest.h"

using namespace ::testing;

// Данный класс заменит стандартный сервис печати.
class LaconicPrinter : public ::testing::EmptyTestEventListener {
// Вызывается до начала работы теста.
virtual void OnTestStart(const TestInfo& test_info) {
printf("*** Test %s.%s starting.\n",
test_info.test_case_name(), test_info.name());
}

// Вызывается при срабатывании какого-либо утверждения или явного вызова
// функции SUCCESS().
virtual void OnTestPartResult(const TestPartResult& test_part_result) {
printf("%s in %s:%d\n%s\n",
test_part_result.failed() ? "*** Failure" : "Success",
test_part_result.file_name(),
test_part_result.line_number(),
test_part_result.summary());
}

// Вызывается после выполнения теста.
virtual void OnTestEnd(const TestInfo& test_info) {
printf("*** Test %s.%s ending.\n",
test_info.test_case_name(), test_info.name());
}
};

TEST(One, Simple) {
EXPECT_EQ(1, 2);
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);

// Получаем ссылку на список сервисов печати.
::testing::TestEventListeners& listeners =
::testing::UnitTest::GetInstance()->listeners();

// Удаляем стандартный сервис печати.
delete listeners.Release(listeners.default_result_printer());
// Добавляем наш сервис в список. Google Test самостоятельно удалит этот объект.
listeners.Append(new LaconicPrinter);

return RUN_ALL_TESTS();
}
Теперь отчет по работе тестов будет выглядеть так:
*** Test One.Simple starting.
*** Failure in runner.cpp:31
Value of: 2
Expected: 1
*** Test One.Simple ending.
Необходимо отметить, что одновременно может быть зарегистрировано несколько сервисов печати. Но в этом случае их выводы могут смешиваться и превращаться в кашу. Для избежания этого мы принудительно удаляем стандартный сервис печати, чтобы его вывод не мешал нашему.

Полностью интерфейс сервиса печати выглядит так:
class EmptyTestEventListener : public TestEventListener {
public:
// Вызывается при начале прогона всех тестов.
virtual void OnTestProgramStart(const UnitTest& unit_test);
// Вызывается при начале очередной итерации тестирования. Google Test
// позволяет управлять количеством итерации при прогоне тестов.
virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration);
// Вызывается до функции Environment::SetUp(), устанавливающей необходимое
// окружение для работы всех тестов.
virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test);
// Вызывается после функции Environment::SetUp(), устанавливающей необходимое
// окружение для работы всех тестов.
virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test);
// Вызывается при начале прогона группы тестов (у которых первый параметр
// макроса TEST/TEST_F одинаковый).
virtual void OnTestCaseStart(const TestCase& test_case);
// Вызывается при начале работы теста.
virtual void OnTestStart(const TestInfo& test_info);
// Вызывается при срабатывании утверждения в тесте или явного вызова
// функции SUCCESS().
virtual void OnTestPartResult(const TestPartResult& test_part_result);
// Вызывается после завершения работы теста.
virtual void OnTestEnd(const TestInfo& test_info);
// Вызывается после прогона группы тестов.
virtual void OnTestCaseEnd(const TestCase& test_case);
// Вызывается до функции Environment::TearDown(), производящей освобождение
// ресурсов, занятых Environment::StartUp().
virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test);
// Вызывается после функции Environment::TearDown().
virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test);
// Вызывается после очередной итерации тестирования.
virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);
// Вызывается после прогона всех тестов.
virtual void OnTestProgramEnd(const UnitTest& unit_test);
};
Также из значимого можно отметить новый ключ командной строки --gtest_shuffle, позволяющий запускать тесты в случайном порядке. Ключом --gtest_random_seed=SEED можно "управлять" случайностью этого порядка. Если SEED равен 0, то случайность будет действительно случайной, так как случайная последовательность будет определяться текущим временем.

Что приятно, теперь формат XML файлов, генерируемых при использовании ключа --gtest_output, полностью совместим с форматом JUnit. Это значит, что, например, система автоматической сборки, тестирования и интеграции Hudson теперь понимает отчеты Google Test без дополнительный конвертации.

Также теперь при работе в Visual Studio сообщения о сбойных тестах выводятся прямо в окно "Output", что позволяет, кликая на них, сразу находить строки, где сбоят тесты. Здорово, что данная фича основана на моем коде.

Еще, теперь время работы тестов печатается всегда, по умолчанию, то есть опция --gtest_print_time будто бы всегда включена.

Есть еще несколько незначительных улучшений:
  • поддержка CodeGear Studio
  • собственная реализация tuple для независимости от boost при использовании Combine()
  • множество улучшений для совместимости с Solaris, Windows Mobile и некоторыми другими платформами.
Радость, одно слово.

Я перестал что-то писать что-либо на C++ без тестов, а Google Test делает этот процесс простым и легким.

Я уже обновился до версии 1.4.0, а вы?

Ссылки по теме:

среда, 30 сентября 2009 г.

const T* или T const*

Не секрет, что выражение "const T*" при объявления указателя полностью эквивалентно записи "T const*", ибо тут важно только, что const стоит до знака *, а порядок его употребления с именем типа Т роли не играет:
const T* p;
и
T const* p;
объявляют указатель на константный объект, а не константный указатель, то есть значение самого указателя можно менять:
T const* p;
...
p = NULL;
Но менять сам объект нельзя:
T const* p;
...
p->some_member = 0; // ОШИБКА: error C2166: l-value specifies const object
Но все это была вводная, и сейчас не об этом.

Меня больше интересуют читабельность исходников. Я могу ошибаться, но как мне кажется, что с общечеловеческой точки зрения употребление const в начале выражения (например, "const T* p;") подразумевает константность всего выражения, и, собственно, не важно, что там на самом деле указатель, и по правилам С++ данный const значит только константность объекта, а не указателя.

Поэтому запись "T const* p;" может читаться несколько иначе, а именно: "тип T, который константный, и на него объявляется указатель. Читабельность немного лучше.

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

пятница, 25 сентября 2009 г.

Эмулятор Радио-86РК на JavaScript

Первым компьютером, на котором я начал программировать, был Радио-86РК. Его собрал мой брат и... понеслась, и несется до сих пор.

Поэтому я до сих пор питаю слабость к данному раритету. К результат этой слабости я постоянно писал эмуляторы этого компьютера.

Первый был под ДОС. Оригинальный сайт сего творения я храню до сих пор в неизменном виде. Этот эмулятор был весьма неплох: там был и встроенный отладчик, и метода взлома игр и т.д. Но ДОС ушел, поэтому данный эмулятор работает теперь разве что в DosBox'e. Исходники творения можно скачать.

Вторая реинкарнация любимого РК была уже под Windows и работала через SDL. Тут уже не было встроенного отладчика, да и проект так и остался сырым (хотя и работающим), поэтому на публике лежит только бинарь с комплектом игр.

И вот, пару дней назад я наткнулся вот на это - эмулятор Спектрума на чистом JavaScript'е! - ни апплетов, ни activex'ов.

Так я от этой темы завелся, что за день-два оживил старого монстра РК на платформе JavaScript. Оказывается правильные браузеры дают весьма недурственную скорость скриптования. 2d графика реализуется через тэг canvas из HTML5.

Получился проект - Эмулятор Радио-86РК на JavaScript.

Эмулятор и набор игр живут в одном единственном файле radio86.html. Можно нажать на ссылку, и эмулятор запустится в браузере. Внизу есть селектор для выбора игры, и возможность поиграться со размерами экрана и скоростью.

Эмуляция происходит на уровне команд процессора Intel 8080.

Скриншот классической игры Volcano, сделанный в этом эмуляторе.



На текущий момент я проверял только в Google Chrome 4.*. Думаю, я не буду заморачиваться особо на тему совместимости с другими браузерами, хотя посмотрим, как пойдет. В IE точно работать не будет, а вот про FF и Оперу ничего сказать не могу, пока.

Волшебный мир Радио-86РК снова вернулся!

P.S. К слову сказать, лучший эмулятор РК (и множества совместимых моделей), что я видел - это эмулятор Виктора Пыхонина. Может и он мигрирует на модную платформу со временем.

Update: Обновил эмулятор до версии 0.2. Изменения незначительные: добавил некоторые системные программы (языки программирования, отладчики, редакторы и т.д.) и немного улучшил селектор выбора игры, который теперь срабатывает при нажатии кнопки "Run".

Update: Обновил эмулятор до версии 0.3. Отрисовка экране теперь работает значительно быстрее и не так грузит процессор.

Update: В версии 0.4 теперь встроенный ассемблер. Можно писать и ассемблировать прямо в окне эмулятора. После есть возможно загрузить результат в сам эмулятор и запустить командой "G" в Мониторе.

Скриншот этого ассемблера:



Update: В версии 0.6 теперь есть встроенный дизассемблер. Им можно просматривать не только программый код, но и данные.

Скриншот дизассемблера:

понедельник, 21 сентября 2009 г.

Двойная точка с запятой в разделе объявления переменных

Казалось бы, невинный пример (vs_double_semicolumn.c):
void main() {
int a;;
int b;
}
Компилируем (в режиме языка С, то есть без /TP):
cl vs_double_semicolumn.c
Результат:
vs_double_semicolumn.c
vs_double_semicolumn.c(3) : error C2143: syntax error : missing ';' before 'type'
Результат в Codegear/Borland примерно такой же (хотя описание ошибки более ясное):
CodeGear C++ 5.93 for Win32 Copyright (c) 1993, 2007 CodeGear
vs_double_semicolumn.c:
Error E2140 vs_double_semicolumn.c 3: Declaration is not allowed here in function main
*** 1 errors in Compile ***
Проблемка заключается в случайной опечатке в виде двойного символа ;. Кстати, пример абсолютно реальный, из жизни. Случайная опечатка - и сразу много вопросов.

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

Проверил на gcc, на родных компиляторах AIX, Solaris и HP-UX. Эти все съели пример без проблем.

Посты по теме:

суббота, 12 сентября 2009 г.

Ошибка в компиляторе Godegear (Borland) C++ при приведении типов указателей

Тривиальный пример (bcc32_5.93_cast_bug.cpp):
class A {};
class C {};
A* a;
A* b = static_cast<C*>(a);
Компилируем в bcc32.exe (версия 5.93) из Codegear RAD Studion 2007:
bcc32 -c bcc32_5.93_cast_bug.cpp
Падает c internal compiler error:
CodeGear C++ 5.93 for Win32 Copyright (c) 1993, 2007 CodeGear
bcc32_5.93_cast_bug.cpp:
Fatal F1004 bcc32_5.93_cast_bug.cpp 4: Internal compiler error at 0x44b34e with base 0x400000
Fatal F1004 bcc32_5.93_cast_bug.cpp 4: Internal compiler error
Люблю собирать падения компиляторов на стадии компиляции. А у вас есть что-нибудь подобное в загашнике?

четверг, 10 сентября 2009 г.

Виртуальные функции в конструкторе и деструкторе

Рассмотрим простой пример (virtual_funct_const.cpp):

#include <iostream>

class A {
public:
A() {
construct();
}

~A() {
destruct();
}

virtual void construct() {
std::cout << "A::construct()" << std::endl;
}

virtual void destruct() {
std::cout << "A::destruct()" << std::endl;
}
};

class B: public A {
public:
B() {
construct();
}

~B() {
destruct();
}

virtual void construct() {
std::cout << "B::construct()" << std::endl;
}

virtual void destruct() {
std::cout << "B::destruct()" << std::endl;
}
};

int main() {
B b;
return 0;
}
Что напечатает эта программа?

А вот что:
A::construct()
B::construct()
B::destruct()
A::destruct()
Получается, что конструкторы и деструкторы классов A и B при вызове объявленных виртуальными функций construct() и destruct() реально вызывали функции только своего класса.

В этом нет никакого секрета, а просто есть правило: виртуальная функция не является виртуальной, если вызывается из конструктора или деструктора.

Правило надо заучивать, что неудобно. Проще понять принцип. А принцип тут в краеугольном камне реализации наследования в C++: при создании объекта конструкторы в иерархии вызываются от базового класса к самому последнему унаследованному. Для деструкторов все наоборот.

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

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

Итак, виртуальная функция не является виртуальной, если вызывается из конструктора или деструктора.

пятница, 4 сентября 2009 г.

Мультипотоковый отладчик TCP/IP соединений

Трассировка данных, передаваемых по TCP/IP, является весьма частой задачей при разработке сетевых приложений, особенно низкого уровня.

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

Несколько лет назад первые версии моей утилиты были на PHP, но текущая версия переписана на Питоне.

Итак, pyspy.

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

Ни разу не претендую на оптимальность или крутизну использования Питона, поэтому принимаю любую критику.

Основные особенности и возможности:
  • программа "слушает" на указанном порту и перенаправляет траффик на указанные адрес и порт
  • умеет сохранять лог в файл
  • программа является многопотоковой, то есть может принимать сразу несколько входящих содинений
  • механизм записи лога работает также в отдельном потоке, ускоряет работу
Пример использования (для работы по Windows есть специальный скрипт pyspy.cmd).

Запускаем сервер:
pyspy.cmd -a 10.44.5.138 -p 5467 -l 9999 -L trace.log
Запускаем клиента:
telnet localhost 9999
и вводим GET / HTTP/1.0<ENTER><ENTER>

В файле лога и в консоли получаем вот такое:
0000: Listen at port 9999, remote host ('10.44.5.138', 5467)
0000: Connection accepted from ('127.0.0.1', 15223), thread 1 launched
0001: Thread started
0001: Connecting to ('10.44.5.138', 5467)...
0001: Remote host: ('127.0.0.1', 15223)
0001: Recevied from ('127.0.0.1', 15223) (1)
0001: ----: 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F
0001: ------------------------------------------------
0001: 0000: 47 | G
0001: Sent to ('10.44.5.138', 5467) (1)
0001: Recevied from ('127.0.0.1', 15223) (13)
0001: ----: 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F
0001: ------------------------------------------------
0001: 0000: 45 54 20 2F 20 48 54 54 50 2F 31 2E 30 | ET / HTTP/1.0
0001: Sent to ('10.44.5.138', 5467) (13)
0001: Recevied from ('127.0.0.1', 15223) (2)
0001: ----: 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F
0001: ------------------------------------------------
0001: 0000: 0D 0A | ..
0001: Sent to ('10.44.5.138', 5467) (2)
0001: Recevied from ('127.0.0.1', 15223) (2)
0001: ----: 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F
0001: ------------------------------------------------
0001: 0000: 0D 0A | ..
0001: Sent to ('10.44.5.138', 5467) (2)
0001: Recevied from ('10.44.5.138', 5467) (379)
0001: ----: 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F
0001: ------------------------------------------------
0001: 0000: 48 54 54 50 2F 31 2E 31 20 33 30 32 20 46 6F 75 | HTTP/1.1 302 Fou
0001: 0010: 6E 64 0D 0A 44 61 74 65 3A 20 46 72 69 2C 20 30 | nd..Date: Fri, 0
0001: 0020: 34 20 53 65 70 20 32 30 30 39 20 30 38 3A 35 33 | 4 Sep 2009 08:53
0001: 0030: 3A 30 33 20 47 4D 54 0D 0A 53 65 72 76 65 72 3A | :03 GMT..Server:
0001: 0040: 20 41 70 61 63 68 65 0D 0A 50 72 61 67 6D 61 3A | Apache..Pragma:
0001: 0050: 20 6E 6F 2D 63 61 63 68 65 0D 0A 45 78 70 69 72 | no-cache..Expir
0001: 0060: 65 73 3A 20 46 72 69 2C 20 30 31 20 4A 61 6E 20 | es: Fri, 01 Jan
0001: 0070: 31 39 39 39 20 30 30 3A 30 30 3A 30 30 20 47 4D | 1999 00:00:00 GM
0001: 0080: 54 0D 0A 43 61 63 68 65 2D 63 6F 6E 74 72 6F 6C | T..Cache-control
0001: 0090: 3A 20 6E 6F 2D 63 61 63 68 65 2C 20 6E 6F 2D 63 | : no-cache, no-c
0001: 00A0: 61 63 68 65 3D 22 53 65 74 2D 43 6F 6F 6B 69 65 | ache="Set-Cookie
0001: 00B0: 22 2C 20 70 72 69 76 61 74 65 0D 0A 4C 6F 63 61 | ", private..Loca
...
[обрезано]
...
0001: 0100: 76 3D 31 0D 0A 43 6F 6E 6E 65 63 74 69 6F 6E 3A | v=1..Connection:
0001: 0110: 20 63 6C 6F 73 65 0D 0A 43 6F 6E 74 65 6E 74 2D | close..Content-
0001: 0120: 54 79 70 65 3A 20 74 65 78 74 2F 68 74 6D 6C 0D | Type: text/html.
0001: 0130: 0A 0D 0A 52 65 64 69 72 65 63 74 20 70 61 67 65 | ...Redirect page
0001: 0140: 3C 62 72 3E 3C 62 72 3E 0A 54 68 65 72 65 20 69 | <br><br>.There i
0001: 0150: 73 20 6E 6F 74 68 69 6E 67 20 74 6F 20 73 65 65 | s nothing to see
0001: 0160: 20 68 65 72 65 2C 20 70 6C 65 61 73 65 20 6D 6F | here, please mo
0001: 0170: 76 65 20 61 6C 6F 6E 67 2E 2E 2E | ve along...
0001: Sent to ('127.0.0.1', 15223) (379)
0001: Connection reset by ('10.44.5.138', 5467)
0001: Connection closed
Теперь, собственно, исходник:
#!/usr/bin/python

import socket, string, threading, os, select, sys, time, getopt
from sys import argv

def usage():
name = os.path.basename(argv[0])
print "usage:", name, "-l listen_port -a host -p port [-L file] [-c] [-h?]"
print " -a host - address/host to connect"
print " -p port - remote port to connect"
print " -l listen_port - local port to listen"
print " -L file - log file"
print " -c - supress console output"
print " -h or -? - this help"
print " -v - version"
sys.exit(1)

PORT = False
REMOTE_HOST = REMOTE_PORT = False

CONSOLE = True
LOGFILE = False

try:
opts, args = getopt.getopt(argv[1:], "l:a:p:L:ch?v")

for opt in opts:
opt, val = opt
if opt == "-l":
PORT = int(val)
elif opt == "-a":
REMOTE_HOST = val
elif opt == "-p":
REMOTE_PORT = int(val)
elif opt == "-L":
LOGFILE = val
elif opt == "-c":
CONSOLE = False
elif opt == "-?" or opt == "-h":
usage()
elif opt == "-v":
print "Python TCP/IP Spy Version 1.01 Copyright (c) 2009 by Alexander Demin"
sys.exit(1)
else:
usage()

if not PORT:
raise StandardError, "listen port is not given"

if not REMOTE_HOST:
raise StandardError, "remote host is not given"

if not REMOTE_PORT:
raise StandardError, "remote port is not given"

except Exception, e:
print "error:", e, "\n"
usage()

# Remote host
REMOTE = (REMOTE_HOST, REMOTE_PORT)

# Create logging contitional variable
log_cond = threading.Condition()

queue = []

def logger():
global queue
while 1:
log_cond.acquire()

while len(queue) == 0:
log_cond.wait()

if LOGFILE:
try:
logfile = open(LOGFILE, "a+")
logfile.writelines(map(lambda x: x+"\n", queue))
logfile.close()
except: pass

if CONSOLE:
for line in queue:
print line

queue = []
log_cond.release()

# Thread safe logger
def log(thread, msg):
if CONSOLE or LOGFILE:
log_cond.acquire()
queue.append("%04d: %s" % (thread, msg))
log_cond.notify()
log_cond.release()

def printable(ch):
return (int(ch < 32) and '.') or (int(ch >= 32) and chr(ch))

# Pre-build a printable characters map
printable_map = [ printable(x) for x in range(256) ]

# Thread safe dumper
def log_dump(thread, msg):

if CONSOLE or LOGFILE:
log_cond.acquire()

width = 16

header = reduce(lambda x, y: x + ("%02X-" % y), range(width), "")[0:-1]
queue.append("%04d: ----: %s" % (thread, header))
queue.append("%04d: %s" % (thread, '-' * width * 3))

i = 0
while 1:
line = msg[i:i+width]
if len(line) == 0: break
dump = reduce(lambda x, y: x + ("%02X " % ord(y)), line, "")
char = reduce(lambda x, y: x + printable_map[ord(y)], line, "")
queue.append("%04X: %04X: %-*s| %-*s" % (thread, i, width*3, dump, width, char))
i = i + width

log_cond.notify()
log_cond.release()

# Spy thread
def spy_thread(local, addr, thread_id):
log(thread_id, "Thread started")

try:
log(thread_id, "Connecting to %s..." % str(REMOTE))
remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote.connect(REMOTE)
except Exception, e:
log(thread_id, "Unable connect to %s -> %s" % (REMOTE, e))
local.close()
return

LOCAL = str(addr)

log(thread_id, "Remote host: " + LOCAL)

try:
running = 1;
while running == 1:

rd, wr, er = select.select([local, remote], [], [local, remote], 3600)

for sock in er:
if sock == local:
log(thread_id, "Connection error from " + LOCAL)
running = 0
if sock == remote:
log(thread_id, "Connection error from " + REMOTE)
running = 0

for sock in rd:
if sock == local:
val = local.recv(1024)
if val:
log(thread_id, "Recevied from %s (%d)" % (LOCAL, len(val)))
log_dump(thread_id, val)
remote.send(val)
log(thread_id, "Sent to %s (%d)" % (REMOTE, len(val)))
else:
log(thread_id, "Connection reset by %s" % LOCAL)
running = 0;

if sock == remote:
val = remote.recv(1024)
if val:
log(thread_id, "Recevied from %s (%d)" % (REMOTE, len(val)))
log_dump(thread_id, val)
local.send(val)
log(thread_id, "Sent to %s (%d)" % (LOCAL, len(val)))
else:
log(thread_id, "Connection reset by %s" % str(REMOTE))
running = 0;

except Exception, e:
log(thread_id, ("Connection terminated: " + str(e)))

remote.close()
local.close()

log(thread_id, "Connection closed")

try:
# Server socket
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.bind(("", PORT))
except Exception, e:
print "error", e
sys.exit(1)

counter = 1

threading.Thread(target=logger, args=[]).start()

log(0, "Listen at port %d, remote host %s" % (PORT, REMOTE))

while 1:
srv.listen(1)
local, addr = srv.accept()
log(0, "Connection accepted from %s, thread %d launched" % (addr, counter))
threading.Thread(target=spy_thread, args=[local, addr, counter]).start()
counter = counter + 1
Лично я постоянно использую этот скрипт на Windows, Linux и Solaris.

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

А что стоит у вас на вооружении по этому вопросу?

четверг, 3 сентября 2009 г.

Google Code Jam Qualification Round 2009

Остается еще часов двенадцать до окончания квалификационного раунда Google Code Jam 2009.

Выполнив задачи A (Alien Language), B (Watersheds) и малую размерность задачи C (Welcome to Code Jam) я успешно сел в лужу на большой размерности задачи С. У меня там и не пахло отведенными 8 минутами на прогон данных. Сейчас ищу проблему, но поезд ушел -- на решение большой размерности дается только одна попытка.

Традиционный вывод: Good algorithms are better than supercomputers.

В итоге, пока я на почетном пенсионерском 1784-ом месте с 76-ю баллами (под именем begoon) из около 5700 всех участников.

Радует, что в топовой двадцатке три российских флага.

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

Забавно, через 3-4 часа после начала забега у них там что-то упало, и нельзя было скачать задания. Быстро все починили, но продлили время для участников на пару часов.

Кто-нибудь учавствует?

вторник, 4 августа 2009 г.

Хорошо ли использовать std::string?

Я регулярно имею споры с коллегами на тему стоит ли повсеместно использовать std::string, или таки надо реализовывать свой класс для строчек.

Один из вопросов - это вопрос качества самой реализации std::string. Забавно, что большинство людей, которых я просил набросать прототип класса для строчек, более менее эффективного с точки зрения работы с памятью, писали примерно следующее:
class String {
public:
explicit String(const std::string& value) {
init(value.c_str(), value.length());
}
String(const String& value) { init(value.data_, value.sz_); }
~String() { free(data_); }

String& operator=(const String& value) {
if (this != &value) {
if (value.sz_ > sz_) data_ = (char*)std::realloc(data_, value.sz_);
sz_ = value.sz_;
std::memcpy(data_, value.data_, sz_);
}
return *this;
}

private:
void init(const char* data, size_t sz) {
sz_ = sz;
data_ = (char*)malloc(sz_);
std::memcpy(data_, data, sz_);
}
char* data_;
size_t sz_;
};
Ясно, что при такой реализации оператора присваивания строка в плане занимаемой памяти будет только расти. Это сделано специально.

Практически никто сразу не думал о необходимости наличия операции перемещения, например swap. Почему-то наличие конструктора копирования и оператора присваивания считается достаточным.

Что ответить на этот вопрос раз и навсегда для себя самого, я написал тестовую программу. Это программа сортирует массив из длинных строк. Строки представлены четырьмя способами: объект std::string, указатель на std::string, объект самопального класса String (см. выше) и указатель на String.

По предварительным очевидным оценкам работа через указатель должна быть максимально эффективная, так как в данном случае при перемещении объектов физически std::sort() переставляет только указатели, а не сами объекты.

А вот при работе непосредственно с объектами будет интересно сравнить, насколько банальная реализации строки будет уступать std::string.

Итак, std_string.cpp:
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cassert>

#include "gtest/gtest.h"

static const int N = 100;

// Самопальный класс, реализующий хранение строки более менее
// эффективным образом с точки зрения копирования.
class String {
public:
// "explicit" запрещает неявное приведение аргумента, что мы
// могли точно знать, какие конструктор каких классов вызываются.
explicit String(const std::string& value) {
init(value.c_str(), value.length());
}
String(const String& value) { init(value.data_, value.sz_); }
~String() { free(data_); }

// Данный оператор - это, пожалуй, единственная попытка сделать
// работу с памятью эффективной.
String& operator=(const String& value) {
if (this != &value) {
// Память перераспределяется только если оригинал длинее текущей
// строки. Ясно, что при такой реализации строка может только
// расти в плане занимаемой памяти.
if (value.sz_ > sz_) data_ = (char*)std::realloc(data_, value.sz_);
sz_ = value.sz_;
std::memcpy(data_, value.data_, sz_);
}
return *this;
}

friend class StringCmp;
friend class StringPointerCmp;

private:
void init(const char* data, size_t sz) {
sz_ = sz;
data_ = (char*)malloc(sz_);
std::memcpy(data_, data, sz_);
}
char* data_;
size_t sz_;
};

std::vector<std::string> std_strings;
std::vector<std::string*> std_strings_p;
std::vector<String> strings;
std::vector<String*> strings_p;

// Объект для сравнения двух std::string.
class StlStringCmp {
public:
bool operator()(const std::string& a, const std::string& b) {
return a < b;
}
};

TEST(SortingStlString, StlString) {
std::sort(std_strings.begin(), std_strings.end(), StlStringCmp());
}

// Объект для сравнения двух std::string*.
class StlStringPointerCmp {
public:
bool operator()(const std::string* a, const std::string* b) {
return *a < *b;
}
};

TEST(SortingStlString, StlStringPointer) {
std::sort(std_strings_p.begin(), std_strings_p.end(), StlStringPointerCmp());
}

// Объект для сравнения двух String.
class StringCmp {
public:
bool operator()(const String& a, const String& b) {
assert(a.sz_ == b.sz_);
return std::memcmp(a.data_, b.data_, a.sz_);
}
};

TEST(SortingStlString, String) {
std::sort(strings.begin(), strings.end(), StringCmp());
}

// Объект для сравнения двух String*.
class StringPointerCmp {
public:
bool operator()(const String* a, const String* b) {
assert(a->sz_ == b->sz_);
return std::memcmp(a->data_, b->data_, a->sz_);
}
};

TEST(SortingStlString, StringPointer) {
std::sort(strings_p.begin(), strings_p.end(), StringPointerCmp());
}

int main(int argc, char* argv[]) {
// Это наполнитель, чтобы строки были длинные, и копирование было
// ощутимо дорого.
std::string big(1024 * 1024, '?');
for (int i = 0; i < N; ++i) {
// Все строки будут одинаковой длины. Функции сравнения рассчитывают
// на это.
std::stringstream fmt;
fmt << N * 2 - i << big;
// std::string строка-объект.
std_strings.push_back(fmt.str());
// std::string строка-указатель.
std_strings_p.push_back(new std::string(fmt.str()));
// Моя строка-объект.
strings.push_back(String(fmt.str()));
// Моя строка-указатель.
strings_p.push_back(new String(fmt.str()));
}

testing::InitGoogleTest(&argc, argv);
// Принудительно печатаем время работы тестов.
testing::GTEST_FLAG(print_time) = true;
return RUN_ALL_TESTS();
}
Компилируем:
cl /O2 /EHsc /I. std_string.cpp gtest-all.cc
Запускаем:
[==========] Running 4 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 4 tests from SortingStlString
[ RUN ] SortingStlString.StlString
[ OK ] SortingStlString.StlString (203 ms)
[ RUN ] SortingStlString.StlStringPointer
[ OK ] SortingStlString.StlStringPointer (0 ms)
[ RUN ] SortingStlString.String
[ OK ] SortingStlString.String (891 ms)
[ RUN ] SortingStlString.StringPointer
[ OK ] SortingStlString.StringPointer (0 ms)
[----------] 4 tests from SortingStlString (1125 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 1 test case ran. (1125 ms total)
[ PASSED ] 4 tests.
Видно, что версии с указателями отработали примерно одинаково быстро, а вот при работе с объектами std::string обогнал самопальную реализацию в 4 раза - 203 мс против 891 мс.

Несложно понять, почему это так. std::sort() для перестановки элементов использует шаблонную функцию std::swap(), которая для std::string реализована так, чтобы делать перестановку без физического копирования данных. А для String все происходит банально через конструктор копирования и оператор присваивания.

Вобщем, для себя я вынес, что не надо городить свой огород, так как в большинстве случаев std::string решает все проблемы. Но возникает вопрос - как добавлять в std::string свою функциональность? Например, поиск слов.
Проблема в том, что у std::string деструктор объявлен как невиртуальный (может это сделано по соображениям эффективности), а наследование от класса с невиртуальным деструктором в C++ является не самой правильной затеей.
Автор STL Александр Степанов в своем труде Notes for the Programming course at Adobe советует реализовать дополнительную функциональность для стандартных контейнеров STL через шаблонные алгоритмы. Плюсов тут много, например, реализовав какой-то разбор строки через итераторы в виде шаблонной функции-алгоритма, можно автоматически получить её же для все остальных контейнеров, у которых есть такие же итераторы.

Интересно, что пишет Степанов про свой взляд на то, как надо реализовывать функцию length() для контейнеров (в документе, ссылка на который дана выше, Степанов показывает шаг за шагом процесс создания эффективного контейнера):
While we could make a member function to return length, it is better to make it a global friend function. If we do that, we will be able eventually to define the same function to work on built-in arrays and achieve greater uniformity of design. I made size into a member function in STL in an attempt to please the standard committee. I knew that begin, end and size should be global functions but was not willing to risk another fight with the committee.
Он считает, что глобальная шаблонная функция length() - это правильнее, чем length() как член класса. Если б не комитет стандартизации - так оно и было бы в STL.

Итак, подытоживая сказанное, не стоит не доверять std::string. Для большинства задач этот класс решает все проблемы. Если надо добавить функциональность - это надо делать через реализацию шаблонного алгоритма.

Есть замечания или протесты? Прилагайте.

Посты по теме:

четверг, 30 июля 2009 г.

Александр Степанов

Александр Степанов - это создатель STL. Ни добавить, ни отнять. Последние годы он занимается в основном преподаванием и написанием книг.

Недавно я купил его последнюю книгу "Elements of Programming".



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

Еще интересный факт: в данной книге везде при использовании шаблонов используются концепты, хотя недавно было принято решение, что в C++0x их не будет из-за общей пока недоработанности идеи.

Но вернемся к другим публикациям Степанова.

Одни из мои любимых - Notes for the Programming course at Adobe и Science of C++ Programming.

Например, ставший классикой, его пример на тему итераторов:
if (!v.empty()) {
sort(&*begin(), &*v.begin() + v.size());
}
когда спрашивается, почему в данном вполне рабочем примере обязательно нужна проверка v.empty() и почему нельзя второй аргумент нельзя записать как &*v.end()?

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

Но Степанов говорит следующее: "...присваивание должно осуществляться вызовом деструктора и последующим конструктором". То есть надо просто сделать полноценный конструктор копирования, а оператор присваивание реализовать так:
T& T::operator=(const T& x) {
if (this != &x) {
this->T::~T();
new (this) T(x);
}
return *this;
}
Получается, что старый объект сам себя разрушает, вызвав деструктор (но память под ним не освобождается), а затем оператором new с явным размещением (память под объект тут уже повторно не распределяется) объект создается снова через конструктор копирования.

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

среда, 29 июля 2009 г.

Параметры по умолчанию в С++ - это опасно

Значения формальных параметров по умолчанию в С++ - удобная штука. Удобная и опасная.

Вот книжный пример (default_values.cpp):
#include <iostream>

class X {
public:
virtual void f(int i = 0) {
std::cout << "X::f(): " << i << std::endl;
}
};

class Y: public X {
public:
void f(int i = 1) {
std::cout << "Y::f(): " << i << std::endl;
}
};

int main() {
X* a = new Y;
a->f();

Y* b = new Y;
b->f();

return 0;
}
Что распечатает эта программа? Вот что:
Y::f(): 0
Y::f(): 1
В обоих случаях была вызвана функция Y::f(), но значение аргумента было разное.

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

Я намеренно опустил модификатор virtual в описании функции f() в классе Y. Но от этого она невиртуальной не стала. При сложной иерархии классов очень несложно не заметить, что изначально функция была виртуальной, и для собственного удобства подправить ей значение параметра по умолчанию для какого-то конкретного случая, тем самым привнеся очень неприятную ошибку (и тут уже надежда на зоркий глаз коллеги при code view или на статические анализаторы).

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

Посты по теме:


Как я уже писал ранее, все исходные тексты и статьи теперь дублируются на одноименном сайте, размещенном на Google Code.

воскресенье, 26 июля 2009 г.

Парадокс Монти-Холла

Есть такая интересная задача - парадокс Монти-Холла.

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

Естественно, ведущий точно заранее знает где приз и заведомо открывает пустой ящик. Итак, вы изначально выбрали ящик, но потом ведущий открыл один из оставшихся и выяснилось, что он пустой. Перед вами выбор: оставить свой изначальный выбор неизменным или изменить его, выбрав третий ящик (тот, что остался после вашего первого выбора и после открытия ведущим пустого ящика). При какой стратегии вероятность выигрыша выше?

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

Но эта задачка тем и интересна, что при столь тривиальной постановке ее правильное решение не совсем очевидно, хотя с точки зрения теории вероятности тут все прозрачно - теорему Байеса еще никто не отменял.

Правильный ответ - да, надо менять выбор, так как в этом случае вероятность угадать повышается с 1/3 до 2/3 (и даже не 1/2).

В Википедии приведено исчерпывающее объяснение.

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

montihall.cpp:
#include <iostream>
#include <set>
#include <cstdlib>
#include <cassert>
#include <ctime>

int all_doors[] = { 1, 2, 3 };

bool no_change_strategy() {
// doors - это множество доступных дверей (1, 2, 3) для выбора игроком.
std::set<int> doors(all_doors, all_doors + 3);

// Выбираем истинную дверь (от 1 до 3).
int real_door = (std::rand() % 3) + 1;

// Выбираем первый и окончательный выбор игрока (от 1 до 3).
int primary_choice_door = (std::rand() % 3) + 1;

return real_door == primary_choice_door;
}

bool change_strategy() {
// doors - это множество доступных дверей (1, 2, 3) для выбора двери,
// открываемой ведущим после первого выбора игрока.
std::set<int> doors(all_doors, all_doors + 3);

// Выбираем истинную дверь (от 1 до 3).
int real_door = (std::rand() % 3) + 1;

// Выбираем первый выбор игрока (от 1 до 3)
int primary_choice_door = (std::rand() % 3) + 1;

// Исключаем из множества дверей истинную дверь и выбор игрока.
doors.erase(real_door);
doors.erase(primary_choice_door);
// На всякий пожарный проверим целостность данных.
assert(doors.size() == 1 || doors.size() == 2);

// Из оставшихся элементов (их может быть 1 или 2 штуки) выбираем дверь,
// которую откроет ведущий. reveal_door равно либо 1, либо 2.
int reveal_door = (std::rand() % doors.size()) + 1;

// i указывает на первый элемент в множестве (всего в нем 1 или 2 элемента).
std::set<int>::const_iterator i = doors.begin();
// Сдвигаем итератор на элемент, номер которого равен reveal_door.
// Можно было бы написать "if (reveal_door == 2) ++i;", но цикл как-то
// универсальнее.
while (--reveal_door) ++i;
reveal_door = *i;

// 'doors2' - это множество доступных дверей (1, 2, 3) для игрока,
// меняющего свой первоначальный выбор.
std::set<int> doors2(all_doors, all_doors + 3);

// Исключаем из множества дверей первый выбор игрока и
// и дверь, открытую ведущим.
doors2.erase(primary_choice_door);
doors2.erase(reveal_door);
// На всякий пожарный проверим целостность данных.
assert(doors2.size() == 1);

// В множестве оставшихся дверей будет только одна дверь, так как истинная
// дверь точно не равна двери, открытой ведущим, во второй выбор игрока
// точно отличается от первоначального. Поэтому просто берем из этого
// множества первый элемент.
int second_choice = *doors2.begin();

return real_door == second_choice;
}

int main() {
std::srand(std::time(0));
int guess_on_change = 0;
int guess_on_not_change = 0;
int N = 100000;
for (int i = 0; i < N; ++i) {
if (change_strategy())
guess_on_change = guess_on_change + 1;
if (no_change_strategy())
guess_on_not_change = guess_on_not_change + 1;
}
std::cout << "Вероятность выиграть при смене изначального выбора: "
<< guess_on_change * 1.0 / N << std::endl;
std::cout << "Вероятность выиграть не меняя изначального выбора: "
<< guess_on_not_change * 1.0 / N << std::endl;
return 0;
}
Компилируем и запускаем:
cl /EHsc /D_NDEBUG montihall.cpp && montihall
Результат подтверждает теорию:
Вероятность выиграть при смене изначального выбора: 0.67005
Вероятность выиграть не меняя изначального выбора: 0.33347
Лично я провел замечательные несколько часов, вспоминая всю эту тему условных вероятностей. А вы?

Ведение блога на Google Code

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

Теперь ближе к делу. Blogspot как механизм для ведения блога меня вполне устраивает. Единственное, чего не хватает - это возможности форматирования постов не в HTML или WYSIWYG редакторе, а на каком-нибудь диалекте Wiki. Мне много-то не надо, хотя бы базовые элементы.

Последнее время я как-то уж сблизился с Google Code. А там как раз есть Wiki для ведения документации. Вот и возникала мысль скрестить Blogspot и Google Code для ведения блога.

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

Я пришел вот к такой схеме. Для начала я завел одноименный проект на Google Code - Easy Coding.

Каждый пост пишется на Google Code в виде отдельной странички Wiki (лишний повод не писать двух-строчных одноразовых постов). Отлаживается верстка и общий внешний вид. Можно писать прямо в онлайне на страничке, но лично я изначально набиваю все в notepad++ и aspell. Благо разметка Wiki не требует ежеминутного закольцованного ерзанья типа исправил-посмотрел-как-выглядит, как в HTML.

Если надо повесить картинку, то она выкладывается в раздел Downloads, и ссылка на картинку ведет туда. Исходники тоже можно было бы заливать в этот раздел, есть есть способ лучше. Исходники удобнее держать в разделе Source. Там их можно удобно просматривать, скачивать, комментировать и т.д. И тут уже можно насладиться контролем версий для них (еще раз уточню - когда выкладываешь работающие исходники, это становится важно) - Subversion или Mercurial, кому что больше нравится. Кстати, раздел Wiki тоже под контролем версий.

Я использую Mercurial, так как распределенная модель позволяет работать долго локально, а потом одним махом отправить изменения на сервер. Получается, что Mercurial в данном случае - это как файловый клиент для обмена файлами с Google Code.

Итак, пост закончен и отлажен. Теперь его надо выложить на Blogspot.

Для этого я написал элементарный скрипт на php. Данный скрипт преобразует пост из формата .wiki в .html с ориентацией на особенности шаблонов и стилей конкретно Blogspot'а. Он, конечно, не идеален, но я решил так - я не буду делать универсального монстра, а буду править его по мере возникновения проблем или требования новых возможностей.

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

Посты по теме: