Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по
Скачать 5.88 Mb.
|
ГЛАВА 32 Самодокументирующийся код 783 /* Следующий код нужен потому, что метод WriteData() содержит ошибку, проявляющуюся, только когда третий параметр равен 500. Значение ‘500’ ради ясности заменено на именованную константу. */ if ( blockSize == WRITEDATA_BROKEN_SIZE ) { blockSize = WRITEDATA_WORKAROUND_SIZE; } WriteData ( file, data, blockSize ); Обосновывайте нарушения хорошего стиля программирования Если вы вынуждены нарушить хороший стиль программирования, объясните причину. Благодаря этому программисты, исполненные благих намерений, узнают, что попытка улучшения вашего кода может привести к нарушению его работы, и не станут изменять его. Объяснение ясно скажет, что вы знали, что делаете, а не до# пустили небрежность — улучшите свою репутацию, если есть такая возможность! Не комментируйте хитрый код — перепишите его Вот один комментарий из проекта, в котором я принимал участие: Пример комментирования хитрого кода (C++) // ОЧЕНЬ ВАЖНОЕ ЗАМЕЧАНИЕ: // Конструктор этого класса принимает ссылку на объект UiPublication. // Объект UiPublication НЕЛЬЗЯ УНИЧТОЖАТЬ раньше объекта DatabasePublication, // иначе программу ожидает мученическая смерть. Это хороший пример одного из самых распространенных и опасных заблужде# ний, согласно которому комментарии следует использовать для документирова# ния особенно «хитрых» или «нестабильных» фрагментов кода. Данная идея обо# сновывается тем, что люди должны знать, когда им следует быть осторожными. Это плохая идея. Комментирование хитрого кода — как раз то, чего делать не следует. Коммента# рии не могут спасти сложный код. Как призывают Керниган и Плоджер, «не доку# ментируйте плохой код — перепишите его» (Kernighan and Plauger, 1978). Исследования показали, что фрагменты исходного кода с большим чис# лом комментариев обычно включали максимальное число дефектов и отнимали бóльшую долю ресурсов, уходивших на разработку ПО (Lind and Vairavan, 1989). Ученые предположили, что программисты склонны щедро ком# ментировать сложный код. Когда кто#то говорит: «Это по#настоящему хитрый код,» — я слышу: «Это по#настоящему плохой код». Если вам что#то кажется хитрым, для кого# то другого это окажется непонятным. Даже если что#то не кажется вам очень уж хитрым, другой человек, который не сталкивался с этим трюком, сочтет его очень замысловатым. Если вы спрашиваете себя: «Хитро ли это?» — это хит# ро. Всегда можно найти несложный вариант решения проблемы, поэтому пере# пишите код. Сделайте его несколько хорошим, чтобы нужда в комментариях во# обще отпала, а затем прокомментируйте код, чтобы сделать его еще лучше. 784 ЧАСТЬ VII Мастерство программирования Этот совет относится преимущественно к коду, который вы пишете впервые. Если вы сопровождаете программу и не имеете возможности переписывать плохой код, комментирование хитрых фрагментов — хороший подход. Комментирование объявлений данных Комментарий объявления переменной описывает аспекты переменной, которые невозможно выразить в ее имени. Тща# тельно документировать данные важно: по крайней мере одна компания, изучавшая собственные методики, пришла к вы# воду, что комментировать данные даже важнее, чем процес# сы, в которых эти данные используются (SDC в Glass, 1982). Указывайте в комментариях единицы измерения численных величин Ес# ли число представляет длину, укажите единицы представления: дюймы, футы, метры или километры. Если речь идет о времени, поясните, в чем оно выражено: в се# кундах, прошедших с 1 января 1980 года, миллисекундах, прошедших с момента запуска программы, или как#то иначе. Если это координаты, напишите, что они представляют (ширину, долготу и высоту) и в чем они выражены (в радианах или градусах), укажите систему координат и т. д. Не предполагайте, что единицы из# мерения очевидны — для нового программиста они такими не будут. Для кого#то, кто работает над другой частью системы, они такими не будут. После значитель# ного изменения программы они тоже очевидными не будут. Единицы измерения часто следует указывать в именах переменных, а не в ком# ментариях. Например, выражение вида distanceToSurface = marsLanderAltitude вы# глядит корректным, тогда как distanceToSurfaceInMeters = marsLanderAltitudeInFeet ясно указывает на ошибку. Указывайте в комментариях диапазоны допустимых значений численных величин Если предполагается, что значение переменной должно попадать в некоторый диа# пазон, задокументируйте это. Одной из мощных возможно# стей языка Ada была возможность указания диапазона до# пустимых значений численной переменной. Если ваш язык не поддерживает такую возможность (а большинство язы# ков ее не поддерживает), используйте для документирования диапазона ожидае# мых значений комментарии. Например, если переменная представляет денежную сумму в долларах, укажите, что в вашем случае она должна находиться в пределах от 1 до 100 долларов. Если переменная представляет напряжение, напишите, что оно должно находиться в пределах от 105 В до 125 В. Комментируйте смысл закодированных значений Если ваш язык поддер# живает перечисления (как C++ и Visual Basic), используйте их для выражения смысла закодированных значений. Если нет, указывайте смысл каждого значения в ком# ментариях и представляйте каждое значение в форме именованной константы, а не литерала. Так, если переменная представляет виды электрического тока, заком# ментируйте тот факт, что 1 представляет переменный ток, 2 — постоянный, а 3 — неопределенный вид. Перекрестная ссылка О форма- тировании данных см. подраз- дел «Размещение объявлений данных» раздела 31.5. Об эф- фективном использовании дан- ных см. главы 10–13. Перекрестная ссылка Более эффективный способ докумен- тирования диапазонов допусти- мых значений переменных — использование утверждений в начале и в конце метода (см. раздел 8.2). ГЛАВА 32 Самодокументирующийся код 785 Вот пример, иллюстрирующий три предыдущих рекомендации: вся информация о диапазонах значений указана в комментариях: Пример грамотного документирования объявлений переменных (Visual Basic) Dim cursorX As Integer ‘ горизонтальная позиция курсора; диапазон: 1..MaxCols Dim cursorY As Integer ‘ вертикальная позиция курсора; диапазон: 1..MaxRows Dim antennaLength As Long ‘ длина антенны в метрах; диапазон: >= 2 Dim signalStrength As Integer ‘ мощность сигнала в кВт; диапазон: >= 1 Dim characterCode As Integer ‘ код символа ASCII; диапазон: 0..255 Dim characterAttribute As Integer ‘ 0=Обычный; 1=Курсив; 2=Жирный; 3=Жирный курсив Dim characterSize As Integer ’ размер символа в точках; диапазон: 4..127 Комментируйте ограничения входных данных Входные данные могут быть получены в виде входного параметра, прочитаны из файла или введены пользо# вателем. Предыдущие советы относятся к входным параметров методов в той же степени, что и к другим видам данных. Убедитесь, что вы документируете ожида# емые и неожиданные значения. Комментарии — это один из способов докумен# тирования того, что метод никогда не должен принимать некоторые данные. За# документировать диапазоны допустимых значений можно также, использовав утверждения, и тогда эффективность обнаружения ввода неверных данных заметно повысится. Документируйте флаги до уровня отдельных би' тов Если переменная используется как битовое поле, укажите смысл каждого бита: Пример документирования флагов до уровня битов (Visual Basic) ‘ Значения битов переменной statusFlags в порядке от самого старшего ’ бита (MSB) до самого младшего бита (LSB): ’ MSB 0 обнаружена ли ошибка?: 1=да, 0=нет ’ 12 тип ошибки: 0=синтаксич., 1=предупреждение, 2=тяжелая, 3=фатальная ’ 3 зарезервировано (следует обнулить) ’ 4 статус принтера: 1=готов, 0=не готов ’ ... ’ 14 не используется (следует обнулить) ’ LSB 1532 не используются (следует обнулить) Dim statusFlags As Integer Если бы мы писали этот пример на C++, следовало бы использовать синтаксис битовых полей — тогда значения полей были бы самодокументирующимися. Включайте в комментарии, относящиеся к переменной, имя переменной Если какие#то комментарии относятся к конкретной переменной, убедитесь, что вы обновляете их вместе с переменной. Помочь в этом может использование в комментариях имени переменной. Благодаря этому при поиске переменной в коде вы найдете не только ее, но и связанные с ней комментарии. Перекрестная ссылка Об имено- вании переменных-флагов см. подраздел «Именование пере- менных статуса» раздела 11.2. 786 ЧАСТЬ VII Мастерство программирования Документируйте глобальные данные Если вы исполь# зуете глобальные данные, комментируйте их в местах объяв# ления. В этом комментарии следует указать роль данных и причину, по которой они должны быть глобальными. Используя их, каждый раз поясняйте, что данные глобальны. Первое средство подчеркивания глобального статуса переменной — конвенция именования. Если такую конвенцию именова# ния вы не приняли, комментарии могут восполнить этот пробел. Комментирование управляющих структур Обычно самое подходящее место для комментирования управляющей структуры — предшествующие ей строки. Если это оператор if или блок case, вы можете пояснить в ком# ментарии условие и результаты. Если это цикл, можно ука# зать его цель. Пример комментирования цели управляющей структуры (C++) Цель цикла. // копирование символов входного поля до запятой while ( ( *inputString != ‘,’ ) && ( *inputString != END_OF_STRING ) ) { *field = *inputString; field++; inputString++; Комментарий конца цикла (полезен в случае длинных вложенных циклов, хотя необходимость та- кого комментария указывает на чрезмерную сложность кода). } // while — копирование символов входного поля *field = END_OF_STRING; if ( *inputString != END_OF_STRING ) { Цель цикла. Положение комментария ясно говорит, что переменная inputString устанавливается с целью использования в цикле. // пропуск запятой и пробелов для нахождения следующего входного поля inputString++; while ( ( *inputString == ‘ ‘ ) && ( *inputString != END_OF_STRING ) ) { inputString++; } } // if — конец строки Опираясь на этот пример, можно дать несколько советов по комментированию управляющих структур. Пишите комментарий перед каждым оператором if, блоком case, циклом или группой операторов Эти конструкции часто требуют объяснения, а мес# то перед ними лучше всего подходит для этого. Используйте комментарии для по# яснения цели управляющих структур. Перекрестная ссылка О глобаль- ных данных см. раздел 13.3. Перекрестная ссылка Об управ- ляющих структурах см. также разделы 31.3 и 31.4 и главы с 14 по 19. > > > ГЛАВА 32 Самодокументирующийся код 787 Комментируйте завершение каждой управляющей структуры Исполь# зуйте комментарий для объяснения того, что именно завершилось, например: } // for clientIndex — обработка записей всех клиентов Комментарии особенно полезно применять для обозначения концов длинных циклов и для пояснения их вложенности. Вот пример комментариев, поясняющих концы циклов: Пример использования комментариев, иллюстрирующих вложенность (Java) for ( tableIndex = 0; tableIndex < tableCount; tableIndex++ ) { while ( recordIndex < recordCount ) { if ( !IllegalRecordNumber( recordIndex ) ) { Эти комментарии сообщают, какая управляющая структура завершается. } // if } // while } // for Эта методика комментирования дополняет визуальную информацию о логиче# ской структуре кода, предоставляемую отступами кода. Если циклы коротки, а вло# женности нет, эта методика не нужна, однако в случае глубокой вложенности или длинных циклов она окупается. Рассматривайте комментарии в концах циклов как предупреждения о сложности кода Если цикл настолько сложен, что в его конце нужен коммен# тарий, подумайте, не упростить ли цикл. Это же правило относится к сложным операторам if и блокам case. Комментарии в концах циклов сообщают полезную информацию о логической структуре кода, но писать и поддерживать их иногда утомительно. Зачастую луч# ший способ предотвратить эту нудную работу — переписать код, который в силу своей сложности требует подобной документации. Комментирование методов С комментариями методов связан одни из самых худших советов, который дается в типичных учебниках по програм# мированию. Многие авторы советуют независимо от размера или сложности метода нагромождать перед его началом це# лые информационные баррикады: Пример монолитного натуралистичного пролога метода (Visual Basic) ‘********************************************************************** ’ Имя: CopyString ’ ’ Цель: Этот метод копирует строкуисточник (источник) ’ в строкуприемник (приемник). ’ Перекрестная ссылка О форма- тировании методов см. раздел 31.7. О создании высококаче- ственных методов см. главу 7. 788 ЧАСТЬ VII Мастерство программирования ’ Алгоритм: Метод получает длину “источника”, после чего поочередно ’ копирует каждый символ в “приемник”. В качестве индекса ’ массивов “источника” и “приемника” используется индекс ’ цикла. Индекс цикла/массивов увеличивается после ’ копирования каждого символа. ’ ’ Входные данные: input Копируемая строка ’ ’ Выходные данные: output Строка, содержащая копию строки “input” ’ ’ Предположения об интерфейсе: нет ’ ’ История изменений: нет ’ ’ Автор: Дуайт К. Кодер ’ Дата создания: 01.10.04 ’ Телефон: (555) 2222255 ’ SSN: 111223333 ’ Цвет глаз: Зеленый ’ Девичья фамилия: — ’ Группа крови: AB ’ Девичья фамилия матери: ’ Любимый автомобиль: “Понтиак Ацтек” ’ Персонализированный номер автомобиля: “Tekie” ’********************************************************************** Это глупо. Метод CopyString тривиален и скорее всего включает не более пяти строк кода. Комментарий совершенно не соответствует объему метода. Цель и алгоритм метода высосаны из пальца, потому что трудно описать что#то настолько простое, как CopyString, на уровне детальности между «копированием строки» и самим ко# дом. Предположения об интерфейсе и история изменений также бесполезны — эти комментарии только занимают место в листинге. Фамилия автора дополнена избыточными данными, которые можно легко найти в системе управления реви# зиями. Заставлять указывать всю эту информацию перед каждым методом — зна# чит подталкивать программистов к написанию неточных комментариев и затруд# нять сопровождение программы. Эти лишние усилия не окупятся никогда. Другая проблема с тяжеловесными заголовками методов состоит в том, что они мешают факторизовать код: затраты, связанные с созданием нового метода, так велики, что программисты будут стремиться создавать меньше методов. Конвен# ции кодирования должны поощрять применение хороших методик — тяжеловес# ные заголовки методов поощряют их игнорировать. А теперь нескоько советов по комментированию методов. Располагайте комментарии близко к описываемому ими коду Одна из при# чин того, что пролог метода не должен содержать объемной документации, в том, что при этом комментарии далеки от описываемых ими частей метода. Если ком# ментарии далеки от кода, вероятность того, что их не будут изменять вместе с кодом при сопровождении, повышается. Смысл комментариев и кода начинает расхо# диться, и внезапно комментарии становятся никчемными. Поэтому соблюдайте ГЛАВА 32 Самодокументирующийся код 789 Принцип Близости и располагайте комментарии как можно ближе к описывае# мому ими коду. Тогда их будут поддерживать, а они сохранят свою полезность. Несколько компонентов, которые по мере необходимости следует включать в прологи методов, описаны ниже. Ради удобства создавайте стандартизованные прологи. Не думайте, что перед каждым методом нужно указывать всю информа# цию. Включайте действительно важные элементы и опускайте остальные. Описывайте каждый метод одним'двумя предложе' ниями перед началом метода Если вы не можете опи# сать метод одним или двумя краткими предложениями, вам, вероятно, следует лучше обдумать роль метода. Если крат# кое описание придумать трудно, значит, проект метода не так хорош. Попробуйте перепроектировать метод. Краткое резюмирующее пред# ложение должно присутствовать почти во всех методах, кроме простых методов доступа Get и Set. Документируйте параметры в местах их объявления Самый простой спо# соб документирования входных и выходных переменных — написать коммента# рии после их объявления: Пример документирования входных и выходных данных в местах их объявления — хороший подход (Java) public void InsertionSort( int[] dataToSort, // массив элементов, подлежащих сортировке int firstElement, // индекс первого сортируемого элемента (>=0) int lastElement // индекс последнего сортируемого элемента (<= MAX_ELEMENTS) ) Этот совет — уместное исключение из правила, предписы# вающего избегать комментариев в концах строк; такие ком# ментарии крайне полезны при документировании входных и выходных параметров. Кроме того, данный случай хоро# шо иллюстрирует полезность выравнивания параметров методов с помощью стандартных отступов, а не отступов в конце строк — при отступах в конце строк у вас просто не останется места для выразительных комментариев. В этом примере комментариям тесно даже при стандартных отступах. Этот пример также показывает, что комментарии — не единственный способ документирования. Если имена переменных достаточно хороши, их можно не комментировать. Наконец, необходимость документирова# ния входных и выходных переменных — хорошая причина избегать глобальных данных. Где вы будете их документировать? Вероятно, документировать глобаль# ные данные следует в огромном прологе. Для этого нужно выполнить большую работу, что на практике, увы, обычно означает, что глобальные данные не доку# ментируются. Это очень плохо, так как глобальные данные нужно документиро# вать не менее тщательно, чем все остальное. Используйте утилиты документирования кода, такие как Javadoc Если бы предыдущий пример нужно было на самом деле написать на Java, вы могли бы адаптировать код к Javadoc — утилите извлечения документации Java. Тогда Перекрестная ссылка Удачный выбор имени метода — важней- ший аспект документирования методов (см. раздел 7.3). Перекрестная ссылка О коммен- тариях в концах строк см. выше подраздел «Комментарии в кон- цах строк и связанные с ними проблемы» этого раздела. 790 ЧАСТЬ VII Мастерство программирования смысл совета «документируйте параметры в местах их объявления» несколько из# менился бы, что привело бы к получению такого кода: Пример документирования входных и выходных параметров для использования Javadoc (Java) /** * ... <описание метода> ... * * @param dataToSort массив элементов, подлежащих сортировке * @param firstElement индекс первого сортируемого элемента (>=0) * @param lastElement индекс последнего сортируемого элемента (<= MAX_ELEMENTS) */ public void InsertionSort( int[] dataToSort, int firstElement, int lastElement ) При использовании инструмента вроде Javadoc выгода от специфической адап# тации кода к последующему извлечению документации из него перевешивает риск, связанный с отделением описания параметров от их объявлений. Если среда не поддерживает извлечение документации, комментарии обычно лучше располагать ближе к именам параметров во избежание несогласованного редактирования кода и комментариев, а также дублирования самих имен. Проведите различие между входными и выходными данными Знать, ка# кие данные являются входными, а какие выходными, полезно. При работе с Visual Basic определить это относительно легко, потому что выходным данным предше# ствует ключевое слово ByRef, а входным — ByVal. Если ваш язык не поддерживает такую дифференциацию автоматически, выразите это при помощи комментари# ев. Вот пример: Пример проведения различия между входными и выходными данными (C++) void StringCopy( char *target, // out: строкаприемник const char *source // in: строкаисточник ) Объявления методов C++ немного хитры, потому что иногда звездочка (*) говорит о том, что аргумент является выходным параметром, но очень часто это просто означает, что с пере# менной легче работать как с указателем. Как правило, лучше идентифицировать входные и выходные параметры явно. Если ваши методы достаточно коротки и вы поддерживаете ясное различие меж# ду входными и выходными данными, документировать статус данных (входной или выходной), наверное, не нужно. Однако если метод более объемен, указание статуса поможет всем, кто будет читать код метода. Перекрестная ссылка Порядок этих параметров соответствует стандартному порядку, принято- му для методов C++, но кон- фликтует с более общими ме- тодиками (см. подраздел «Пе- редавайте параметры в поряд- ке „входные значения — изме- няемые значения — выходные значения“» раздела 7.5). Об ис- пользовании конвенции имено- вания для проведения различия между входными и выходными данными см. раздел 11.4. |