Почему я не должен использовать «с» в Delphi? - PullRequest
19 голосов
/ 16 сентября 2008

Я слышал, что многие программисты, особенно программисты на Delphi, презирают использование 'with'.

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

Вот пример:

procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32);
begin
  with ARect do FillRectS(Left, Top, Right, Bottom, Value);
end;

Мне нравится использовать with. Что со мной не так?

Ответы [ 16 ]

33 голосов
/ 16 сентября 2008

Одно неудобство с использованием with - то, что отладчик не может справиться с этим. Это затрудняет отладку.

Большая проблема в том, что код труднее читать. Особенно, если оператор with немного длиннее.

procedure TMyForm.ButtonClick(...)
begin
  with OtherForm do begin
    Left := 10;
    Top := 20;
    CallThisFunction;
  end;
end;

Какая форма CallThisFunction будет вызываться? Self (TMyForm) или OtherForm? Вы не сможете узнать, не проверив, есть ли в OtherForm метод CallThisFunction.

И самая большая проблема в том, что вы можете легко исправлять ошибки, даже не подозревая об этом. Что, если и TMyForm, и OtherForm имеют функцию CallThisFunction, но она является закрытой. Вы можете ожидать / хотите вызвать функцию OtherForm.CallThisFunction, но на самом деле это не так. Компилятор предупредил бы вас, если бы вы не использовали with, но теперь это не так.

Использование нескольких объектов в with умножает проблемы. Смотри http://blog.marcocantu.com/blog/with_harmful.html

11 голосов
/ 05 марта 2010

Было бы здорово, если бы оператор with был бы расширен следующим образом:

with x := ARect do
begin
  x.Left := 0;
  x.Rigth := 0;
  ...
end;

Вам не нужно объявлять переменную 'x'. Он будет создан компилятором. Это быстро написать и не путать, какая функция используется.

11 голосов
/ 16 сентября 2008

Я предпочитаю синтаксис VB в этом случае, потому что здесь, вам нужно префикс членов внутри блока с ., чтобы избежать двусмысленности:

With obj
    .Left = 10
    .Submit()
End With

Но на самом деле, нет ничего плохого в with в целом.

8 голосов
/ 16 сентября 2008

Маловероятно, что "with" заставит код работать быстрее, более вероятно, что компилятор скомпилирует его с тем же исполняемым кодом.

Основная причина, по которой людям не нравится «с», заключается в том, что это может ввести в заблуждение относительно области и приоритета пространства имен.

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

Из-за возможной путаницы некоторые разработчики предпочитают воздерживаться от использования «с» полностью, даже в тех случаях, когда такой путаницы может не быть. Это может показаться догматичным, однако можно утверждать, что по мере того, как код изменяется и растет, использование «с» может оставаться даже после того, как код был изменен до такой степени, что запутывает «с», и поэтому лучше не сначала представьте его использование.

6 голосов
/ 04 сентября 2012

На самом деле:

procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32);
begin
  with ARect do FillRectS(Left, Top, Right, Bottom, Value);
end;

и

procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32);
begin
  FillRectS(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom, Value);
end;

Сгенерирует точно такой же ассемблерный код.

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

На самом деле:

with MyRect do
begin
  Left := 0;
  Right := 0;
end;

кодируется компилятором как псевдокод как таковой:

var aRect: ^TRect;

aRect := @MyRect;
aRect^.Left := 0;
aRect^.Right := 0;

Тогда aRect может быть просто регистром ЦП, но также может быть истинной временной переменной в стеке. Конечно, я использую указатели здесь, поскольку TRect является record. Он более прямой для объектов, поскольку они уже являются указателями.

Лично я использовал иногда с моим кодом, но я почти каждый раз проверяю сгенерированный asm, чтобы убедиться, что он делает то, что должен. Не каждый может или имеет время сделать это, поэтому ИМХО локальная переменная является хорошей альтернативой.

Мне действительно не нравится такой код:

for i := 0 to ObjList.Count-1 do
  for j := 0 to ObjList[i].NestedList.Count-1 do
  begin
    ObjList[i].NestedList[j].Member := 'Toto';
    ObjList[i].NestedList[j].Count := 10;
  end;

Это все еще довольно читабельно с помощью:

for i := 0 to ObjList.Count-1 do
  for j := 0 to ObjList[i].NestedList.Count-1 do
  with ObjList[i].NestedList[j] do
  begin
    Member := 'Toto';
    Count := 10;
  end;

