Pull to refresh

Comments 203

Спас. Но он был сделан ночью, а таблица была удалена днем. За это время прошло несколько сотен платежей. Было выполнено около 10 тысяч операций с балансом. Было зарегистрировано много новых пользователей. Кто-то менял пароли. Кто-то менял информацию в профиле. И многое другое, все было в одной таблице.
UFO just landed and posted this here
xxx: Кадровичка случайно удалила папку, прибежала: «Восстанови!» Сказал, что бэкап будет только вчерашний. Тётушка поинтересовалась, когда делается бэкап и как. Удивился, показал, объяснил, что скрипт срабатывает каждой ночью. Кадровичка сказала, что данные нужны сегодняшние, и она подождёт до завтра, когда сделается сегодняшний бэкап.
Бэкап + бинарный лог. И ничего не потерялось бы.
UFO just landed and posted this here
Если сообщение обведено в красный цвет, то после нажатия кнопки «да» должен полюбому произойти пи*дец, это же ясно :)
Сам пару раз случайно очищал таблицы. Теперь с особенным вниманием отношусь к сообщениям в PMA, и не только.
UFO just landed and posted this here
Я работаю под linux, тоже забывал про бэкап БД. Потом сделал скрипт, который бэкапит домашнюю папку, БД (postgresql/mysql) и различные конфиги.
А разгадка проста: эти ваши гуи. Если бы пришлось ввести вот этими вот ручками Truncate Table USERS;, вероятность факапа была бы куда ниже.
пишите миграции — вполне себе контроль версий для базы
Миграции — это контроль версий для структуры базы, но не для данных.
Жалуетесь на лень прочитывать подтверждающий текст, а сами предлагаете ввести еще и подтверждение в текстовом виде. Противоречите сами себе, вам так не кажется?

И еще далеко не все автоматически нажимают на при подтверждающих запросах. Конечно, бывает, что раздражает тыкать постоянно. Но одно дело тыкнуть «добавить комментарий», не прочитав подтверждения и совсем другое — работа с важной базой данных.
Проблема ене в том, что лень читать, а в том, что такие «предупреждения» на каждом углу, особенно в МС'овских продуктах. Я когда работаю на виндовой машине, зачастую вобще не глядя жму ОК на всех предупреждалках. потому что скоро в ворде символ нельзя стереть будет не нажав очередного «да уверен, тупая машина, прекрати считат меня идиотом». С Линуксом немного проще, он не будет предлупреждать лишний раз, так что сразу ясно, если предупреждают, значит надо еще раз подумать.
Какой толк, к примеру, спрашивать пользователя уверен ли он что хочет выкинуть файлы если они всего-то окажутся в корзине? Щтука в том как защитить пользователя от непоправимых действий сделанных по-ошибке, но не привить ему рефлекс: опять спрашивают — ответить да.
Вспомнилась поговорка:

«В основе любых аварий лежит человеческий фактор»

Актуальны три подхода к проблеме:
1. предусмотреть,
2. не допустить,
3. уменьшить последствия
Life is provided as is. Do any changes at your own risk.
Люди стают внимательными только после огромного косяка. Человек должен внимательно читать сообщения, и научиться этому можно только по своему горькому опыту. И никакая защита «от дурака» не сработает на 100%.

Люди становятся
Не в обиду, просто совсем уж как-то… так :)
Сразу видно земляка с Украины. «стают» — это украинизм.
Не знаю как у Вас, но нас так ни кто не говорит.
$ sudo apt-get purge apt
...
WARNING: The following essential packages will be removed.
This should NOT be done unless you know exactly what you are doing!
apt
0 upgraded, 53 newly installed, 56 to remove and 0 not upgraded.
Need to get 42.8 MB of archives.
After this operation, 107 MB of additional disk space will be used.
You are about to do something potentially harmful.
To continue type in the phrase 'Yes, do as I say!'
?]
Я однажды компилировал и переставлял ядро debian way и решил удалить ядро, которое мне не понравилось. Проблема была в том что я удалял загруженное ядро и выскочило такое же уведомление.

Но я то был уверен что знаю что делают!

Что могло бы заставить меня остановиться? Пожалуй кроме замены слова WARNING на моргающее SHIT ничего простого в голову не приходит.
а его удаление не потянуло за собой снос 100500 пакетов?
в цитате из моего коммента строчка "..." — это два экрана списка 56 пакетов.
Прт Тьюринга забыли)
UFO just landed and posted this here
Такой извив текста как раз для не очень трезвого рассудка, ИМО =)
В конце бы добавить: " и согласен возместить весь ущерб"
:)
Спасибо автору. Я как раз уже второй день забываю обновить phpMyAdmin — а тут вспомнил.
Напоминаю про «покушать» и про туалет. Просто на всякий случай.
Ну насчет подтверждений — для частоиспользуемых операций они всегда будут доводиться до автоматизма, и лишние защиты будут только зазря кушать нервы пользователя тогда, когда в этом необходимости нет, в частности текстовое поле — это по-моему сильный перебор. Как говорится, хочешь изменить мир — измени себя :)

По-моему вас от вашей ошибки спасло-бы следующее:
1) повышенное внимание в случае работы с рабочей базой сервера, даже если время не терпит — все равно осознание критичности ошибки должно заставить остановиться, остыть, так сказать, и выполнять действия обдуманно.
2) повышенное внимание в случае необратимых операций, причем на этапе выполнения, а не подтверждения операции, это мне кажется самое главное, решение нужно-ли делать TRUNCATE `users` должно обдумываться на момент нажатия TRUNCATE `users`, а не на момент нажатия подтверждения.

Ну и в конце концов если цена ошибки так высока — может стоит вообще отказаться от работы с оперативной базой через phpmyadmin? «Резание по живому» никогда не заканчивается слишком уж хорошо.

