Реализация модулей

Реализация модулей

В чем тайный смысл такой горячей спешки, Что стала ночь сотрудницею дня?.
Шекспир. Гамлет.
Написание программ на скорую руку дает быстрый, но сомнительный результат; дисциплинированный подход, напротив, обеспечивает более высокое качество с меньшими временными затратами.
В этой главе не предпринимается попытка охватить все аспекты программирования в духе таких книг, как [44]. Вместо этого мы обсудим принципы программирования и дадим рекомендации, которые относятся в большей степени к компаниям, разрабатывающим программные приложения. Положение этой главы в контексте разработки программного обеспечения показано на рис. 7.1.
♦ Разделы 7.1-7.7.
♦ Упражнения
Рис. 7.1. План разработки программ: темы главы 7
♦ Примеры.
1) Обновление Плана контроля качества программного обеспечения (SQAP).
2) Обновление Плана управления конфигурациями программного обеспечения (SCMP).
3) Индивидуальная программная документация (PSD), часть 1.
4) Пример исходного кода для класса EncounterCharacter (ПерсонажВстречи) В этой главе рассматриваются следующие вопросы:.
4- Выбор стандартов.
♦ Выбор стиля кодирования.
♦ Кодирование с обоснованием корректности.
♦ Определение требований качества.
7.1. Введение в реализацию.
7.1.1.
Определение «реализации модулей».
Термин «реализация» относится к программированию. Термин «модуль» относится к самым мелким частям реализации, которые можно поддерживать отдельно: это может быть отдельный метод или класс. В примерах мы будем считать методы модулями самого низкого уровня.
7.1.2.
Цели реализации.
Целью реализации является удовлетворение требований способом, определенным в детальном проектировании. Детального проекта должно быть достаточно, поскольку это документ, на котором основывается программирование. Однако часто программист исследует параллельно все предшествующие документы (архитектуру, D-требования и С-требования), что помогает сгладить несогласованность этих документов.
7.1.3.
Типичная схема процесса реализации модулей.
Типичный процесс написания кода программы показан на рис. 7.2.
1.
Стандарты кодирования на рис. 7.2 определены так, чтобы исходный код имел стандартный вид (раздел 7.3).
2.
Архитектура определяет каркас и пакеты прикладных программ. Каждый класс каждого пакета затем реализуется через кодирование методов, определенных требованиями в детальном проектировании. Пакеты структуры необходимо иметь до начала разработки программы.
3.
Каждый класс инспектируется, как только он завершен.
4.
Затем каждый класс тестируется, как описано в главе 8.
5.
Пакет или классы затем выпускаются для интеграции в разрабатываемое приложение.
Рис. 7.2. План реализации модуля
ОДИН ИЗ СПОСОБОВ ПОДГОТОВИТЬСЯ К РЕАЛИЗАЦИИ----.
1.
Подтвердите детальное проектирование, которое вы должны реализовать.
♦ Кодируйте только записанные этапы проектирования (части SDD).
2.
Подготовьтесь измерить затраченное время, классифицируя по следующим пунктам:.
♦ остаточное детальное проектирование; обзор детального проектирования; кодирование; обзор кодирования; компилирование и исправление синтаксических дефектов; тестирование модулей (глава 8) и исправление дефектов, найденных при тестировании.
3.
Подготовьтесь записать дефекты, упорядочив их по двум характеристикам:.
♦ серьезность: высокая (требование не выполнено), низкая или ни то, ни другое;.
♦ тип: ошибка, присваивание имен, окружение, система, данные, другой.
4.
Стремитесь понимать, какие стандарты требуются:.
♦ для кодирования;.
♦ для персональной документации, которую вы должны хранить.
♦ См. учебный пример в конце главы.
5.
Оцените объем работ и время на основе ваших предыдущих данных.
6.
Планируйте работу сегментами порядка 100 строк кода.
Некоторые из наиболее сложных методов будут приведены в псевдокоде или блок-схемах. Псевдокод из документа детальных требований может стать комментарием к коду. План тестирования разрабатывается для каждого модуля, как описано в следующей главе. Каждый из модулей, показанных на диаграмме, тестируется. Когда класс реализован, можно использовать инструменты обратного проектирования (например, Javadoc) для повторной генерации аспектов детального проектирования (рис. 7.3).
Рис. 7.3. Использование обратного проектирования в детальном проектировании
Обратное проектирование можно посоветовать после начальной реализации, поскольку исходный код часто становится более современным, чем нижний уровень детального проектирования. Это также может оказаться полезным для унаследованного кода, который плохо документирован. Вообще говоря, обратное проектирование нужно использовать только когда оно действительно необходимо. Автор наблюдал много ситуаций, когда обратное проектирование использовалось для скрытия недостатков проекта. Другими словами, инженеры преждевременно «ныряют» в написание кода, а затем подтверждают его разработкой обратного «проекта», полученного из кода. Читателей таких «проектов» редко удается обмануть.
Обратное проектирование также обсуждается в главах 6 и 10.
7.1.4. Реализация в USDP.
Вспомните, что USDP дает названия группам итераций. Большинство итераций слабо влияют на реализацию, однако итерации конструирования затрагивают основную часть реализации [64] (рис. 7.4).
USDP рассматривает реализацию как еще одну модель (наряду с моделями вариантов использования, тестовой моделью и т. д.). Хотя составные части проекта и должны отображаться как можно более точно на соответствующие части физической файловой системы, простое отображение может быть непрактично. Например, несколько классов можно отобразить на один файл, и артефакты реализации могут содержать ознакомительные файлы README, исходные файлы, объектные файлы и сжатые версии, такие как JAR-файлы. Модель реализации показывает, как организованы физические артефакты реализации и как на них отображаются элементы проекта (рис. 7.5).
Рис. 7.4. Реализация в USDP
Рис. 7.5. Компоненты модели реализации USDP (1)
Модель показывает, что классы Зона и ДругойКласс реализованы в одном файле — Area.java (ДругойКласс может быть внутренним по отношению к классу Зона).
Модель реализации состоит из вложенных подсистем. Они состоят из таких компонентов, как файлы и интерфейсы реализации (рис. 7.6). Исходный код для классов Зона и ДругойКласс (рис. 7.6) находится в файле Area.java. Интерфейс Зоны (набор функций типа public) в проекте соответствует реально существующей функциональности, реализованной в Area.java. Модель реализации также предоставляет нотацию для отображения связи между файлами, например «реализует», «описывает» или «сжимает», посредством кавычек (« »). (Организация может сама определять эти связи.) Например, на рис. 7.6 указано, что файл README, исходный код Зоны и его объектный код находятся в одной коллекции и что файл impl.jar — это их jar-версия (сжатый Java-архив).
Рис. 7.6. Компоненты модели реализации USDP (2)
Модель реализации описывает интерфейс уровней классов и языка, отвечающий за реализацию системного интерфейса. Пример для видеоигры Встреча с использованием одного интерфейсного класса показан на рис. 7.7. С помощью этой нотации можно показать несколько классов, отвечающих за части интерфейса подсистем. Их можно объединить в интерфейсный пакет, отвечающий за интерфейс, и т. д.
Рис. 7.7. Интерфейс подсистемы USDP
7.1.5. Языки программирования.
Существует огромное количество языков программирования, от специализированных (например, языки тестирования аппаратуры) до универсальных языков высокого уровня, таких как С++, Java и COBOL. Развитие Интернета затрагивает широкий спектр языков программирования. Принципы, описанные в этой книге, можно применять к любому из этих языков, однако объектно-ориентированные языки твердо придерживаются наиболее точного использования этих принципов. Предполагаемый язык реализации влияет на проект программного приложения. Например, при использовании такого языка, как С, может оказаться предпочтительнее разрабатывать проект более нисходящим, функциональным способом, несмотря на то что объектно-ориентированные проекты тоже могут в некоторой степени реализовываться на С. Некоторые языки, такие как Javascript и ранее Visual Basic, основываются на концепции объектов, что дает программистам возможность использовать инкапсуляцию и агрегирование, но без наследования.
7.2. Программирование и стиль.
Популярное представление о программировании как о процессе подготовки текста программы для компиляции на самом деле отражает лишь незначительную часть общей картины. Действительной целью написания программы является создание правильного кода, то есть такого кода, который полностью соответствует сформулированным для нее требованиям. Компиляторы же только проверяют корректность синтаксиса и генерируют объектный код. За правильность программы отвечает человек, и поэтому настоящий профессионал должен убедиться в правильности своей программы до того, как ее компилировать. Конечно, можно и сначала компилировать, а затем убеждаться в правильности, но это так же неэффективно, как сначала делать синтаксическую правку письма, а потом проверять, насколько правильно в нем изложены мысли. Заметим также, что программисты обычно упорно не хотят анализировать свою программу после того, как она успешно откомпилирована.
Завершает этот раздел рекомендуемая последовательность шагов для написания программ, разработанная на основе материалов этой главы. Здесь выделены типичные советы по стилю программирования. Существует много хороших книг, содержащих советы по программированию, однако в этой главе мы не ставим перед собой цели повторить их все.
ОДИН ИЗ СПОСОБОВ РЕАЛИЗАЦИИ КОДА (1) -.
1.
Спланируйте структуру вашего кода.
Восполните пробелы в детальном проектировании, если таковые имеются.
♦ Отметьте предусловия и постусловия;.
♦ отметьте затраченное время.
2.
Проинспектируйте проект и (или) структуру.
♦ Отметьте затраченное время, тип, серьезность и источник дефектов.
3.
Наберите код:.
♦ пока не компилируйте;.
♦ попробуйте использовать методы, перечисленные ниже;.
+ примените необходимые стандарты;.
+ кодируйте таким способом, который будет легче всего проверить (используйте формальные методы).
ОДИН ИЗ СПОСОБОВ РЕАЛИЗАЦИИ КОДА (2) -.
1.
Проинспектируйте код, но пока не компилируйте:.
♦ убедитесь, что ваш код выполняет требуемую работу (компилятор никогда не сделает это за вас: он просто проверит синтаксис!);.
♦ отметьте затраченное время, тип, серьезность и источник дефектов;.
♦ просмотрите контрольный список инспектирования (начало раздела 7.6) для часто используемых деталей конструирования методов и классов.
2.
Откомпилируйте код:.
♦ исправьте синтаксические ошибки;.
+ отметьте затраченное время, тип, серьезность и источник дефектов, число строк кода.
3.
Протестируйте код.
Примените методы модульного тестирования из главы 7.
7.2.1. Общие принципы надежной реализации.
Рекомендуется учитывать два общих принципа в программировании.
1.
Сначала постарайтесь повторно использовать имеющийся код.
2.
Принцип «осуществления намерений».
Если вы планируете использовать ваш код только в каких-то частных случаях, пишите его так, чтобы этот код было невозможно использовать никак иначе.
Поясним эти принципы подробнее.
1.
Мы сделали акцент на необходимости разрабатывать проекты наших собст венных программ так, чтобы разрешить повторное использование построенных нами компонентов. Аналогично, мы будем рассматривать возможность повторного использования существующего надежного кода, прежде чем начать писать новый. Сравните, например, использование компонента Java Swing GUI или существующего Java Beans. На самом деле поиск компонентов для повторного использования в Интернете обычно дает хорошие результаты.
2.
Если вы размышляете над тем, как ваш код будет использоваться другими частями программы, постарайтесь претворить это ваше намерение в жизнь. Автор называет это принципом «осуществления намерений». Часто из пользовательского интерфейса очевидно, где пользователю не разрешено вводить запрещенные данные. Однако мы относим это к внутренней обработке.
Принцип «осуществления намерений» аналогичен конструированию обочин и разделительных полос на дорогах для направления движения только по тем путям, по которым это было запланировано инженерами движения, и никаким другим. Такое принуждение делает дороги более безопасными и широко используется в каждой инженерной отрасли. Далее приведены примеры принципа «осуществления намерений» в разработке программ.
♦ Используйте спецификаторы, такие как final, const в С++ и abstract, для осуществления соответствующих намерений. Классы final не могут быть унаследованы; final-методы нельзя переписать в унаследованных классах:.
значения переменных final нельзя изменить. Если это приводит к ошибкам во время компиляции, это означает, что вы не до конца осознаете вашу собственную программу и никакого вреда еще не причинили. Особенно мы стараемся избежать ошибок во время запуска.
♦ Сделайте константы, переменные и классы настолько локальными, насколько это возможно в пределах разумного. Например, определяйте счетчики циклов в цикле: не давайте им более широкой области использования.
♦ Используйте образец проектирования одноэлементных множеств Singleton, если предполагается только один объект класса (см. главу 6).
♦ В общем случае делайте члены класса недоступными, если нет отдельного требования обеспечить прямой доступ к ним.
♦ Делайте атрибуты скрытыми (private). Доступ к ним реализуйте по необходимости через функции общего доступа. (В Java установка типа protected для атрибутов дает объектам или подкласса», доступ к членам их базового класса, что часто нежелательно.).
♦ Делайте методы скрытыми, если они должны использоваться только методами того же класса.
♦ Добавьте в документацию примеры. Программисты обычно колеблются на счет этого, однако примеры могут значительно помочь читателю. В нашем учебном проекте пример приведен в комментариях к методу adjustQualityO класса EncounterGame (ИграВстреча).
♦ Перечисляйте методы в алфавитном порядке. Не пытайтесь найти порядок их вызова. Некоторые программисты любят группировать методы private, protected и publ ic, а затем разбивают их на подгруппы статических и нестатических методов (в нашем примере эта практика не используется).
7.2.2. Указатели и ссылки.
Далее перечислены рекомендации, сформулированные в [47].
♦ Избегайте использования переменных-указателей в С++ — вместо этого используйте ссылки.
♦ Никогда не возвращайте указатель на новую ссылку на «кучу» в С++. Иначе вы будете ссылаться на неиспользуемую память, и к обязанностям программиста добавится восстановление пространства памяти.
♦ Производите сбор мусора (С++), используя оператор deleteO для всех ненужных объектов. Неудача при восстановлении памяти (утечка памяти) является основной проблемой в программах на С++. Некоторые организации, занимающиеся разработкой на С++, позволяют программистам выделять память только посредством специальных служебных функций, чтобы улучшить контроль над процессом выделения памяти. Другими словами, они стараются избегать new с(): программисты должны использовать такие утилиты, как F: :getNewCObject() для некоторого класса F.
♦ Существуют также коммерческие инструменты, пытающиеся определить потенциально возможные утечки памяти. Сбор мусора в Java производится автоматически, так что утечка памяти такого масштаба, как в С++, невозможна. Однако Java-программы могут накапливать такие ресурсы, как файлы и сокеты, и программист должен вести учет ресурсов, которые его программа использует во время выполнения.
7.2.3.
Функции.
♦ Избегайте запросов типов, например if ( myObject instanceOf MyClass ), за исключением случаев, когда это действительно необходимо. Вместо этого используйте виртуальные функции. (См., например, образец проектирования State в главе 5.).
♦ В С++ избегайте дружественных функций (friend) за исключением случаев, когда их достоинства явно преобладают над недостатками.
♦ Обратите особое внимание на перегрузку операций, поскольку когда вашу программу будут читать другие люди, они могут неправильно понять смысл ваших операций. В связи с этим Java не разрешает перегрузку.
7.2.4.
Исключения.
Перехватывайте только те исключения, для которых вам известен способ обработки. Метод, обрабатывающий исключение, выглядит так:.
ReturnType myMethod( ... ) { ...
try { ... // вызвать метод, генерирующий ExceptionX } catch( ExceptionX е ) {... // обработать его }.
Метод, не обрабатывающий исключение, выглядит так:.
ReturnType myMethod(...) throws ExceptionX{...}.
♦ Если существующий метод не может обработать исключение, должен быть обработчик во внешнем коде, который может это осуществить.
♦ Если вы можете обработать часть исключения, обработайте ее, а затем повторно сгенерируйте исключение для его обработки во внешнем коде.
♦ Делайте разумные предположения относительно возможности вызывающих операторов обработать ваше исключение или найдите альтернативный вариант разработки, поскольку необработанные исключения приводят к аварийным ситуациям в программе.
♦ Будьте аккуратны и не используйте исключения в ситуациях, которые могут быть предметом тестирования. Например, если предусловие говорит, что метод никогда не должен вызываться с нулевым параметром, это необходимо проверить тестированием. Генерация исключения в случае ненулевого параметра не должна быть стандартным способом обработки такой проблемы.
♦ «Если вам нужно выбрать между генерацией исключения и продолжением вычислений, продолжайте вычисления, если можете» [47]. Здесь имеется в виду, что программу имеет смысл закрыть только в тех случаях, когда вы тщательно продумали последствия.
Например, в нашем учебном проекте видеоигры программа продолжает работать с именем, присвоенным по умолчанию, если задан неразрешенный строковый параметр (например, null). Это лучше, чем закрывать игру только из-за ввода неразрешенного имени. Однако банковская операция с неразрешенной суммой не должна обрабатываться. В [47] указаны несколько причин, которые могут привести к неправильной работе конструкторов:.
♦ первой причиной ошибки конструктора может быть неправильный аргумент. В [47] рекомендуется установить объект в некоторое состояние по умолчанию и только затем передавать управление обработчику ошибок;.
♦ вторая причина — недоступность необходимых ресурсов. Эту ситуацию лучше всего обработать через генерацию исключения, поскольку мы ничего не можем с этим сделать;.
♦ программисты (особенно на С++) должны быть особенно внимательны при работе с памятью, которая будет оставлена после генерации исключения в ответ на ошибку конструктора.
7.2.5. Обработка ошибок.
Разработчики постоянно сталкиваются с проблемой обработки некорректных данных. Примером некорректных данных может быть номер счета, не соответствующий существующему банковскому счету. Несмотря на то что мы стремимся делать реализации как можно более простыми, окружающий нас мир не так уж прост. Довольно большая часть процесса программирования связана с обработкой ошибок. Здесь очень важен дисциплинированный подход: выберите подход, сформулируйте его и убедитесь, что все остальные члены команды понимают его и будут его придерживаться. Один из способов обработки ошибок показан ниже. Он будет объяснен далее.
ОДИН ИЗ СПОСОБОВ РЕАЛИЗАЦИИ ОБРАБОТКИ ОШИБОК -.
1.
Следуйте заранее определенному процессу разработки; выполняйте инспектирование.
2.
Вводя классы, проверяйте корректность инкапсулируемых значений параметров:.
♦ private-конструктор, factory-функции для создания объектов;.
♦ при компиляции можно отловить достаточно много ошибок.
3.
Если обработка ошибок определена в требованиях, реализуйте ее согласно требованиям. Используйте исключения при передаче управления во время обработки ошибок.
4.
Для программ, которые никогда не должны давать отказ, старайтесь предвидеть все возможные дефекты реализации (например, используйте значения по умолчанию).
5.
Альтернативой может быть следование согласованной политике проверки параметров. В основном полагайтесь на качественное проектирование и разработку.
Нашей основной целью является предотвращение появления ошибок, а не их исправление. Использование четко определенного процесса, инспектирование этапов и т. д. являются жизненно важной первой линией обороны. Некоторые образцы проектирования также могут помочь предотвратить ошибки. Например, если метод evaluate О принимает в качестве параметров только строки «саг», «truck» и «bus», имеет смысл отказаться от использования параметра String, поскольку оно может привести к появлению некорректных параметров. Правильнее будет определить класс, например SpecializedVehicle, со скрытым конструктором и функциями образца проектирования Factory:.
Special izedVehicle createCarO SpecializedVehicle createTruck() Special izedVehicle createBusO.
Тогда наш метод будет принимать параметры только этого типа. Другими словами, вместо конструктора:.
evaluate( String vehicleP ) ... //проблема с некорректными String-параметрами необходимо использовать:.
evaluate( Speciа 1 izedVehiclе vehicleP ) ... //значение параметра не может быть некорректным.
Когда возможные значения параметра ограничены, но не определены, все еще можно использовать отдельный класс. Например, если возраст человека будет представлен целым числом от 0 до 105, то метод.
getYearOfBirth( int ageP ).
может получить неправильный параметр. Более того, обработку ошибок придется повторить для всех методов, получающих в качестве параметра возраст. Ниже описаны несколько вариантов решения этой проблемы. Их недостаток заключается в быстром увеличении количества дополнительных классов и некоторой странности вызовов, например, когда мы используем вызов:.
... getYearOfBirth( getAge( n ) ) ...
вместо такого простого вызова:.
... getYearOfBirth( n ) ...
Второй линией защиты при работе с возможно некорректными данными является взаимодействие с источником данных до тех пор, пока они не будут приведены к разрешенному виду, которое должно предшествовать дальнейшей обработке данных. Это возможно в большинстве случаев программирования пользовательского интерфейса, где мы обычно можем проверить ввод неразрешенных данных. Если в текстовом поле могут быть введены только слова «саг», «truck» или «bus», очень легко не дать пользователю ввести некорректные данные. Обычным решением такой проблемы будет использование поля со списком. Однако даже здесь могут появиться ошибки. Например, пользователь может ввести день рождения как 01.01.80 и возраст 30 лет (в 2000 году). Согласованность этих данных легко проверить, однако именно разработчик обязан продумать все возможные проверки согласованности и граничных условий. Эту проверку может быть трудно сделать абсолютной, особенно когда в ней задействованы несколько полей. Когда предоставление данных внешнего объекта выполняется отдельной программой, вероятность ошибки еще больше.
Учитывая, что вероятные ошибки должны быть обработаны, необходимо выяснить, как отдельная программа или метод обрабатывает некорректные входные данные. (Например, метод, рассчитывающий баланс на банковском счете, если в предусловиях метода требуется, чтобы параметр счета был корректным.) Если все этапы процесса программирования были соответствующим образом выполнены, параметры метода всегда будут разрешенными, в какой бы момент метод ни был вызван, но должны ли мы программировать проверку значения параметра, если наш проект или реализация имеют дефекты? Это зависит от требований и обсуждается в конце этого раздела.
Например, предположим, что есть системное требование, согласно которому продолжение выполнения программы превыше всего, даже если выполнение ухудшилось или стало дефективным. В этом случае программист должен рассматривать все возможные входные данные, даже бессмысленные. Например, представьте себе программу, контролирующую работу сердца и снабжение пациента кислородом. Допустим, что мы кодируем метод process(int measurementType, ...), где параметр measurementType должен быть положительным числом. Будем считать, что зависание программы недопустимо, даже если внутренний метод получил некорректное целочисленное значение параметра в связи с дефектом разработки. Тогда этот метод проверит входные данные и либо, если это возможно, установит по умолчанию безопасные значения, либо переведет всю программу в другой режим работы, либо сгенерирует исключение, передавая вызывающей операции всю ответственность. Программа также должна сгенерировать сигнал тревоги.
Иногда получение неразрешенных данных можно обработать таким способом, который будет согласовываться с детальными требованиями. Это происходит, например, когда данные пересылаются через плохую линию передачи. Получающий метод может быть разработан таким образом, чтобы он ожидал некоторые конкретные данные, однако программа часто требует продолжать выполнение, даже если полученные данные некорректны. В этом случае данные следует проверить, а ошибки обработать в соответствии с требованиями (например, «Если сигнал не лежит в диапазоне от 3,6 до 10,9, сбросьте его значение и ожидайте следующего сигнала»).
Что можно сказать о методах, исключительное поведение которых не определено в требованиях? Во-первых, их предусловия должны быть подробно определены, чтобы условия, при которых они вызываются, были четко сформулированы. Но нужно ли даже в этом случае проверять значения параметров, чтобы убедиться в удовлетворении предусловий? Чтобы выяснить это, мы будем различать выполнение во время разработки и выполнение после ввода в действие.
Выполнение во время разработки позволяет тестировать и проверять код во многих частях программы, и мы, возможно, захотим добавить код, который будет проверять предусловия, как показано ниже.
/** предусловие: параметры положительны */.
int sum( int intlP. int2P ) {.
// код проверки при разработке: проверить параметры.
// теперь выполнить обработку ... }.
При рассмотрении выполнения поставленного заказчику продукта необходима другая точка зрения. Если метод был вызван с отрицательными параметрами, это показывает, что существует ошибка в самой программе. Мы бы хотели защитить себя от наших собственных ошибок, но «лечение» должно быть предпочтительнее «болезни».
/** предусловие: параметры положительны */ int sum( int intlP, int int2P ) {.
// код проверки развернутой программы: проверяем параметры // только если нам очевидно, что делать, если параметры отрицательны.
// теперь выполнить обработку ... }.
«Болезнь» в этом примере — случай, когда параметр, который должен быть положительным, является отрицательным. «Лечение» должно вернуть ответ, не согласующийся с состоянием программы.
Без четкой политики такая проверка для выпущенных продуктов является скользкой дорожкой, если проверка всех частей программы невозможна (например, следует ли проверять код проверки?). При фиксированном времени разработки есть опасность потратить его на добавление кода встроенной защиты вместо того, чтобы проверить правильность основы самой системы.
Разработчики теряют контроль над программой, если они используют значения по умолчанию, последствия которых непредсказуемы. Для этого существует несколько причин. Во-первых, неэтично распространять без предупреждения программу, обрабатывающую дефект разработки с частично некорректным продолжением (то есть таким продолжением, которое не предусмотрено требованиями). Дефект — это ошибка, но произвольное значение по умолчанию, не указанное четко в требованиях, покрывает эту ошибку. На практике часто предпочитают перезапустить остановленную программу вместо продолжения ее некорректной работы (представьте себе программу, планирующую маршруты самолетов). Во-вторых, недисциплинированная обработка ошибок скрывает дефект, и обнаружить такой дефект становится дороже, чем позволить зависание программы (с надеждой, что оно произойдет во время тестирования). В-третьих, мы должны быть последовательны в предположениях при проектировании программы. Код, в котором прослеживается принцип: «Я думаю, мы спроектировали это корректно, но если это не так, вот как мы собираемся выполнять программу», не решает проблему разработки и представляет собой плохое программирование.
7.2.6. Другие практические вопросы.
♦ Перед тем как изменять значение переменной, убедитесь, что при чтении значения не произошло никакой ошибки. В Java программисты стараются следовать этому правилу, поскольку в случае неправильного чтения возникает исключение и прочитанное значение считают бесполезным.
♦ Будьте особенно осторожны при применении множественного наследования в С++. (Java избавлен от этих проблем благодаря запрету на множественное наследование.) Например, когда оба породивших класса имеют переменные с одинаковым именем, порожденный класс имеет две версии переменной, что представляет собой трудную в решении ситуацию.
7.3. Стандарты программирования.
Использование стандартов способствует дисциплине программирования и повышает читаемость и переносимость программ. Мы предоставим вашему вниманию набор стандартов, который можно использовать в качестве примера. Некоторые из приведенных далее стандартов были взяты из [А]. Другие можно найти на сайте Sun Corporation, посвященном Java.
7.3.1. Соглашения об именах: примеры на языке Java.
Используйте соглашения об именах переменных. Разработчики обычно принимают близко к сердцу свои любимые соглашения, и часто бывает невозможно прийти к единому мнению. И все же соглашения необходимы. На координирование соглашений и способ их выработки следует выделить ограниченное время. Например, можно поручить члену команды разработать черновик соглашений и отправить его по электронной почте остальным членам команды для отзывов. Затем набор соглашений должен быть утвержден лицом, назначенным на разработку соглашений, и лидером команды. Должны быть также сформулированы основные идеи относительно того, в каких случаях возможно отступать от этих соглашений.
Ниже приводится пример соглашения об именовании.
♦ Именуйте объекты с помощью конкатенации слов, например длинаЦилиндра. Такие имена легко понять, и они экономят место. Исключения со знаком подчеркивания оставляются на выбор программиста. Например, вместо аАААААААвто было бы лучше написать а _ААА_АА_А_ А в т о.
♦ Начинайте имена классов с большой буквы. Так вы будете легко отличать их от имен переменных. Некоторые инструменты начинают имена объектов стандартными буквами или комбинациями букв, такими как С... для классов, например СЗаказчик. Это полезно, когда важность понимания типов имен выше возможной странности написания.
♦ Имена переменных начинайте с маленькой буквы. Исключением могут быть константы.
♦ Используйте в именах констант прописные буквы, например Я_ЕСТЬ_ КОНСТАНТА (используйте спецификаторы static final). Имя ЯЕСТЬКОНСТАНТА было бы трудно читать; ЯестьКОНСТАНТА можно спутать с классом; яестьконстанта никак не указывает на то, что это действительно константа.
♦ Начинайте (или заканчивайте) имена атрибутов классов подчеркиванием, например timeOfDay, чтобы отличать их от других переменных, поскольку они являются глобальными по отношению ко всему объекту. Это соглашение используется [33], но и критикуется [4].
Существует соглашение, используемое в нашем примере, по которому суффикс I является признаком атрибута, например _timeOfDayI. Каждый атрибут является глобальным для каждого объекта класса, и когда такое имя встречается в блоке кода, удобно знать, что это атрибут.
♦ Договоритесь о нотации для выделения статических переменных класса. В нашем примере используется суффикс S, например numCarsEverBuiltsS. Вспомните, что статическая переменная является глобальной для класса, и поэтому нам полезно знать, что переменная, появившаяся в части кода, является одной из них.
♦ Для методов доступа используйте имена, начинающиеся с get, set и is, например, getNameO, setNameO, isBoxO (возвращает значение типа Boolean).
В качестве альтернативы можно использовать пагпеО и name( String ) для имени атрибута (например, в CORBA [84]).
♦ Добавьте стандартные дополнительные методы get и set для коллекций, например insertlntoNameO, removeFromNameO, newNameO.
♦ Договоритесь о соглашении для параметров. Можно использовать префикс а, например sum( int aNumlP, int aNum2P ), или суффикс P, например sum( int NumlP, int Num2P ).
Документируя методы, описывайте их следующие характеристики:.
♦ предусловия и постусловия (см. обсуждение корректности программы ниже):.
♦ что делает метод;.
♦ почему он делает то, что делает;.
♦ какие параметры следует ему передать (для Javadoc используйте тег @param);.
♦ исключения, генерируемые им (для Javadoc используйте тег @exception);.
♦ причина выбора видимости (private и т. д.);.
♦ способы изменения атрибутов;.
♦ известные ошибки;.
♦ описание теста с указанием, тестировался ли метод, и местоположение тестового сценария;.
+ история изменений, если вы не используете систему управления конфигурациями;.
♦ пример работы метода;.
♦ специальная документация о потоковых и синхронизированных методах. Используйте согласованный стандарт для разделителей. Поскольку одиночные пустые строки полезны для разделения частей кода внутри метода, согласованным стандартом могут быть две пустых строки между методами.
Договоритесь о следующих стандартах внутри методов:.
♦ выполняйте только одну операцию на одной строке;.
♦ старайтесь, чтобы ваши методы помещались на один экран;.
♦ используйте скобки внутри выражений, чтобы разъяснить их значение, даже если синтаксис языка этого не требует. Это применение принципа «если знаешь это, покажи».
Присваивая имена классам, используйте слова в единственном числе, например Заказчик, за исключением очевидных случаев, когда вы хотите собрать объекты (в этом случае Заказчики подошло бы больше). Чтобы избежать размножения классов, иногда желательно, чтобы класс собирал все свои элементы. Это можно сделать с помощью статического элемента данных класса.
7.3.2.
Документирование атрибутов.
(См. [4].).
♦ Укажите все подходящие инварианты (вычислимые факты относительно атрибутов, например «36
< Jength * width
< 193".).
Для каждого атрибута:.
♦ сформулируйте его цель;.
♦ предоставьте все подходящие инварианты (вычислимые факты относительно атрибутов, например «1
< _аде
< 130" или »36
< Jength * width
< 193".).
Подробное описание инвариантов вы найдете в разделе 7.4.
7.3.3.
Константы.
Перед тем как отнести переменную к типу final (сделать ее константой), убедитесь, что она должна быть таковой. Эмблер [4] предлагает использовать не саму константу, а метод, представляющий ее. Например, вместо.
protected static final MAX_CHARS_IN_NAME = 20:.
следует использовать метод:.
protected final static int getMaxCharsInName().
{ return 20: }.
Этот способ выигрывает благодаря своей наглядности (например, при использовании метода getMaxCharsInNameO вместо непонятного числа 20) и гибкости.
7.3.4. Инициализация атрибутов.
Атрибуты всегда должны быть инициализированы, чтобы программист мог контролировать свою программу. Обычно мы представляем инициализацию следующим образом:.
private float balancel = 0: //соглашение: атрибут xxxl.
Однако атрибут, нуждающийся в инициализации, может быть объектом другого класса:.
private Customer customerl;.
Эта инициализация часто выполняется с помощью конструктора:.
private Customer customerl = new Customer( «Edward». «Jones» ):.
Проблема этой техники заключается, согласно [4], в ее эксплуатационной надежности. Когда в Customer будут добавляться новые атрибуты, все эти инициализации, возможно, придется обновлять. Другими словами, она нарушает объектно-ориентированную цель инкапсуляции всех связанных с Customer аспектов класса Customer. Другая проблема заключается в том, что такая конструкция может потребовать ненужного постоянного хранения.
Согласованное решение заключается в использовании инициализации только в тех случаях, когда к значению сначала получается доступ («ленивая инициализация»), В общем случае мы добавляем в MyClass статическую функцию, которую назовем getDefaultMyClassO. Теперь можно объявлять атрибуты в других классах типа MyClass без инициализации и менять их значения только при первом доступе. Пример представлен в листинге 7.1, где MyClass — это класс Customer.
Листинг 7.1. Одно из решений инициализации объекта.
Используйте инициализацию при первом обращении к значению. Добавьте в MyClass статическую функцию getDefaultMyClassO. Атрибуты объявляются без инициализации, и им присваиваются значения при первом обращении.
В классе Customer:
Public static Customer getDefaultCustomer() // Далее по умолчанию выбраны разумные значения { return new Customer.
( «John», «Doe». 0. 1000. -2000 ):.
}.
private float balancel = -10: private Customer customerl:.
public Account (...)...
public float getBalanceO.
{ return balancel; }.
public Customer getCustomer() //Обращение к customerl.
{.
if( customerl == null ) //Обращения не было.
customerl = Customer.getDefaultCustomer(): //Исходное значение return customerl; //Текущее значение.
}.
public getDefaultAccount() //Для пользователей Account.
{ return new Account( -10, 3. «regular» ); }.
7.4. Доказуемо корректные программы.
Говорят, что программма доказуемо корректна, если имеется математическое доказательство того, что она удовлетворяет сформулированным применительно к ней требованиям. Доказательство основывается на этих требованиях и на коде программы. Оно никак не связано ни с результатами компиляции, ни с результатами тестирования программы. Это идеальный случай.
Хорошое математическое доказательство — это правильно построенное рассуждение об истинности доказываемого утверждения, убедительное как для самого автора доказательства, так и для всех прочих. Ключевым моментом в проведении доказательств является использование языка математики, однако чтобы использовать этот язык, прежде всего нужно, чтобы само доказываемое утверждение было сформулировано математически.
Во-первых, требование само по себе должно быть сформулировано точно. В главе 4 мы описали Z-спецификации для формального изложения требований. Здесь мы будем использовать более простую форму введенных в главе 4 требований, — предусловия и постусловия. Предусловия определяют все предположения, сделанные при вызове функции. Постусловия определяют необходимое состояние по завершении выполнения функции. Например, рассмотрим следующее требование к функции f():.
Предусловие: g — массив целых чисел длины п ( g «дано»). Постусловие: г = max{ g[0], g[l], ..., g[n-1] } ( г — «результат»).
В неформальных терминах, это требование вызывает функцию f() для определения максимального значения массива g. Это легко запрограммировать, однако мы сделаем это так, чтобы нам было легко доказать корректность кода.
Одним из способов познакомиться с программированием фрагментов кода, корректность которых можно доказать, является применение (псевдокода):.
while (Переменным присвоены неразрешенные значения) Выполнить соответствующие действия.
/*0сталось только доказать, что этот цикл конечный, чтобы убедиться, что переменным будут присвоены необходимые значения*/.
Для доказательства того, что переменной присваивается необходимое значение, программисту достаточно убедиться в конечности цикла. Если программист может доказать, что цикл конечен, утверждение «Переменной не присваивается необходимое значение» не будет справедливым (двойное отрицание), и необходимое состояние будет достигнуто.
На первый взгляд, доказуемо корректные программы кажутся новым направлением в программировании. Это не совсем так. Интуитивный процесс, обычно используемый программистом, по сути ничем не отличается от формального доказательства: формальность просто помогает контролировать процесс с гораздо более высокой точностью. Например, большинство из нас будут программировать функцию тах() посредством создания цикла и накопления максимального значения «из рассмотренных». Другими словами, во время компиляции мы будем хранить следующее утверждение как истинное:.
.
Как упоминалось в предыдущих главах, подобное этому утверждение, которое должно поддерживаться истинным, называется инвариантом. Мы также говорим, что утверждение сохраняется инвариантным (неизменным). Хотя значения отдельных переменных в нем (/ и г) изменяются, утверждение в целом остается истинным. По ходу вычисления тах() и роста j значения j и г согласовываются, благодаря чему утверждение / остается истинным. Обратите внимание, что тах() — это имя функции, которую мы разрабатываем, в то время как термин «максимум», использовавшийся в утверждении инварианта, является математическим понятием, с которым читатель должен быть знаком.
Инвариант используется в качестве фундамента, на котором строится вся программа. Аналогичную роль играет инфраструктура в структурном программировании: «дано», на котором мы можем основываться, чтобы удовлетворить требованиям. Это проиллюстрировано на рис. 7.8, где показана разработанная инфраструктура, предоставляющая «инвариант», на основе которого можно строить горное шоссе.
Программа с доказуемой корректностью для тах() представлена в листинге 7.2.
Листинг 7.2. Программа с доказуемой корректностью, определяющая максимум в массиве.
// Определить I: 0 =
< j.
// После следующих двух команд I будет истинно: int r=g[0]: int j=0;.
// В этом блоке для I поддерживается значение «истина»: while( j
< n-1 ) { if( g[j
l] > г ) r=g[j+l];
j:.
}
Рис. 7.8. Инвариант как инфраструктура
Обратите внимание, что мы используем цикл while вместо for, поскольку циклы while проще в использовании при доказательстве корректности. Существует ограничение по и, поскольку реальные компьютеры не могут гарантировать эффективность программы для любого п без ограничения. Доказательство того, что программа корректна, сформулировано в листинге 7.3.
Листинг 7.3. Программа с доказуемой корректностью, определяющая максимум в массиве: доказательство корректности.
/* Предположив, что цикл конечен (будет доказано ниже), мы знаем на этот момент, что j
< п-1 больше не истинно. Помимо этого, мы сохранили I как инвариант (истина). Объединяя эти утверждения, получаем: j
< п-1 ложь. AND.
j.
г = max {g[0], g[l]..... g[j]} (из первого утверждения).
Следовательно, j = n-1 AND г = max {g[0], g[l]..... g[n-l]}.
Что и требовалось доказать.
Остается только доказать, что цикл while конечен. Поскольку величина I сохранялась инвариантной, величина n-j всегда положительна. Более того, n-j уменьшается на 1 с каждой итерацией. Единственный случай, когда такая ситуация будет иметь место, - когда цикл конечен.*/.
Этот простой пример иллюстрирует мнение ученых и программистов, полагающих, что технология программирования со временем будет все активнее использовать формальные методы. Они утверждают, что формальные методы постепенно делают наш процесс реализации программы более профессиональным и точным. В книге [25] показано, что даже простейшие программы могут обладать богатством потенциальных изменений, более или менее эффективных, удовлетворяющих требованиям или нет. Таким образом, не удивительно, что в коде даже лучших программистов скрываются ошибки. Следовательно, нам нужны все возможные инструменты и техники для производства надежных программ.
Приведенный ниже пример взят из [25]. Более простой для понимания является книга [39]. Задача состоит в нахождении наибольшего общего делителя (НОД) двух положительных целых чисел: наибольшего натурального числа, на которое делятся оба заданных числа. Например, НОД для 18 и 12 равен 6.
Мы будем различать «НОД» — речевой термин (см. предыдущий раздел), и gcd() — код, который мы будем создавать для реализации НОД. Программа (листинг 7.4) использует тот математический факт, что НОД двух положительных целых чисел awb тот же, что и у пары а-b и Ъ при условии, что разность а-b положительна. Например, НОД для 30 и 18 (равный 6) будет тем же, что и НОД для (30-18) и 18. В последней паре чисел, 12 и 18, НОД также равен 6.
Требования для метода — gcd(int хР, int уР) — можно сформулировать в терминах предусловий и постусловий.
♦ Предусловия:.
хР нуР — натуральные числа.
♦ Постусловия (повторить определение НОД):.
1) gcd(xP, уР) — положительное целое;.
2) gcd(xP, уР) кратно хР и уР;.
3) если z натуральное число, кратное хР и уР, тогда z Код для функции gcd(int хР, int уР) показан в листингах 7.4 и 7.5. Доказательство окончания программы — это одно из нескольких возможных объяснений причин, по которым цикл будет иметь конечное число итераций.
Листинг 7.4. Программа отыскания НОД с доказуемой корректностью кода.
// Пусть I - следующее утверждение: gcd(xP. уР) = gcd(x, у).
// Инициализировать переменные, чтобы I было истинно: int х = хР; int у = уР:.
/* Следующий цикл сохраняет I и в результате дает х==у. Когда цикл закончится (доказательство конечности цикла см. в листинге 7.5): НОД(хР. уР) = НОДСх. у) согласно инварианту.
= НОД (х. х) поскольку х==у.
= х.
Вернуть х как результат, и наша работа на этом закончена.*/.
Листинг 7.5. Программа отыскания НОД с доказуемой корректностью кода: доказательство конечности цикла.
whi1е( !( х==у ) ) { if( х>
у ).
х = х - у; // I сохраняется (свойство НОД) else // !(х==у) AND !(х>у). значит, х
< у.
у = у - х; //I сохраняется (то же самое свойство НОД).
}.
/* Величина х + у уменьшается по крайней мере на 1 на каждой итерации, но остается положительной. Значит, число итераций должно быть конечно.*/ System.out.println(«НОД чисел» + xP + «и» + yP «равен» + x);.
Подведем итоги обсуждения программ с доказуемой корректностью кода. В общем случае создание программ с доказуемой корректностью подразумевает следующие шаги.
1.
Определите отношение I среди переменных, которое легко установить, и отношение г так, что I AND г является постусловием.
2.
Напишите код, в котором I будет истинно.
3.
Создайте код со следующей структурой: while(!г).
{ Выполнение операций, которые сохраняют I инвариантом и в конце концов приведут к окончанию цикла.
}.
4.
Докажите, что цикл whi 1 е конечен.
7.5. Инструменты и среды программирования.
Часто говорят, что человек сам создает инструменты, и это также относится к разработчикам программ. Существует постоянно растущее число инструментов, помогающих в программировании.
Интерактивные среды разработки широко используются, помогая программистам производить больше кода за меньшие сроки. Сюда относятся возможность перетаскивания для конструирования компонентов графического пользовательского интерфейса, графическое представление каталогов, отладчики, мастера и т. д. В Java Beans используется подход, стандартизирующий исходный текст таким образом, что среда разработки Java Beans может обрабатывать исходные данные. Преимущество этого подхода в том, что разработчики не привязаны к одной среде разработки. Значительно упростилось создание СОМ-объектов.
Программы профилирования, такие как Jprobe, могут использоваться для накопления статистических данных, например:.
♦ совокупное время центрального процессора и истекшее время;.
♦ время, затраченное на каждый метод;.
♦ совокупное количество созданных объектов;.
♦ количество вызовов;.
♦ среднее время работы каждого метода.
Программы должны иметь исходный код в той или иной форме. Многие организации первого уровня СММ, напротив, не производят ничего, кроме исходного кода. После того как производители инструментов осознали, что их клиенты будут сами производить код, возможно документированный, они стали разрабатывать средства обратного проектирования, получающие на вход исходный код и формирующие документацию. Примером инструмента обратного проектирования исходного кода является Javadoc (см. раздел 6.7.3). Обратное проектирование обсуждается более подробно в главе 9.
Несколько объектно-ориентированных инструментов (таких, как Rational Rose, Together/J/C++) генерируют исходный код из объектных моделей. Эти инструменты прямого проектирования не могут сгенерировать нечто большее, чем каркас кода, по которому должна работать программа, являющаяся финальной реализацией. Те же инструменты также выполняют обратное проектирование, механически производя объектные модели из исходного кода (отсюда появился термин «проектирование туда-обратно»).
Эволюция инструментальных средств в других областях проектирования (например, CAD/CAM) позволяет надеяться, что инструменты программирования будут продолжать улучшаться, будут так же активно использоваться программистами и приведут к снижению количества монотонных и механических заданий.
7.6. Качество в реализации.
В этом разделе обсуждаются метрики и методы достижения качественных реализаций. Ниже представлены контрольные списки инспектирования кода.
ОДИН ИЗ СПОСОБОВ ИНСПЕКТИРОВАНИЯ КОДА (1): КЛАССЫ В ЦЕЛОМ -.
1.
Подходит ли имя для класса:.
♦ согласуется с требованиями или проектом?.
♦ достаточно конкретно (обобщенно)?.
2.
Может ли класс быть абстрактным (чтобы использоваться только как основа)?.
3.
Описывает ли заголовок класса его цель?.
4.
Ссылается ли заголовок класса на требования элемента проекта, которому этот класс соответствует?.
5.
Указан ли там пакет, к которому этот класс принадлежит?.
6.
Является ли класс настолько скрытым, насколько это возможно?.
7.
Не сделать ли его final (Java)?.
8.
Использовались ли стандарты документации? Например, Javadoc.
ОДИН ИЗ СПОСОБОВ ИНСПЕКТИРОВАНИЯ КОДА (2): АТРИБУТЫ -.
1.
Является ли атрибут действительно необходимым?.
2.
Можно ли сделать его статическим?.
♦ Нужна ли в каждом классе своя переменная?.
3.
Не сделать ли его final?.
♦ На самом ли деле изменяется его значение?.
♦ Будет ли достаточно одного метода get?.
4.
Правильно ли были применены соглашения о присваивании имен?.
5.
Является ли атрибут настолько скрытым, насколько это возможно?.
6.
Являются ли атрибуты настолько независимыми, насколько это возможно?.
7.
Существует ли ясная стратегия инициализации.
♦ в момент объявления?.
♦ с конструктором (конструкторами)?.
♦ используя Statiс{}?.
♦ все вышеупомянутое? Как?.
ОДИН ИЗ СПОСОБОВ ИНСПЕКТИРОВАНИЯ КОДА (3): КОНСТРУКТОРЫ -.
1.
Является ли конструктор необходимым?.
♦ Не лучше ли использовать factory-метод?.
♦ Большая гибкость.
♦ Дополнительная функциональность.
2.
Нет ли уже существующих подобных конструкторов? (Только для Java.).
3.
Все ли атрибуты инициализирует конструктор?.
4.
Является ли конструктор настолько скрытым, насколько это возможно?.
5.
Исполняет ли конструктор при необходимости унаследованные конструкторы?.
ОДИН ИЗ СПОСОБОВ ИНСПЕКТИРОВАНИЯ КОДА (4): ЗАГОЛОВКИ МЕТОДОВ -.
1.
Присвоено ли методу подходящее имя?.
♦ Согласуется ли имя метода с требованиями или проектом?.
2.
Является ли метод настолько скрытым, насколько это возможно?.
3.
Может ли он быть static?.
4.
Может ли он быть final?.
5.
Описана ли в заголовке цель метода?.
6.
Ссылается ли заголовок на раздел в требованиях и (или) проекте, которому он соответствует?.
7.
Формулирует ли он все необходимые варианты (раздел 7.4)?.
8.
Сформулированы ли в методе все предусловия?.
9.
Сформулированы ли в методе все постусловия?.
10.
Использованы ли в методе стандарты документации?.
11.
Ограничены ли типы параметров (раздел 7.2.5)?.
ОДИН ИЗ СПОСОБОВ ИНСПЕКТИРОВАНИЯ КОДА (5): МЕТОДЫ -.
1.
Согласуется ли алгоритм метода с псевдокодом или блок-схемой в детальном проектировании?.
2.
Выполняет ли код сформулированные предусловия и только их?.
3.
Выполняет ли код абсолютно все постусловия?.
4.
Выполняет ли код требуемый инвариант?.
5.
Каждый ли цикл конечен?.
6.
Выполнены ли необходимые стандарты нотации?.
7.
Каждая ли строка была внимательно проверена?.
8.
Все ли фигурные скобки сбалансированы?.
9.
Учтены ли неразрешенные значения параметров (раздел 7.2.5)?.
10.
Возвращает ли код корректный тип?.
11.
Добавлены ли к коду подробные комментарии?.
7.6.1. Стандартные метрики для исходного кода.
7.6.1.1.
Подсчет строк.
Число строк кода хоть и не является идеальным показателем, но представляет собой одну из важных программных метрик. Необходимо выбрать стандартный способ подсчета строк. Например, нужно решить, как учитывать при подсчете:.
♦ утверждения, занимающие несколько строк (1 или и?); + комментарии (О?);.
♦ строки, состоящие из while, for, do и т. д. (1?).
Выбор может зависеть от применяемого стандарта и от используемого инструментария, осуществляющего автоматический подсчет. Следование выбранному способу подсчета значительнее важнее, чем сам выбор этого способа.
7.6.1.2.
Метрики IEEE.
Ниже приведен небольшой пример метрик из [59], показывающий варианты оценок исходного кода. Эти оценки не зависят от тестирования (оно будет рассмотрено в следующей главе).
IEEE-метрика 14. Программные научные метрики.
Используемые измеряемые величины:.
Пусть п
— это число отдельных операторов (+, * и т. д.) в программе. Например, в программе {х = х + у; z = х * w} щ = 2.
Пусть п
= число отдельных операндов в программе. Например, в программе {х = х + у; z = х * w} щ = 4, поскольку операторы сложения и умножения имеют по два операнда каждый.
Пусть JV, = число всех операторов в программе. Пусть N
= число всех операндов в программе.
Пример оценки:.
предполагаемая длина программы: n,(log п
) + w
(log п
);.
сложность программы: (n,JV
) / (2п
).
Дополнительную информацию по программным научным метрикам см. в [53). IEEE-метрика 16. Цикломатическая сложность.
Эта метрика определяет структурную сложность блока кода по вычислительным циклам, которые являются важными факторами сложности. Ее можно использовать для определения модулей, сложность которых необходимо уменьшить.
Примером политики, основанной на этой метрике, является требование специально проверить все модули с цикломатической сложностью выше средне! i на 20 %.
Используемые измеряемые величины:.
N — число узлов (операторов программы);.
Е — число ребер (ребро соединяет узел т с узлом п, если оператор п следует сразу за оператором гп).
Один из способов вычисления цикломатической сложности заключается в вычислении Е - N + 1. Другой способ получения этой оценки заключается в вычислении количества замкнутых областей, образованных ребрами, которые нельзя разбить на меньшие области. Это очевидно из рис. 7.9.
Рис. 7.9. Цикломатическая сложность
7.6.2. Индивидуальные метрики для исходного кода.
Цикломатическая сложность выражает число циклов в программе, но не проводит различия между вложенными и невложенными циклами. В общем случае циклы внутри циклов подвержены ошибкам чаще, чем последовательности независимых циклов.
Нетрудно создать метрики, измеряющие важные величины. Например, чтобы вычислить сложность цикла, можно предположить, что каждый вложенный цикл увеличивает сложность на порядок (грубо говоря, в 10 раз). Приведенный ниже пример можно расценивать как описание коэффициента сложности вложенных циклов.
Цикл 1 — сложность 1.
Цикл 2 — сложность 1.
Цикл 2.1 — сложность 10 (цикл внутри цикла 2).
Цикл 2.2 — сложность 10.
Цикл 2.2.1 — сложность 100.
Цикл 3 — сложность 1.
Цикл 4 — сложность 1.
Цикл 5 — сложность 1.
Цикл 5.1 — сложность 10.
Используя эту метрику, получаем сложность 135. Эту метрику можно нормировать, разделив полученную величину на число строк кода и сравнив результат со средним значением по фирме.
7.6.3. Инспектирование кода.
Вспомните, что серьезность ошибок является важной составляющей инспектирования данных, поскольку именно она позволяет расставлять приоритеты дефектов и тем самым рационально распределять работу (табл. 7.1). Нет смысла тратить время инспектирования на тривиальные дефекты. Неплохим способом обработки тривиальных дефектов является ситуация, когда инспекторы передают распечатку исходного кода с замечаниями автору.
Таблица 7.1. Классификация серьезности дефектов с использованием метода отбраковки
Серьезность дефекта
Описание дефекта
Важный
Требование не выполнено
Средний
Дефект нельзя отнести ни к важным, ни к тривиальным
Тривиальный
Дефект не влияет на работу программы и сопровождение
Кроме того, дефекты можно описать, основываясь на их типе; одна из таких классификаций, приведенная в главе 6, повторена ниже.
♦ Проблема логики (забытые случаи или шаги; повторенная логика; пропуск граничных условий; неважные функции; некорректное прерывание; недостаток текста условия; проверка неправильных переменных; некорректность итерационных циклов и т. д.).
+ Вычисления (некорректное или недостаточное уравнение; потеря точности; ошибка в знаке).
+ Интерфейс и синхронизация (некорректная обработка прерываний; некорректная синхронизация ввода-вывода, несоответствие модулей или подпрограмм).
♦ Обработка данных (некорректная инициализация данных; некорректное получение или хранение данных; некорректный масштаб или единицы измерения данных).
♦ Границы данных.
♦ Данные (некорректны или отсутствуют данные с датчиков, данные оператора, данные таблиц, внешние данные, выходные данные, входные данные).
♦ Документация (неоднозначное описание и т. д.).
♦ Качество документа (несоответствие применяемым стандартам и т. д.).
♦ Расширение (изменение в требованиях и т. д.).
+ Ошибки, возникшие в результате предыдущих исправлений.
♦ Возможность взаимодействия (несовместимость с другими программами или компонентами).
♦ Соответствие стандартам.
♦ Другие (не перечисленные выше).
♦ В качестве примера мы проведем инспектирование кода метода adjustQual ity() класса ПерсонажВстречи в нашем учебном проекте видеоигры (рис. 7.10 и рис. 7.11).
Рис. 7.10. Исходный код adjustQualityO и обнаруженные дефекты (1)
Рис. 7.11. Исходный код adjustQuality() и обнаруженные дефекты (2)
7.6.4. Индивидуальная программная документация (PSD).
Каждый разработчик обязан хранить документацию своей текущей работы. Эта документация называется индивидуальной программной документацией (PSD — Personal Software Documentation). Она позволяет в любой момент получить информацию о статусе разработки, и часть ее позднее будет добавлена в архив проекта. Эта документация может содержать исходный код и данные об индивидуальной регистрации дефектов, состоящие из следующих пунктов.
♦ Типы дефектов.
♦ Индивидуальный этап, на котором дефект возник.
♦ Индивидуальный этап, на котором дефект был исправлен. Индивидуальные этапы — это:.
1) дополнительное детальное проектирование (если применимо);.
2) код (записывайте возникшие или обнаруженные дефекты в исходном коде до компилирования);.
3) отчет о компиляции (записывайте обнаруженные и исправленные дефекты после попытки компиляции);.
4) модульное тестирование. Модульное тестирование проводится группой контроля качества и не является частью этой документации.
♦ Учет времени. Время, затраченное на дополнительное детальное проектирование, кодирование, компиляцию и тестирование.
♦ Записная книга проектирования. Содержит статус дополнительного детального проектирования (если применяется) и кода, а также сведения о инцидентах и требующих внимания вопросах разработки.
В индивидуальном процессе разработки, созданном в Институте технологий разработки программного обеспечения (SEI), ведется учет времени и информации о дефектах. Команда или лидер проекта определяет, как использовать и архивировать индивидуальную документацию членов команды. Слово «индивидуальная» не должно восприниматься как определение собственности разработчика: работа, оплачиваемая организацией, обычно является собственностью этой организации. Работу членов команды обычно оценивают по конечным результатам их личного участия в разработке и по тому, насколько хорошо их процесс работы был документирован. Вообще говоря, их работу не оценивают на каждой стадии процесса.
7.7. Итоги процесса реализации.
Целью реализации является корректное программирование детального проекта при поддержке эксплуатационной надежности продукта. Использование стандартов программирования помогает в этом процессе. Очень важно иметь организованный подход при определении цели каждого метода, и этого можно достичь путем формулирования предусловий и постусловий. Чтобы корректность кода, особенно потенциально сложного кода, можно было доказать, необходимо писать текст программы организованным способом, желательно допускающим формальное доказательство. Это облегчает доказательство удовлетворения требованиям. Подведем итоги.
♦ Всегда помните о цели программирования:.
♦ корректность;.
♦ четкость.
♦ Используйте стандарты программирования.
♦ Определите предусловия и постусловия.
♦ Докажите корректность отдельных частей без компилятора.
♦ Реализуйте программу за заранее заданное время.
♦ Поддерживайте качество и профессионализм.
Упражнения.
Ответы и подсказки для упражнений, помеченных символами «о» или «п», приводятся в конце этой главы.
Вопросы для проверки.
П7.1". Вы собираетесь закодировать метод. Какие два основных ресурса предоставляют вам информацию о том, что ваш метод должен делать?.
П7.2". Сформулируйте 3-5 шагов, которые обычно происходят после того, как вы закодировали метод.
Для ответов на вопросы 7.3-7.6 обратитесь к разделам 7.2.1-7.2.4.
П7.3. Назовите 3-4 общих принципа выполнения качественной реализации.
III А. Дайте 2-3 общих принципа работы с указателями. П7.5. Дайте 2-3 общих принципа работы с функциями. П7.6. Дайте 4-6 общих принципов работы с исключениями. П7.7°. Предположим, вы хотите, чтобы ваша часть программы осуществляла состояние S с использованием переменных типа float х, у, z, например х = шах( у, z ).
1.
Покажите общую структуру цикла while, выполняющего эту задачу, с доказуемой корректностью.
2.
Что необходимо для полноты доказательства?.
3.
Каким образом структура while предоставляет возможность обработки эффективности процедуры?.
4.
Фрагменты кода не существуют в вакууме. Цикл while обычно находится среди другого кода. Опишите в общих чертах способ связи цикла с окружением, который часто используется для установления корректности.
П7.8°. Подведите итог общему строгому способу определения того, что метод должен делать.
П7.9. Назовите 5 метрик исходного кода (ответ ищите в разделе 7.6.1).
Общие упражнения.
07.1. Приведите 2-4 достоинства и 1-2 недостатка принудительного использования стандартов кодирования.
Упражнения в команде.
К7.1. («Реализация».).
Реализуйте ключевые части вашей программы в виде прототипа.
Ответы.
П7.1. Метод либо соответствует напрямую D-требованию в SRS, либо необходим в проекте.
П7.2. Вы должны измерять время, которое ушло на выполнение работы, как в PSP . Вы должны внимательно инспектировать и тестировать себя. Метод сам по себе либо вместе с другими методами необходимо регулярно инспектировать. Далее метод инспектируется при модульном тестировании и только после этого, если он проходит тестирование успешно, он интегрируется в программу.
П7.7.
1).
while (not S).
Выполнить вычисления.
2) Докажите конечность цикла.
3) Вероятно, вы можете сравнить эффективность таких алгоритмов, сравнивая число итераций и количество вычислений, выполняемых в теле цикла whi 1 е до окончания этого цикла.
4) Часто существует утверждение (в отношении переменных), остающееся неизменным (инвариантным).
П7.8. Определите предусловия и постусловия.
Пример 1. Обновление Плана контроля качества (SQAP).
(Добавление в раздел: 5.2 примера 2 в главе 1.).
5.2.1.
Соглашения по программированию (этот раздел был добавлен).
Будут использоваться приведенные ниже соглашения.
Части имен элементов программы, не являющихся константами, выделяются с помощью прописных букв, например этоЕстьИня.
Имена классов и интерфейсов начинаются с прописной буквы, например Account.
Имена атрибутов в классах начинаются со строчной буквы и заканчиваются буквой I, например balancel.
Статические переменные (класса) начинаются со строчной буквы и заканчиваются буквой 5, например interestRateS.
Переменные, определенные в методе и являющиеся для метода глобальными, начинаются со строчной буквы и заканчиваются буквой М, например interestM.
Параметры начинаются со строчной буквы и заканчиваются буквой Р, например principal Р.
Переменные final будут записываться прописными буквами и знаками подчеркивания, например BANK_NAME.
5.2.2.
Нотация, показывающая местоположение файлов.
Для описания реализации мы будем исйользовать UML [15].
5.2.3.
Индивидуальная программная документация (PSD).
Каждый разработчик будет поддерживать документацию своей работы, которая будет называться Индивидуальной программной документацией (PSD). Это позволит разработчику в любой момент сообщать состояние свой работы, и эта документация становится частью архива проекта. Команда или руководитель проекта будет определять способ организации PSD членов команды. Обычно набор личных документов соответствует задаче, выделенной конкретному разработчику, и состоит из набора классов.
[Примечание для студентов. Пример PSD представлен в разделе «Пример 3» ниже.].
Пример 2. Обновление Плана контроля качества (SQAP). Приложение: Модель реализации
Пример.
Часть модели реализации видеоигры Встреча приведена на рис. 7.12.
Рис. 7.12. Модели проектирования и реализации игры Встреча
Пример 3. Индивидуальная программная документация (PSD), часть 1.
[Примечание для студентов. Этот документ поддерживается каждым разработчиком и описывает текущее состояние работы. Он должен быть достаточно полным, чтобы позволять разработчику давать отчет о статусе выполения на собраниях и при необходимости позволять другим разработчикам принять работу за прие.млемое время.
Если разработчик придерживается плана PSP или TSP, формат и содержание этого документа должны быть определены в этих процессах. Формат, приведенный ниже, использует идеи PSP, но не в полной форме. Вторая часть приведена в главе 8.[.
1.
Введение.
Этот документ описывает работу Джона Джонса над классом EncounterCharacter (ПерсонажВстречи). Конфигурация этого класса описана в файле PSD _EncounterCharacter. Файлы, на которые мы будем ссылаться, хранятся в каталоге EncounterPSDJjones в системе Galaxy.
2.
Учет дефектов.
Журнал учетных записей дефектов (табл. 7.2) поддерживается в файле defectLog. Таблица 7.2. Учетные записи дефектов (Хэмфри)
Дата
Номер
Тип
Этап,.
на котором обнаружили
Этап,.
на котором исправили
Время.
исправления (минуты)
14.06.99
142
Интерфейс
Индивидуальное.
детальное.
проектирование
Индивидуальная проверка кода
10
Описание: пропущена проверка длины имени в классе ПерсонажВстречи
16.06.99
143
Документация
Код
Индивидуальная проверка кода
4
Описание: некорректное описание Javadoc в классе ПерсонажВстречи
Эта таблица содержит дефекты, обнаруженные во время модульного тестирования (глава 8)
3. Учет времени.
[Примечание для студентов. Разработчики ведут учет того, сколько времени у них ушло на выполнение различных действий, необходимых в разработке (табл. 7.3). Эта информация очень важна для проекта, поэтому разработчику также предоставляется профессиональный набор инструментов. Информация о затраченном времени может быть собрана как в письменной форме, так и с помощью программных инструментов, резидентно установленных на компьютерах. Инженеры должны разработать общее понимание степени точности, требуемое организацией. Обратите внимание, что приблизительное измерение времени может легко привести к рассогласованиям на практике.].
Эта информация (табл. 7.3) хранится в файле Time_Rrecording_Log.
Таблица 7.3. Учетные записи времени (Хэмфри)
Дата
Начало
Конец
Перерывы
Затраченное время
Этап
Комментарии
06.99
10:04
10:25
4 + 6
11
Детальное проектирование
Проконсультировался у N.
06.99
13:20
16:34
15 + 20
159
Личная.
проверка кода
Дефект 14
07.99
4. Модульное тестирование. PSD для класса EncounterCharacter (ПерсонажВстречи).
Пример в конце главы 8.
Пример 4. Исходный код (без тестового кода): класс EncounterCharacter (ПерсонажВстречи).
Исходный код класса EncounterCharacter (ПерсонажВстречи) представлен в листинге 7.6. Символами //рс обозначается ссылка на псевдокод.
Листинг 7.6. Класс EncounterCharacter (ПерсонажВстречи): исходный код.
Package Encounter.EncounterCharacters;.
/* Имя класса EncounterCharacter.
* Дата 01.13.2000.
* Уведомление об авторском праве copyright 1999-2000 by Е. Braude */.
import java.awt.*; import java.io.*; import FrameworkRPG.Characters.*; import TestUti1ities.*:.
/** Основной класс для персонажей игры Встреча. SDD-ссылка: 6.2.1.
* Инварианты: значения qualValuel[] >= О.
* @author Eric Braude, Tom VanCourt.
* (aversion 0.2 */.
public class EncounterCharacter extends GameCharacer {.
/**Суммарное значение характеристик при инициализации*/ private static final float QUAL_T0TAL_INIT = 100.Of;.
//Обозначения, используемые при обращении других классов к характеристикам.
/**0бозначение одной из характеристик персонажа*/.
public static final String QUAL_CONCENTRATION = «concentration».
/**0бозначение одной из характеристик персонажа*/.
public static final String QUAL_INTELLIGENCE = «intelligence».
/**0бозначение одной из характеристик персонажа*/ public static final String QUAL_PATIENCE = «patience».
/**0бозначение одной из характеристик персонажа*/ public static final String QUAL_STAHINA = «stamina» /**0бозначение одной из характеристик персонажа*/ public static final String QUAl_STRENGTH = «strength».
/^Характеристики, имеющиеся у каждого персонажа.
Req:3.2.ЕС. 1. 1*/ public static final String[] qalityTypeS= { QUAL_CONCENTRATION. QUAL_STAMINA, QUAL_INTELLIGENCE, QUAL_PATIENCE. QUAL_STRENGTH.
}:.
/*Объектные переменные*/.
/**3начения характеристик Требование 3.2.ПВ.1.2*/ private float[] qualValuel = new float[qualityTypeS.length]; /** Имя GIF файла, содержащего изображение персонажа.
* Подразумевается, что на этом изображении персонаж стоит лицом налево.
* Выбрать рост персонажа относительно роста других персонажей, добавляя.
* сверху и снизу прозрачные пикселы. Самый высокий персонаж не должен.
* иметь этих добавочных пикселов. */.
private String imageFi1eNamel = null: /*Конструкторы*/.
/** Равномерно распределить исходное число очков-жизней по всем характеристикам.
* Требование: 3.2.ПВ.1.2 (инициализация значений характеристик) */.
protected EncounterCharacterO { superO;.
for( int i = 0: i
< qalityTypeS.length; ++i ).
qualValuelti] = QUAL_TOTAL_INIT / qualityTypeS.length;.
}.
/** Создать новый персонаж, используя заданное имя и файл с изображением.
* Требование: 3.2.ПВ.1.1 (присвоение имени персонажу).
* (Эрагаш nameP Печатаемое имя персонажа.
* (Pparam imageFi 1еР Имя файла относительно файла с изображением персонажа */.
protected EncounterCharacter( String nameP. String imageFi1eP ) { thisO:.
setName( nameP):.
imageFi1eNamel = imageFileP;.
}.
/** Создание нового персонажа с использованием заданного имени.
* Требование: 3.2.ПВ.1.1 (присвоение имени персонажу).
* (Pparam nameP Печатаемое имя персонажа */.
Листинг 7.6 (продолжение).
protected EncounterCharacterC String nameP).
{ this( nameP, null ): }.
/*Методы*/.
/** Требование 3.2.ПВ.3.2 (возможность настраивать значения.
* характеристик персонажа игры).
* Благодаря синхронизации значение qualityValuel постоянно даже при наличии.
* других потоков.
* SDD-ссылка: 6.1.2.1.1.
* Инварианты: см. инварианты класса.
* Предусловия: qualityP принадлежит qualityTypeS[].
* AND qualityValueP >
= О.
* AND qualityValueP.
* Постусловия: qualityP имеет значение qualityValueP.
* AND значения остальных характеристик остаются в том же соотношении, что и раньше, за исключением значений менее некоторого допустимого - они приравниваются к нулю,.
* @param qualityP Характеристика, значение которой мы будем настраивать.
* (Pparam qualityValueP Значение, которое мы присвоим этой характеристике */.
public synchronized void adjustQuality(String qualiryP, float qualityValueP) { // Значение характеристики, которое следует изменить float qualityValueM = qualityValuel[indexOf( qualityP )];.
// Сохраняем сумму значений.
float origi nalSumM = sumOfQualitiesO;.
// рс Присвоить заданное значение, учитывая пороговую величину setQuality( qualityP. qualityValueP );.
// рс Если настраивается единственная ненулевая характеристика. // распределить введенное значение поровну между // остальными характеристиками if( originalSumM == qualityValueM ) { float qualityDiffEach=(originalSumMqual i tyVa1ueP)/(quali tyTypeS.1ength-1);.
for( int i = 0: i
< qualityTypeS.length: ++i ).
if( !qualityTypeS[i].equalsIgnoreCase( qualityP ) ) setQuality( qualityTypeS[i], qualityDiffEach ):.
}.
else {.
/* Множитель (proportionM), на который следует изменить все.
* остальные характеристики.
* Пример: пусть были значения 1, 3, 5 (то есть сумма равна 9),.
* и первую характеристику изменили с 1 на 2. Тогда для «3» и «5».
* сумма должна измениться с 8/9 от общей на 7/9. так что каждое из.
* этих чисел следует умножить на 7/8, то есть на (9-2)/(9-1). */.
float proportionM=(originalSumM-qualityValueP)/(originalSumM-qualityVa1ueM);.
//pc Настроить остальные характеристики, сохраняя их соотношение for( int i = 0; i
< qualityTypeS.length: ++i ).
if( !qualityTypeS[i].equalslgnoreCaseC qualityP ) ) setQuality( qua 1ityTypeS[i], qua 1Valuel[i] * proportionM);.
}.
}.
/** Получить копию списка имен значений характеристик.
* ^return Рабочие копии строк имен, представляющие характеристики. */.
public static String[] getQua1ityTypes() {.
// Скопировать массив строк.
String [] returnListM = new String[qualityTypeS.length]:.
// Скопировать каждую строку for( int i = 0: i
< qualityTypeS.length; i++ ) returnListM[i] = new String(qualityTypeS[i]);.
// Вернуть копию return returnListM;.
}.
/** Возвращает значение определенной характеристики.
* Предусловие: qualityP принадлежит qualityTypeS[].
* Pparam qualityP Характеристика, значение которой мы хотим получить.
* @return Значение определенной характеристики */.
public float getQualityValue( String qualityP ) {.
return qualValuel[ indexOf( qualityP ) ];.
}.
/** Значения характеристик ниже этого порогового значения обнуляются.
* во избежание зависания программы на неопределенное время.
* Требование: например. 3.2.ПВ.1.2 (нижняя граница ненулевых значений.
* характеристик).
* @return Допустимое значение */.
static final float getToleranceO.
{ return 0.5f; }.
/** Возвращает индекс заданной характеристики.
* Предусловие qualityP принадлежит qualityTypeS[].
Листинг 7.6 (продолжение).
* @param qualityP Характеристика, которую мы ищем.
* @return Индекс характеристики */.
private static int indexOf( String qualityP ) {.
// По умолчанию для ненайденных значений int returnlndexM = -1;.
// Поиск в таблице имен характеристик for( int i = 0: i
< qualityTypeS,length; ++i ) if( qualityTypeS[ i ],equalsIgnoreCase( qualityP ) ) //Нашли имя? { returnlndexM = i; // Запомнить значение индекса.
break;.
}.
return returnlndexM;.
}.
/** Установить по умолчанию максимальное число букв в именах персонажей.
* Требование; 3.2.ПВ.1.1 (ограничение на длину имени персонажа).
* ^return Максимальное число символов, разрешенное в имени персонажа */.
protected int maxNumCharsInNameO.
{ return 15; }.
/** Установить значение характеристики без учета значений других.
* характеристик. Отбросить значения ниже минимального порога, обнулив.
* значение характеристики.
* Синхронизация не допускает изменений в qualityValuel. даже если она.
* используется и другими потоками.
* Требования: 3.2.ПВ.2 (нижний предел ненулевых значений характеристик).
* Предусловие: qualityP принадлежит qua!ityTypeS[].
* Постусловие: Значения характеристик выше минимально допустимого или.
* равны нулю.
*.
* Pparam qualityP Характеристика, которой присваивается новое значение.
* @param valueP Новое значение, присваиваемое характеристике. */.
public synchronized void setQuality( String qualityP, float valueP ).
{.
if( valueP
< getToleranceO ).
qualValueIC indexOf( qualityP ) ] = O.Of:.
else.
qualValuelt indexOf( qualityP ) ] = valueP;.
}.
/** Изобразить персонаж.
* Требования: 2.1.2.1 (персонаж, изображенный в зоне игры).
* SRS 3.2.ПИЛ (выбор изображения персонажа).
* SRS 3.2.ХИЛ (изображение персонажа в окне установки характеристик).
* @param compP Компонент пользовательского интерфейса, в котором.
* персонаж должен быть нарисован.
* @рагаш drawP Графический контекст для рисования.
* @param posP Пиксельные координаты в compP для центра изображения.
* (Ррагагл heightPixP Желаемая высота изображения, в пикселах.
* @param faceLeftP
Если персонаж повернут лицом налево.
*
если персонаж повернут лицом направо */.
public void showCharacter( Component compP, Graphics drawP, Point posP. int heightPixP. boolean faceLeftP ).
{.
if( imageFileNamel == null ).
{ // He указано имя файла. Вывести вместо него имя персонажа. drawP.setColor( Color.magenta ): FontMetrics fm = drawP.getFontMetrics(): // Вывести имя по центру расположения персонажа drawP.drawString( getNameO.
posP.x - fm.stringWidth( getNameO ) / 2, posP.y - fm.getHeightO/2 ):.
}.
else { // Имя файла предоставлено. Показать изображение из файла Image chlmagel = compP.getToolkit().getImage( imageFileNamel ):.
// Размеры изображения.
int imageWidth « chlmagel.getWidth(compP);.
int imageHeight = chlmagel.getHeight(compP);.
// Отмасштабировать ширину и высоту.
int sealedWidth = imageWidth * heightPixP / imageHeight:.
// Считайте, что обычно персонаж повернут лицом налево. // Решите, развернуть изображение или нет. if( faceLeftP ).
// Нарисовать изображение как есть, отмасштабировать и отцентровать drawP.drawlmage( chlmagel.
posP.x - scaledWidth/2, posP.y - heightPixP/2. posP.x + scaledWidth/2, posP.y + heightPixP/2. 0. 0. imageWidth-1, imageHeight-1. null );.
else.
// Нарисовать изображение, повернув персонаж лицом в другую сторону. // отмасштабировать и отцентровать drawP.drawlmage( chlmagel,.
posP.x + scaledWidth/2, posP.y - heightPixP/2, ,.
Листинг 7.6 (продолжение).
posP.x - scaledWidth/2, posP.y + heightPixP/2, О, 0. imageWidth-1. imageHeight-1, null ):.
}.
}.
/** Вычисляет сумму значений характеристик.
* Благодаря синхронизации гарантируется, что другой поток не изменит.
* qualityValuel в тот момент, когда этот поток вычисляет сумму.
* Требования: 3.2.ПВ.3.2 (пропорции значений характеристик).
* (Preturn Сумма значений характеристик игрока, >= 0 */.
public synchronized float sumOfQualitiesO {.
float sumM = O.Of;.
for( int i = 0; i
< qualityTypeS.length; ++i ).
sumM += qua!ValueI[i]; return sumM;.
}.
} //Конец класса EncounterCharacter

Популярные статьи

Свежие статьи