четверг, 4 декабря 2008 г.

Twitter

Зарегался на twitter.com. Можете там отслеживать, чем я страдаю обычно вместо кодинга %)
http://twitter.com/nosmileface

ООП в языках

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

Итак попорядку. Мне не нравится термин <эдакое> программирование, потому что я понимаю под программированием процесс превращения дизайна программы в непосредственно код. То есть существует некоторый дизайн-документ, где подробно описан каждый аспект программы и чтобы дизайн-документ стал непосредственно программой, нужно написать реализацию согласно этому дизайн-документу на конкретном языке программирования. Помоему часто программированием называют как процесс создания дизайна программы, её архитектуры, так и процесс непосредственно материализации этого дизайна в виде кода на конкретном языке программирования. Вероятней всего это случилось от того, что в древние времена роль дизайнера и программиста исполнял один человек или даже группа, но всё равно не стояла задача разделения таких понятий. Вообщем не столь важны причины, просто отмечу, что я не люблю называть программированием весь процесс создания программы. Я разделаю весь процесс на две части: дизайн и программирование.

Под дизайном я понимаю обдумывание и документирование (иногда в голове) принципов работы будущей программы в виде какой-либо системы символов и понятий (понятийного аппарата). Дизайн может быть функциональный, объектно-ориентированный или какой-либо другой. Именно методика дизайна определяет какими способами ведется продумывание и построение/создание дизайн-документа. Можно рассматривать проблему с точки зрения множества объектов, которые взаимодействуют друг с другом или же как некий набор функционала, который имеет свойство быть декомпозированным в некую модульную структуру. Что конкретно вы применяете не так важно, потому что дизайн дело тонкое и не простое, лучше всего в этом процессе полагаться на то, что называется чутьем. Интуиция должна подсказивать какое решение верное, а какое нет. Ну и это в последствии проверяется на практике. Каждый хороший дизайнер/архитектор обладает хорошим чутьем.

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

Но конечно же эти два этапа последовательны только в идеале. Зачастую они прогрессируют параллельно. Части дизайна переписываются и изменения отражаются в коде программы. Процесс приобретает итеративный вид. Иными словами так чаще всего (если не всегда) случается на практике. Поэтому вероятно и обзывают всё это одним словом - программирование.

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

Что я имею ввиду? Давайте посмотрим на примере. Пусть в терминах объектно-ориентированного дизайна существует некая абстракция - интерфейс Reader. А также существуют его реализации: ZipReader, MemoryReader и FileReader.

Давайте напишем реализацию на языке Си:


struct Reader;
struct ReaderInterface { /* it's like vtable */
void (*close)(struct Reader *reader);
int (*read)(struct Reader *reader, void *memory, size_t size);
};

struct Reader {
struct ReaderInterface *interface;
void *private_data;
};

/* inline functions */
inline void rclose(struct Reader *reader)
{
reader->interface->close(reader);
}
inline int rread(struct Reader *reader, void *memory, size_t size)
{
return (*reader->interface->read)(reader, memory, size);
}

/* now we're defining somewhere interfaces with their private_data
meaning and with their own interface table */

struct Reader *create_zip_reader(const char *zipfile, const char *file_in_zip);
struct Reader *create_file_reader(const char *filename);
struct Reader *create_memory_reader(const void *ptr, size_t size);

/* and the usage */
void usage_function()
{
struct Reader *r = create_zip_reader("archive.zip", "file/in/archive.txt");
if (!r) {
error();
return;
}

int a = 0, b = 0, c = 0;

rread(r, &a, sizeof(int));
rread(r, &b, sizeof(int));
rread(r, &c, sizeof(int));

rclose(r);
}

А теперь напишем то же самое на С++:


struct Reader {
virtual ~Reader() {} // good C++ style, virtual destructor in interface
// virtual void close() = 0;
// no close() function, closing on delete
virtual int read(void *memory, size_t size) = 0;
};

