Re[12]: Что вы предлагаете на замену эксепшенов?
От: Сергей Губанов Россия http://sergey-gubanov.livejournal.com/
Дата: 22.11.05 14:51
Оценка:
Здравствуйте, eao197, Вы писали:

E>Здравствуйте, Сергей Губанов, Вы писали:


E>Т.е. вот этот вызов:

СГ>>
СГ>>      this.НачалоЭксклюзивнойОперации;
СГ>>


E>гарантированно завершается успешно? Всегда, при любых условиях?


Да считаю. Это монитор — вход в эксклюзивный блок кода, обычная блокировка. Например, на C# было бы написано:
lock (this)
{
  ...
}

эквивалентное:
try
{
  System.Threading.Monitor.Enter(this); // this.НачалоЭксклюзивнойОперации;
  ...
}
finally
{
  System.Threading.Monitor.Exit(this) // this.КонецЭксклюзивнойОперации;
}
Re[11]: Что вы предлагаете на замену эксепшенов?
От: Cyberax Марс  
Дата: 22.11.05 15:04
Оценка:
Сергей Губанов wrote:

> S>Такими, которые обеспечивают ACID. Т.е. снятие/положение денег —

> некая сложная, но атомарная операция. Не обязательно приводить их
> полный код. Можно пользоваться ими как черными ящиками.
> Программа, я так понимаю, работает в банке со счета которого деньги
> снимаются.
>
> this.журнал.РапортОбУдачномОсуществленииДенежногоПеревода(this, счёт, сумма);
>
>
А если в этот момент выдернут сетевой кабель, и email не отправится? Или
запись в журнал из-за переполненого диска не произойдет?

--
С уважением,
Alex Besogonov (alexy@izh.com)
Posted via RSDN NNTP Server 1.9
Sapienti sat!
Re[11]: Что вы предлагаете на замену эксепшенов?
От: Sinclair Россия https://github.com/evilguest/
Дата: 22.11.05 16:33
Оценка: +7
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Программа, я так понимаю, работает в банке со счета которого деньги снимаются.
Прекрасно. Великолепный пример. Что мы из него видим?
1. Все функции обязаны возвращать булевый признак успешности выполнения.
2. Все функции обязаны передавать информацию о причине неудачи.
3. Если нам надо что-то большее, чем текст, в качестве информации о причине неудачи, то мы встряли. Потому что сигнатуры всех функций, имевших несчастье попасть в стек вызовов между тем местом, где мы обрабатываем ситуацию, и тем местом, где она произошла, должны быть модифицированы для передачи этой информации. Что-то более-менее общее мы использовать не сможем.
Более того, у нас нет никакого способа комбинировать обработку, и с ростом количества потенциальных неудач у нас нарастает объем кода, посвященного обработке. Если бы мы обрабатывали исключения, то все было бы сделано одной строчкой.

Вот в приведенном примере — что делать, если журнал не смог записать успешную запись в файл? О да, в данном случае он отправит письмо "внутри". Потому, что он инкапсулирует как запись протокола, так и сообщения об ошибках. А если бы мы разделили эту функциональность (что, в общем-то, более правильно, т.к. она ортогональная), то в коде пришлось бы отдельно проверять, удалось ли в провести запись в файл.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Что вы предлагаете на замену эксепшенов?
От: EvilChild Ниоткуда  
Дата: 22.11.05 19:00
Оценка: 25 (2)
Здравствуйте, eao197, Вы писали:

E>Стоит, безусловно. А что именно? Если можно URL, т.к. у нас достать печатное издание крайне проблематично.


Object Oriented Software Construction
Re[4]: Что вы предлагаете на замену эксепшенов?
От: iZEN СССР  
Дата: 22.11.05 20:36
Оценка: 63 (5)
Здравствуйте, eao197, Вы писали:

E>>>А вообще, кроме классика Н.Вирта, есть еще и классик Б.Страуструп. В его книге "Язык программирования С++. 3-е издание" есть целая глава #14, которая посвящена обработке исключений. Тебе бы не мешало прочитать из нее хотя бы 14.1 (Обработка ошибок [except.error]) и 14.1.1 (Альтернативный взгляд на исключения [except.views]). Там очень сжато и точно описываются разные стратегии диагностики ошибок и роль в этом деле исключений.

E>>>Note. Названия разделов я привел из англиского варианта книги, т.к. русский вариант у меня на работе.
iZEN>>Ещё есть один классик т.н. "контрактоного программирования" (кстати, последователь Н.Вирта, сейчас работает в ETH) — Бертран Мейер, автор языка Eiffel.
iZEN>>Стоит почитать и его.
E>Стоит, безусловно. А что именно? Если можно URL, т.к. у нас достать печатное издание крайне проблематично.
"Объектно-ориентированное конструирование программных систем", Бертран Мейер, изд. Русская редакция, 2005, 1232 стр., ISBN: 5-7502-0255-0

Несколько определений и правил из главы "Когда контракт нарушается: обработка исключений".

Определения

Далее приводятся примеры. Как не следует обрабатывать исключения в языках Си (сигналы в UNIX) и Ада — рассмотрены тривиальные ошибки программиста.
<...>

Принципы дисциплинированной обработки исключений

Ключевые концепции

E>Я упомянул разделы 14.1 и 14.1.1 книги Страуструпа потому, что там описываются следующие стратегии поведения в случае возникновения ошибок:

E> E>и приводится сравнение этих вариантов с вариантом использования исключений.

E>Ну и еще одно соображение. Исключения, в отличии от того же контрактного программирования, стали широко призанным и адаптированным механизмом. C++, Java, C#, Python, Ruby, Ada, Smalltalk -- везде есть исключения. Конечно, это не панацея от всех болезней, и применять их нужно осторожно. Тем не менее, вряд ли идея исключений получила бы такое широкое распространение, если бы она не была настолько удачной.

Как видите, Бертран Мейер имеет ясное представление об исключениях, и эта тема — одна из важнейших в контрактном проектировании/программировании. Он ни в коей мере не отрицает возможность и нужность исключений, наоборот — чётко формализует и внедряет в процесс проектирования ПО в виде неотъемлемой части.
Поэтому исключения нужно применять не "осторожно", как вы предлагаете, а просто-напросто ГРАМОТНО работать с ними. Это целая "субархитектура" (по аналогии с суб-культурой) в основании любой программной системы, и игнорировать её нельзя.
Re[3]: Что вы предлагаете на замену эксепшенов?
От: reductor  
Дата: 22.11.05 21:43
Оценка: 12 (2)
ГА>А можно маленький примерчик применения CPS вместо исключений?

Да, конечно.
вот здесь подробное описание того, что в Common Lisp: http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html
С очень понятным объяснением, я считаю. И в сравнении с явой и питоном. Умру, но лучше не напишу.


А в Хаскеле — например, самый, наверное, простейший вариант:
module Main where

import IO

main :: IO () -- Это комментарий
main = let processError e = -- Обработчик ошибки I/O. Типизируется как :: IOError -> IO ()
               putStrLn . show e
       in doIO "Who the hell are you? " `catch` processError -- Передача action'a и обработчика конструтору catch
    where
        doIO out = do -- Обернутый I/O action
            putStrLn out >> hFlush stdout
            str <- getLine
            putStrLn ("Nice to meet you, " ++ str)


Я постарался оформить это все попонятнее с точки зрения демонстрации именно CP-подхода, но вообще конечно в хаскеле все эти синтаксические условности ничего не значат. Вот, что на самом деле происходит (делает то же самое, но выглядит ближе к лямбда-нотации, может быть менее, или, наоборот — более понятно):

main :: IO ()
main = catch (do putStrLn "Who the hell are you? " >> hFlush stdout
                 getLine >>= putStrLn . (++) "Nice to meet you, ")
             (\e -> putStrLn $ show e)


