Файл: Руководство по стилю программирования и конструированию по.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 30.11.2023

Просмотров: 866

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

СОДЕРЖАНИЕ

ГЛАВА 6 Классы129쐽Вариант 3: использовать неявные экземпляры (с большой осторожностью). Вы должны создать новый сервис — скажем, SetCurrentFont ( fontId ), — при вызо- ве которого заданный экземпляр шрифта делается текущим. После этого все остальные сервисы используют текущий шрифт, благодаря чему в них не нуж- но передавать параметр fontId. При разработке простых приложений такой под- ход может облегчить использование нескольких экземпляров данных. В слож- ных приложениях подобная зависимость от состояния в масштабе всей сис- темы подразумевает, что вы должны следить за текущим экземпляром шрифта во всем коде, вызывающем методы Font; разумеется, сложность программы при этом повышается. Каким бы ни был размер приложения, всегда можно найти более удачные альтернативы данному подходу.Внутри АТД вы можете реализовать работу с несколькими экземплярами данных как угодно, но вне его при использовании языка, не являющегося объектно-ори- ентированным, возможны только три указанных варианта.АТД и классыАбстрактные типы данных лежат в основе концепции классов. В языках, поддержи- вающих классы, каждый АТД можно реализовать как отдельный класс. Однако обыч- но с классами связывают еще две концепции: наследование и полиморфизм. Може- те рассматривать класс как АТД, поддерживающий наследование и полиморфизм.6.2. Качественные интерфейсы классовПервый и, наверное, самый важный этап разработки высококачественного клас- са — создание адекватного интерфейса. Это подразумевает, что интерфейс дол- жен представлять хорошую абстракцию, скрывающую детали реализации класса.Хорошая абстракцияКак я говорил в подразделе «Определите согласованные абстракции» раздела 5.3,под абстракцией понимается представление сложной операции в упрощенной форме. Интерфейс класса — это абстракция реализации класса, скрытой за ин- терфейсом. Интерфейс класса должен предоставлять группу методов, четко согла- сующихся друг с другом.Рассмотрим для примера класс «сотрудник». Он может содержать такие данные,как фамилия сотрудника, адрес, номер телефона и т. д., и предлагать методы ини- циализации и использования этих данных. Вот как мог бы выглядеть такой класс:Пример интерфейса, формирующего хорошую абстракцию (C++)class Employee {public: // открытые конструкторы и деструкторы Employee(); Employee( FullName name, String address, String workPhone,Перекрестная ссылка Примеры кода в этой книге отформати- рованы с использованием кон- венции, поддерживающей сход- ство стилей между нескольки- ми языками. Об этой конвенции(и разных стилях кодирования)см. подраздел «Программирова- ние с использованием несколь- ких языков» раздела 11.4. 130ЧАСТЬ II Высококачественный код String homePhone, TaxId taxIdNumber, JobClassification jobClass ); virtual Employee(); // открытые методы FullName GetName() const; String GetAddress() const; String GetWorkPhone() const; String GetHomePhone() const; TaxId GetTaxIdNumber() const; JobClassification GetJobClassification() const;private:};Внутри этот класс может иметь дополнительные методы и данные, поддержива- ющие работу этих сервисов, но пользователям класса знать о них не нужно. Пред- ставляемая интерфейсом этого класса абстракция великолепна, потому что все методы интерфейса служат единой согласованной цели.Интерфейс, представляющий плохую абстракцию, содержал бы набор разнород- ных методов, например:Пример интерфейса, формирующегоплохую абстракцию (C++)class Program {public: // открытые методы void InitializeCommandStack(); void PushCommand( Command command ); Command PopCommand(); void ShutdownCommandStack(); void InitializeReportFormatting(); void FormatReport( Report report ); void PrintReport( Report report ); void InitializeGlobalData(); void ShutdownGlobalData();private:};Похоже, этот класс содержит методы работы со стеком команд, форматирования отчетов, печати отчетов и инициализации глобальных данных. Трудно увидеть связь между стеком команд, обработкой отчетов и глобальными данными. Интерфейс такого класса не формирует согласованную абстракцию, и класс обладает плохой ГЛАВА 6 Классы131связностью. В данном случае методы следует реорганизовать в более четкие классы,интерфейсы которых будут представлять более удачные абстракции.Если бы эти методы были частью класса Program, для формирования согласован- ной абстракции их можно было бы изменить так:Пример интерфейса, формирующего более удачную абстракцию (C++)class Program {public: // открытые методы void InitializeUserInterface(); void ShutDownUserInterface(); void InitializeReports(); void ShutDownReports();private:};В ходе очистки интерфейса одни его методы были перемещены в более подходя- щие классы, а другие были преобразованы в закрытые методы, используемые методом InitializeUserInterface() и другими методами.Данный способ оценки абстракции класса основан на изучении открытых методов класса, т. е. его интерфейса. Однако из того, что класс в целом формирует хорошую абстракцию, вовсе не следует, что его отдельные методы также представляют удач- ные абстракции. Рекомендации по проектированию методов см. в разделе 7.2.Чтобы ваши классы имели высококачественные абстрактные интерфейсы, соблю- дайте при их проектировании следующие принципы.Выражайте в интерфейсе класса согласованный уровень абстракцииКлассы полезно рассматривать как механизмы реализации абстрактных типов дан- ных, описанных в разделе 6.1. В идеале каждый класс должен быть реализацией только одного АТД. Если класс реализует более одного АТД или если вам не уда- ется определить, реализацией какого АТД класс является, самое время реоргани- зовать класс в один или несколько хорошо определенных АТД.Так, следующий класс имеет несогласованный интерфейс, потому что формируе- мый им уровень абстракции непостоянен:Пример интерфейса, включающего разныеуровни абстракции (C++)class EmployeeCensus: public ListContainer {public: // открытые методы 132ЧАСТЬ II Высококачественный кодАбстракция, формируемая этими методами, относится к уровню «employee» (сотрудник). void AddEmployee( Employee employee ); void RemoveEmployee( Employee employee );Абстракция, формируемая этими методами, относится к уровню «list» (список). Employee NextItemInList(); Employee FirstItem(); Employee LastItem();private:};Этот класс представляет два АТД: Employee и ListContainer (список-контейнер).Подобные смешанные абстракции часто возникают, когда программист реализу- ет класс при помощи класса-контейнера или других библиотечных классов и не скрывает этот факт. Спросите себя, должна ли информация об использовании класса-контейнера быть частью абстракции. Обычно это является деталью реали- зации, которую следует скрыть от остальных частей программы, например так:Пример интерфейса, формирующего согласованную абстракцию (C++)class EmployeeCensus {public: // открытые методыАбстракция, формируемая всеми этими методами, теперь относится к уровню «employee». void AddEmployee( Employee employee ); void RemoveEmployee( Employee employee ); Employee NextEmployee(); Employee FirstEmployee(); Employee LastEmployee();private:Тот факт, что класс использует библиотеку ListContainer, теперь скрыт. ListContainer m_EmployeeList;};Программисты могут утверждать, что наследование от ListContainer удобно, потому что оно поддерживает полиморфизм, позволяя создать внешний метод поиска или сортировки, принимающий объект ListContainer. Но этот аргумент не проходит главный тест на уместность наследования: «Используется ли наследование толь- ко для моделирования отношения „является“?» Наследование класса EmployeeCensus(каталог личных дел сотрудников) от класса ListContainer означало бы, что Employee-Census «является» ListContainer, что, очевидно, неверно. Если абстракция объектаEmployeeCensus заключается в том, что он поддерживает поиск или сортировку,>>>> ГЛАВА 6 Классы133эти возможности должны быть явными согласованными частями интерфейса класса.Если представить открытые методы класса как люк, предотвращающий попадание воды в подводную лодку, несогласованные открытые методы — это щели. Вода не будет протекать через них так быстро, как через открытый люк, но позже лодка все же потонет. На практике при смешении уровней абстракции именно это и проис- ходит. По мере изменений программы смешанные уровни абстракции делают ее все менее и менее понятной, пока в итоге код не станет совсем загадочным.Убедитесь, что вы понимаете, реализацией какой абстракцииявляется класс Некоторые классы очень похожи, поэтому при разра- ботке класса нужно понимать, какую абстракцию должен представлять его интерфейс. Однажды я работал над программой, которая должна была под- держивать редактирование информации в табличном формате. Сначала мы хо- тели использовать простой элемент управления «grid» (сетка), но доступные эле- менты управления этого типа не позволяли закрашивать ячейки ввода данных в другой цвет, поэтому мы выбрали элемент управления «spreadsheet» (электронная таблица), который такую возможность поддерживал.Элемент управления «электронная таблица» был гораздо сложнее «сетки» и пре- доставлял около 150 методов в сравнении с 15 методами «сетки». Так как наша цель заключалась в использовании «сетки», а не «электронной таблицы», мы поручили одному программисту написать класс-оболочку, который скрывал бы тот факт, что мы подменили один элемент управления другим. Он поворчал по поводу ненуж- ных затрат и бюрократии, ушел и вернулся через пару дней с классом-оболочкой,который честно предоставлял все 150 методов «электронной таблицы».Но нам было нужно не это — нам требовался интерфейс «сетки», инкапсулирую- щий тот факт, что за кулисами мы использовали гораздо более сложную «элект- ронную таблицу». Программисту следовало предоставить доступ только к 15 ме- тодам «сетки» и еще одному, шестнадцатому методу, поддерживающему закраши- вание ячеек. Открыв доступ ко всем 150 методам, программист подверг нас риску того, что после нескольких изменений реализации класса нам в итоге придется поддерживать все 150 открытых методов. Он не смог обеспечить нужную нам инкапсуляцию и проделал гораздо больше работы, чем стоило.В зависимости от конкретных обстоятельств оптимальной абстракцией может оказаться как «сетка», так и «электронная таблица». Если приходится выбирать между двумя похожими абстракциями, убедитесь, что выбор правилен.Предоставляйте методы вместе с противоположными им методамиБольшинство операций имеет соответствующие противоположные операции. Если одна из операций включает свет, вам, вероятно, понадобится и операция, его вы- ключающая. Если одна операция добавляет элемент в список, элементы скорее всего нужно будет и удалять. Если одна операция активизирует элемент меню, вторая,наверное, должна будет его деактивизировать. При проектировании класса про- верьте каждый открытый метод на предмет того, требуется ли вам его противо- положность. Создавать противоположные методы, не имея на то причин, не сле- дует, но проверить их целесообразность нужно. 134ЧАСТЬ II Высококачественный кодУбирайте постороннюю информацию в другие классы Иногда вы будете обнаруживать, что одни методы класса работают с одной половиной данных, а другие — с другой. Это значит, что вы имеете дело с двумя классами, скрывающи- мися под маской одного. Разделите их!По мере возможности делайте интерфейсы программными, а не семан-тическими Каждый интерфейс состоит из программной и семантической ча- стей. Первая включает типы данных и другие атрибуты интерфейса, которые могут быть проверены компилятором. Вторая складывается из предположений об ис- пользовании интерфейса, которые компилятор проверить не может. Семантический интерфейс может включать такие соображения, как «Метод А должен быть выз- ван перед Методом B» или «Метод А вызовет ошибку, если переданный в него Эле- мент Данных 1 не будет перед этим инициализирован». Семантический интерфейс следует документировать в комментариях, но вообще интерфейсы должны как можно меньше зависеть от документации. Любой аспект интерфейса, который не может быть проверен компилятором, является потенциальным источником оши- бок. Старайтесь преобразовывать семантические элементы интерфейса в программ- ные, используя утверждения (assertions) или иными способами.Опасайтесь нарушения целостности интерфейса приизменении класса При модификации и расширении клас- са часто обнаруживается дополнительная нужная функци- ональность, которая не совсем хорошо соответствует интер- фейсу первоначального класса, но плохо поддается реализации иным образом. Так,класс Employee может превратиться во что-нибудь вроде:Пример интерфейса, изуродованного при сопровождениипрограммы (C++)class Employee {public: // открытые методы FullName GetName() const; Address GetAddress() const; PhoneNumber GetWorkPhone() const; bool IsJobClassificationValid( JobClassification jobClass ); bool IsZipCodeValid( Address address ); bool IsPhoneNumberValid( PhoneNumber phoneNumber ); SqlQuery GetQueryToCreateNewEmployee() const; SqlQuery GetQueryToModifyEmployee() const; SqlQuery GetQueryToRetrieveEmployee() const;private:};То, что начиналось как ясная абстракция, превратилось в смесь почти несогласо- ванных методов. Между сотрудниками и методами, проверяющими корректностьПерекрестная ссылка О поддер- жании качества кода при его изменении см. главу 24. ГЛАВА 6 Классы135почтового индекса, номера телефона или ставки зарплаты (job classification), нет логической связи. Методы, предоставляющие доступ к деталям SQL-запросов, от- носятся к гораздо более низкому уровню абстракции, чем класс Employee, нару- шая общую абстракцию класса.Не включайте в класс открытые члены, плохо согласующиеся с абстрак-цией интерфейса Добавляя новый метод в интерфейс класса, всегда спраши- вайте себя: «Согласуется ли этот метод с абстракцией, формируемой существую- щим интерфейсом?» Если нет, найдите другой способ внесения изменения, позво- ляющий сохранить согласованность абстракции.Рассматривайте абстракцию и связность вместе Понятия абстракции и связности (cohesion) тесно связаны: интерфейс класса, представляющий хорошую абстракцию, обычно отличается высокой связностью. И наоборот: классы, имею- щие высокую связность, обычно представляют хорошие абстракции, хотя эта связь выражена слабее.Я обнаружил, что при повышенном внимании к абстракции, формируемой ин- терфейсом класса, проект класса получается более удачным, чем при концентра- ции на связности класса. Если вы видите, что класс имеет низкую связность и не знаете, как это исправить, спросите себя, представляет ли он согласованную аб- стракцию.Хорошая инкапсуляцияКак я уже говорил в разделе 5.3, инкапсуляция является бо- лее строгой концепцией, чем абстракция. Абстракция по- могает управлять сложностью, предоставляя модели, позво- ляющие игнорировать детали реализации. Инкапсуляция не позволяет узнать детали реализации, даже если вы этого захотите.Две этих концепции связаны: без инкапсуляции абстракция обычно разрушается.По своему опыту могу сказать, что вы или имеете и абстракцию, и инкапсуляцию,или не имеете ни того, ни другого. Промежуточных вариантов нет.Минимизируйте доступность классов и их членов Ми- нимизация доступности — одно из нескольких правил, под- держивающих инкапсуляцию. Если вы не можете понять,каким делать конкретный метод: открытым, закрытым или защищенным, — некоторые авторы советуют выбирать са- мый строгий уровень защиты, который работает (Meyers,1998; Bloch, 2001). По-моему, это прекрасное правило, но мне кажется, что еще важнее спросить себя: «Какой вари- ант лучше всего сохраняет целостность абстракции интер- фейса?» Если предоставление доступа к методу согласуется с абстракцией, сделайте его открытым. Если вы не уверены, скрыть больше обычно предпочтительнее, чем скрыть меньше.Не делайте данные-члены открытыми Предоставление доступа к данным- членам нарушает инкапсуляцию и ограничивает контроль над абстракцией. КакПерекрестная ссылка Об инкап- суляции см. подраздел «Инкап- сулируйте детали реализации»раздела 5.3.Самым важным отличием хоро- шо спроектированного модуля от плохо спроектированного яв- ляется степень, в которой мо- дуль скрывает свои внутренние данные и другие детали реали- зации от других модулей.Джошуа Блох (Joshua Bloch) 136ЧАСТЬ II Высококачественный код указывает Артур Риэль, класс Point (точка), который предоставляет доступ к дан- ным:float x;float y;float z;нарушает инкапсуляцию, потому что клиентский код может свободно делать с данными Point что угодно, при этом сам класс может даже не узнать об их изме- нении (Riel, 1996). В то же время класс Point, включающий члены:float GetX();float GetY();float GetZ();void SetX( float x );void SetY( float y );void SetZ( float z );поддерживает прекрасную инкапсуляцию. Вы не имеете понятия о том, реализо- ваны ли данные как float x, y и z, хранит ли класс Point эти элементы как double,преобразуя их в float, или же он хранит их на Луне и получает через спутник.Не включайте в интерфейс класса закрытые детали реализации Истинная инкапсуляция не позволяла бы узнать детали реализации вообще. Они были бы скрыты и в прямом, и в переносном смыслах. Однако популярные языки — в том числе C++ — требуют, чтобы программисты раскрывали детали реализации в интерфейсе класса, например:Пример обнародования деталей реализации класса (C++)class Employee {public: Employee( FullName name, String address, String workPhone, String homePhone, TaxId taxIdNumber, JobClassification jobClass ); FullName GetName() const; String GetAddress() const;private:Обнародованные детали реализации. String m_Name; String m_Address; int m_jobClass;};> 1   ...   14   15   16   17   18   19   20   21   ...   104

ГЛАВА 20 Качество ПО463Табл. 20-2. Эффективность нахождения дефектовпри использовании разных методикМетодикаМинимальнаяТипичнаяМаксимальнаяустранения дефектовэффективностьэффективностьэффективностьНеформальные25%35%40%обзоры проектаФормальные45%55%65%инспекции проектаНеформальные20%25%35%обзоры кодаФормальные45%60%70%инспекции кодаМоделирование35%65%80%или прототипированиеСамостоятельная20%40%60%проверка кодаБлочное тестирование15%30%50%Тестирование новых20%30%35%функций (компонентов)Интеграционное25%35%40%тестированиеРегрессивное15%25%30%тестированиеТестирование системы25%40%55%Ограниченное бета-тес- 25%35%40%тирование (менее чем в 10 организациях)Крупномасштабное бета- 60%75%85%тестирование (более чем в 1000 организаций)Источники: «Programming Productivity» (Jones, 1986a), «Software Defect-RemovalEfficiency» (Jones, 1996) и «What We Have Learned About Fighting Defects» (Shull et al., 2002).Самое интересное в этих данных то, что типичная эффективность об- наружения дефектов при использовании любой методики не превышает75% и что в среднем она равна примерно 40%. Более того, самые попу- лярные методики — блочное тестирование и интеграционное тестирование — по- зволяют найти обычно только около 30–35% дефектов. Как правило, использует- ся подход, основанный на интенсивном тестировании, что позволяет устранить лишь около 85% дефектов. Ведущие организации используют более широкий ди- апазон методик, достигая при этом 95%-ой или более высокой эффективности ус- транения дефектов (Jones, 2000).Итак, если разработчики хотят достигнуть более высокой эффективности обна- ружения дефектов, они должны полагаться на комбинацию методик. Одно из подтверждений этого вывода было получено в классическом исследовании Глен- форда Майерса (Myers, 1978b). Участниками исследования были программисты,обладавшие минимум 7-, а в среднем — 11-летним опытом. Исследуемая программа 464ЧАСТЬ V Усовершенствование кода содержала 15 известных ошибок. Майерс попросил каждого программиста най- ти эти ошибки, используя одну из следующих методик:쐽тестирование выполнения программы по спецификации;쐽тестирование выполнения программы по спецификации с возможностью изу- чения исходного кода;쐽анализ/инспекция с использованием и спецификации, и исходного кода.Различия эффективности обнаружения дефектов оказались очень боль- шими: программисты нашли от 1 до 9 дефектов. Средний показатель был равен 5,1, или 1/3 от общего числа известных дефектов.При использовании одной методики никакая из них не имела статистически зна- чимого преимущества над любой другой. В то же время любая комбинация двух методик — в том числе использование одной методики двумя независимыми груп- пами — приводила к увеличению общего числа найденных дефектов почти вдвое.В исследованиях, проведенных в Лаборатории проектирования ПО NASA, компа- нии Boeing и других компаниях, было обнаружено, что разные программисты находят разные дефекты. Только примерно каждую пятую ошибку, обнаруженную в ходе инспекций, находят двое или более разработчиков (Kouchakdjian, Green,and Basili, 1989; Tripp, Struck, and Pflug, 1991; Schneider, Martin, and Tsai, 1992).Майерс обращает внимание на то, что поиск одних видов ошибок оказывается более эффективным при непосредственном участии людей (например, при инспекции или анализе кода), а других видов — при компьютерном тестировании (Myers, 1979).Этот вывод подтвердился в более позднем исследовании, показавшем, что чтение кода способствует нахождению дефектов интерфейса, а функциональное тести- рование — нахождению дефектов управляющих структур (Basili, Selby, and Hutchens,1986). Гуру тестирования Борис Бейзер (Boris Beizer) сообщает, что неформаль- ные подходы к тестированию обычно позволяют достигнуть покрытия кода тес- тами лишь на 50–60%, если только вы не используете анализатор покрытия(Johnson, 1994).Таким образом, методики поиска дефектов лучше применять в комбина- ции. Джонс (Jones) также подтверждает этот вывод. Используя исключи- тельно тестирование, высоких результатов добиться невозможно. Джонс сообщает, что комбинация блочного тестирования, функционального тестирования и тестирования системы часто приводит к обнаружению менее 60% дефектов, что обычно неприемлемо для конечного продукта.Эти данные также помогают понять, почему программисты, начинающие приме- нять дисциплинированную методику устранения дефектов, такую как экстремаль- ное программирование, добиваются более высокой степени устранения дефектов.Как показывает табл. 20-3, набор методик устранения дефектов, применяемых в экстремальном программировании, позволяет устранить около 90% дефектов в обычной ситуации и 97% в лучшем случае, что гораздо выше среднего для отрас- ли показателя, равного 85%. Некоторые программисты связывают этот факт с синергичными отношениями между методиками экстремального программиро- вания, но на самом деле это просто предсказуемый результат использования кон- кретного набора методик устранения дефектов. Эффективность других комбинаций методик может оказаться такой же или даже более высокой, поэтому выбор кон- ГЛАВА 20 Качество ПО465кретных методик устранения дефектов, позволяющих достичь желаемого уровня качества, является одним из аспектов эффективного планирования проекта.Табл. 20-3. Эффективность обнаружения дефектов, характернаядля экстремального программированияМетодика устраненияМинимальнаяТипичнаяМаксимальнаядефектовэффективностьэффективностьэффективностьНеформальные обзоры25%35%40%проекта (парное программи- рование)Неформальные обзоры кода20%25%35%(парное программирование)Самостоятельная20%40%60%проверка кодаБлочное тестирование15%30%50%Интеграционное25%35%40%тестированиеРегрессивное тестирование15%25%30%Общая эффективность74%90%97%устранения дефектовСтоимость нахождения дефектовНекоторые методики обнаружения дефектов дороже других. Наиболее экономич- ные при прочих равных условиях имеют наименьшую стоимость в расчете на один обнаруженный дефект. Равенством прочих условий пренебрегать нельзя, поскольку стоимость методики в расчете на один дефект зависит от общего числа обнару- женных дефектов, этапа обнаружения каждого дефекта и других факторов, не связанных с экономическими аспектами конкретной методики.Как правило, эксперименты показывают, что инспекции обходятся дешев- ле, чем тестирование. В исследовании, проведенном в Лаборатории про- ектирования ПО, было обнаружено, что при чтении кода число дефек- тов, находимых в час, было примерно на 80% более высоким, чем при тестирова- нии (Basili and Selby, 1987). В другой организации поиск дефектов проектирова- ния с использованием блочного тестирования был вшестеро дороже, чем при ис- пользовании инспекций (Ackerman, Buchwald, and Lewski, 1989). Более позднее ис- следование, проведенное в IBM, показало, что на обнаружение каждой ошибки раз- работчики тратили 3,5 человеко-часа в случае инспекций кода и 15–25 в случае тестирования (Kaplan, 1995).Стоимость исправления дефектовСтоимость нахождения дефектов — только одна часть уравнения. Другой частью является стоимость их исправления. На первый взгляд, методика обнаружения дефектов не играет роли: стоимость их исправления всегда будет одинаковой.Это неверно, потому что чем дольше дефект остается в системе, тем больше средств придется потратить на его устранение. Следовательно, методика, способствующая раннему обнаружению ошибок, снижает стоимость их исправления. Еще важнее 466ЧАСТЬ V Усовершенствование кода то, что одни методики — такие как инспекции — позволя- ют определить и симптомы, и причины дефектов за один этап; другие — например, тестирование — указывают на симптомы дефекта, но требуют выполнения дополнитель- ной работы для диагностики и устранения его причины. Витоге одноэтапные методики оказываются гораздо более дешевыми, чем двухэтапные.В одном из подразделений Microsoft обнаружили, что при использова- нии инспекции кода — одноэтапной методики — на нахождение и ис- правление дефекта уходит 3 часа, тогда как при использовании тестиро- вания — двухэтапной методики — на это требуется 12 часов (Moore, 1992). Кол- лофелло и Вудфилд сообщили, что при разработке программы из 700 000 строк,над которой работало более 400 программистов, обзоры кода имели гораздо бо- лее высокую экономическую эффективность, чем тестирование: прибыль на ин- вестированный капитал была равной 1,38 и 0,17 соответственно (Collofello andWoodfield, 1989).Суть сказанного в том, что эффективная программа контроля качества должна включать комбинацию методик, применяемых на всех стадиях разработки. Для достижения высокого качества ПО можно использовать следующую комбинацию:쐽формальные инспекции всех требований, всех аспектов архитектуры и всех проектов критических частей системы;쐽моделирование или прототипирование;쐽чтение или инспекции кода;쐽тестирование выполнения программы.20.4. Когда выполнять контроль качества ПО?Как было отмечено в главе 3, чем раньше ошибка внедряет- ся в приложение, тем сильнее она переплетается с другими частями приложения и тем больше средств придется потра- тить на ее устранение. Дефект в требованиях может вылить- ся в один или несколько дефектов в проекте, которые могут привести к появлению множества дефектов в коде. Ошибка в требованиях может привести к разработке дополнительных компонентов архитектуры или подтолкнуть к неудачным ар- хитектурным решениям. Дополнительные архитектурные компоненты требуют написания дополнительного кода, те- стов и документации. С другой стороны, ошибка в требованиях может привести к выбрасыванию частей архитектуры, кода и тестов. Если идея устранения ошибок из чертежей дома перед заливкой фундамента бетоном кажется вам разумной, то вы согласитесь и с тем, что дефекты требований и архитектуры также следует уст- ранять до того, как они повлияют на более поздние этапы разработки.Кроме того, ошибки в требованиях или архитектуре обычно имеют более широ- кие следствия, чем ошибки конструирования. Одна ошибка в архитектуре может затронуть несколько классов и десятки методов, тогда как одна ошибка констру-Перекрестная ссылка О зависи- мости стоимости исправления дефектов от срока их присут- ствия в системе см. раздел «Об- ращение к данным» раздела 3.1.Сами ошибки более подробно обсуждаются в разделе 22.4.Перекрестная ссылка Контроль качества предварительных дей- ствий — например, определения требований и разработки архи- тектуры — в этой книге не рас- сматривается. Информацию по этим темам можно найти в кни- гах, указанных в разделе «До- полнительные ресурсы» в кон- це этой главы. ГЛАВА 20 Качество ПО467ирования скорее всего повлияет только на один метод или класс. Это еще одно убедительное обоснование как можно более раннего нахождения ошибок.Дефекты проникают в ПО на всех стадиях разработки, поэтому контро- лю качества следует уделять должное внимание на всех этапах проекта,начиная с самых ранних. Контроль качества нужно внести в планы в начале работы над программой; его следует выполнять по мере прогресса; нако- нец, он должен подчеркивать удачное завершение работы над проектом.20.5. Главный Закон Контроля Качества ПОНи в одном ресторане посетителей не кормят бесплатно, и даже если б кормили, никто не смог бы поручиться за качество блюд. Однако разра- ботка ПО — совсем не кулинарное искусство, и качество ПО имеет одну важную необычную особенность. Главный Закон Контроля Качества ПО заключа- ется в том, что повышение качества системы снижает расходы на ее разработку.В основе этого закона лежит одно важное наблюдение: лучшим способом повы- шения производительности труда программистов и качества ПО является мини- мизация времени, затрачиваемого на исправление кода, чем бы оно ни объясня- лось: изменениями требований, изменениями проекта или отладкой. Средняя для отрасли производительность труда программистов эквивалентна примерно 10–50 строкам кода на одного человека в день (с учетом всех затрат, не связанных с кодированием). Написание 10–50 строк кода требует нескольких минут, — на что же уходит остальное время?Такая, казалось бы, низкая производительность труда час- тично объясняется тем, что в подобных средних показате- лях учитывается время, не связанное непосредственно с программированием. Время тестировщиков, руководителей,секретарей — все эти факторы включены в данный показа- тель. Определение требований, разработка архитектуры и другие действия, не относящиеся к кодированию, также отра- жены в «строках кода в день». Однако основные временные затраты объясняются не этим.Самый длительный этап в большинстве проектов — отладка и исправление не- правильного кода. При традиционном цикле разработки ПО эти действия зани- мают около 50% времени (см. раздел 3.1). Сокращение потребности в отладке,достигаемое благодаря предотвращению ошибок, повышает производительность труда. Следовательно, наиболее очевидный метод сокращения графика разработ- ки — повышение качества ПО и снижение объема времени, уходящего на его отладку и исправление.Этот анализ подтверждается реальными данными. В обзоре 50 проектов,потребовавших более 400 человеколет и включивших почти 3 000 000строк кода, проведенном в Лаборатории проектирования ПО NASA, было обнаружено, что повышенное внимание к контролю качества позволяло снизить уровень ошибок, но не повышало общие расходы на разработку (Card, 1987).Перекрестная ссылка О разли- чиях между написанием отдель- ной программы и созданием программного продукта см. под- раздел «Программы, продукты,системы и системные продукты»раздела 27.5. 468ЧАСТЬ V Усовершенствование кодаВ исследовании, проведенном в IBM, были получены аналогичные результаты:Программным проектам с наименьшими уровнями дефектов соответствовали самые короткие графики разработки и максимальные показатели производитель- ности труда… устранение дефектов на самом деле — самый дорогой и длитель- ный этап разработки ПО (Jones, 2000).Это верно и для противоположного края шкалы. В одном исследовании1985 года ученые попросили 166 профессиональных программистов написать программы по одной и той же спецификации. Итоговые про- граммы содержали в среднем 220 строк, а на их написание ушло в среднем чуть меньше 5 часов. Результаты оказались поистине удивительными: программисты,работавшие над своими программами средний объем времени, допустили наиболь- шее число ошибок. Программисты, которым потребовалось больше или меньше времени, допустили значительно меньше ошибок (DeMarco and Lister, 1985). Ре- зультаты показаны на рисунке 20-2.Рис. 20-2. Ни при самом быстром, ни при самом медленном подходе к разработкеПО не наблюдается наибольший уровень дефектовВ сравнении с самой быстрой группой двум самым медленным группам понадо- билось примерно в 5 раз больше времени для достижения результата с примерно тем же уровнем дефектов. Таким образом, на создание ПО без дефектов не всегда уходит больше времени, чем на написание ПО с дефектами.Вероятно, в некоторых случаях контроль качества требует значительных затрат.Если вы пишете приложение управления космическим кораблем или медицин- ской системой жизнеобеспечения, высокие требования к надежности ПО делают проект более дорогим.В сравнении с традиционным циклом «кодирование — тестирование — отладка»улучшенная программа контроля качества ПО оказывается более экономичной.Она перенаправляет ресурсы от отладки и рефакторинга к предварительным этапам контроля качества. Предварительные этапы влияют на качество системы больше,чем последующие, поэтому время, потраченное на предварительных этапах, по- зволяет сэкономить больше времени потом. Результатом является снижение уровня ГЛАВА 20 Качество ПО469дефектов, сокращение сроков разработки и снижение затрат. В трех следующих главах вы найдете еще несколько примеров, иллюстрирующих Главный ЗаконКонтроля Качества ПО.Контрольный список: план контроля качества Идентифицировали ли вы специфические характеристики качества, имеющие особую важность в вашем проекте? Сообщили ли вы другим программистам целевые характеристики качества? Провели ли вы различие между внешними и внутренними характеристика- ми качества? Обдумали ли вы, как некоторые характеристики могут усиливать или ос- лаблять другие? Призывает ли ваш проект к использованию нескольких методик обнаруже- ния ошибок, ориентированных на поиск разных видов ошибок? Составили ли вы план контроля качества, охватывающий все этапы разра- ботки ПО? Оцениваете ли вы качество системы каким-нибудь образом, чтобы можно было определить, повышается оно или понижается? Понимают ли руководители, что контроль качества требует дополнительных расходов в начале проекта, но зато позволяет добиться общей экономии средств?Дополнительные ресурсыСоставить список книг для этой главы несложно, потому что методики повышения качества ПО и производительности труда описываются почти во всех трудах, посвященных эф- фективным методологиям разработки ПО. Сложность в том, чтобы выделить книги,касающиеся непосредственно качества ПО. Ниже я указал две такие работы.Ginac, Frank P. Customer Oriented Software Quality Assurance. Englewood Cliffs, NJ: PrenticeHall, 1998. В этой очень краткой книге описаны атрибуты качества, метрики каче- ства, программы контроля качества, роль тестирования в контроле качества, а так- же известные программы повышения качества, в том числе модель CMM, разрабо- танная в институте Software Engineering Institute, и стандарты ISO серии 9000.Lewis, William E. Software Testing and Continuous Quality Improvement, 2d ed. Auer- bach Publishing, 2000. В этой книге можно найти подробное обсуждение цикла контроля качества, а также методик тестирования. Кроме того, в ней вы найдете много контрольных форм и списков.Соответствующие стандартыIEEE Std 730-2002 — стандарт IEEE планирования контроля качества ПО.IEEE Std 1061-1998 — стандарт IEEE методологии метрик качества ПО.IEEE Std 1028-1997 — стандарт обзоров ПО.http://cc2e.com/2043http://cc2e.com/2050http://cc2e.com/2057 470ЧАСТЬ V Усовершенствование кодаIEEE Std 1008-1987 (R1993) — стандарт блочного тестирования ПО.IEEE Std 829-1998 — стандарт документирования тестов ПО.Ключевые моменты쐽Высокого качества можно достичь без дополнительных затрат, но для этого вы должны перераспределить ресурсы и предотвращать дефекты вместо того,чтобы их исправлять.쐽Стремление к одним характеристикам качества препятствует достижению дру- гих. Четко определите цели, имеющие для вас первостепенную важность, и сообщите об этом всем членам группы.쐽Никакая методика обнаружения дефектов не является достаточно эффектив- ной. Тестирование само по себе — не самый лучший способ устранения оши- бок. Составляя программу контроля качества, предусмотрите применение не- скольких методик, позволяющих обнаружить разные виды ошибок.쐽Существуют многие эффективные методики контроля качества, применяемые как во время конструирования, так и до его начала. Чем раньше вы обнаружи- те дефект, тем слабее он переплетется с остальным кодом и тем меньше вреда он успеет принести.쐽В мире программирования контроль качества ориентирован на процесс.В отличие от промышленного производства разработка ПО не включает по- вторяющегося этапа, влияющего на конечный продукт, поэтому качество ре- зультата определяется процессом, используемым для разработки ПО. 1   ...   53   54   55   56   57   58   59   60   ...   104

ГЛАВА 32 Самодокументирующийся код775граммой, и после тщательного изучения документации он обнаружил только та- кой комментарий:MOV AX, 723h ; R. I. P. L. V. B.Поломав над ним голову всю ночь, программист в итоге все исправил и пошел домой спать. Несколько месяцев спустя он встретился с автором программы на конференции и узнал, что комментарий означал «Rest in peace, Ludwig van Beet- hoven» (Покойся в мире, Людвиг ван Бетховен). Бетховен умер в 1827 году, кото- рому соответствует шестнадцатеричное значение 723. Необходимость использо- вания значения 723h не имела никакого отношения к комментарию. &%@*?#%

БиблиографияNewcomer, Joseph M. 2000. «Optimization: Your Worst Enemy,» May 2000, www.flounder. com/optimization.htm.Norcio, A. F. 1982. «Indentation, Documentation and Programmer Comprehension.» Proceedings:Human Factors in Computer Systems, March 15–17, 1982, Gaithersburg, MD: 118–20.Norman, Donald A. 1988. The Psychology of Everyday Things. New York, NY: Basic Books. (Also published in paperback as The Design of Everyday Things. New York, NY: Doubleday, 1990.)Oman, Paul and Shari Lawrence Pfleeger, eds. 1996. Applying Software Metrics. Los Alamitos, CA:IEEE Computer Society Press.Oman, Paul W., and Curtis R. Cook. 1990a. «The Book Paradigm for Improved Maintenance.» IEEESoftware, January, 39–45.Oman, Paul W., and Curtis R. Cook. 1990b. «Typographic Style Is More Than Cosmetic.» Commu-nications of the ACM 33, no. 5 (May): 506–20.Ostrand, Thomas J., and Elaine J. Weyuker. 1984. «Collecting and Categorizing Software Error Data in an Industrial Environment.» Journal of Systems and Software 4, no. 4 (November): 289–300.Page-Jones, Meilir. 2000. Fundamentals of Object-Oriented Design in UML. Boston, MA: Addison-Wesley.Page-Jones, Meilir. 1988. The Practical Guide to Structured Systems Design. Englewood Cliffs, NJ:Yourdon Press.Parikh, G., and N. Zvegintzov, eds. 1983. Tutorial on Software Maintenance. Los Alamitos, CA: IEEEComputer Society Press.Parikh, Girish. 1986. Handbook of Software Maintenance. New York, NY: John Wiley & Sons.Parnas, David L. 1972. «On the Criteria to Be Used in Decomposing Systems into Modules.»Communications of the ACM 5, no. 12 (December): 1053–58.Parnas, David L. 1976. «On the Design and Development of Program Families.» IEEE Transactionson Software Engineering SE-2, 1 (March): 1–9.Parnas, David L. 1979. «Designing Software for Ease of Extension and Contraction.» IEEE Trans-actions on Software Engineering SE-5, no. 2 (March): 128–38.Parnas, David L. 1999. ACM Fellow Profile: David Lorge Parnas,» ACM Software Engineering Notes,May 1999, 10–14.Parnas, David L., and Paul C. Clements. 1986. «A Rational Design Process: How and Why to FakeIt.» IEEE Transactions on Software Engineering SE-12, no. 2 (February): 251–57.Parnas, David L., Paul C. Clements, and D. M. Weiss. 1985. «The Modular Structure of ComplexSystems.» IEEE Transactions on Software Engineering SE-11, no. 3 (March): 259–66.Perrott, Pamela. 2004. Private communication.Peters, L. J., and L. L. Tripp. 1976. «Is Software Design Wicked» Datamation, Vol. 22, No. 5 (May1976), 127–136.Peters, Lawrence J. 1981. Handbook of Software Design: Methods and Techniques. New York, NY:Yourdon Press.Peters, Lawrence J., and Leonard L. Tripp. 1977. «Comparing Software Design Methodologies.»Datamation, November, 89– 94.Peters, Tom. 1987. Thriving on Chaos: Handbook for a Management Revolution. New York, NY:Knopf.Petroski, Henry. 1994. Design Paradigms: Case Histories of Error and Judgment in Engineering.Cambridge, U.K.: Cambridge University Press.Pietrasanta, Alfred M. 1990. «Alfred M. Pietrasanta on Improving the Software Process.» SoftwareEngineering: Tools, Techniques, Practices 1, no. 1 (May/ June): 29–34. Библиография857Pietrasanta, Alfred M. 1991a. «A Strategy for Software Process Improvement.» Ninth Annual PacificNorthwest Software Quality Conference, October 7–8, 1991. Oregon Convention Center, Portland, ORPietrasanta, Alfred M. 1991b. «Implementing Software Engineering in IBM.» Keynote address. NinthAnnual Pacific Northwest Software Quality Conference, October 7– 8, 1991. Oregon ConventionCenter, Portland, OR.Pigoski, Thomas M. 1997. Practical Software Maintenance. New York, NY: John Wiley & Sons.Pirsig, Robert M. 1974. Zen and the Art of Motorcycle Maintenance: An Inquiry into Values. WilliamMorrow.Plauger, P. J. 1988. «A Designer’s Bibliography.» Computer Language, July, 17–22.Plauger, P. J. 1993. Programming on Purpose: Essays on Software Design. New York, NY: PrenticeHall.Plum, Thomas. 1984. C Programming Guidelines. Cardiff, NJ: Plum Hall.Polya, G. 1957. How to Solve It: A New Aspect of Mathematical Method, 2d ed. Princeton, NJ: PrincetonUniversity Press.Post, Ed. 1983. «Real Programmers Don’t Use Pascal,» Datamation, July 1983, 263– 265.Prechelt, Lutz. 2000. «An Empirical Comparison of Seven Programming Languages,» IEEE Computer,October 2000, 23–29.Pressman, Roger S. 1987. Software Engineering: A Practitioner’s Approach. New York, NY: McGraw-Hill.Pressman, Roger S. 1988. Making Software Engineering Happen: A Guide for Instituting theTechnology. Englewood Cliffs, NJ: Prentice Hall.Putnam, Lawrence H. 2000. «Familiar Metric Management – Effort, Development Time, and DefectsInteract.» Downloadable from www.qsm.com.Putnam, Lawrence H., and Ware Myers. 1992. Measures for Excellence: Reliable Software On Time,Within Budget. Englewood Cliffs, NJ: Yourdon Press, 1992.Putnam, Lawrence H., and Ware Myers. 1997. Industrial Strength Software: Effective ManagementUsing Measurement. Washington, DC: IEEE Computer Society Press.Putnam, Lawrence H., and Ware Myers. 2000. «What We Have Learned.» Downloadable fromwww.qsm.com, June 2000.Raghavan, Sridhar A., and Donald R. Chand. 1989. «Diffusing Software-Engineering Methods.»IEEE Software, July, 81–90.Ramsey, H. Rudy, Michael E. Atwood, and James R. Van Doren. 1983. «Flowcharts Versus ProgramDesign Languages: An Experimental Comparison.» Communications of the ACM 26, no. 6 (June):445–49.Ratliff, Wayne. 1987. Interview in Solution System.Raymond, E. S. 2000. «The Cathedral and the Bazaar,» www.catb.org/esr/writings/cathedral-bazaar.Raymond, Eric S. 2004. The Art of Unix Programming. Boston, MA: Addison-Wesley.Rees, Michael J. 1982. «Automatic Assessment Aids for Pascal Programs.» ACM Sigplan Notices 17,no. 10 (October): 33–42.Reifer, Donald. 2002. «How to Get the Most Out of Extreme Programming/Agile Methods,»Proceedings, XP/Agile Universe 2002. New York, NY: Springer; 185–196.Reingold, Edward M., and Wilfred J. Hansen. 1983. Data Structures. Boston, MA: Little, Brown.Rettig, Marc. 1991. «Testing Made Palatable.» Communications of the ACM 34, no. 5 (May): 25–29.Riel, Arthur J. 1996. Object-Oriented Design Heuristics. Reading, MA: Addison-Wesley.Rittel, Horst, and Melvin Webber. 1973. «Dilemmas in a General Theory of Planning.» Policy Sciences4: 155–69. 858БиблиографияRobertson, Suzanne, and James Robertson, 1999. Mastering the Requirements Process. Reading,MA: Addison-Wesley.Rogers, Everett M. 1995. Diffusion of Innovations, 4th ed. New York, NY: The Free Press.Rombach, H. Dieter. 1990. «Design Measurements: Some Lessons Learned.» IEEE Software, March,17–25.Rubin, Frank. 1987. «‘GOTO Considered Harmful’ Considered Harmful.» Letter to the editor.Communications of the ACM 30, no. 3 (March): 195–96. Follow-up letters in 30, no. 5 (May 1987):351–55; 30, no. 6 (June 1987): 475–78; 30, no. 7 (July 1987): 632–34; 30, no. 8 (August 1987):659–62; 30, no. 12 (December 1987): 997, 1085.Sackman, H., W. J. Erikson, and E. E. Grant. 1968. «Exploratory Experimental Studies Comparing Online and Offline Programming Performance.» Communications of the ACM 11, no. 1 (January): 3–11.Schneider, G. Michael, Johnny Martin, and W. T. Tsai. 1992. «An Experimental Study of FaultDetection in User Requirements Documents,» ACM Transactions on Software Engineering andMethodology, vol 1, no. 2, 188–204.Schulmeyer, G. Gordon. 1990. Zero Defect Software. New York, NY: McGraw-Hill.Sedgewick, Robert. 1997. Algorithms in C, Parts 1-4, 3d ed. Boston, MA: Addison-Wesley.Sedgewick, Robert. 2001. Algorithms in C, Part 5, 3d ed. Boston, MA: Addison-Wesley.Sedgewick, Robert. 1998. Algorithms in C++, Parts 1-4, 3d ed. Boston, MA: Addison-Wesley.Sedgewick, Robert. 2002. Algorithms in C++, Part 5, 3d ed. Boston, MA: Addison-Wesley.Sedgewick, Robert. 2002. Algorithms in Java, Parts 1-4, 3d ed. Boston, MA: Addison-Wesley.Sedgewick, Robert. 2003. Algorithms in Java, Part 5, 3d ed. Boston, MA: Addison-Wesley.SEI 1995. The Capability Maturity Model: Guidelines for Improving the Software Process, SoftwareEngineering Institute, Reading, MA: Addison-Wesley, 1995.SEI, 2003. «Process Maturity Profile: Software CMM®, CBA IPI and SPA Appraisal Results: 2002Year End Update,» Software Engineering Institute, April 2003.Selby, Richard W., and Victor R. Basili. 1991. «Analyzing Error-Prone System Structure.» IEEETransactions on Software Engineering SE-17, no. 2 (February): 141–52.SEN 1990. «Subsection on Telephone Systems,» Software Engineering Notes, April 1990, 11–14.Shalloway, Alan, and James R. Trott. 2002. Design Patterns Explained. Boston, MA: Addison-Wesley.Sheil, B. A. 1981. «The Psychological Study of Programming.» Computing Surveys 13, no. 1 (March):101–20.Shen, Vincent Y., et al. 1985. «Identifying Error-Prone Software—An Empirical Study.» IEEETransactions on Software Engineering SE-11, no. 4 (April): 317–24.Sheppard, S. B., et al. 1978. «Predicting Programmers’ Ability to Modify Software.» TR 78-388100-3, General Electric Company, May.Sheppard, S. B., et al. 1979. «Modern Coding Practices and Programmer Performance.» IEEEComputer 12, no. 12 (December): 41–49.Shepperd, M., and D. Ince. 1989. «Metrics, Outlier Analysis and the Software Design Process.»Information and Software Technology 31, no. 2 (March): 91–98.Shirazi, Jack. 2000. Java Performance Tuning. Sebastopol, CA: O’Reilly & Associates.Shlaer, Sally, and Stephen J. Mellor. 1988. Object Oriented Systems Analysis—Modeling the Worldin Data. Englewood Cliffs, NJ: Prentice Hall.Shneiderman, Ben, and Richard Mayer. 1979. «Syntactic/Semantic Interactions in ProgrammerBehavior: A Model and Experimental Results.» International Journal of Computer and InformationSciences 8, no. 3: 219–38. Библиография859Shneiderman, Ben. 1976. «Exploratory Experiments in Programmer Behavior.» International Journalof Computing and Information Science 5: 123–43.Shneiderman, Ben. 1980. Software Psychology: Human Factors in Computer and InformationSystems. Cambridge, MA: Winthrop.Shneiderman, Ben. 1987. Designing the User Interface: Strategies for Effective Human-ComputerInteraction. Reading, MA: Addison-Wesley.Shull, et al. 2002. «What We Have Learned About Fighting Defects,» Proceedings, Metrics 2002.IEEE; 249–258.Simon, Herbert. 1996. The Sciences of the Artificial, 3d ed. Cambridge, MA: MIT Press.Simon, Herbert. The Shape of Automation for Men and Management. Harper and Row, 1965.Simonyi, Charles, and Martin Heller. 1991. «The Hungarian Revolution.» BYTE, August, 131–38.Smith, Connie U., and Lloyd G. Williams. 2002. Performance Solutions: A Practical Guide to CreatingResponsive, Scalable Software. Boston, MA: Addison-Wesley.Software Productivity Consortium. 1989. Ada Quality and Style: Guidelines for Professional Progra-mmers. New York, NY: Van Nostrand Reinhold.Soloway, Elliot, and Kate Ehrlich. 1984. «Empirical Studies of Programming Knowledge.» IEEETransactions on Software Engineering SE-10, no. 5 (September): 595–609.Soloway, Elliot, and Sitharama Iyengar, eds. 1986. Empirical Studies of Programmers. Norwood,NJ: Ablex.Soloway, Elliot, Jeffrey Bonar, and Kate Ehrlich. 1983. «Cognitive Strategies and Looping Constructs:An Empirical Study.» Communications of the ACM 26, no. 11 (November): 853–60.Solution Systems. 1987. World-Class Programmers’ Editing Techniques: Interviews with SevenProgrammers. South Weymouth, MA: Solution Systems.Sommerville, Ian. 1989. Software Engineering, 3d ed. Reading, MA: Addison-Wesley.Spier, Michael J. 1976. «Software Malpractice—A Distasteful Experience.» Software—Practice andExperience 6: 293–99.Spinellis, Diomidis. 2003. Code Reading: The Open Source Perspective. Boston, MA: Addison-Wesley.SPMN. 1998. Little Book of Configuration Management. Arlington, VA; Software Program ManagersNetwork.Starr, Daniel. 2003. «What Supports the Roof?» Software Development. July 2003, 38–41.Stephens, Matt. 2003. «Emergent Design vs. Early Prototyping,» May 26, 2003, www.softwarereality.com/design/early_prototyping.jsp.Stevens, Scott M. 1989. «Intelligent Interactive Video Simulation of a Code Inspection.» Communi-cations of the ACM 32, no. 7 (July): 832–43.Stevens, W., G. Myers, and L. Constantine. 1974. «Structured Design.» IBM Systems Journal 13, no.2 (May): 115–39.Stevens, Wayne. 1981. Using Structured Design. New York, NY: John Wiley & Sons.Stroustrup, Bjarne. 1997. The C++ Programming Language, 3d ed. Reading, MA: Addison-Wesley.Strunk, William, and E. B. White. 2000. Elements of Style, 4th ed. Pearson.Sun Microsystems, Inc. 2000. «How to Write Doc Comments for the Javadoc Tool,» 2000. Available from http://java.sun.com/j2se/javadoc/writingdoccomments/.Sutter, Herb. 2000. Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions.Boston, MA: Addison-Wesley.Tackett, Buford D., III, and Buddy Van Doren. 1999. «Process Control for Error Free Software: ASoftware Success Story,» IEEE Software, May 1999. 860БиблиографияTenner, Edward. 1997. Why Things Bite Back: Technology and the Revenge of UnintendedConsequences. Vintage Books.Tenny, Ted. 1988. «Program Readability: Procedures versus Comments.» IEEE Transactions onSoftware Engineering SE-14, no. 9 (September): 1271–79.Thayer, Richard H., ed. 1990. Tutorial: Software Engineering Project Management. Los Alamitos,CA: IEEE Computer Society Press.Thimbleby, Harold. 1988. «Delaying Commitment.» IEEE Software, May, 78–86.Thomas, Dave, and Andy Hunt. 2002. «Mock Objects,» IEEE Software, May/June 2002.Thomas, Edward J., and Paul W. Oman. 1990. «A Bibliography of Programming Style.» ACM SigplanNotices 25, no. 2 (February): 7–16.Thomas, Richard A. 1984. «Using Comments to Aid Program Maintenance.» BYTE, May, 415–22.Tripp, Leonard L., William F. Struck, and Bryan K. Pflug. 1991. «The Application of Multiple TeamInspections on a Safety-Critical Software Standard,» Proceedings of the 4th Software EngineeringStandards Application Workshop, Los Alamitos, CA: IEEE Computer Society Press.U.S. Department of Labor. 1990. «The 1990– 91 Job Outlook in Brief.» Occupational OutlookQuarterly, Spring. U.S. Government Printing Office. Document 1990-282-086/20007.Valett, J., and F. E. McGarry. 1989. «A Summary of Software Measurement Experiences in the SoftwareEngineering Laboratory.» Journal of Systems and Software 9, no. 2 (February): 137–48.Van Genuchten, Michiel. 1991. «Why Is Software Late? An Empirical Study of Reasons for Delay inSoftware Development.» IEEE Transactions on Software Engineering SE-17, no. 6 (June): 582–90.Van Tassel, Dennie. 1978. Program Style, Design, Efficiency, Debugging, and Testing, 2d ed. EnglewoodCliffs, NJ: Prentice Hall.Vaughn-Nichols, Steven. 2003. «Building Better Software with Better Tools,» IEEE Computer,September 2003, 12–14.Vermeulen, Allan, et al. 2000. The Elements of Java Style. Cambridge University Press.Vessey, Iris, Sirkka L. Jarvenpaa, and Noam Tractinsky. 1992. «Evaluation of Vendor Products: CASETools as Methodological Companions.» Communications of the ACM 35, no. 4 (April): 91–105.Vessey, Iris. 1986. «Expertise in Debugging Computer Programs: An Analysis of the Content ofVerbal Protocols.» IEEE Transactions on Systems, Man, and Cybernetics SMC-16, no. 5 (September/October): 621–37.Votta, Lawrence G., et al. 1991. «Investigating the Application of Capture-Recapture Techniques to Requirements and Design Reviews.» Proceedings of the Sixteenth Annual Software EngineeringWorkshop, December 4–5, 1991. Greenbelt, MD: Goddard Space Flight Center. Document SEL-91-006.Walston, C. E., and C. P. Felix. 1977. «A Method of Programming Measurement and Estimation.»IBM Systems Journal 16, no. 1: 54–73.Ward, Robert. 1989. A Programmer’s Introduction to Debugging C. Lawrence, KS: R & D Publications.Ward, William T. 1989. «Software Defect Prevention Using McCabe’s Complexity Metric.» Hewlett-Packard Journal, April, 64–68.Webster, Dallas E. 1988. «Mapping the Design Information Representation Terrain.» IEEE Computer,December, 8–23.Weeks, Kevin. 1992. «Is Your Code Done Yet?» Computer Language, April, 63–72.Weiland, Richard J. 1983. The Programmer’s Craft: Program Construction, Computer Architecture,and Data Management. Reston, VA: Reston Publishing.Weinberg, Gerald M. 1983. «Kill That Code!» Infosystems, August, 48–49. 1   ...   96   97   98   99   100   101   102   103   104


1   ...   36   37   38   39   40   41   42   43   ...   104

ГЛАВА 13 Нестандартные типы данных
319
Пример кода традиционной вставки в список нового узла (C++)
void InsertLink(
Node *currentNode,
Node *insertNode
) {
// добавляем “insertNode” после “currentNode”
insertNode->next = currentNode->next;
insertNode->previous = currentNode;
if ( currentNode->next != NULL ) {
Эта строка излишне сложна.
currentNode->next->previous = insertNode;
}
currentNode->next = insertNode;
}
Этот традиционный код добавления нового узла в связный список излишне сло- жен для понимания. В добавлении элемента задействованы три объекта: текущий узел, узел, в данный момент следующий за текущим, и узел, который надо вста- вить между ними. Однако в коде явно упомянуты только два объекта:
insertNode и
currentNode. Из-за этого вам придется запомнить, что currentNode->next тоже уча- ствует в алгоритме. Если вы попробуете изобразить диаграммой, что происходит,
не используя элемент, изначально следующий за
currentNode, у вас получится что- то вроде этого:
Гораздо лучшая диаграмма содержит все три объекта. Она может выглядеть так:
Вот пример кода, который явно упоминает все три объекта, участвующих в алго- ритме:
Пример более читабельного кода для вставки узла (C++)
void InsertLink(
Node *startNode,
Node *newMiddleNode
) {
// вставляем “newMiddleNode” между “startNode” и “followingNode”
Node *followingNode = startNode->next;
newMiddleNode->next = followingNode;
newMiddleNode->previous = startNode;
if ( followingNode != NULL ) {
followingNode->previous = newMiddleNode;
}
startNode->next = newMiddleNode;
}
Этот код содержит одну дополнительную строку, но без участия выражения
current-
Node->next->previous из первого фрагмента этот пример легче для понимания.
>

320
ЧАСТЬ III Переменные
Упрощайте сложные выражения с указателями Сложные выражения с ис- пользованием указателей тяжело читать. Если в вашем коде есть выражения вро- де
p->q->r->s.data, подумайте о том человеке, которому придется это читать. Вот особенно вопиющий пример:
Пример сложного для понимания
выражения с указателем (C++)
for ( rateIndex = 0; rateIndex < numRates; rateIndex++ ) {
netRate[ rateIndex ] = baseRate[ rateIndex ] * rates->discounts->factors->net;
}
Подобные выражения заставляют разбираться в коде, а не читать его. Если в ва- шей программе есть сложное выражение, присвойте его понятно названной пе- ременной, чтобы прояснить смысл операции. Вот улучшенная версия примера:
Пример упрощения сложного выражения с указателем (C++)
quantityDiscount = rates->discounts->factors->net;
for ( rateIndex = 0; rateIndex < numRates; rateIndex++ ) {
netRate[ rateIndex ] = baseRate[ rateIndex ] * quantityDiscount;
}
Это упрощение не только позволяет увеличить удобочитаемость, но, возможно, и повысить производительность, упростив операцию с указателем внутри цикла. Но,
как обычно, улучшение производительности надо измерить до того, как делать на это крупные ставки.
Нарисуйте картинку Описание указателей в коде про- граммы может сбивать с толку. Обычно помогает картинка.
Например, изображение задачи по вставке элемента в связ- ный список может выглядеть так (рис. 13-2):
Рис. 13-2. Пример рисунка, помогающего осмыслить шаги, необходимые
для изменения связей между элементами
Удаляйте указатели в связных списках в правильном порядке Обычной проблемой в работе с динамически созданными связными списками является освобождение сначала первого указателя, после чего становится невозможно
Перекрестная ссылка Такие диаграммы, как на рис. 13-2,
могут стать частью внешней документации вашей програм- мы. О хорошей практике доку- ментирования см. главу 32.


ГЛАВА 13 Нестандартные типы данных
321
получить указатель на следующий узел списка. Чтобы избежать этой проблемы,
перед удалением текущего элемента убедитесь, что у вас есть указатель на следу- ющий элемент списка.
Выделите «запасной парашют» памяти Если в программе используется ди- намическая память, необходимо избежать проблемы ее внезапной нехватки, при- водящей к исчезновению пользовательских данных на бескрайних просторах оперативной памяти. Один из способов дать вашей программе запас прочности
— заранее выделить «парашют» памяти. Определите, какой объем памяти нужен программе для сохранения работы, освобождения ресурсов и аккуратного завер- шения. Зарезервируйте эту память в начале работы программы как запасной па- рашют и оставьте ее в покое. Когда памяти станет не хватать, раскройте резерв- ный парашют — освободите эту память и завершите работу программы.
Уничтожайте мусор Ошибки указателей сложно отсле- живать, потому что момент времени, когда память, адресу- емая указателем, станет недействительной, не определен.
Иногда содержимое памяти после освобождения указателя долго еще выглядит корректным. В другой раз ее содержи- мое изменится сразу.
Вы можете избежать ошибок с освобожденными указателями, записывая мусор в блоки памяти прямо перед их освобождением. Если вы используете методы дос- тупа, то это, как и многие другие операции, можно делать автоматически. В C++
при каждом удалении указателя можно делать так:
Пример принудительной записи мусорных данных в освобождаемую память (C++)
memset( pointer, GARBAGE_DATA, MemoryBlockSize( pointer ) );
delete pointer;
Естественно, эта технология требует поддержки списка размеров памяти, выде- ленной для указателей, которые можно было бы получить функцией
MemoryBlock-
Size(). Мы обсудим это позднее.
Устанавливайте указатели null при их удалении или освобождении Из- вестный тип ошибок указателей — это «висячий указатель» (dangling pointer), т. е.
обращение к нему после вызова функций
delete или free. Одна из причин, по ко- торым ошибки в указателях так сложно обнаружить, в том, что иногда симптомы ошибки никак не проявляются. Записывая в указатели пустое значение после их освобождения, вы не измените факт чтения данных, адресуемых висячим указа- телем. Но вы добьетесь того, что запись данных по этому адресу приведет к ошибке.
Возможно, это будет ужасная, катастрофическая ошибка, но по крайней мере ее обнаружите вы, а не кто-то другой.
Код, предшествующий операции
delete в предыдущем примере, можно дополнить,
чтобы обрабатывать и эту ситуацию:
Пример установки указателя в null после его удаления (C++)
memset( pointer, GARBAGE_DATA, MemoryBlockSize( pointer ) );
delete pointer;
pointer = NULL;
Дополнительные сведения От- личное обсуждение безопасных подходов к обработке указате- лей в языке C см. в книге «Wri- ting Solid Code» (Maguire, 1993).


322
ЧАСТЬ III Переменные
Проверяйте корректность указателя перед его удалением Один из луч- ших способов обрушить программу — вызвать функции
delete() или free() для ука- зателя, который уже был освобожден. Увы, лишь немногие языки обнаруживают такой тип ошибок.
Если вы присваиваете освобождаемым указателям пустое значение, то перед ис- пользованием или повторным удалением указателя вы сможете проверить его на равенство
null. Разумеется, если вы не устанавливаете в null освобождаемые ука- затели, у вас такой возможности не будет. В связи с этим можно предложить сле- дующее дополнение к коду удаления указателя:
Пример проверки утверждения о неравенстве
указателя null перед его удалением (C++)
ASSERT( pointer != NULL, “Attempting to delete null pointer.” );
memset( pointer, GARBAGE_DATA, MemoryBlockSize( pointer ) );
delete pointer;
pointer = NULL;
Отслеживайте распределение памяти для указателей Ведите список ука- зателей, для которых была выделена память. Это позволит вам проверить, нахо- дится ли указатель в этом списке перед его освобождением. Вот как для этих це- лей может быть изменен код удаления указателя:
Пример проверки, выделялась ли память для указателя (C++)
ASSERT( pointer != NULL, “Attempting to delete null pointer.” );
if ( IsPointerInList( pointer ) ) {
memset( pointer, GARBAGE_DATA, MemoryBlockSize( pointer ) );
RemovePointerFromList( pointer );
delete pointer;
pointer = NULL;
}
else {
ASSERT( FALSE, “Attempting to delete unallocated pointer.” );
}
Напишите методы-оболочки, чтобы централизовать стратегию борь-
бы с ошибками в указателях Как видно из этого примера, каждый вызов опе- раторов
new и delete может сопровождаться достаточно большим количеством дополнительного кода. Некоторые технологии, описанные в этом разделе, явля- ются взаимоисключающими или избыточными, и не хотелось бы использовать несколько конфликтующих стратегий в одной программе. Например, вам не надо создавать и проверять обязательные признаки, если вы поддерживаете собствен- ный список действительных указателей.
Вы можете минимизировать избыточность в программе и уменьшить вероятность ошибок, написав методы-оболочки для общих операций с указателями. В C++ вы могли бы использовать следующие методы:

SAFE_NEW Вызывает new для выделения памяти, добавляет указатель в спи- сок задействованных указателей и возвращает вновь созданный указатель вы-

ГЛАВА 13 Нестандартные типы данных
323
зывающей стороне. Он может также проверить, что оператор new не вернул
null (ошибка нехватки памяти). Поскольку это надо сделать только единожды в этом месте, упрощается процесс обработки ошибок в других частях вашей программы.

SAFE_DELETE Проверяет, находится ли переданный ему указатель в списке действительных указателей. Если он там есть, метод записывает мусор в адре- суемую им память, удаляет указатель из списка, вызывает C++-оператор
delete
для освобождения памяти и устанавливает указатель в
null. Если указатель не найден в списке,
SAFE_DELETE выводит диагностическое сообщение и преры- вает программу.
Метод
SAFE_DELETE, реализованный в виде макроса, может выглядеть так:
Пример добавления оболочки для кода удаления указателя (C++)
#define SAFE_DELETE( pointer ) { \
ASSERT( pointer != NULL, “Attempting to delete null pointer.”); \
if ( IsPointerInList( pointer ) ) { \
memset( pointer, GARBAGE_DATA, MemoryBlockSize( pointer ) ); \
RemovePointerFromList( pointer ); \
delete pointer; \
pointer = NULL; \
} \
else { \
ASSERT( FALSE, “Attempting to delete unallocated pointer.” ); \
} \
}
В C++ этот метод будет освобождать единичные указатели,
поэтому вам придется создать похожий макрос
SAFE_DELE-
TE_ARRAY для удаления массивов.
Централизовав управление памятью в этих двух методах, вы также сможете менять поведение
SAFE_NEW и SAFE_DELETE
в отладочной и промышленных версиях продукта. Напри- мер, обнаружив попытку освободить пустой указатель в период разработки,
SAFE_
DELETE может остановить программу. Но если это происходит во время эксплуа- тации, он может просто записать ошибку в журнал и продолжить выполнение.
Вы легко сможете адаптировать эту схему для функций
calloc и free в языке C,
а также для других языков, использующих указатели.
Используйте технологию, не основанную на указателях Указатели в це- лом сложнее для понимания, они подвержены ошибкам и приводят к созданию машинно-зависимого, непереносимого кода. Если вы можете придумать разумную альтернативу указателям, избавьте себя от головной боли и возьмите ее за основу.
Указатели в C++
Язык C++ добавил специфические тонкости при работе с указателями и ссылка- ми. В следующих подразделах описаны основные принципы, применяемые в ра- боте с указателями на C++.
Перекрестная ссылка О планах по удалению отладочного кода см. подраздел «Запланируйте удаление отладочных средств»
раздела 8.6.


