Руководство по стилю программирования и конструированию по
Скачать 7.6 Mb.
|
ГЛАВА 9 Процесс программирования с псевдокодом 225 쐽 Проверьте общее качество конструкции. Убедитесь, что метод выполняет един- ственную задачу и делает это хорошо, имея в виду его слабое сопряжение с другими методами и проектирование в соответствии с методикой защитного программирования (см. главу 7). 쐽 Проверьте переменные метода: корректность их именования, неиспользуемые объекты, необъявленные переменные, неверно инициализированные объек- ты и т. д. (см. главы 10–13). 쐽 Проверьте логику метода. Проанализируйте наличие ошибок занижения/завы- шения на 1, некорректной вложенности и утечки ресурсов (см. главы 14–19). 쐽 Проверьте форматирование метода. Убедитесь в корректном использовании пробелов для структурирования метода, выражений и списка параметров (см. главу 31). 쐽 Проверьте документирование метода. Убедитесь в корректности псевдокода, переведенного в комментарии. Проверьте описание алгоритма, документиро- вание интерфейса, неочевидных зависимостей и нестандартных подходов (см. главу 32). 쐽 Удалите лишние комментарии. Иногда комментарии, полученные из псевдо- кода, являются избыточными, особенно когда ППП применяется рекурсивно и комментарии лишь описывают вызов метода, назначение которого и так понятно из его имени. Повторите нужное число раз Если качество метода неудовлетворительное, вернитесь к псевдокоду. Создание высококачественного ПО — итеративный процесс, так что без колебаний повто- ряйте весь цикл конструирования вновь и вновь. 9.4. Альтернативы ППП Для меня ППП — идеальная методика создания классов и методов. Другие специ- алисты рекомендуют иные подходы. Вы можете применять их как альтернативу или дополнение ППП. Разработка с изначальными тестами Это популярный стиль разработки, при котором тестовые задания пишутся до самого кода (см. раздел 22.2). Есть хорошая книга Кента Бека на эту тему — «Test-Driven Development: By Example» (Beck, 2003). Рефакторинг Рефакторинг — это подход к разработке с усовершенствовани- ем кода посредством последовательности семантически корректных преобразо- ваний. Программисты пользуются шаблонами плохого кода или «запахами» (smells) для выявления разделов кода, подлежащих усовершенствованию. Этот подход по- дробно описан в главе 24, а также в книге Мартина Фаулера «Refactoring: Improving the Design of Existing Code» (Fowler, 1999). Проектирование по контракту Это подход предполагает, что каждый метод имеет пред- и постусловия (см. раздел 8.2). Лучший источник информации на эту тему — книга Бертрана Мейера «Object-Oriented Software Construction» (Meyer, 1997). 226 ЧАСТЬ II Высококачественный код Бессистемное программирование Некоторые программисты лепят программу как попало, а не используют тот или иной систематический подход, например ППП. Если вы не понимаете, что делать дальше, это признак того, что надо переходить на ППП. Не было ли у вас такого, чтобы вы забывали написать часть класса или метода? Вряд ли такое могло случиться при применении ППП. Если вы глядите на экран и не знаете, с чего начать, пора начать ППП, который сделает вашу про- граммистскую долю проще. Контрольный список: Процесс Программирования с Псевдокодом Проверили ли вы, что удовлетворяются предваритель- ные условия? Определена ли проблема, которую решает класс? Достаточно ли понятна высокоуровневая конструкция, чтобы дать классам и методам адекватные имена? Продумали ли вы тестирование класса и каждого из его методов? Рассматривали ли вы эффективность с позиции стабиль- ных интерфейсов и понятной реализации или с позиции соответствия ресурсам и бюджету? Проанализировали ли вы стандартные и другие библиотеки на предмет наличия подходящих методов и компонентов? Просмотрели ли вы литературу в поисках полезных алгоритмов? Проектировали ли вы каждый метод с использованием подробного псевдо- кода? Проверили ли вы псевдокод умозрительно? Легко ли его понять? Уделили ли вы внимание предупреждениям, которые указывают на необхо- димость перепроектирования (использование глобальных данных, операции, которые лучше перенести в другой класс или метод и т. д.)? Точно ли вы перевели псевдокод в код? Рекурсивно ли вы применяли ППП, разбивая методы на более мелкие при необходимости? Документировали ли вы принимаемые допущения? Убрали ли вы избыточные комментарии? Проделали ли вы несколько итераций или остановились после первой? Вполне ли вы понимаете свой код? Легко ли в нем разобраться? Ключевые моменты 쐽 Конструирование классов и методов — процесс итеративный. Особенности, замечаемые при конструировании отдельных методов, заставляют возвращаться к проектированию класса. 쐽 Написание хорошего псевдокода предполагает употребление понятного есте- ственного языка без специфических особенностей конкретного языка програм- мирования, а также формулировок на уровне намерений (описания сути кон- струкции, а не способов ее работы). http://cc2e.com/0943 Перекрестная ссылка Назначе- ние этого списка — проверить, применяете ли вы правильные методики при создании мето- дов. Контрольный список по качеству методов как таковых см. в главе 7. ГЛАВА 9 Процесс программирования с псевдокодом 227 쐽 Процесс Программирования с Псевдокодом — полезный инструмент деталь- ного проектирования, упрощающий кодирование. Псевдокод транслируется непосредственно в комментарии, гарантируя их адекватность и полезность. 쐽 Не останавливайтесь на первой придуманной вами конструкции — испробуй- те несколько подходов и выберите лучший, прежде чем писать код. 쐽 Проверяйте свою работу на каждом шаге и просите об этом других. При этом вы отловите ошибки на наименее дорогостоящем уровне, когда вы вложили в работу меньше усилий. ГЛАВА 10 Общие принципы использования переменных 229 Часть III ПЕРЕМЕННЫЕ 쐽 Глава 10. Общие принципы использования переменных 쐽 Глава 11. Сила имен переменных 쐽 Глава 12. Основные типы данных 쐽 Глава 13. Нестандартные типы данных 230 ЧАСТЬ III Переменные Г Л А В А 1 0 Общие принципы использования переменных Содержание 쐽 10.1. Что вы знаете о данных? 쐽 10.2. Грамотное объявление переменных 쐽 10.3. Принципы инициализации переменных 쐽 10.4. Область видимости 쐽 10.5. Персистентность 쐽 10.6. Время связывания 쐽 10.7. Связь между типами данных и управляющими структурами 쐽 10.8. Единственность цели каждой переменной Связанные темы 쐽 Именование переменных: глава 11 쐽 Фундаментальные типы данных: глава 12 쐽 Редкие типы данных: глава 13 쐽 Размещение объявлений данных: одноименный подраздел раздела 31.5 쐽 Документирование переменных: подраздел «Комментирование объявлений данных» раздела 32.5 Если при конструировании приходится заполнять небольшие пробелы в требо- ваниях и архитектуре, это нормально и даже желательно. Проектирование про- граммы вплоть до микроскопических деталей было бы неэффективным. В этой главе рассматривается один из низкоуровневых вопросов конструирования — использование переменных. Эта глава будет особенно полезна опытным программистам. Довольно часто мы применяем рискованные подходы, не имея полного представления об альтерна- тивах, а после используем их в силу привычки. Особый интерес для опытных программистов могут представлять разделы 10.6 и 10.8, посвященные соответствен- http://cc2e.com/1085 ГЛАВА 10 Общие принципы использования переменных 231 но времени связывания и использованию переменных только с одной целью. Если вы не знаете, насколько «опытным» программистом вы являетесь, пройдите «Тест на знание типов данных» в разделе 10.1 и узнайте. В этой главе я буду понимать под «переменными» и объекты, и встроенные типы данных, такие как целые числа и массивы. «Типами данных» я, как правило, буду называть встроенные типы данных, а просто «данными» — и встроенные типы данных, и объекты. 10.1. Что вы знаете о данных? Создавая данные, вы должны в первую очередь понять, какие именно данные вам нужны. Обширные знания о разных типах данных — важней- ший компонент в инструментарии любого программиста. Введения в типы данных в этой книге вы не найдете, но «Тест на знание данных» поможет вам определить, сколько еще вам нужно о них узнать. Тест на знание данных Поставьте себе 1 балл за каждый термин, который вам известен. Если термин ка- жется знакомым, но вы не уверены в его значении, поставьте себе 0,5 балла. Вы- полнив тест, просуммируйте баллы и оцените результат по приведенному ниже описанию. ______ abstract data type ______ literal (литерал) (абстрактный тип данных) ______ array (массив) ______ local variable (локальная переменная) ______ bitmap (растровое изображение) ______ lookup table (таблица поиска) ______ boolean variable (булева переменная) ______ member data (данные-члены) ______ B-tree (B-дерево) ______ pointer (указатель) ______ character variable ______ private (закрытый) (символьная переменная) ______ container class (класс-контейнер) ______ retroactive synapse (ретроактивный синапс) ______ double precision (двойная точность) ______ referential integrity (целостность ссылочных данных) ______ elongated stream (удлиненный поток) ______ stack (стек) ______ enumerated type (перечисление) ______ string (строка) ______ floating point (число ______ structured variable с плавающей точкой) (структурная переменная) ______ heap (куча) ______ tree (дерево) ______ index (индекс) ______ typedef (синоним типа) ______ integer (целое число) ______ union (объединение) ______ linked list (связанный список) ______ value chain (цепочка начисления стоимости) ______ named constant ______ variant (универсальный тип) (именованная константа) ______ Общее число баллов 232 ЧАСТЬ III Переменные Интерпретировать результаты можно примерно так. 0–14 Вы — начинающий программист; возможно, вы учитесь на первом курсе ин- ститута или самостоятельно изучаете свой первый язык программирования. Вы можете многое узнать, прочитав одну из книг, перечисленных в следующем подразделе. Вернувшись к этой части книги потом, вы извлечете больше пользы, потому что этот материал адресован более опытным программистам. 15–19 Вы — программист среднего уровня или опытный программист, который мно- гое забыл. Из книг, указанных чуть ниже, вы также сможете извлечь выгоду, хотя многие концепции будут вам знакомы. 20–24 Вы — эксперт в программировании. Вероятно, на вашей полке уже стоят книги, указанные ниже. 25–29 Вы знаете о типах данных больше, чем я. Может, напишете собственную книгу (пришлите мне экземпляр!)? 30–32 Вы — тщеславный мошенник. Термины «удлиненный поток», «ретроактивный синапс» и «цепочка начисления стоимости» не имеют никакого отношения к типам данных — я их выдумал! Прочитайте раздел «Профессиональная чест- ность» в главе 33! Дополнительные ресурсы Хорошими источниками информации о типах данных являются следующие книги: Cormen, H. Thomas, Charles E. Leiserson, Ronald L. Rivest. Introduction to Algorithms. New York, NY: McGraw Hill. 1990. Sedgewick, Robert. Algorithms in C++, Parts 1-4, 3d ed. Boston, MA: Addison-Wesley, 1998. Sedgewick, Robert. Algorithms in C++, Part 5, 3d ed. Boston, MA: Addison-Wesley, 2002. 10.2. Грамотное объявление переменных В этом разделе описаны способы оптимизации объявления переменных. Строго говоря, это не такая уж и крупная за- дача, и вы могли бы подумать, что она не заслуживает соб- ственного раздела. И все же создавать переменные прихо- дится очень часто, и приобретение правильных привычек поможет вам сэкономить время и исключить ненужные разочарования. Неявные объявления Некоторые языки поддерживают неявное объявление переменных. Так, если, про- граммируя на Microsoft Visual Basic, вы попытаетесь использовать необъявленную переменную, компилятор может автоматически объявить ее для вас (это зависит от параметров компилятора). Неявное объявление переменных — одна из самых опасных возможностей язы- ка. Если вы программировали на Visual Basic, то знаете, как жаль времени, потра- ченного на поиск причины неправильного значения acctNo, если в итоге обнару- живается, что вы по ошибке вызвали переменную acctNum, которая была иници- ализирована нулем. Если язык не заставляет объявлять переменные, подобную ошибку допустить очень легко. Перекрестная ссылка О форма- тировании объявлений перемен- ных см. одноименный подраз- дел раздела 31.5, а о докумен- тировании — подраздел «Ком- ментирование объявлений дан- ных» раздела 32.5. ГЛАВА 10 Общие принципы использования переменных 233 Если язык требует объявления переменных, для столкновения с данной проблемой нужно сделать две ошибки: во-первых, использовать в теле метода и acctNum, и acctNo, ну, а во-вторых, объявить в методе обе эти переменные. Такую ошибку допустить сложнее, что практически устраняет про- блему похожих имен переменных. По сути языки, требующие явного объявления переменных, заставляют более внимательно использовать данные, что является одним из важнейших достоинств таких языков. А если язык поддерживает неяв- ные объявления? Несколько советов я привел ниже. Отключите неявные объявления Некоторые компиляторы позволяют запре- тить неявные объявления. Например, в Visual Basic для этого служит директива Option Explicit On, которая заставляет объявлять все используемые переменные. Объявляйте все переменные Печатая имя новой переменной, объявите ее, даже если компилятор этого не требует. Пусть не от всех, но от некоторых ошибок это вас избавит. Используйте конвенции именования Чтобы не исполь- зовать две переменные там, где предполагается одна, задайте конвенцию употребления популярных суффиксов в именах переменных. Скажем, конвенция может требовать примене- ния директивы Option Explicit On и суффикса No. Проверяйте имена переменных Просматривайте список перекрестных ссы- лок, сгенерированный компилятором или утилитой. Многие компиляторы состав- ляют список всех переменных метода, что, например, позволяет узнать о неумыш- ленном применении двух переменных с похожими именами. Кроме того, компи- ляторы могут указывать на объявленные, но неиспользованные переменные. 10.3. Принципы инициализации переменных Неверная инициализация данных — один из самых плодородных источ- ников ошибок в программировании. Эффективные способы предотвра- щения проблем с инициализацией могут значительно ускорить отладку. При неверной инициализации проблемы объясняются тем, что переменная име- ет не то первоначальное значение, которое вы ожидаете. Это может случиться по одной из следующих причин. 쐽 Переменной не было присвоено значения. Она имеет то случайное значение, которое находилось в соответству- ющих ячейках памяти при запуске программы. 쐽 Значение переменной устарело. Когда-то переменной было присвоено значение, но оно утратило свою акту- альность. 쐽 Одним частям переменной были присвоены значения, а другим нет. Последняя причина имеет несколько вариаций. Вы можете инициализировать несколько членов объекта, но не все. Вы можете забыть выделить память и ини- циализировать «переменную», на которую указывает неинициализированный ука- затель. При этом на самом деле вы занесете некоторое значение в случайный блок памяти. Им может оказаться область памяти, содержащая данные. Им может ока- Перекрестная ссылка О стандар- тизации сокращений имен см. подраздел «Общие советы по сокращению имен» раздела 11.6. Перекрестная ссылка О тести- ровании, основанном на шабло- нах инициализации данных и их использования, см. подраздел «Тестирование, основанное на потоках данных» раздела 22.3. 234 ЧАСТЬ III Переменные заться область памяти, содержащая код. В этом блоке может находиться фрагмент ОС. Проблема с указателями может проявляться совершенно неожиданным обра- зом, изменяющимся от случая к случаю, поэтому найти такие ошибки сложнее, чем любые другие. Ниже описаны способы предотвращения проблем, связанных с инициализацией. Инициализируйте каждую переменную при ее объявлении Инициализация переменных при их объявлении — простая методика защитного программиро- вания и хорошая страховка от ошибок инициализации. Так, следующий код по- зволяет гарантировать, что инициализация массива studentGrades будет выполняться при каждом вызове метода, содержащего массив: Пример инициализации массива при его объявлении (C++) float studentGrades[ MAX_STUDENTS ] = { 0.0 }; Инициализируйте каждую переменную там, где она используется в первый раз Visual Basic и некоторые другие языки не позволяют инициализировать переменные при их объявлении. В результате код может принимать вид, при котором сначала выполняется объявление нескольких переменных, а потом эти переменные инициализируются — и то и другое про- исходит вдали от места фактического использования переменных в первый раз: Пример плохой инициализации переменных (Visual Basic) ‘ объявление всех переменных Dim accountIndex As Integer Dim total As Double Dim done As Boolean ’ инициализация всех переменных accountIndex = 0 total = 0.0 done = False ’ использование переменной accountIndex ’ использование переменной total ’ использование переменной done While Not done Лучше инициализировать каждую переменную как можно ближе к месту первого обращения к ней: Перекрестная ссылка Проверка входных параметров является еще одной формой защитного программирования (см. главу 8). ГЛАВА 10 Общие принципы использования переменных 235 Пример хорошей инициализации переменных (Visual Basic) Dim accountIndex As Integer accountIndex = 0 ’ использование переменной accountIndex Dim total As Double Переменная total объявляется и инициализируется непосредственно перед ее использованием. total = 0.0 ’ использование переменной total Dim done As Boolean Переменная done также объявляется и инициализируется непосредственно перед ее использова- нием. done = False ’ использование переменной done While Not done Второй вариант лучше первого по нескольким причинам. Пока выполнение пер- вого примера дойдет до фрагмента, в котором используется переменная done, она может оказаться измененной. Даже если при написании программы это не так, нельзя гарантировать, что этого не произойдет после нескольких ее изменений. Кроме того, в первом примере все переменные инициализируются в одном месте, из-за чего создается впечатление, что все они используются на протяжении всего метода, тогда как на самом деле переменная done вызывается только в его конце. Наконец, в ре- зультате изменений программы (которые неизбежно придется вносить, и не толь- ко при отладке) код, использующий переменную done, может оказаться заключен- ным в цикл, при этом переменную каждый раз нужно будет инициализировать за- ново. Во второй пример в этом случае придется внести лишь небольшое измене- ние. Первый пример слабее защищен от досадных ошибок инициализации. Два этих фрагмента иллюстрируют Принцип Близости: груп- пируйте связанные действия вместе. Этот принцип предпо- лагает также близость комментариев к описываемому ими коду, близость кода настройки цикла к самому циклу, груп- пировку команд в линейных участках программы и т. д. В идеальном случае сразу объявляйте и определяйте каждую переменную непосредственно перед первым обращением к ней При объявлении пере- менной вы указываете ее тип. При определении вы присваиваете ей конкретное значение. Если язык позволяет (к таким языкам относятся, например, C++ и Java), переменную следует объявлять и определять перед фрагментом, в котором она используется впервые. В идеале каждую переменную следует определять при ее объявлении: Перекрестная ссылка О группи- ровке связанных действий см. раздел 10.4. > > 236 ЧАСТЬ III Переменные Пример хорошей инициализации переменных (Java) int accountIndex = 0; // использование переменной accountIndex Переменная total инициализируется непосредственно перед ее использованием. double total = 0.0; // использование переменной total Переменная done также инициализируется непосредственно перед ее использованием. boolean done = false; // использование переменной done while ( ! done ) { Объявляйте переменные по мере возможности как final или const Объявив переменную как final в Java или const в C++, вы можете предотвратить изменение ее значения пос- ле инициализации. Ключевые слова final и const полезны для определения констант класса, исключительно входных параметров и любых ло- кальных переменных, значения которых должны оставаться неизменными после инициализации. Уделяйте особое внимание счетчикам и аккумуляторам Переменные i, j, k, sum и total часто играют роль счетчиков или аккумуляторов. Нередко програм- мисты забывают обнулить счетчик или аккумулятор перед его использованием в очередной раз. Инициализируйте данные-члены класса в его конструкторе Подобно пе- ременным метода, которые следует инициализировать при вызове каждого мето- да, данные класса следует инициализировать в его конструкторе. Если в конструк- торе выделяется память, в деструкторе ее следует освободить. Проверяйте необходимость повторной инициализации Спросите себя, нуж- но ли будет когда-нибудь инициализировать переменную повторно: например, для применения в цикле или для переустановки ее значения между вызовами метода. Если да, убедитесь, что команда инициализации входит в повторяющийся фраг- мент кода. Инициализируйте именованные константы один раз; переменные иници- ализируйте в исполняемом коде Если переменные служат для имитации име- нованных констант, вполне допустимо инициализировать их один раз при запуске программы. Инициализируйте их в методе Startup(). Истинные переменные ини- циализируйте в исполняемом коде неподалеку от места их вызова. Очень часто метод, который первоначально применялся один раз, после изменения програм- мы вызывается многократно. Переменные, которые инициализируются в методе Startup() уровня программы, не будут инициализироваться повторно. Перекрестная ссылка О группи- ровке связанных действий см. также раздел 14.2. > > |