Pull to refresh

Comments 237

В 1С существуют объектные блокировки, так называемые "оптимистические" и "пессимистические". Кто придумал термин, не знаю, убил бы :). Совершенно невозможно запомнить, какая из них за что отвечает.

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


И эти термины придуманы вовсе не в 1C.

Я знаю, что они придуманы не в 1С, и даже написал там про «литературу общего назначения». Однако, термины неочевидные (по крайней мере, мне лично). Что такое, например, «ускоряет худший случай»?

Худший случай — это случай когда объект уже кем-то занят. Для того, чтобы его ускорить, нужно обнаружить этот факт как можно раньше. Для этого и используется вызов Заблокировать(). При этом в лучшем случае, когда объект свободен, работа замедляется из-за лишнего вызова.


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

Я постараюсь запомнить эту семантику, но есть риск, что опять запутаюсь :) В любом случае, спасибо!
Попробуйте использовать «ресторанную» аналогию.
«Оптимист» просто поедет в заведение в надежде, что там окажутся свободные столики. «Пессимист» позвонит заранее и забронирует столик (либо узнает, что в данном заведении свободных столиков нет и не потратит время на пустую поездку).
Классная аналогия. Теперь точно запомнится. Спасибо!
    Если ТранзакцияАктивна() Тогда
        ОтменитьТранзакцию();
    КонецЕсли;

Я обычно в случае отмены вместо Если делаю Пока. Кто его знает, что там в вызванной процедуре наоткрывалось.
На мой взгляд, это все же перебор. В идеале — я должен отменить именно свою транзакцию, т.е. «вычесть» именно свою единичку, добавленную в счетчик транзакций.
Ну, это если абстрактная процедура в вакууме. Какая-нибудь экспортная в общем модуле. Если список мест применения известен и ограничен, то отмена транзакции в вложенной процедуре не имеет смысла, а на внешнем контуре — полный откат циклом.
Вот как раз писать «Пока ТранзакцияАктивна() Цикл» нельзя, потому что вы можете закрыть чужую внешнюю транзакцию, внутри которой вызывался ваш код. Отменять эту внешнюю транзакцию ваш код не должен, так как внешний код сам должен обрабатывать свои транзакции.
Согласен. Я выше уже уточнил, что это стоит делать на внешнем контуре транзакции.
В чем заключается троллинг?
Посмотрите под спойлером «Код на английском»
А вот вы о чем. Просите, я Вас не правильно понял)
Можете пояснить зачем вообще напрямую работать с транзакциями? Обычно пишут код на вставку/обновление/удаления данных из базы данных, а движок базы данных уже сам решает где, когда и в каком порядке проводить транзакцию. В 1С нет механизма работы с базой данных без транзакций?
В 1С нет механизма работы с базой данных без транзакций?

А что, где-то можно работать с базой данных без транзакций?
Скорее всего имелось ввиду: явная и неявная транзакции…
Можно ли писать вместо
НачатьТранзакцию()
Пока Выборка.Следующий() Цикл

    Попытка
    Объект.Записать();
    Исключение
           Сообщить("Не получилось записать");
    КонецПопытки;

КонецЦикла;
ЗафиксироватьТранзакцию();


Что-то типа этого:

Запрос."Обновить 'таблицу' сделать 'поле1' = 'значение1' где 'ид записи' = 1"
нет, нельзя. но можно и не открывать явно транзакцию, при каждой записи объекта она будет открываться и закрываться неявно.
другой вопрос, что часто требуется согласованно менять данные в нескольких таблицах, и это без явного управления транзакциями сделать невозможно.

p.s. возможно, конечно, на несвязанных неявных транзакциях, но в случае эксепшена вы сами себе злобный буратино.
UPDATE/DELETE и прочие DDL инструкции в запросах 1С запрещены, т.к. используется подход «бизнес-объектов», а не записей в таблицах БД. Каждое изменение данных сопровождается (может сопровождаться) каким-то прикладным кодом, проверками по бизнес-логике, etc. Поэтому, прямая модификация таблиц от разработчика закрыта. Только через слой бизнес-логики объекта.
да где угодно. Цитата от микрософт: «всегда можно переписать программу так, что транзакции будут не нужны».

И что, MS SQL Server позволит выполнять операции с базой данных вне транзакций? Серьезно?

я тебе про «написание программ», а ты мне про «MS SQL Server» — не кажется ли тебе, что это разные вещи?
Не помню, чтобы пил с Вами брудершафта, коллега.
Откройте для себя термин целостность базы данных.
Ну, пример.
Вы переводите деньги со счета на счет.
С одного, допустим, успешно сняли.
А вот при добавлении денег на второй счет по какой-то причине запись не прошла.
Без явных транзакций деньги просто потеряются в вашей кривой программе. Да, в тот самый момент, когда код будет начислять вам зарплату:)
Автор, я не претендую на абсолютную истину, но где-то читал, что верна следующая конструкция:

Попытка
НачатьТранзакцию();

Исключение
КонецПопытки;
НачатьТранзакцию();
Попытка

ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Внутри блока Исключение нельзя оставлять пустоту. Полный игнор ошибок это как ездить ночью без фар по встречке, да еще и предварительно слив тормозуху.
Автор не упомянул, что только после не восстановимого исключения мы можем получить ошибку «В данной транзакции уже происходили ошибки»
(https://its.1c.ru/db/content/metod8dev/src/developers/platform/metod/other/i8102313.htm?_=1533744348)
Автор просто не сказал, что мы в 1С не знаем — восстановимое или невосстановимое исключение мы поймали
Невосстановимая ошибка не ловится. Если такая ошибка случилась — она вылетает из встроенного языка безо всяких Попытка Исключение, сразу в интерактив. А если исключение поймалось — оно восстановимое. Но от этого ошибка в транзакции все равно наступит.
its.1c.ru/db/v83doc/bookmark/dev/TI000000528
Ну и цитата из ссылки:
В зависимости от характера произошедшей ошибки возможны различные сценарии обработки этой ситуации.
Если произошедшая ошибка не связана с базой данных, то возможно продолжение транзакции и дальнейшей работы модуля. Если разработчик считает это необходимым, он может отменить транзакцию или, наоборот, продолжить выполнение транзакции, если произошедшая ошибка не нарушает атомарность транзакции.
Если же исключительная ситуация была вызвана ошибкой базы данных, то система фиксирует факт возникновения ошибки в этой транзакции, и дальнейшее продолжение транзакции или ее фиксация становятся невозможны. Единственная операция с базой данных, которую разработчик может произвести в данной ситуации, ‑ это отмена транзакции. После этого он может осуществить попытку выполнения этой транзакции еще раз.
Я как раз про то, что мы не знаем — с базой данных связана ошибка или нет. В отсутствие типизированных исключений сложно сказать — пометила платформа транзакцию, как ошибочную или нет.
Тогда это не про [не]восстановимое исключение :)
Причина исключения, действительно, не предоставляется
Вопрос терминологии. С точки зрения транзакции — оно невосстановимое. Можно только откатиться. С точки зрения ВМ 1С — восстановимое, т.к. его можно поймать
Есть подозрение, что лучше использовать ту терминологию, которая и в официальной документации. Просто для того, чтобы не было многозначных толкований простых фраз.
И, насколько я понимаю, невосстановимость исключения заключается в том, что в некоторых случаях нельзя продолжить исполнение встроенного языка.
Я не помню места в документации, в котором бы давалось определение невосстановимого исключения. Но я и не знаю ее наизусть, возможно, упустил. Покажете?
Ссылка выше дает такое определение.
Но, с формальной точки зрения, оно отличается от того, что я сказал выше :)
nixel, o4karek

Невосстановимая ошибка не ловится

не правда, у вас не верное понимание что есть восстановимая, а что нет.

Берем для примера такой код
	НачатьТранзакцию();
	
	ЗаписатьВБазу();
	Попытка
		а = 1 /0;
	Исключение
	КонецПопытки;
	
	ПрочитатьИзБазы();
	
	ЗафиксироватьТранзакцию();


это есть восстановимое исключение т.к. ничего страшного не произойдет в этом случаи.
Теперь меняем код на такой:
	НачатьТранзакцию();
	
	Попытка
		ЗаписатьВБазу();
        Исключение
	КонецПопытки;
	
	ПрочитатьИзБазы();

	ЗафиксироватьТранзакцию();


в модуль объекта при записи мы переносим эту строку
а = 1 /0;
Вот теперь при попытки прочитать из базы мы получим ту самую ошибку «В данной транзакции уже происходили ошибки»
Вот это и есть невосстановимая ошибка!

А вот если мы перенесем это в событие при записи
Попытка
а = 1 /0;
Исключение
КонецПопытки;

ошибки «В данной транзакции уже происходили ошибки» не произойдет. Так что понятие «восстановимое/не восстановимое исключение» это довольно таки тонкая грань которую нельзя формализировать.
:)
В данном случае я просто постулирую, что я прав, а вы — нет :)
Причин много, но одна вот:
Невосстановимые ошибки ‑ это ошибки, при возникновении которых нормальное функционирование системы «1С: Предприятие» может быть нарушено, например, могут быть испорчены данные. При возникновении невосстановимой ошибки выполнение системы «1С: Предприятие» прекращается в любом случае.

Это цитата из платформенной документации. Именно это фирма «1С» считает невосстановимой ошибкой. При этом совершенно безразлично, где эта ошибка случится — в транзакции или вне транзакции. Результат будет один — при возникновении невосстановимой ошибки ваш код полностью прекратит свою работу. При этом прекратит работу также и клиентское приложение, а также — может умереть и сервер.
А дальше опять читаем цитату из поста habr.com/post/419715/#comment_18990099 или или по ссылке из того же поста читаем весь раздел документации.
Не надо придумывать, что фирма «1С» понимает под какими-то терминами, особенно если сама фирма «1С» привела в своей документации прямые определения :)
Не нужно приводить всякого рода выдержки из документации, просто проверьте. Смысл не в том какое определение дано восстановимому или не восстановимому исключению, смысл в том как это все работает. И основной посыл EvilBeaver (как мне кажется) был в том, что бы обратить на это внимание и возможно научить кого-то. А вы тут про терминологию.
Любой спор — это спор об определениях. Если использовать неверные определения — спор бесполезный.
Просто поверьте мне безотносительно — невосстановимое исключение вы во встроенном языке не поймаете (т.е. это аксиома). Соответственно: все остальные исключения — восстановимые. И вот как восстановимое исключение обрабатывается платформой при использовании транзакций — это и есть тема обсуждения текущей статьи.
И чтобы обсуждать вопросы с единым использованием применяемых терминов — лучше использовать терминологию вендора, а не придуманную самостоятельно.

По вашей же ссылке во втором абзаце написано обратное. Данная ошибка может возникать при восстановимом исключении на работе с БД. Причем не обязательно на записи, исключение при чтении тоже выкинет "в данной транзакции уже происходили ощибки" при повторном обращении к бд

Ошибся в первом сообщении, вот полная конструкция:

Попытка
НачатьТранзакцию();
Действия();
ЗафиксироватьТранзакцию();
Исключение
КонецПопытки;

Почему именно так: все, что находится внутри Попытки, убивается в случае ошибок в Исключении, т.е. нет нужды отменять транзакцию, она отменится автоматически. Фиксация транзакции расположена последней в очереди внутри попытки. По-моему, вопрос закрыт. Как вы считаете?

Она отменится автоматически только в конце серверного вызова, либо когда кто-то выше по стеку не сделает отмену либо фиксацию. Без явной отмены в блоке попытки весь код выше по стеку, выполняемый после вызова данного куска с попыткой будет в открытой поломанной транзакции

Я пробовал отменять в блоке попытки, вылезала ошибка: «Транзакция не активна»
Значит она неактивна. Перечитайте статью еще раз
А неактивна она, потому что Исключение отменяет транзакцию )
Можно еще вот так, но я не вижу в этом смысла:

Попытка
НачатьТранзакцию();
Действия();
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
Конецесли;
КонецПопытки;
А оставлять пустым блок Исключение — это вообще преступление
1С. Ошибка «В данной транзакции уже происходили ошибки».
Причина появления данной ошибки — вызов исключительной ситуации во вложенной транзакции. Попытка создает неявную транзакцию, тем самым исключение откатывает транзакцию полностью.

Внутри вложенной транзакции вызывается исключительная ситуация, что откатывает всю транзакцию и некорректно отрабатывает. Например:

НачатьТранзакцию();


Попытка

Исключение // если тут будет вызвано исключение — то вы увидите ошибку «В данной транзакции уже происходили ошибки»

КонецПопытки;

КонецТранзакцию();
Во избежания таких ситуаций, нужно избегать использования попытки внутри транзакции.

avditor.ru/index.php/programmirovanie-1s/102-1s-oshibka-v-dannoj-tranzaktsii-uzhe-proiskhodili-oshibki
Ну и чем написанное вами отличается от написанного в статье?
Вы запутались. Еще раз по полочкам:
1) Если внутри открытой транзакции происходит ошибка, то происходит откат ранее записанного и прерывание выполнения (без использования попыток мы вообще не попадем на «В данной транзакции уже происходили ошибки»)

2) Не очень знаком с неявными транзакциями «попыток» (они явно не полноценные, так как если в попытке сделать запись нескольких элементов и на одном из них получить ошибку, то предыдущие останутся записанными в базу), но хорошо, что они закрываются и не выходят за рамки блока try-catch. А тем временем у нас «сверху» все еще есть активная транзакция.

3) Использование «попытки» внутри активной транзакции позволяет продолжить выполнение кода после «пойманной» ошибки, но каждая попытка продолжить работу с СУБД (даже чтение) будет генерировать новую ошибку (вышеозвученную), пока транзакцию программно не закроют (или не прекратится выполнение кода, но тогда произойдет откат).
без использования попыток мы вообще не попадем на «В данной транзакции уже происходили ошибки»

Всмысле?
Просто после начала транзакции вызовите исключение и тгда после любого обращения к БД будет «В данной транзакции уже происходили ошибки»
У вас код продолжает выполнение как ни в чем не бывало, если бросать неотлавливаемые исключения? Особая сборка платформы? :)
Да, точно, согласен. Без попытки никак )
Я хотел пройти мимо єтого поста, но немного смутил такой вот незаметній комментарий с коде
// Путевка в ад, серьезный разговор с автором о наших сложных трудовых отношениях.

То есть Вы не только сами это используете но и других учите. Более того как бы незаметно намекаете что Вы не тоько учите но вершите там судьбы. Поэтому разберу немного В/аш подход.

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

О, Господи… ну вот вам, господа, и пример компетенций в отрасли.
Да, но нет в коде у разработчиков на местах. У них часто и подписки на ИТС нет, по которой эти стандарты можно прочесть. А Хабр читают все. Тут, глядишь, и заметят
Так, да не так. В отличии от стандартов разработки, ваш подход, лично меня напрягает тем, как раз тем, что предполагет умышленное расхождение счета открытых и закрытых транзакций.

Посмотрите внимательно — что произойдет, если «ДелаемЧтоТо()» будет написана по стандарту 1С и отменит транзакцию внутри себя? Транзакция в нашей функции не будет завершена и вывалится в исключение с незакрытой(!) транзакцией. В стандарте 1С такое исключено — при любых ошибках транзакция безусловно закрывается.

Более того, где гарантия, что ЗафиксироватьТранзакцию() — самая главная часть нашей логики вообще выполниться? В приведенной методике ловить это прискорбное событие должна почему-то вызывающая функция, которой, в общем виде, до этого нет никакого дела.

Вариант от 1С обеспечивает более целостную и модульную обработку транзакций и их ошибок, и гарантированно фиксирует или откатывает транзакцию.
НачатьТранзакцию();
Попытка
    ДелаемЧтоТо();
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    лог.Сообщение(лог_Ошибка, ПодробноеПредставлениеОшибки());
КонецПопытки;
Вы сейчас про какой мой фрагмент кода говорите? Просто я вроде как про то же самое пишу и не вижу каких-то противоречий
Да, про код. Остальное все верно и я полностью с вами согласен.

Вы в коде отчего-то предположили, что вложенная функция при ошибке непременно вызывает исключение или не обрабатывает возникающее внутри себя исключение. Это очень странная предпосылка — даже невнимательные 1С-ники все же пишут код который в норме не вызывает исключения или стараются хоть как-то обработать исключения в своём коде.
Интересно, если ошибка произойдёт в ЗафиксироватьТранзакцию(), платформа разве её сама не откатит?
Т. е. в этом случае мы не потеряем текст ошибки на строке ОтменитьТранзакцию()?
Нет. Транзакция будет «битой» и любые последующий попытки её зафиксировать будут вызывать исключение, при этом ТранзакцияАктивна() будет возвращать Истина. Так что только ОтменитьТранзакцию() корректно сможет её завершить.
Хмм… Это точно? Получается платформа неявно откатывает транзакцию при любых ошибках внутри неё, КРОМЕ данного случая.
Получается ещё одно исключение из правил, которое нужно знать.
А когда ещё платформа откатывает транзакцию кроме ОтменитьТранзакцию()? При возникновении ошибки внутри транзакции транзакцию становиться невозможно зафиксировать, но транзакция при этом всё еще активна.
Нет. Платформа НИКОГДА не откатывает неявно транзакции при ошибках. Об этом и статья. При возникновении ошибки платформа транзакцию не отменяет и исключение летит вверх по стеку при все еще активной транзакции.
Посмотрите внимательно — что произойдет, если «ДелаемЧтоТо()» будет написана по стандарту 1С и отменит транзакцию внутри себя?

Вариант от 1С обеспечивает более целостную и модульную обработку транзакций и их ошибок, и гарантированно фиксирует или откатывает транзакцию.
НачатьТранзакцию();
Попытка
ДелаемЧтоТо();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
лог.Сообщение(лог_Ошибка, ПодробноеПредставлениеОшибки());
КонецПопытки;

Вижу противоречие или неточное формулирование
Если в «ДелаемЧтоТо()» будет откат транзакции, которую не она начала, код вывалится по ошибке на фиксации транзакции.
Автор статьи как раз и говорит, что нужно управлять только своими транзакциями, а не чужими.

Даже если внутри «ДелаемЧтоТо()» по ошибке будет отменена наша транзакция, то совсем необязательно мы попадем в Исключение, но точно не сможем зафиксировать транзакцию.
Даже если внутри «ДелаемЧтоТо()» по ошибке будет отменена наша транзакция, то совсем необязательно мы попадем в Исключение, но точно не сможем зафиксировать транзакцию.

Вы невнимательны :(


я уже писал, напишу еще раз, чуть перефразировав:
Если нет активной транзакции, что верно при нашем условии "в «ДелаемЧтоТо()» будет откат транзакции", тогда ЗафиксироватьТранзакцию само выдаст исключение :(


в итоге мы в любом случае попадем в исключение.

в итоге мы в любом случае попадем в исключение.

Безусловно. Но есть момент:
1. Мы в итоге не отменили свою транзакцию.
2. Наше исключение вынужден ловить вышестоящий код, и он же должен решать что делать с нашей транзакцией. Это при том, что у него может быть и своя собственная транзакция, и если обработка транзакции написана так же, то проблема идет еще на уровень выше.

Как-то это не очень масшабируемо и модульно.
У них часто и подписки на ИТС нет, по которой эти стандарты можно прочесть.

Методическая поддержка по платформе и стандарты разработки доступны без подписки (в отличие от док на разные продукты).
Это действительно так, но народ не знает. «Раз URL стандартов начинается с „its.1c.ru“, то я даже и заходить не буду, все равно подписки нет» — примерно такие настроения в головах, когда посылаешь читать стандарты.
Справедливости ради открыли они их недавно. Еще года полтора назад заходил — были закрыты. Не говоря уж про доки к БСП. А сейчас да, можно пользоваться, даже канал в телеграме есть с рассылкой отдельных пунктов и пояснениями.
Ух ты, круто! Ссылку на канал можно?
t.me/v8std
Я так понимаю канал неофициальный, но ведет кто то из сотрудников.
Всё так, t.me/v8std веду я. Разбираю день по стандарту, чтобы было не скучно ехать в метро. Подписывайтесь!
Перешел, почти все ссылки нужные серые (только глоссарий разработчика судя по цвету открыт, правда не прогружается инфа) При переходе по серым ссылкам окно авторизации.
Методическая поддержка открыта давно — с августа 14 года.
Стандарты действительно открыты недавно — с апреля 18 года.

EvilBeaver


Так стандарты же примерно полгода назад открыли всем :)
Читай без подписки на здоровье.


Кстати в АПК проверка конкретно этого стандарта по транзакциям тоже реализована.

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


То, что ИТС постепенно открывается — это очень хорошо. Но популяризируете вы этот факт мало.

Осталось разрешить их поисковикам индексировать, чтобы народ чаще на всю эту информацию натыкался. Большинство сначала в Google полезут по любому вопросу.
Исключение из ЗафиксироватьТранзакцию не уменьшает счетчик транзакций в платформе. И если метод, написанный с вашим подходом, находится в глубине стека прикладного кода, то есть риск того, что код выше по стеку будет получать ошибки, и проблема с «В этой транзакции уже происходили ошибки» повторится вновь.
Поэтому рекомендуемый в документации подход, при котором ЗафиксироватьТранзакцию находится внутри Попытка/Исключение — более правильный.
Если исключение в Зафиксировать не уменьшает счётчик, то Вы правы. Однако, я не могу придумать ситуацию при которой Зафиксировать бы выдавало исключение на активной транзакции, при значении счетчика равном тому, который установился при начале транзакции… Иными словами, если парность операторов работы с транзакциями не была нарушена, то Зафиксировать не должна бросать исключение. Я не помню на практике таких ситуаций. Просветите, пожалуйста
В файловом варианте ЗафиксироватьТранзакцию кинет исключение при отсутствии места на диске. В серверном это сильно менее вероятно, но возможность окончания места для журнала транзакций СУБД — нельзя списывать.
Да и вопрос не в том, когда ЗафиксироватьТранзакцию может стрелять исключением. Вопрос в том, что в вашей логике — изъян. И если я прав (а я прав :) ), то своей статьей вы провоцируете разработчиков 1С на неправильное поведение, т.к. вы стремитесь к тому, чтобы счетчик транзакций в начале и конце метода совпадал, а я предъявляю ситуацию, когда он не совпадает.
Я, пожалуй, соглашусь. Фиксацию транзакции лучше разместить внутри попытки. Исправлю. Однако, настаиваю, что отмену транзакции в блоке Исключение, все-таки, нужно выполнять в условии, а не сразу. В статье пояснил почему — из-за риска затереть возникшую проблему новой.
В статье пояснил почему — из-за риска затереть возникшую проблему новой.


Если выставлять условие на отмену транзакции, то не выполняется требование безусловности фиксации или отмены транзакции внутри кода.
Чтобы не породить новую проблему (исключение при отмене) вызов ОтменитьТранзакцию() тоже должен быть обернут в Попытка-Исключение.

Условие в блоке исключений нужно только если вызывать код написанный не по стандарту.
В общем случае он не нужен.
Потому все просто: если коду вызываемому доверяешь — проверка не нужна.

От доверия до недоверия — один шаг. Я предпочитаю писать условие. Так код становится надежнее. А надежность — это хорошо.

Мой перфекционизм требует приводить к стандарту вызываемый код. :)

Кто сказал, что стандартны перфектны (т.е. идеальны)?

