Следует ли инициализировать переменную перед вызовом функции?
когда я вызываю функции для получения значения, я обычно инициализирую varible, если функция терпит неудачу или ничего не возвращает, и я хочу избежать работы с неинициализированной переменной. Я делаю то же самое для string, integer или любого другого типа.
пример для целых переменных:
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;
в этом случае значение, возвращаемое функцией, является случайным числом.
в этом случае инициализация представляется допустимым подходом. ИЛИ НЕТ?
EDIT 2:
как Дэвид указал в его ответ, верный, был предупреждающим
[dcc32 Warning] Unit1.pas(33): W1035 Return value of function 'GetValue' might be undefined
но, я проигнорировал его, без причины, просто не существует. Поскольку это позволило мне скомпилировать его, я думал, что все в порядке. Итак, я искал предупреждение, и я "исправил" довольно много функций, которые имели аналогичную проблему, потому что результат IFs, возможно, не был определен.
EDIT 3 & ЗАКЛЮЧЕНИЕ:
Я надеюсь, что это добавляет к сфере вопроса и объяснения:
может быть, пример другого твист, который я использую в большинстве своих функций, также объяснил бы, почему я думал, что моя инициализация переменной необходима, заключается в том, что я не был уверен, что мои функции будут вести себя правильно все время, особенно в случае вложенной функции. Mots из них все еще установлены так:
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
строки..) и, вероятно, другие ненужные вещи.
4 ответов
предполагая, что vPropValue
является локальной переменной, то этот код
vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');
неотличима от
vPropValue := GetPropValue(vObject,'Height');
более простой пример может быть таким:
i := 0;
i := 1;
какой смысл назначать 0
to i
и затем сразу же назначить 1
to 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 для некоторых более сложных типов на самом деле является . Так что эта функция
function foo: string;
на самом деле, семантически, то же самое, что это:
procedure foo(var result: string);
это действительно странное решение, принятое дизайнерами Delphi. В большинстве других языков, таких как C, C++, С#, Java и т. д., возвращаемое значение функции похоже на параметр by-value, передаваемый от вызываемого абонента вызывающему.
это означает, что вы можете, если хотите быть извращенным, передавать значения в функцию через ее возвращаемое значение. Например, рассмотрим этот код:
// 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
. Это довольно странно деталь реализации. Вы не должны полагаться на это. Позвольте мне повторить. Вы не должны полагаться на это. Не привыкайте инициализировать возвращаемые значения на сайте вызовов. Это приводит к недостижимому коду.
вместо этого следуйте простому правилу, гарантирующему, что возвращаемое значение функции всегда назначается функцией и никогда не считывается до его назначения.
более подробная информация о реализации возвращаемых значений функции предоставляется документация, С моим акцентом:
для возврата результата функции используются следующие соглашения ценности.
- порядковые результаты возвращаются, когда это возможно, в регистре ЦП. Байты возвращаются в AL, слова возвращаются в AX и double-words возвращаются в EAX.
- реальные результаты возвращаются в регистр верхнего стека сопроцессора с плавающей запятой (ST (0)). Результаты функции типа Currency , значение в ST (0) масштабируется на 10000. Например, значение валюты 1.234 возвращается в ST (0) как 12340.
- для строки, динамического массива, указателя метода или результата варианта эффекты такие же, как если бы результат функции был объявлен как дополнительный параметр var после объявленных параметров. В других слова, вызывающий передает дополнительный 32-разрядный указатель, который указывает на переменная, в которую возвращается результат функции.
- Int64 возвращается в EDX: EAX.
- указатель, класс, класс-ссылка, и порядок-указатель результат возвращается в eax.
- для статического массива, записи и набора результатов, если значение занимает один байт, оно возвращается в AL; если значение занимает два байта, оно возвращается в AX; и если значение занимает четыре байта, оно возвращается в РЕГИСТР EAX. В противном случае результат возвращается в дополнительный параметр var это передается функции после заявленные параметры.
следующий код (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
что-нибудь кроме "Привет" результат функции будет практически случайным. (Для ради обсуждения, предположим, что он вернется -42
.)
блок кода (A) будет делать следующее:
- Set
vPropValue
до 0 - затем установить
vPropValue
к - 42
блок кода (B) просто установит vPropValue
to - 42 немедленно.
совет: нет смысла писать расточительную строку кода только потому, что вы беспокоитесь, что вы могли сделать ошибку в функции, которую вы вызываете.
Во-Первых, Как Дэвид указывает, что вы можете избежать многих ошибок, просто обратив внимание на подсказки и предупреждения компилятора. Во-вторых, такого рода "параноидальное" кодирование просто приводит к более расточительному коду, потому что теперь вы должны начать рассматривать недопустимые значения как возможные результаты.
Это становится хуже, когда однажды ваше "безопасное значение" на самом деле является допустимое значение. Е. Г. как бы вам объяснить разницу между "по умолчанию 0" и "правильно вернулся 0"?
не делайте Программирование искусственно сложным, раздувая код с ненужными избыточностями.
Примечание
есть несколько особых ситуаций, когда код может вести себя по-разному. Но вы должны в любом случае избегать конструкций, которые приводят к этим ситуациям, потому что они значительно затрудняют поддержание кода.
я упоминаю их исключительно ради полноты, совет выше все еще стоит.
1) Если vPropValue
реализуется как свойство, сеттер может иметь побочные эффекты, которые вызывают различное поведение. Хотя нет ничего плохого в свойствах, когда они делают неожиданные вещи, у вас есть серьезная проблема на ваших руках.
2) Если vPropValue
- это поле класса (или, что еще хуже, глобальная переменная), затем (A) и (B) мог бы вести себя по-другому но только если GetPropValue
поднимает исключение. Это связано с тем, что исключение предотвратит назначение результата. Обратите внимание, что этого следует избегать, за исключением особых случаев, поскольку это затрудняет рассуждение о том, что делает ваш код.
и на самом деле, это то, что делает его гораздо более важным, чтобы избежать резервные initialisations. Вы хотите, чтобы ваш код особого случая выделялся среди остальных.
очистка моих советов от комментариев верхнего уровня в случае сбоя 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
можно использовать утверждения для проверки правильности ввода. Утверждения помогают в поиске логических ошибок в коде.
использование утверждений заставляет вас думать о допустимых и недопустимых входных данных и помогает писать лучший код. Включение и отключение утверждений во время компиляции выполняется с помощью компилятора \$C или \ $ASSERTIONS (global switch)
в вашем примере функция 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-оператор должен поймать окончательный еще! (По-моему, всегда!)