Почему функция Вычислить() работает некорректно при перестановке слагаемых и как правильно валидировать числовые строки?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Постановка задачи: странное поведение "коллайдера"

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

  1. Проверяем, что строковая длина и числовое значение (через функцию Число()) совпадают с результатом Вычислить().
  2. Составляем два контрольных выражения: А + "+" + Б и Б + "+" + А.
  3. Сравниваем результаты их вычисления. Теоретически, от перестановки мест слагаемых сумма меняться не должна.

Код функции выглядит следующим образом:


Функция ВычислительРаботаетКорректно()
	
	А = "1.0"; 
	Б = "0.1";
	
    // Предварительная проверка
	Если СтрДлина(А) <> СтрДлина(Б)
		ИЛИ Число(А) <> Вычислить(А)
		ИЛИ Число(Б) <> Вычислить(Б) Тогда
		
		Сообщить("Обнаружено некорректное вмешательство!");
		Возврат Ложь;
	КонецЕсли; 	
	
	КонтрольноеВыражение1 = А + "+" + Б; // А+Б
	КонтрольноеВыражение2 = Б + "+" + А; // Б+А
		
	// Проверка на коммутативность сложения
   	Возврат Вычислить(КонтрольноеВыражение1) = Вычислить(КонтрольноеВыражение2); 
КонецФункции

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

Анализ проблемы: Точка как камень преткновения

Главная проблема кроется в том, как платформа 1С воспринимает числа с плавающей точкой, у которых опущена целая часть (например, .1 вместо 0.1).

Рассмотрим, как работают разные механизмы платформы с такой строкой:

  1. Функция Число(".1"): Это механизм приведения типов (soft casting). Платформа понимает, что пользователь имел в виду число 0.1, и успешно преобразует строку в число.
  2. Функция Вычислить(".1"): Эта функция запускает полноценный синтаксический анализатор встроенного языка. В грамматике языка 1С символ "точка" является оператором обращения к свойствам или методам. Когда анализатор видит строку, начинающуюся с точки, он ожидает объект перед ней. Числовой литерал в коде 1С обязан иметь цифру перед точкой (например, 0.1). Запись .1 является синтаксически некорректной для компилятора.

Именно поэтому "вмешательство", при котором строки превращаются в А = "1." и Б = ".1", ломает логику.

Если мы составим выражение КонтрольноеВыражение2 = Б + "+" + А, то получим строку ".1+1.". При попытке выполнить Вычислить(".1+1.") платформа выдаст синтаксическую ошибку, так как выражение начинается с точки, которая не воспринимается как часть числа.

Решения проблемы

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

Способ 1: Использование блока Попытка-Исключение

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


КонтрольноеВыражение1 = А + "+" + Б; 
КонтрольноеВыражение2 = Б + "+" + А; 
		
Попытка
       // Если одно из выражений некорректно, мы попадем в Исключение
  	    Возврат Вычислить(КонтрольноеВыражение1) = Вычислить(КонтрольноеВыражение2); 
Исключение
	// Значит, строки содержат недопустимые для парсера символы
	Возврат Ложь; 
КонецПопытки;

Способ 2: Проверка на начало строки с точки

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


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

Способ 3: Строгая типизация через XML (Рекомендуемый)

Наиболее надежный способ проверки того, что строка является корректным числом с точки зрения платформы и стандартов обмена данными — использование механизмов XML-сериализации. Метод XMLЗначение работает строже, чем Число, но безопаснее, чем Вычислить.

Алгоритм следующий: мы пытаемся преобразовать нашу строку в число, а затем обратно в каноническую строку XML. Если результат совпадает с эталонным преобразованием, значит, формат корректен.


// Функция для строгой проверки
Попытка
    // Пробуем получить значение типа Число из строки А
    ЗначениеА = XMLЗначение(Тип("Число"), А);
    // Проверяем обратную совместимость или формат
    // В данном контексте достаточно убедиться, что XMLЗначение не упало 
    // и результат адекватен.
        
    // Более простая проверка на соответствие формату XML:
    Если НЕ (XMLСтрока(Число(А)) = XMLЗначение(Тип("Строка"), А)) Тогда
         // Логика проверки может варьироваться, но суть в использовании XML-методов
    КонецЕсли;
Исключение
    Возврат Ложь;
КонецПопытки;

Однако наиболее элегантное решение, предложенное в обсуждении, основано на сравнении строкового представления.

Способ 4: Отказ от функции Вычислить в пользу явного преобразования

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


Функция ВычислительРаботаетКорректноБезопасно()
    
    А = "1."; 
    Б = ".1"; // Даже такой ввод будет обработан корректно через Число()
    
    // Используем Число() для безопасного преобразования
    // Число(".1") вернет 0.1, ошибки не будет
    ЧислоА = Число(А);
    ЧислоБ = Число(Б);
    
    КонтрольноеВыражение1 = ЧислоА + ЧислоБ;
    КонтрольноеВыражение2 = ЧислоБ + ЧислоА;
        
    // Сравниваем результаты
    Возврат КонтрольноеВыражение1 = КонтрольноеВыражение2;  
    
КонецФункции

Резюме

  1. Функция Число() и функция Вычислить() используют разные алгоритмы разбора строки. Число() более "всеядна" и допускает форматы вроде .1, тогда как Вычислить() требует строгого соответствия синтаксису языка 1С (наличие ведущего нуля: 0.1).
  2. Символ точки в начале строки интерпретируется парсером Вычислить() как синтаксическая ошибка или некорректное обращение к контексту, что приводит к аварийному завершению кода, если не использовать Попытка...Исключение.
  3. Для безопасности и стабильности кода рекомендуется избегать использования Вычислить() для простых математических операций. Используйте явное приведение типов через Число().

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

← На главную