Елена Филиппова дата публикации 15-04-2003 14:59 Hints and Warnings или Спасение утопающих Содержание:
Каждая программа содержит по крайней мере одну ошибку
Народная мудрость
Никогда не делает ошибок тот, кто просто ничего не делает. Это тоже народная мудрость. Поэтому с ошибками в коде
сталкивается в своей работе каждый программист. После того, как программа успешно откомпилирована, преодолен первый этап
борьбы. :о)
Не секрет, что гораздо сложнее бороться с ошибками, возникающими во время выполнения
программы, особенно, когда они приводят не просто к ее "падению", а к неадекватной работе, наслаивая проблемы и создавая "наведенные" ошибки.
И здесь уже надежды на компилятор нет... Спасение утопающих, как известно, дело рук этих самых утопающих.
Материал данной статьи не имеет отношения к теме тестирования и отладки.
Он предназначен начинающим программистам, дабы обратить их внимание на "соломинку", которую протягивает утопающим
IDE Delphi в нелегком деле борьбы с ошибками :о) Ведь не зря же ее называют дружественной средой
разработки.
Хочу сразу обратить ваше внимание на то, что все приводимые примеры не являются реальными, они специально упрощены и только иллюстрируют
объяснение материала.
Типы сообщений компилятора |
Информацию о результате компиляции и сборки программы можно увидеть в окне, показывающем
процесс компиляции (рис. 1), и на панели сообщений, встроенной в редактор кода (рис. 2).
рис. 1
Сообщения компилятора бывают трех типов. В этом списке они приведены по убыванию степени опасности, если так можно выразиться :о)
- Error — ошибка
- Warning - предупреждение
- Hint - подсказка или совет.
Довольно распространенное отношение начинающих программистов к этим сообщениям заключается в полном игнорировании
предупреждений и советов. Ведь не ошибки же? Программа откомпилирована и запускается на исполнение.
И, может быть, даже работает :о)
Мне приходилось встречать на некоторых форумах "дружеские советы" новичкам, сводившиеся к предложению "не обращать на эту ерунду
внимания, это оптимизатор у Delphi выделывается."
Так ли это на самом деле?
При наличии в проекте ошибок-Errors, не будет сформирован исполняемый файл и, волей не волей, ошибки придется исправлять. Наличие
же сообщений Hints и Warnings позволяет запускать приложение.
Обратите внимание на окно процесса компиляции (рис. 1),
в строке "Done" написано не Compiled, что, в общем-то, ожидалось, а предупреждение There are warnings.
Несмотря на отсутствие ошибок, проект откомпилирован с тремя "подсказками" и пятью "предупреждениями".
Насколько безопасно не обращать на это внимание?
Начнем с самых безобидных сообщений компилятора, с его советов — Hints.
Ниже приведен код простой функции, которая не содержит синтаксических ошибок, но при её компиляции
будет получено три Hint'а (в коде они отмечены красным). Давайте разберем их подробно.
Function FunctonName( Code : String) : Integer;
Var i,j : Integer; ‹—— Variable 'i' is declared but never used in 'FunctonName'
Begin
j:=0; ‹—— Value assigned to 'j' never used
For j:=0 To -1 Do Writeln(j); ‹—— FOR or WHILE loop executes zero times - deleted
Result:=StrToInt(Code);
End;
| |
- Variable 'i' is declared but never used in 'FunctonName'
-
Переменная 'i' определена, но никогда не используется в 'FunctonName' — это одно из самых часто встречающихся
сообщений. Чаще всего оно просто говорит о неаккуратном коде. Однако, наличие таких переменных в принципе может
означать потенциальную ошибку в реализации алгоритма, ведь зачем-то она была объявлена. Именно поэтому компилятор обращает ваше внимание на эту переменную: вдруг вы просто забыли доделать задуманное?
Простейшее решение — удалить все неиспользованные переменные. А заодно и проверить, действительно ли они не нужны :о)
- Value assigned to 'j' never used
-
Значение, присвоенное 'j' никогда не используется.
Это сообщение не означает, что программа неправильная — оно означает только то, что компилятор обнаружил, что после присвоения переменной j значения 0, эта переменная не участвует
более ни в каких операциях.
Что делает это присвоение абсолютно бессмысленным.
И, если используется оптимизатор, оно будет удалено в откомпилированном коде.
Так же, как и предыдущий Hint, это сообщение чаще всего является
признаком "мусора" в коде, от которого следует избавляться, чтобы не пропустить потенциальные ошибки.
Опасность в том, что в реальности может оказаться, что это присвоение было сделано не той переменной, которой нужно. Например, надо было присвоить что-то переменной i, а присвоили j.
- FOR or WHILE loop executes zero times - deleted
-
Цикл FOR или WHILE исполняется ноль раз — удалено. Собственно, текст этого сообщения полностью объясняет ситуацию.
Конечно же это не специально, это "рука дрогнула", "глаз замылился" или что-то в таком духе. И компилятору остается только сказать спасибо.
Итак, получается, что Hint'ы обращают наше внимание на странности и несуразицы в коде с точки зрения "правильного" компилятора.
Конечно, приведенный пример очень прост и надуман и может не убедить вас, но
если в коде функции, которая содержит не один десяток операторов, появляется hint, стоит обратить на него внимание, поверьте.
Рассмотренные выше ситуации можно и нужно исправлять.
Но бывают случаи, когда нет возможности исправить код так, чтобы
не получать Hint's при компиляции.
Рассмотрим небольшой пример по созданию собственных классов:
Type
TLists = class(TList)
Protected
procedure Clear; override; ‹—— Overriding virtual method 'TLists.Clear'
has a lower visibility (private) than base class (public)
End;
TExLists = class(TList)
Private
Function FutureTools(Sender : TObject) : Boolean; ‹—— Private symbol 'FutureTools'
declared but never used
Public
...
End;
| |
- Overriding virtual method 'TLists.Clear' has a lower visibility (private) than base class (public)
-
Переопределенный виртуальный метод 'TLists.Clear' имеет видимость ниже, чем в базовом классе
Это не то, чтобы ошибка, но на практике понижение видимости свойств и методов класса
встречается довольно редко и говорит об ошибках на этапе проектирования базовых классов. Это понижение видимости
может создать в проблему в будущем, если от класса TLists будут наследоваться при создании новых классов.
- Private symbol 'FutureTools' declared but never used
-
Приватный символ 'FutureTools' определен, но никогда не используется. Это сообщение
сродни уже описанному Variable '<name>' is declared but never used...
Так как этот метод приватный, то он
по определению не может быть доступен нигде более, как внутри класса. Тем не менее, компилятор там его использования не обнаруживает. Из чего следует естественный вывод, что функция 'FutureTools'
нигде не будет использоваться.
Допустим, что в этом случае все не так просто, как это видится компилятору и функция FutureTools, например, нигде не используется вовсе не потому, что вы о ней забыли или она никому не нужна.
Возможно это задел на будущее. Можно, конечно, закомментировать и объявление функции и код ее реализации до поры до времени.
Но можно сделать и иначе, несколько изящнее.
Возможно, что по условию конкретной
задачи понижение видимости метода в классе TLists оправдано, а корректировать код базового класса нет возможности,
тогда придется попросить компилятор не принимать во внимание эту ситуацию.
Как раз для таких случаев предусмотрена сцециальная дирректива компилятора: {$HINTS OFF}.
Она отключает проверку кода на предмет обнаружения Hint'ов
до тех пор, пока в коде не встретится обратная дирректива — {$HINTS ON}.
Если в обрамленном этими специальными комментариями коде и будут "опасные" Hint-ситуации, они будут игнорироваться компилятором.
Воспользовавшись этими диррективами, мы получим код, который компилируется
не только без ошибок, но и без Hint'ов:
Type
TLists = class(TList)
Private
procedure Clear; override;
End;
TExLists = class(TList)
Private
Function FutureTools(Sender : TObject) : Boolean;
Public
...
End;
| |
Примечание:
Не поддавайтесь искушению раз и навсегда "заткнуть" с помощью {$HINTS OFF} упрямый компилятор, пользы
от этого вам, как программисту, не будет никакой...
О пользе сообщений компилятора |
Небольшое лирическое отступление:
В каждом уважающем себя форуме есть список вопросов, признанных как off-topic. Часть из них сто раз уже разжевана, часть
решается нажатием клавиши F1 и так далее. На каждом форуме борятся с ними по-своему, но, к огромному сожалению, задающих такие вопросы не становится меньше.
Более того, вопрошающие частенько еще и обижаются, когда их отсылают :о)
Вот пример классического off-topic'а:
Привет Алл! Пишу код
s:tstrings;
s:=tstrings.create;
s.insert(... // здесь ОШИБКА! Какой-то Abstract Error
s.clear;
Господа подскажите что делать?
| |
В ответ на такой вопрос, господа, как правило, начинают страшно ругаться. :о) Самые вежливые слова, которые получает автор вопроса,
звучат примерно так — "Сколько же можно?! Хелп когда научитесь читать?!" На что автор, как ему кажется, абсолютно справедливо, начинает
огрызаться, что типа, откуда ему было знать, что такое абстрактный метод и что на этом самом TStrings не написано, какие у него
методы!
Проведем маленький эксперимент и напишем такой код:
Procedure AbstractMethod;
Var Buffer : TStrings;
Begin
Buffer:=TStrings.Create; ‹—— Constructing instance of 'TStrings' containing abstract methods
Buffer.LoadFromFile('test.txt');
Buffer.Free;
End;
| |
При компиляции нам будет выдан warning, как раз на той строке, где создается экземпляр класса — Constructing instance of 'TStrings' containing abstract methods.
Я надеюсь, что текст этого предупреждения абсолютно ясен и не требует пояснений...
Смотрите, что получается, ошибок компиляции нет, человек с высоко поднятой головой игнорирует "всю эту ерунду" и просто не обращает
внимания на предупреждения компилятора! В итоге, он получает ошибку времени выполнения, некоторое личное недоумение, кучу словесных
тычков и подзатыльников на форуме. А ведь его предупреждали! :о)
рис. 2
IDE Delphi, как дружественная среда программирования, кроме обычного факта уведомления о сообщениях компилятора,
предоставляет дополнительные возможности — если дважды кликнуть на тексте сообщения (рис. 2), то курсор автоматически переместиться
на ту строку в редакторе кода, в которой, по мнению компилятора, возникает спорная ситуация. Если же на тексте
сообщения (hint или warning) нажать F1, то откроется
окно справочной системы (рис. 3) по конкретному hint'у или warning'у. Там будет описано, в каких случаях компилятор выдает
такое сообщение и что Delphi вообще "думает" по этому поводу.
рис. 3
Предупреждения-warnings обладают гораздо более высоким уровнем опасности с точки зрения компилятора.
История с абстрактным классом служит тому примером.
Разберем еще несколько случаев возникновения warning'ов:
- Return value of function 'VarCompare' might be undefined
-
Значение результата функции 'VarCompare' может быть неопределено.
Function VarCompare(Index1, Index2: Integer): Integer;
Begin
IF Index1 = Index2 Then Result:=0;
IF Index1 < Index2 Then Result:=-1;
IF Index1 > Index2 Then Result:=1;
End; ‹——Return value of function 'VarCompare' might be undefined
| |
Казалось бы, с точки зрения логики в тексте функции все верно. Перекрыты все возможные случаи и сообщение компилятора
выглядит несколько неуместно. Но не стоит ждать от него слишком много, компилятор не может (да и не обязан) вникать
в логику программы. Для того, чтобы избавиться от этого сообщения, было бы правильно переписать это код.
Например, вот так:
Function VarCompare(Index1, Index2: Integer): Integer;
Begin
IF Index1 = Index2
Then Result:=0
Else IF Index1 < Index2
Then Result:=-1
Else Result:=1;
End;
| |
В итоге и компилятор "отстанет", и код будет более читабельным.
Это сообщение только на первый взгляд кажется безобидным, ниже приведен пример, в котором возникает аналогичное предупреждение и
содержится реальная ошибка — если возникнет исключительная ситуация при открытии файла, результат функции, действительно, не будет определен.
В итоге это скажется при выполнении программы, когда ошибки никто не будет ожидать.
Function ReadList( FileName : String) : Boolean ;
Var Stream : TFileStream;
Begin
IF FileExists(FileName)
Then Try
Stream:=TFileStream.Create(FileName , fmOpenRead);
Stream.Free;
Result:=True;
Except
End
Else Result:=False;
End;‹——Return value of function 'ReadList' might be undefined
| |
Правильный вариант:
Function ReadList( FileName : String) : Boolean ;
Var Stream : TFileStream;
Begin
IF FileExists(FileName)
Then Try
Stream:=TFileStream.Create(FileName , fmOpenRead);
Stream.Free;
Result:=True;
Except
Result:=False;
End
Else Result:=False;
End;
| |
Еще один пример коварного warning'а:
- Variable 'list' might not have been initialized
-
Переменная 'list' может быть не инициализирована.
Function SomethingList( Text : String) : Integer;
Var list : TStringList;
Begin
IF Text <> '' Then
Begin
list:=TStringList.Create;
list.CommaText:=Text;
End;
Result:=list.Count; ‹—— Variable 'list' might not have been initialized
list.Free;
End; | |
Совершенно справедливое замечание.
Если во время работы программы в функцию будет передана пустая строка, нам обеспечен знаменитый Access violation.
Вернемся еще раз к примеру с определением собственных классов.
TExLists = class(TList)
Public
procedure Clear; ‹—— Method 'Clear' hides virtual method of base type 'TList'
End;
| |
- Method 'Clear' hides virtual method of base type 'TList'
-
Метод 'Clear' прячет виртуальный метод базового класса 'TList'.
Эта ситуация буквально означает перекрытие виртуального метода родительского класса.
То есть, в классе TExLists определен статический метод, имя которого совпадает с виртуальным методом родительского
класса TList.
Если в дальнейшем, от класса TExLists будут наследоваться, то метод Clear для этих наследников
будет закрыт.
Правильный вариант:
TExLists = class(TList)
Public
procedure Clear; override;
End;
| |
Точно также, как и в случае с hint'ами, существуют опции для отключения сообщений компилятора о предупреждениях — {$WARNINGS OFF},
и для их включения — {$WARNINGS ON}. И точно так же хочу обратить внимание на нежелательность использования этих опций без нужды.
Молчание компилятора в этом случае не будет означать отсутствие проблемы :о)
Цель этого материала, не рассказать обо всех возможных hint'ах и warning'ах, их список слишком велик
для одной статьи, а обратить внимание на необходимость анализировать ВСЕ сообщения компилятора в ваших программах.
Елена Филиппова
Специально для Королевства Delphi
[Редактор кода] [Директивы компилятора]
Обсуждение материала [ 05-10-2013 08:51 ] 17 сообщений |