Создание, анализ ирефакторинг
Скачать 3.16 Mb.
|
44 Глава 2 . Содержательные имена Как участвующему в проекте программисту понять, какую из этих функций вы- зывать в конкретном случае? При отсутствии жестких именных схем имя moneyAmount не отличается от money , customerInfo не отличается от customer , accountData не отличается от account , а theMessage — от message . Записывайте различающиеся имена так, чтобы чита- тель кода понимал, какой смысл заложен в этих различиях . Используйте удобопроизносимые имена Людям удобно работать со словами . Значительная часть нашего мозга специ- ализируется на концепции слов, а слова по определению удобопроизносимы . Было бы обидно не использовать ту изрядную часть мозга, которая развивалась для разговорной речи . Следовательно, имена должны нормально произноситься . Если имя невозможно нормально произнести, то при любом его упоминании в об- суждении вы выглядите полным идиотом . «Итак, за этим би-си-эр-три-си-эн-тэ у нас идет пи-эс-зэт-кью, видите?» А это важно, потому что программирование является социальной деятельностью . В одной известной мне компании используется переменная genymdhms (дата ге- нерирования, год, месяц, день, час, минуты и секунды), поэтому программисты упоминали в своих разговорах «ген-уай-эм-ди-эйч-эм-эс» . У меня есть противная привычка произносить все так, как написано, поэтому я начал говорить «генъя- мадда-химс» . Потом переменную начали так называть многие проектировщи- ки и аналитики, и это звучало довольно глупо . Впрочем, мы делали это в шутку . Но как бы то ни было, мы столкнулись с типичным примером неудачного выбо- ра имен . Новым разработчикам приходилось объяснять смысл переменных, по- сле чего они начинали изъясняться дурацкими неестественными словами вме- сто нормальной разговорной речи . Сравните: class DtaRcrd102 { private Date genymdhms; private Date modymdhms; private final String pszqint = "102"; /* ... */ }; и class Customer { private Date generationTimestamp; private Date modificationTimestamp;; private final String recordId = "102"; /* ... */ }; Теперь становится возможным осмысленный разговор: «Эй, Майк, глянь-ка на эту запись! В поле временного штампа заносится завтрашняя дата! Разве такое возможно?» 44 Избегайте схем кодирования имен 45 Выбирайте имена, удобные для поиска У однобуквенных имен и числовых констант имеется один специфический не- достаток: их трудно искать в большом объеме текста . Строка MAX_CLASSES_PER_STUDENT отыскивается легко, а с числом 7 могут возник- нуть проблемы . Система поиска находит эту цифру в именах файлов, в опреде- лениях констант и в различных выражениях, где значение используется с совер- шенно другим смыслом . Еще хуже, если константа представляет собой длинное число, в котором были случайно переставлены цифры; в программе появляется ошибка, которая одновременно скрывается от поиска . Также не стоит присваивать имя e переменной, которая может использоваться при поиске . Самая распространенная буква английского алфавита с большой ве- роятностью встречается в любом текстовом фрагменте каждой программы . В этом отношении длинные имена лучше коротких, а имена, удобные для поиска, луч- ше констант в коде . Лично я считаю, что однобуквенные имена могут использоваться ТОЛЬКО для локальных переменных в коротких методах . Длина имени должна соответство- вать размеру его области видимости [N5] . Если переменная или константа мо- жет встречаться или использоваться в нескольких местах кодового блока, очень важно присвоить ей имя, удобное для поиска . Снова сравните: for (int j=0; j<34; j++) { s += (t[j]*4)/5; } и int realDaysPerIdealDay = 4; const int WORK_DAYS_PER_WEEK = 5; int sum = 0; for (int j=0; j < NUMBER_OF_TASKS; j++) { int realTaskDays = taskEstimate[j] * realDaysPerIdealDay; int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK); sum += realTaskWeeks; } Имя sum в этом фрагменте не слишком содержательно, но по крайней мере его удобно искать . Сознательное присваивание имен увеличивает длину функции, но подумайте, насколько проще найти WORK_DAYS_PER_WEEK , чем искать все вхождения цифры 5 и фильтровать список до позиций с нужным смыслом . Избегайте схем кодирования имен У нас и так хватает хлопот с кодированием, чтобы искать новые сложности . Ко- дирование информации о типе или области видимости в именах только создает новые хлопоты по расшифровке . Вряд ли разумно заставлять каждого нового работника изучать очередной «язык» кодирования — в дополнение к изучению 45 46 Глава 2 . Содержательные имена (обычно немалого) объема кода, с которым он будет работать . Это только услож- няет его работу при попытке решения задачи . Как правило, кодированные имена плохо произносятся и в них легко сделать опечатку . Венгерская запись В доисторические времена, когда в языках действовали ограничения на дли- ну имен, мы нарушали это правило по необходимости — и не без сожалений . В Fortran первая буква имени переменной обозначала код типа . В ранних верси- ях BASIC имена могли состоять только из одной буквы и одной цифры . Венгер- ская запись (HN, Hungarian Notation) подняла эту проблему на новый уровень . Венгерская запись играла важную роль во времена Windows C API, когда про- граммы работали с целочисленными дескрипторами (handle), длинными указа- телями, указателями на void или различными реализациями «строк» (с разным применением и атрибутами) . Компиляторы в те дни не поддерживали проверку типов, поэтому программистам были нужны «подсказки» для запоминания типов . В современных языках существует куда более развитая система типов, а компи- ляторы запоминают типы и обеспечивают их соблюдение . Более того, появилась тенденция к использованию меньших классов и более коротких функций, чтобы программисты видели точку объявления каждой используемой переменной . Java-программисту кодировать типы в именах не нужно . Объекты обладают силь- ной типизацией, а рабочие среды развились до такой степени, что могут выявить ошибку типа еще до начала компиляции! Таким образом, в наши дни венгерская запись и другие формы кодирования типов в именах превратились в обычные пережитки прошлого . Они усложняют изменение имени или типа переменных, функций и классов . Они затрудняют чтение кода . Наконец, они повышают риск того, что система кодирования собьет с толку читателя кода . PhoneNumber phoneString; // Имя не изменяется при изменении типа! Префиксы членов классов Префиксы m_ , которыми когда-то снабжались переменные классов, тоже стали ненужными . Классы и функции должны быть достаточно компактными, чтобы вы могли обходиться без префиксов . Также следует использовать рабочую среду с цветовым выделением членов классов, обеспечивающим их наглядную иден- тификацию: public class Part { private String m_dsc; // Текстовое описание void setName(String name) { m_dsc = name; } } _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 46 Избегайте мысленных преобразований 47 public class Part { String description; void setDescription(String description) { this.description = description; } } Кроме того, люди быстро учатся игнорировать префиксы (и суффиксы), чтобы видеть содержательную часть имени . Чем больше мы читаем код, тем реже заме- чаем префиксы . В конечном итоге префикс превращается в невидимый балласт, характерный для старого кода . Интерфейсы и реализации Иногда в программах встречается особый случай кодирования . Допустим, вы строите АБСТРАКТНУЮ ФАБРИКУ для создания геометрических фигур . Фа- брика представляет собой интерфейс, который реализуется конкретным классом . Как их назвать? IShapeFactory и ShapeFactory ? Я предпочитаю оставлять имена интерфейсов без префиксов . Префикс I , столь распространенный в старом коде, в лучшем случае отвлекает, а в худшем — передает лишнюю информацию . Я не собираюсь сообщать своим пользователям, что они имеют дело с интерфейсом . Им достаточно знать, что это ShapeFactory , то есть фабрика фигур . Следовательно, при необходимости закодировать в имени либо интерфейс, либо реализацию, я выбираю реализацию . Имя ShapeFactoryImp , или даже уродливое CShapeFactory , все равно лучше кодирования информации об интерфейсе . Избегайте мысленных преобразований Не заставляйте читателя мысленно преобразовывать ваши имена в другие, уже известные ему . Обычно эта проблема возникает из-за нежелания использовать понятия как из пространства задачи, так и из пространства решения . Такая проблема часто возникает при использовании однобуквенных имен пере- менных . Конечно, счетчик цикла можно назвать i , j или k (но только не l !), если его область видимости очень мала, и он не конфликтует с другими именами . Это связано с тем, что однобуквенные имена счетчиков циклов традиционны . Однако в большинстве других контекстов однобуквенные имена нежелательны; в сущности, вы создаете временный заменитель, который должен быть мысленно преобразован пользователем в реальную концепцию . Нет худшей причины для выбора имени c , чем та, что имена a и b уже заняты . Как правило, программисты весьма умны . А умные люди иногда любят показы- вать мощь интеллекта, демонстрируя свои способности к мысленному жонглиро- ванию . В конце концов, если вы помните, что переменная r содержит URL-адрес с удаленным хостом и схемой, преобразованный к нижнему регистру, это совер- шенно очевидно свидетельствует о вашем уме . 47 48 Глава 2 . Содержательные имена Одно из различий между умным и профессиональным программистом заклю- чается в том, что профессионал понимает: ясность превыше всего . Профессио- налы используют свою силу во благо и пишут код, понятный для других людей . Имена классов Имена классов и объектов должны представлять собой существительные и их комбинации: Customer , WikiPage , Account и AddressParser . Старайтесь не использо- вать в именах классов такие слова, как Manager , Processor , Data или Info . Имя клас- са не должно быть глаголом . Имена методов Имена методов представляют собой глаголы или глагольные словосочетания: postPayment , deletePage , save и т . д . Методы чтения/записи и предикаты образуют- ся из значения и префикса get , set и is согласно стандарту javabean 1 string name = employee.getName(); customer.setName("mike"); if (paycheck.isPosted())... При перегрузке конструкторов используйте статические методы-фабрики с име- нами, описывающими аргументы . Например, запись Complex fulcrumPoint = Complex.FromRealNumber(23.0); обычно лучше записи Complex fulcrumPoint = new Complex(23.0); Рассмотрите возможность принудительного использования таких методов; для этого соответствующие конструкторы объявляются приватными . Избегайте остроумия Если ваши имена будут излишне остроум- ными, то их смысл будет понятен только людям, разделяющим чувство юмора авто- ра — и только если они помнят шутку . Все ли догадаются, что делает функция с име- нем HolyHandGrenade ? 2 Конечно, это очень мило, но, возможно, в данном случае лучше 1 http://java .sun .com/products/javabeans/docs/spec .html . 2 «Святая ручная граната» — оружие огромной разрушительной силы из фильма «Монти Пайтон и Священный Грааль» . — Примеч. перев. 48 Воздержитесь от каламбуров 49 подойдет имя DeleteItems . Отдавайте предпочтение ясности перед развлекатель- ной ценностью . Остроумие часто воплощается в форме просторечий или сленга . Например, не используйте имя whack() вместо kill() . Не используйте шуточки, привязанные к конкретной культуре, — например, eatMyShorts 1 () вместо abort() Выберите одно слово для каждой концепции Выберите одно слово для представления одной абстрактной концепции и при- держивайтесь его . Например, существование в разных классах эквивалентных методов с именами fetch , retrieve и get неизбежно создаст путаницу . Как запом- нить, к какому классу относится то или иное имя метода? К сожалению, чтобы запомнить, какой термин использовался в той или иной библиотеке или классе, нередко приходится помнить, какой компанией, группой или программистом эта библиотека была создана . В противном случае вы потратите массу времени на просмотр заголовков и предыдущих примеров кода . Современные рабочие среды (такие, как Eclipse и IntelliJ) предоставляют контекстно-зависимые подсказки — скажем, список методов, которые могут вы- зываться для конкретного объекта . Однако следует учитывать, что в этом спи- ске обычно не приводятся комментарии, которые вы записываете рядом с име- нами функций и списками параметров . И вам еще повезло, если в нем будут ука- заны имена параметров из объявлений функций . Имена функций должны быть законченными и логичными, чтобы программист мог сразу выбрать правильный метод без сбора дополнительной информации . Аналогичным образом, использование терминов controller , manager и driver в одной кодовой базе тоже вызывает путаницу . Чем DeviceManager принципи- ально отличается от ProtocolController ? Почему в двух случаях не использу- ются одинаковые термины? Такие имена создают ложное впечатление, что два объ екта обладают совершенно разными типами, а также относятся к разным классам . Единый, согласованный лексикон окажет неоценимую помощь программистам, которые будут пользоваться вашим кодом . Воздержитесь от каламбуров Старайтесь не использовать одно слово в двух смыслах . В сущности, обозначе- ние двух разных идей одним термином — это каламбур . 1 Из мультипликационного сериала «Симпсоны» . — Примеч. перев. 49 50 Глава 2 . Содержательные имена Если следовать принципу «одно слово для каждой концепции», в программе мо- жет появиться много классов, содержащих, например, метод add . Пока списки па- раметров и возвращаемые значения разных методов add остаются семантически эквивалентными, все хорошо . Однако программист может решить использовать имя add «ради единообразия» независимо от того, выполняет ли этот метод добавление в прежнем смысле или нет . Допустим, программа содержит много классов с методами add , которые со здают новое значение сложением или конкатенацией двух существующих значений . Вы пишете новый класс с методом, помещающим свой единственный параметр в коллекцию . Стоит ли присвоить этому методу имя add ? На первый взгляд это выглядит последовательно, потому что в программе уже используется множество других методов add , но новый метод имеет другую семантику, поэтому ему лучше присвоить имя insert или append . Присваивая новому методу имя add , вы создаете нежелательный каламбур . Задача автора — сделать свой код как можно более понятным . Код должен вос- приниматься с первого взгляда, не требуя тщательного изучения . Ориентируй- тесь на модель популярной литературы, в которой сам автор должен доступно выразить свои мысли, а не на академическую модель, в которой ученик усерд- ным трудом постигает скрытый смысл публикации . Используйте имена из пространства решения Не забывайте: ваш код будут читать программисты . А раз так, не стесняйтесь ис- пользовать термины из области информатики, названия алгоритмов и паттер- нов, математические термины и т . д . Не ограничивайтесь именами исключитель- но из пространства задачи; не заставляйте своих коллег постоянно бегать к кли- енту и спрашивать, что означает каждое имя, когда соответствующая концепция уже знакома им под другим названием . Имя AccountVisitor сообщит много полезной информации программисту, знако- мому с паттерном «Посетитель» (Visitor) . И какой программист не знает, что та- кое «очередь заданий» ( JobQueue )? Существует множество сугубо технических понятий, с которыми имеют дело программисты . Как правило, таким понятиям разумнее всего присваивать технические имена . Используйте имена из пространства задачи Если для того, что вы делаете, не существует подходящего «программизма», ис- пользуйте имя из пространства задачи . По крайней мере программист, занима- 50 Добавьте содержательный контекст 51 ющийся сопровождением кода, сможет узнать у специалиста в предметной об- ласти, что означает это имя . Разделение концепций из пространств задачи и решения — часть работы хоро- шего программиста и проектировщика . В коде, главным образом ориентирован- ном на концепции из пространства задачи, следует использовать имена из про- странства задачи . Добавьте содержательный контекст Лишь немногие имена содержательны сами по себе . Все остальные имена следу- ет помещать в определенный контекст для читателя кода, заключая их в классы, функции и пространства имен с правильно выбранными названиями . В крайнем случае контекст имени можно уточнить при помощи префикса . Допустим, в программе используются переменные с именами firstName , lastName , street , houseNumber , city , state и zipcode . Вполне очевидно, что в совокупности они образуют адрес . Но что, если переменная state встретилась вам отдельно от других переменных внутри метода? Сразу ли вы поймете, что она является ча- стью адреса? Контекст можно добавить при помощи префиксов: addrFirstName , addrLastName , addrState и т . д . По крайней мере читатель кода поймет, что переменные явля- ются частью более крупной структуры . Конечно, правильнее было бы создать класс с именем Address , чтобы даже компилятор знал, что переменные являются частью чего-то большего . Возьмем метод из листинга 2 .1 . Нужен ли переменным более содержательный контекст? Имя функции определяет только часть контекста; алгоритм предостав- ляет все остальное . При чтении функции становится видно, что три переменные number , verb и pluralModifier являются компонентами сообщения guessМessage К сожалению, контекст приходится вычислять . При первом взгляде на метод смысл переменных остается неясным . |