Reader *create_zip_reader(const char *zipfile, const char *file_in_zip);
Reader *create_file_reader(const char *filename);
Reader *create_memory_reader(const void *ptr, size_t size);

void usage_function()
{
Reader *r = create_zip_reader("archive.zip", "file/in/archive.txt");
if (!r) {
error();
return;
}

int a = 0, b = 0, c = 0;

r->read(&a, sizeof(int));
r->read(&b, sizeof(int));
r->read(&c, sizeof(int));

delete r;
}

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

Каждая конкретная реализация методики полюбому имеет свои ограничения. А что если я захочу так, как делать нельзя? Приходится думать как обойти проблему в рамках поддержки этих конструкций в языке и если не получается, то мы спускаемся на уровень языка Си, где вообще никаких средств для реализации тех или иных идей из ООД нет. Или другой случай, когда появляется новая методика и её невозможно выразить в терминах старой методики, не переписывать же язык! Поэтому я считаю, что каждая конкретная реализация методики не нужна, ибо имеет слишком узкую область применения. А что если попробовать реализовать поддержку методик ООД, без конкретики. Допустим предложить некий синтаксис для непосредственных манипуляций с vtable или ещё что-нибудь достаточно общее, но не вводить понятия класс, объект и наследование. Впрочем надо сказать, что и самого языка Си уже достаточно, т.к. на примере выше можно чётко видеть, что простые inline функции почти решают проблемы нечитаемого вида обращений к виртуальной таблице. 

Впрочем, возможно я погорячился и поддержка ООД действительно не может иметь никаких иных удобных для использования форм. Да и в конце концов все средства ООД в основном в С++ это операции с virtual table путем формирования иерархий классов. Возможно это и не так критично для языка. Но что однозначно я бы покритиковал, это языки в которых "всё является объектом". Это неправильный подход к формированию средств кодогенерации однозначно.

Но если же отойти от примера с ООД и вспомнить про другие техники, начать мыслить обобщенно и пойти по этому направлению. В голове начинают всплывать всяческие страшные картины языка в котором можно модифицировать синтаксис и придумывать новые конструкции и/или модифицировать уже существующие меняя их семантику. И рядом тут ещё метапрограммирование где-то стоит. Но стоит взглянуть на современные языки типа Nemerle или D, то оказывается, что мысли не так далеки от правды. В этих языках уже можно определять новые синтаксические конструкции и представлять свою семантику в виде макросов, которые оперируют с AST (abstract syntax tree). Поживем - увидим. Может именно это направление и будет ключевым в ближайшие 10-20 лет развития средств кодогенерации. Язык с динамическим синтаксисом и плавающей семантикой. Как вам такое?

понедельник, 1 декабря 2008 г.

Шаблоны и библиотеки

В продолжении темы про кодогенерацию опять нахлынуло на меня несколько мыслей по этому поводу. Многие говорят: "Шаблоны, шаблоны! Generic programming! Создание компонент для настоящего повторного использования". Но как всё обстоит на самом деле? Любой шаблонный класс или шаблонная библиотека скорее всего представляют собой инструмент кодогенерации, а сам механизм шаблонов средство генерации средств кодогенерации :) Т.е. да, у шаблонов и препроцессора много общего. Использовать библиотеки на шаблонах зачастую значит копипастить и плодить код, пусть и потенциально более оптимальный. Конечно важно иметь такую возможность, особенно работая с кодом у которого высокие требования к производительности, но ведь шаблоны сплошь и рядом. То, что можно было смело засунуть в shared object, пихается в header'ы теперь. Понимаю на системах, где даже libc принято линковать статически (да да, я о Windows), там такие штуки никого не удивляют. Но там же и не удивляет никого очередной продукт софтостроения на пару гигабайт весом. А я считаю компоненты предназначенные для повторного использования и должны иметь вид отдельных shared object'ов. По крайней мере надо понимать и однозначно разграничивать, что является Библиотекой с большой буквы, а что является инструментом кодогенерации. А ещё более важно знать на столько ли нужен супер оптимальный код, подстроеный под конкретный тип или может быть по настоящему generic версия в библиотеке сгодится вполне. И ведь вроде так просто называть вещи своими именами. Не все почему-то это делают.

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

