Чистыйкод дляпродолжающи х
Скачать 7.85 Mb.
|
ЧАСТЬ II ПЕРЕДОВЫЕ ПРАКТИКИ, ИНСТРУМЕНТЫ И МЕТОДЫ 3 Форматирование кода при помощи Black Форматированием кода называется его оформление посред- ством применения к нему определенного набора правил. Хотя форматирование кода не важно для компьютера, разбирающего вашу программу, оно существенно упрощает чтение кода людь- ми, а это необходимо для его успешного сопровождения. Если ваш код непонятен человеку (это может быть ваш коллега или даже вы сами), в нем трудно исправить ошибки или добавить новую функциональность. Форматирование кода не является чисто косметической операцией. Удобочитае- мость — одна из важнейших причин популярности языка Python. В этой главе рассматривается Black — система автоматического форматирования кода, в результате чего код становится последовательным и удобочитаемым без из- менения работы самой программы. Это полезный инструмент, потому что ручное форматирование в текстовом редакторе или IDE слишком утомительно. Сначала я приведу обоснования стилевых решений, которые принимает Black, а потом рас- скажу, как установить, использовать и настроить эту программу. Как потерять друзей и настроить против себя коллег Существует много разных вариантов написания кода, которые приводят к одному и тому же результату. Например, при записи списка можно ставить один пробел после каждой запятой и последовательно применять только одну разновидность кавычек: spam = ['dog', 'cat', 'moose'] Руководства по стилю и PEP 8 71 Но даже если в списке будут встречаться разные отступы и виды кавычек, код все равно останется действительным с точки зрения синтаксиса Python: spam= [ 'dog' ,'cat',"moose"] Программистам, предпочитающим первый вариант, может нравиться наглядное разделение элементов и единообразие в выборе кавычек. Но другие программисты иногда выбирают второй вариант, потому что они не хотят беспокоиться о подроб- ностях, которые не влияют на правильность работы программы. Новички часто не обращают внимания на форматирование, потому что они сосре- доточены на концепциях программирования и синтаксисе языка. Тем не менее им следует выработать привычку к качественному форматированию кода. Программи- рование само по себе достаточно сложно, и написание кода, понятного для других (и для вас в будущем), может весьма упростить его использование. Даже если вы начинаете писать программу в одиночку, не факт, что впоследствии вы не объединитесь с единомышленниками. Если несколько программистов, работающих над одним файлом с исходным кодом, пишут каждый в своем стиле, результат может выглядеть как невразумительное месиво, даже если код работает без ошибок. Или, что еще хуже, некоторые начнут переформатировать код напарников «под себя», что в итоге приведет к потере времени и вызовет споры. Решение о том, сколько пробелов должно следовать после запятой, один или ни одного, — дело личных предпочтений. Такие стилевые решения можно сравнить с выбором того, по левой или по правой стороне должны двигаться автомобили в стране; неважно, какую сторону считать правильной, — важно, чтобы все участники движения придерживались именно ее. Руководства по стилю и PEP 8 Чтобы ваш код хорошо читался, проще всего следовать требованиям, прописанным в руководстве по стилю — документе, определяющем набор правил форматирования, которые должны соблюдаться в программном проекте. PEP 8 (Python Enhancement Proposal 8) — одно из таких руководств, написанное командой разработчиков ядра Python. Но некоторые компании разработали собственные руководства по стилю. Текст PEP 8 можно найти в интернете по адресу https://www.python.org/dev/peps/pep- 0008/. Многие программисты Python рассматривают PEP 8 как авторитетный набор правил, хотя создатели PEP 8 утверждают иное. Раздел руководства «Тупой единый подход — беда мелких умов» напоминает читателю, что главной причиной для со- блюдения руководств по стилю является последовательность и удобочитаемость кода в рамках проекта, а не фанатичное соблюдение отдельных правил форматирования. В PEP 8 даже включен следующий совет: «Знайте, когда стоит действовать непо- следовательно, — иногда рекомендации по стилю попросту неприменимы. Если не 72 Глава 3.Форматирование кода при помощи Black уверены, руководствуйтесь здравым смыслом». Независимо от того, собираетесь вы выполнять рекомендации полностью, частично или вообще никак, документ PEP 8 стоит прочитать. Так как мы используем систему форматирования Black, наш код будет следовать правилам руководства по стилю Black, которое было разработано согласно реко- мендациям PEP 8. Не поленитесь изучить эти рекомендации, потому что Black не всегда имеется под рукой. Рекомендации для форматирования Python, о которых я расскажу в этой главе, также применимы к другим языкам, для которых автома- тические системы форматирования не всегда доступны. Мне не все нравится в том, как Black форматирует код, но это компромиссное ре- шение. Black использует правила форматирования, которые в целом устраивают программистов, чтобы они могли меньше тратить времени на споры и больше — на программирование. Горизонтальные отступы Для удобочитаемости вашего кода пустое место не менее важно, чем код, который вы пишете. Отступы помогают отделить обособленные части кода друг от друга, чтобы их было проще распознать. В этом разделе объясняются горизонтальные отступы — то есть пустые места в строке кода, в том числе в начале строки. Использование пробелов для создания отступов Для создания отступов в начале строки можно выбрать один из двух символов — пробел или табуляцию. И хотя годится любой вариант, для формирования отступов лучше использовать пробелы. Дело в том, что эти символы ведут себя по-разному. Пробел всегда выводится на экран как строковое значение, состоящее из одного пробела — ' ' . А символ табу- ляции, который выводится как строковое значение, содержащее служебный символ '\t' , не столь однозначен. Символы табуляции часто (хотя и не всегда) выводятся как переменное количество пробелов, чтобы текст начинался со следующей позиции табуляции. Позиции табуляции устанавливаются через каждые восемь пробелов по ширине текстового файла. Этот вариант продемонстрирован в следующем примере, где слова сначала разделяются пробелами, а затем символами табуляции: >>> print('Hello there, friend!\nHow are you?') Hello there, friend! How are you? >>> print('Hello\tthere,\tfriend!\nHow\tare\tyou?') Hello there, friend! How are you? Горизонтальные отступы 73 Так как символы табуляции представляют переменное количество пробелов, лучше не использовать их в исходном коде. Многие редакторы кода и IDE при нажатии клавиши TAB автоматически вставляют четыре или восемь пробелов вместо одного символа табуляции. Также не следует смешивать пробелы и символы табуляции для создания от- ступов в одном блоке кода. Использование обоих символов настолько часто становилось причиной коварных ошибок в более ранних программах Python, что в Python 3 код с такими отступами даже не выполняется — выдается исклю- чение TabError: inconsistent use of tabs and spaces in indentation (Непоследо- вательное применение табуляций и пробелов в отступах). Black автоматиче- ски преобразует все символы табуляции, используемые в отступах, в четыре пробела. Что касается длины каждого уровня отступов, в Python один уровень обычно обо- значен четырьмя пробелами. В следующем примере пробелы для большей нагляд- ности помечены точками: def getCatAmount(): ....numCats = input('How many cats do you have?') ....if int(numCats) < 6: ........print('You should get more cats.') Стандарт с четырьмя пробелами имеет практические преимущества перед другими альтернативами; в коде с восемью пробелами на уровень быстро возникает проблема ограничения длины строки, тогда как при уровне с двумя символами отступы не столь заметны. Программисты обычно не рассматривают другие значения (напри- мер, 3 или 6), потому что из-за привычки к двоичным вычислениям они предпо- читают работать со степенями двойки: 2, 4, 8, 16 и т. д. Отступы в середине строки Пробелы играют важную роль для визуального разделения разных частей кода. Если вы их не используете, ваш код может оказаться слишком плотным и нераз- борчивым. В следующих подразделах я расскажу о некоторых полезных правилах при создании отступов. Отделяйте операторы от идентификаторов одним пробелом Если операторы и идентификаторы не разделены ни одним пробелом, создается впечатление, что ваш код выполняется подряд. Например, в следующей строке операторы и переменные разделены пробелами: ДА: blanks = blanks[:i] + secretWord[i] + blanks[i + 1 :] 74 Глава 3.Форматирование кода при помощи Black А здесь все пробелы удалены: НЕТ: blanks=blanks[:i]+secretWord[i]+blanks[i+1:] В обоих случаях оператор + используется для сложения трех значений, но без про- белов может показаться, что + в blanks[i+1:] добавляет четвертое значение. Про- белы более наглядно показывают, что + является частью сегмента в значении blanks Не ставьте пробелы перед разделителями, ставьте один пробел после разделителей Элементы списков и словарей, а также параметры в определениях функций def разделяются запятыми. Не ставьте пробел перед этими запятыми, но поставьте один пробел после них, как в следующем примере: ДА: def spam(eggs, bacon, ham): ДА: weights = [42.0, 3.1415, 2.718] В противном случае у вас получится перенасыщенный код, который плохо читается: НЕТ: def spam(eggs,bacon,ham): НЕТ: weights = [42.0,3.1415,2.718] Не добавляйте пробелы перед разделителем, потому что этим вы привлекаете не- нужное внимание к символу-разделителю: НЕТ: def spam(eggs , bacon , ham): НЕТ: weights = [42.0 , 3.1415 , 2.718] Black автоматически вставляет пробелы после запятых и удаляет пробелы перед ними. Не ставьте пробелы до и после точек Python разрешает вставлять пробелы до и после точек, отмечающих начало атрибу- тов Python, но делать этого не стоит. Таким образом вы подчеркиваете связь между объектом и его атрибутом, как в следующем примере: ДА: 'Hello, world'.upper() Если же поставить пробелы до или после точки, объект и атрибут выглядят так, словно они не связаны друг с другом: НЕТ: 'Hello, world' . upper() Black автоматически удаляет пробелы вокруг точек. Горизонтальные отступы 75 Не ставьте пробелы после имен функций, методов и контейнеров Имена функций и методов легко узнаваемы, потому что за ними следует пара круг- лых скобок. Не ставьте пробел между именем и открывающей круглой скобкой. Обычно вызов функции записывается в следующем виде: ДА: print('Hello, world!') С добавлением пробела единый вызов функции выглядит так, словно в коде идут два раздельных фрагмента: НЕТ: print ('Hello, world!') Black удаляет все пробелы между именем функции или метода и его открывающей круглой скобкой. По той же причине не ставьте пробел перед открывающей квадратной скобкой для индекса, сегмента или ключа. Обычно при обращении к элементам типа «контей- нер» (список, словарь или кортеж) пробел между именем переменной и открыва- ющей квадратной скобкой не ставится: ДА: spam[2] ДА: spam[0:3] ДА: pet['name'] Как и в предыдущем случае, при добавлении пробела этот код выглядит как два раздельных фрагмента: НЕТ: spam [2] НЕТ: spam [0:3] НЕТ: pet ['name'] Black удаляет все пробелы между именем переменной и открывающей квадратной скобкой. Не ставьте пробелы после открывающих и перед закрывающими скобками Не должно быть пробелов, отделяющих круглые, квадратные или фигурные скобки от того, что внутри. Например, параметры в команде def или значения в списке должны начинаться и заканчиваться непосредственно после и перед круглыми и квадратными скобками: ДА: def spam(eggs, bacon, ham): ДА: weights = [42.0, 3.1415, 2.718] 76 Глава 3.Форматирование кода при помощи Black Не ставьте пробел после открывающих или перед закрывающими круглыми и ква- дратными скобками: НЕТ: def spam( eggs, bacon, ham ): НЕТ: weights = [ 42.0, 3.1415, 2.718 ] Добавление этих пробелов не делает код более удобочитаемым, поэтому они стано- вятся лишними. Black удаляет эти пробелы, если они присутствуют в вашем коде. Ставьте два пробела перед комментариями в конце строки Если вы добавляете комментарии в конце строки кода, поставьте два пробела между последним символом кода и символом # , начинающим комментарий: ДА: print('Hello, world!') # Вывод приветствия. Два пробела помогают отделить код от комментария. С одним пробелом (или еще хуже — без пробелов) заметить комментарий будет трудно: НЕТ: print('Hello, world!') # Вывод приветствия. НЕТ: print('Hello, world!')# Вывод приветствия. Black вставляет два пробела между последним символом кода и началом коммен- тария. В общем случае я не рекомендую размещать комментарии в конце строки кода, потому что строка становится слишком длинной для чтения на экране. Вертикальные отступы Под вертикальными отступами понимается вставка пустых строк между строками кода. Подобно тому как разбивка текста книги на абзацы членит текст и способству- ет его правильному и быстрому восприятию, вертикальные отступы группируют строки кода и визуально отделяют группы друг от друга. PEP 8 содержит ряд рекомендаций по вставке пустых строк в коде: руководство утверждает, что функции должны разделяться двумя пустыми строками, классы — двумя пустыми строками, а методы внутри класса — одной пустой строкой. Black автоматически следует этим правилам, вставляя или удаляя пустые строки в вашем коде. Например, фрагмент: НЕТ: class ExampleClass: def exampleMethod1(): pass def exampleMethod2(): Вертикальные отступы 77 pass def exampleFunction(): pass преобразуется к следующему виду: ДА: class ExampleClass: def exampleMethod1(): pass def exampleMethod2(): pass def exampleFunction(): pass Пример использования вертикальных отступов Однако Black не может решить, где должны располагаться пустые строки внутри ваших функций, методов или глобальной области видимости. Решение о том, какие из этих строк должны группироваться вместе, принимает программист. Для примера рассмотрим класс EmailValidator из файла validators.py фреймворка веб-приложений Django. Понимать, как работает этот код, необязательно. Обратите внимание на то, как пустые строки разделяют код метода __call__() на четыре группы: def __call__(self, value): if not value or '@' not in value: ❶ raise ValidationError(self.message, code=self.code) user_part, domain_part = value.rsplit('@', 1) ❷ if not self.user_regex.match(user_part): ❸ raise ValidationError(self.message, code=self.code) if (domain_part not in self.domain_whitelist and ❹ not self.validate_domain_part(domain_part)): # Проверить возможные имена IDN try: domain_part = punycode(domain_part) except UnicodeError: pass else: if self.validate_domain_part(domain_part): return raise ValidationError(self.message, code=self.code) 78 Глава 3.Форматирование кода при помощи Black Несмотря на отсутствие комментариев, описывающих эту часть кода, пустые строки показывают, что группы концептуально изолированы друг от друга. Первая группа ❶ проверяет символ @ в параметре value . Эта задача отличается от задачи второй группы ❷ , которая разбивает строку с адресом электронной почты из value на две новые переменные, user_part и domain_part . Третья ❸ и четвертая ❹ группы используют эти переменные для проверки пользовательской и доменной части адреса электронной почты соответственно. И хотя четвертая группа состоит из 11 строк (намного больше, чем в других груп- пах), все они решают одну задачу проверки домена из адреса электронной почты. Если вы чувствуете, что в действительности здесь несколько подзадач, разделите их пустыми строками. Программист, создававший эту часть Django, решил, что все строки проверки до- мена должны принадлежать одной группе, но другие программисты могут считать иначе. Так как это субъективное мнение, Black не изменяет вертикальные отступы внутри функций или методов. Рекомендации по использованию вертикальных отступов Python предоставляет одну возможность, о которой знают не все: точка с запятой ( ; ) может использоваться для разделения нескольких команд в одной строке. Это означает, что следующие две строки: print('What is your name?') name = input() можно записать в одну, разделив их точкой с запятой: print('What is your name?'); name = input() Как и при использовании запятых, перед точкой с запятой пробел не ставится, а после ее ставится один пробел. Для команд, завершающихся двоеточием (например, if , while , for , def и class ), однострочный блок, как вызов print() в следующем примере: if name == 'Alice': print('Hello, Alice!') можно записать в одной строке с командой if : if name == 'Alice': print('Hello, Alice!') Но хотя Python и разрешает разместить несколько команд в одной строке, это не считается хорошей практикой. Строки кода получаются слишком длинными Black: бескомпромиссная система форматирования кода 79 и содержат слишком много информации, что затрудняет их восприятие. Black раз- бивает такие команды на отдельные строки. Аналогичным образом можно импортировать несколько модулей одной командой import : import math, os, sys Тем не менее PEP 8 рекомендует разбить эту команду на несколько коротких ко- манд, по одной для каждого модуля: import math import os import sys Если модули импортируются в отдельных строках, вам будет проще заметить любые добавления или удаления импортированных модулей при сравнении изменений функцией diff системы контроля версий (системы контроля версий, такие как Git, рассматриваются в главе 12). PEP 8 также рекомендует объединять команды import в следующие три группы в указанном порядке. 1. Модули стандартной библиотеки Python: math , os , sys и т. д. 2. Сторонние модули: Selenium, Requests, Django и т. д. 3. Локальные модули, являющиеся частью программы. Эти рекомендации не являются обязательными, и Black не изменяет форматиро- вание команд import в вашем коде. |