P.S. Ну и конечно если уж говорить о защите от такого рода ошибок — предупреждения и подтверждения нужно выдавать там, где это действительно критично, именно тогда на них будут обращать внимание:
» chmod -R 644 some_folder
« тишина
» chmod -R 644 /
« Вы уверены что хотите рекурсивно задать права на все файлы и подкаталоги корневого каталога? Y/N
и т.п.
Мой коллега всегда перед важным изменением базы делает предварительно бекап, именно состояния в текущую минуту (конечно, тоже может быть ошибка, но куда деваться?). Это, мне кажется, просто должно войти в привычку. Ну и да, во время изменений данных все делается в транзакциях (тоже, конечно же, не панацея).
Если это DDL, то транзакции не спасут, увы.
«Если цена ошибки так высока», может стоит вообще отказаться от использования БД без онлайн-репликации на standby? Ведь кроме truncate/drop table сервер может просто упасть (в т.ч. физически) и на восстановление потребуется больше 10 часов. :)

Ну и работать из под пользователя БД, который может сделать TRUNCATE/DELETE — так же не комильфо, так же как работать из-под root на сервере
Эти предупреждения действенны только в момент, когда появляются неожиданно.
Выполняя какое-либо действие, связанное с критичными изменениями в системе, автоматически ждешь этого подтверждения и на том же автомате нажимаешь на согласие.
Если же запрос выскакивает неожиданно (типа «а чего это она жалуется? я ж, вроде, ничего особенного не делал...») — то, разумеется, начинаешь читать, что же там написано. А то еще и полезешь в подробности — какой же процесс и что именно пытается сделать.
Проблема неразрешима, если пользователь выполняет критичное действие, понимает, что запрос выскочит, но ошибается в самом действии. Либо в объекте этого действия, как в данном случае.
Предугадать, что же на самом деле планировал сделать пользователь — невозможно.
жму «да». Вижу структуру таблицы и понимаю, что сделал что-то не так. Поздно.

Обычно в таких случаях (хотя мозг уже понимает, что п**дец уже успел настать) по привычке впустую папу раз жму Ctrl-Z.
Лучше уж тогда креститься :) Шанс чуда значительно возрастает :)
Креститься, на моей памяти, ещё ни разу чуда не произвело, а ctrl+z много раз!
*пару раз

Это был знак. Пора спать, всем счастливо выспаться.
Сделайте так, чтобы у PMA был другой фон при работе с production-базой.

Я использую разноцветные prompt'ы в shell. Беглого взгляда (даже не читая хостнейм) при наборе команды достаточно, чтобы понять, где работаю.
может для опасных операций добавить еще один диалог «Вы уверены?» и текст, кратко поясняющий опасные последствия указанного действия? этого вполне достаточно. и бекапы конечно же :)
добавить еще один диалог «Вы уверены?»

и приписку: «Помнишь, что было в прошлый раз?» )
В гос учреждениях сбор подписей так строится.
Кто то что то пропустил и подписал. Добавили еще одно звено.
Помню, когда учился, чтобы заплатить за общежитие надо было то ли 10 то ли 11 подписей собрать.

Если на каждый инцидент создавать по доп. проверке, то рано или поздно до самого действия не добраться будет)
одно дело — одно окно — всё внимание одному делу. никаких вкладок — лучше использовать несколько браузеров для каждого дела.
Лучше использовать несколько компьютеров, в разных городах, в разных странах…
лучше просто не работать)
А не надо по живой базе через phpMyAdmin лазать. Заведите уже себе нормальную админку и делайте всё через неё.
Угу, откуда PMA знать, в таблице финансовые данные без бэкапа, или какой-нибудь кэш. Если он на каждый чих будет просить вводить простыню текста, проще будет через SQL все делать. И там уж точно никто не будет спрашивать, делать ли TRUNCATE TABLE ))
Совершенно не согласен. Идеология — полный бред.
Не надо усложнять и так перегруженный интерфейс! Бесит же постоянно подтверждать. У большинства юзеров только вырабатывается рефлекс кликать не читаю.
Не нужно больше клавиш, больше операций, более заметные окошки. Наоборот, надо убрать.

Единственное, что нужно — возможность отмены.

И не надо рассказывать, что это иногда невозможно. Бред. Надо просто продумывать. Автор должен был использовать бекапы. ОСОБЕННО в сайте с финансами. Если же вопрос, как улучшить пхпадмин, то он должен по-умолчанию делать бекапы. Тем более, на таки команды.
Даже если отмену как таковую не реализовать, просто нужно добавить запрос в очередь, а не исполнять немедленно, как, например, в гуглопочте при отправке письма. Если до пользователя дошло, что он сделал не так, он может успеть нажать отмену. Конечно, не панацея, зато сравнительно легко реализовать и не напрягает.
Hint'а можно понять — сам нарывался на потерю данных/кода по причине автоматического нажатия не той кнопки диалога. Особенно зарубают интерфейсы не соответствующие гайдлайнам операционной системы, например инверсный вопрос о сохранении файла в редакторе ресурсов LWUIT.

Но если говорить о стремлении к идеалу, то хороший интерфейс вообще не должен задавать вопросов, но позволять совершить отмену последнего действия. Мотивация проста — я ошибся только десяток раз, а остальные стотысячмиллионов раз сделал все правильно, зачем мне лишние вопросы?
На эту тему товарищ Joel Spolsky хорошо рассуждал в лекции Simplicity vs. Choice
Понять нельзя — если ты отвечаешь за свои действия, то отвечай. Какой бы не была плохой программой, управляет ей админ.

