Всеукраинская конференция разработчиков и пользователей свободных программ
Посмотрим что будет.
вторник, 31 августа 2010 г.
четверг, 26 августа 2010 г.
Условия и переменные (заметки Кэпа)
Решил я как-то проверить каким образом вычисляются составные условия в 1С 8.1
Не думаю, что профи найдут для себя в этой статье нечто новое или интересное, но Капитан Очевидность продолжает вещать.
Первое, что пришлось проверить - не инициализированные переменные:
На код вроде:
В этом случае при сохранении ошибки синтаксиса не будет, а при запуске надписи "Ок!" не появляется. По логике следования кода мы нигде не определяли переменную Б, но код успешно выполняется.
Что говорит нам отладчик?
Обратите внимание, что в самом начале, когда ни одна переменная еще не определена память под них уже зарезервирована и им назначен тип "Неопределено", в отличии от переменной В, которая вообще не упоминается в процедуре.
Сделаем пару шагов:
Теперь все очевидно. Условие совершенно корректно и дает на выходе ложь, так как единица не равна "Неопределено" и вообще разных типов.
Итак, предварительный вывод:
Чисто теоретически следующий код мог бы работать:
Но нет. Не проходит синтаксический контроль, что вообщем-то правильно.
Чем же эта информация может быть полезна? Например тем, что если в процессе отладки у вас какая-то переменная имеет тип "Неопределено", то возможно она была инициализирована неявно и вам стоит проверить свои условия. Хотя, вносить инициализацию переменной в условие вообще-то плохой тон. Да, да, как раз из-за вышеизложенного.
Однако, неявная инициализация это не совсем то, что я собирался выяснить с самого начала и я вернулся к условиям.
Следующий код работает как положено и выдает правильный результат:
Запускаем - бинго! Все работает, несмотря на то, что в составном условии есть обращение к реквизиту переменной "Товар", к "Наименованию", которого у типа "Неопределено" быть не может.
Для проверки попробуем еще два вида условия:
1.
В первом случае мы поменяли "и" на "или" в условии, во-втором поменяли местами части условия.
В обоих случая при попытке выполнения мы получили сообщение об ошибке. Почему? Потому, что "компилятор" вычисляя выражение условия идет от начала к концу по-очереди вычисляя каждое вложенное выражение (по правилам арифметики, ага) и как только становится очевидно, что все выражение имеет определнный результат (истина/ложь) вычисление прекращается.
В нашем случае, когда мы сначала проверяли не является ли переменная "Товар" неопределенной и получали "ложь" смысл проверки остальных операндов утрачивался, так как в операции "И" достаточно одному операнду быть ложным, чтобы остальные можно было не проверять - все-равно конечный результат будет "ложь".
В первом случае, когда мы заменили "и" на "или" необходимость проверять все выражение возникла снова так как в определении "или" говорится, что истинным должен быть "хотя бы один" из операндов. Но пытаясь вычислить второй операнд мы сталкивались с ошибкой о которой я писал выше.
Во-втором варианте, результат выполнения так же изменился, несмотря на то, что операция "и" коммутативная. Теперь проверка начинала выполняться с первого, заведомо провального операнда.
Такое поведение, на мой взгляд оправдано и удобно. Сравните, например с похожим кодом в платформе 7.7:
Для платформы 7.7 пришлось бы применить следующее:
Итак, теперь вы можете смело записывать в одно условие конструкции, которые могут вызывать ошибки, в случае расхождения типов, главное не забывайте предварять их проверками на соответствие.
Спасибо за внимание и хорошего вам кода!
Не думаю, что профи найдут для себя в этой статье нечто новое или интересное, но Капитан Очевидность продолжает вещать.
Первое, что пришлось проверить - не инициализированные переменные:
На код вроде:
А = 1;
Если (А=1) и (Б=1) Тогда
Сообщить("Ок!");
КонецЕсли;
Выругался сам "компилятор" при попытке сохранить обработку. Логично, так как переменная Б нигде не определена. Но, если обмануть синтаксический контроль следующим образом:Если (А=1) и (Б=1) Тогда
Сообщить("Ок!");
КонецЕсли;
А = 1;
Если 0=1 Тогда // заведомо ложное условие
Б=1; // никогда не выполняющийся код
КонецЕсли;
Если (А=1) и (Б=1) Тогда
Сообщить("Ок!");
КонецЕсли;
Если 0=1 Тогда // заведомо ложное условие
Б=1; // никогда не выполняющийся код
КонецЕсли;
Если (А=1) и (Б=1) Тогда
Сообщить("Ок!");
КонецЕсли;
В этом случае при сохранении ошибки синтаксиса не будет, а при запуске надписи "Ок!" не появляется. По логике следования кода мы нигде не определяли переменную Б, но код успешно выполняется.
Что говорит нам отладчик?
Обратите внимание, что в самом начале, когда ни одна переменная еще не определена память под них уже зарезервирована и им назначен тип "Неопределено", в отличии от переменной В, которая вообще не упоминается в процедуре.
Сделаем пару шагов:
Теперь все очевидно. Условие совершенно корректно и дает на выходе ложь, так как единица не равна "Неопределено" и вообще разных типов.
Итак, предварительный вывод:
Под переменные, которые могут быть инициализированы, внутри своей области видимости память выделяется в самом начале и они неявно инициализируются типом "Неопределено".
Чисто теоретически следующий код мог бы работать:
А = 1;
Если (А=1) и (Б=1) Тогда
Сообщить("Ок!");
КонецЕсли;
Если 0=1 Тогда
Б=1;
КонецЕсли;
Если (А=1) и (Б=1) Тогда
Сообщить("Ок!");
КонецЕсли;
Если 0=1 Тогда
Б=1;
КонецЕсли;
Но нет. Не проходит синтаксический контроль, что вообщем-то правильно.
Чем же эта информация может быть полезна? Например тем, что если в процессе отладки у вас какая-то переменная имеет тип "Неопределено", то возможно она была инициализирована неявно и вам стоит проверить свои условия. Хотя, вносить инициализацию переменной в условие вообще-то плохой тон. Да, да, как раз из-за вышеизложенного.
Однако, неявная инициализация это не совсем то, что я собирался выяснить с самого начала и я вернулся к условиям.
Следующий код работает как положено и выдает правильный результат:
Товар = Справочники.Номенклатура.НайтиПоКоду("00000026");
Если (ТипЗнч(Товар) <> Тип("Неопределено")) и (Товар.Наименование = "Тест") Тогда
Сообщить("Ок!");
КонецЕсли;
В описании функции "НайтиПоКоду" написано, что:Если (ТипЗнч(Товар) <> Тип("Неопределено")) и (Товар.Наименование = "Тест") Тогда
Сообщить("Ок!");
КонецЕсли;
Если не существует ни одного элемента с требуемым кодом, то будет возвращена пустая ссылка.Если честно, я не смог сразу добиться от функции возврата значения "Неопределено" и потому поступил проще:
Если код не задан, то будет возвращено Неопределено.
//Товар = Справочники.Номенклатура.НайтиПоКоду("00000026");
Товар = Неопределено;
Если (ТипЗнч(Товар) <> Тип("Неопределено")) и (Товар.Наименование = "Тест") Тогда
Сообщить("Ок!");
КонецЕсли;
Товар = Неопределено;
Если (ТипЗнч(Товар) <> Тип("Неопределено")) и (Товар.Наименование = "Тест") Тогда
Сообщить("Ок!");
КонецЕсли;
Запускаем - бинго! Все работает, несмотря на то, что в составном условии есть обращение к реквизиту переменной "Товар", к "Наименованию", которого у типа "Неопределено" быть не может.
Для проверки попробуем еще два вида условия:
1.
Если (ТипЗнч(Товар) <> Тип("Неопределено")) или (Товар.Наименование = "Тест") Тогда
2. Если (Товар.Наименование = "Тест") и (ТипЗнч(Товар) <> Тип("Неопределено")) Тогда
В первом случае мы поменяли "и" на "или" в условии, во-втором поменяли местами части условия.
В обоих случая при попытке выполнения мы получили сообщение об ошибке. Почему? Потому, что "компилятор" вычисляя выражение условия идет от начала к концу по-очереди вычисляя каждое вложенное выражение (по правилам арифметики, ага) и как только становится очевидно, что все выражение имеет определнный результат (истина/ложь) вычисление прекращается.
В нашем случае, когда мы сначала проверяли не является ли переменная "Товар" неопределенной и получали "ложь" смысл проверки остальных операндов утрачивался, так как в операции "И" достаточно одному операнду быть ложным, чтобы остальные можно было не проверять - все-равно конечный результат будет "ложь".
В первом случае, когда мы заменили "и" на "или" необходимость проверять все выражение возникла снова так как в определении "или" говорится, что истинным должен быть "хотя бы один" из операндов. Но пытаясь вычислить второй операнд мы сталкивались с ошибкой о которой я писал выше.
Во-втором варианте, результат выполнения так же изменился, несмотря на то, что операция "и" коммутативная. Теперь проверка начинала выполняться с первого, заведомо провального операнда.
Такое поведение, на мой взгляд оправдано и удобно. Сравните, например с похожим кодом в платформе 7.7:
//Товар = СоздатьОбъект("Справочник.Номенклатура");
//Товар.НайтиПоКоду(" 52200");
Товар = ПолучитьПустоеЗначение();
Если (ПустоеЗначение(Товар) = 0) и (Товар.Наименование = "Тест") Тогда
Сообщить("Ок!");
КонецЕсли;
В отличии от 8.1 он не будет работать, так как проверяются все операнды операции "и", а второй у нас будет выдавать ошибку.//Товар.НайтиПоКоду(" 52200");
Товар = ПолучитьПустоеЗначение();
Если (ПустоеЗначение(Товар) = 0) и (Товар.Наименование = "Тест") Тогда
Сообщить("Ок!");
КонецЕсли;
Для платформы 7.7 пришлось бы применить следующее:
Если (ПустоеЗначение(Товар) = 0) Тогда
Если (Товар.Наименование = "Тест") Тогда
Сообщить("Ок!");
КонецЕсли;
КонецЕсли;
Что на мой взгляд является неоправданным (хоть и небольшим) усложнением структуры кода.Если (Товар.Наименование = "Тест") Тогда
Сообщить("Ок!");
КонецЕсли;
КонецЕсли;
Итак, теперь вы можете смело записывать в одно условие конструкции, которые могут вызывать ошибки, в случае расхождения типов, главное не забывайте предварять их проверками на соответствие.
Спасибо за внимание и хорошего вам кода!
среда, 18 августа 2010 г.
Сила слова
На секлабе очередная статья про ужасного хакера подозреваемого во взломе банка. Статья уныла, но каменты жгут:
Гость: гик 17.08.2010 10:00:06
Уж сколько раз твердили миру, что если подразумевается негативный смысл, то надо говорить "кракер" или "крекер", или "крэкер".
Как всегда всё опошлили, на этот благородное слово "хакер", теперь все думают, что "хакер" - это плохо.
Гость: 23404 17.08.2010 11:31:47
если подразумевается негативный смысл, то нужно говорить: "кракен".
вторник, 17 августа 2010 г.
среда, 4 августа 2010 г.
Сам я считаю, что дефектом многих существующих учебников по технологиям программирования (это относится не только к COM, но и вообще к любой технологии излагаемой в современных учебниках) является то, что они начинаются "сверху" - "вызовите Wizard, отметьте в нём... поставьте... Wizard сгенерировал вам код...". Но в учебнике очень невнятно объясняется, почему Wizard сгенерировал именно такой код! Что означают те или иные макросы по ходу текста, как выглядит и сам протокол к которому Wizard строит реализацию. Словом, современные учебники пытаются обучить сложению используя в качестве наглядного пособия калькулятор - а как калькулятор выполняет сложение учебник не объясняет. С моей точки зрения это - тяжелейший порок, поскольку квалификация программиста определяется прежде всего пониманием философии, основ, концепций. Писать реально работающие программы, конечно, нужно с применением соответствующих инструментов. Писать вручную - анахронизм, часто выдающий дремучесть программиста. Но и владея распрекрасным инструментом всё равно нужно знать, как работает механизм, потому, что в какой-то момент времени может обнаружиться ошибка в самом инструменте, произойти несчастливое стечение обстоятельств и параметров, а тогда программист должен суметь, пользуясь своим общим знанием, отыскать то самое место, в котором возникает ошибка и исправить её.(с) Михаил Безверхов, "Технология COM"
понедельник, 26 июля 2010 г.
1С 8.2: Отключить сортировку в динамическом списке
Все форумы в один голос утверждают, что нельзя отключить сортировку динамического списка, которая включается по щелчку по заголовку колонки.
И все-таки есть один трюк, который позволит отключить сортировку. Логика простая - если клик по заголовку сбивает сортировку, то просто отключите заголовки!
Конечно, это может оказаться неудобным решением, но для простых списков можно применять:
Номенклатура:
Тут и без заголовков очевидно какая колонка что означает.
Еще можно применить трикс и сделать элемент списка, который при вашей сортировке всегда будет вверху, а в его реквизиты записать название колонок, но имейте ввиду, что это все-равно, что чесать левое ухо левой рукой через жопу. Используйте возможности платформы по назначению и будет вам счастье!
И все-таки есть один трюк, который позволит отключить сортировку. Логика простая - если клик по заголовку сбивает сортировку, то просто отключите заголовки!
Конечно, это может оказаться неудобным решением, но для простых списков можно применять:
Номенклатура:
Тапки | 10 грн. |
Сапоги | 83 грн. |
Мокасины | 2000 грн. |
Тут и без заголовков очевидно какая колонка что означает.
Еще можно применить трикс и сделать элемент списка, который при вашей сортировке всегда будет вверху, а в его реквизиты записать название колонок, но имейте ввиду, что это все-равно, что чесать левое ухо левой рукой через жопу. Используйте возможности платформы по назначению и будет вам счастье!
воскресенье, 25 июля 2010 г.
Обработка проверки заполнения константы. Контекст превыше всего!
Сегодня речь пойдет о "модуле менеджера значения" у объекта "константа". Зачем нужен этот модуль и как его применять? Конечно, в нем можно описать произвольную экспортную процедуру или функцию и вызывать ее из любого места вашей конфигурации, получив предварительно модуль менеджера значения вашей константы. Вызов может выглядеть, например, так:
Но это не главное свойство этого модуля. Его самая приятная особенность в том, что в нем могуть быть описаны такие события, как:
Рассмотрим событие "ОбработкаПроверкиЗаполнения". Это очень хорошее событие, чтобы убедиться в том, что константа заполнена правильно. Синтакс-помощник сообщает нам об этом событии следующее:
Не слишком понятное описание (как на мой взгляд) запутывающее смысл вот этим "список проверяемых реквизитов". О каком списке идет речь, если у нас одна константа? Непонятно. Очевидно, что раздел просто дублирует описание аналогичного события модуля объекта справочника, где список действительно имеется.
Однако, не будем отвлекаться. Как же проверить значение и где его (это самое значение) взять? (Если честно, я несколько поломал над этим голову, и мне кажется, что момент не вполне очевидный для начинающих). Параметром оно не передается, объекта как бы и нет...
И тут необходимо применить один из основных способов, заложенных в саму парадигму платформы 8.2:
Сообщает нам значение, которое было установлено в константу, но еще не было записано в базу (для случая, когда проверка вызывается автоматически перед записью).
Этот же контекст используется для событий "ПередЗаписью" и "ПриЗаписи".
Теперь вы можете добавить свой обработчик, который поможет проверить верно ли заполнено значение константы. Я, например, таким образом реализовал проверку на возможность изменения базовой валюты, которую нельзя менять, если в регистрах денег уже есть движения, а так же запретил константе "ВалютаПоУмолчанию" быть пустой.
Так же следует помнить, что событие "ОбработкаПроверкиЗаполнения" вызывается автоматически только при интерактивном изменении константы на форме констант. Для контроля программного изменения лучше использовать событие "ПередЗаписью", которое вызывается в обоих случаях.
Спасибо за внимание и хорошего вам кода!
Константы.<ИмяВашейКонстанты>.СоздатьМенеджерЗначения().<ИмяВашейПроцедуры>();
Но это не главное свойство этого модуля. Его самая приятная особенность в том, что в нем могуть быть описаны такие события, как:
- ОбработкаПроверкиЗаполнения
- ПриЗаписи
- ПередЗаписью
Рассмотрим событие "ОбработкаПроверкиЗаполнения". Это очень хорошее событие, чтобы убедиться в том, что константа заполнена правильно. Синтакс-помощник сообщает нам об этом событии следующее:
Вызывается расширением формы при необходимости проверки заполнения реквизитов при записи в форме, а также при выполнении метода ПроверитьЗаполнение.И декларирует, что на вход функции-события передается два параметра: <Отказ> и <ПроверяемыеРеквизиты>.
Позволяет разработчику конфигурации самостоятельно реализовать проверку заполнения в обработчике события. При этом в обработчике можно полностью отказаться от системной обработки (очистив список проверяемых реквизитов), отказаться от проверки системой части реквизитов (выполнив проверку отдельных реквизитов особенным образом и исключив эти реквизиты из списка), а также добавить для проверки другие реквизиты, проверка которых не была указана.
Не слишком понятное описание (как на мой взгляд) запутывающее смысл вот этим "список проверяемых реквизитов". О каком списке идет речь, если у нас одна константа? Непонятно. Очевидно, что раздел просто дублирует описание аналогичного события модуля объекта справочника, где список действительно имеется.
Однако, не будем отвлекаться. Как же проверить значение и где его (это самое значение) взять? (Если честно, я несколько поломал над этим голову, и мне кажется, что момент не вполне очевидный для начинающих). Параметром оно не передается, объекта как бы и нет...
И тут необходимо применить один из основных способов, заложенных в саму парадигму платформы 8.2:
Нужно понять в каком именно контексте вызывается событие.
Синтакс-помощник говорит нам, что событие вызывается в контексте этого самого объекта "КонстантаМенеджерЗначения.<Имя константы>", а значит, можно предположить, что свойства и методы объекта будут доступны. И действительно, следующий код прекрасно работает:Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)
Сообщить(Значение);
КонецПроцедуры
Сообщить(Значение);
КонецПроцедуры
Сообщает нам значение, которое было установлено в константу, но еще не было записано в базу (для случая, когда проверка вызывается автоматически перед записью).
Этот же контекст используется для событий "ПередЗаписью" и "ПриЗаписи".
Теперь вы можете добавить свой обработчик, который поможет проверить верно ли заполнено значение константы. Я, например, таким образом реализовал проверку на возможность изменения базовой валюты, которую нельзя менять, если в регистрах денег уже есть движения, а так же запретил константе "ВалютаПоУмолчанию" быть пустой.
Так же следует помнить, что событие "ОбработкаПроверкиЗаполнения" вызывается автоматически только при интерактивном изменении константы на форме констант. Для контроля программного изменения лучше использовать событие "ПередЗаписью", которое вызывается в обоих случаях.
Спасибо за внимание и хорошего вам кода!
среда, 21 июля 2010 г.
вторник, 20 июля 2010 г.
Софт за 24 часа
Зарегистрировался на флешмобном сумасшествии "Софт за 24 часа". Интересно, что из этого всего получится?
воскресенье, 18 июля 2010 г.
Quick sort
Сегодня разминки ради скрипел мозгами над quick sort.
Накнопал вот такой прекрасный код:
Поразительно, как раньше люди писали программы, когда языки не поддерживали красивую структурную вложенность? Я сломал себе половину мозга, пока реализовал это несложный алгоритм.
Отдельной проблемой было представить как правильно реализовать рекурсию без такой структуры, как "функция". Пришлось писать "болванку" с переходами для того, чтобы представить себе как рекурсивная функция должна вызываться из двух мест собственного тела.
Естественно, столкнулся с проблемой "кочующей переменной", когда значение переменной приходит из другой части кода, а не должно (забыл положить в стек L).
Опять-таки непонятно как вообще можно написать что-то существенное на языке, который не поддерживает действительноДлинныеИменаПеременных.
Вы спросите, для чего же было терпеть все эти мучения и ужас? Дело в том, что мне нужно реализовать это на ассемблере. Думаю, что такой код теперь не сложно будет перевести.
Накнопал вот такой прекрасный код:
#include <iostream> #include <stack> using namespace std; void showM(int M[], int l, int r) { for(int i = l; i <= r; i++) cout << M[i] << " "; cout << endl; } int main () { const int size = 8; int M[size] = {1,5,2,4,5,3,2,0}; stack<int> st; int ret_flag = 0; int t; int s = 0, e = size-1; showM(M, 0, size-1); func: int l = s, r = e; int G = M[l]; m3: while (M[l]<G) l++; while (M[r]>G) r--; if (l>r) goto m4; t=M[l]; M[l]=M[r];M[r]=t; l++;r--; goto m3; m4: if(l<=r) goto m5; if(M[r]<=M[l]) goto m5; t=M[l]; M[l]=M[r]; M[r]=t; m5: if(l-s <= 1) goto m1; st.push(s); st.push(e); st.push(l); st.push(ret_flag); ret_flag = 2; e = l-1; goto func; ret2: ret_flag=st.top();st.pop();l=st.top();st.pop();e=st.top();st.pop();s=st.top();st.pop(); m1: if(e-l < 1) goto m2; st.push(s); st.push(e); st.push(l); st.push(ret_flag); ret_flag = 1; s = l; goto func; ret1: ret_flag=st.top();st.pop();l=st.top();st.pop();e=st.top();st.pop();s=st.top();st.pop(); m2: if(ret_flag==1) goto ret1; if(ret_flag==2) goto ret2; showM(M, 0, size-1); return 0; }
Поразительно, как раньше люди писали программы, когда языки не поддерживали красивую структурную вложенность? Я сломал себе половину мозга, пока реализовал это несложный алгоритм.
Отдельной проблемой было представить как правильно реализовать рекурсию без такой структуры, как "функция". Пришлось писать "болванку" с переходами для того, чтобы представить себе как рекурсивная функция должна вызываться из двух мест собственного тела.
Естественно, столкнулся с проблемой "кочующей переменной", когда значение переменной приходит из другой части кода, а не должно (забыл положить в стек L).
Опять-таки непонятно как вообще можно написать что-то существенное на языке, который не поддерживает действительноДлинныеИменаПеременных.
Вы спросите, для чего же было терпеть все эти мучения и ужас? Дело в том, что мне нужно реализовать это на ассемблере. Думаю, что такой код теперь не сложно будет перевести.
Визуализаторы базовых алгоритмов
Хотите посмотреть как мне удалось обойти самого графа?
Смотрите подробное описание каждого шага с картинками!
Там ещё много забавного и увлекательного.
Смотрите подробное описание каждого шага с картинками!
Там ещё много забавного и увлекательного.
пятница, 16 июля 2010 г.
Конференция "Коктейль для менеджера"
Александр Орлов и Макс Дорофеев в одном флаконе.
Любопытно, что же будет?
Подробности на http://www.dev-labs.ru/
Любопытно, что же будет?
Подробности на http://www.dev-labs.ru/
четверг, 15 июля 2010 г.
Тренинг "Разработка через тестирование"
Компания SCRUMguids приглашает на тренинг "Разработка через тестирование".
Думаю, будет интересно.
Думаю, будет интересно.
вторник, 13 июля 2010 г.
понедельник, 5 июля 2010 г.
Мифический человеко-месяц
Забавно читать книги по IT написанные 35 лет назад. Многое изменилось на противополженое.
Например, о выпуске релизов:
Леман и Белади дают свидетельства в пользу того, что квант изменений должен быть либо очень большим и редким, либо очень маленьким и частым. (Lehman M., Belady L. Programming system dynamics, oct.1971) Последняя стратегия, согласно их модели, больше подвержена неустойчивости. Мой опыт это подтверждает: я никогда не рискну использовать ее на практике.
Или о диаграммах ПЕРТ:
Подготовка диаграммы ПЕРТ есть самая ценная часть ее применения. ... Первая диаграмма всегда ужасна, и для создания второй приходится проявить много изобретательности.
В последнем удивительно еще и то, что сам Брукс говорит о том, что такие диаграммы полезны. Но читать это можно очень по-разному.
Например, о выпуске релизов:
Леман и Белади дают свидетельства в пользу того, что квант изменений должен быть либо очень большим и редким, либо очень маленьким и частым. (Lehman M., Belady L. Programming system dynamics, oct.1971) Последняя стратегия, согласно их модели, больше подвержена неустойчивости. Мой опыт это подтверждает: я никогда не рискну использовать ее на практике.
Или о диаграммах ПЕРТ:
Подготовка диаграммы ПЕРТ есть самая ценная часть ее применения. ... Первая диаграмма всегда ужасна, и для создания второй приходится проявить много изобретательности.
В последнем удивительно еще и то, что сам Брукс говорит о том, что такие диаграммы полезны. Но читать это можно очень по-разному.
среда, 30 июня 2010 г.
Подписаться на:
Сообщения (Atom)