Функция-конструктор catch оборачивает экшены ввода-вывода в вариантный тип и в зависимости от его значения вызывается соответствущий обработчик.
Это в принципе более всего похоже на обычные иксепшены (за тем исключением, что обернутый action и обработчик могут общаться друг с другом напрямую за спиной catch, т.к сами — обычные функции и стек остается целым) и наверное самый простой способ отделить обработчик от основной логики.
+ несмотря на то, что ничто не мешает создать самому сколь угодно навороченную систему обработки, модуль Control.Exception определяет огромное количество готовых примитивов:
http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Exception.html
Re[3]: Что вы предлагаете на замену эксепшенов?
От: Дарней Россия  
Дата: 23.11.05 04:04
Оценка:
Здравствуйте, Cyberax, Вы писали:

C>Такие идеи пробовались в 70-80х годах. Оказалось, что корректно

C>обрабатывать retry'и достаточно сложно, и почти никогда не нужно. А
C>когда все же действительно нужно, то проще явный while поставить.

например, у меня несколько раз возникала такая необходимость: если операция не удалась, то вызвать функцию, которая запрашивает юзера "retry, abort, skip?". Ну и далее действовать по результатам ответа.
Действительно — в С++ остается только завернуть операцию в цикл, который прерывается по ответу "skip" и по благополучному завершению операции, или кидается исключением по ответу "abort".
Проблема в том, что таких мест в программе десятки, а код цикла с проверками получается довольно-таки объемистый — нужно каким-то образом использовать его повторно. Вопрос только, как?
Делегатов в С++ нет, а каждой операции нужно передавать набор аргументов, и получать обратно результат. Использовать boost по некоторым причинам низзя.
Ваши варианты, как решить эту проблему? (лучшее, до чего я тогда додумался — это написать большой страшнючий макрос, которому нужная операция передается как аргумент)
... << RSDN@Home 1.1.4 stable rev. 510>>
Всех излечит, исцелит
добрый Ctrl+Alt+Delete
Re[4]: Что вы предлагаете на замену эксепшенов?
От: Павел Кузнецов  
Дата: 23.11.05 04:08
Оценка:
Дарней,

> Делегатов в С++ нет, а каждой операции нужно передавать набор аргументов, и получать обратно результат. Использовать boost по некоторым причинам низзя. Ваши варианты, как решить эту проблему?


Boost -- не единственное, и даже не первое место, где предоставлена функциональность аналогов boost::function на C++. Более того, для этой частной задачи реализовать это добро "ручками" совершенно несложно и недолго.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[5]: Что вы предлагаете на замену эксепшенов?
От: Дарней Россия  
Дата: 23.11.05 05:16
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>Более того, для этой частной задачи реализовать это добро "ручками" совершенно несложно и недолго.


несложно и недолго — если компилятор нормальный
... << RSDN@Home 1.1.4 stable rev. 510>>
Всех излечит, исцелит
добрый Ctrl+Alt+Delete
Re[5]: Что вы предлагаете на замену эксепшенов?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 23.11.05 05:53
Оценка:
Здравствуйте, iZEN, Вы писали:

E>>Стоит, безусловно. А что именно? Если можно URL, т.к. у нас достать печатное издание крайне проблематично.

ZEN>"Объектно-ориентированное конструирование программных систем", Бертран Мейер, изд. Русская редакция, 2005, 1232 стр., ISBN: 5-7502-0255-0

ZEN>Несколько определений и правил из главы "Когда контракт нарушается: обработка исключений".


Бегло просмотрел эту главу. Мне показалось, что у Страуструпа про исключения написано более лаконично и практично.

E>>Ну и еще одно соображение. Исключения, в отличии от того же контрактного программирования, стали широко призанным и адаптированным механизмом. C++, Java, C#, Python, Ruby, Ada, Smalltalk -- везде есть исключения. Конечно, это не панацея от всех болезней, и применять их нужно осторожно. Тем не менее, вряд ли идея исключений получила бы такое широкое распространение, если бы она не была настолько удачной.

ZEN>Как видите, Бертран Мейер имеет ясное представление об исключениях,

и, как мне видится, собственное представление. Которое он и выразил в Eiffel

