Средства для автоматического форматирования кода Delphi

Несколько лет назад я зафиксировал на GitHub (GitBook) описание правил форматирования кода принятых в компании где я работаю. Описания не дописано, но часть со ссылками на форматеры кода для Delphi может быть кому-то полезна. Мне удалось найти только три которые более-менее развивались. Вроде, с того времени чего-то нового не появилось.

JEDI Code Format

JEDI Code Format - отдельное приложение для форматирования исходных кодов Object Pascal и Delphi.

Experimental GExperts Version

Это экспериментальная версия эксперта для IDE RAD Studio GExpert.

Поддерживаемые версии IDE: 6, 7, 2005, 2006, 2007, 2009, 2010, XE1, XE2.

Стандартное форматирование кода в Delphi 2010

Начиная с Delphi 2010 в IDE добавлено стандартное средство для автоматического форматирования исходного кода.

Использование пустого блока try…except в Delphi

Есть несколько доводов чтобы никогда не использовать пустой блок try...except, такой как

А причин чтобы применять такую конструкцию мне найти не удалось.

1. Исключения для исключений, не для логики

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

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

Следовательно, блокирование исключения не имеет смысла. Если какая-то проблема предполагалась, то надо обработать её через условия. Если возможны какие-то исключения и их нужно обрабатывать, то должен быть какой-то код в внутри except. Если же не первое не второе то зачем этот пустой блок?

2. Трудности с отладкой

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

3. Временное решение

Предположим, появляется ошибка причина которой непонятна. Участок кода не критичен и чтобы её скрыть применяется такая конструкция.

Такой подход допустим временно, если ситуация критичная и нужно срочно решить проблему. Но это именно временное решение. Дальше всё равно нужно разобраться и исправить корень проблемы. Если этого не сделать, то не решённая должным образом проблема может проявиться и другим способом. А для получения дополнительной информации, чтобы было проще искать проблему, можно например использовать логирование в блоке except, вместо того чтобы оставлять его пустым.

4. Запись в лог

Даже если это действительно какая-то особая ситуация, то можно как минимум записать это в лог и потом решить что делать. Ситуация не останется незамеченной, при любом странном поведении можно будет просмотреть этот лог.

SQL Injection в Delphi

Перевод поста SQL Injection

Я понял, что в своём недавнем посте security assumptions пропустил одно важное предположение (сейчас это исправлено):

Предполагайте, что все входные данные могут быть вредоносными.

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

Сегодняшний пример является VCL приложением использующим FireDAC для доступа к SQLite. Пример можно найти в репозитории security-demo на GitHub.

Внедрения SQL имеют место когда вы не используете параметры, из-за этого входные данные имеют возможность изменять вашу команду SQL.

Ниже показан небезопасный путь передать значение из поля ввода в запрос

Если я введу следующий текст в edtSearchTerm.text

То я получу список таблиц в база данных, в котором просто увидеть другую таблицу с названием salary. Затем я могу ввести следующее и получить всю информацию об оплате.

Ниже показан безопасный способ сделать туже функцию

Теперь я могу снова попытаться произвести внедрение SQL с помощью тех-же строк. И это мне не удаться, так как строки теперь внутри параметра и следовательно не могут изменить оператор SQL. Проблемы легко избежать, но всё же это довольно распространённая ошибка.

Тестирование на внедрения SQL кода очень простое — вы пробуете завершить ввод одиночной или двойной кавычкой. Если вы получаете неожиданные ошибки то у вас есть возможность SQL Injection.

Мы склонны концентрироваться на вводе от пользователя, но API такие как SOAP или REST также могут стать целью SQL Injection. Поэтому я очень осторожно сказал что нужно проверять все входные данные, а не только ввод пользователя.

Статьи про Delphi c softwarer.ru

Довольно давно существовал сайт Александра Просторова со статьями про Delphi softwarer.ru, Сейчас он не работает. Часть статей можно найти через web.archive.org, но это не очень удобно. Я скачал наиболее полезные, на мой взгляд, и сохранил в архив.

Из них самые лучше первые три:

Run-time packages.

В статье рассказано о том как и зачем применять пакеты времени выполнения.

Работа с динамической памятью.

Подробно разобрана тема создания и уничтожения объектов. Включая использования try...finnaly, концепции владения и нотификации в VCL, создание и уничтожение форм.

Применение модулей данных в немодальном интерфейсе.

Описано решение проблемы связывания модуля данных и нескольких экземпляров формы.

Остальные статьи:

  • Принципы Delphi.
  • Crack-классы.
  • Разнесение design- и runtime свойств.
  • История одной оптимизации.
  • Макрокомпоненты.
  • О сериализации множеств.
  • Обработчики событий.
  • Ошибки интерфейса.
  • Подробности Oracle.
  • Подсказки по использованию Delphi.
  • Расстановка TabOrder-ов.
  • Реализация синглтонов.
  • Таймеры.

