Март 2004


Профессионалу разработчику


Стивен Фернстайн,
член OTN с 2001

Реализация Codecheck: фаза построения
(Implementing Codecheck: The Construction Phase,
by Steven Feuerstein, OTN Member since 2001)

Время шаг за шагом создавать код пакетов

Часть 4 серии Фернстайна “Построение утилиты анализа кода”

Источник:

[От редакции OM/RE: Перевод первых трех статей серии ("Как построить утилиту анализа", "Начинаем, начинаем с тестирования" и "Создание высокоуровневого дизайна") опубликованы в выпусках OM/RE:


В каждой статье автор дает содержание всей (еще полностью неопубликованной) серии:

Шаги по созданию утилиты

Ниже приведено краткое содержание статей, которые будут появляться на Oracle Technology Network (OTN) в течении следующих нескольких месяцев:

Статья 1. Формулировка проблемы: неоднозначные перегрузки в пакетах.
Я начну с подробного изучения важной задачи, которую должна будет решить утилита Codecheck: проблемы неоднозначных перегрузок в пакетах. Знаете ли вы, что весьма реально, и даже очень просто, перегружать программы в пакетах таким образом, что пакет будет компилироваться, но вы не сможете вызвать эти программы? Иногда такая неоднозначность очевидна, а иногда она едва уловима. Моей целью является написание утилиты, которая будет находить эти неоднозначности. На этом первоначальном шаге я покажу, что вовлекается в создание утилиты, как выполнять необходимый анализ, и как переводить результаты этого анализа в форму, полезную для разработчиков.

Статья 2. Начинаем, начинаем с тестирования
Я меня есть основная идея относительно того, что Codecheck должна делать. Я нашел некоторые встроенные пакеты и представления словаря данных, которые мне помогут. Что дальше? Моим естественным желание является открыть мой любимый IDE (интегрированную среду разработки - integrated development environment) и начать писать код – писать быстро и неистово, увлекаясь волнением творческого процесса, продумывая детали по мере продвижения, решая возникающие задачи, заставляя что-то работать, и затем настраивая это. Однако, этот путь не ведет к высококачественной реализации. Поэтому, я отложу написание кода и, вместо этого определю, как я буду тестировать мой код, чтобы убедиться, что он удовлетворяет всем требованиям.

Статья 3. Создание дизайна высокого уровня
Хотя я не собираюсь использовать никаких инструментов проектирования для построения Codecheck, все же необходимо посвятить некоторое время продумыванию общей архитектуры приложения, которое я собираюсь конструировать. Должно ли оно состоять из одной большой толстой процедуры? Едва ли. Нуждается ли оно во вспомогательных структурах данных? В этой статье я буду создавать фундаментальную, но осуществимую архитектуру Codecheck, стараясь избежать перепроектирования.

Статья 4. Реализация Codecheck: фаза конструирования
Наконец-то! Я начинаю писать код. Однако, это странный сорт кода, потому что я собираюсь строго следовать нисходящему дизайну, или стратегии пошаговой детализации. На каждом этапе этого пути, я буду прятать сложности и подробности реализации. Другими словами, я буду чрезвычайно мешкать, но ради стоящей цели: улучшение читаемости кода и уменьшение количества ошибок. Я буду также следовать одной из моих 10 заповедей для высококачественного PL/SQL-кодирования: ограничить размер исполняемой секции не более, чем 50 или 60 строками. Звучит невероятно? Последуйте моему совету, и это существенно улучшит ваш код.

Статья 5. Улучшение информации об аргументах
Забираясь дальше в дебри Codecheck, я примусь за наиболее сложную логику, связанную с анализом потенциально неоднозначных перегрузок. Я подробно рассмотрю многоуровневые коллекции и коллекции, индексированные строками (string-indexed), и покажу, насколько они важны для того, чтобы спрятать сложность структур такого рода в процедуру или функцию.

Статья 6. Создание “Поставщика сервисов”: пакетов с концентрированной функциональностью.
На нижнем уровне иерархии программного обеспечения Codecheck находятся несколько пакетов, являющихся относительно маленькими концентрированными модулями. Они предоставляют сервисы пакетам более высоких уровней. Одним из важных элементов дизайна программного обеспечения является определение таких отдельных сервисов и создание индивидуальных пакетов (или, может быть, объектных типов) для объединения всего, относящегося к этому сервису.

Статья 7. Создание “Поставщика сервисов”, Часть 2.
Я объясню, как использовать преимущества очень полезной процедуры DBMS_UTILITY.NAME_RESOLVE и рассмотрю отдельный механизм отчетности для Codecheck. Я покажу также общий пакет обработки ошибок, который включает механизм утверждений и “изящную” процедуру RAISE.

Статья 8. Повторная настройка.
Теперь, имея работающую версию Codecheck, настало время использовать механизм тестирования utPLSQL и подготовленные мной тестовые данные, чтобы выполнить тестирование. Когда я обнаружу проблемы в коде, я буду не только исправлять их, но и открывать возможности улучшения внутренней работы моих программ, этот процесс называется рефакторинг.


Скачать codecheck.zip

В первых статьях этой серии (см. обзор) показано, как я разрабатывал план тестирования и определял случаи тестирования для своей утилиты контроля качества. Я также руководствовался здравым смыслом при общем проектировании Codecheck. И вот наконец-то настало время начать писать код. Как это лучше сделать?

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

Это звучит ужасно благоразумно. Однако существует одна большая проблема. Я планирую следовать девизу Экстремального Программирования – “проектировать только то, что нужно сейчас” - то есть, делать все как можно проще. Если я начну с элементов низкого уровня, и буду подниматься вверх, как я узнаю, что “нужно сейчас” и что потребуется позже или не потребуется вовсе?

Например, можно начать с cc_types, пакета с конкретным назначением: объединить все знания о различных типах данных, известных и поддерживаемых PL/SQL. Прекрасно. Какую функциональность я должен предложить в спецификации моего пакета? Теперь я вхожу в режим мозгового штурма и генерирую множество идей:

  • Именованные константы для каждого числового кода типов данных (многие, но не все из них, взяты из DBMS_TYPES, нового пакета в базе данных Oracle9i).
  • Именованные константы или контейнеры некоторого рода для имен различных типов данных.
  • Функции, которые возвращают TRUE или FALSE: является ли этот тип числовым типом (например, в “семействе” NUMBER)? Строковым типом? Сложным типом (таким как запись, массив или объект)?
  • Функция, которая возвращает имя типа данных по его числовому коду.
  • Функция, которая возвращает числовой код типа данных по его имени.
  • Функция, которая возвращает TRUE, если тип является типом запись.
  • Функция, которая возвращает TRUE, если тип является типом запись, объявленным с %ROWTYPE (в этом случае мне придется обрабатывать кое-что в ALL_ARGUMENTS).
  • Функция для вычисления, принадлежат ли два типа данных одному семейству или одному типу данных.
  • Процедура для отображения всех поддерживаемых в настоящее время типов данных в PL/SQL.
  • Функция, которая возвращает TRUE, если тип доступен в базе данных или только в PL/SQL (как например Boolean)
  • Функция, которая возвращает TRUE, если Oracle не одобряет использование этого типа данных (как например CHAR или LONG)
  • Функция, которая возвращает более предпочтительный тип данных для “неодобряемых” типов данных.

Вот так так! Этого, пожалуй, достаточно, чтобы дать вам повод к размышлению. Даже для такого базового пакета, как cc_types можно придумать массу интересных идей. Тем не менее, это не выход. Разве я строю этот пакет, потому что я люблю писать код, чем больше, тем лучше? Допустим так, но это уже задача для моего психотерапевта. Шутки в сторону, ответ – НЕТ. Я строю cc_types, для того, чтобы написать Codecheck самым простым, удобным для чтения и изменения способом. С этой точки зрения не имеет значения, что я могу придумать 10 или 20 или 50 интересных идей. Имеет значение следующее: какие фрагменты функциональности требуются Codecheck? На этом этапе я еще не знаю.

Должен ли я просто написать все эти программы? Ничего подобного. Осознание этого неизбежно приводит меня к необходимости прекратить процесс снизу-вверх. Вместо этого, я предпочту подход сверху-вниз. Я начну на самом высоком уровне Codecheck (а именно, с выполняемого раздела процедуры codecheck.overloadings) и буду продвигаться вглубь. Вместо того чтобы писать программу в виде одного огромного блока, состоящего из сотен строк исполняемого кода, я разобью его на более мелкие модули. Кроме того, я детально обдумаю логику, необходимую на каждом уровне детализации, перед тем, как идти дальше. В этом процессе, как вы увидите, я определю конкретно, что мне нужно в cc_types, cc_names, cc_arguments и во всех других пакетах.

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

Высокоуровневое описание codecheck.overloadings

Первой программой, которую я хочу реализовать в Codecheck, является программа, которая будет анализировать перегрузки пакета на предмет возможных неоднозначностей. Иначе говоря, я хочу проверять перегрузки пакета. Поскольку имя моего пакета уже содержит слово check, я назову эту процедуру overloadings, то есть codecheck.overloadings. (Для меня это звучит лучше, чем codecheck.check_overloadings.)

Перед тем, как начать писать код, стоит подумать, как будет вызываться эта процедура? Какие параметры необходимо указать? Только имя пакета, содержащего перегрузки, которые я хочу анализировать, ничего более. С учетом этого, вот пример вызова моей процедуры: codecheck.overloadings (package_in => l_name);

Это начало. У меня есть заголовок (имя и список параметров) программы, поэтому я могу начать конструировать ее. Снова пришло время перейти в режим “мозгового штурма”, но с другой точки зрения. Вместо того чтобы придумывать список всех возможных программ, которые, возможно, потребуются, я буду думать о больших кусках логики, которые будут нужны, чтобы сделать работу – и ни о чем кроме этого.

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

В псевдокоде, у меня получится нечто вроде этого:

Для каждого имени программы в пакете
loop
   If эта программа перегружаемая
   then
      Для каждой перегрузки этой программы
      loop
         проверить на соответствие все другие перегрузки 
      end loop для каждой перегрузки
   end if программа перегружаемая
end loop для каждой программы

Показать результаты анализа

Кажется, достаточно ясно. На Листинге 1 показан PL/SQL код. (Далее в этой статье я буду пропускать псевдокод, и буду выражать свою высокоуровневую логику непосредственно на PL/SQL.)

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

Теперь я закончил с первым уровнем спецификации процедуры overloadings. Я знаю гораздо больше о том, что мне нужно из cc_smartargs, чем до этого, но я стараюсь избегать любых подробностей реализации этого пакета. На следующем этапе у меня есть некоторый выбор. Я могу реализовать следующий уровень детализации в overloadings (initialize и compare_with_others), или могу переключиться на cc_smartargs, и начать искать, что ему потребуется, чтобы предоставить всю эту информацию overloadings. Пожалуй, я повожусь еще немного с overloadings. Мне необходимо знать больше о требованиях к cc_smartargs (и кто знает о чем еще?).

Логика инициализации

Какие этапы инициализации необходимы overloadings? Снова, оставаясь на самом высоком уровне, насколько это возможно, я сформулировал следующие этапы:

  • Убедиться, что объект с именем package_in действительно является пакетом. Нет смысла анализировать перегрузки для отдельной программы или представления.
  • Загрузить всю информацию об аргументах для этого пакета.
  • Инициализировать компонент отчета. (Мне необходимо передать информацию о том, запускаю ли я его в тестовом режиме).

Что привело меня к следующей процедуре инициализации:

 1  PROCEDURE initialize
 2  IS
 3  BEGIN

 4     cc_smartargs.load_arguments (package_in);
 5
 6     cc_error.assert (
 7        cc_smartargs.ispackage
 8       ,cc_error.c_not_a_package
 9       ,package_in);
10
11     cc_report.initialize (package_in);
12* END initialize;

В строке 4 я прошу cc_smartargs выполнить трудную задачу: загрузить всю информацию об аргументах этого пакета в – ладно, в то, что будет лучше работать, это я решу позже. Я не собираюсь беспокоиться об этом прямо сейчас.

В строках с 6 по 9 я вызываю мою первую программу, связанную с ошибками: программу утверждений (assertion routine). Я вызываю эту программу, утверждая, что конкретное выражение является истинным. Если оно ложное, я остановлю выполнение программы. Программы утверждений являются нормальной частью программирования в других языках, таких как Java и C, и должны чаще использоваться в мире PL/SQL. В этом случае я хочу убедиться, что package_in действительно является именем пакета, поэтому вызываю функцию в cc_smartargs, чтобы получить эту информацию; определяю ошибку, которую я получу, если это не пакет; и также предоставляю имя этого не пакета, которое появится в сообщении для пользователя.

Я еще не знаю, что cc_error будет делать со всей этой информацией, но, используя подход передачи основных данных отдельной программе утверждений, я полагаю, что дам cc_error все необходимое для выполнения ее работы, не диктуя, как нужно выполнять эту работу. Это даст мне максимум гибкости позднее, когда я перейду к реализации пакета.
Возможно, вы удивитесь, зачем обращаться к cc_smartargs, чтобы узнать существует или нет указанный пакет. Как это относится к “удобной информации” об аргументах? Чтобы ответить на этот вопрос нам необходимо задать другой: Как можно легко выяснить, является ли программа пакетом? DBMS_UTILITY.NAME_RESOLVE может сказать мне это. И эта программа будет вызываться из cc_names, которая, как мне кажется, будет вызываться из cc_smartargs или cc_arguments в процессе загрузки всей информации об аргументах. Итак, я собираюсь принять решение, что мне необходимо брать информацию из cc_smartargs.

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

Наконец (возвращаясь к коду инициализационной программы), строка 11 вызывает отчетную программу пакета initialize, чтобы определить, работает ли она в тестовом режиме. Еще раз, вообще говоря, я не знаю, что этот тестовый режим может означать внутри cc_report, и, возможно, мне потребуется изменить его позднее, но сейчас, включение этой возможности кажется мне правильным шагом.

Сравнение одной перегрузки с другой

Следующей по порядку программой, необходимой мне, чтобы завершить codecheck.overloadings является compare_with_others. Она будет несколько более интересной и перспективной, чем initialize. Вот заголовок программы, в качестве напоминания:

PROCEDURE compare_with_others (
   program_in IN all_arguments.object_name%TYPE
  ,check_this_ovld_in IN PLS_INTEGER)

Что конкретно эта программа должна делать? Мне необходимо видеть, может ли текущая перегрузка быть вызвана способом, который был бы неотличим от вызова другой перегрузки. Поэтому, я должен сравнить ее с N—1 другими перегрузками. Это приводит меня непосредственно к более интересному, глубокому вопросу, какая нужна логика, чтобы выполнить это сравнение? Какие условия я должен проверить? Два основных фактора приходят в голову:

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

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

Примечание: когда я обсуждал возможные варианты правильных вызовов программы, я решил, что буду работать только с позиционной нотацией. Проблема неоднозначности перегрузок в большой степени исчезает, при использовании именованной нотации (parameter_name => parameter_value).

Поэтому, когда я говорю “сравнить перегрузку N со всеми другими перегрузками”, на самом деле я имею в виду (и собираюсь сделать) “сравнить все корректные варианты вызовов перегрузки N со всеми корректными вызовами всех других перегрузок”. Реализация compare_with_others представлена в Листинге 2.

Границы цикла FOR loop гарантируют, что я не сравниваю никакую перегрузку с самой собой, и не делаю повторно сравнений, уже сделанных на предыдущем шаге цикла, из которого вызывается compare_with_others.

Можно также заметить, что я определил еще две программы в Codecheck - same_program_types и compare_all_invocations – чтобы обрабатывать нетривиальные аспекты compare_with_others. Это начало кажется похожим на русскую матрешку, в которой вы открываете одну матрешку только для того, чтобы внутри нее найти другую? Я уверяю вас, что я не оттягиваю и не откладываю трудную работу. Напротив, я тружусь в поте лица, чтобы сделать все простым и понятным. Это не только обеспечивает избавление от ненужной работы (и переделывания изначально неверного кода), но также минимизирует возможность возникновения ошибок, поскольку я проверяю и корректирую логику на каждом этапе.

Надеюсь, что я вас убедил, на Листинге 3 представлена реализация same_program_types. Таблица 2 описывает эту реализацию.

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

Алгоритмы для идентификации неоднозначности

Я обнаружил, что мне необходимо сравнить все корректные вызовы каждой различной пары перегрузок. Сompare_all_invocations делает это (см. Листинг 4). В таблице 3 объясняется ее логика.

Покажите мне результат?

К этому времени, я полагаю, вы уже потеряли со мной терпение. Зачем нужна еще другая программа? Когда мы наконец увидим какой-нибудь реальный код? Я понимаю вашу реакцию. Но позвольте вас спросить: трудно ли следовать за логикой (сейчас состоящей более чем из 100 строк кода), которую я представлял до настоящего времени? Или она кажется настолько прозрачной – фактически упрощенной – что вам не терпится идти дальше?

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

Проверка сходства

Процедура check_for_similarity является последней или находится на самом низком уровне детализации, необходимом Codecheck, чтобы выполнить анализ неоднозначности. Она принимает в качестве параметров информацию, необходимую для идентификации двух перегрузок и специфическое подмножество списка параметров для каждой перегрузки. Вы можете описать это как сравнение перегрузки 1, вызываемой с ее первыми тремя параметрами, с перегрузкой 2, вызываемой с шестью параметрами. Если check_for_similarity обнаруживает, что две перегрузки являются неоднозначными, она сообщает об этом cc_report.

Как узнать, являются ли они неоднозначными? Давайте обсудим некоторую необходимую логическую схему:

  • Если количество аргументов в перегрузке первого вызова отлично от количества аргументов в перегрузке второго, то они не являются неоднозначными.
  • Если ни один из вызовов не имеет аргументов, то они определенно очень похожи.
  • В противном случае, мне необходимо сравнить типы данных каждого аргумента в перегрузке 1 с соответствующим (по позиции) аргументом перегрузки 2. Если каждое сравнение даст слишком похожие ответы, то перегрузки являются неоднозначными.

Это дает мне достаточно подробностей, чтобы начать реализацию процедуры (см. Листинг 5). Я описал код в таблице 4.

Это все, что необходимо написать (по крайней мере, мне так кажется) в процедуре codecheck.overloadings, чтобы выполнить анализ. Могу ли я теперь запустить программу, чтобы увидеть работает ли она? Размечтался. Все, что я сделал, это, в сущности, мозговой штурм (с использованием методологии нисходящего дизайна), чтобы заставить codecheck.overloadings работать.

Я точно осведомлен о том, что мне необходимо на следующем уровне улучшения этой логики. В следующей статье этой серии я перейду к следующему уровню детализации (в программистских кругах это называется пошаговая детализация) и реализую каждую из программ в cc_arguments и cc_smartargs, которые определены в Codecheck.


Стивен Ферстайн (Steven Feuerstein - steven@stevenfeuerstein.com) - один из мировых лидеров в PL/SQL от Oracle. Он автор или соавтор девяти книг по PL/SQL, включая “Oracle PL/SQL Programming”, 3-е издание и “ Oracle PL/SQL Best Practices” (O'Reilly, oracle.oreilly.com). Стивен – главный технический консультант компании Quest Software, он разрабатывает программное обеспечение с 1980, он работал в Oracle с 1987 по 1992. С. Ферстайн также является президентом совета директоров каомпании Crossroads Fund (www.CrossroadsFund.org).


Таблица 1: Процесс обдумывания для описания элементов
Строки Пояснение
2 Вызов программы инициализации. Что она делает? Я вернусь к этому позже, потому что это следующий уровень детализации, и сейчас это не важно.
3 Мне необходимо пройти по всем различным (distinct) именам программ в пакете. Как их узнать? Эта информация доступна в ALL_ARGUMENTS:
SELECT DISTINCT object_name FROM all_arguments
 WHERE owner = <the owner> and package_name = <the package>

Но я уже решил, что вся такая информация будет обрабатываться в cc_arguments или, возможно, в cc_smartargs. Действительно, поскольку предполагается, что cc_arguments будет делать нечто большее, чем просто отображать содержимое ALL_ARGUMENTS, я могу сказать, что список различных имен программ – это “удобное” дополнение, и поэтому оно принадлежит cc_smartargs. Это позволяет мне сразу думать в терминах API или набора программ, которые дают мне информацию об этом наборе различных имен: первый, следующий, последний. Поэтому строка 4 использует первое имя.

5-6 Начинаем цикл и выясняем, как его завершить (когда я пройду по всем различным именам, имя программы будет NULL).
8 Является ли это имя именем перегружаемой программы в пакете? Только cc_smartargs может сказать мне, и поэтому я объявляю другую функцию в API: булевскую has_overloadings.
10-13 Теперь мне необходимо посмотреть на каждую перегрузку для этой программы. Похоже, что мне снова необходима информация, которую я собираюсь получить из данных, извлекаемых из ALL_ARGUMENTS, поэтому я буду полагаться на новые программы в cc_smartargs, чтобы определить границы: от первой до последней перегрузки для текущей программы.
14 Мне необходимо сравнить каждую найденную перегрузку с другой перегрузкой, чтобы увидеть, являются ли они различными или слишком похожими вызовами. Это похоже на логику процедуры codecheck.overloadings, поэтому я просто помешаю здесь заглушку для процедуры compare_with_others, принадлежащей overloadings.
18 Когда я покончу с этой программой, я перейду к следующей, снова полагаясь на функцию из cc_smartargs.
21 Время показывать результаты. Я объявил заголовок для show_ambig_ovld_results, первой процедуры в пакете cc_report. Мне определенно необходимо передать ей имя программы, но на этом этапе я не знаю, что еще нужно. Я вернусь к этому, когда буду готов.

Таблица 2: Описание реализации same_program_types
Строки Пояснение
1 Хотя этого нельзя сказать из Листинга 3, эта функция не является локальным модулем, объявленным внутри пакета, в отличии от initialize и compare_with_others. Я решил объявить ее на том же уровне, что и overloadings внутри пакета. Я сделал так, потому что мне кажется, что содержание same_program_types характерно для логики overloadings. Другая высокоуровневая процедура проверки в Codecheck может пожелать узнать, принадлежат ли две перегрузки одному программному типу.
8-11 Как мне выяснить тип конкретной перегрузки? Вы не будете очень удивлены, что я могу взять, и возьму эту информацию из cc_smartargs. Она имеет список перегрузок, поэтому также может отслеживать информацию о каждой перегрузке. Вот так так. Похоже, что cc_smartargs собирается стать самым трудным пакетом для реализации. Будем надеяться, что если я буду делать по одному уровню за раз, все будет не так страшно.
13-17 Основной логикой same_program_types является простое булевское выражение. Типы являются одинаковыми, если они оба функции, или оба “не функции” (т.е. процедуры).

Таблица 3: Логика compare_all_invocations
Строки Пояснение
2-4 Параметры. У меня есть перегрузка (check_this_in), которую я сравниваю с другой перегрузкой (against_this_in). Перегрузка идентифицируется просто числом, N-ая перегрузка заданной программы.
9-16 Внешний цикл для логики сравнения. Я использую метку, чтобы дать имя этому (и внутреннему) циклу, чтобы улучшить читабельность кода. Еще раз, я обращаюсь к cc_smartargs, чтобы выбрать первый и последний вызов для "check this" перегрузки. Как определяются эти вызовы? Я не знаю, и сейчас не беспокоюсь об этом. Я просто знаю, что существует N корректных вызовов, и что cc_smartargs проведет меня через них.
8-25 Внутренний цикл, который проводит нас через вызовы "against this".
26-32 Наконец, “действие” - тело вложенных циклов. И здесь я обнаруживаю, что я просто вызываю другую процедуру - check_for_similarity. Я передаю этой программе всю информацию, необходимую для сравнения этих двух вызовов.

Таблица 4: Описание реализации check_for_similarity.
Строки Пояснение
1-4 Заголовок и список параметров. Аргументы overload1_in и overload2_in ссылаются на конкретные перегрузки program_in. perm1_in и perm2_in – это конкретные вызовы со списками параметров для каждой соответствующей перегрузки.
7-13 Чтобы надлежащим образом сравнить каждую пару аргументов в двух перегрузках, мне необходимо получить список информации о параметрах “верхнего уровня” (те строки в ALL_ARGUMENTS, которые действительно появляются в списке параметров программы) для cc_smartargs. Но я буду выбирать список параметров, как объявлено в cc_arguments. Поэтому, я объявляю две локальных переменных, как коллекции или списки параметров, объявленных в cc_arguments. И вызываю функцию parameter_list в cc_smartargs, которая возвращает только необходимую информацию – а именно, от первого до N-го аргумента, как задано в этом вызове.
15-16 Здесь я получаю количество строк в каждом списке параметров и присваиваю их локальным переменным.
21-27 Простая часть этой процедуры: я смотрю количество аргументов в каждом списке параметров и принимаю решение, закончил ли я (явно различны или явно похожи – нет аргументов).
29-39 Время сравнивать соответствующие параметры в каждом из списков. Я использую простой цикл для прохода по каждой позиции списка. Я остановлюсь, когда закончатся параметры в первой перегрузке или когда выяснится, что существует достаточное различие между перегрузками, чтобы закончить проверку. В теле цикла я вызываю функцию из cc_arguments, чтобы выяснить, принадлежат ли типы данных двух аргументов к одному “семейству”. Строка 38 переводит меня к следующему параметру в списке.
42-49 Если две перегрузки слишком похожи, наступает время использовать cc_report, чтобы сообщить об этом факте. Мне необходима процедура, чтобы сообщить о неоднозначной перегрузке – и ей нужно передать всю информацию, необходимую для идентификации этих двух отдельных вызовов: имя программы, порядковый номер перегрузки и, наконец, позицию аргумента.


LISTING 1

1  BEGIN -- main overloadings

 2     initialize;
 3     l_program_name := cc_smartargs.first_program;
 4
 5     LOOP
 6        EXIT WHEN l_program_name IS NULL;
 7
 8        IF cc_smartargs.has_overloadings (l_program_name)
 9        THEN
10           FOR overloading IN
11              cc_smartargs.first_overloading (l_program_name) ..
12              cc_smartargs.last_overloading (l_program_name)
13           LOOP
14              compare_with_others (l_program_name, overloading);
15           END LOOP;
16        END IF;
17
18        l_program_name := cc_smartargs.next_program (l_program_name);
19     END LOOP;
20
21     cc_report.show_ambig_ovld_results (program_in); -- Parameters?
22  END overloadings;

LISTING 2


 1  PROCEDURE compare_with_others (
 2     program_in IN all_arguments.object_name%TYPE
 3    ,check_this_ovld_in IN PLS_INTEGER

 4  )
 5  IS
 6  BEGIN
 7     FOR against_this_ovld IN
 8           check_this_ovld_in
 9         + 1 .. cc_smartargs.last_overloading (program_in)
10     LOOP
11        IF same_program_types (program_in
12                              ,check_this_ovld_in
13                              ,against_this_ovld
14                              )
15        THEN
16           compare_all_invocations (program_in
17                                    ,check_this_ovld_in
18                                    ,against_this_ovld
19                                    );
20        END IF;
21     END LOOP;
22  END compare_with_others;

LISTING 3


 1  FUNCTION same_program_types (
 2    program_in IN VARCHAR2
 3   ,overload1_in IN PLS_INTEGER
 4   ,overload2_in IN PLS_INTEGER
 5  )
 6    RETURN BOOLEAN
 7  IS
 8    l_arg1_is_function BOOLEAN
 9             := cc_smartargs.is_function (program_in, overload1_in);
10    l_arg2_is_function BOOLEAN
11             := cc_smartargs.is_function (program_in, overload2_in);
12  BEGIN
13    RETURN (   (    l_arg1_is_function
14                AND l_arg2_is_function)
15            OR (    NOT l_arg1_is_function
16                AND NOT l_arg2_is_function)
17           );
18  END same_program_types;


LISTING 4


 1  PROCEDURE compare_all_permutations (
 2     program_in IN VARCHAR2
 3    ,check_this_in IN PLS_INTEGER
 4    ,against_this_in IN PLS_INTEGER
 5  )
 6  IS
 7  BEGIN
 8
 9     <>
10     FOR check_this_perm IN
11        cc_smartargs.first_permutation (
12           program_in,check_this_in)
13        ..
14        cc_smartargs.last_permutation (
15           program_in, check_this_in)
16     LOOP
17
18        <>
19        FOR against_this_perm IN
20           cc_smartargs.first_permutation (
21              program_in, against_this_in)
22           ..
23           cc_smartargs.last_permutation (
24              program_in, against_this_in)
25        LOOP
26           check_for_similarity (program_in
27                                ,check_this_in
28                                ,check_this_perm
29                                ,against_this_in
30                                ,against_this_perm
31                                ,show_in
32                                );
33        END LOOP against_this;
34     END LOOP check_this;
35  END compare_all_permutations;

LISTING 5

 1  PROCEDURE check_for_similarity (
 2     program_in IN VARCHAR2
 3    ,overload1_in IN PLS_INTEGER, perm1_in IN PLS_INTEGER
 4    ,overload2_in IN PLS_INTEGER, perm2_in IN PLS_INTEGER
 5  )
 6  IS
 7     arglist1 cc_arguments.arguments_tt
 8        := cc_smartargs.parameter_list (
 9              program_in, overload1_in, 1, perm1_in);10
11     arglist2 cc_arguments.arguments_tt
12        := cc_smartargs.parameter_list (
13              program_in, overload2_in, 1, perm2_in);
14
15     l_numargs_perm1 PLS_INTEGER := arglist1.COUNT;
16     l_numargs_perm2 PLS_INTEGER := arglist2.COUNT;
17
18     arg_row PLS_INTEGER := 1;
19     too_similar BOOLEAN;
20  BEGIN
21     IF l_numargs_perm1 != l_numargs_perm2
22     THEN
23        too_similar := FALSE;
24     ELSIF     l_numargs_perm1 = 0
25           AND l_numargs_perm2 = 0
26     THEN
27        too_similar := TRUE;
28     ELSE
29        LOOP
30           EXIT WHEN arg_row IS NULL
31                 OR NOT too_similar
32                 OR arg_row > l_numargs_perm1;
33
34           too_similar :=
35              cc_arguments.in_same_family (
36                 arglist1 (arg_row), arglist2 (arg_row));
37
38           arg_row := arglist1.NEXT (arg_row);
39        END LOOP;
40     END IF;
41
42     IF too_similar
43     THEN
44        cc_report.ambig_ovld (
45           program_in
46          ,overload1_in, arg1_end_in
47          ,overload2_in, arg2_end_in
48          );
49     END IF;
50  END;
E-mail this page