ZEN> и эта тема — одна из важнейших в контрактном проектировании/программировании. Он ни в коей мере не отрицает возможность и нужность исключений, наоборот — чётко формализует и внедряет в процесс проектирования ПО в виде неотъемлемой части.

ZEN>Поэтому исключения нужно применять не "осторожно", как вы предлагаете, а просто-напросто ГРАМОТНО работать с ними. Это целая "субархитектура" (по аналогии с суб-культурой) в основании любой программной системы, и игнорировать её нельзя.

Грамотно, по отношению к исключениям, имхо, это и есть осторожно. При проектировании библиотек во многих случаях есть выбор: как реализовать информирование пользователя библиотеки об ошибках -- с помощью исключений или кодов возврата. Иногда использование исключений может привести к сильнейшему геморрою при использовании библиотеки, т.к. придется каждый вызов заворачивать в try...catch (begin...rescue, do...requie...rescue и пр.). Иногда -- наоборот. Здесь важнейшую роль играют опыт, здравый смысл, интуиция и чувство меры проектировщика. ИМХО, конечно.
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[2]: Что вы предлагаете на замену эксепшенов?
От: Joker6413  
Дата: 23.11.05 08:16
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, Сергей Губанов, Вы писали:


PD>Я не собираюсь предлагать что-то вместо эксепшн, но вот идею их я бы расширил. Сейчас — это просто уведомление о чем-то. Я бы добавил в них возможность исправления, если, конечно, возможно.


PD>Происходит исключение. Выбрасывется экземпляр класса исключения. Это экземпляр получает доступ к классу/методу, где произошло исключение, анализирует причину и изменяет состояние класса, после чего, возможно, повторяет операцию, вызвавшую исключение.


Это будет круче множественного наследования! Имхо повысит энтропию системы не в разы, а на порядки!
Re[4]: Что вы предлагаете на замену эксепшенов?
От: Joker6413  
Дата: 23.11.05 08:19
Оценка:
Здравствуйте, Дарней, Вы писали:

Д>Здравствуйте, Cyberax, Вы писали:


C>>Такие идеи пробовались в 70-80х годах. Оказалось, что корректно

C>>обрабатывать retry'и достаточно сложно, и почти никогда не нужно. А
C>>когда все же действительно нужно, то проще явный while поставить.

Д>например, у меня несколько раз возникала такая необходимость: если операция не удалась, то вызвать функцию, которая запрашивает юзера "retry, abort, skip?". Ну и далее действовать по результатам ответа.

Д>Действительно — в С++ остается только завернуть операцию в цикл, который прерывается по ответу "skip" и по благополучному завершению операции, или кидается исключением по ответу "abort".
Д>Проблема в том, что таких мест в программе десятки, а код цикла с проверками получается довольно-таки объемистый — нужно каким-то образом использовать его повторно. Вопрос только, как?

С помощью декомпозиции и какой-то матери...

Д>Делегатов в С++ нет, а каждой операции нужно передавать набор аргументов, и получать обратно результат. Использовать boost по некоторым причинам низзя.


Делегат — это ведь тот же самый callback.
Re[5]: Что вы предлагаете на замену эксепшенов?
От: Дарней Россия  
Дата: 23.11.05 08:29
Оценка:
Здравствуйте, Joker6413, Вы писали:

J>С помощью декомпозиции и какой-то матери...


точно. особенно с помощью второго пункта

J>Делегат — это ведь тот же самый callback.


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

В общем, что я хотел сказать? Сама идея continuations кажется мне вполне интересной, хотя с помощью такой-то матери можно обойтись и без них
... << RSDN@Home 1.1.4 stable rev. 510>>
Всех излечит, исцелит
добрый Ctrl+Alt+Delete
Re[2]: error is not an exception
От: Владек Россия Github
Дата: 23.11.05 08:32
Оценка: -2
Здравствуйте, c-smile, Вы писали:

CS>Здравствуйте, Сергей Губанов, Вы писали:


СГ>>Я делю ошибки на две категории:


CS>Изначальный посыл неправильный.


CS>исключительная ситуация это есть нечто ожидаемое но исключительное.

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

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

То есть, мы хотели изменить Y-координату каретки, но также изменилась и X-координата — это не ошибка, это исключительная ситуация.
Майкл Джексон — наше всё!
Re[4]: Что вы предлагаете на замену эксепшенов?
От: Cyberax Марс  
Дата: 23.11.05 08:35
Оценка:
Здравствуйте, Дарней, Вы писали:

Д>например, у меня несколько раз возникала такая необходимость: если операция не удалась, то вызвать функцию, которая запрашивает юзера "retry, abort, skip?". Ну и далее действовать по результатам ответа.

Д>Действительно — в С++ остается только завернуть операцию в цикл, который прерывается по ответу "skip" и по благополучному завершению операции, или кидается исключением по ответу "abort".
Д>Проблема в том, что таких мест в программе десятки, а код цикла с проверками получается довольно-таки объемистый — нужно каким-то образом использовать его повторно. Вопрос только, как?
Примерно как у меня:
typedef boost::function<SomeResultType ()> SomeResultFunctor;
...

//Обернем функтор в графическую оболочку
SomeResultFunctor res = wrap_with_gui<SomeResultType>(m_hWnd,boost::bind(&MyClass::func,this),std_exception_translator();

SomeResultType val = res(); //Вызовет появление окна если все будет плохо

У меня такой же подход используется для организации future-переменных.

Д>Делегатов в С++ нет, а каждой операции нужно передавать набор аргументов, и получать обратно результат. Использовать boost по некоторым причинам низзя.

Ну так в простом варианте делегаты за вечер пишутся. Я бы просто взял Boost и художественно спер бы оттуда код
Sapienti sat!
Re[12]: Что вы предлагаете на замену эксепшенов?
От: Сергей Губанов Россия http://sergey-gubanov.livejournal.com/
Дата: 23.11.05 11:38
Оценка:
Здравствуйте, Cyberax, Вы писали:

C>А если в этот момент выдернут сетевой кабель, и email не отправится? Или

C>запись в журнал из-за переполненого диска не произойдет?

Объекту "ЛокальныйСчёт" нет до этого ни какого дела, он свою задачу выполнил на 100%. Всё остальное находится в компетенции объекта "Журнал".
Re[13]: Что вы предлагаете на замену эксепшенов?
От: Cyberax Марс  
Дата: 23.11.05 11:44
Оценка:
Сергей Губанов wrote:

> C>А если в этот момент выдернут сетевой кабель, и email не отправится?

> Или
> C>запись в журнал из-за переполненого диска не произойдет?
> Объекту "ЛокальныйСчёт" нет до этого ни какого дела, он свою задачу
> выполнил на 100%. Всё остальное находится в компетенции объекта "Журнал".

А как тогда реагировать на его ошибки? А они вполне воможны в данном случае.

Ваше решение — просто на них плюнуть, так как функиця
"РапортОбУдачномОсуществленииДенежногоПеревода" никак не может повлять
(кроме полного останова программы) на ход вызывающей функции.

--
С уважением,
Alex Besogonov (alexy@izh.com)
Posted via RSDN NNTP Server 1.9
Sapienti sat!
Re[13]: Что вы предлагаете на замену эксепшенов?
От: Privalov  
Дата: 23.11.05 11:53
Оценка:
Здравствуйте, Сергей Губанов, Вы писали:


СГ>Объекту "ЛокальныйСчёт" нет до этого ни какого дела, он свою задачу выполнил на 100%. Всё остальное находится в компетенции объекта "Журнал".


Так ведь если запись в журнал транзакций не прошла, состояние объекта "ЛокальныйСчет" восстановить требуется. Кто этим займется? Кстати, в действительности там все намного сложнее.
Re[12]: Что вы предлагаете на замену эксепшенов?
От: Сергей Губанов Россия http://sergey-gubanov.livejournal.com/
Дата: 23.11.05 12:09
Оценка:
Здравствуйте, Sinclair, Вы писали:

Отвечу в обратном порядке.

S>Вот в приведенном примере — что делать, если журнал не смог записать успешную запись в файл?


Что делать кому? Объекту "ЛокальныйСчёт" по этому поводу точно делать ни чего не надо, он свою задачу выполнил на 100%, а уж как там внутри объекта "Журнал" чего-то стряслось — не его дело.

S>Прекрасно. Великолепный пример. Что мы из него видим?

S>1. Все функции обязаны возвращать булевый признак успешности выполнения.
S>2. Все функции обязаны передавать информацию о причине неудачи.
S>3. Если нам надо что-то большее, чем текст, в качестве информации о причине неудачи, то мы встряли. Потому что сигнатуры всех функций, имевших несчастье попасть в стек вызовов между тем местом, где мы обрабатываем ситуацию, и тем местом, где она произошла, должны быть модифицированы для передачи этой информации. Что-то более-менее общее мы использовать не сможем.
S>Более того, у нас нет никакого способа комбинировать обработку, и с ростом количества потенциальных неудач у нас нарастает объем кода, посвященного обработке. Если бы мы обрабатывали исключения, то все было бы сделано одной строчкой.

Ничего подобного.
1) Не все процедуры обязаны возвращать булевый признак успешности. Например, операция рапорта о чём-то произошедшем, ничего не должна возвращать — это никому не интересно кроме неё самой.
2) Не все процедуры обязаны возвращать информацию о причине неудачи. Ну не получилось, что-то сделать — иногда интересно узнать почему, а иногда — причина по барабану.
3) С чего Вы взяли, что информация о неудаче есть обязательно текст? Можно возвращать текст, можно число, можно указатель на абстрактный объект, и т.д.

