При разработке интерактивных элементов интерфейса, таких как калькуляторы или формы быстрого расчета, начинающие разработчики часто сталкиваются с неожиданным поведением платформы 1С. Типичный сценарий: операция сложения выполняется абсолютно корректно, но при попытке вычесть, умножить или разделить числа программа аварийно завершает работу с ошибкой «Преобразование значения к типу Число не может быть выполнено».
В этой статье мы подробно разберем, почему возникает эта иллюзия «работающего сложения», проанализируем внутренние механизмы приведения типов в платформе 1С, научимся локализовать проблему с помощью отладчика и рассмотрим профессиональные способы написания надежного кода для математических вычислений.
Для начала проанализируем исходный код, который часто пишут на этапе обучения. Рассмотрим две процедуры, привязанные к кнопкам калькулятора на управляемой форме:
&НаКлиенте
Процедура Сложение(Команда)
Объект.Операция = "Сложение";
Попытка
Объект.Буфер = Число(Объект.Табло) + Число(Объект.ТаблоКалькулятора);
Исключение
Объект.Буфер = Объект.Табло + Объект.ТаблоКалькулятора;
КонецПопытки;
Объект.Табло = Объект.Буфер;
ВычислитьОтвет();
Объект.ТаблоКалькулятора = "";
КонецПроцедуры
&НаКлиенте
Процедура Вычитание(Команда)
Объект.Операция = "Вычитание";
Попытка
Объект.Буфер = Число(Объект.Табло) - Число(Объект.ТаблоКалькулятора);
Исключение
Объект.Буфер = Объект.Табло - Объект.ТаблоКалькулятора;
КонецПопытки;
Объект.Табло = Объект.Буфер;
ВычислитьОтвет();
Объект.ТаблоКалькулятора = "";
КонецПроцедуры
На первый взгляд логика процедур идентична, за исключением используемых математических знаков. Однако именно в этом различии кроется причина кардинально разного поведения системы.
Конструкция Попытка — Исключение предназначена для обработки исключительных ситуаций, которые программист не может предотвратить проверками (например, сетевые сбои, блокировки базы данных). Использовать ее для маскировки ошибок приведения типов — крайне нерекомендуемая практика (так называемый «антипаттерн»).
Выясним по шагам, что происходит во время выполнения процедуры Сложение, когда реквизит Объект.ТаблоКалькулятора содержит пустую строку "":
Попытка.Число(Объект.ТаблоКалькулятора). Так как пустая строка не может быть преобразована в число, система генерирует исключение.Исключение, минуя сложение чисел.Исключение выполняется код: Объект.Буфер = Объект.Табло + Объект.ТаблоКалькулятора;."5" и ""), оператор + в языке 1С выполняет строковую конкатенацию (склеивание строк)."5" + "" становится строка "5". Операция завершается успешно, без системных ошибок. Разработчику кажется, что «калькулятор считает правильно», хотя на самом деле математического сложения не произошло — выполнилась работа со строками.Теперь проанализируем работу процедуры Вычитание в аналогичных условиях (когда один из операндов пуст):
Попытка.Исключение.Исключение выполняется код: Объект.Буфер = Объект.Табло - Объект.ТаблоКалькулятора;."10" - "").- (а также умножения и деления) не определен для строковых типов данных. Строки нельзя «вычитать» друг из друга.Число, чтобы выполнить арифметическое действие."" к числу ожидаемо завершается аварийно.Исключение, и она не обернута во внешнюю попытку, платформа прекращает работу процедуры и выдает пользователю системное сообщение: «Преобразование значения к типу Число не может быть выполнено».Таким образом, иллюзия «работающего сложения» возникала исключительно из-за того, что знак плюс умеет соединять строки, а знак минус — нет.
Для того чтобы наглядно увидеть этот процесс, настроим и используем встроенный отладчик 1С. Пройдем по шагам:
Вычитание (клавиша F9).Число() сразу на строку внутри блока Исключение.Объект.ТаблоКалькулятора, нажмем Shift + F9 (Быстрое вычисление выражения) и убедимся, что тип значения — Строка, а само значение пустое.Этот простой эксперимент наглядно демонстрирует, что логика приложения пошла по незапланированному сценарию.
Существует два принципиально разных подхода к решению этой проблемы: через правильную типизацию метаданных (наиболее надежный архитектурный путь) и через программную валидацию данных.
Самый простой и надежный способ избежать подобных ошибок — хранить данные в правильном типе с самого начала. Если реквизиты Табло, ТаблоКалькулятора и Буфер предназначены исключительно для математических расчетов, они должны иметь тип Число.
Выполним настройку реквизитов:
Табло, ТаблоКалькулятора и Буфер.15, и точность 2 или 3 знака после запятой).При таком подходе платформа 1С автоматически инициализирует эти поля нулями вместо пустых строк, а также заблокирует ввод любых недопустимых символов в текстовые поля на форме. Код процедур упростится до предела и избавится от громоздких «Попыток»:
&НаКлиенте
Процедура Сложение(Команда)
Объект.Операция = "Сложение";
Объект.Буфер = Объект.Табло + Объект.ТаблоКалькулятора;
Объект.Табло = Объект.Буфер;
ВычислитьОтвет();
Объект.ТаблоКалькулятора = 0; // Теперь здесь хранится число
КонецПроцедуры
&НаКлиенте
Процедура Вычитание(Команда)
Объект.Операция = "Вычитание";
Объект.Буфер = Объект.Табло - Объект.ТаблоКалькулятора;
Объект.Табло = Объект.Буфер;
ВычислитьОтвет();
Объект.ТаблоКалькулятора = 0;
КонецПроцедуры
Если по каким-то причинам (например, для реализации сложного ввода с плавающей точкой, отображения промежуточных формул или особых требований к дизайну интерфейса) реквизиты обязательно должны оставаться строковыми, нам необходимо написать отказоустойчивую функцию преобразования.
Создадим вспомогательную функцию, которая будет безопасно извлекать числовое значение из строки, обрабатывая любые пустые или некорректно заполненные поля:
&НаКлиенте
Функция ПолучитьБезопасноеЧисло(ЗначениеСтрокой)
// Удаляем лишние пробелы и неразрывные пробелы, которые часто копируются из таблиц
ЧистаяСтрока = СокрЛП(ЗначениеСтрокой);
ЧистаяСтрока = СтрЗаменить(ЧистаяСтрока, Символы.НПП, "");
// Если строка пустая, возвращаем ноль
Если ПустаяСтрока(ЧистаяСтрока) Тогда
Возврат 0;
КонецЕсли;
// Проверяем возможность преобразования
Попытка
Возврат Число(ЧистаяСтрока);
Исключение
// В случае ввода некорректных символов, сообщаем пользователю и возвращаем 0
ПоказатьПредупреждение(, "Введено некорректное числовое значение! Автоматически заменено на 0.");
Возврат 0;
КонецПопытки;
КонецФункции
Теперь перепишем наши основные процедуры вычислений с использованием этой функции. Обратите внимание, как лаконично и понятно выглядит код, когда логика валидации вынесена отдельно:
&НаКлиенте
Процедура Сложение(Команда)
Объект.Операция = "Сложение";
Число1 = ПолучитьБезопасноеЧисло(Объект.Табло);
Число2 = ПолучитьБезопасноеЧисло(Объект.ТаблоКалькулятора);
Объект.Буфер = Число1 + Число2;
Объект.Табло = Объект.Буфер;
ВычислитьОтвет();
Объект.ТаблоКалькулятора = "";
КонецПроцедуры
&НаКлиенте
Процедура Вычитание(Команда)
Объект.Операция = "Вычитание";
Число1 = ПолучитьБезопасноеЧисло(Объект.Табло);
Число2 = ПолучитьБезопасноеЧисло(Объект.ТаблоКалькулятора);
Объект.Буфер = Число1 - Число2;
Объект.Табло = Объект.Буфер;
ВычислитьОтвет();
Объект.ТаблоКалькулятора = "";
КонецПроцедуры
Раз уж мы разрабатываем надежный калькулятор, обязательно предусмотрим защиту от деления на ноль. Это еще одна классическая ошибка, способная уронить сеанс пользователя. Напишем процедуру деления с предварительной проверкой делителя:
&НаКлиенте
Процедура Деление(Команда)
Объект.Операция = "Деление";
Число1 = ПолучитьБезопасноеЧисло(Объект.Табло);
Число2 = ПолучитьБезопасноеЧисло(Объект.ТаблоКалькулятора);
Если Число2 = 0 Тогда
ПоказатьПредупреждение(, "Ошибка: Деление на ноль невозможно!");
Возврат;
КонецЕсли;
Объект.Буфер = Число1 / Число2;
Объект.Табло = Объект.Буфер;
ВычислитьОтвет();
Объект.ТаблоКалькулятора = "";
КонецПроцедуры
Мы выяснили истинную причину избирательного поведения платформы 1С при работе со строками и математическими операторами в блоках Попытка — Исключение.
Главные правила безопасной разработки математических модулей:
Число в метаданных). Это решает проблему на корню на уровне платформы.СокрЛП, ПустаяСтрока) перед преобразованием.Попытка — Исключение для сокрытия логических ошибок в коде. Ошибки приведения типов должны обрабатываться явно до проведения математических операций.