324
ЧАСТЬ III Переменные
Осознайте различие между указателями и ссылка-
ми В C++ и указатели (*), и ссылки (&) косвенно ссылают- ся на объект. Для непосвященных единственной, чисто кос- метической разницей между ними будет способ обращения к полю:
object->field или object.field. Наиболее значительным различием является то, что ссылка обязана всегда ссылаться на объект, тогда как указатель может быть равен
null. Кроме того, после инициализации ссылки нельзя изменить то, куда она ссылается.
Используйте указатели для передачи параметров «по ссылке» и констан-
тные ссылки для передачи параметров «по значению» По умолчанию C++
передает в методы аргументы по значению, а не по ссылке. Когда объект переда- ется по значению, C++ создает копию объекта, и при передаче объекта вызываю- щей программе вновь создается копия. Для больших объектов такое копирование может съедать много времени и ресурсов. Следовательно, при передаче объектов в метод вы обычно стараетесь избегать копирования объектов, а это означает, что вы хотите передавать их по ссылке, а не по значению.
Однако иногда хотелось бы использовать
семантику передачи по значению (т. е.
передаваемый объект должен остаться неизменным) и
реализацию передачи па- раметра по ссылке (т. е. передавать сам объект, а не его копию).
В C++ решением этой проблемы является применение указателей для передачи по ссылке, и — как ни странно может звучать — «
константных ссылок» для пе- редачи по значению! Приведем пример:
Пример передачи параметров по значению и по ссылке (C++)
void SomeRoutine(
const LARGE_OBJECT &nonmodifiableObject,
LARGE_OBJECT *modifiableObject
);
Дополнительным преимуществом этого подхода является синтаксическое разли- чие между изменяемыми и неизменяемыми объектами в вызванном методе.
В изменяемых объектах ссылка на элементы будет осуществляться с помощью но- тации
object->member, тогда как в неизменяемых будет использоваться нотация
object . member.
Недостаток этого подхода состоит в необходимости постоянного применения кон- стантных ссылок. Хорошим тоном считается использование модификатора
const
везде, где это возможно (Meyers, 1998). Поэтому в своем коде вы сможете объяв- лять передаваемые по значению параметры как константные ссылки. В библио- течном коде и других неподконтрольных вам местах вы столкнетесь с проблемой константных параметров. Компромиссной позицией будет все же задавать пара- метры, предназначенные только для чтения, с помощью ссылок, но не объявлять их константными. При этом подходе вы не в полной мере реализуете преимуще- ство проверки компилятором попыток модификации неизменяемых аргументов метода, однако по крайней мере предоставляете возможность визуального разли- чения
object->member и object. member.
Дополнительные сведения Мно- жество других советов по при- менению указателей в C++ см.
в «Effective C++», 2d ed. (Meyers,
1998) и «More Effective C++»
(Meyers, 1996).