четверг, 26 августа 2010 г.

Условия и переменные (заметки Кэпа)

Решил я как-то проверить каким образом вычисляются составные условия в 1С 8.1
Не думаю, что профи найдут для себя в этой статье нечто новое или интересное, но Капитан Очевидность продолжает вещать.
Первое, что пришлось проверить - не инициализированные переменные:

На код вроде:
    А = 1;

    Если (
А=1) и (Б=1) Тогда
       
Сообщить("Ок!");
    КонецЕсли;

Выругался сам "компилятор" при попытке сохранить обработку. Логично, так как переменная Б нигде не определена. Но, если обмануть синтаксический контроль следующим образом:
    А = 1;

    Если
0=1 Тогда  // заведомо ложное условие
       
Б=1;        // никогда не выполняющийся код
   
КонецЕсли;

    Если (
А=1) и (Б=1) Тогда
       
Сообщить("Ок!");
    КонецЕсли;

В этом случае при сохранении ошибки синтаксиса не будет, а при запуске надписи "Ок!" не появляется. По логике следования кода мы нигде не определяли переменную Б, но код успешно выполняется.
Что говорит нам отладчик?
Обратите внимание, что в самом начале, когда ни одна переменная еще не определена память под них уже зарезервирована и им назначен тип "Неопределено", в отличии от переменной В, которая вообще не упоминается в процедуре.
Сделаем пару шагов:

Теперь все очевидно. Условие совершенно корректно и дает на выходе ложь, так как единица не равна "Неопределено" и вообще разных типов.

Итак, предварительный вывод:
Под переменные, которые могут быть инициализированы, внутри своей области видимости память выделяется в самом начале и они неявно инициализируются типом "Неопределено".

Чисто теоретически следующий код мог бы работать:
    А = 1;

    Если (
А=1) и (Б=1) Тогда
       
Сообщить("Ок!");
    КонецЕсли;

    Если
0=1 Тогда
       
Б=1;
    КонецЕсли;

Но нет. Не проходит синтаксический контроль, что вообщем-то правильно.

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

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

Следующий код работает как положено и выдает правильный результат:
    Товар = Справочники.Номенклатура.НайтиПоКоду("00000026");

    Если  (
ТипЗнч(Товар) <> Тип("Неопределено")) и (Товар.Наименование = "Тест") Тогда
       
Сообщить("Ок!");
    КонецЕсли;

В описании функции "НайтиПоКоду" написано, что:
Если не существует ни одного элемента с требуемым кодом, то будет возвращена пустая ссылка.
Если код не задан, то будет возвращено Неопределено.
Если честно, я не смог сразу добиться от функции возврата значения "Неопределено" и потому поступил проще:
    //Товар = Справочники.Номенклатура.НайтиПоКоду("00000026");
   
Товар = Неопределено;

    Если  (
ТипЗнч(Товар) <> Тип("Неопределено")) и (Товар.Наименование = "Тест") Тогда
       
Сообщить("Ок!");
    КонецЕсли;

Запускаем - бинго! Все работает, несмотря на то, что в составном условии есть обращение к реквизиту переменной "Товар", к "Наименованию", которого у типа "Неопределено" быть не может.
Для проверки попробуем еще два вида условия:
1.
    Если  (ТипЗнч(Товар) <> Тип("Неопределено")) или (Товар.Наименование = "Тест") Тогда
2.
    Если (Товар.Наименование = "Тест") и (ТипЗнч(Товар) <> Тип("Неопределено")) Тогда

В первом случае мы поменяли "и" на "или" в условии, во-втором поменяли местами части условия.

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

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

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

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

Такое поведение, на мой взгляд оправдано и удобно. Сравните, например с похожим кодом в платформе 7.7:
    //Товар = СоздатьОбъект("Справочник.Номенклатура");
    //Товар.НайтиПоКоду(" 52200");
   
Товар = ПолучитьПустоеЗначение();

    Если (ПустоеЗначение(
Товар) = 0) и (Товар.Наименование = "Тест") Тогда
        Сообщить(
"Ок!");
    КонецЕсли;

В отличии от 8.1 он не будет работать, так как проверяются все операнды операции "и", а второй у нас будет выдавать ошибку.
 

Для платформы 7.7 пришлось бы применить следующее:
    Если (ПустоеЗначение(Товар) = 0) Тогда
        Если (
Товар.Наименование = "Тест") Тогда
            Сообщить(
"Ок!");
        КонецЕсли;
    КонецЕсли;

Что на мой взгляд является неоправданным (хоть и небольшим) усложнением структуры кода.

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

Спасибо за внимание и хорошего вам кода!

2 комментария:

Александр Максюк комментирует...

Забавно!
Получается 8 стала более гибкой в операции "И". Код с А и Б отрабатывает и в 7 версии. :) Только вот к переменной В я так и не смог добраться ;)

Green FiLin комментирует...

Странно, но в посте нет ссылки на т.н. "Ленивые вычисления".
Исправляюсь.

Отправить комментарий