Проблемы перегрузки операторов в Delphi

Вольный перевод поста On the operator overloading in Delphi.

Перегрузка операторов в Delphi является простой если запись не содержит в себе полей-ссылок на объекты в куче. Чтобы проиллюстрировать эту проблему рассмотрим следующий (некорректный!) пример:

Читать далее Проблемы перегрузки операторов в Delphi

Программирование в функциональном стиле в Delphi

Вольный перевод поста Functional programming style in Delphi.

Парадигма функционального программирования находит свой путь даже в Delphi — императивном языке без сборщика мусора. Рассмотрим следующий пример кода, который меняет заголовок кнопки используя расширенный RTTI (требуется версия Delphi 2010 или выше).

При анализе этого кода обратите внимание на следующее:

  • Тип TRttiContext - запись (record); вам не нужно создавать и уничтожать переменную с типом запись в том виде как это делается для объектов.
  • TRttiProperty и TRttiType - классы; Экземпляры этих классов создаются соответствующими методами классов TRttiType и TRttiContext, но вам не нужно уничтожать их самостоятельно — лежащая в основе них расширенная реализация RTTI заботится об этом сама.

В результате код может переписан в функциональном стиле:

Все переменные стали не нужны и нет утечек памяти.

Автоматическое создание объектных полей с помощью RTTI в Delphi

Вольный перевод поста Automagically Creating Object Fields with RTTI

На работе возникла задача создания иерархии классов, причём классы почти не содержали реализации, только объявление классов, с одним исключением — каждый класс отвечал за создание и уничтожение внутренних объектов. Я задумался, может быть, я могу использовать атрибуты и RTTI чтобы реализовать это поведение в одном месте, вместо того чтобы реализовывать в каждом классе.

Проблема

Мне нужны классы следующего вида

Но я слишком ленив чтобы писать в каждом классе раз примерно одинаковый конструктор и деструктор. Что мне делать?

Читать далее Автоматическое создание объектных полей с помощью RTTI в Delphi

Конструкторы записей (record) в Delphi

Вольный перевод поста Record Constructors in Delphi

Конструкторы записей в Delphi — особенность языка которая вызывает вопросы. Зачем они нужны и когда их использовать вместо методов записей? В документации Delphi написано следующее:

Records are constructed automatically, using a default no-argument constructor, but classes must be explicitly constructed. Because records have a default no-argument constructor, any user-defined record constructor must have one or more parameters.

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

Приведённая выше документация ничего не объясняет. В Delphi не существует такой вещи как "конструктор по умолчанию без параметров". Сразу возникает ещё один вопрос — почему в Delphi запрещены конструкторы без параметров и разрешены с параметрами?

На самом деле конструкторы записей в Delphi это просто специальный синтаксис для методов записей.

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

или процедуру

Используя конструктор в записи вы можете совместить обе формы

Можно вызывать конструктор как функцию

или как процедуру

Обе формы корректны.

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

Условная компиляция и возможности языка Delphi

Вольный перевод поста Conditional Compilation with Features.

Вы разрабатываете код который должен работать в разных версиях Delphi? Вы устали от написания примерно такого кода?

При этом вы

  • никогда не знаете точно когда определённая возможность была введена в Delphi
  • всегда сомневаетесь в том какая версия Delphi имеет версию компилятора (CompilerVersion) равную 22

Вместо написания условий по версии компилятора, вы можете думать в терминах возможностей языка. Код выше требует версию Delphi которая поддерживает обобщённое программирования и содержит реализацию TArray<T>. Так что можно переписать код примерно так

Это проще для написания (так как не нужно помнить что в какой версии добавлено) и проще для понимания.

Директивы для таких проверок можно подключить из файла jedi.inc. В этом файле содержатся директивы вида HAS_XXXXX и SUPPORTS_XXXXX, например, такие как

Предварительное объявление записей (record) в Delphi

Вольный перевод поста Forward record declaration.

Предварительная объявление не новая концепция. Она уже присутствовала в оригинальном Паскале Вирта, где она позволяла программистам делать только одну вещь — вызывать процедуру A из процедуры B и вызывать процедуру B из процедуры A. В те времена не было интерфейсов, классов, модулей, только процедуры и функции. Вот пример

Более знакомая современная концепция — предварительное объявление для классов и интерфейсов.

В тоже время в Object Pascal нет концепции предварительного объявления записей. Следующий код не компилируется

Существует трюк который позволяет достичь аналогичной функциональности с помощью другого синтаксического сахара - record helpers. Мы можем удалить объявление TRecB.Other из TRecB и потом снова добавить его через хелпер для TRecB.

Но помните, что это решение менее стабильно: другой код может скрыть функционал TRecBHelper введя свой собственный хелпер для TRecB. (противная особенность языка которая действительно должна быть исправлена уже давно)