Не идеальны. Но они проверены специалистами разных групп и разработчиками платформы и согласованы между собой.
Все ошибки и пожелания к стандартам рассматриваются и исправляются в рамках партнерской конференции БСП (https://partners.v8.1c.ru/forum/forum/186/topics).

UFO just landed and posted this here

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

UFO just landed and posted this here

Появилась Community версия для бранч плагина? 0_о

UFO just landed and posted this here

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


изменение уже существующего и используемого кода может быть совсем тяжелым :(


PS если что, принцип "бойскаута" я люблю и сам применяю

Понятное дело, но тут тоже можно поспорить :)
Есть база данных угроз ФСТЭКА, там есть замечательная угроза УБИ.165
https://bdu.fstec.ru/threat/ubi.165
Угроза включения в проект не достоверно испытанных компонентов


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

Я не помню на практике таких ситуаций. Просветите, пожалуйста


Как минимум, один вендор СУБД прямо говорит, что при вызове фиксации или отмены транзакции всегда надо быть готовым поймать исключение:
Try/Catch exception handling should always be used when committing or rolling back a SqlTransaction. Both Commit and Rollback generates an InvalidOperationException if the connection is terminated or if the transaction has already been rolled back on the server.


Конечно, на практике такое встретить сложно, но ведь мы как раз о том как правильно работать с транзакциями, а не о том, что на практике что-то кто-то не видел.
На всякий случай уточню, что вы привели документацию по .net, а не по СУБД. Я что-то проспал и новая версия 1с на .net написана? :-)
Вот как правильно зафиксировать транзакцию более менее понятно. А как правильно её отменить?

Очевидный вариант — предполагает два последовательных вызова ОтменитьТранзакцию().
НачатьТранзакцию();
Попытка
    ЧтоТо = ДелаемЧтоТо();
    Если ЧтоТо Тогда
        ЗафиксироватьТранзакцию();
    Иначе
        ОтменитьТранзакцию();
    КонецЕсли;
Исключение
    ОтменитьТранзакцию();
    лог.Сообщение(лог_Ошибка, ПодробноеПредставлениеОшибки());
КонецПопытки;
А чем кинуть исключение не вариант?
Исключение в 1С это просто строка, т.е. сознательно бросать исключение нужно только с расчетом, что его увидит пользователь. Если код не предполагает уведомлять пользователя «матерным» окном на полэкрана, то исключения нужно отлавливать и записывать в лог самостоятельно. И желательно это делать по месту возникновения исключения, а не передавать обработку вышестоящему коду в надежде что уж тот код точно знает что это за исключение и что с ним делать.
сознательно бросать исключение нужно только с расчетом, что его увидит пользователь.

Блин, кто вам такое сказал? Сознательно бросать исключение нужно тогда, когда нужно, когда есть исключительная ситуация, с которой данный слой архитектуры не в состоянии справиться. Мы даже параметризовывали исключения и передавали в них Структуры, когда надо было.
Привет!
Хорошая статья. Единственно только хочу обсудить правильное решение по паттерну, где используется функция ТранзакцияАктивна().
Чтобы выполнить обязательства по одному открытию и одному закрытию транзакции, может быть имеет смысл сначала этот признак записывать в переменную, а потом уже писать участок кода, работающий с транзакцией? Вот так:

ОткрытьЗакрытьТранзакцию = НЕ ТранзакцияАктивна();

Если ОткрытьЗакрытьТранзакцию Тогда
    НачатьТранзакцию();
КонецЕсли;

Попытка
    ДелаемЧтоТо();
Исключение
    Если ТранзакцияАктивна() Тогда // здесь так, потому что 
                                   // транзакции может уже не быть
        ОтменитьТранзакцию();
    КонецЕсли;
    ВызватьИсключение ОписаниеОшибки();
КонецЕсли;

Если ОткрытьЗакрытьТранзакцию Тогда
    ЗафиксироватьТранзакцию();
КонецЕсли;

Так ведь нет никаких обязательств по одному открытию и одному закрытию, есть лишь обязательства их парности.
Замечание не относится к сути моего комментария и вопроса внутри него.
ВызватьИсключение ОписаниеОшибки();

лучше не делать, просто ВызватьИсключение достаточно, так будет проброшено исходное исключение. Синтаксис доступен только в блоке catch


На счет паттерна:


  1. ЗафиксироватьТранзакцию(); обязательно должен быть в блоке Попытка-Исключение
  2. Не должно быть логических операций между НачатьТранзакцию(); и Попытка
  3. ОтменитьТранзакцию(); должна быть первой операцией в блоке Исключение-КонецПопытки

ТранзакцияАктивна() не обязательно использовать для начала новой транзакции, читайте документацию по транзакциям. Открытие новой транзакции в существующей — откроет вложенную транзакцию, закрытие — закроет вложенную, но нужна повторная фиксация чтобы закрыть основную (как раз ваш случай), отмена транзакции отменяет все, включая весь стек сложенных.


Весь код приходит в вид:


НачатьТранзакцию(); // Если транзакция открыта - откроется вложенная
Попытка
    ДелаемЧтоТо();
    ЗафиксироватьТранзакцию(); // если это вложенная - произойдет переход к основной, иначе произойдет фиксация в базу
Исключение
    ОтменитьТранзакцию(); // Отменятся все транзакции, даже если это вложенная
    ВызватьИсключение; // Выбросится изначальное исключение
КонецПопытки;
лучше не делать, просто ВызватьИсключение достаточно, так будет проброшено исходное исключение

Спасибо за совет! Попробовал — яростно плюсую. Не знал что так можно.
Когда автор так написал, я думал, что он просто сократил для наглядности.

По поводу конструкции с условием, сегодня начал вспоминать, откуда оно у меня взялось? И вспомнил, что это была попытка использовать один код, который бы работал как в объектах с автоматическими блокировками, так и отдельно — с управляемыми (старая конфигурация). Нужен пример, но скорее всего там требовался просто более глубокий рефакторинг. Финальный вид, который приведён в статье — максимально правильный, и если появляется необходимость написать иначе — вопрос к правильности такой необходимости.

По остальному, спасибо за объяснение. Я уже понял, что нужно так, почитал комментарии из верхних веток.
Хотя похоже в финальной версии в статье вот это условие похоже тоже не нужно, я так понял оно всегда будет ИСТИНА:

Попытка
    НачатьТранзакцию();
    ДелаемЧтоТо();
    ЗафиксироватьТранзакцию();
Исключение
    Если ТранзакцияАктивна() Тогда // <--------
        ОтменитьТранзакцию();
    КонецЕсли;
    ВызватьИсключение;
КонецПопытки;


Это если посмотреть стандарт «Перехват исключений в коде», как Вы посоветовали ниже.

Это условие далеко не всегда будет Истина, к огромному сожалению. Поэтому и статья написалась.

Если в ДелаемЧтоТо(); будет ОтменитьТранзакцию(); без НачатьТранзакцию(); то будет проблема.
Но это значит, что ДелаемЧтоТо() написан не по стандарту, т.к. не соблюдает парность операций.
Потому проверка для всего кода написанного по стандарту не нужна, а если вызываешь чей-то код, которому не доверяешь — то либо надо сделать проверку, либо, поступить правильно и переписать вызываемый ДелаемЧтоТо() по стандарту.

Получается действительно самый правильный вариант такой, и он мне нравится:

НачатьТранзакцию();
Попытка
    ДелаемЧтоТо();
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

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


нам проверку на активность транзакции диктует опыт использования различных конфигураций 1С, в т.ч. и типовых от 1С, написанных/доработанных специалистами разного же уровня, за многие годы использования 1С.

И мы плавно идем к тому, что не только ЗафиксироватьТранзакцию() может вызвать исключение, но и ОтменитьТранзакцию(). Следовательно, обе функции должны быть обрамлены в Попытка-Исключение, а условие на ТранзакцияАктивна() лишь попытка избежать неминуемого.

И, тут в комментариях есть фрагмент документации от Microsoft который именно так и рекомендует делать.

Про стандарты есть момент, что стандарт это не данное нам откровение свыше и вполне может меняться.
Даже пример кода есть:
transaction = connection.BeginTransaction("SampleTransaction");
try
{
    doWhatever();
    transaction.Commit();
}
catch (Exception ex)
{
    Console.WriteLine("Message: {0}", ex.Message);
    try
    {
        transaction.Rollback();
    }
    catch (Exception ex2)
    {
        Console.WriteLine("Message: {0}", ex2.Message);
    }            
}


И весьма прозрачно транслируется в код 1С:
НачатьТранзакцию();
Попытка
    ДелаемЧтоТо();
    ЗафиксироватьТранзакцию();
Исключение
    Сообщить(ОписаниеОшибки());
    Попытка
        ОтменитьТранзакцию();
    Исключение
        Сообщить(ОписаниеОшибки());
    КонецПопытки;
КонецПопытки;
Интересно, да.

1 Но все-таки ОтменитьТранзакцию имеет намного-намного меньше шансов упасть, если мы предварительно убедимся, что есть открытые транзакции через Транзакция Активна.

2 И пример нехороший совсем.
Зачем «Сообщить» то? и где переброс исключений на верхний уровень? и т.п. и т.д.
явное зло.

Не понимаю чем Сообщить лучше ВызватьИсключение.
Обработка вызова исключения корректно дойдет до пользователя и покажет ошибку.
Обрабатывать дополнительно ошибку отмены транзакции не нужно.


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


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

Обрабатывать дополнительно ошибку отмены транзакции не нужно.

Не нужна Попытка как таковая, или не нужно ничего делать в Исключение-КонецПопытки?

Если ничего не нужно делать в блоке исключений (как с отменой транзакции) то и попытка не нужна. Гасить исключения бесследно — плохой тон. Есть очень редкие случаи, когда исключение не приводит к проблемам и можно его заменить на запись в журнал регистрации типом предупреждение, но в общем исключение должно быть выброшено пользователю и при этом платформа сама его запишет в журнал как ошибку.

Гасить исключения бесследно — плохой тон.

Какой уж там «плохой тон». Это прямо запрещено стандартом "Перехват исключений в коде":
3.4. Недопустимо перехватывать любые исключения, бесследно для системного администратора


Про запись в ЖР там тоже есть.
открытые транзакции через Транзакция Активна

Нарушается принцип безусловности — если мы открыли транзакцию, то мы должны или зафиксировать её либо отменить.

Если ОтменитьТранзакцию() выдаст исключение, то значит ДелаемЧтоТо() написана неправильно, и, чем раньше по стеку вызовов мы это отловим, тем лучше.

Зачем «Сообщить» то?

В конкретном примере это «калька» с кода выше, не более.

и где переброс исключений на верхний уровень?

А он точно есть? Может этот код прямо из команды формы вызывается — и пользователь получит «красивое» окно на полэкрана.

Наверно все же запись в ЖР обязательна, а необходимость выброса исключения зависит от бизнес-логики. В стандарте "Перехват исключений в коде" есть три варианта развития событий:
1. Вызов исключения
2. Использование кодов возврата
3. Перехват исключения незаметно для пользователя

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

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

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

Для отладки есть отличный флаг «Останавливаться по ошибке» как раз для таких случаев.

Я не об этом. Я о том, что в зависимости от того разрешена ли отладка в текущем сеансе окно меняется.


Обработка:


&НаСервереБезКонтекста
Процедура ТестВызватьИсключениеНаСервере()

    ВызватьИсключение НСтр("ru = 'Тест'");

КонецПроцедуры

&НаКлиенте
Процедура ТестВызватьИсключение(Команда)

    ТестВызватьИсключениеНаСервере();

КонецПроцедуры

Если включена:


Если отключена:

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

Сценарий транзакции должен начинать транзакцию и завершать ее, находясь в начале стека выполнения на сервере. Вызов извне будет только из клиентского события. Тогда не нужно оборачивать транзакцию в попытку, так как она откатится естественным образом. И никаких НачатьТранзакцию в глубине стека! Можно вызывать процедуры модификации данных, но они не должны начинать транзакции. Только код обработки клиентского запроса на верхнем уровне знает, когда начинается транзакция – собственно, когда обрабатывается клиентский запрос.
никаких НачатьТранзакцию в глубине стека

Вот в корне не соглашусь! Это усложняет изоляцию модулей, увеличивает сложность и вообще…
Изоляция модулей идет в разрез с двумя свойствами транзакции – атомарностью и согласованностью. Между началом и завершением транзакции все изменения данных, в том числе вложенные подоперации, должны быть согласованны.
Не вижу противоречий между ACID и изоляцией модулей. Раскройте мысль, пожалуйста.
Хорошо, пусть у нас есть две процедуры, которые построены аналогично ОченьПолезныйИВажныйКод. Процедура ИзменитьОкладыСотрудников устанавливает новые оклады в справочнике сотрудников, и все это во вложенной транзакции. Вторая процедура ИзменитьОкладыДолжностей записывает оклады в справочник должностей, тоже во вложенной транзакции. Задача написать обработку Индексация, которая устанавливает новые оклады для сотрудников и их должностей.
Если вызвать эти процедуры последовательно, понадеявшись на их корректность, мы потеряем атомарность. Например, ИзменитьОкладыСотрудников сработает, а ИзменитьОкладыДолжностей – нет. В итоге в справочнике сотрудников будут новые оклады, а в справочнике должностей – старые. Нельзя менять оклады сотрудников, не меняя оклады должностей, и наоборот. Эти данные должны быть согласованы. Поэтому нужно главную процедуру обработки выполнять в транзакции.
А внутренние транзакции не имеют смысла. Зачем в процедуре, которая что-то выполняет и сделана для вызова из других процедур, внутренняя транзакция? Все равно будет более общая транзакция, которая объединит все модификации данных и сделает это согласованно. В итоге получается такой вариант:

Процедура ОченьПолезныйИВажныйКод(СписокСсылокСправочника)
    Для Каждого Ссылка Из СписокСсылокСправочника Цикл
        ОбъектСправочника = Ссылка.ПолучитьОбъект();
        ОбъектСправочника.КакоеТоПоле = "Я изменен из программного кода";
        ОбъектСправочника.Записать();
    КонецЦикла;
КонецПроцедуры

Список ссылок нужно получать из запроса ДЛЯ ИЗМЕНЕНИЯ.
Список ссылок нужно получать из запроса ДЛЯ ИЗМЕНЕНИЯ.

Кхм, автоматические блокировки? В 2018 году? Сурово.
Да запросто, если компания использует например модифицированную УТ10.3. Переезд на актуальную версию — весьма желанен. Но хочется это сделать нормально. Ждём бизнес-процессы в расширениях.
Нужна более объемлющая операция, объединяющая эти две. Все просто и естественно

Попытка
    НачатьТранзакцию();
    ИзменитьОкладыСотрудников(); 
    ИзменитьОкладыДолжностей();
    ЗафиксироватьТранзакцию();
Исключение
   // здесь код из статьи
КонецПопытки;


В примере нам неважно — есть ли вызовы НачатьТранзакцию внутри операций. Нам важно, что ОБЕ они должны быть выполнены атомарно. Это обеспечивает обрамляющая транзакция, а внутренние транзакции этих методов становятся просто «увеличителями счетчиков». Таким образом целостность обеспечивается просто и прозрачно.

Обычная комбинаторика модулей и повторное использование, на этом держится весь IT.
А что дают внутренние транзакции?
Они дают атомарность на уровне абстракции метода ИзменитьОкладыСотрудников.

Предположим, у вас в этом методе должны атомарно выполниться изменения по всем сотрудникам сразу. И этот метод вы можете вызывать, как в рамках указанного примера (вместе с должностями), так и сам по себе — только по сотрудникам. Тогда этот метод изолирован и независим. Он внутри транзакционно по всем сотрудникам делает начисления. А еще, его можно повторно использовать в более крупной операции, когда и по сотрудникам и по должностям меняем оклады.

RollBack забыл первой строкой в исключении

Т.е. вы допускаете наличие неявных вложенных транзакций, но против явных? Это, как минимум, непоследовательно.
Не должно быть ни явных вложенных транзакций, ни неявных (Попытка внутри транзакции).
И как записать что-либо в базу без неявной транзакции? Как проконтролировать, что внутри неявной транзакции нет других неявных транзакций (например при проведении документа не записываются наборы записей каких-либо регистров)?
Что значит без неявной? Запись объектов выполняется в неявной транзакции. А вот проконтролировать, что внутри транзакции нет вложенных транзакций, может только разработчик. Если он, конечно же, станет придерживаться архитектуры приложения без вложенных транзакций (как явных так и не явных) внутри основной транзакции (явной или неявной).
Вот именно. Запись объектов всегда производится в неявной транзакции. Внутри неявной транзакции могут быть другие неявные транзакции, которые тоже могут иметь свои вложенные неявные транзакции. И это не только допустимо, но и нормально для платформы.

Почему вы отказываете в праве вложенности явным транзакциям?
Во-первых, вложенные они только на уровне синтаксиса. Всегда действует только транзакция самого верхнего уровня.

А во-вторых, во вложенных транзакциях нет никакой необходимости. Атомарность – первое свойство транзакции. Изменения данных инициирует клиентский запрос, который может быть вызван нажатием кнопки в форме, да и вообще любым интерактивным действием, это может быть вызов web-сервиса или запуск платформы из bat-файла с автоматическим выполнением обработки, обращение через COM-соединение и так далее. Во всех этих случаях есть главная процедура, которая обрабатывает клиентский запрос. Именно она и должна начинать и фиксировать транзакцию. С точки зрения пользователя вложенных транзакций нет. Он либо получает атомарно изменения, которые инициировал, либо нет. В этом и заключается атомарность.
Попытка внутри транзакции это просто try-catch и ведет себя он ровно так же, как и вне транзакции. Почему вы приплетаете Попытки к механизмам неявных транзакций??
Да, соглашусь этот вариант правильный, и он мне нравится:
Попытка
    НачатьТранзакцию();
    ДелаемЧтоТо();
    ЗафиксироватьТранзакцию();
Исключение
    Если ТранзакцияАктивна() Тогда
        ОтменитьТранзакцию();
    КонецЕсли;
    ВызватьИсключение;
КонецПопытки;

Вот только не понимаю, зачем нужно
ВызватьИсключение;
Чтобы не терять стек вызовов исходного исключения

Хватит изобретать велосипед, есть пример в стандарте "Перехват исключений в коде"


3.6. При использовании транзакций следует придерживаться следующей схемы обработки исключений в коде на сервере:


// 1. Начало транзакции
НачатьТранзакцию();
Попытка
 // 2. Вся логика блокировки и обработки данных размещается в блоке Попытка-Исключение
 Запрос = Новый Запрос("...");
 Выборка = Запрос.Выполнить().Выбрать();
 Пока Выборка.Следующий() Цикл
  ... 
 КонецЦикла;

 // 3. В самом конце обработки данных выполняется попытка зафиксировать транзакцию
 ЗафиксироватьТранзакцию();
Исключение
 // 4. В случае любых проблем с СУБД, транзакция сначала отменяется...
 ОтменитьТранзакцию();
 // 5. ...затем проблема фиксируется в журнале регистрации...
 ЗаписьЖурналаРегистрации(НСтр("ru = 'Выполнение операции'"), УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
 // 6. ... после чего, проблема передается дальше вызывающему коду.
 ВызватьИсключение;
КонецПопытки;

Поскольку исключение не отменяет транзакцию сразу, но запрещает успешное завершение транзакции, то все вызовы НачатьТранзакцию, с одной стороны, и ЗафиксироватьТранзакцию или ОтменитьТранзакцию, с другой стороны, должны быть парными.


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

Этот пример тоже не идеален, но не с точки зрения работы с транзакции, а с точки зрения работы с исключениями.
Нормальный программист не будет помещать код, который не должен генерировать исключений в try-catch, как например это сделано с созданием объема Запрос в вашем примере. Ровно так же, как и перехват исключений потенциально разной природы (к сожалению типизации в 1С нет) одним блоком — плохой стиль.
Потом, привыкнув, программисты начинают оборачивать вызовы функций в try-catch, и сиди потом, гадай, действительно там предполагается выброс исключений или они просто от «быдлокода» страхуются.

Что верно то верно.
Но работаем с тем, что имеем.


Сколько раз видел, как методы БСП оборачивают в try-catch, не смотря на то, что эти методы были спроектированы так, чтобы никогда не выбрасывать исключений...


Как с этим бороться? Пока ответа нет.

Вы ссылочку-то на нормальный workround как настроить ELKу для журнала 1С от начала до конца дайте что ли. Только, пожалуйста, не на любимый infostart, где все сводиться к запуску некоего поделия, написанного на VB и выгружающего raw данные, которые потом надо как-то оттранслировать так, чтобы ELKа показывала журнал в том же виде, что и 1С. А то ваши коллеги одинэсники такой ёлкой пользоваться отказываются. А пара сисадминов — не одинэсников уже месяц с выгрузкой справиться не могут. Хоть и все на infostart'е прошерстили. Я вот влез в это дело и очень понял почему ELK-стек под 1С только единицы настраивают.
Мы настраиваем и/или консультируем за деньги. Есть дешевые варианты, есть красивые — на любой вкус.
Что и объясняет почему никто нормально настроить не может. Те кто знают «как правильно» пилят бабло, а остальные мучаются. Ну и еще с вашей т.з. очевидно «не очень профессиональные люди, которые не могут нормально настроить систему». Вы бы тогда уж, лучше, про ELK промолчали.
Ну и еще с вашей т.з. очевидно «не очень профессиональные люди, которые не могут нормально настроить систему»


Не понял этот тезис. Я не говорил такого. Постараюсь раскрыть: ELK стек нужен тем, у кого много логов, т.е. на крупных внедрениях, тем у кого есть как минимум лишний сервер под Elastic и прочее. То есть, это чисто корпоративный продукт. И вполне нормально, когда компания просто покупает себе систему хранения логов от 1С не тратя собственные ресурсы на разработку и хождение по граблям. Не совсем ясно, в чем Ваша претензия?
Я вот месяц с настройкой мучаюсь. Нормально не взлетает.Наверное из-за этого меня фраза «А журнал регистрации, как вы знаете, медленный, а ELK-стек для журналов 1С у нас в отрасли настраивают единицы…» очень уж покоробила. Извините.
Единицы настраивают не потому что не хотят или ленятся. А потому что фиг настроишь.
Да, задача непростая. Но решаемая. И камень был не в огород тех, кто настраивает, но пока не удалось. Камень был в огород тех, кто даже не пытается настраивать.
Ну так помогли бы. Если уж не инструкцией, то хотя бы общим направлением. А то пока у меня вышел многозвенный монстр с промежуточной трансляцией на sql сервере, который сегодня этот сервер и положил не переварив объем журнала…
Я вроде и так помог с направлением :) b2b@silverbulleters.org

Если серьезно, то это тема для отдельной статьи или разработки. Эти знания не то чтобы сильно уникальны, но действительно единичны. Мы стараемся продавать ELK-стек для 1С, как услугу. Это не жадность и не «пилить бабло», обычное коммерческое решение, и я не вижу нет ничего крамольного, в том чтобы продавать ПО и его поддержку.

Мы можем настроить ELK или GrayLog или любую другую систему обработки логов, сэкономив компании человекомесяц классного специалиста. Разве это плохо?
Ну и еще один момент: те кто нас знает давно, в курсе нашей политики. Мы охотно помогаем тем, кто помогает другим. Например, вы контрибьютор в наши open-source продукты. Или вы написали обучающую статью, которая помогла другим специалистам. Или вы еще каким-либо образом приносите пользу сообществу. Тогда помочь — святое дело. А подход «Дайте мне» — не работает. С чего бы это вдруг? А вы нам что?
Подтверждаю.
Теперь и я за деньги настраиваю ELK, о котором мне когда-то поведала пуля. Намучался я с ним, правда, достаточно сильно и много.
Кстати, про ELK и 1С можно было много услышать на хакатоне isthisdesign.org. Там ~4 способа отправки ЖР в ЕЛК разобрали
UFO just landed and posted this here
Алексей, спасибо за развернутый ответ. Я в целом не «одинэсник» и этим проектом занялся, т.к. «люди в желтых майках» из своего привычного мирка не выползают. Их и там не плохо (я бы сказал даже неоправданно хорошо) кормят. А если что не так, то всегда можно свалить на «сисадминов». Для реиндексации сразу в Эластик сисадминам уже квалификации очевидно не хватает :-(
Ну и, к слову, куда контрибьютить, если вы закрыли проект на github?
UFO just landed and posted this here
Вы меня извините за занудство, но зачем вы НачатьТранзакцию в финальном варианте засунули внутрь Попытка/Исключение?
Если исключение из НачатьТранзакцию (платформа не увеличит счетчик транзакций), то вы откатите чужую транзакцию при обработке исключения.
Хотя на практике я не видел, чтобы НачатьТранзакцию приводило к исключению.
Засунул для красоты. Так отступы красивее )
Красота — это предмет третьей степени важности (первый — правильность, второй — производительность).
Так вот, на 3 из 4 поддерживаемых СУБД, при вызове НачатьТранзакцию платформа идет в СУБД и открывает там транзакцию. В этот момент может произойти все что угодно: нехватка памяти для очередного соединения с СУБД, разрыв связи, ошибка дисковой подсистемы.
Давайте все-таки делать, как написано в документации: НачатьТранзакцию до начала Попытки, Зафиксировать — внутри, Откатить — в обработке исключения. Стандартная для всех языков практика, в общем-то. У вас статья как называется?
Стоп-стоп. Чем принципиально хуже размещение Начать внутри попытки? Если НачатьТранзакцию() выкинет исключение, то это будет какое-то неперехватываемое исключение работы с СУБД, одно из тех, о которых упоминал o4karek, и которое все равно убъет сеанс целиком, независимо от всяких «Попыток».

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

