Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
Обучение от Mail.Ru Group.
Онлайн-университет
для программистов с
гарантией трудоустройства.
Набор открыт!
2003 г

Hints and Warnings, или Спасение утопающих

Елена Филиппова, Королевство Дельфи
15 апреля 2003г.

Содержание:

Каждая программа содержит по крайней мере одну ошибку
Народная мудрость

Никогда не делает ошибок тот, кто просто ничего не делает. Это тоже народная мудрость. Поэтому с ошибками в коде сталкивается в своей работе каждый программист. После того, как программа успешно откомпилирована, преодолен первый этап борьбы. :о)

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

Материал данной статьи не имеет отношения к теме тестирования и отладки. Он предназначен начинающим программистам, дабы обратить их внимание на "соломинку", которую протягивает утопающим IDE Delphi в нелегком деле борьбы с ошибками :о) Ведь не зря же ее называют дружественной средой разработки.

Хочу сразу обратить ваше внимание на то, что все приводимые примеры не являются реальными, они специально упрощены и только иллюстрируют объяснение материала.

Типы сообщений компилятора

Информацию о результате компиляции и сборки программы можно увидеть в окне, показывающем процесс компиляции (рис. 1), и на панели сообщений, встроенной в редактор кода (рис. 2).

Результат компиляции проекта
рис. 1 Сообщения компилятора бывают трех типов. В этом списке они приведены по убыванию степени опасности, если так можно выразиться :о)

  1. Error — ошибка
  2. Warning - предупреждение
  3. Hint - подсказка или совет.

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

Мне приходилось встречать на некоторых форумах "дружеские советы" новичкам, сводившиеся к предложению "не обращать на эту ерунду внимания, это оптимизатор у Delphi выделывается."
Так ли это на самом деле?

При наличии в проекте ошибок-Errors, не будет сформирован исполняемый файл и, волей не волей, ошибки придется исправлять. Наличие же сообщений Hints и Warnings позволяет запускать приложение. Обратите внимание на окно процесса компиляции (рис. 1), в строке "Done" написано не Compiled, что, в общем-то, ожидалось, а предупреждение There are warnings. Несмотря на отсутствие ошибок, проект откомпилирован с тремя "подсказками" и пятью "предупреждениями".
Насколько безопасно не обращать на это внимание? Начнем с самых безобидных сообщений компилятора, с его советов — Hints.

Безобидные(?) 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
 {$HINTS OFF}
  TLists = class(TList)
  Private
    procedure Clear; override; 
  End;
 {$HINTS ON}

  TExLists = class(TList)
  Private
  {$HINTS OFF}
    Function FutureTools(Sender : TObject) : Boolean;  
  {$HINTS ON}
  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

Предупреждения-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

Новости мира IT:

Архив новостей

Последние комментарии:

Вышло обновление Firefox 57.0.1 (1)
Среда 06.12, 09:14

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 985 1945361
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2015 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...