Пояснения

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

  1. Весь рассказ про историю говорит о том, что развитие средств кодогенерации идет в неверном направлении уже 20-30 лет. Программисты упустили из виду сущность того, чем они занимаются.
  2. Всё, что я сказал про ООП говорит о том, что влияние ООП на языки программирования преувеличено. Термин ООП - не соответствует реальности. Существует только объектно-ориентированный дизайн.
  3. Обзор некоторых особенностей языков показывает, что не всё так плохо. Развитие средств кодогенерации наблюдается, но в темпах значительно ниже возможных.
  4. Вероятно причина всему - сильная коммерциализация явления "программирование", а также дурное влияние методик дизайна и архитектуры на программистов (бедные).
  5. Тут можно почитать схожие мысли. Идея в том, что вся эволюция в языках программирования последние 20-30 лет суетится вокруг именно средств дизайна и архитектуры. Средства же кодогенерации почти не развиваются.
  6. При осознании всего процесса эволюции и со взглядом на всё это снаружи, становится понятно, что не так и что можно поделать. Например: переписать препроцессор языка Си, переосмыслить вид представления поддержки объектно-ориентированного дизайна в языках.
  7. Дизайн/архитектура ПО и собственно программирование это разные процессы, хотя часто и происходят одновременно, параллельно. Оба процесса равнозначно важны и развивать нужно их методику в равной степени.
  8. Полезно остановиться и освободится от жизненной рутины и взглянуть на всё, что ты делаешь вне процесса/системы. Такие моменты дают бесценное понимание некоторых вещей. :-)

среда, 26 ноября 2008 г.

Эволюция языков программирования

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

Заранее предупреждаю, я не имею полного набора фактов относительно истории развития языков программирования. Я не сведущ относительного того кто, когда и главное каких недалал поделок, называя их "языками программирования" и что уж скрывать, меня это не особо интересует. На данный момент времени понятно, что существует ряд популярных языков программирования в которых миллионы мух (вы уж простите, что я так о программистах, конечно же вы самые умные и интеллектуальные и <вставьте ваш комплимент>, но это звучит символично) что-то эдакое нашли. И опираясь на то, что я вижу сейчас и то, что было когда-то (немножко-то истории я всё же знаю), я сделаю свои выводы. И пожалуйста, не подумайте, что я ярый приверженец какой-то одной идеи будь то ООП или функциональщина. Я скорее пытаюсь не вдаваясь в термины/понятия парадигм поделиться своим представлением относительно явления "программирование".

Дженерейшн намба уан. Или его отсутствие.

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

Поколение некст или намба ту.

И тут появился первый язык программирования, если его можно так назвать - assembly language. Главное, на что стоит обратить внимание - это появление стадии трансляции из символьного представления машинных команд в непосредственно машинные. Ещё раз - главное это сама стадия, а не что она делает. Т.е. человек в то время открыл для себя важную штуку - машинный код он писать нифига не способен и появилась потребность в средствах кодогенерации.

Я немного отойду от главной темы и немного углублюсь в абстрактное понимание процесса программирования. Что это такое по сути? Мы пишем программы на любимых языках и формошлепим иногда, рисуем какие-то GUI элементы, подготавливаем какие-то наборы данных. По сути я называю этот процесс одним словом - кодогенерация. Т.е. если мы пишем в Си статический массив - это кодогенерация, если мы пишем функцию для рекурсивной обработки сложной древовидной структуры - это кодогенерация. Ключевая фишка тут это то, что данные и код это одно и то же. Рисуем картинку для программы - кодогенерация. Да, я понимаю звучит не важно, просто запомните пожалуйста, что я называю это так. Когда нужно будет различить, что я имею ввиду, я буду упоминать кодогенерацию данных или кодогенерацию исполняемого кода :)

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

