![]() |
|
Модераторы: Daevaorn |
![]()
|
|
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
Вопрос не связан с синтаксисом. Как бросать исключения, хватать их, порождать иерархию все это давным давно, на автомате и руки сами собою пишут.
Сейчас больше всего волнуют вопросы : культуры и проектирования. Вопрос №1: Стоит ли в библиотечном коде при бросании исключения указывать и текстовку ошибки? К чему вопрос и что смущает? Последнее время прихожу к тому, что текстовку ошибки должен формировать код приложения использующий библиотечный код, т.к. приложение может быть многоязычным и как вывод лучше всю работу с текстом сосредотачивать в одном месте, а не по разным участкам проекта. |
|||
|
||||
| bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
EvilsInterrupt, а теперь представь, что исключений в библиотеке 100 штук. Причем только 10 из них зависят от действий пользователя. Остальные 90 маловероятны. И ты будешь делать тексты для всех них? Если так, то ты мазохист.
Думаю, самый простой и портабельный способ - наследовать исключения от std::exception (или других его потомков, например: std::runtime_error) и сопровождать текстом. А в основном коде отлавливать ряд наиболее вероятных исключений и делать им локализацию. А остальным оставлять как есть. Есть более корректный метод - сразу позаботиться о локализации (см. Qt, boost, gettext) внутри библиотеки. |
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
>>Есть более корректный метод - сразу позаботиться о локализации (см. Qt, boost, gettext) внутри библиотеки.
И привязаться к какой-либо системе локализации? Так сказать гвоздями вколотить ? ) |
|||
|
||||
| Amp |
|
|||
|
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 886 Регистрация: 17.2.2009 Репутация: 3 Всего: 17 |
Можете свою систему локализации написать и прибить гвоздями |
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
Вопрос №2: Как правильно откатить изменения, так сказать "транзакционность входных данных"? Мы все стремимся решить проблему и не создавать новых. Но не всегда такое возможно. К примеру нужно сделать изменения во входном файле и допустим пользователь подал слишком большой. Если создавать копию чтобы сделать изменения, то это будет расточительным. Программист может решать что лучше открыть по чтению и записи, сделать изменения. Но в программе может быть брошено исключение, что может привести к порче входного файла. Как из таких ситуаций выкручиваться? Пока не вижу выхода, кроме как "хочешь транзакцинность, то создавай копию и с ней ковыряйся". Может еще есть способы? Причем не сильно дорогие в плане затрачиваемых человеко-часов. |
|||
|
||||
| bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
EvilsInterrupt, сначала проверяешь возможность выполнения операции, и только если она есть - выполняешь. Другое дело, аппаратный сбой. Тут никто не застрахован. И речь уже не идет о сохранности данных, модификация которых уже началась (тут поможет только копирование) - лишь бы побыстрее завершить работу и сообщить о проблеме пользователю.
|
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
bsa,
>>Другое дело, аппаратный сбой. Да именно от таких, когда "никто не застрахован" и хочется "подстраховаться". Пока это на дилетантском уровне: 1) Используется лог-файл, все пишется в лог-файл 2) Если нет возможности использовать копию входных данных, то logger.debug() - сообщения пишутся в лог не зависимо от уровня логирования, даже если выставлен critical, info или error |
|||
|
||||
| bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
EvilsInterrupt, если тебе необходима 100% надежность, то тогда используй копирование. Иначе, "забей" на аппаратные сбои.
|
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
Ок, переформулирую Вопрос №2 с учетом советов. Вопрос №3: Суть вопроса: Какие подходы существуют при обработке ошибок в приложениях с повышенными требованиями к надежности качеству? Другими словами : Как поступать в случае если приложение должно быть максимально живучим и требования к качеству очень высокие. К примеру приложение управляет ядерным реактором, вентиляцией легких у больного, банковское приложение, да мало ли. Я понимаю, что ответ на этот вопрос может занять книгу и т.д. Но почему-то уверен что основную и идею и принцип, так сказать brief можно вложить в абзац или в несколько абзацев. Интересует Используются ли бросание исключений в таких приложениях, что делается если обнаружена фатальная ошибка? |
|||
|
||||
| bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
EvilsInterrupt, у меня профильное образование в переводе не русский язык звучит как "автоматизация ядерных реакторов". Так вот. В этих случаях используют уже несколько иные системы. Горячий, теплый и/или холодный резерв. А так же аварийные системы защиты (например, "ножницы" для отрезания тросов, поднимающих графитовые стержни, в случае сбоя автоматики).
Ну ты сам подумай, что будет делать твоя программа, если произойдет сгорание процессора, матери, винта, сбой ОС наконец или сегфолт в самой программе? Если у тебя идет управление жизненно важным объектом, то в этом случае от исключений следует вообще отказаться. А систему писать уже с расчетом на то, что будет аппаратный дублер (горячий или теплый резерв), причем не один. |
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
Вопрос №4:
Как бороться с увеличением секции кода из catch-блоков ? При написании кода по обработке исключений в теле функции может вырасти кол-во catch-блоков. Если в коде метода вызывается 5 других методов, то это может потенциально привести к достаточно большому кол-ву блоков. Ведь в каждом из 5 вызванных может быть брошено 5 типов исключений, а в каждом из 5 методов также могут быть вызваны другие методы которые тоже могут бросать исключения. Пока нашел Паттерн Visitor для обработки иерархии исключений. |
|||
|
||||
| bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
EvilsInterrupt, а может стоит как-то пересмотреть архитектуру? У тебя что, на каждое исключение своя уникальная реакция предполагается? Скорее всего нет.
|
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
bsa,
Мои вопросы не связаны с каким-либо из моих проектов. Это вопросы понимание которых на уровне "каша в голове" ;) Связано с тем что пока при написании кода по обработке ошибок использую подходы "коды возврата" или "некорректное значение". Возвращаясь к последнему вопросу №4: >> а может стоит как-то пересмотреть архитектуру? Мне не стоит. Мне хочется понять, как писать код обработки ошибок использующий методику бросания исключений. Еще раз поясню что мне не понятно, возможно это пояснение будет более понятно: В книге Страуструпа сказано, что бросать надо тогда, когда ВЫ обнаружили ошибку, но ничего в этом конкретном месте с нею сделать не можете. Также сказано, что ловит исключение тот, кто Может и знает как отреагировать на появление этой ошибки. Из эти слов следует что вызывая в своем методе super_future() я не могу знать точно, а сколько в нем вызывается методов и ф-ций. Вполне возможно что там произойдет вызов одной функции или вообще ни одной, но ведь может и 500 и больше! Как вывод, в любом месте вызванного кода может быть брошено исключение, на который мой метод может отреагировать и вдруг этих "может" 20 ситуаций с брошенными исключениями. Это что? Означает что я вынужден писать 20 catch блоков ? |
|||
|
||||
| drug007 |
|
||||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: нет Всего: 1 |
Придерживаюсь подхода (особенно актуально для библиотек) что код должен вести себя как живая клетка - максимально сопротивляться негативным воздействиям извне и быть легко уязвимым для проблем внутри. Иными словами, приложение должно быть устойчиво, если пользователь вводит неверные данные и сразу же падать, если обнаружена внутренняя ошибка - как бы странно не показалось. Весь код снабжаю ассертами, исключения намного реже - хотя не уверен, что просто задача такая и позднее исключений будет хватать. Смысл ассертов в том, чтобы приложение упало как можно быстрее - т.е. я специально пытаюсь сломать свой код. В итоге приложение получается более устойчивым, так как многие баги выявляются на ранних этапах, а не при эксплуатации. А для надежности нужно дублировать приложения методом голосования - это более дешевый способ, так как все предусмотреть будет очень сложно и дорого и все равно не получится. Ну если только Вы на госкорпорацию какую-нить не работаете - тогда на создание такой уберсистемы можно очень даже денег попилить.
Обычно при защите важного кода через catch вы перехватываете то исключение, которое можете обработать на данном уровне. Остальные уходят выше. Вы же сами привели слова Страуструпа? Нет нужды обрабатывать все исключения, которые могут быть выброшены, только те, которые можете. Это сообщение отредактировал(а) drug007 - 16.1.2013, 13:35 |
||||
|
|||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
Прошу прочитать еще раз! Если именно на данном уровне летит 20 исключений от нижестоящих уровней и которые я могу обработать? Надо ли мне в этом методе писать 20 catch-блоков? |
|||
|
||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: нет Всего: 1 |
Ну если на данном уровне можно обработать 20 исключений, нужно их обрабатывать - у Страуструпа это же четко обозначено. Но если в одном месте вылетает 20 разнотипных исключений то это проблема архитектуры, но не самих исключений. Потому что это значит, что в кучу смешано слишком много разного. Смысл их собирать столько на одном уровне?
|
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
drug007,
Все еще продолжаем обсуждение Вопроса №4. К примеру пишется код использующий результаты методы класса парсера файла текстового формата, что может произойти? 1) Семантическая ошибка 2) Синтаксическая ошибка 3) Некорректный символ. К приму встретился не текстовой символ, такое может быть если файл не докачался 3) Ошибка ввода вывода, к примеру во время чтения файла или его открытия Построим дерево вызовов: 0) super_future(): 1) ....parser.parse() 2) ........semantic_something_doing() 3) ............syntax_something_doint() 4) ................read_char() 5) ....................open_file() 6) ....................read_file() Как в этом случае должна быть построен код по обработки ошибок? может быть брошено исключение на любом из 1-6 уровней. |
|||
|
||||
| bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
EvilsInterrupt, в данном случае почти все исключения должны иметь одного общего предка parse_error (метод what() возвращает текст с ошибкой и местом в файле). Таким образом, вышестоящий код (уровень 0 или меньше) перехватывает только его. Этому коду, если честно, сильно фиолетово, из-за чего не удалось распарсить файл (вряд ли он будет этот файл исправлять). Главное, что распарсить не удалось, а дальше уже пусть разбирается пользователь по представленной в ошибке информации.
А вот с ошибкой ввода/вывода сложней. Она к парсингу отношения не имеет и, по хорошему, ввод вывод следует осуществлять вне парсера. Но это неудобно. В итоге, тебе необходимо сделать только два catch блока, для отлова ошибок ввода/вывода и парсинга. В принципе, если действия будут одинаковыми, то может будет иметь смысл тупо ловить std::exception и все. |
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
bsa,
Ок. А что если ввести концептуальные уровни(не знаю как это проще назвать) попробую пояснить: Концептуальный уровень 1: уровни 1, 2, 3 - это уровни парсинга Концептуальный уровень 2: уровни 4, 5, 6 - это уровни ввода-вывода Тогда в коде любого из методов концептуального уровня 1 ловить брошенные исключения нижестоящего концептуального уровня 2 и бросать вместо них типы исключений относящиеся к концептуальному уровню 1. Тогда методу super_feature() достаточно будет ловить ошибки только одного концептуального уровня - парсинга. А о том что есть еще и ввода-вывода он и знать не будет. Другими словами появляются вертикальные слои, если смотреть на глубину вызовов как на направление сверху-вниз. Насколько такое применимо на практике? ЗЫ: Спрашиваю то что еще не применял на практике и это лишь мои догадки |
|||
|
||||
| bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
Имхо, вопрос не очень корректный. Это может быть применимо. Другое дело, что такое делать не стоит. |
|||
|
||||
| baldina |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3433 Регистрация: 5.12.2007 Где: Москва Репутация: 32 Всего: 101 |
разнотипных исключений нужно примерно столько, сколько требуется разных реакций на них. если почти все исключения приводят к сообщению об ошибке, то польза от уровней может быть только в детализации сообщений. скорее всего оно того не стоит |
|||
|
||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: нет Всего: 1 |
Это да, можно и больше. Но не для обработки же в одном месте! Хоть bsa и ответил уже, повторю его ответ 0) super_future(): <-------- ошибка парсинга 1) ....parser.parse() 2) ........semantic_something_doing() 3) ............syntax_something_doint() <-------- здесь только ошибка ввода-вывода 4) ................read_char() 5) ....................open_file() 6) ....................read_file() т.е. только два типа исключений мы можем здесь обработать. Более того, если это библиотека не факт, что их вообще нужно обрабатывать. Концептуальные уровни обязательно должны присутствовать - собственно разные класс, унаследованные от exception для этой цели и служат. Но обрабатывать исключение нужно только если действительно это нужно по бизнес-логике, например часть ошибки может устранена на текущем уровне, а остальное = на более высоком. Перехватывать просто с подменой класса исключения напрасная трата времени, имхо. В Вашем примере я бы перехватывал исключение ввода-вывода и выбрасывал взамен ошибку ввода-вывода только в том случае, если имя файла формируется внутри super_feature, если же параметры файла передаются извне, то и перехватывать не нужно. |
|||
|
||||
| baldina |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3433 Регистрация: 5.12.2007 Где: Москва Репутация: 32 Всего: 101 |
Тот, кто бросает исключение, не знает где и как оно будет обработано. Поэтому типов исключений много - для тонкой обработки. С точки зрения вызывающей стороны типов исключений (однотипных ситуаций, требующих обработки) обычно меньше. В общем случае будет несоответствие количества генерируемых и обрабатываемых типов исключений, и это решается наследованием типов исключений. Стратегия обработки сообщений может быть разной; вполне возможно и нормально для большой группы исключений иметь один обработчик. Без описания конкретной задачи и архитектуры сказать хорошо это или нет нельзя. |
|||
|
||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: нет Всего: 1 |
Трудно не согласиться. Но я бы в любом случае избегал бы такого развития событий, какая бы архитектура и задача не была. Если уж только legacy code заставляет, либо "тяп-ляп" сделать достаточно. |
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
Ок, спасибо за обсуждение по Вопросу №4.
Вопрос №5: От какого базового типа лучше всего создавать свою иерархию исключений в библиотеки? От своего или от std::exception и почему? Я видел проект, которые имел свой BaseException с методом where() в котором возвращались __LINE__, __FILE__, __FUNCTION__ . Но если честно, то на практике это не шибко может помочь, ведь если возникла ошибка, то можно поставить брякпоинт глядя на callstack в окне отладчика |
|||
|
||||
| bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
EvilsInterrupt, в любом случае лучше наследоваться от std::exception. Так как код, который будет использовать твою либу, может понятия не иметь о твоих исключениях. Более того, в линухе есть стандартный перехватчик исключений, который поддерживает метод what у std::exception.
|
|||
|
||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: нет Всего: 1 |
Люди писали проект, обрабатывали исключения-наследники std::exception и тут нашли Вашу замечательную библиотеку. А у нее своя иерархия исключений и, соответственно, чтобы их обрабатывать, им придется переписывать свой код и добавлять поддержку Ваших исключений. В их глазах это может оказаться серьезным аргументом против. |
|||
|
||||
| bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
Но если очень хочется использовать свою иерархию, то можно сделать свой базовый класс наследником std::exception. В этом случае, и волки сыты, и овцы целы.
|
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
Я придя в один проект увидел достаточно стройную библиотеку. Библиотека написана силами команды компании. Однако там свой собственный Exception класс, от которого наследуются все типы исключений. На вопрос: "Почему?" получаю ответ "Так исторически сложилось, лет 10 назад С++ был далеко не тем чем является сейчас. Более того даже STL был далеко от того состояния чтобы его можно было использовать в проектах". Но тут же добавили "Если бы подобный выбор стоял сейчас, конечно мы бы наследовались от std::exception". Но я не понимаю: Что в этом классе такого? Ведь есть риск смешать свои ошибки с ошибками стандартной библиотеки. |
|||
|
||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: нет Всего: 1 |
Каким образом Вы смешаете? Класс std::exception он один такой, другого такого класса нет. Или Вы имеете в виду, что перехватывая std::exception Вы "случайно" перехватите другое исключение? Так для этого и предназначена иерархия. А если Вам надо перехватить наследника std::exception, то в начале делаете перехват наследника, а следующим делаете перехват std::exception. В нем ничего нет особенного, кроме того что он наследник для всех остальных. Не забывайте, что если Вы забудете перехватить исключение, то программа просто упадет с непонятной ошибкой (если обработчик не установите - то это нужно понимать для чего вы так делаете), а наследуясь от std::exception вы гарантируете, что все исключения будут обработаны. |
|||
|
||||
| EvilsInterrupt |
|
|||
|
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 2 Всего: 9 |
Никак не могу понять : А почему? Что мешает моей либе кидать исключение от собственного базового класса исключений, а не от std::exception? Что им мешает написать очередной catch-блок ловящий мое custom_general_library_exception? На мой взгляд не важно на какую либу наткнулись на мою "замечательную" или на чью-то еще, программисту все равно не избежать интеллектуальной работы. Ведь программирование это же не слепое подключение библиотеки какой бы легкой она на первый взгляд не была. Как вывод и новый код работающий с этой найденной либой все равно надо писать. |
|||
|
||||
| bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 63 Всего: 196 |
EvilsInterrupt, хорошая библиотека отличается от плохой тем, что для ее использование нет необходимости изучать все детали ее работы, а достаточно узнать только ту часть, которая необходима именно тебе.
Вот ты сделал свое хитрое исключение в библиотеке. Другой программист взял ее и использовал в своем проекте. Через какое-то время пользователь сообщает, что программа валится без сообщений об ошибке раз в неделю... Программист проверяет код - все заключено внутрь try/catch. Поэтому, если бы была ошибка из-за исключения, то он бы ее отловил... Через несколько дней (недель, месяцев) потения, он случайно выясняет, что твоя либа кидает свое собственное исключение, которое не является потомком std::exception. Поверь мне, программист помянет тебя и может всех твоих предков... |
|||
|
||||
![]()
|
| Правила форума "С++:Общие вопросы" | |
|
|
Добро пожаловать!
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn |
| 0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
| 0 Пользователей: | |
| « Предыдущая тема | C/C++: Общие вопросы | Следующая тема » |
|
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |