gifts2017

Способы написать действительно надежный код в 1Сv7.7

Опубликовал Александр Венгер (venger) в раздел Программирование - Практика программирования

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

«Вы преднамеренно строите свой код так, что ваша чувствительность к нечистоплотности делает большей вероятность того, что ваш код будет работать правильно.»

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

Джоэл Спольски ( http://www.joelonsoftware.com/articles/Wrong.html )


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

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

Стоит использовать в именах переменных префиксы, указывающие на признак «глобальности» переменной и тип данных, который будет использоваться для хранения в этой переменной. На подобии венгерской аннотации.

Например:
// Локальная строковая переменная процедуры или функции
стрМояПеременная = "Моя строка"; 
// Локальная переменная процедуры или функции для хранения дат
датЕщеПеременная = '08.08.08'; 
// Локальная переменная процедуры или функции для чисел
чслПростоПеременная = 5; 
// Глобальная переменная глобального модуля для справочника сотрудников
гл_спрСотрудники = СоздатьОбъект("Справочник.Сотрудники");
// Переменная не глобального модуля для справочника сотрудников
г_спрСотрудники = СоздатьОбъект("Справочник.Сотрудники");

Тут стоит обратить внимание на то, что, если имена глобальных переменных в глобальном и не глобальном модуле будут совпадать, то в не глобальном модуле будет использоваться именно не глобальная переменная, тоже про имена функций и процедур. А вот с локальными переменными функций и процедур несколько наоборот, если имя локальной переменной будет совпадать с глобальной, то локальная переменная будет использоваться только в том случае, если она явно объявлена оператором Перем, в остальных случаях будет использоваться глобальная переменная. Но привычка явно объявлять все локальные переменные процедуры или функции, например:
Перем МояПеременная; 

На самом деле не так хороша, как кажется на самом деле. Так как она несет в себе некоторые минусы в применении к 1С.

Например, если Вы явно объявите переменные и забудете их определить присвоением начальных значений, задав тем самым их тип, и начнете использовать их в выражениях или передаче параметров в функции и процедуры, то интерпретатор не сообщит Вам об этой ошибке и результат выполнения такого кода будет не совсем ожидаем Вами:
Процедура Выполнить()
      Перем чслПер, чслРез;
      чслРез = чслПер + 3;
      Сообщить("" + чслРез);
КонецПроцедуры


Это относится и к передаче переменных в параметры функций:
Функция ПриемПараметров1(чслПар1, чслПар2, чслПар3)
      Возврат чслПар1 + чслПар2 + чслПар3;
КонецФункции

Процедура Выполнить()
      Перем чслПер1;
	  
      //
      // Текст процедуры
      //
	  
      Сообщить(""+ПриемПараметров1(чслПер1, 2, 3));
КонецПроцедуры

А вот если не иметь привычки явно объявлять, а только определять присвоением, то в таком случае, при интерпретации Вам интерпретатор выдаст ошибку: «Переменная не определена (ИмяПеременной)», что позволит отловить такие вещи еще на этапе компиляции. Например:
 
 Процедура Выполнить()
      чслРез=0; чслПер1=0;
      чслРез = чслПер1 + чслПер2 + 3;
      Сообщить("" + чслРез);
КонецПроцедуры

Или:
Функция ПриемПараметров1(чслПар1, чслПар2, чслПар3)
      Возврат чслПар1 + чслПар2 + чслПар3;
КонецФункции

Процедура Выполнить()
	  чслПер1=0;
	  
	  //
	  // Текст процедуры
	  //
	  
	  Сообщить(""+ПриемПараметров1(чслПер1, чслПер2, 3));
КонецПроцедуры


Поэтому, я бы рекомендовал явно объявлять только глобальные переменные модулей, а локальные переменные процедур и функций, определять не явно в начале процедуры или функции начальными значениями:
Процедура Выполнить()
      // Объявления локальных переменных процедуры или функции
      стрМояПеременная = ""; // Строковая переменная
      датЕщеПеременная = '00.00.00'; // Переменная для хранения дат
      чслПростоПеременная = 0; // Переменная для чисел
      // Переменная для справочника сотрудников
      спрСотрудники = СоздатьОбъект("Справочник.Сотрудники"); 
      
      //
      // Операторы процедуры или функции
      //
КонецПроцедуры

Единственно, когда в таком случае, интерпретатор не выдаст Вам ошибку, если Вы без определения переменной присвоением начального значения запишете:
чслПер= чслПер+1; // Переменная не увеличится на 1, она останется пустой

Но, если в начале функции Вы будете иметь привычку присваивать начальные значения переменным, то Вы напишете:
чслПер=0;
//
// Операторы процедуры или функции
//
чслПер= чслПер+1; 

И получите ожидаемый результат, не тратя усилия на воспоминания о том, имеет эта переменная числовой тип и присваивали ли Вы ей начальное значение.

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

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

Так как при вызове процедур, по мимо динамической типизации переменных, еще и позволительно опускать не только параметры, которым заданы значения по умолчанию при определении функции, но и параметры без значений по умолчанию. Например, Вы вполне можете вызвать функцию таким образом и интерпретатор примет Ваш вызов ни слова не сказав:
Функция ПриемПараметров1(чслПар1, чслПар2, чслПар3)
      Возврат чслПар1 + чслПар2 + чслПар3;
КонецФункции

Процедура Выполнить()
      Сообщить(""+ПриемПараметров1( , 3, ));
КонецПроцедуры

Также существует нюанс при передаче параметров описанный в статье:

Занимательная передача параметров
http://infostart.ru/profile/20743/blogs/628/

Поэтому, принимая параметры процедур и функций, стоит проверять их типы и приводить к нужным типам принудительно. Например:
Функция ПриемПараметров1(чслПар1, чслПар2, чслПар3)
      чслПар1=Число(чслПар1);
      чслПар2=Число(чслПар2);
      чслПар3=Число(чслПар3);
      Возврат чслПар1+чслПар2+чслПар3;
КонецФункции

А для агрегатных типов:
Функция ПриемПараметров1(чслПар1, спрПар2)
      чслРез=1; // Результат: =1 – удачно, =0 – не удачно
      чслПар1=Число(чслПар1);
      Если ТипЗначения(спрПар2)<>11 Тогда // 11 – тип Справочник
             // Возможно еще вывод сообщения или другие действия
             чслРез=0;
             Возврат чслРез;
      Иначе
             Если спрПар2.Вид()<>"Сотрудники" Тогда
                    // Возможно еще вывод сообщения или другие действия
                    чслРез=0;
                    Возврат чслРез;
             КонецЕсли;  
      КонецЕсли;
	
      //
      // Операторы процедуры или функции
      //
	
      Возврат чслРез;
КонецФункции

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


См. также

Подписаться Добавить вознаграждение

Комментарии

1. Venger (venger) 10.10.08 02:53
Это по мотивам прочтения 2-5 глав описания языка:-)
2. Артур Аюханов (artbear) 10.10.08 07:25
Еще нужно не забыть создавать простой код, разделяя его на небольшие и процедуры/функции, у которых из названия понятно их назначение. Также очень желательно переменным давать внятные и понятные наименования.
.
Код, занимающий кучу экранов, как правило, очень сложен для понимания, прочтения и исправления. Но 1С это очень любит в своих типовых :(
motogon; lustin; JohnyDeath; lomok; noprogrammer; +5 Ответить 1
3. Lomok (lomok) 10.10.08 08:18
4. Lomok (lomok) 10.10.08 08:19
А так, плюс, идея хорошая.
5. VasilyKushnir (vasilykushnir) 10.10.08 09:06
(2) А еще:
- писать толковые комментарии
- обязательно писать в среде 1С (кода делфи 1с не понимает)
- и наконец - писать программы так, чтобы они работали.

Но шутки в сторону:
1. "А вот если не иметь привычки явно объявлять, а только определять присвоением, то в таком случае, при интерпретации Вам интерпретатор выдаст ошибку: «Переменная не определена (ИмяПеременной)», что позволит отловить такие вещи еще на этапе компиляции."
- очень и очень спорная технология. Ловить свои же сознательно встроенные в код ошибки? Я дико извиняюсь...
2. "Стоит использовать в именах переменных префиксы, указывающие на признак «глобальности» переменной и тип данных"
Этим пользуюсь. Даже больше: функциям и процедурам имена даю по такому же принципу:
ПриОткрытии () - локальная
глОбработкаВизуальности () - в глобальном модуле
дгм1ПарсерСЗ () - в дополнительном глобальном модуле №1
и т.д.
6. Андрей (vinsentfire) 10.10.08 11:38
Мне как начинающему осваивать 1С - это действительно очень важные вещи. Это как при начале строительства - как заложен фундамент, так дальше и будет идти строительство. Тут тоже самое. Одним словом - спасибо. Тем более, что и много специалистов делятся своим опытом, тоже можно многое чего узнать и избежать в дальнейшем ошибок.
7. Андрей Скляров (coder1cv8) 10.10.08 12:17
Никогда не объявляю переменные в начале процедуры/функции... ) Только по мере необходимости.
motogon; artbear; +2 Ответить 1
8. Артур Аюханов (artbear) 10.10.08 12:42
(7) Ага, нужно как в С++ - объявлять/инициализировать по мере необходимости.
Это намного более надежно.
9. Аркадий Кучер (Abadonna) 10.10.08 12:46
(5) Вась, твоя привычка объявлять по делу и не по делу ВСЕ функции и процедуры форвардом - ваще зубная боль :( И не лень тебе ;)
10. Аркадий Кучер (Abadonna) 10.10.08 12:49
+(9) Не фиг вообще в 1С объявлять переменные, кроме тех случаев, когда они являются принимающими, например
ТекущееВремя(Час, МИнут, Сек) - тут уж никуда не денешься
11. Venger (venger) 10.10.08 12:54
А вот по поводу глобальных процедур и функций в глобальном модуле, я все-таки стараюсь объявлять их все в начале модуля со словом Далее, это как документация по библиотеке функций, не надо долго выискивать в тексте глоб. модуля, какие функции там есть, чтоб понять, что можно применить и с какими параметрами и что делают. Все перед глазами в начале модуля, надо уточнить, имя из объявления скопировал, поиск запустил и спустился к определению, посмотрел, если нужно как она организована.
12. Venger (venger) 10.10.08 12:55
+(11) Ну и не паришся с порядком вызова. Это про глобальный модуль в основном я говорю.
13. Venger (venger) 10.10.08 12:59
Кстати, по поводу префиксов в именах функций и процедур, можно еще в префиксах задавать принадлежность к "классу", т.е., например, все функции и процедуры для работы с Excel через Ole в глоб. модуле, могут начинаться с префикса гл_xlsole, префикс в данном случае просто пример, можно получше придумать.
14. Venger (venger) 10.10.08 13:00
+(13) Главное в этом деле однообразие и последовательность в применении.
15. Аркадий Кучер (Abadonna) 10.10.08 13:14
Вот пример на мой взгляд совершенно ничем не оправданного объявления переменных внутри процедуры, ктр. строит конструктор запросов в 7-ке:
Перем Запрос, ТекстЗапроса, Таб;
Ну и толку от этого объявления?
16. Сhe Burashka (CheBurator) 10.10.08 13:30
(15) толк имхо есть - по "заголовку" процедуры и объявлениям переменных я уже пойму про что речь...
..
было бы хорошо, если бы автор систематизировал вопросы именования переменных...
у мну например последнее время в этом смысле разброд какой-то...
17. Venger (venger) 10.10.08 13:33
(15) На то он и конструктор. Кстати, а прикиньте лет так через 100, создадут исскуственный интеллект, который ведь сможет программировать. И будем мы наблюдать процесс программирования, как сейчас на подобии, когда два компа между собой в шахматы играют:-)
18. Venger (venger) 10.10.08 13:35
+(17) Хотя это уже будет саморазвитие и самосовершенствование компа, так он и до медитации может дойти:-)
19. Venger (venger) 10.10.08 13:36
+(17) Хотя до медитации, по-моему, уже дошел, вспомните Win98, зависания всякие и т.п.:-)
20. Аркадий Кучер (Abadonna) 10.10.08 13:38
(16) Смотри код от Камина. Там четко описаны переменные с префиксами типа л_МряПеременная и что префиксы означают.
21. Venger (venger) 10.10.08 13:39
(16) Так можно писать в начале процедуры или функции, например:
зпрПоСкладу=СоздатьОбъект("Запрос"); стрЗапроса=""; табВывод=СоздатьОбъект("Таблица");

И все видно...
22. Venger (venger) 10.10.08 13:40
23. Lomok (lomok) 10.10.08 13:48
Ссылку из 3 никто я так понимаю и не посмотрел :(
24. Аркадий Кучер (Abadonna) 10.10.08 13:48
А это самая что ни на есть суперская прога по расчету зарплаты на базе 7.7 рядом с которой ЗиК и делать не фиг
25. Venger (venger) 10.10.08 14:01
(23) Я просто отложил на вечер почитать, но почитаю непременно. Спасибо.
26. Venger (venger) 10.10.08 14:02
(24) Это про Камина? А эту прогу можно посмотреть?
27. Артур Аюханов (artbear) 10.10.08 14:25
(15)Цитата:
>>Вот пример на мой взгляд совершенно ничем не оправданного объявления переменных внутри процедуры, ктр. строит конструктор запросов в 7-ке:
>>Перем Запрос, ТекстЗапроса, Таб;
>>Ну и толку от этого объявления?
Для конструктора толк есть.
В этом случае тот текст, который он вставляет, 100% независим от остальных переменных модуля и ГМ, и не влияет на переменные модуля и ГМ.
Например, в модуле может быть переменная модуля ТекстЗапроса и т.д.
28. Аркадий Кучер (Abadonna) 10.10.08 14:26
(27) С точки зрения Конструктора - согласен:))) Тем более я сам по образованию инженер-конструктор;)
29. Алексей Коробов (WiseSnake) 11.10.08 18:56
(24) Абсолютно не согласен с Вами. Многие отказываются от Камина, слишком геморно там Все сделано. Хотя ЗиК тоже сложна для понимания пользователями, но работает и успешно... в отличии от камина... Ну и обслуживание....
30. Сhe Burashka (CheBurator) 11.10.08 19:22
Как пример, ваял отчет сегодня для древней конфы, в отчете юзаю множественный фильтр (из штатного ТиСа) - временно пришлось В МОДУЛЬ отчета передрать из ТиСа процедуры и функции и ГМ ТиСа - не работаю!!! Потому как на форме лежит ТаблицаМФ, а в процедуре, которая раньше была в ГМ выглядит вот так:
Процедура ЧтоТоДляМФ(Конт)
ТаблицаМФ = Конт.таблицаМФ;
в итоге имеем ошибку...
..а вот так
Процедура ЧтоТоДляМФ(Конт)
Перем ТаблицаМФ;
ТаблицаМФ = Конт.ТаблицаМФ;
- не ругается!!
31. AlexQC (alexqc) 15.10.08 17:37
Я категорически :) не согласен с тем что НЕ следует объявлять переменные в ф-циях. Наоборот, все локальные переменные НУЖНО объявлять! Сам бы так делал, да вот лень, лень (вот если б была допустима конструкция вида "Перем а=1+2;" в любом месте)... Поскольку в таком случае локальные переменные не пересекаются с переменными вышестоящего уровня (текущего модуля, текущего контекста и ГМ). В этом смысле мне кстати больше подход в PHP нравится: все переменные - локальные, а хочешь до глобальной достучаться - объявляй ее global явно.