Третье поколение. Революция.

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

Звездец.

Пожалуй в конце 80ых и в начале 90ых, такое устройство как компьютер начинает получать известность и массовость. В сущность вливаются реклама и деньги. Всё подвергается жуткой коммерциализации и прочим прелестям от лукавого. Одним словом - неприятно. Но это не самое страшное, появляются некоторые чёрные концепции типа ООП, АОП, которые намеренно отступают от главной сути языков программирования и дают какие-то свои ничем не подкрепленные понятия. Теперь язык программирования не является средством кодогенерации, а становится вдруг средством выражения идей человека в первую очередь, хотя я считаю это в корне не верно. Нельзя избавится от того факта, что программирование это процесс ориентированный на железо. Т.е. Hardware-oriented programming. В ООП появляются какие-то непонятно чем навеенные понятия: наследование, класс, инкапсуляция, объект, полиморфизм. И они якобы имеют какие-то отношение к реальности человеческой или машинной.  Иными словами настал звездец. А теперь я предлагаю вам немного передохнуть, очистить голову от всяких бранных мыслей и продолжить чтение. Далее я раскрою тему того, что я имею ввиду.

Что не так с нынешним путем. Или его отсутствие ;-)

Стоит заметить, что под ООП я имею ввиду все эти новомодные фичи с наследованием, инкапсуляцией, абстракцией и полиморфизмом. Ими в полной мере или частично страдают такие языки как Java, C#, C++, Python, D, Ruby, PHP, Delphi (вероятней всего ваш основной язык программирования в этом списке). А теперь давайте по порядку посмотрим, что же из себя представляют эти фичи.

Абстракцию можно даже не рассматривать, т.к. программирование это такая история про абстракцию. Нет программирования без абстракции. И почему её приписывают к ООП?

Наследование - все помнят типичный пример Shape, Rectangle, Circle. Идея в том, что какие-то структуры данных строятся на основе уже существующих. Каждая Shape имеет площадь, но скажем Rectangle имеет в дополнение к этому такой параметр как отношение сторон, а Circle имеет радиус. Я считаю, что наследование хоть и имеет какую-то логику, но реальная её польза ощутима при использовании понятия полиморфизм. Иначе же это простая композиция данных и/или алгоритмов, т.е. кода. Вообще я бы сказал, что наследование это и есть способ реализации идеи полиморфизма. Помнится в злой науке невнятные понятия частое явление. Есть о чём задуматься? Так, обратно к теме. Пусть наследование и полиморфизм представляют один набор тесно взаимосвязанных фич, на практике же всё это представлено в виде неявного использования таблицы виртуальных функций/методов (vtbl). Т.е. такой простой прием кодогенерации встраивают в язык по какой-то причине и позволяют работать с ним на уровне синтаксиса. Оно и понятно, никому не охото писать myobject->vtbl[METHOD_ID](args). Но фактический в такой фиче нет ничего нового и по сути своей она ничем не отличается от простого использования указателей на функции. Возможно вид несколько иной, пусть и более удобный. Но однозначно фича не стоит того, чтобы о ней писали трактаты и на площадях выкрикивали: "Everything is an object!".

Инкапсуляция - что-то там про сокрытие данных и выставление на показ только интерфейса для пользователя-дебила. Ну хорошо, пусть это средство самоконтроля себя от своих же ошибок. Также можно отнести к средству какого-никакого дизайна/архетектуры, но сомнительно. #define private public - это реальность, а не сказка. Понятно дело, что никакого кода не генерирует. В С++ если использовать класс на прямую, не по указателю, то компилятору обязательно нужно знать все детали его реализации, для определения размера. Так что смысла в кодогенерации нет никакого. Получаем бесплатную порцию жучков мозгоедов.

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

Куда идти?

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

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

