Подтвердить что ты не робот

Следует ли инициализировать переменную перед вызовом функции?

Когда я вызываю функции для получения значения, я обычно инициализирую varible, в случае, если функция терпит неудачу или ничего не возвращает, и я хочу избежать использования неинициализированной переменной. Я делаю то же самое для строки, целого или любого другого типа.

Пример для целочисленных переменных:

vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');

IF vPropValue > 0 Then
...

это самый распространенный способ использования.

Я знаю, что могу использовать:

If GetPropValue(vObject,'Height') > 0 Then
...

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

То же самое для строки (хотя я знаю, что локальные строки инициализируются пустой строкой, а целые числа не могут содержать любое значение)

vName := '';
vName := GetObjectName(vObject,'ObjectName');

IF Trim(vPropStrValue) <> '' Then
...

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

Я пытаюсь понять, почему это нежелательная практика и как ее лучше избегать.

ИЗМЕНИТЬ

Вот пример, когда функция не возвращает правильное значение или 0:

function GetValue(vType:integer):integer;
begin
   if vType=1 then
    Result:=100
   else if (vType>2) and (vType<=9) then
     Result:=200;

end;

procedure TForm1.Button1Click(Sender: TObject);
var vValue:integer;
begin

  vValue:=GetValue(11);
  Button1.Caption:=IntToStr(vValue);

end;

В этом случае значение, возвращаемое функцией, является случайным числом.

В этом случае инициализация представляется действительным. ИЛИ НЕ?

ИЗМЕНИТЬ 2:

Как указал Дэвид в своем ответе, правильно, было предупреждение

[dcc32 Warning] Unit1.pas(33): W1035 Return value of function 'GetValue' might be undefined

но я проигнорировал это, без всякой причины, просто не смотрел. Поскольку это позволило мне скомпилировать его, я подумал, что все в порядке. Итак, я искал предупреждение, и я "исправил" несколько функций, которые имели схожую проблему, поскольку все IFs Result могут не быть определены.

ИЗМЕНИТЬ 3 и ЗАКЛЮЧЕНИЕ:

Я надеюсь, что это добавит рамки вопроса и объяснения:

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

function GetProperty(vType:integer):integer;
begin
  Try
    if vType = 99 then
      Result:=GetDifferentProperty(vType)// <-- Call to another Function, that could return whatever...
    else
    begin
       if vType=1 then
          Result:=100
       else if (vType>2) and (vType<=9) then
         Result:=200;
    end;
  except
  end;
end;

Теперь я обращаюсь к этим Try Except End;, но некоторым функциям исполняется 10 лет, и ожидать, что они будут работать на 100%, основываясь на моем опыте, не на что положиться.

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

Итак, мой вывод: поскольку я не заботился об основных принципах - правильно разработанных Функции, мне нужно иметь все эти проверки (переменная инициализация, Try Except строки..) и, вероятно, некоторые другие ненужные вещи.

4b9b3361

Ответ 1

Предполагая, что vPropValue является локальной переменной, тогда этот код

vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');

неотличим от

vPropValue := GetPropValue(vObject,'Height');

Более простой пример может быть таким:

i := 0;
i := 1;

Как назначить 0 на i, а затем сразу назначить 1 на i? Значит, ты никогда не напишешь об этом. Вы должны написать:

i := 1;

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

Второй пример немного сложнее. Предполагая, что ваши функции написаны правильно и всегда назначают их возвращаемое значение, а vName - локальная переменная, тогда

vName := '';
vName := GetObjectName(vObject,'ObjectName');

неотличим от

vName := GetObjectName(vObject,'ObjectName');

Причина, по которой я добавил дополнительную оговорку, связана с причудой реализации возвращаемых значений функции, обсуждаемой ниже. Разница между этим случаем и вышеприведенным случаем - тип возвращаемого значения. Здесь это управляемый тип string, тогда как в первом примере тип является простым Integer.

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


Что касается функции в вашем редакторе, компилятор предупредит вас о своей ошибочной реализации, если вы включите подсказки и предупреждения. Компилятор скажет вам, что не все пути кода возвращают значение.

function GetValue(vType:integer):integer;
begin
  if vType=1 then
    Result:=100
  else if (vType>2) and (vType<=9) then
    Result:=200;
end;

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

function GetValue(vType:integer):integer;
begin
  if vType=1 then
    Result:=100
  else if (vType>2) and (vType<=9) then
    Result:=200
  else
    Result:=0;
end;

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


Причина того, что ваше двойное назначение иногда кажется полезным для вас, связана с причудой реализации возвращаемых значений функции в Delphi. В отличие от почти всех других языков значение возвращаемой функции Delphi для некоторых более сложных типов на самом деле является параметром var. Итак, эта функция

function foo: string;

на самом деле семантически совпадает с этим:

procedure foo(var result: string);

Это действительно странное решение, сделанное дизайнерами Delphi. В большинстве других языков, таких как C, С++, С#, Java и т.д., Возвращаемое значение функции похоже на параметр байтового значения, переданный от вызываемого абонента.

Это означает, что если вы хотите быть извращенным, вы можете передать значения в функцию через возвращаемое значение. Например, рассмотрите этот код:

// Note: this code is an example of very bad practice, do not write code like this

function foo: string;
begin
  Writeln(Result);
end;

procedure main;
var
  s: string;
begin
  s := 'bar';
  s := foo;
end;

При вызове main он выведет bar. Это довольно странная деталь реализации. Вы не должны полагаться на это. Позвольте мне повториться. Вы не должны полагаться на это. Не пытайтесь инициализировать возвращаемые значения на сайте вызова. Это приводит к недостижимому коду.

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


Более подробная информация о реализации возвращаемых значений функций содержится в документации , с акцентом:

Следующие выражения используются для возврата результата функции значения.

  • Порядковые результаты возвращаются, когда это возможно, в регистр CPU. Байты возвращаются в AL, слова возвращаются в AX, а двойные слова возвращаются в EAX.
  • Реальные результаты возвращаются в регистр верхнего уровня стека сопроцессора с плавающей точкой (ST (0)). Для результатов функции типа Currency, значение в ST (0) масштабируется на 10000. Например, значение Currency 1.234 возвращается в ST (0) как 12340.
  • Для строки, динамического массива, указателя метода или варианта результата эффекты такие же, как если бы результат функции был объявлен как дополнительный параметр var после объявленных параметров. В других слов, вызывающий передает дополнительный 32-разрядный указатель, который указывает на переменная, в которой для возврата результата функции.
  • Int64 возвращается в EDX: EAX.
  • В EAX возвращаются указатели, класс, ссылка на класс и результат указателя процедуры.
  • Для статического массива, записи и установки результатов, если значение занимает один байт, оно возвращается в AL; если значение занимает два байта, это возвращен в AX; и если значение занимает четыре байта, оно возвращается в EAX. В противном случае результат возвращается в дополнительном параметре varкоторый передается функции после объявленных параметров.

Ответ 2

Следующий код (A)

vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');

неотличим от (B)

vPropValue := GetPropValue(vObject,'Height');

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

Давайте рассмотрим, что произойдет, даже если вы неправильно написали GetPropValue, например.

function GetPropValue(AObject: TObject; AStr: String): Integer;
begin
  if AStr = 'Hello' then Result := 5;
end;

Как вы знаете, когда вход AStr является чем-то другим, кроме "Hello", результат функции будет довольно случайным. (Ради обсуждения, давайте предположим, что он вернет -42.)

Блок кода (A) будет выполнять следующие действия:

  • Установите vPropValue в 0
  • Затем установите vPropValue в - 42

Блок кода (B) просто устанавливает vPropValue в - 42 немедленно.

СОВЕТ. Нет смысла писать расточительную строку кода только потому, что вы беспокоитесь, что можете совершить ошибку в функции, которую вы вызываете.
Во-первых, как указывает Дэвид, вы можете избежать многих ошибок, обратив внимание на подсказки и предупреждения компилятора. Во-вторых, такое "параноидальное" кодирование просто приводит к более расточительному коду, потому что теперь вы должны начать рассматривать недопустимые значения как возможные результаты.
Это становится хуже, когда однажды ваша "безопасная стоимость" на самом деле является допустимым значением. Например. как бы вы сказали разницу между "default 0" и "правильно возвращенным 0"?

Не пытайтесь искусственно усложнять программирование с помощью раздувания кода с ненужными сокращениями.


Боковое примечание

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

Я упоминаю их исключительно ради полноты, совет выше все еще стоит.

1), если vPropValue реализуется как свойство, у сеттера могут быть побочные эффекты, вызывающие другое поведение. Хотя нет ничего плохого в свойствах, когда у них возникают неожиданные вещи, у вас есть серьезные проблемы с вашими руками.

2), если vPropValue - поле в классе (или, что еще хуже, глобальная переменная), тогда (A) и (B) могут вести себя по-разному , но только если GetPropValue вызывает исключение, Это связано с тем, что исключение не позволит назначить результат. Обратите внимание, что это следует избегать, за исключением особых случаев, потому что это затрудняет рассуждение о том, что делает ваш код.

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

Ответ 3

Удаление моих советов из комментариев на верхнем уровне в случае сбоя Pastebin

function GetValueUpdate3(vType:integer):integer;
begin
// with SOME types like Integer you can use Pascal case-block instead of error-prone many-ifs-ladder

  case vType of 

    99:   Result:=GetDifferentProperty(vType);// <-- Call to another Function, that could return whatever...
    1:    Result:=100;
    3..9: Result:=200;

    else Result := 12345; // initialization with safe default value for illegal input like vType=2  

  end; // case-block
end;

function GetValueUpdate3e(vType:integer):integer;
begin
  case vType of

    99:   Result:=GetDifferentProperty(vType);// <-- Call to another Function, that could return whatever...
    1:    Result:=100;
    3..9: Result:=200;

    else raise EInvalidArgument.Create('prohibited value - GetValue( ' + IntToStr(vType) +' )' );
      // runtime eror when vType = 2 or any other illegal input
  end;
end;


function GetValueUpdate1(vType:integer):integer;
begin
  Result := 12345; // initialization with safe default value;

  if vType=1 then Exit(100); // special value for special case #1

  if (vType>2) and (vType<=9) then Exit(200); // special value for special case #2

  // exit with default value
end;

procedure TForm1.Button1Click(Sender: TObject);
var vValue:integer;
begin

  vValue:=GetValue(11);
  Button1.Caption:=IntToStr(vValue);

end;

// http://stackoverflow.com/questions/33927750

Ответ 4

Вы можете использовать Утверждения, чтобы проверить правильность ввода. Утверждения помогают в поиске логических ошибок в вашем коде.

Использование утверждений заставляет вас думать о действительных и недопустимых входных данных и помогает писать лучший код. Включение и отключение утверждений во время компиляции выполняется с помощью компилятора \$C или\$ASSERTIONS (глобальный коммутатор)

В вашем примере функция GetValue может использовать утверждение следующим образом:

function GetValue(vType:integer):integer;
begin
   Assert((vType >= 1) and (vType <=9), 'Invalid input value in function GetValue');

   if vType=1 then
    Result:=100
   else if (vType>2) and (vType<=9) then
     Result:=200
   else Result := 0; // always catch the last else!
end;

Кроме того, каждый оператор if должен ловить финал еще! (По-моему ВСЕГДА!)