Кстати, говорите интерпретатор ругнется? Попробуйте это
Код
 
Процедура ГдеТутРугань()
А=А+1;
КонецПроцедуры;
Показать полностью


По поводу префиксов - вешь конечно нужная, но злоупотреблять ими также не следует. Достаточно когда префикс отражает НАЗНАЧЕНИЕ переменной, а не тип.
Для себя я выработал примерно такую систему: то что в ГМ - всегда "гл". Флаги имеют префикс "ф", списки - "спис", таблицы значений - "тз". Строковые представления каких-либо значений - "стр", и т.п. Переменные в "локальной части" алгоритма (например, промежуточные результаты, вобщем то что будет использовано пределах ближайших нескольких строчек) - префикс "т". НО: все эти префиксы (коме префиксов "места" - "гл", "т") - диктуются не типом переменной, а необходимостью алгоритма. То есть, грубо говоря, если у меня есть строка, показывающая адрес, и строка, показывающая представление клиента - то в первом случае переменная скорее всего так и будет "Адрес", а во втором - "стрКлиент". И ИМХО, уж совсем изврат - префиксы перед числами. Вот только посмотрите: чслСумма=чслЦена*чслКоличество. Неужели красиво?
При этом, как ни странно, иногда и
32. AlexQC (alexqc) 15.10.08 17:48
** что-то сбилось, продолжу
иногда создается впечатление, что мои префиксы определяются типом (списКлиенты, стрКлиент, зпрКлиент). Но это не так - они определяются именно значением для алгоритма (а тип просто является "следствием" этого значения).
33. Venger (venger) 15.10.08 21:48
(31) > ...если б была допустима конструкция вида "Перем а=1+2;" в любом месте