Вот, пример полиморфной информации об ошибке:
MODULE Errors;

  TYPE
    Error* = POINTER TO EXTENSIBLE RECORD
      msg*: ARRAY 256 OF CHAR;
    END;
  
    List* = POINTER TO RECORD
      head-: Error;
      tail-: List
    END;

  PROCEDURE NewError* (msg: ARRAY OF CHAR): Error;
    VAR e: Error;
  BEGIN 
    NEW(e); 
    e.msg := msg$; 
    RETURN e
  END NewError;

  PROCEDURE NewList* (head: Error; tail: List): List;
    VAR list: List;
  BEGIN 
    NEW(list); 
    list.head := head; 
    list.tail := tail;
    RETURN list
  END NewList;

END Errors.


Пользовательский модуль:
MODULE MyModule;

  IMPORT Errors;

  TYPE
    MyError* = POINTER TO EXTENSIBLE RECORD (Errors.Error)
      extendedParam: MyExtendedParam;
    END;

  PROCEDURE NewMyError* (s: ARRAY OF CHAR; p: MyExtendedParam): Error;
    VAR e: MyError;
  BEGIN 
    NEW(e); 
    e.msg := s$; 
    e.extendedParam := p;
    RETURN e
  END NewError;

  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

  PROCEDURE TryDoSmth* (...params.., VAR err: Errors.List): BOOLEAN;
  BEGIN

    ...

    IF обычнаяХерняСлучилась THEN
      err := Errors.NewList(Errors.NewError("обычнаяХерняСлучилась"), err);
      RETURN FALSE
    END;

    ...

    IF мояХерняСлучилась THEN
      err := Errors.NewList(NewMyError("мояХерняСлучилась"), err);
      RETURN FALSE
    END;

    ...

    RETURN TRUE
  END TryDoSmth;

  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

END MyModule.
Re[14]: Что вы предлагаете на замену эксепшенов?
От: Сергей Губанов Россия http://sergey-gubanov.livejournal.com/
Дата: 23.11.05 12:19
Оценка: -1
Здравствуйте, Privalov, Вы писали:

P>Так ведь если запись в журнал транзакций не прошла, состояние объекта "ЛокальныйСчет" восстановить требуется. Кто этим займется?


Ага, а удалённый банк надо попросить: "Пожалуйста, верните деньги обратно"...

P>Кстати, в действительности там все намного сложнее.


Ясное дело.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.