Со второй частью согласен — собственно, что и сказал.
Не надо слишком умных приложений. Не надо phpMyAdmin-у делать бэкапы при попытке очистить таблицу. Слишком умные приложения хороши для обычных пользователей, у которых не возникает нестандартных задач. А вот в программировании, когда сам делаешь функционал, слишком умные только мешают. Пример, допустим я через phpmyadmin администрирую боевую базу :), нужно внести изменения в огромную таблицу, чтобы не мешать работе базы, копирую таблицу, делаю во временной все изменения и вот настал час, когда нужно бысто удалить старую таблицу и переименовать временную в основную. Нажимаю я truncate, и тут phpmyadmin внезапно начинает бэкапить абсолютно ненужную уже многомилионную таблицу. Ну это как пример, а вообще, имхо, phpmyadmin не для серьезных боевых баз, консоль и только консоль (мало ли где ошиблись разработчики phpmyadmin и sql который он сформирует будет неверным), и перед выполнением каждой команды прочитать ее, проверить, нажать у себя в уме кнопку «Да, я согласен». Ну и конечно защита от дурака, которую предлагает автор не нужна, было бы желание, а имея доступ к боевой базе сломать можно все. Такая защита наоборот приучила всегда жать «да» не читая, слишком часто она ставится, уже рефлекс выработался, видишь окошечко (надпись) с кнопками «Да», «Отмена» жми на «Да» сразу.
Оригинальный пост «как бумажник — откроешь, а в нем два отделения». С одной стороны, конечно, сквозит проблема принятых процессов разработки. И тут смайл про живую базу принимается. Но с другой стороны пост в блоге «Интерфейсы» позволяет рассуждать о прекрасном :)
Согласен, мне как разработчику, не всегда нравится умный инструмент. Но только тогда, когда он действует самостоятельно. Я же не случайно добавил «по умолчанию». ПХПадмин, как раз для простых юзеров. Ну не сильно простых, но не для навороченных базоделов. Я думаю пользователям пхпадмина, в большинстве своем, как раз надо по умолчанию бекапить базу. А для более продвинутых пользователей — галочка «не городить город и делать только что говорят».

Вообще говоря, я хотел сказать только про философию дизайна:
Минимум окошек, вопросов и кнопочек. Максимум возможностей исправить ошибку.
Как это реализовать в конкретном приложении (пхпадмин) готов обсудить. Только не думаю, что это что-либо изменит в указанном приложении.

ЗЫ Вы тоже заметили, что у всех только рефлекс на «да» появился, безопаснее не стало.
Реально крутой подход. Даже мечтать начал.
Опыт печальный.
Правильный способ решить данную проблему — не вырабатывать такой привычки у пользователя. Хватит вечно спрашивать, уверены мы или нет.
И конечно, надо всегда давать возможность сделать шаг назад.
Кстати, абсолютно идентичную проблему описывал Джеф Раскин в своей книге.
Работаю АБД + админю и развиваю ERP. Из наблюдений: юзер думает ПОСЛЕ действия.

Т.е. вот есть документ, готовый к учёту, юзер нажимает учёт, а пока док учитывается, у юзера включается голова на тему «ничего ли я там не забыл?» Причём этот мыслительный процесс у опытного юзера занимает секунды 3-5, но выполняется строго ПОСЛЕ нажатия учёта И после утвердительного ответа на вопрос «Вы уверены, что хотите учесть документ?»
Так вот, может принять это как входные условия? Наверное, не мне одному пришла в голову эта мысль, и на форме, где показывают текущий прогресс учёта есть кнопка «Отмена». Но системы работают всё быстрее и эта формочка теперь практически не задерживается на экране более 2-3 секунд.
Дык вот, можно ведь попробовать не меняя поведения системы, сделать отложенный коммит. Секунд 10, например. И роллбекать с оповещением по возгласу «НЕЕТ!», «СТООЙ!» или «БЛЯЯ!» >_< Юзер-френдли же.

Кстати, диалоги типа «Вы уверены, что хотите учесть документ?» вообще можно убирать нафиг — их подтверждают «на автомате»: я ради интереса ставил на кнопку «Нет» каунтер — 11 нажатий за месяц против over 1000, уверен, что если отнести её подальше от «Да» — станет 0.
UFO just landed and posted this here
Эта фича уже давно все новостные заголовки обошла :)
Ну да, как вариант. Но оно работает, насколько мне известно, в пределах одного сервиса, т.е. если отослал на mail.ru — отозвать не удастся. У Exchange такая фича уже очень давно.
Гмейл не отзывает свои письма, а фича работает для любого сервиса. Здесь применена хитрость и смекалка — в течение 5 секунд, когда доступна «Отмена» письмо вовсе никуда не отправляется — оно просто ждёт «вдруг вы захотите отменить».
Такой «обман», кстати, мог бы спасти и в случае, описанном автором топика.
А, тогда, действительно, то, что нужно.
От автоматизма не спасают никакие требования подтверждения, капчи и прочее.
ИМХО это бутерброд «надо кушать с другой стороны» — а именно предусматривать и реализовывать для критичных данных модель хранения и резервирования с обязательной возможностью отката (исключительно например, навскидку и не от фаната — см. возможность отката по тому же логу транзакций в МС СКЛ).
транкейт не пишет в лог, в этом-то и его опасность.
UFO just landed and posted this here
Автор Visual Basic и один из авторитетнейших специалистов в области проектирования взаимодействия (в т.ч. интерфейсов) Алан Купер в своих книгах «Об интерфейсе» и «Психбольница в руках пациентов» (очень советую почитать) приводит кучу агументов против любых видов запросов подтверждения действия пользователей. По его мнению, любое запрашиваемое пользователем действие должно быть выполнено немедленно без каких-либо подтверждений. Защита от ошибки: отмена действия. В вашем случае проблема должна решаться на уровне phpMyAdmin, например, созданием бэкапа таблицы перед выполнением запроса, с возможностью ручного или автоматического отката.
Любого запроса? Update, delete? Есть если не ошибаюсь в phpmyadin возможность загрузить sql из файла, там могут быть тысячи запросов. И что перед каждым бэкап? Как вы это себе представляете? Имхо, если ты ошибся при выполнении действия при адмнистрировании базы, ты сам себе злобный буратино. Хочешь защитить себя от себя же, есть же пользователи и разграничения прав. Не сиди под рутом, логинься в базу под рутом только тогда когда нужно сделать что-то особенное, и если уж залогинился, проверяй каждое свое действие.

