Почему мне не следует использовать with в Delphi?

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

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

Вот пример:

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

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


person Baldric    schedule 16.09.2008    source источник


Ответы (16)


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

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

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

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

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

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

person Lars Truijens    schedule 16.09.2008

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

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

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

person Konrad Rudolph    schedule 16.09.2008
comment
Согласованный. По сути, это единственное, что мне больше нравится в VB по сравнению с Delphi. - person Mason Wheeler; 02.11.2008
comment
Это не сработает с вложенными с. (Нравится вам это или нет.) - person Uli Gerhardt; 05.02.2009
comment
@Ulrich: синтаксис VB действительно работает с вложенными with: With fooWith .bar…. - person Konrad Rudolph; 27.07.2010
comment
@Konrad: Тогда что означает .Left во внутреннем with - foo.Left или foo.bar.Left? - person Uli Gerhardt; 27.07.2010
comment
@Ulrich: foo.bar.Left, конечно. В противном случае вложение with не имело бы смысла, не так ли? - person Konrad Rudolph; 27.07.2010
comment
Ни одна из версий не имеет особого смысла. :-) Внутри первого with вы можете определить, является ли Left членом переменной withed или исходит из окружающей области, поставив перед ней префикс . или не включив его. Это различие исчезнет, ​​если вы вложите свои with на один уровень глубже. Если бы синтаксис был согласованным, .Left означал бы foo.Left, а ..Left означал бы foo.bar.Left или что-то в этом роде. - person Uli Gerhardt; 28.07.2010
comment
@Ulrich: ну, я думаю, что способ, которым VB решает эту проблему, - это адекватный компромисс между общностью и полезностью. Верно, что концепция могла быть обобщена и дальше. Фактически, я больше не использую With. Конструкторы или компоновщики с плавными интерфейсами обеспечивают гораздо лучшую читаемость. Но я не думаю, что здесь дело. - person Konrad Rudolph; 28.07.2010

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

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

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

person markus_ja    schedule 05.03.2010
comment
В XE2 и новее просто позвоните: x.Create(left,top,right,bottom);. Больше не нужно использовать with. Многие стандартные типы реализованы с помощью advanced record типов. - person LU RD; 05.09.2012

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

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

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

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

person Graza    schedule 16.09.2008
comment
Однажды я провел очень ограниченное тестирование использования с при оптимизации часто вызываемого кода. Это не улучшило время работы. Если это была проблема, например, при поиске в массиве, то временная переменная - это решение. Компилятор настолько умен, что обычно лучше оставить его в покое. - person Richard A; 04.11.2008

По факту:

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. Это более прямолинейно для объектов, поскольку они уже являются указателями.

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

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

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, чтобы получить внутренние значения.

person Arnaud Bouchez    schedule 04.09.2012

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

Рассмотрите возможность сделать код в вашем операторе with методом объекта, на который вы ссылаетесь.

person SoftDeveloper    schedule 16.09.2008

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

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

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

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

person Cruachan    schedule 04.03.2010

Эти споры часто происходят и в Javascript.

По сути, из-за синтаксиса With очень сложно с первого взгляда определить, какое свойство / метод Left / Top / etc вы вызываете. У вас может быть локальная переменная с именем Left и свойство (прошло некоторое время с тех пор, как я done delphi, извините, если имя неправильное) называется Left, возможно, даже функция с именем Left. Любой, кто читает код и не очень хорошо знаком со структурой ARect, может очень потеряться.

person Dan F    schedule 16.09.2008

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

person Ralph M. Rickenbach    schedule 16.09.2008
comment
Это похоже на проблему с отладчиком, а не на языковую функцию. - person Greg Hewgill; 16.09.2008
comment
@GregHewgill Вы правы, проблема в отладчике. Но это по-прежнему веская причина избегать его использования. Более того, это не извиняет всех проблем с языковыми функциями, которые предлагает . Самым большим из них является то, что с на самом деле снижает удобочитаемость. ПРИМЕЧАНИЕ. Уменьшение количества кода (использование с) не улучшает читаемость автоматически. Это уменьшает его, потому что больше не ясно, на что ссылаются идентификаторы в блоке with. Объявление и инициализация псевдонима локальной переменной приводит к аналогичному сокращению кода, но поддерживает явное разыменование. - person Disillusioned; 07.06.2012

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

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

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

Попытка определить, откуда берется xyz, может быть настоящей болью. Если бы это было c, я бы скорее написал его как

я: = c.xyz;

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

person Community    schedule 16.09.2008

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

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

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

person mmmm    schedule 27.06.2010

В этом нет ничего плохого, если вы сохраняете простоту и избегаете двусмысленностей.

Насколько мне известно, это ничего не ускоряет - это чисто синтаксический сахар.

