Книга «Python. Чистый код для продолжающих»

20

Вы прошли обучающий курс программирования на Python или прочли несколько книг для начинающих. Что дальше? Как подняться над базовым уровнем, превратиться в крутого разработчика? «Python. Чистый код для продолжающих» — это не набор полезных советов и подсказок по написанию чистого кода. Вы узнаете о командной строке и других инструментах профессионального разработчика: средствах форматирования кода, статических анализаторах и контроле версий. Вы научитесь настраивать среду разработки, давать имена переменным и функциям, делающие код удобочитаемым, грамотно комментировать и документировать ПО, оценивать быстродействие программ и сложность алгоритмов, познакомитесь с ООП. Такие навыки поднимут вашу ценность как программиста не только в Python, но и в любом другом языке. Ни одна книга не заменит реального опыта работы и не превратит вас из новичка в профессионала. Но «Чистый код для продолжающих» проведет вас чуть дальше по этому пути: вы научитесь создавать чистый, грамотный, читабельный, легко отлаживаемый код, который можно будет назвать истинно питоническим.

Мифы о запахах кода

Некоторые запахи кода вообще не являются таковыми. В программировании полно полузабытых плохих советов, которые были вырваны из контекста или просуществовали так долго, что пережили свою полезность. Я виню в этом авторов технических книг, которые пытаются выдать свои субъективные мнения за передовые практики. Возможно, вы слышали, что некоторые из них являются причинами ошибок в коде, но в основном в них нет ничего плохого. Я называю их мифами о запахах кода: это всего лишь предупреждения, которые можно и нужно игнорировать. Рассмотрим несколько примеров.

Миф: функции должны содержать только одну команду return в самом конце

Идея «один вход, один выход» происходит из неправильно интерпретированного совета из эпохи программирования на языке ассемблера и FORTRAN. Эти языки позволяли войти в подпрограмму (структуру, сходную с функцией) в любой точке, в том числе и в середине, из-за чего в ходе отладки было труднее определить, какие части подпрограммы уже были выполнены. У функций такой проблемы нет (выполнение всегда начинается с начала функции). Но совет продолжал существовать и в конце концов трансформировался в «функции и методы должны содержать только одну команду return, которая должна находиться в конце функции или метода».

Попытки добиться того, чтобы в функции или методе была только одна команда return, часто приводят к появлению запутанных последовательностей команд if-else, которые создают гораздо больше проблем, чем несколько команд return. Функция или метод может содержать несколько команд return, ничего страшного в этом нет.

Миф: функции должны содержать не более одной команды try

«Функции и методы должны делать что-то одно» — в большинстве случаев это хороший совет. Но требовать, чтобы обработка исключений выполнялась в отдельной функции, значит заходить слишком далеко. Для примера рассмотрим функцию, которая проверяет, существует ли удаляемый файл:

>>> import os
>>> def deleteWithConfirmation(filename):
… try:
… if (input(‘Delete ‘ + filename + ‘, are you sure? Y/N’) == ‘Y’):
… os.unlink(filename)
… except FileNotFoundError:
… print(‘That file already did not exist.’)

Сторонники этого мифа возражают, что функции должны всегда иметь только одну обязанность. Обработка исключений — это обязанность, поэтому функцию нужно разбить на две. Они считают, что, если вы используете команду try-except, она должна быть первой командой и охватывать весь код функции:

>>> import os
>>> def handleErrorForDeleteWithConfirmation(filename):
… try:
… _deleteWithConfirmation(filename)
… except FileNotFoundError:
… print(‘That file already did not exist.’)

>>> def _deleteWithConfirmation(filename):
… if (input(‘Delete ‘ + filename + ‘, are you sure? Y/N’) == ‘Y’):
… os.unlink(filename)

Этот код излишне усложнен. Функция _deleteWithConfirmation() теперь помечена как приватная при помощи префикса _, который указывает, что функция никогда не должна вызываться напрямую — только косвенно, через вызов handleErrorForDeleteWithConfirmation(). Имя новой функции получилось неудобным, потому что она вызывается для удаления файла, а не для обработки ошибки при удалении.

Ваши функции должны быть простыми и компактными, но это не значит, что они всегда должны делать что-то одно (как бы вы это ни определяли). Вполне нормально, если ваши функции содержат несколько команд try-except и эти команды не охватывают весь код функции.

Миф: аргументы-флаги нежелательны

Логические аргументы функций или методов иногда называются аргументами-флагами. В программировании флагом называется значение, включающее бинарный выбор «включено — выключено»; для представления флагов часто используются логические значения. Такие настройки можно описать как установленные (True) или сброшенные (False).

Ложная уверенность в том, что аргументы-флаги функций чем-то плохи, основана на утверждении, что в зависимости от значения флага функция решает две совершенно разные задачи, как в следующем примере:

def someFunction(flagArgument):
if flagArgument:
# Выполнить код…
else:
# Выполнить совершенно другой код…