Ну, если б...

> Поскольку в таком случае локальные переменные не пересекаются с переменными вышестоящего уровня (текущего модуля, текущего контекста и ГМ)

Префиксы исключают вазможность такого пересечения.

> Кстати, говорите интерпретатор ругнется? Попробуйте это
> Процедура ГдеТутРугань()
> А=А+1;
> КонецПроцедуры;

Прямо в блоге и говорю, что:
----------------------------------------------
Единственно, когда в таком случае, интерпретатор не выдаст Вам ошибку, если Вы без определения переменной присвоением начального значения запишете:
Код
чслПер= чслПер+1; // Переменная не увеличится на 1, она останется пустой
Показать полностью

Но, если в начале функции Вы будете иметь привычку присваивать начальные значения переменным, то Вы напишете:
Код
чслПер=0;
//
// Операторы процедуры или функции
//
чслПер= чслПер+1;
Показать полностью

И получите ожидаемый результат, не тратя усилия на воспоминания о том, имеет эта переменная числовой тип и присваивали ли Вы ей начальное значение.
----------------------------------------------

И что?

> По поводу префиксов - вешь конечно нужная, но злоупотреблять ими также не следует. Достаточно когда префикс отражает НАЗНАЧЕНИЕ переменной, а не тип.

Назначение можно передать и именем, но в принципе в 1С большинство перфиксов будут отражать тип (в общем случае я считаю, что "ф" - это тот же тип в каком-то смысле). Да и проще тогда ориентироваться, что это, особенно в чужом коде.
34. Maljaev (maljaev) 15.10.08 22:43
Мне не понравилось предложение прописывать в начале каждой переменной ее тип. Это сильно затруднит работу с телепатом, так как при наборе имени переменной он ищет ее по начальным символам. Соответственно вместо того чтобы вывалить список наиболее вероятных переменных через 2-3 введенных символа, я буду вынужден каждый раз вводить уже 5-6 символов, и хорошо еще если я помню тип переменной. Лучше уж тип переменной дописывать в конце, тогда при тех же достоинствах не будет описанных неудобств.
35. Сhe Burashka (CheBurator) 16.10.08 00:42
(34) я тоже так думал.. фиг там.. телепат ищет по последовательности в любом месте!
36. Сергей (Shagan) 16.10.08 07:10
37. Maljaev (maljaev) 16.10.08 08:42
(35) Я не знаю, какой там у тебя телепат стоит, лично у меня версии "t" и ни хрена подобного не происходит. Проверьте: в конфигураторе создаете пустую обработку, в модуле "сформировать" пишете:

