Чистыйкод дляпродолжающи х
Скачать 7.85 Mb.
|
for i in range(10): ... for j in range(3): ... print(i, j) 0 0 0 1 0 2 1 0 Еще одно исключение — использование x и y для декартовых координат. В боль- шинстве других случаев я не рекомендую применять однобуквенные имена пере- менных. Хотя, допустим, w и h для ширины (width) и высоты (height) или n для числа (number) могут соблазнить вас краткостью, другим это может быть не очевидно. Слишком длинные имена Как правило, чем больше область видимости имени, тем более содержательным оно должно быть. Короткое имя (например, payment ) хорошо подойдет для ло- кальной переменной в одной короткой функции. Однако имя может оказаться недостаточно содержательным, если использовать его для глобальной переменной в 10 000-строчной программе, потому что в такой программе могут обрабатываться различные виды платежных данных. В такой ситуации лучше использовать более содержательное имя salesClientMonthlyPayment или annual_electric_bill_payment Дополнительные слова в имени уточняют смысл и устраняют неоднозначность. Длина имен 89 Лучше лишние пояснения, чем их нехватка. Однако существуют рекомендации для определения того, когда необходимы более длинные имена. Н ПРПСКЙТ БКВ В СВМ KД Не пропускайте буквы в своем коде. Хотя пропущенные буквы в именах были популярны в языках программирования C до 1990-х годов — напри- мер, memcpy (memory copy) или strcmp (string compare) — они создают плохо читаемый стиль выбора имен, который не стоит использовать сегодня. Если имя нельзя легко произнести, его не удастся легко понять. Кроме того, старайтесь использовать короткие фразы, с которыми ваш код читается как обычный текст. Например, имя number_of_trials читается лучше, чем number_trials. Префиксы в именах Префиксы в именах иногда избыточны. Например, если у вас есть класс Cat с атри- бутом weight , очевидно, что weight (вес) относится к кошке (cat). Таким образом, имя catWeight будет слишком подробным и длинным. Аналогичным образом устаревшей считается венгерская запись — практика вклю- чения сокращения типа данных в имена. Например, имя strName указывает, что переменная содержит строковое значение, а iVacationDays — что переменная со- держит целое число. Современные языки и IDE могут предоставить программисту информацию о типе данных без этих префиксов, поэтому венгерская запись счи- тается излишней в наше время. Если вы все еще включаете типы данных в имена переменных, пора отказаться от этой привычки. С другой стороны, префиксы is и has у переменных, содержащих логические зна- чения, или функций и методов, возвращающих логические значения, делают эти имена более понятными. Рассмотрим пример использования переменной с именем is_vehicle и метода с именем has_key() : if item_under_repair.has_key('tires'): is_vehicle = True С методом has_key() и переменной is_vehicle этот код читается как естественная фраза: «if the item under repair has a key named ‘tires,’ then it’s true that the item is a vehicle» (если у ремонтируемого объекта имеется ключ с именем ‘tires’, следова- тельно, это транспортное средство). Также включение единиц измерения в имена может предоставить полезную ин- формацию. Переменная weight , в которой хранится значение с плавающей точкой, 90 Глава 4.Выбор понятных имен неоднозначна: в чем измеряется вес — в фунтах, килограммах или тоннах? Ин- формация об единицах измерения не является типом данных, поэтому включение префикса или суффикса kg или lbs нельзя считать венгерской записью. Если вы не используете тип данных, предназначенный специально для хранения веса и со- держащий информацию о единицах измерения, имя переменной вида weight_kg оказывается вполне разумным. Вспомните, что в 1999 году автоматизированный космический зонд Mars Climate Orbiter был потерян из-за того, что программа, разработанная фирмой «Локхид-Мартин», выполняла вычисления в единицах, принятых в Великобритании, тогда как в NASA использовалась метрическая си- стема и это привело к ошибке в расчете траектории. По имеющейся информации создание космического аппарата обошлось в 125 миллионов долларов. Последовательные числовые суффиксы в именах Последовательные числовые суффиксы в именах указывают на то, что вам, воз- можно, стоит изменить тип данных переменной или включить дополнительную информацию в имя. Числа сами по себе, как правило, не предоставляют достаточной информации, чтобы имена можно было отличить друг от друга. Имена переменных вида payment1 , payment2 и payment3 не сообщают читателю кода, чем они различаются. Возможно, программисту стоит преобразовать эти три переменные в один список или переменную-кортеж с именем payments , в которой хранятся три значения. Функции вида makePayment1(amount) , makePayment2(amount) и т. д., вероятно, сто- ит преобразовать в одну функцию, которая получает целочисленный аргумент: makePayment(1, amount) , makePayment(2, amount) и т. д. Если эти функции обладают разным поведением, оправдывающим определение отдельных функций, смысл чисел должен быть отражен в имени, например: makeLowPriorityPayment(amount) и makeHighPriorityPayment(amount) или make1stQuarterPayment(amount) и make2n dQuarterPayment(amount) Если у вас имеется веская причина для выбора имен с последовательными число- выми суффиксами, ничто не мешает вам их использовать. Но если вы задаете такие имена, чтобы не задумываться лишний раз, вам стоит пересмотреть свой подход. Выбирайте имена, пригодные для поиска Во всех программах, кроме самых коротких, вам, вероятно, придется воспользо- ваться редактором или функцией поиска ( Ctrl-F ) в IDE, чтобы найти упоминания переменных и функций. Если вы будете выбирать короткие, обобщенные имена переменных (например, num или a ), вы наверняка получите ряд ложных совпадений. Избегайте шуток, каламбуров и культурных отсылок 91 Чтобы имя было найдено немедленно, создавайте уникальные имена с более длин- ными именами переменных, которые содержат конкретную информацию. В некоторых IDE реализованы средства рефакторинга, способные идентифициро- вать имена на основании их использования в вашей программе. Например, часто встречается функция «переименования», способная отличать переменные с име- нами num и number или локальную переменную num от глобальной переменной num Однако вам следует выбирать имена так, словно эти инструменты недоступны. Если вы будете помнить об этом правиле, вам будет проще выбирать содержа- тельные имена вместо обобщенных. Имя email слишком многозначно, поэтому лучше выбрать более содержательное имя: emailAddress , downloadEmailAttachment , emailMessage , replyToAddress и т. д. Такое имя не только более точное, но его проще найти в исходном коде. Избегайте шуток, каламбуров и культурных отсылок На одном из моих предыдущих мест работы кодовая база содержала функцию с име- нем gooseDownload() . Я понятия не имел, что это означало, потому что продукт не имел отношения ни к птицам, ни к загрузке птиц (goose = «гусь»). Когда я обратился к более опытному коллеге, который когда-то написал эту функцию, он объяснил, что слово goose в данном случае было глаголом. Этого я тоже не понял. Ему пришлось дальше объяснять, что выражение goose the engine из жаргона водителей означает нарастить обороты двигателя. Таким образом, функция gooseDownload() должна была ускорить загрузку. Я кивнул и вернулся к рабочему столу. Годы спустя, когда мой коллега уволился из компании, я переименовал его функцию и присвоил ей имя increaseDownloadSpeed() При выборе имен в программе у вас может возникнуть соблазн использовать шутки, каламбуры или культурные отсылки, чтобы ваш код выглядел более непринуж- денно. Не делайте этого. Шутки плохо передаются в текстовом виде, и, возможно, в будущем они уже не покажутся столь забавными. Каламбуры не всегда понятны, а многократные сообщения об ошибках от коллег, принявших каламбур за опечатку, могут быть довольно неприятными. Культурные отсылки могут помешать ясно передать цель вашего кода. Благодаря интернету исходный код легко распространяется по всему миру, и его читате- ли не всегда хорошо владеют английским или понимают английские шутки. Как упоминалось ранее в этой главе, имена spam , eggs и bacon в документации Python взяты из комедийных скетчей группы Monty Python, но мы используем их только как метасинтаксические переменные; применять их в реальном коде не рекомендуется. 92 Глава 4.Выбор понятных имен Лучше всего писать код, понятный тем, для кого английский язык не является родным, то есть прямолинейно, традиционно и без юмора. Возможно, мой бывший коллега решил, что gooseDownload() — смешная шутка, но ничто не убивает шутку быстрее, чем необходимость ее объяснять. Не заменяйте встроенные имена Никогда не используйте встроенные имена Python для своих переменных. Напри- мер, присвоив переменной имя list или set , вы заместите функции Python list() и set() , что позднее может привести к появлению ошибок. Функция list() создает объекты списков, но ее замена может вызвать ошибку: >>> list(range(5)) [0, 1, 2, 3, 4] >>> list = ['cat', 'dog', 'moose'] ❶ >>> list(range(5)) ❷ Traceback (most recent call last): File " TypeError: 'list' object is not callable Присвоив списковое значение переменной list ❶ , мы потеряем исходную функцию list() . Попытка вызвать list() ❷ приведет к ошибке TypeError . Чтобы узнать, используется ли имя в Python, введите его в интерактивной оболочке или попро- буйте импортировать. Если имя не используется, вы получите ошибку NameError или ModuleNotFoundError . Например, в Python используются имена open и test , но не используются spam и eggs : >>> open >>> import test >>> spam Traceback (most recent call last): File " NameError: name 'spam' is not defined >>> import eggs Traceback (most recent call last): File " ModuleNotFoundError: No module named 'eggs' Наиболее часто заменяемые имена Python — all , any , date , file , format , hash , id , input , list , min , max , object , open , random , set , str , sum , test и type . Не берите их для своих идентификаторов. Другая распространенная проблема — присваивание файлам .py имен, совпада- ющих с именами сторонних модулей. Например, если вы установили сторонний модуль Pyperclip , но также создали файл pyperclip.py , команда import pyperclip Итоги 93 импортирует pyperclip.py вместо модуля Pyperclip . При попытке вызвать функцию copy() или paste() модуля Pyperclip вы получите ошибку, в которой говорится, что функции не существует: >>> # Запустите этот код с файлом pyperclip.py в текущем каталоге. >>> import pyperclip # Импортирует ваш файл pyperclip.py вместо настоящего. >>> pyperclip.copy('hello') Traceback (most recent call last): File " AttributeError: module 'pyperclip' has no attribute 'copy' Помните об опасности замены существующих имен в коде Python, особенно если вы неожиданно получаете такие сообщения об отсутствии атрибута. Худшие из возможных имен Имя data — ужасное и абсолютно бессодержательное, потому что буквально лю- бая переменная содержит данные (data). То же можно сказать об имени var — все равно что выбрать для собаки кличку Собака. Имя temp часто используется для переменных, содержащих временные данные, но и этот выбор плох: в конце концов, с точки зрения дзен-буддизма все переменные временны. К сожалению, несмотря на неоднозначность, эти имена встречаются часто; избегайте их использования в своем коде. Если вам нужна переменная для хранения статистического отклонения темпера- турных данных, используйте имя temperatureVariance . Не стоит и говорить, что выбор имени tempVarData будет неудачным. Итоги Выбор имен не имеет никакого отношения к алгоритмам или компьютерной тео- рии, и все же это важнейший фактор написания удобочитаемого кода. В конечном счете выбор имен, используемых в вашем коде, остается на ваше усмотрение, но вы должны учитывать существующие рекомендации. В PEP 8 вы найдете несколько соглашений о выборе имен — например, имена в нижнем регистре для модулей и имена в схеме Pascal для классов. Имена не должны быть слишком короткими или слишком длинными. Однако часто лучше сделать имя избыточным, чем недо- статочно содержательным. Имя должно быть лаконичным, но информативным. Оно должно легко находиться функцией поиска Ctrl-F . То, насколько просто можно найти выбранное имя, свиде- тельствует о его уникальности. Также задумайтесь, будет ли понятно это имя про- граммисту, который недостаточно хорошо владеет английским языком; избегайте 94 Глава 4.Выбор понятных имен шуток, каламбуров и культурных отсылок в своих именах; вместо этого выбирайте имена прямолинейные, традиционные и без отсылок к хохмам. Избегайте имен, уже используемых стандартной библиотекой Python, — таких как all , any , date , file , format , hash , id , input , list , min , max , object , open , random , set , str , sum , test и type . Их применение может создать трудноуловимые ошибки в вашем коде. Для компьютера неважно, какие имена вы выбрали, содержательные или общие. Имена упрощают чтение кода людьми, а не выполнение его компьютерами. Если ваш код хорошо читается, он будет понятным. Если он понятен, его легко изменить. А если его легко изменить, вам будет проще исправить ошибки или добавить новые возможности. Использование понятных имен — основополагающий фактор раз- работки качественного программного обеспечения. 5 Поиск запахов в коде Если выполнение кода приводит к аварийному завершению программы, с кодом очевидно что-то не так, но сбой — не един- ственный признак проблем в программах. Другие признаки могут свидетельствовать о наличии более коварных ошибок или неудобочитаемого кода. Подобно тому как запах газа может указывать на утечку, а запах дыма — на возможный пожар, запах кода представляет собой паттерн исходного кода, сигнализирующий о возможных ошибках. Наличие запаха кода не всегда означает, что проблема существует, но вам стоит по крайней мере проанализировать свою программу. В этой главе я расскажу о некоторых распространенных признаках, указывающих на проблемы в коде. На предотвращение ошибок требуется намного меньше времени и усилий, чем на их выявление, анализ и исправление. У каждого программиста найдется история о том, как он долгие часы отлаживал программу, а потом оказа- лось, что достаточно было исправить всего одну строку. Из-за этого даже при мимо- летном подозрении на потенциальную ошибку вы должны остановиться и лишний раз удостовериться, что вы не создаете себе проблемы в будущем. Конечно, запахи кода не всегда свидетельствуют о наличии ошибки. В конечном итоге именно вам решать, разбираться со своими подозрениями или не обращать на них внимания. Дублирование кода Самый распространенный источник ошибок — дублирование кода. Так называют любой исходный код, который создают копированием и вставкой фрагмента вашей программы. Например, следующая короткая программа содержит дублирующийся 96 Глава 5.Поиск запахов в коде код. Обратите внимание: она трижды задает пользователю вопрос о том, как тот себя чувствует (“How are you feeling?”): print('Good morning!') print('How are you feeling?') feeling = input() print('I am happy to hear that you are feeling ' + feeling + '.') print('Good afternoon!') print('How are you feeling?') feeling = input() print('I am happy to hear that you are feeling ' + feeling + '.') print('Good evening!') print('How are you feeling?') feeling = input() print('I am happy to hear that you are feeling ' + feeling + '.') Дублирование кода создает проблемы, потому что оно усложняет редактирование кода: изменение в одной копии должно быть внесено во все его дубли в программе. Если вы забудете где-то внести изменение или вы внесете разные изменения в раз- ных копиях, в вашей программе, скорее всего, возникнет ошибка. Проблема дублирования кода решается дедупликацией — код преобразуют так, чтобы он встречался только в одном месте программы — обычно в функции или цикле. В следующем примере я переместил дубликат в функцию, а затем повторно вызывал эту функцию: def askFeeling(): print('How are you feeling?') feeling = input() print('I am happy to hear that you are feeling ' + feeling + '.') print('Good morning!') askFeeling() print('Good afternoon!') askFeeling() print('Good evening!') askFeeling() В следующем примере дублирующийся код был перемещен в цикл: for timeOfDay in ['morning', 'afternoon', 'evening']: print('Good ' + timeOfDay + '!') print('How are you feeling?') feeling = input() print('I am happy to hear that you are feeling ' + feeling + '.') Также можно объединить два приема и совместить функцию с циклом: def askFeeling(timeOfDay): print('Good ' + timeOfDay + '!') «Магические» числа 97 print('How are you feeling?') feeling = input() print('I am happy to hear that you are feeling ' + feeling + '.') for timeOfDay in ['morning', 'afternoon', 'evening']: askFeeling(timeOfDay) Обратите внимание: код, который выдает сообщения «Good morning/afternoon/ evening!», похож, но не идентичен. В третьей версии программы я параметризовал код, чтобы исключить дублирование идентичных частей. Параметр timeOfDay и переменная цикла timeOfDay заменяют различающиеся части. Теперь, когда из кода были устранены дубликаты (лишние копии), необходимые изменения достаточно внести только в одном месте. Как и со всеми запахами кода, исключение дублирования не является неукосни- тельным правилом, которое следует соблюдать всегда. Как правило, чем длиннее фрагмент дубля или чем больше копий присутствует в вашей программе, тем се- рьезнее стоит задуматься об их устранении. Я не против того, чтобы скопировать код один и даже два раза. Но если в программе присутствуют три или четыре дубля, я обычно серьезно задумываюсь об их устранении. Иногда дедупликация не стоит затраченных усилий. Сравните первый пример кода в этом разделе с последним. Хотя код с дубликатами длиннее, он проще и прямо- линейнее. Пример без дубликатов делает то же самое, но с добавлением цикла, новой переменной цикла timeOfDay и новой функции с параметром, которому также присвоено имя timeOfDay Дублирование кода способно вызывать ошибки, потому что оно усложняет целост- ное изменение кода. Если программа содержит несколько одинаковых фрагментов, стоит поместить код в функцию или цикл, чтобы он вызывался только один раз. |