Насчет производительности — опять непонятно. Как принципиально влияет на производительность размещение НачатьТранзакцию до или внутри Попытки?
Если будет перехватываемое исключение из НачатьТранзакцию, то вы откатите транзакцию не своего уровня. Вы же не проверяете ТранзакцияАктивна до начала транзакции. С чего вы взяли, что ваша транзакция — самая первая?

Насчет производительности — здесь неактуально. Вы написали, что руководствовались эстетическими соображениями для переноса строчки кода. Я же рассматривал более общий случай.
Ок, принято. Хотя науке и неизвестны случаи выброса перехватываемых исключений из НачатьТранзакцию() — если они есть, то Начать лучше расположить ДО попытки.
Вы исходите (исходили) из того, что есть только существующая платформа и все, она не развивается. А что, если в следующей версии будет какая-то доработка, которая добавит возможность, которой раньше не было. И в рамках этой возможности вопрос станет актуальным. Что, обновлять статью, и изменять все конфигурации?
Странно, что никто не обращает внимание на то, сколько не связанного с логикой транзакций кода, попадает под попытку, при подходе ИТС…

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

Еще два вопроса практического характера.
Вдруг он там взял, да и вызвал метод ОтменитьТранзакцию или наоборот, зафиксировал ее? и финальный вариант:
Попытка
    НачатьТранзакцию();
    ДелаемЧтоТо();
    ЗафиксироватьТранзакцию();
Исключение
    Если ТранзакцияАктивна() Тогда
        ОтменитьТранзакцию();
    КонецЕсли;
    ВызватьИсключение;
КонецПопытки;

На каком основании делается вывод, что ДелаемЧтоТо(); начал и не закончил транзакцию лишь однажды, а если это произошло два или более раз?

Подскажите пожалуйста (отбросив на время все прописные истины) на практическом примере пагубность использования подхода, при котором мы будем заботиться о транзакциях там, где это нужно, а не на всякий случай везде. Давайте на минутку представим, что во всей конфигурации мы не втыкаем попытки/исключения там где видим НачатьТранзацию, а отматываем транзакции там, где логика кода позволяет случаться исключениям, ведь таковых существенно меньше, чем первых, не говоря о том, что можно допустить механическую ошибку.
Как пример: перепроведение документов; мы только в том куске кода, который отвечает за “записать” — делаем попытку/исключение с отматыванием тразнаций через ТранзакцияАктивна () в цикле. Отматывая их назад 1) Мы не можем утверждать, что мы отмотали чужую транзакцию (до нас), это мог сделать вызывающий нами ненадежный код 2) Отсутствие вложенности транзакций заставляет их открутить до 0 каждый раз, перед тем, как продолжить работать с базой.
На счет комментария:
Отменять эту внешнюю транзакцию ваш код не должен, так как внешний код сам должен обрабатывать свои транзакции!

Мне не хватает практического опыта, чтобы подтвердить это правило, учитывая, что ЗафиксироватьТранзакцию () вне любой другой пары просто работает как счетчик, ничего не фиксируя в базе.
UFO just landed and posted this here
Я это читаю как

Я очень умный специалист, эти ваши стандарты кодирования для юных падаванов, я сам решу где мне и что писать

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

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

Очень интересно, особенно если учесть, что кол-во этих вызовов в итоге будет одинаковым, просто вынесенным из исключений нижестоящих вызовов.
UFO just landed and posted this here
Нет никакой неприязни, для меня ценны только знания, которые я могу извлечь из общения с кем бы то ни было. Простите, что не робею писать о том, что действительно думаю, и выверял на практике. Но у меня есть мнение, очень непопулярное, что стандарт в данном случае — неверный. Это не потому, что я решил выделиться, а потому, что сталкивался с описываемыми проблемами, и наиболее практичным нахожу способ работы с раскруткой транзакций в том месте, где это требуется логикой программы, а вставка попыток — по моему мнению, ничего не решает, потому что достаточно один раз так не сделать, и при описываемом подходе — мы получим те же проблемы. Я допускаю, что не прав, и буду раз на восполнение пробела знаний, но от того, что вы повторите мне “не применяется правило”, “упадет продуктив” и так далее, это, не сердитесь, больше какое-то запугивание, а не четкий ответ.
На каком основании делается вывод, что ДелаемЧтоТо(); начал и не закончил транзакцию лишь однажды, а если это произошло два или более раз?

Насколько я понял, защита тут от ровно противоположного — вдруг ДелаемЧтоТо() не начинал транзакцию, но ее закончил.

НачатьТранзакцию()
Пока Выборка.Следующий() Цикл

    // чтение объекта по ссылке
    // запись объекта

КонецЦикла;
ЗафиксироватьТранзакцию();


ввести управляемую блокировку во избежание deadlock
ввести вызов метода Заблокировать
обернуть в «попытку», как показано выше

После управляемой блокировки применять Заблокировать () уже не нужно.
приведите пожалуйста сценарий на примере кода выше, при котором в методе Заблокировать () понадобится необходимость.

Привожу.


Секунда 1. (события внутри секунды произошли в порядке следования)


  • Пользователь открыл форму документа
  • Фоновый поток с вашим кодом установил упр. блокировку и сделал ПолучитьОбъект()
  • Пользователь изменил строчку в форме и тем самым установил пессимистическую блокировку (считай, что вызвал ДокументОбъект.Заблокировать())

Секунда 2:


  • Ваш фоновый поток не вызывал ДокументОбъект.Заблокировать() — и это ошибка
  • Ваш фоновый поток, проигнорировал занятость объекта и вызвал метод Записать()

Секунда 3:


  • Пользователь нажал кнопку "Записать", полагая успешную операцию и получил ошибку "Объект изменен или удален" (сработала оптимичтическая блокировка)

Секунда 28:


  • Пользователь пишет письмо в саппорт с предположением (в данном случае резонным), что программисты мудаки плохо работают.

Правильный сценарий


  • В секунду №1 ваш код вызывает ДокументОбъект.Заблокировать() сразу после получения объекта. И если не смог заблокировать — отказывается от операции.
  • Пользователь, пытаясь изменить поле формы на вашем уже пессимистически заблокированном объекте получает внятное диагностическое сообщение "Объект А уже редактируется пользователем Х на компьютере Y".

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

Ясно, что имелось ввиду, спасибо!
Меня резанула строгость паттерна, и если сочтете нужным, то возможно, имеет смысл смягчить формулировку в статье на счет Заброкировать (), потому что в высоко нагруженных системах, откатывать транзакцию может быть непозволительной роскошью ради забывчивого пользователя (усыпляют компьютеры в режиме редактирования, а с мобильным клиентом, эта проблема происходит чаще, плюс мы сталкивались, что оптимистичная не всегда уходит через 20 минут, а если уходит — то с сообщением Session is not available or has been dropped). Нередко наихудшими, являются последствия невыполнения сложных алгоритмом в фоне, чем выброс объекта пользователя.
В качестве соседнего пользователя может ведь быть и другой фоновый сеанс
Куча вариантов, но ожидал увидеть (с v8std):
НачатьТранзакцию();
Попытка
//здесь формирую и провожу документы программно
 ...
 ЗафикситоватьТранзакцию();
Исключение
 ОтменитьТранзакцию()//тут ошибка "Транзакция не активна"
КонецПопытки;

С одной стороны автор пишет «мы обязаны заботиться только о нашей транзакции», и тут же пытается проверить состояние общего счетчика транзакции «Если ТранзакцияАктивна() Тогда ОтменитьТранзакцию(); ...». Транзакцию какого уровня пытаетесь отменить?

На самом деле _код_ не так плох, как вам кажется и оптимистичные подходы имеют подавляющее большинство. Количество вызовов ОтменитьТранзакцию/ТранзакцияАктивна в коде… ммм… УТ11 имеет соотношение 504/50, т.е. 10 к 1 (опять же большая часть которых относится к логике _текущего_ контекста).

Предложенный в статье финальный вариант в общем случае является неверным и имеет смысл применять только как костыль отладки ошибки «В данной транзакции уже ...»
Предложенный вариант более безопасен с точки зрения логирования фоновых процессов и в тексте даже выделено жирным шрифтом — почему введено условие с проверкой активности
И еще вопрос: где начинать транзакцию? До начала попытки или в начале самой попытки? Есть вообще разница?
В стандарте — до. Я, со своей стороны, разницы не вижу. Хотя допускаю, что она есть. Можем прямо тут и выяснить, кстати говоря. Случай подходящий.
UFO just landed and posted this here
нет никакой нужды добавлять эту инструкцию в попытку от слова совсем

Нет нужды, кроме красивых отступов, про которые писалось выше.
До попытки. Поищите мои комментарии выше.
UFO just landed and posted this here
Ну а чо, холивар это завсегда весело. Добавить для пущего котят, политоты, сисег и разжиганий. Смешать но не взбалтывать :)
Кстати, интересно было бы почитать про стандарты оформления кода на разных языках
UFO just landed and posted this here

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

UFO just landed and posted this here
Сонар содержит собственные проверки. Большинство официальных плагинов с поддержкой языков как раз-таки использует собственные реализации. есть исключения (c#, например, использует проверки на Roslyn), но это единичные случаи.
А как вам такой вариант?

ЦелоеЧисло Ж;
Ж=1;
НачатьТранзакцию(); 
Ж=2;
Попытка 
       ДелаемЧтоТо(); 
       ЗафиксироватьТранзакцию(); 
Исключение 
	Если ( ТранзакцияАктивна() И Ж=2)
		Тогда ОтменитьТранзакцию(); 
	КонецЕсли; 
	ВызватьИсключение; 
КонецПопытки;
Это пример, когда код говорит лучше слов.
Если коротко: Ж

Кстати, объявление переменной в стиле int g, вероятно, говорит, что автор — не 1Сник. Хотя все равно это странный

предположу, что автор комментария считает, что отмена транзакции отменяет переприсвоение переменных.
Смысл я вкладывал такой:
Если инструкция НачатьТранзакцию() выбросит исключение
то попадаем в блок перехвата исключения и при этом
Если ( ТранзакцияАктивна() ) Тогда ОтменитьТранзакцию();
может вполне отменить некоторую другую транзакцию.
Для предотвращения этого введена целочисленная переменная Ж.
Если Ж=2 то значит команда НачатьТранзакцию() прошла успешно и в крайнем случае мы отменим «свою» транзакцию. Если Ж=1 это говорит о том, что команда НачатьТранзакцию() не была успешной (выбросила исключение) и в этом случае мы не будем выполнять ОтменитьТранзакцию(), так как «свою» мы так и не начали.
Если у вас НачатьТранзакцию выбросит исключение то в блок обработки исключения вы никак не попадете, разве что где то выше по стеку. Так что в любом случае Ж будет всегда равно 2, либо до блока исключения выполнение не дойдет.
Хорошо,
а так?

ЦелоеЧисло Ж;
Ж := 1;
Попытка 
       НачатьТранзакцию(); 
       Ж := 2;
       ДелаемЧтоТо(); 
       ЗафиксироватьТранзакцию(); 
Исключение 
	Если (  ТранзакцияАктивна() И (Ж==2)  )
		Тогда ОтменитьТранзакцию(); 
	КонецЕсли; 
	ВызватьИсключение; 
КонецПопытки;
Ну, так по крайней мере отработает как вами и задумано. Но вообще да, перебор уж. Лично я ни разу не сталкивался с исключением в НачатьТранзакцию, так что предпочел бы все что можно поотменять и бить во все колокола что что то случилось)) Вообще не уверен, но если ошибка свалится на НачатьТранзакцию — есть подозрение что транзакция верхнего уровня так или иначе не сможет завершиться, в таком случае не сильно важно где ее отменят, если конечно выше по стеку есть проверка на ТранзакцияАктивна
UFO just landed and posted this here
Не совсем так.
Смысл такой — написать код таким образом, чтобы в случае, если исключение происходило в команде
НачатьТранзакцию()
(при этом транзакция не начнётся — поскольку — исключение выкидывает) тогда команда
ОткатитьТранзакцию()
выполняться не должна, иначе возможен случай, когда мы откатим транзакцию, которую не мы начинали — т.е. откатим «чужую» транзакцию, которая не была начата в этом программном блоке.
Не надо стараться «не откатить чужую транзакцию». Надо заботиться только о своей собственной. Если вложенный код «выпустил» транзакцию из своего скоупа, то это не ваша проблема. Ваш код должен отвечать только за свою транзакцию и не вводить дополнительных конструкций, управляющих сторонними транзакциями.

Ну и потом, перехватывать неначатую транзакцию, имхо, не стоит. Это какой-то фатальный облом, лучше выпустить это исключение наверх и пусть процесс упадет. С ним что-то явно не так, и лучше начать заново.
Я так понимаю thorstein описывает случай когда откатится внешняя а не вложенная транзакция по отношению к текущей в случае исключения на НачатьТранзакцию.

По-моему, предлагаемый в статье паттерн корректно обработает эту ситуацию и без введения переменной

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

	Попытка
		НачатьТранзакцию(РежимУправленияБлокировкойДанных.Автоматический);
		ДелаемЧтоТо();
		ЗафиксироватьТранзакцию();
	Исключение
		Если ТранзакцияАктивна() Тогда
			ОтменитьТранзакцию(); // ОШИБКА: Транзакция не активна
		КонецЕсли;
		ВызватьИсключение;
	КонецПопытки;


Поймал исключение на строке ОтменитьТранзакцию();:
Ошибка при вызове метода контекста (ОтменитьТранзакцию)
ОтменитьТранзакцию();
по причине:
Транзакция не активна


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

То есть функция ТранзакцияАктивна показывает активность транзакции в общем, она не показывает успех запуска процедуры НачатьТранзакцию здесь, или где-то в ДелаемЧтоТо. Следовательно использовать в паре с ней ОтменитьТранзакцию получается не совсем корректно.

Проблему можно обойти, если всё-таки вызывать НачатьТранзакцию снаружи попытки.
Что и требовалось доказать: стандарт 1С — хороший стандарт.
Ну, по хорошему, Должно быть что-то вроде:
Транзакция = НачатьТранзакцию();
Если Транзакция.Активна() Тогда
    Транзакция.Отменить();
КонецЕсли;

Так что я согласен, что в 1с реализация хромает.
В смысле, вы против — транзакции как объекта?

Я не вижу радикального преимущества такого подхода перед существующим

Радикальное преимущество такого подхода в том, что сложнее допустить ошибку. Когда транзакция — объект, программист уже не может случайно отменить чужую транзакцию. А если язык ещё и автоматический вызов деструкторов позволяет, то сразу получаем и защиту от "забытых" транзакций.


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

На самом деле должно быть что-то вроде


Транзакция = НачатьТранзакцию();
Транзакция.Отменить();

Спасибо, познавательно!

Статья реально помогла мне бороться с ошибками, которые унаследовались из старого кода после перехода на более новый релиз платформы 1С.

Sign up to leave a comment.

Articles