датНачалоПериода="";
датКонецПериода="";
Если на...

И что вам телепат вывалит? Лично у меня в списке телепата отсутствует "датНачалоПериода", когда начинаешь набирать с "да" - присутствует.
38. rw.spb (Cifer) 16.10.08 14:50
Да, это все конечно интересно, особенно когда пишешь "с нуля". Но как же быть с внедрением типовых конфигураций? Большая часть кода значит будет написана по одним правилам, а новые участки кода по другим?
Впрочем с конфигурациями написанными "с нуля" тоже ведь придется разбираться людям, которые привыкли к общепринятым правилам написания кода 1С. Считаю, что к 1С такой подход неприменим, и нужно придерживаться одного стиля.
39. Venger (venger) 16.10.08 15:45
(38) А про общепринятые правила написания кода 1С можно поподробней?
40. Василий Демидов (Душелов) 16.10.08 15:46
(39) по-моему на ИТС-е было где-то...
41. rw.spb (Cifer) 17.10.08 12:05
ИТС -> 1С:Предприятие. Работаем с программами -> 1С. Система стандартов и методик разработки конфигураций для платформы 1С:Предприятие 8
42. nickVZ (nickVZ) 20.10.08 16:26
датНачалоПериода...
чслСумма....
стрАдресПрописки....