person Blorgbeard    schedule 16.09.2008
comment
Это может ускорить процесс, если задействовано несколько уровней косвенного обращения. Рассмотрим следующее: с aControl.parent.parent.parent do ... с последующими несколькими операциями. Оператор with оценивает тройную косвенность только один раз и использует эту ссылку несколько раз. - person Mason Wheeler; 02.11.2008
comment
Откуда вы знаете, что компилятор этого не сделает без with? Некоторое время назад я провел базовое тестирование и не нашел никакой разницы. - person Blorgbeard; 03.11.2008
comment
@MasonWheeler - того же результата можно достичь, используя GreatGrandParent := aControl.parent.parent.parent; и обращаясь к участникам GreatGrandParent. Только гораздо читабельнее, чем с with. - person mg30rg; 31.10.2014

Это позволяет некомпетентным или злым программистам писать нечитаемый код. Поэтому используйте эту функцию только в том случае, если вы не являетесь ни некомпетентным, ни злым.

person jedediah    schedule 16.09.2008
comment
И если вы можете гарантировать, что ваш код никогда не будет прочитан или отредактирован программистом менее конкурентоспособным, чем вы. - person Richard A; 04.11.2008
comment
И если вы достаточно компетентны, чтобы всегда правильно использовать с: вместо этого вы просто объявляете псевдоним локальной переменной, потому что в этом случае код будет явным и гораздо более читабельным в будущее за несколько лишних секунд набора текста сейчас. - person Disillusioned; 08.06.2012

... run faster ...

Не обязательно - ваш компилятор / интерпретатор обычно лучше оптимизирует код, чем вы.

Думаю, это заставляет меня сказать "фу!" потому что это лениво - когда я читаю код (особенно чужой), мне нравится видеть явный код. Так что я бы даже написал this.field вместо field в Java.

person Flint    schedule 16.09.2008

Недавно мы запретили его в наших стандартах программирования Delphi.

Плюсы часто перевешивали минусы.

То есть были внесены ошибки из-за неправильного использования. Это не оправдало экономии времени на написание или выполнение кода.

Да, использование with can привело к (мягко говоря) более быстрому выполнению кода.

Далее foo оценивается только один раз:

with foo do
begin
  bar := 1;
  bin := x;
  box := 'abc';
end

Но здесь он оценивается трижды:

foo.bar := 1;
foo.bin := x;
foo.box := 'abc';
person Matt Lacey    schedule 16.09.2008
comment
Вы уверены в этом? Я был бы очень удивлен, если бы компилятор не был достаточно умен, чтобы оптимизировать второй случай до первого. - person Blorgbeard; 19.10.2008
comment
Совершенно верно. Delphi - это основной env. на моей основной работе, и я хорошо с ней знаком. Природа Паскаля упрощает оптимизацию из-за повсеместной статической типизации, но оптимизации, которые выполняет компилятор, к настоящему времени технологически отстают на несколько поколений. Этот компилятор не такой умный, как мы. - person Mihai Limbășan; 20.10.2008
comment
Если вы сохраните foo в локальной переменной, вы получите такой же уровень производительности, как и с. - person Barry Kelly; 20.10.2008
comment
Совершенно верно. Падение производительности наблюдается при доступе к свойствам типа функции (вызов функции, не встроенная версия до Delphi10) и переменным экземпляра (поиск в vtable). Если вы используете локальный объект и позаботитесь о том, чтобы размещать доступы один за другим, это так же быстро как с предложением. - person Mihai Limbășan; 20.10.2008
comment
Если Foo является функцией или свойством, вызываемым методом GetFoo, компилятор ДОЛЖЕН выполнять функцию каждый раз, так как это может иметь другие побочные эффекты (обычно плохой дизайн, но это случается). - person Gerry Coll; 23.11.2008
comment
@GerryColl. В этом случае вы также не захотите использовать with. - person Disillusioned; 07.06.2012
comment
Единственный случай, когда @Matt Lacey мог быть прав, если foo был бы методом (или свойством, привязанным к методу), а компилятор был бы достаточно безумным, чтобы буферизовать его значение. Теперь, если это было так - а я только надеюсь, что это не так - что произойдет, если я вызову метод bar() (или установлю / получу свойство привязки метода barisnotvalid) для foo? Если бы это не было переоценено, все дальнейшие считываемые поля / свойства могли иметь поддельные значения (потому что метод, который был вызван, изменил их значение)? Или компилятор таинственным образом придет переоценить foo? Как он узнает, что это необходимо? - person mg30rg; 29.08.2013

Для Delphi 2005 существует жесткая ошибка в операторе with-do - указатель оценки теряется и повторно выполняется указателем вверх. Здесь необходимо использовать локальную переменную, а не тип объекта напрямую.

person ijcro    schedule 27.07.2010
comment
Вы можете перефразировать свой ответ? Очень сложно понять, что вы пытаетесь сказать. - person Kenneth Cochran; 27.07.2010