или даже

for i := 0 to ObjList.Count-1 do
  with ObjList[i] do
  for j := 0 to NestedList.Count-1 do
  with NestedList[j] do
  begin
    Member := 'Toto';
    Count := 10;
  end;

но если внутренний цикл огромен, локальная переменная имеет смысл:

for i := 0 to ObjList.Count-1 do
begin
  Obj := ObjList[i];
  for j := 0 to Obj.NestedList.Count-1 do
  begin
    Nested := Obj.NestedList[j];
    Nested.Member := 'Toto';
    Nested.Count := 10;
  end;
end;

Этот код не будет медленнее, чем with: компилятор фактически делает это за сценой!

Кстати, это упростит отладку: вы можете установить точку останова, а затем навести указатель мыши на Obj или Nested, чтобы получить внутренние значения.

3 голосов
/ 04 марта 2010

В первую очередь это проблема технического обслуживания.

Идея WITH имеет разумный смысл с языковой точки зрения, и аргумент о том, что он сохраняет код, когда он используется разумно, меньше и яснее, имеет некоторую обоснованность. Однако проблема заключается в том, что большая часть коммерческого кода будет поддерживаться несколькими разными людьми в течение всего его жизненного цикла, и то, что начинается с небольшой, легко анализируемой конструкции при написании, со временем может легко трансформироваться в громоздкие большие структуры, где область действия WITH не легко анализируется сопровождающим Это, естественно, приводит к ошибкам, и их трудно найти.

Например, скажем, у нас есть небольшая функция foo, которая содержит три или четыре строки кода, которые были обернуты внутри блока WITH, тогда действительно нет проблем. Однако через несколько лет эта функция, возможно, расширилась под несколькими программистами в 40 или 50 строк кода, все еще заключенных в WITH. Это теперь хрупко и созрело для появления ошибок, особенно если сопровождающий отмечает, что вводит дополнительные встроенные блоки WITH.

WITH не имеет других преимуществ - код должен анализироваться точно так же и выполняться с той же скоростью (я провел несколько экспериментов с этим в D6 внутри замкнутых циклов, используемых для 3D-рендеринга, и я не смог найти никакой разницы). Неспособность отладчика справиться с ним также является проблемой, но она должна быть исправлена ​​некоторое время назад и ее стоит игнорировать, если есть какая-то выгода. К сожалению, нет.

3 голосов
/ 16 сентября 2008

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

Подумайте о том, чтобы сделать код внутри вашего оператора with методом объекта, к которому вы обращаетесь.

3 голосов
/ 16 сентября 2008

Эта дискуссия также часто встречается в Javascript.

По сути, с помощью синтаксиса очень сложно сразу определить, какое свойство / метод Left / Top / etc вы вызываете. У вас может быть локальная переменная с именем Left и свойство (давно Я сделал Delphi, извините, если имя неверно) называется Left, возможно, даже функция с именем Left. Любой, кто читает код и не знаком со структурой ARect, может быть очень и очень потерян.

2 голосов
/ 27 июня 2010

Вы можете комбинировать с утверждениями, так что вы получите

with Object1, Object2, Object3 do
begin
  //... Confusing statements here
end

И если вы думаете, что отладчик смущен тем, с кем я не понимаю, как кто-то может определить, что происходит в блоке with

2 голосов
/ 16 сентября 2008

На работе мы даем очки за удаление Withs из существующей базы кода Win 32 из-за дополнительных усилий, необходимых для поддержки кода, который их использует. Я обнаружил несколько ошибок в предыдущем задании, в котором локальная переменная с именем BusinessComponent была замаскирована, находясь в блоке «С начала» для объекта, который опубликовал свойство BusinessComponent того же типа. Компилятор решил использовать опубликованное свойство, а код, который имел в виду использование локальной переменной, потерпел крах.

Я видел код как

С a, b, c, d do {за исключением того, что они гораздо более длинные имена, здесь только сокращены) начать i: = xyz;
конец;

Это может быть настоящая боль, пытаясь определить, откуда взялся xyz. Если бы это было c, я бы гораздо раньше записал это как

i: = c.xyz;

Вы думаете, что это довольно тривиально понять, но не в функции длиной 800 строк, которая использовала символ with в самом начале!

...