Не назвать ли нам кошку "Кошкой"?@стиш детский
43. Maljaev (maljaev) 20.10.08 17:03
(42) Ну да, иногда получается масло масляное... Но если уж вводить такую систему именования, то исключений быть не должно.
44. Venger (venger) 20.10.08 17:04
(42) В меру, в меру и там, где есть смысл:-)
45. Venger (venger) 20.10.08 17:06
(42) Просто в условиях когда среда не может сказать мне тип переменной во время программирования, особенно с чужим кодом это не удобно, когда работаешь, а и еще за меня проверять на соответствия типам переменные, то вполне даже стоит...
46. Аркадий Кучер (Abadonna) 27.10.08 09:17
(22)>А что за код от Камина?
Во! Нашел у себя систему обозначения переменных от Камина
// Система префиксов для именования объектов от фирмы КАМИН
// Гл_ - глобальная переменная, процедура или функция. Описана
// в глобальном модуле с ключевым словом ЭКСПОРТ.
// м_ - переменная, описанная явно или неявно в текущем программном модуле.
// л_ - переменная, описанная явно или неявно в текущей процедуре или функции.
// п_ - параметр текущей процедуры или функции.
// рд_ - реквизит диалога. Описан в форме диалога.
47. Алексей (ADirks) 27.10.08 14:13
И опять Джоэл Спольски: статья "Making Wrong Code Look Wrong"
http://www.joelonsoftware.com/articles/Wrong.html
По английски, но очень поучительно
48. Venger (venger) 30.10.08 23:40
Есть переводы многих его блогов и на русском:

http://local.joelonsoftware.com/mediawiki/index.php/Russian

Это Вам не fixin:-))))
49. Сhe Burashka (CheBurator) 30.10.08 23:50
рд_ - мало!
я типа так
рНастройка - рамка
рб - радиобатон
фл - флажок
т - текст
кн - кнопка
.. и т.д.
50. Сhe Burashka (CheBurator) 30.10.08 23:56
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа