Monday, December 21, 2009

Оптимистический и пессимистический взгляд на жизнь

Существует два кардинальных подхода к решению той или иной задачи. Я вкратце опишу суть, а затем проиллюстрирую на трёх разных примерах.

Итак, нужно выполнить какое-либо задание. Оно может быть как банальным, типа помыть пол, так и менее банальный, например, сдать экзамен или пройти интервью на работу.

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

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

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

Бытовые примеры пессимистического подхода. Для иллюстрации рассмотрим мытьё пола, хотя, предупреждаю, читать будет смешно. Итак, чтобы помыть пол нужно:
а) иметь ведро;
б) иметь тряпку;
в) иметь тёплую воду;
г) иметь моющее средство;

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

Чтобы проверить наличие ведра, нужно пойти сначала в то место, куда его ставили в последний раз, если его там нет, в то место, где его обычно ставят и т.д. Аналогичны с проверкой тряпки и моющего средства. Проверку воды осуществить нужно следующий образом:
1. Пойти в ванную и открыть краны с холодной водой.
2. Если вода нет, пойти в кухню и проверить там кран с холодной водой.
3. Если и там нет, спросить у соседей, есть ли у них вода.
...
Думаю, продолжать смысла особого не имеет.

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

Рассмотрим другой пример, пройти интервью для устройства на работу. Нужно "освежить" в памяти знания, прочитав что-нибудь по работе. Это не обязана быть книга, это могут быть даже свои какие-то заметки. Поспрашивать друзей\знакомы\в интернете, какого типа вопросы "модны" сейчас. Попытаться найти кого-то кто работал\работает именно на том месте. Подучить темы, которые часто спрашивает или в которых плаваешь, подумать, что ответить на всякие каверзные вопросы отдела кадров, типа "кем ты хочешь стать через 10 лет?".

Математический пример оптимистического подхода. Вы занимаетесь исследованием в какой-то области в математике. После долго усилий, вам удалось сформулировать некую гипотезу, теперь вы ищете как её можно доказать. Оптимистическим подходом будет поиск доказательства "в лоб". Например, нам дано вот это, из этого следует то-то, а из этого то-то... Так можно "случайно" найти и то, что требуется доказать. Это в каком-то мере похоже, на решение типичной задачи в школе. Берётся, что дано, крутится-вертится, пока не получим результат. Разница в том, что в школе мы знаем, что за чем нам нужно вычислять, а тут мы должны испробовать много различных путей.

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

Пример пессимистического подхода в программировании номер один - пример со Thread-ами. Для простоты, у нас одноядерный процессор, на котором мы хотим, чтобы бежали несколько потоков. Классическая стратегия тут, состоит в использовании "критических секций", блоков кода, в котором от начала блока до конца бежит только один Thread (используется Mutex, Semaphore; Lock или synchronized в Java). При этом подходе мы предполагаем худшее (если мы не закроем дверь, придут гремлины и всё переставят) и не двигаемся пока мы не можем гарантировать, прося соответствующий lock, что другие Thread-ы не будут нам мешать.

Пример оптимистичего подхода в программировании номер один - пример со Thread-ами. В последнее время внимание исследований в области параллельных алгоритмов направлено на non-blocking алгоритм, который использует низкоуровневые атомные машинные инструкции такие как compare-and-set вместо lock-ов, чтобы обеспечить data integrity при параллельном доступе. При этом подходе мы делаем изменение, надеясь, что мы можем их завершит без помех. Этот подход опирается на collision detection чтобы определить была ли помеха от других участников во время изменения. В этом случае операция терпит неудачу (fails) и может быть повторена (или нет, тут могу быть использованы разнообразные стратегии). Этот подход похож на высказывание "Легче получить прощение, чем разрешение" (permission), где "легче" обозначает "эффективней".

Пример пессимистического подхода в программировании номер два - Lock в базе данных. Вы работаете с таблицей в базе данных. Типичное использование следующее - делается select for update из таблицы, результат показывается на экране. Затем пользователь, попивая кофе, делает какие-либо изменения на экране и сохраняет их. Мы хотим избежать, среди прочего, следующего сценария. Другой пользователь, параллельно с первым заходит на тот же экран и получает те же (или частично те же) данные. Первый пользователь сохраняет свои изменения, второй пользователь не зная об этом продолжает вносит свои изменения. Затем он сохраняет их. Здесь может случится всё что угодно, от того, что изменения первого пользователя будут затёрты до того, что последние изменения не удастся сохранить, так как не хватает каких либо данных в базе данных (они были стёрты первым пользователем), в то время когда второй пользователь опирается на них (ведь он, видел их на своём экране). Пессимистический подход состоит в том, чтобы как только первый пользователь начал вносить изменения, строки становится locked на уровне базы данных, так что никто другой не может их менять. Другие пользователи должны ждать пока эти строки будут unlocked, т.е. пока первый пользователь завершить вносить изменения. Если другие пользователи попытаются сохранить свои изменения то база данных не даст их сохранит, аппликация получит Exception и пользователь получит сообщение об ошибке.