Шаблоны - неосознанная попытка заменить препроцессор на типизированный препроцессор с возможностями метапрограммирования. Шаблоны сильно полезны в области generic программирования. Непосредственный инструмент кодогенерации. Более совершенная система подобного рода должна рано или поздно заменить устаревший препроцессор. Можно посмотреть в сторону языка D, где препроцессора уже нет и сам язык выполняет его роль на стадии компиляции. Появились даже AST макросы, позволяющие работать с AST напрямую. Однозначно правильная идея развития кодогенерации как данных так и исполняемого кода.

Перегрузка операторов - средством генерации вряд-ли назовешь, скорее синтаксический сахар, но тем не менее польза есть. Просто удобно. А кто не понимает - пусть пишут на Lisp.

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

Есть в С++ ещё и плохие штуки. Например сломанный static. В Си изначально keyword static является исключительно инструментом кодогенерации данных. В С++ он приобрел невнятное состояние из-за возможности запускать исполняемый код. Это от части проблема того, что в языке нет нормальных инструментов метапрограммирования, от части вероятно недальнозоркость авторов языка. Вроде бы в С++0x должны появиться какие-то фичи связанные с этим в виде constexpr. Посмотрим.

Теперь о GUI. Не поверите как текущие GUI системы себя инициализируют! Вместо того, чтобы программа напрямую содержала snapshot GUI иерархии виджетов и после загрузки программы память сразу начинала с ним работать, в GUI программах содержится код создания этой самой иерархии, который вызывается ровно один раз и является абсолютно не нужным баластом с точки зрения логики. Всё, что можно делать вне runtime'а, должно делаться именно там. Проблема в том, что в современных языках недостаточно выразительных средств кодогенерации данных. Нельзя описать в разумном виде сложную иерархию объектов и использовать её. Языки имеют средство описания массивов например, но в приличном виде невозможно описать скажем red-black tree. Почему? А потому что мыслим в другом направлении. Я однозначно за развитие таких средств, пусть они и будут в виде техник метапрограммирования. Конечно, ничего не мешает мне написать генератор данных и встроить их в Си код, но такие средства должны уметь взаимодействовать сразу с языком программирования. Подведя итоги. В GUI вообще не должно быть кода инициализации самих GUI виджетов. Конечно всегда есть исключения, которые имеют под собой вполне понятное обоснование. Если программа загружает конфиг и в зависимости от значений полученных оттуда что-то меняет в виджетах, это понятно. Но когда программа при каждом запуске запускает один и тот же код, который генерирует в памяти один и тот же набор данных при любых условиях и более того, этот набор данных меньше или равен по размеру самому кода(!), это лишний код и он требует удаления. Поверьте, такой код не редкость!

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

понедельник, 24 ноября 2008 г.

Насекомые

Сегодня немного необычный пост... о насекомых. А именно речь пойдет о Cimex lectularius, он же Постельный клоп.

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

Могут без еды существовать по пол года, самки откладывают в день до 12 яиц. Жрут кровь по ночам, укус не чувствуется, попадает гад не с первого раза куда надо, поэтому оставляет 3-4 точки, чешутся.

Убил больше 40+ тварей руками, обработал комнату дихлофосом (читал, что не сильно помогает, но эффект есть всё же), удалил софу из комнаты. Теперь в комнате пусто и прекрасно. Давно мечтал избавится от старой мебели :) 

А ещё режим сна вроде вернул в нормальное русло. Надеюсь завтра всё встанет на свои места.

Мораль: во всём плохом и мерзком, всегда есть что-то хорошее

P.S. Не завидую тем, кто столкнется с клопами.

воскресенье, 23 ноября 2008 г.

Блогонеприязнь

Я столько раз начинал блог, что уже сбился со счету. Не знаю почему я их удаляю каждый раз. Видимо так надо зачем-то кому-то. Но на этот раз, я в самом деле даю публично обещание не удалять его и пусть даже он будет пустовать, как некий nsf.blogspot.com (прямо говорит мне - место занято, кыш), но блог будет жить!