Действительно, если ваша функция выглядит так, лучше создать две разные функции, вместо того чтобы в зависимости от аргумента выбирать, какая половина кода функции должна выполняться. Но большинство функций с аргументами-флагами работает не так. Например, логическое значение может передаваться в ключевом аргументе reverse функции sorted() для определения порядка сортировки. Разбиение функции на две функции с именами sorted() и reverseSorted() не улучшит код (а также удвоит объем необходимой документации). Таким образом, мнение о нежелательности аргументов-флагов является мифом.

Миф: глобальные переменные нежелательны

Функции и методы напоминают мини-программы внутри вашей программы: они содержат код, включая локальные переменные, которые теряются при выходе из функции (подобно тому как переменные программы теряются после ее завершения). Функции существуют изолированно: либо их код выполняется правильно, либо содержит ошибку в зависимости от аргументов, переданных при вызове.

Но функции и методы, использующие глобальные переменные, отчасти утрачивают эту полезную изоляцию. Каждая глобальная переменная, используемая в функции, фактически становится дополнительным входным значением функции наряду с аргументами. Больше аргументов — больше сложности, что в свою очередь означает более высокую вероятность ошибок. Если ошибка проявляется в функции из-за неправильного значения глобальной переменной, это значение может быть задано в любой точке программы. Чтобы найти вероятную причину ошибочного значения, недостаточно проанализировать код функции или строку кода с вызовом функции; придется рассмотреть всю программу. Поэтому следует ограничить использование глобальных переменных.

Для примера возьмем функцию calculateSlicesPerGuest() в воображаемой программе partyPlanner.py, содержащей тысячи строк. Я включил номера строк, чтобы дать представление о размере программы:

1504. def calculateSlicesPerGuest(numberOfCakeSlices):
1505. global numberOfPartyGuests
1506. return numberOfCakeSlices / numberOfPartyGuests

Допустим, при выполнении этой программы возникает следующее исключение:

Traceback (most recent call last):
File «partyPlanner.py», line 1898, in <module>
print(calculateSlicesPerGuest(42))
File «partyPlanner.py», line 1506, in calculateSlicesPerGuest
return numberOfCakeSlices / numberOfPartyGuests
ZeroDivisionError: division by zero

В программе возникает ошибка деления на 0, за которую ответственна строка return numberOfCakeSlices / numberOfPartyGuests. Чтобы это произошло, переменная numberOfPartyGuests должна быть равна 0, но где numberOfPartyGuests было присвоено это значение? Так как переменная является глобальной, это могло произойти в любой из тысяч строк программы! Из данных трассировки мы знаем, что функция calculateSlicesPerGuest() вызывалась в строке 1898 нашей вымышленной программы. Взглянув на строку 1898, можно узнать, какой аргумент передавался для параметра numberOfCakeSlices. Но значение глобальной переменной numberOfPartyGuests могло быть присвоено где угодно до этого вызова функции.

Следует заметить, что применение глобальных констант не считается нежелательной практикой. Так как их значения никогда не изменяются, они не повышают сложность кода так, как это делают другие глобальные переменные. Когда программисты говорят о том, что глобальные переменные нежелательны, они не имеют в виду константы.

Глобальные переменные увеличивают объем работы по отладке — программист должен найти точку, в которой было присвоено значение, вызвавшее исключение. Из-за этого чрезмерное использование глобальных переменных нежелательно. Но сама идея о том, что все глобальные переменные плохи, неверна. Глобальные переменные часто используют в небольших программах и для хранения настроек, действующих во всей программе. Если без глобальной переменной можно обойтись, вероятно, лучше это сделать. Но утверждение «все глобальные переменные плохи» — слишком упрощенное и субъективное.

Миф: комментарии излишни

Плохие комментарии хуже, чем отсутствие комментариев. Комментарий с устаревшей или ошибочной информацией не разъясняет программу, а только создает лишнюю работу для программиста. Но такая локальная проблема иногда порождает тезис, что все комментарии плохи. Его апологеты считают, что каждый комментарий должен заменяться более понятным кодом вплоть до момента, когда в программе вообще не останется комментариев.

Комментарии пишутся на английском (или другом языке, на котором общается программист), что дает возможность гораздо более полно и подробно передавать информацию, чем с помощью имен переменных, функций или классов. Тем не менее написать лаконичные и эффективные комментарии непросто. Комментарии, как и код, приходится неоднократно редактировать. Наш код нам абсолютно понятен после того, как он написан, поэтому комментарии могут показаться бессмысленной и лишней работой. Тут и возникает мнение: комментарии излишни.

Но чаще на практике в программах слишком мало комментариев (или их нет вообще) или они так запутаны, что могут дезинформировать. Отказываться от комментариев на этом основании все равно что заявлять: «Перелеты через Атлантический океан безопасны только на 99,999991%, поэтому я лучше поплыву на пароходе».

Источник

Вам также могут понравиться