Если молоток обмотать паролоном, он конечно не так больно будет бить по пальцам, но гвозди забивать станет значительно хуже. Давайте учится пользоватся инструментом, а не делать инструмент который не сможет навредить но и работать с ним станет невозможно.
Тыканье не относится к вам, это я так абстрактного пользователя назваю :)
Бэкап таблицы — действие конкретно для truncate. Для delete и update бэкап записей, которые затрагиваются этими действиями. Вообще, Купер писал больше об интерфейсах для конечных пользователей. Тем не менее, на примере топикстартера очевидно, что ошибка профессионала бывает более дорогостоящей, чем обычного оператора, последнему ведь никто не даст махом удалить кучу записей. Логику «сам виноват» я считаю неправильной, ведь это компьютеры работают на нас, а не наоборот; компьютер должен страховать нас от ошибок, не затрудняя нам жизнь (например, подтверждениями на любые действия). Вот пусть он не оскорбляет нас, сомневаясь в каждом нашем решении, но дает нам возможность изменить это решение. Это почти цитата вышеупомянутого Купера (по памяти).
Немного философии: компьютер это инструмент. Изначально он был инструментом который позволял выполнять математические расчеты значительно быстрее чем человек или группа людей. Теперь компьютеры выполняют много разных действий, но это все равно инструмент для реализации решений челоека. Решения остаются за человеком, и это хорошо, иначе да здравствует скайнет. А значит и ответственность за решения должна быть за человеком и компьютеры должны страховать от неправильных решений только там где они для этого предназначены (автоматическая система на атомной станции не позволит человеку случайно взрыв устроить например). Ну и следует различать профессиональный инструмент и любительский (обычного пользования). На обычный автомобиль можно поставить компьютер, который будет сглаживать перегрузки, помогать при заносах, автоматически переключать передачи и пр., но на гоночном болиде такую «услужливую» систему ставить не стоит, такой болид придет последним или вообще не придет к финишу, там нужен максимально простой и быстрый доступ ко всем системам машины. Так что на обычной машине, да, нужно страховать пользователя от ошибок, но на гоночной, пользователь «сам виноват».

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

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

А вариант с бэкапами пройдет только на небольших таблицах. Я выше приводил пример по которому бэкап при truncate заставит сервер надолго задуматся когда это совсем не желательно. Update как и delete может затрагивать миллионы записей за раз и если еще бэкапить все эти записи то станет совсем нехорошо. Ну и в пример приводится реляционная база, что со связями делать, все связанное бэкапить при удалении? А связи, могут быть явно и не объявлены, т.е. на 100% быть уверенными что удастся откатить все равно нельзя, а само знание что можно откатить расслабляет и зачтавляет делать ошибки не задумываясь о последствиях заранее.

Я честно не знаю, объясните, сколько по времени может занимать бэкап базы данных? Разве невозможно сделать затраченное время на эту операцию равноценным времени копирования файла?
Между вашими и моими рассуждениями не такая большая разница :) Однако насчет роли компьютера я не согласен. Именно к «Зачем думать прежде чем что-то сделать, всемогущий компьютер все равно все исправит если я ошибусь» и движется айти-индустрия, и это совершенно правильно, иначе стоило оставить компьютеру роль большого калькулятора. Можно порассуждать о деградации людей в такой схеме, благо на эту тему много фантастических рассказов и романов, но это уже другой разговор.

А что касается времени на бэкап и сложностей с программной реализацией восстановления связей — это сравнительно небольшая плата за минимизацию рисков, пример топикстартера это наглядно показывает. Благо задачи по удалению большого количества данных обычно редки. Если немного пофантазировать, можно придумать разные варианты решения задачи избавления от подтверждений в нашем случае. Десятки или сотни человеко-часов для реализации этого вполне можно потратить.
Вы же специалист в области БД, вы и придумайте как сохранять последние состояния при всяких update, delete и прочих командах, которые опасные действия с данными совершают. Полный там бэкап делать, таблички временные создавать или еще чего.

Не надо только тут сказок рассказывать про то, что БД будет работать хуже, если такие действия пользователя (не скриптов заметьте, а именно пользователя) будет по человечески обрабатывать.
Браво, «Вы же специалист» — последний и самый убойный аргумент, у меня дикая аллергия на эту фразу. Есть один замечательный рассказ, букв конечно много, но оно того стоит, великолепно объясняет ситуацию что иногда желания все таки расходятся с возможностями, хоть на первый взгляд «сделать можно все» — alex-aka-jj.livejournal.com/66984.html

Да-да, один умный человек написал сказочку, чтобы показать как иногда уперты бывают заказчики и теперь каждый технарь её использует, когда ему лень подумать на тему «Как сделать» и он выдает «Это не возможно».

Даже в этой теме я видел 2 варианта реализации механизма Undo. Но мы конечно самые умные и расскажем еще разок сказку про красно-синие прозрачно-перпендикулярные прямые. Правильно, зачем думать, если можно просто расписать все трудности и сказать, что это не возможно?
не нужно капчи и других усложнений.
Сделайте кнопку «Отменить» как в Gmail.
Чудеснейшая статья на эту тему.
использую навикат. к рабочей базе коннекчусь только пользователем, который умеет только читать
Доводы за кнопку «отмена» однобоки. Да, безусловно, это важный и нужный элемент интерфейса и про него многие говорят. Но этот метод не универсален. Если действие предполагает уничтожение данных, оно ещё и подразумевает увеличение свободного места для новых данных. В этом случае добавление функции «отмена» по сути дела испортит действие: мы всё удалили, а место не появилось, потому что для работы этой функции данные нужно продолжать хранить. Тогда потребуется сделать специальную кнопку «очистить буфер отмены» (что-то вроде «очистить корзину»), после нажатия которой ресурсы действительно освободятся, но с другой стороны восстановить уже ничего будет нельзя. И мы снова возвращаемся к изначальной проблеме. Человек, желающий освободить ресурсы, сперва выполнит действие на уничтожение данных, затем выполнит действие на очистку буфера отмены. По большому счёту это то же подтверждение. Для функции «очистить корзину», заметьте, Ctrl+Z не работает.
Мой любимый Far при удалении нескольких непустых каталогов выдаёт аж три предупреждения.
Первичный запрос на удаление:

Подтверждение удаления нескольких элементов:

Подтверждение удаления непустых папок:

(нужно нажать Tab+Enter).
Любые из этих подтверждений можно отключить. Однажды я случайно чуть не грохнул недельную работу, нажимая по инерции Shift+Delete, Enter, Enter, Tab, но перед последним Enter'ом всё же опомнился, что я делаю что-то не то. Поэтому я никогда не отключаю эти подтверждения. Чем больше вопросов, тем больше времени подумать.
С файлами проще: есть еще корзина (если не отключили) и восстановление удаленных файлов, если опомнится более менее быстро — даст 100% успеха
Shift+Delete в Far'е удаляет мимо корзины
Я всегда макросом переназначал на Delete — удобнее, не надо тянуться =)
Ваш случай — исключение. Ну кто в здравом уме работает с «живой» базой на 300 тысяч пользователей на рабочем сервере? А даже если и работает (чтобы отловить чего-нибудь очень специфичное), то проверяет все запросы и действия перед выполнением. Из-за дебильных ситуациях обычно и появляются дебильные интерфейсы с миллионом подтверждений.
Конкретно описанная проблема никак не решается, т.к. виноват не интерфейс, а невнимательный пользователь.
В общем случае можно делать задержку на выполнение критичных команд, например после выполнения TRUNCATE появляется надпись «таблица <big>users</big> будет очищена через 10 секунд. |отменить очищение таблицы <big>users</big>|       |очистить таблицу <big>users</big> без ожидания прямо сейчас|»
UFO just landed and posted this here
DELETE тоже катастрофичный инструмент (кто хоть раз не забывал WHERE в DELETE?)…
Только UPDATE с нужным статусом записи с возможной физической очисткой (а лучше — переносом в архив) таблицы по крону. Да и голые INSERT/REPLACE/UPDATE в критичном сервисе использовать пользователям (не DBA) нежелательно — только процедурные обёртки.
кто в здравом уме работает с «живой» базой на 300 тысяч пользователей на рабочем сервере?


вы не поверите…
Я в таких случая пользуюсь только консолью. Прогоняю все необходимые действия на тестовой базе, проверяю результат, формирую скрипт и запускаю его на боевой базе. Лазить по продакшену через левую поделку, да ещё и транкейты через неё же делать, это же неминуемый ахтунг. Что автор и доказал.
О, работая с консолью можно точно так же ошибиться. Например, в одном окне тестовая база, а в другом — рабочая. Переключаясь с одной на другую, можно в конце концов перестать ощущать в какой именно ты реально находишься.
Это да, поэтому для разных хостов надо выставлять разный бэкграунд.
В консоли часто ошибки вида rm -rf lots_of_files_and_subdirs *, а должно быть rm -rf lots_of_files_and_subdirs*
в консоли всегда выставляю понятный и удобный промпт
Тоже хотел написать, что никогда не тыкаю мышками в truncate и drop — только руками набираю такое. В случае drop (слава ораклу, есть еще recyclebin) purge как правило не дописываю, а пишу только после того как все сделал и все ок.
Ещё можно руками явно открыть транзакцию, если БД поддерживает их для DDL. Пока наберёшь COMMIT; — успеешь подумать.
Кстати в каких субд ddl транзакционный?
Как минимум, PostgreSQL, и, как ни странно, простенький SQLite3.
TRUNCATE автоматически завершает транзакцию.
Пруф.
Я счастлив, что не пользуюсь MySQL.
Oracle также автоматически завершает транзакцию на truncate…
Вас не спасет это поле. Вы с таким же успехом введете таблицу, потом будете думать, что сделали не то. Винить весь мир и неправильный интерфейс в вашей ошибка — «лучший» поступок по-моему.
Как говорят выше — отмена последнего действия бы помогла, а сотня подтверждений — вряд ли.
На таком серьезном продакшне надо ежедневный бэкап + дневной лог всех операций с БД сохранять. Если даже БД поломалась в течение дня, то по логу можно ее быстро восстановить, просто прогнав без последней критической команды
Знаете, я когда-то думал что не смогу проигнорировать будильник, который надо выключить с помощью слайда по экрану в заданном месте, предварительно сняв полностью чехол. Я ошибался.
UFO just landed and posted this here
Если мозг хочет спать он и интегралы посчитает а потом выключится. Мне эта арифметика помогала с неделю, потом такие вещи щелкал (благо всегда было нормально с счетом в уме) в полудреме и отрубался дальше.
Бери как левелап что-нить покруче, скажем вспоминай Формулу Кирхгофа из мат. физ.-а.
1. никогда не работайте на боевой базе
2. зашли поработать с боевой базой — снимите бэкап
3. трижды читайте то что делаете, прежде чем жать энтер на боевой базе.
4. не делайте исключений из правил 3 и 4.

пусть лучше больше времени потратите чем вот так.
> 4. не делайте исключений из правил 3 и 4.

надеюсь, вы не используете goto
замыкание документа на самого себя не я придумал. прочитайте например 135 статью конституции.
Тут алгоритм, а там юриспруденция — это же небо и земля.
тут список правил по работе с боевой базой. я не поставил заголовок, но вы могли заключить самостоятельно исходя из того что в п4 я назвал пункты 3 и 4 правилами.
А чем плохо его использование-то? Вот я конкретно у Вас спрашиваю. Единственное, по моему мнению, что код становится плохочитаем.
В принципе, ничем не плохо.

Но в данном случае автор комментария зациклил ссылку на саму себя. А теперь представьте такое в коде.
Золото! Я бы к вашему добавил:
5. После работы с боевой базой (а лучше — с любым ИТ-ресурсом, если он в самое ближайшее время не понадобится) — закройте соответствующее приложение (pma, шелл, консоль и т.п.)
Хорошо бы ещё второй шаг автоматизировать.
А меня всегда бесят вылетающие в фокус диалоговые окна «да/нет» с фокусом на кнопке, которая выполняет не то действие, которое обычно нужно. Но бесят они меня потому, что я при этом что-то печатаю в другом окне, и пробел вдруг выполняет нажатие на эту самую кнопку по-умолчанию. Что там произошло, и то ли это, что нужно — понять невозможно.
с локальными базами работаю через phpMyAdmin, а вот с продакшеном уже через SQLyog, поэтому такие косяки исключены, тьфу-тьфу-тьфу :)
>Как защитить пользователей?
не вести разработку на боевой базе
Мне нравится функционал в gmail, который перед отправкой в позднее время предлагает решить три простых арифметических задачи. Пока будешь решать, увидишь, для чего именно ты их решаешь.
Напомнило мне DOS, и систему подтверждения, что я действительно хочу форматировать диск
format c:
The type of the file system is NTFS.
Enter current volume label for drive C:


И при этом я все время начинаю искать, какое же у меня имя диска (:
как же так написана база, что она дала удалить пользователей, у которых уже были какие-то операции?
Определенно без внешних ключей…
Ну почему обязательно без внешних, вполне возможно «on update cascade», «on delete cascade», чтобы уж наверняка :)
В таком случае придется сочинение писать при очистке сразу нескольких таблиц.
Именно поэтому разработчик не должен иметь полный доступ к боевой базе данных и коду ;) Все изменения в прод должны вноситься ТОЛЬКО админами по предварительно созданному SQL-файлу :) Цена ошибки слишком высока.
Вариант Firefox'а: кнопка «ОК» первоначально запрещена и на ней идёт обратный отсчёт нескольких секунд, раньше истечения которого нажать кнопку невозможно.

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

image
шаблон замкнётся обратно посли трёх таких «да»
Автоматизм вырабатывается на более простых вещах.
Нас зачем то спрашивают «да / нет» на каждом шагу в ОС.

А часто ли человек делает «truncate table»? Нет.
И на это редкое событие не выработается устойчивое поведение.

И отличие в диалоге будет тем фактором, который, как минимум заставит остановиться и задуматься еще раз.
для разраба, который разрабатывает базу — truncate такое же обыденное действие,
как и rm /var/log/foo/* для сисадмина отстраивающего какиенить сервисы.
Да, согласен. Для разработчика на тестовой базе это обыденно и в порядке вещей.
Но не на production.
ну вот таки вопрос возвращается опять к проблеме попутать продакшн и тестинг.
При выработанном автоматизме надписи на кнопочках не читают и даже на сами кнопочки не смотрят. В лучшем случае человек заметит, что кнопочка другая, но рассчитывать на такое несколько утопично.
Отлично. Плюс еще идеи от Splurov или wfwell и будет идеальное решение:
— либо после нажатия говорить «Таблица `users` будет очищена через 10 сек. Отмена»
— либо «очищать» сразу, но методом RENAME TABLE, с окончательной подчисткой ночью
Зачем rename, если мне нужно именно truncate?
Я думал это очевидно:
1. сохраняем SHOW CREATE TABLE `users`
2. делаем RENAME TABLE `users` TO `trash_2435`
3. исполняем CREATE TABLE из пункта 1.
Насчет того, как именно это сделать, у меня вопросов не возникло :)
Меня интересовало: зачем делать лишние действия? Я просто считаю их лишними, вот и всё…
Лишнее действие — это по случайности удалить все то, что удалил автор. Там где деньги, причем чужие, надо поосторожнее. Ну я, во всяком случае, так думаю.
А во временной таблице разве не бывает 6432 строки? Как это спасёт от неверного действия? :))
Спасет то, что это не просто «да / нет», и человек поймет что он именно «удаляет» а не просто «да жмет».

А вообще, по хорошему нужно еще внизу результат «select * from users» вывести, а кнопки подтверждения внизу расположить.
Никакие предостережения не спасают от действия. Как уже много раз было упомянуто уважаемыми комментаторами, единственное спасение — «фальшивое» удаление, возможность отменить это действие.
Естественно, что идеальный случай не удалять, а только делать вид что удалили и возвращать по требованию.

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

Что-то вроде «Очистить users (23511 строк)». Нынешний вариант с да/нет, конечно полная ерунда.
Представьте ситуацию: таблица в 5 миллионов записей (в принципе, не редкость). И на таких объемах выполняется select count (1) from table_name только для того, чтобы предоставить этот «максимум».

На мой взгляд, это лишнее.
Лучше не предоставить этих данных и потерять ценную информацию?

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

Кроме того, насколько я знаю это время и само по себе очень невелико для проиндексироанной таблицы.
А какую смысловую нагрузку, на Ваш взгляд, несут данные о количестве удаляемых записей?
Ну если я планировал очистить таблицу, в которой 20 записей, а тут 4 миллиона, я точно призадумаюсь. То есть, опять-таки, защита от ошибки.
А какие проблемы? Неужели вы думаете, что для этого БД потребуется просматривать таблицу? Попробуйте сделать explain, что ли.
Я в курсе, что full access не случится, если расставлены индексы. Дело ведь не в этом.

> если я планировал очистить таблицу, в которой 20 записей, а тут 4 миллиона, я точно призадумаюсь
Обычно у меня есть привычка предварительно просматривать те данные, которые я собираюсь удалять, поэтому если GUI хочет мне еще раз «помочь» и зачем-то посчитать количество строк, которые я собираюсь удалить — это меня вводит в смятение. Ну не привык я к тому, что машина слишком часто и помногу думает за меня :)
select count(*) from table_name;
У меня как раз пару часов назад вот такой запрос отрабатывал шесть с половиной минут.
И это таблица всего на 50 млн записей и не на самом плохом сервере.
Долговато прийдётся окошко ждать временами.
50 миллионов записей без индекса? Удивительное дело.
Почему без индекса? Даже с двумя индексами — один составной на пять полей, а второй по полю типа DOUBLE.
А вот первичного ключа или простого уникального индекса нет, ибо не нужен.
А тип таблицы какой? И что говорит show table status?
Можно первые несколько секунд при появлении сообщения показывать кнопку подтверждения операции неактивной, или просто сделать настраиваемый параметр, дабы не трепать себе нервы на локальной машине.
подобная защита от случайного нажатия давно используется в игре World of Warcraft
imageP.S. скриншот не мой
я просто разместил шамана…
Ну у них наверное защита не от случайностей, а лишнее «А может все-таки еще полчасика?»
Вообще-то нужно отвыкать от phpmyadmin'a. Он дыряв, атрофирует мозг, построен на фреймах, имеет кривую авторизацию — и это как минимум.

Хотя в вашем случае, может быть стоило завести read-only юзера, с под которого только смотреть на продакшине.

А при операции truncate нужно спрашивать не имя таблицы, а повторения ввода пароля.
И что же плохого в фреймах, которые в phpmyamin?
Вполне адекватное использование инструмента.
Подтверждение конечно же надо. Но мне кажется надо добавить не только на удаление.
Но и на проверку наличия Where в UPDATE :(
Да всё проще на самом деле. Надо быть чуточку внимательнее при работе с боевой базой, при должной внимательности и осторожности всякие «защиты от дурака», по большому счету, становятся не нужны.

Как решаю проблему лично я:
1. truncate, drop, alter набираю вручную — никаких «быстрых кнопочек в GUI-интерфейсе»;
2. еще раз внимательно читаю код перед тем, как запустить его на выполнение на «живой» базе.

That's it.
Что касается UPDATE и DELETE: я всегда визуально проверяю с помощью SELECT — что же именно я хочу изменить или удалить, а уже потом меняю select на update/delete в тексте запроса.
По хорошему этот «SELECT» и надо выдавать каждый раз и спрашивать:
«Изменить 3454 записи?» / «Не изменять»
На мой взгляд, проще приучить себя не пользоваться GUI для таких вещей.
UFO just landed and posted this here
Я не пользуюсь GUI для truncate, alter, drop, update и delete уже давно, привык.
Получается не намного медленнее, чем с помощью GUI (да-да) и вероятность ошибки из-за некорректных действий разработчика сводится к минимуму.

Ваше мнение понятно, не буду спорить.
видимы вы уже имели горячи опыт :)
Просто частенько приходится исправлять баги прямо «на горячую» в production-среде.
Слава богу, серьезных ошибок не возникало. Осторожность превыше всего :)
UFO just landed and posted this here
Кнопка должна быть другой, это самое главное.

P.S: на картинке ещё не хватает таскбара. Там случаем не написано ещё раз? :)
Сделали открытие, блин. Про автоматизм и неработоспособность всех этих подтверждений писали еще в седые 90е.

Решение уже придумали и реализовали лет 5 как. Тупо внедрять на все опасные и критичные действия механизм Undo.

И да, механизм Undo нельзя реализовать только на действия, которые вот прямо сейчас будут произведены в физическом мире (и то не все). Во всех остальных случаях можно скопировать куда-нибудь текущее состоянии и написать механизм его восстановления.

А вообще, писать такую банальность в блог UI… это как еще разок обсудить родимое пятно на лысине Горбачева.
А вообще, писать такую банальность в блог UI… это как еще разок обсудить родимое пятно на лысине Горбачева.

Сравнение неуместно. С пятном мы уже ничего не сделаем, а вот выполнять на практике то, о чем писали в седые 90е, еще можем научиться. Или вы считаете, что все интерфейсы, кроме интерфейса PMA, уже давно лишены подобных недостатков?
Нет не все.

Их сейчас лишены все сервисы, на разработку которых не жалеют времени/денег и понимают, что это самое UI все же нужная штука.

Но это уже не столько проблема UI, как UI. Это проблема развития ПО и рынка в целом. Ну вот представьте — зашел я на сайт, а там закругленные рамочки все еще с помощью картинок делают. Стоит про это написать в блог про CSS?
Знаете, я согласен, что пост на грани бесполезности. И скорее всего сам поставил бы ему минус. Он был написан на эмоциях, сегодня бы мне вряд ли пришла в голову подобная идея.

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

Добавление дополнительного поля «для ручного ввода» для таких вещей как DROP, TRUNCATE и пр. считаю в данном случае уместным.
Был по юности подобный случай, в конфиге hibernate стояла политика create, а не update. При очередном комите обнулил базу и потерял информацию о полумиллионе американских денег. Востановил за пару часов из очень подробных логов без потерь. Очко тогда вспотело, с тех пор имею привычку перепроверять каждый шаг по многу раз при работе с боевой базой.
UFO just landed and posted this here
Если человек невнимателен, то ничего не поможет. Хоть три раза проси ввести имя таблица задом наперед. Рано или поздно сложится привычка и для этих лишних бессмысленных действий и на автомате будет удалена нужная информация.
Поэтому я люблю консоль. Вероятность написать DELETE FROM «user»; — уж совсем мала.
Идея такая: после информации о действии перед нажатием на кнопку подтверждения надо искусственно сделать паузу.

Как уже сказали выше FF делает отсчет на кнопке.
APT выводит подробную информацию и просит не нажать, а напечатать подтверждение.
С тем же успехом можно делать captcha на изображениях (выбери кота).

Но все это, ес-но, будет дико бесить пользователя. Поэтому я согласен с мнением, что надо предусмотреть возможность отмены операции. Тут тоже есть негативный момент: мусор на экране. Если я сделал операцию осознанно (не ошибся, а именно хотел что-то удалить), то я хочу видеть результат и скорее всего сделать некое следующее действие. Но вместо этого я буду читать сообщение вида «вы сделали то-то и то-то. отменить?». И я начинаю напрягать мозг.

Как вариант паузы можно сделать так: вывести окно, внутри окна описание операции, кнопка подтверждения неактивна. Чтобы она стала активной надо кликнуть на название действия — грубо говоря поставить чекбокс. После этого можно уже жать на «применить». Ес-но такой сложный механизм (как и механизм отмены) нужен только для очень серьезных операций.
Что интересно, такая ситуация наверное была у каждого, но в интерфейсах управления базами не появилось ничего нового. Мне кажется что уже давно назрел механизм удаления в «корзину» (да, такую системную таблицу сделать и пусть там записи из прочих объектов складываются при удалении) и аналогичную для измененных записей. Понимаю, что это можно реализовать, но такие возможности должны быть что называется «из коробки». Пользователь лучше раз в месяц почистит корзину, чем попадет в вышеописанную ситуацию.

Второе, что можно взять из файловых систем — установка защиты на объекты БД, как RO для файлов. И если хочешь удалить — получаешь уведомление что объект защищен. И дальше видишь кнопку не «все равно удалить», а «для удаления снимите защиту». Тут уж невольно задумаешься — раз защита стоит, то наверно это неспроста. Да и снимая ее можно 10 раз понять контекст операции.
Лень заходить в админку, открываю в новой вкладке PMA с таблицей users.

Вот тут то собака и зарыта :)
А про правильное разграничение прав пользователей так никто и не задумался.
Знаете, ни ввод слов ни нажатие кнопки не спасет от случайного удаления. Все доводится до автоматизма.
Вывод? Создавать системы, в которых можно восстанавливать любые данные на любой момент времени.
Сделать транкейт в РМА платным ещё не предлагали?..
/просто спросил/
Какие бы гуи не рисовали, это не избавит от человеческого фактора. С чем только не приходилось сталкиваться за свою жизнь: удаляли важные записи и целые таблицы, через PMA, через самописные админки, криво написанными SQL-запросами и т.д. Бэкапы — это хорошо, но они, как правило, дискретны по времени, поэтому накатывание бэкапа на поврежденную базу обычно решает проблему лишь частично.

И, в итоге, я пришел к единственному решению, которое гарантировано спасает любую ситуацию — логгирование всех действий, которые затрагивают изменение БД. Юзер меняет свои данные — пишем в лог, что на что поменял (кроме пароля, разумеется), проводятся какие-то транзакции — тоже все логгируется. И это (вкупе с регулярными бэкапами) позволяет восстановить базу практически в любой временнОй точке.
К сожалению, временные затраты на восстановление при этом никто не отменяет.
Хотя их и стануовится существенно меньше.
Говорилось уже как-то на Хабре, что лучшее решение — это писать коротко о сути действия кнопки на самой этой кнопке. Например в вашем случае «Да, удалить таблицу users» и «Не удалять таблицу users».
Пускай лучше кнопки будут большие, чем проблемы после их нажатия.
Здесь можно выделить как минимум два важных момента:
  1. негативный опыт «тупых окошек»
  2. часто повторяющиеся действия

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

Тупость окошек заключается в отсутствии классификации (сообщение, диалог, ошибка) и проработке (e.g. информационное сообщение — зеленое, ошибка — красное, etc.). Печальный опыт работы, скажем, с осью MS, формирует негативный опыт взаимодействия юзера. «Произошла какая-то херня! ОК.» как исток зарождения бездумного тыканья на кнопки.

Даже если нам повезло и мы работаем с качественным приложением, мы сталкиваемся с повторяемостью действий. И совершенно логично, что при любом раскладе выдавать сообщение на подтверждение удаления будет правильным, потому как на кону потеря данных. И получается, что после того, как мы удалили что-то не нужное, обвинять приложение в отсутствии заботы о нас, мы не можем, потому что это как бы явно не так. Тем не менее «уже поздно» и приложению мы не доверяем :)

В данном случае, на мой взгляд, проблема связана в не до конца акцентном привлечении внимания. Бȯльшая часть окошек PMA идентична, а следовательно пользователя ничего не колышет в случае когда действительно важно прочитать. Удачной проработкой окошек здесь я бы назвал выделение красным ахтунгом всего окошка, чтобы юзер, находясь даже в самом неосознанном состоянии имел возможность взбодриться и все-таки заставить себя прочитать содержание заботливого текста системы. Ну и классика — если написать на кнопке не «да», а «удалить» — это также может быть очень хорошим стимулом включить мозг :)
Я бы в случае подобных операций откладывал выполнение на 30 сек.
И отображал сообщение «Идет процесс ...»
Обычно спохватываются очень быстро, и подобная ошибка в большинстве случаев будет замечена.
NT: — Вы уверены, что хотите удалить папку D:\TEMP?
Админ: — Да.
NT: — В этой папке находятся файлы. Вы уверены, что хотите их удалить?
Админ: — Да!
NT: — Некоторые файлы имеют атрибут «только для чтения». Вы по-прежнему уверены?
Админ: — Да!!!
NT: — Удаление этих файлов может повлиять на зарегистрированные программы. Вы всё ещё уверены?
Админ: — Да! Да! Да!!!
NT: — Эти файлы могут использоваться системой…
— Да пошла ты в жопу! — заорал админ и нажал Cancel.
— Ага, испугался! — подумала NT

Извиняйте за древнейший баян и оффтопик, просто не мог удержаться…
Когда же, наконец, придумают компьютеры, которые по голосовой команде «Бл*дь!» отменяют все последние действия?
Hint, я посидел, подумал, неужели действительно для такого одарённого как вы нужно усложнять жизнь всем остальным? Бывает, что даже лишние вопросы раздражают, когда человек определённо знает, что ему нужно от системы. Пролистал комментарии, у многих действительно лишь ирония.

Конечно, то, что вы просрали данные на продакшене — ССЗБ, нужно быть аккуратней и по семь раз проверять проделываемые неотвратимые операции. Пример из *nix — root может всё, а сидеть нужно под юзером, чтобы не просрать ничего (тут дело не только в безопасности от зловредов). Под БД тоже делают иногда read-only юзеров для себя. У меня на работе, например, есть супер-пользователь к БД, которого разблочивают только на несколько минут пока не предоставишь для чего это тебе понадобилось: написать email, поднять ticket на разблокировку, сослаться на тикет с проблемой.

Другое дело, что для домашнего использования всегда можно потерять данные по неосторожности, незнанию. В файловой системе — это Trash, обычно, спасает. Я пишу диссертацию по созданию концепции ОС домашнего использования, там всё идёт к тому, что любое действие должно быть возможно откатить. Даже навесив кучу вопросов пользователю, это не поможет, можно только потом понять, что удалилось (или изменилось). Например, на сегодня почти ни в какой системе любые настройки нельзя откатить. Ещё, после сохранения и закрытия файла уже нельзя будет откатываться по истории, если только не учитывать документы в системе контроля версий с сохранением каждого действия, а такое в домашнем сегменте — редкость.
Лично я когда делаю любой UPDATE или DELETE на живых данных всегда заключаю команду в BEGIN TRAN запрос ROLLBACK TRAN. Потом сморю нет ли желания сказать «Бл*дь!», если так нет, меняю ROLLBACK TRAN на COMMIT TRAN и выполняю еще раз. Хотя и бекапы есть, и лог транзакций, и на продакшене в крайнем случае, но так спокойнее.
програмить удаление надо по принципу корзины или отложенного удаления без всяких глупых вопросов. Тогда можно или вытянуть из корзины или иметь минуту на обдумывание правильности решения и жмякания в ссылку отменить. И не задалбываться рутинными Да/Нет
Sign up to leave a comment.

Articles