У нас в команде запрещён к использованию оператор with
. Основная причина в проблемах с надёжность кода с with. Существует и другая сторона работы с with
. Часто его применение указывает на плохую организацию кода и переместив код в более адекватное место оператор with
станет просто не нужен. Рассмотрим два примера.
Перемещение кода
Иногда with
используется для формы или датамодуля чтобы установить свойства, возможно, вызывать несколько методов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
procedure TFrmVrachStac.tbDiagrClick(Sender: TObject); var FormChart: TFormChart; begin FormChart := TFormChart.Create(nil); try with FormChart do begin Caption := 'Результаты измерений температуры, других исследований и процедур'; LFio.Caption := MTVrachStac.FieldByName('fio').AsString; LDATE_B.Caption := MTVrachStac.FieldByName('date_b').AsString; LAGE.Caption := MTVrachStac.FieldByName('age').AsString; LCHAMBER.Caption := MTVrachStac.FieldByName('chamb').AsString; LNIB.Caption := MTVrachStac.FieldByName('n_map').AsString; id_ill := mt_id_ill; width := Glob_main_form.Width - 100; height := Glob_main_form.Height - 150; end; |
В таком случае лучше оформить весь блок кода внутри формы FormChart
в виде метода. А вместо with
сделать вызов этого метода. Ещё лучше когда у вызыващие формы есть специальный интерфейс (не в смысле типа в Delphi, а в смысле API) через который идёт работа с формой.
Декомпозиция функций
Задача — перебрать все записи в датасете. Код может быть примерно такой
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
procedure TMyForm.MyFunction(ASourceDataSet: TDataSet); var LItem: TPMRItem; Mark1: TBookMark; begin Mark1 := ASourceDataSet.GetBookmark; with ASourceDataSet do begin DisableControls; First; try while not eof do begin LItem := TPMRItem.Create; with LItem do begin |
Оператор with
применяется к датасету чтобы не писать его название несколько раз. В тоже самое время этот оператор создаёт лишнюю вложенность, затрудняет чтение, особенно если смотреть только diff
в svn. В такой ситуации метод можно разделить на два: один проходит цикл по датасету, другой обрабатывает отдельную запись.
1 2 3 4 5 6 7 8 9 10 11 12 |
procedure TMyForm.MyFunction(ASourceDataSet: TDataSet); var LItem: TPMRItem; Mark1: TBookMark; begin Mark1 := ASourceDataSet.GetBookmark; ASourceDataSet.DisableControls; ASourceDataSet.First; try while not ASourceDataSet.eof do ProcessOneRow(ASourceDataSet <...> ) |
В метод ProcessOneRow
можно даже не передавать весь датасет а только нужные поля. Таким подходом значительно уменьшается вложенность, with
становится не нужен.
with удобен, когда функция возвращает экземпляр объекта и нам нужно с ним немного поработать, но мы не управляем жизненным циклом полученного объекта.
Добавление колонки в TableView:
with TableView.CreateColumn do
begin
Caption := …
Visible := …
DataBinding.ValueType := …
…
end;
Мы добавили столбик и дальше он живёт своей жизнью.
А у вас, разработчик корчится «with нельзя», заводит переменную NewColumn, и если случай не такой очевидный, ещё где-то комментирует, что полученный экземпляр не нужно освобождать, поскольку не мы управляем его жизненным циклом. Это для тех, кому потом доведётся смотреть получившийся код в diff-ах…
Единственное абсолютное правило — нет ни каких абсолютных правил 😉
Спасибо, очень хороший и показательный пример проблемы которую выявляет with. Почему бы, например, не сделать процедуру которая принимает колонку и настраивает свойства?
Вызов вместо with
SetupColumn(TableView.CreateColumn …)
Объявление процедуры может быть таким
procedure SetupColumn(AColumn: TMyColumn … свойства )
begin
AColumn.Caption := …
AColumn.Visible := …
AColumn.DataBinding.ValueType := …
Тем самым мы
1. Выделяем код в один логический блок — функцию.
2. Можем даже тестировать этот блок отдельно если применяется модульное тестирование
3. Убираем лишний уровень вложенности
И в вашем примере изящнее смотрелось бы
procedure SetupColumn(AColumn: TMyColumn … свойства )
begin
with AColumn do
begin
Caption := …
Visible := …
DataBinding.ValueType := …
end;
…
Тогда уже лучше class helper.