Пример оптимистического подхода в программировании номер два - Lock в базе данных. Альтернативный подход к решению данной проблемы состоит в следующем... Мы добавляем в таблицу специальное поле LOCK_ID как часть UNIQUE ID. Типичное использование следующее - делается обычный select из таблицы, в том числе и LOCK_ID берётся, результат, возможно, без LOCK_ID, показывается на экране. Затем пользователь, попивая кофе, делает какие-либо изменения на экране и сохраняет их. Если за это время никто другой не вносил изменения, то это в частности значит, что LOCK_ID тоже не изменился и мы сможет сделать commit. Если другой пользователь успел прочитать данные, внести изменения, сохранить, сделать commit, то в базе данных LOCK_ID будет уже другой. В таком случае, когда первый пользователь попытается сохранить свои изменения и дойдёт до commit, база данных не даст ему это сделать из-за Violation of UNIQUE ID.


И последний пример, пример номер три, заранее извиняюсь за его длину.

Вы пишете J2EE application. У вас есть front-end, грубо говоря UI, и back-end, грубо говоря business logic + persistence model. У front-end есть замечательный "глобальный" (per session) объект, называемый MessageContiner. Это, по-сути Collecion, который UI умеет красиво показать юзеры. MessageContiner хранить Message-ы - сообщения, которые мы хотим показать юзеру. Эти Message-ы могут генерироваться в том числе и back-end. Например, юзер заполняет какие-либо данные и мы их пытаемся сохранить в базе данных. Перед сохранением persistence model может сделать проверки, все ли NOT NULL атрибуты заполнены. Более того, так как мы не хотим мучать юзера говоря ему не заполнено A, потом не заполнено B, мы ему возвращаем сразу несколько Message-й 1) не заполнено A; 2) не заполнено B и т.д. Back-end знает объект Message и объект MessageContainer. Далее, есть следующий момент. Сами Message-ы мы храним в файлах properties. Т.е. у нас есть файл propertie, где написано, что-то вроде

isNull не заполнено {0}

Сделано это среди прочего для того, чтобы в коде не было кириллицы, а также, чтобы можно легко поменять все сообщения системы для других языков. Далее, в самом объекте Message мы храним только ключ (isNull) и набор параметров, что вставить вместо {0}, {1} и т.д. Когда мы показываем этот MessageContainer юзеру front-end обрабатывать эти Message-ы и строить сами сообщение. Тем самым мы сокращаем количество информации, которое передаются между application layers. Таким образом, back-end должен знать только ключи и набор параметров.

Это была присказка, а сказка заключается вот в чём. В один прекрасный день понадобилось, чтобы front end мог вызвать Web Service установленный на другой машине. Одна из вещей, которую делать этот Web Service, это persistence в базу данных. Одна из вещей, что может пойти не так, это не заполнены все поля. Нужно каким-то образом сообщить об этом client-у. Так как это совершенно другая аппликация, она ничего не знает о том, какие ключи использует client. Более того, так как это не Web Application, она не знает ничего и про MessageContainer и про объект Message. Каким образом передать сообщения об ошибке из такого Web Service-а в Web Application - fron-end?

Пример пессимистического подхода в программировании номер три состоит в том, чтобы разобраться что собственно мы хотим сделать. Мы хотим, на уровне Web Service-а:

1. Сделать стандартные проверки persistence model (not null, например).
2. В случаях, если проверка не прошла успешно, нужно передать информацию об этом client-у. При этом требуется собрать все проверки вместе.
3. Очевидно, для выполнения пункта 2 нужно создать некий свой формат. Этот формат должен подходит для использование в качестве data object в Web Service-е (MessageContainer не подходит).
4. Client должен уметь перевести этот новый формат в его знакомый Message и MessageContainer.

Итак, основная сложность состоит в том, чтобы придумать некий свой формат, назовём его SimpleMessage попытаться понять, как его можно перевести в Message и как можно передать коллекцию SimpleMessage-ев в Web Service-compliant form.

Вкратце опишу полное решение. Вместо MessageContainer будет передаваться массив SimpleMessage-ев. С этим проблем в Web Service-е нет. Далее SimpleMessage будет содержать в себе уже отформатированный (полный) текст, а не только ключи. Перевод SimpleMessage в Message будет сделан следующим образом, будет добавлен новый ключ:

simpleMessageKey {0}

Этот ключ будет использован в Message, а в качестве параметра будет использован полный текст из SimpleMessage.

Пример оптимистического подхода в программировании номер три Для начала мы можно вообще игнорировать эту проблему. Сначала нужно написать обе аппликации и сделать между ними интеграцию игнорирую эту проблему. Затем на следующем этапе тот, кто ответственен за Web Service решит как ему удобно передавать эту информацию, придумает SimpleMessage, и добавить в его код проверки, затем тот кто пишет Web Application подумает как он эту информацию покажет на экране. Заметим, при этом может оказаться, что SimpleMessage не возможно перевести в Message.

UPDATE 10-10-2010:
См. также
Сверху вниз и снизу вверх. Часть I
Parsing - синтаксический анализ Сверху вниз и снизу вверх. Часть II
BFS and DFS - поиск в ширину и глубину Сверху вниз и снизу вверх. Часть III
END OF UPDATE.

No comments:

Post a Comment