Создание, анализ ирефакторинг
Скачать 3.16 Mb.
|
35 школы мысли А как насчет меня (Дядюшка Боб)? Что я думаю по поводу чистого кода? Эта книга расскажет вам во всех подробностях, что я и мои соратники думаем о чи- стом коде . Вы узнаете, как, по нашему мнению, должно выглядит чистое имя переменной, чистая функция, чистый класс и т . д . Мы излагаем свои мнения в виде беспрекословных истин и не из- виняемся за свою категоричность . Для нас, на данном моменте наших карьер, они являются бес- прекословными истинами . Они составляют нашу школу мысли в области чистого кода . Мастера боевых искусств не достигли единого мнения по поводу того, какой из видов едино- борств является лучшим, а какие приемы — самы- ми эффективными . Часто ведущие мастера созда- ют собственную школу и набирают учеников . Так появилась школа дзю-дзюцу Грейси, основанная семьей Грейси в Бразилии . Так появилась школа дзю-дзюцу Хаккорю, основанная Окуямой Рюхо в Токио . Так появилась школа Джит Кун-до, основанная Брюсом Ли в Соединенных Штатах . Ученики этих разных школ погружаются в учение основателя школы . Они по- свящают себя изучению того, чему учит конкретный мастер, часто отказываясь от учений других мастеров . Позднее, когда уровень их мастерства возрастет, они могут стать учениками другого мастера, чтобы расширить свои познания и прове- рить их на практике . Некоторые переходят к совершенствованию своих навыков, открывают новые приемы и открывают собственные школы . Ни одна из этих разных школ не обладает абсолютной истиной . Тем не менее в рамках конкретной школы мы действуем так, будто ее учение и арсенал приемов верны . Именно так и следует тренироваться в школе Хаккорю или Джит Кун-до . Но правильность принципов в пределах одной школы не делает ошибочными учения других школ . Считайте, что эта книга является описанием Школы учителей Чистого кода . В ней представлены те методы и приемы, которыми мы сами пользуемся в сво- ем искусстве . Мы утверждаем, что если вы последуете нашему учению, то это принесет вам такую же пользу, как и нам, и вы научитесь писать чистый и про- фессиональный код . Но не стоит думать, что наше учение «истинно» в каком-то абсолютном смысле . Существуют другие школы и мастера, которые имеют ничуть не меньше оснований претендовать на профессионализм . Не упускайте возмож- ности учиться у них . В самом деле, многие рекомендации в этой книге противоречивы . Вероятно, вы согласитесь не со всеми из них . Возможно, против некоторых вы будете яростно протестовать . Это нормально . Мы не претендуем на абсолютную истину . С дру- 35 36 Глава 1 . Чистый код гой стороны, приведенные в книге рекомендации являются плодами долгих, непростых размышлений . Мы пришли к ним после десятилетий практической работы, непрестанных проб и ошибок . Независимо от того, согласитесь вы с нами или нет, нашу точку зрения стоит по крайней мере узнать и уважать . Мы — авторы Поле @author комментария javadoc говорит о том, кто мы такие . Мы — авторы . А как известно, у каждого автора имеются свои читатели . Автор несет ответ- ственность за то, чтобы хорошо изложить свои мысли читателям . Когда вы в следующий раз напишете строку кода, вспомните, что вы — автор, и пишете для читателей, которые будут оценивать плоды вашей работы . Кто-то спросит: так ли уж часто читается наш код? Разве большая часть времени не уходит на его написание? Вам когда-нибудь доводилось воспроизводить запись сеанса редактирования? В 80-х и 90-х годах существовали редакторы, записывавшие все нажатия клавиш (например, Emacs) . Вы могли проработать целый час, а потом воспроизвести весь сеанс, словно ускоренное кино . Когда я это делал, результаты оказывались про- сто потрясающими . Большинство операций относилось к прокрутке и переходу к другим модулям! Боб открывает модуль. Он находит функцию, которую необходимо изменить. Задумывается о последствиях. Ой, теперь он переходит в начало модуля, чтобы проверить инициализацию пере- менной. Снова возвращается вниз и начинает вводить код. Стирает то, что только что ввел. Вводит заново. Еще раз стирает! Вводит половину чего-то другого, но стирает и это! Прокручивает модуль к другой функции, которая вызывает изменяемую функцию, чтобы посмотреть, как она вызывается. Возвращается обратно и восстанавливает только что стертый код. Задумывается. Снова стирает! Открывает другое окно и просматривает код субкласса. Переопределяется ли в нем эта функция? . . . В общем, вы поняли . На самом деле соотношение времени чтения и написания кода превышает 10:1 . Мы постоянно читаем свой старый код, поскольку это не- обходимо для написания нового кода . 36 Предыстория и принципы 37 Из-за столь высокого соотношения наш код должен легко читаться, даже если это затрудняет его написание . Конечно, написать код, не прочитав его, невозмож- но, так что упрощение чтения в действительности упрощает и написание кода . Уйти от этой логики невозможно . Невозможно написать код без предваритель- ного чтения окружающего кода . Код, который вы собираетесь написать сегодня, будет легко или тяжело читаться в зависимости от того, насколько легко или тяжело читается окружающий код . Если вы хотите быстро справиться со своей задачей, если вы хотите, чтобы ваш код было легко писать — позаботьтесь о том, чтобы он легко читался . Правило бойскаута Хорошо написать код недостаточно . Необходимо поддерживать чистоту кода с течением времени . Все мы видели, как код загнивает и деградирует с течением времени . Значит, мы должны активно поработать над тем, чтобы этого не про- изошло . У бойскаутов существует простое правило, которое применимо и к нашей про- фессии: Оставь место стоянки чище, чем оно было до твоего прихода 1 . Если мы все будем оставлять свой код чище, чем он был до нашего прихода, то код попросту не будет загнивать . Чистка не обязана быть глобальной . Присвойте более понятное имя переменной, разбейте слишком большую функцию, устрани- те одно незначительное повторение, почистите сложную цепочку if Представляете себе работу над проектом, код которого улучшается с течением времени? Но может ли профессионал позволить себе нечто иное? Разве посто- янное совершенствование не является неотъемлемой частью профессионализма? Предыстория и принципы Эта книга во многих отношениях является «предысторией» для книги, напи- санной мной в 2002 году: «Agile Software Development: Principles, Patterns, and Practices» (сокращенно PPP) . Книга PPP посвящена принципам объектно- ориентированного проектирования и практическим приемам, используемым про- фессиональными разработчиками . Если вы еще не читали PPP, скажу, что там раз- вивается тема, начатая в этой книге . Прочитавшие убедятся, что многие идеи пе- рекликаются с идеями, изложенными в этой книге на уровне кода . 1 Из прощального послания Роберта Стивенсона Смита Баден-Пауэлла скаутам: «Поста- райтесь оставить этот мир чуть лучшим, чем он был до вашего прихода…» 37 38 Глава 1 . Чистый код В этой книге периодически встречаются ссылки на различные принципы проек- тирования . В частности, упоминается принцип единой ответственности (SRP), принцип открытости/закрытости (OCP) и принцип обращения зависимостей (DIP) . Все эти принципы подробно описаны в PPP . Заключение Книги по искусству не обещают сделать из вас художника . Все, что они могут — познакомить вас с приемами, инструментами и направлением мысли других художников . Эта книга тоже не обещает сделать из вас хорошего программиста . Она не обещает сформировать у вас «чувство кода» . Я могу лишь показать, в каком направлении мыслят хорошие программисты и какие приемы, трюки и инструменты они применяют в своей работе . Подобно книгам по искусству, эта книга насыщена подробностями . В ней много кода — как хорошего, так и плохого . Вы увидите, как плохой код преобразуется в хороший . Вы найдете списки эвристических правил, дисциплин и методов . Вы увидите множество примеров . А дальше дело только за вами . Помните старый анекдот о скрипаче, который заблудился по пути на концерт? Он остановил старика на углу и спросил, как попасть в Карнеги-холл . Старик посмотрел на скрипача, на зажатую у него под мышкой скрипку и сказал: «Ста- райся, сынок . Старайся!» литература [Beck07]: Implementation Patterns, Kent Beck, Addison-Wesley, 2007 . [Knuth92]: Literate Programming, Donald E . Knuth, Center for the Study of Lan- guage and Information, Leland Stanford Junior University, 1992 . 38 Содержательные имена Тим Оттингер Имена встречаются в программировании повсеместно . Мы присваиваем имена своим переменным, функциям, аргументам, классам и пакетам . Мы присваиваем имена исходным файлам и каталогам, в которых они хранятся . Мы присваиваем имена файлам jar, war и ear . Имена, имена, имена… Но то, что делается так часто, должно делаться хорошо . Далее приводятся некоторые простые правила создания хороших имен . 2 39 40 Глава 2 . Содержательные имена Имена должны передавать намерения программиста Легко сказать: имена должны передавать намерения программиста . И все же к вы- бору имен следует относиться серьезно . Чтобы выбрать хорошее имя, понадобит- ся время, но экономия окупит затраты . Итак, следите за именами в своих про- граммах и изменяйте их, если найдете более удачные варианты . Этим вы упро- стите жизнь каждому, кто читает ваш код (в том числе и себе самому) . Имя переменной, функции или класса должно отвечать на все главные вопросы . Оно должно сообщить, почему эта переменная (и т . д .) существует, что она делает и как используется . Если имя требует дополнительных комментариев, значит, оно не передает намерений программиста . int d; // Прошедшее время Имя d не передает ровным счетом ничего . Оно не ассоциируется ни с временными интервалами, ни с днями . Его следует заменить другим именем, которое указы- вает, что именно измеряется и в каких единицах: int elapsedTimeInDays; int daysSinceCreation; int daysSinceModification; int fileAgeInDays; Содержательные имена существенно упрощают понимание и модификацию кода . Например, что делает следующий фрагмент? public List List for (int[] x : theList) if (x[0] == 4) list1.add(x); return list1; } Почему мы не можем сразу сказать, что делает этот код? В нем нет сложных выражений . Пробелы и отступы расставлены грамотно . В коде задействованы только три переменные и две константы . В нем нет никаких хитроумных классов или полиморфных методов, только список массивов (по крайней мере на первый взгляд) . Проблема кроется не в сложности кода, а в его неочевидности, то есть степени, в которой контекст не следует явно из самого кода . Код подразумевает, что мы знаем ответы на вопросы: 1 . Какие данные хранятся в theList ? 2 . Чем так важен элемент theList с нулевым индексом? 3 . Какой особый смысл имеет значение 4? 4 . Как будет использоваться возвращаемый список? 40 Избегайте дезинформации 41 Ответы на все эти вопросы не следуют из примера, хотя и могли бы . Допустим, мы работаем над игрой «Сапер» . Игровое поле представлено в виде списка ячеек с именем theList . Переименуем его в gameBoard Каждая ячейка игрового поля представлена простым массивом . Далее выясняет- ся, что в элементе с нулевым индексом хранится код состояния, а код 4 означает «флажок установлен» . Даже простое присваивание имен всем этим концепциям существенно улучшает код: public List List for (int[] cell : gameBoard) if (cell[STATUS_VALUE] == FLAGGED) flaggedCells.add(cell); return flaggedCells; } Обратите внимание: простота кода несколько не изменилась . Новая версия содер- жит точно такое же количество операторов и констант, с абсолютно таким же ко- личеством уровней вложенности . Однако код стал существенно более понятным . Можно пойти еще дальше и написать простой класс для представления ячеек вместо использования массива int . В класс включается функция, передающая на- мерения программиста (назовем ее isFlagged ); она скрывает «волшебные» числа . В результате мы получаем новую версию функции: public List List for (Cell cell : gameBoard) if (cell.isFlagged()) flaggedCells.add(cell); return flaggedCells; } Не изменилось ничего, кроме имен — но теперь можно легко понять, что здесь происходит . Такова сила выбора хороших имен . Избегайте дезинформации Программисты должны избегать ложных ассоциаций, затемняющих смысл кода . Не используйте слова со скрытыми значениями, отличными от предполагаемого . Например, переменным не стоит присваивать имена hp , aix , and sco , потому что они ассоциируются с платформами и разновидностями Unix . Даже если в пере- менной хранится длина гипотенузы и имя hp кажется хорошим сокращением, оно может ввести в заблуждение читателя кода . Не обозначайте группу учетных записей именем accountList , если только она дей- ствительно не хранится в списке ( List ) . Слово «список» имеет для программиста вполне конкретный смысл . Если записи хранятся не в List , а в другом контейнере, 41 42 Глава 2 . Содержательные имена это может привести к ложным выводам 1 . В этом примере лучше подойдет имя accountGroup , bunchOfAccounts и даже просто accounts Остерегайтесь малозаметных различий в именах . Сколько времени понадобится, чтобы заметить незначительное различие в XYZControllerForEfficientHandlingOf- Strings в одном модуле и XYZControllerForEfficientStorageOfStrings где-то в другом месте? Эти имена выглядят устрашающе похожими . Сходное представление сходных концепций — информация . Непоследователь- ное представление — дезинформация . Современные среды Java поддерживают удобный механизм автоматического завершения кода . Вы вводите несколько символов имени, нажимаете некую комбинацию клавиш (а иногда обходится и без этого) и получаете список возможных вариантов завершения имени . Очень удобно, если имена похожих объектов сортируются по алфавиту, и если различия предельно очевидны — ведь разработчик, скорее всего, выберет ваш объект по имени, не увидев ни ваших обширных комментариев, ни хотя бы списка методов класса . По-настоящему устрашающие примеры дезинформирующих имен встречаются при использовании строчной «L» и прописной «O» в именах переменных, особен- но в комбинациях . Естественно, проблемы возникают из-за того, что эти буквы почти не отличаются от констант «1» и «0» соответственно . int a = l; if ( O == l ) a = O1; else l = 01; Возможно, некоторым читателям этот совет покажется надуманным, однако мы неоднократно видели код, в котором подобных ухищрений было предостаточно . В одном случае автор кода даже предложил использовать другой шрифт, чтобы различия стали более очевидными — в дальнейшем это решение должно было передаваться всем будущим разработчикам на словах или в письменном доку- менте . Простое переименование решает проблему окончательно и без создания новых документов . Используйте осмысленные различия Когда программист пишет код исключи- тельно для того, чтобы удовлетворить за- просы компилятора или интерпретатора, он сам себе создает проблемы . Например, поскольку одно имя в одной области име- ни не может обозначать две разные вещи, 1 Как будет показано ниже, даже если контейнер действительно представляет собой List, лучше обойтись без кодирования типа контейнера в имени . 42 Используйте осмысленные различия 43 возникает соблазн произвольно изменить одно из имен . Иногда для этого имя записывается заведомо неправильно и возникает удивительная ситуация: после исправления грамматической ошибки программа перестает компилироваться 1 Недостаточно добавить в имя серию цифр или неинформативные слова, даже если компилятору этого будет достаточно . Если имена различаются, то они должны обозначать разные понятия . «Числовые ряды» вида (a1, a2, .. aN) являются противоположностью созна- тельного присваивания имен . Такие имена не дезинформируют — они просто не несут информации и не дают представления о намерениях автора . Пример: public static void copyChars(char a1[], char a2[]) { for (int i = 0; i < a1.length; i++) { a2[i] = a1[i]; } } Такая функция будет читаться намного лучше, если присвоить аргументам имена source и destination Неинформативные слова также применяются для создания бессодержательных различий . Допустим, у вас имеется класс Product . Создав другой класс с именем ProductInfo или ProductData , вы создаете разные имена, которые по сути обозна- чают одно и то же . Info и Data не несут полезной информации, как и артикли a, an и the . Следует учесть, что использование префиксов a и the вовсе не является ошибкой, но только при условии, что они создают осмысленные различия . Например, пре- фикс a может присваиваться всем локальным переменным, а префикс the — всем аргументам функций 2 . Проблема возникает тогда, когда вы называете перемен- ную theZork , потому что в программе уже есть другая переменная с именем zork Неинформативные слова избыточны . Слово variable никогда не должно встре- чаться в именах переменных . Слово table никогда не должно встречаться в име- нах таблиц . Чем имя NameString лучше Name ? Разве имя может быть, скажем, вещественным числом? Если может, то это нарушает предыдущее правило о дез- информации . Представьте, что в программе присутствуют два класса с именами Customer и CustomerObject . Что вы можете сказать о различиях между ними? Какой класс предоставляет лучший путь к истории платежей клиента? Эта проблема встретилась нам в одном реально существующем приложении . Мы изменили имена, чтобы защитить виновных, но точная форма ошибки вы- глядит так: getActiveAccount(); getActiveAccounts(); getActiveAccountInfo(); 1 Для примера можно привести совершенно отвратительную привычку создавать перемен- ную klass только из-за того, что имя class было использовано для других целей . 2 Дядюшка Боб действовал так при программировании на C++, но потом бросил эту при- вычку, потому что благодаря современным IDE она стала излишней . 43 |