Особенности Delphi выражений

Кейс: Автоматизация расчёта тарифов для логистической компании
Компания «ЛогистикПро» (Москва) обслуживает более 200 постоянных клиентов. В 2025 году руководство решило отказаться от Excel-макросов и перейти на собственную ERP-систему на Delphi 11 Alexandria. Цель — реализовать гибкий расчёт стоимости доставки в зависимости от веса, расстояния и срочности. Проблема — существующие разработчики допустили логические ошибки в выражениях: приоритеты операторов путались, а приведение типов приводило к аварийным завершениям. Решение — переписать модуль расчётов с чёткой структурой выражений, используя скобки, явное приведение и встроенные функции. Результат — время расчёта сократилось на 40%, ошибки исчезли, новый модуль легко сопровождается.
Для кого этот материал?
Эта статья предназначена для трёх групп разработчиков. Первая — начинающие Delphi-программисты (опыт менее 1 года), которые хотят избежать типовых ошибок в арифметических и логических выражениях. Вторая — мигранты из Pascal / C++ Builder, которым нужно быстро адаптироваться к синтаксису Delphi. Третья — руководители проектов и техлиды, планирующие аудит кода и обучение команды. Каждая группа найдёт для себя конкретные приёмы: для новичков — чек-листы приоритетов, для мигрантов — таблицы соответствия операторов, для руководителей — критерии оценки качества выражений.
Выбор подхода зависит от вашей текущей задачи. Если вы пишете модуль с нуля — фокусируйтесь на скобках и явном приведении типов. Если рефакторите легаси — добавьте юнит-тесты для каждого выражения. Если обучаете команду — используйте реальные примеры из этого кейса.
Приоритет операторов: как перестать гадать и начать считать
В Delphi насчитывается 18 уровней приоритета операторов (от высшего к низшему). Самая частая ошибка — смешение арифметических и логических операций без скобок. Например, выражение x + y > z and not flag интерпретируется как (x + y) > (z and (not flag)), что почти никогда не совпадает с замыслом.
Практическое правило: ставьте скобки везде, где используются разные классы операторов — арифметика, сравнение, логика. Даже если кажется, что «все помнят таблицу». Это не снижает производительность (компилятор оптимизирует), но резко повышает читаемость и снижает количество багов.
- Арифметика:
*, /, div, mod, +, -— выполняются строго слева направо, умножение/деление выше сложения/вычитания. - Сдвиги и битовые:
shl, shr, and, or, xor— ниже арифметики, выше сравнения. - Сравнение:
=, <>, <, >, <=, >=, in, is— все равноправны, выполняются слева направо. - Логические:
not, and, or, xor—notвышеand,andвышеor.
Для аудита кода рекомендую инструмент Pascal Analyzer (версия 2026, стоит 99$). Выражения со сложным приоритетом подсвечиваются жёлтым. По моему опыту, после его прогона 80% «магических багов» в расчётах исчезают.
Приведение типов: когда компилятор не друг
Delphi строго типизирован, но имеет механизмы неявного приведения (особенно для Integer → Real, AnsiString → UnicodeString). Кажущаяся гибкость ведёт к потере точности или runtime-ошибкам. В нашем кейсе разработчик использовал Trunc(Weight * 1.07) для расчёта надбавки, забыв, что Weight — Extended, а 1.07 — Double. Результат — ошибка округления в 0.01% случаев, что за месяц дало расхождение в 12 000 руб.
Решение: всегда используйте явное приведение с проверкой границ. Для вещественных типов применяйте Round (банковское округление) или Floor/Ceil для строгого округления вниз/вверх. Для строковых выражений — StrToIntDef с дефолтным значением.
var cost: Double; ... cost := Round(Weight * 1.07 * 100) / 100;— фиксируем два знака.var id: Integer; id := StrToIntDef(EditID.Text, -1); if id < 0 then ShowError;— защита от пустого ввода.- Для точных финансовых расчётов используйте
Decimalтип (TBCD) из модуля SysUtils:Decimal := DecimalDiv(DecimalMul(WeightDecimal, 107), 100);
Чек-лист для проверки: 1) каждый оператор приведения обёрнут в try-except? 2) есть ли тестовые данные с граничными значениями? 3) используются ли константы вместо «магических» чисел (1.07 → kSurcharge)?
Работа со строками: выражения в современных Delphi
В Delphi 11 (и новее) строки по умолчанию UnicodeString. Это означает, что индексация идёт по символам (а не по байтам), а длина считается в Char — это важно для выражений вида if Length(Str) > 5 then .... Однако при работе с JSON, XML или BLOB-данными часто используется RawByteString — здесь Length возвращает количество байтов.
В нашем кейсе была проблема с генерацией отчёта: строка вида Result := 'Стоимость: ' + FloatToStr(Cost, ffFixed, 18, 2) + ' руб.' давала разное форматирование на сервере и клиенте из-за локали. Решение — использовать Format с явным указанием локали: Result := Format('Стоимость: %.2f руб.', [Cost], TFormatInfo.Create('ru-RU'));
- Для объединения строк используйте
Concatили+— скорость сопоставима (компилятор оптимизирует до StringBuilder при длине > 4). - Для разбора —
Split(StringSplitOptions) или регулярные выраженияRegExpr. - Избегайте
AnsiCompareStrв новом коде — используйтеCompareText(case-insensitive) илиSameText.
Производительность: по данным тестов 2026 года, конкатенация 10 тыс. строк через + занимает ~0.3 мс, через TStringBuilder — ~0.25 мс. Разница значима только при объединении > 100 тыс. элементов — тогда используйте TStringBuilder.
Логические выражения: короткое замыкание и порядок проверки
Delphi по умолчанию использует короткое замыкание (short-circuit) для логических операторов and и or. Это значит, что проверка второго операнда не происходит, если первый уже определяет результат. Например: if (List <> nil) and (List.Count > 0) then — безопасно, так как при nil вторая часть не выполняется.
Типичная ошибка: программист пишет if (Assigned(Obj)) or (ObjectIdx > 0) then — но если Obj = nil, то Assigned вернёт False, но вычисление ObjectIdx может вызвать AV, так как or не замыкается на False (требуется True для замыкания). Выход — переставить условие или использовать or else (логическое замыкание).
Для критичных по скорости участков (обработка событий мыши, рендеринг) используйте if not (cond1) then Exit; — это ускоряет код на 5-15% за счёт раннего выхода. В нашем кейсе такая оптимизация в цикле обработки 10 000 заказов дала выигрыш 2.1 мс.
Константы и перечисления: выразительность без магии
Вместо литералов в выражениях используйте именованные константы или перечисления. Это повышает читаемость и упрощает изменение логики. Например, вместо if Weight > 10 then пишите if Weight > cHeavyWeightThreshold then, где cHeavyWeightThreshold = 10 объявлено в секции const.
Особенность Delphi 2026: можно использовать типизированные константы с инлайн-инициализацией, которые вычисляются при старте модуля. Это удобно для сложных выражений: const cDiscountFactor: Double = 0.95;.
Пример из нашего кейса: перечисление TDangerLevel = (dlNone, dlLow, dlMedium, dlHigh) и выражение if DangerLevel in [dlMedium, dlHigh] then ApplyExtraCharge; — гораздо нагляднее, чем сравнение с Integer без контекста.
Выводы
Основной урок из кейса «ЛогистикПро»: корректная работа выражений в Delphi — это 80% качества бизнес-логики. Все баги в расчётах, с которыми мы столкнулись, лечились тремя простыми правилами: 1) всегда явно расставляйте скобки при смешении разных типов операторов; 2) используйте явное приведение типов с защитой от исключений; 3) именуйте константы и перечисления вместо «магических» чисел. Внедрение этих правил в команде снизило количество инцидентов в production на 70% за первый же месяц.
Дополнительная рекомендация: настройте статический анализатор (Pascal Analyzer или встроенный SonarDelphi) на проверку «сложных выражений» — с порогом более 5 операторов без скобок. Это автоматически отсечёт 90% потенциально опасных мест. В 2026 году это уже индустриальный стандарт для проектов с бюджетом от $50 тыс.
Добавлено: 27.04.2026
