Патрик Виафоре
Санкт-Петербург
«БХВ-Петербург»
2023
УДК 004.43
ББК 32.973.26-018.1
В41
Виафоре П.
В41 Надежный Python: Пер. с англ. — СПб.: БХВ-Петербург, 2023. —
352 с.: ил.
ISBN 978-5-9775-1174-2
Современные проекты на языке Python непрерывно растут, развиваются и при
этом неизбежно усложняются. Добиться надежности кода Python при сохранении
гибкости, понятности и расширяемости приложений позволяет система типов, ко-
торая в данной книге подробно исследована в рамках парадигмы ООП. Особое
внимание уделяется аннотированию и проверке типов, а также созданию пользова-
тельских специализированных типов. Продвинутые главы книги посвящены вопро-
сам тестирования, линтинга и обеспечения надежности программ на Python.
Для программистов
УДК 004.43
ББК 32.973.26-018.1
Группа подготовки издания:
Руководитель проекта Олег Сивченко
Зав. редакцией Людмила Гауль
Перевод с английского Михаила Райтмана
Компьютерная верстка Ольги Сергиенко
Оформление обложки Зои Канторович
© 2023 BHV
Authorized Russian translation of the English edition of Robust Python ISBN 9781098100667 © 2021 Kudzera, LLC.
This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish
and sell the same.
Авторизованный перевод с английского языка на русский издания Robust Python ISBN 9781098100667
© 2021 Kudzera, LLC.
Перевод опубликован и продается с разрешения компании-правообладателя O’Reilly Media, Inc.
Подписано в печать 05.12.22.
Формат 70×1001/16. Печать офсетная. Усл. печ. л. 28,38.
Тираж 1200 экз. Заказ №
"БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20.
Отпечатано с готового оригинал-макета
ООО "Принт-М", 142300, М.О., г. Чехов, ул. Полиграфистов, д. 1
ISBN 978-1-098-10066-7 (англ.) © Kudzera, LLC., 2021
ISBN 978-5-9775-1174-2 (рус.) © Перевод на русский язык, оформление.
ООО "БХВ-Петербург", ООО "БХВ", 2023
Оглавление
Введение ..........................................................................................................................11
Для кого предназначена книга?....................................................................................................12
О чем эта книга? ............................................................................................................................13
Условные обозначения..................................................................................................................15
Использование примеров кода .....................................................................................................15
Как с нами связаться .....................................................................................................................16
Благодарности ................................................................................................................................ 16
Глава 1. Введение в надежный Python ......................................................................19
Надежность кодовой базы ............................................................................................................19
Почему важна надежность?...............................................................................................22
Обмен полезной информацией.....................................................................................................23
Асинхронное взаимодействие...........................................................................................26
Примеры понятного кода на Python.............................................................................................29
Коллекции ...........................................................................................................................29
Итерации .............................................................................................................................32
Принцип наименьшего удивления....................................................................................34
Резюме ............................................................................................................................................35
ЧАСТЬ I. АННОТАЦИИ ТИПОВ..............................................................................37
Глава 2. Введение в типы Python ...............................................................................39
Что такое тип?................................................................................................................................39
Машинное представление .................................................................................................39
Семантическое представление ..........................................................................................41
Системы типов...............................................................................................................................44
Сильная и слабая типизации .............................................................................................44
Динамическая и статическая типизации ..........................................................................45
Неявная (утиная) типизация ..............................................................................................46
Резюме ............................................................................................................................................48
Глава 3. Аннотации типов ...........................................................................................49
Что такое аннотации типов? .........................................................................................................49
Использование аннотаций типов..................................................................................................53
Автодополнение .................................................................................................................53
Проверка типов...................................................................................................................53
Примеры найденных ошибок ............................................................................................55
Когда добавлять аннотации типа .................................................................................................57
Резюме ............................................................................................................................................57
6 | Оглавление
Глава 4. Ограничивающие типы ...............................................................................59
Аннотация Optional .......................................................................................................................59
Аннотация Union ...........................................................................................................................65
Тип-произведение и тип-сумма.........................................................................................67
Аннотация Literal ..........................................................................................................................69
Аннотация Annotated.....................................................................................................................70
Аннотация NewType ......................................................................................................................70
Аннотация Final.............................................................................................................................73
Резюме ............................................................................................................................................74
Глава 5. Коллекции.......................................................................................................75
Аннотирование коллекций ...........................................................................................................75
Однородные и разнородные коллекции ......................................................................................76
Аннотация TypedDict.....................................................................................................................80
Создание новых типов коллекций................................................................................................82
Дженерики ..........................................................................................................................83
Изменение существующих типов .....................................................................................85
Так же просто, как ABC.....................................................................................................88
Резюме ............................................................................................................................................90
Глава 6. Настройка проверки типов .........................................................................91
Инструмент проверки типов mypy...............................................................................................91
Параметры конфигурации .................................................................................................92
Поиск динамической типизации...............................................................................93
Нетипизированные функции.....................................................................................94
Отслеживание None/Optional ....................................................................................94
Отчеты mypy .......................................................................................................................95
Ускорение mypy .................................................................................................................96
Другие инструменты проверки типов..........................................................................................97
Pyre ......................................................................................................................................97
Запросы к кодовой базе .............................................................................................98
Pysa............................................................................................................................100
Pyright ................................................................................................................................103
Резюме ..........................................................................................................................................105
Глава 7. Внедрение проверки типов ........................................................................106
Поиск компромиссов...................................................................................................................107
Быстрое получение выгоды ........................................................................................................108
Поиск слабых мест ...........................................................................................................108
Стратегические фрагменты кода.....................................................................................109
Аннотирование только нового кода .......................................................................109
Аннотирование снизу вверх....................................................................................109
Аннотирование основной бизнес-логики ..............................................................110
Аннотирование часто меняющегося кода..............................................................110
Аннотирование сложного кода...............................................................................110
Использование инструментов .........................................................................................110
MonkeyType ..............................................................................................................112
Pytype ........................................................................................................................116
Резюме ..........................................................................................................................................117
Оглавление | 7
ЧАСТЬ II. ОПРЕДЕЛЕНИЕ ВАШИХ СОБСТВЕННЫХ ТИПОВ....................119
Глава 8. Пользовательские типы: перечисления..................................................121
Пользовательские типы...............................................................................................................121
Перечисления ...............................................................................................................................122
Тип Enum...........................................................................................................................124
Когда не надо их использовать .......................................................................................125
Дополнительные возможности...................................................................................................126
Автоматические значения ...............................................................................................126
Тип Flag.............................................................................................................................127
Целочисленные перечисления ........................................................................................129
Уникальные значения ......................................................................................................130
Резюме ..........................................................................................................................................131
Глава 9. Пользовательские типы: классы данных...............................................132
Примеры классов данных ...........................................................................................................132
Использование классов данных .................................................................................................136
Строковые представления ...............................................................................................136
Равенство...........................................................................................................................137
Реляционное сравнение ...................................................................................................138
Неизменяемость................................................................................................................139
Сравнение с другими типами .....................................................................................................141
Классы данных и словари................................................................................................141
Классы данных и TypedDict.............................................................................................142
Классы данных и namedtuple...........................................................................................142
Резюме ..........................................................................................................................................143
Глава 10. Пользовательские типы: классы ...........................................................144
Строение класса...........................................................................................................................144
Конструктор ...................................................................................................................... 145
Инварианты ..................................................................................................................................146
Нарушение инвариантов..................................................................................................148
Зачем нужны инварианты................................................................................................149
Информативные инварианты ..........................................................................................151
Для пользователей класса .......................................................................................152
При сопровождении кода ........................................................................................153
Инкапсуляция и поддержка инвариантов..................................................................................155
Инкапсуляция ...................................................................................................................155
Доступ к данным ..............................................................................................................155
Работа с данными .............................................................................................................158
Резюме ..........................................................................................................................................160
Глава 11. Определение своих интерфейсов ............................................................162
Проектирование естественного интерфейса .............................................................................163
Думай как пользователь...................................................................................................164
Разработка через тестирование...............................................................................164
Разработка через README.....................................................................................165
Юзабилити-тестирование........................................................................................166
Естественные взаимодействия ...................................................................................................167
Естественные интерфейсы в действии ...........................................................................167
8 | Оглавление
Магические методы..........................................................................................................173
Контекстные менеджеры .................................................................................................175
Резюме ..........................................................................................................................................177
Глава 12. Создание подтипов ....................................................................................178
Наследование ...............................................................................................................................178
Взаимозаменяемость ...................................................................................................................183
Рекомендации по проектированию ............................................................................................188
Композиция.......................................................................................................................189
Резюме ..........................................................................................................................................191
Глава 13. Протоколы ..................................................................................................193
Структурная и номинальная типизации ....................................................................................193
Пустой тип и Any ..............................................................................................................195
Использование Union .......................................................................................................195
Использование наследования..........................................................................................196
Использование миксинов.................................................................................................197
Протоколы .................................................................................................................................... 198
Создание протокола .........................................................................................................199
Расширенное использование ......................................................................................................200
Составные протоколы ......................................................................................................200
Протоколы времени выполнения....................................................................................201
Протоколы для модулей ..................................................................................................202
Резюме ..........................................................................................................................................203
Глава 14. Проверки во время выполнения с помощью pydantic .......................204
Динамическая конфигурация .....................................................................................................204
Библиотека pydantic.....................................................................................................................210
Валидаторы .......................................................................................................................212
Валидация и парсинг........................................................................................................215
Резюме ..........................................................................................................................................216
ЧАСТЬ III. РАСШИРЯЕМЫЙ КОД .......................................................................219
Глава 15. Расширяемость...........................................................................................221
Что такое расширяемость?..........................................................................................................221
Реструктуризация кода ....................................................................................................223
Принцип открытости/закрытости ..............................................................................................227
Поиск нарушений принципа ...........................................................................................228
Недостатки OCP ...............................................................................................................229
Резюме ..........................................................................................................................................230
Глава 16. Зависимости................................................................................................231
Взаимосвязи .................................................................................................................................232
Типы зависимостей .....................................................................................................................234
Физические зависимости .................................................................................................234
Логические зависимости..................................................................................................238
Временные зависимости ..................................................................................................240
Визуализация зависимостей .......................................................................................................241
Визуализация пакетов ......................................................................................................242
Визуализация импорта.....................................................................................................243
Оглавление | 9
Визуализация вызовов функций .....................................................................................243
Интерпретация графа зависимостей ...............................................................................245
Резюме ..........................................................................................................................................246
Глава 17. Компонуемость...........................................................................................248
Что такое компонуемость? .........................................................................................................248
Разделение политик и механизмов.............................................................................................252
Компонуемость в меньшем масштабе .......................................................................................255
Компонуемые функции....................................................................................................255
Декораторы...............................................................................................................256
Компонуемые алгоритмы ................................................................................................259
Резюме ..........................................................................................................................................262
Глава 18. Событийно-ориентированная архитектура .........................................263
Как это работает ..........................................................................................................................263
Недостатки ........................................................................................................................264
Простые события .........................................................................................................................266
Использование брокера сообщений................................................................................266
Шаблон наблюдателя.......................................................................................................268
Поток событий .............................................................................................................................270
Резюме ..........................................................................................................................................273
Глава 19. Подключаемый код...................................................................................274
Шаблонный метод .......................................................................................................................275
Шаблон стратегии .......................................................................................................................278
Архитектура плагинов ................................................................................................................279
Резюме ..........................................................................................................................................283
ЧАСТЬ IV. ВАША СТРАХОВОЧНАЯ СЕТКА ....................................................285
Глава 20. Статический анализ..................................................................................287
Линтинг ........................................................................................................................................287
Написание собственного плагина для Pylint..................................................................289
Разбор плагина..................................................................................................................291
Другие статические анализаторы...............................................................................................293
Проверки сложности ........................................................................................................294
Цикломатическая сложность Маккейба ................................................................294
Проверка отступов...................................................................................................296
Анализ безопасности........................................................................................................297
Утечка секретных данных.......................................................................................297
Проверка уязвимостей.............................................................................................297
Резюме ..........................................................................................................................................298
Глава 21. Стратегия тестирования ..........................................................................299
Определение вашей стратегии тестирования............................................................................299
Что такое тесты?...............................................................................................................300
Пирамида тестирования ..........................................................................................302
Снижение стоимости тестирования ...........................................................................................304
AAA-тестирование ...........................................................................................................304
Arrange (настройка предусловий)...........................................................................305
Annihilate (очистка ресурсов) .................................................................................308
10 | Оглавление
Act (действие)...........................................................................................................310
Assert (утверждение)................................................................................................311
Резюме ..........................................................................................................................................314
Глава 22. Приемочное тестирование .......................................................................315
Разработка через поведение (BDD)............................................................................................316
Язык Gherkin.....................................................................................................................316
Исполняемые спецификации...........................................................................................318
Дополнительные возможности behave ......................................................................................321
Параметризованные шаги................................................................................................321
Требования, составленные в виде таблиц ......................................................................321
Регулярные выражения ....................................................................................................322
Настройка жизненного цикла теста ................................................................................322
Использование тегов для выборочного запуска тестов ................................................323
Генерация отчетов............................................................................................................323
Резюме ..........................................................................................................................................325
Глава 23. Тестирование на основе свойств.............................................................326
Тестирование с помощью Hypothesis ........................................................................................326
Магия Hypothesis ..............................................................................................................330
Отличия от традиционных тестов...................................................................................331
Дополнительные возможности Hypothesis................................................................................332
Стратегии Hypothesis .......................................................................................................332
Генерирование алгоритмов .............................................................................................333
Резюме ..........................................................................................................................................337
Глава 24. Мутационное тестирование.....................................................................338
Что такое мутационное тестирование?......................................................................................338
Использование mutmut ................................................................................................................341
Исправление мутантов.....................................................................................................343
Отчеты о тестировании....................................................................................................343
Внедрение мутационного тестирования....................................................................................344
Проблема с показателем покрытия (и другими метриками) ........................................346
Резюме ..........................................................................................................................................347
Об авторе.......................................................................................................................351
Об обложке....................................................................................................................352
Введение
Еще в 2011 году известный американский разработчик и предприниматель Марк
Андриесен написал: «Программное обеспечение поглощает этот мир»1. И со време-
нем это становится все более очевидным. Программные системы продолжают
усложняться, и их можно найти во всех аспектах современной жизни. В центре это-
го «хищного чудовища» находится язык Python. Программисты называют Python
своим любимым языком2, и его используют везде: от веб-приложений до машинно-
го обучения, в инструментах для разработки и многого другого.
Но не все то золото, что блестит. По мере усложнения программных систем стано-
вится все труднее понять, как наши ментальные модели соотносятся с реальным
миром. Если это не контролировать, то программные системы раздуваются и ста-
новятся хрупкими, за что получили страшное прозвище — унаследованный код
(legacy code). Такие кодовые базы часто содержат предупреждения типа: «Не тро-
гайте эти файлы, иначе все сломается, и мы не знаем причину» и «Ох, только один
разработчик знал этот код, но два года назад он ушел на более высокооплачивае-
мую работу». Разработка программного обеспечения — молодая отрасль, но по-
добные выражения могут напугать как разработчиков, так и менеджеров.
Для того чтобы разрабатывать надежные системы, необходимо принимать обду-
манные решения. Титус Винтерс, Том Маншрек и Хайрам Райт в своей книге отме-
тили, что «разработка программного обеспечения — это программирование, интег-
рированное во времени»3. Ваш код может жить очень долго — я принимал участие
в проектах, код которых был написан, когда я еще учился в начальной школе. Как
долго прослужит ваш код? Дольше, чем вы будете работать на этом проекте (или
сопровождать его)? Как вы хотите, чтобы ваш код воспринимался через несколько
лет? Хотите ли вы, чтобы ваши преемники вспоминали о вас с благодарностью за
вашу дальновидность или ругали вас за сложности, которые вы им оставили?
Python — замечательный язык программирования, но иногда он усложняет сопро-
вождение. Сторонники других языков программирования говорят, что Python не
подходит для коммерческих продуктов и применим только для создания прототи-
пов. Однако большинство разработчиков лишь поверхностно знают и используют
1 https://www.wsj.com/articles/SB10001424053111903480904576512250915629460.
2 https://insights.stackoverflow.com/survey/2019.
3 Титус Винтерс, Том Маншрек и Хайрам Райт. Делай как в Google. Разработка программного
обеспечения. СПб.: Питер, 2021.
12 | Введение
этот язык, вместо того, чтобы изучить все инструменты и приемы для создания на-
дежного кода на Python. Из этой книги вы узнаете, как этого добиться. Мы разбе-
рем множество способов, как сделать код на Python чистым и удобным в сопрово-
ждении. Будущим специалистам понравится сопровождать ваш код, поскольку он
будет продуман так, чтобы упростить им работу. Так что обязательно прочтите эту
книгу, верьте в себя и создавайте надежное программное обеспечение, которое
прослужит долгие годы.
Для кого предназначена книга?
Эта книга будет полезна для каждого Python-разработчика, кто хочет писать рабо-
тающий код, который был бы при этом чистым и удобным в поддержке. Это не
учебник по введению в Python. Я рассчитываю, что вы уже писали на языке Python,
знаете, как работать с потоком управления и классами. Если вы еще не знакомы
с языком программирования Python, я рекомендую сначала прочитать книгу Марка
Лутца «Изучая Python»4.
Хотя я буду рассматривать здесь многие сложные темы, однако не ставлю своей
целью предоставить вам инструкции по использованию абсолютно всех функцио-
нальных возможностей Python. В данном случае функциональные возможности
Python будут служить неким фоном для более широкого разговора о надежности
кода и о том, как ваши решения влияют на дальнейшее сопровождение кода. Время
от времени я буду упоминать стратегии, которые вам следует использовать очень
редко, если они вообще когда-нибудь пригодятся. Это делается для объяснения
принципов надежности. Понимание того, как и почему мы принимаем решения при
написании кода, более важно, чем знание того, какие инструменты нужно исполь-
зовать в оптимальном сценарии. На практике оптимальный сценарий — это боль-
шая редкость. Используйте принципы, изложенные в этой книге, чтобы принимать
собственные решения для своей кодовой базы.
Эта книга не является справочником. Каждая глава — это отправная точка для об-
суждения в вашей команде разработчиков того, как лучше применить описанные
принципы. Создайте свой книжный клуб, дискуссионную группу или вместе собе-
ритесь в кафе. Научитесь налаживать общение в команде! В каждой главе я предла-
гаю темы для обсуждения. Когда вы станете разбирать ту или иную тему, я советую
вам проанализировать свою текущую кодовую базу. Поговорите со своими колле-
гами и используйте эти темы при обсуждении состояния вашего кода и рабочих
процессов. Если вам нужен справочник по языку Python, я настоятельно рекомен-
дую книгу Лучано Рамальо «Python. К вершинам мастерства»5.
Надежность системы можно оценивать по-разному — с точки зрения безопасности,
масштабируемости, отказоустойчивости или появления новых ошибок. Каждый из
этих аспектов надежности заслуживает отдельной книги. Моя книга сосредоточена
4 Марк Лутц. Изучаем Python. М.: Диалектика, 2019.
5 Лучано Рамальо. Python. К вершинам мастерства. М.: ДМК-Пресс, 2016.
Введение | 13
на том, чтобы разработчики, унаследовавшие ваш код, не вносили новых ошибок
в вашу систему. Я покажу вам, как общаться с будущими разработчиками, как
упростить им работу с помощью архитектурных шаблонов и как выявлять ошибки
в кодовой базе до того, как они попадут в готовый продукт. Основное внимание
в книге уделяется надежности вашей кодовой базы на Python, а не надежности ва-
шей системы в целом.
Я охвачу большой объем информации из самых разных областей программирова-
ния, включая программную инженерию, тестирование, функциональное и объект-
но-ориентированное программирование. Я не ожидаю, что у вас есть опыт работы
в этих областях. В некоторых разделах я объясняю все очень подробно с самого
начала, чтобы изменить ваше мнение об основах языка программирования. По
большей части это текст для среднего уровня специалистов.
Книга идеально подходит для:
разработчиков, которые в настоящее время работают с большой кодовой базой
и пытаются найти более эффективные способы взаимодействия со своими кол-
легами;
специалистов по первоначальному сопровождению кодовой базы, которые ищут
способы уменьшить нагрузку при сопровождении в будущем;
самоучек, которые хорошо владеют языком программирования Python, но хотят
лучше понимать, почему и что мы делаем;
начинающих специалистов в области информационных технологий, которым
нужны практические советы по разработке;
опытных разработчиков, которые ищут способ, как обосновать свой дизайн,
опираясь на основные принципы надежности.
Эта книга посвящена разработке программного обеспечения, которая растянута во
времени. Если большая часть вашего кода является прототипом или одноразовым
продуктом, то следование советам, приведенным в этой книге, потребует от вас
много лишней работы, которая не нужна для вашего проекта. Также эта книга не
подойдет, если ваш проект небольшой, например содержит менее ста строк кода
Python. Создание поддерживаемого кода действительно добавляет лишней работы,
в этом нет никаких сомнений. Но я расскажу вам, как это минимизировать. Если
ваш код живет дольше нескольких недель или вырос до значительного размера, вам
необходимо подумать об устойчивости вашей кодовой базы.
О чем эта книга?
Книга охватывает широкий круг знаний, который разделен на четыре части.
Часть I. Аннотации типов
Начнем с изучения типов в Python. Типы являются основой языка программиро-
вания, но обычно не рассматриваются подробно. Выбранные вами типы имеют
значение, поскольку они выражают конкретную идею. Мы рассмотрим аннота-
14 | Введение
ции типов и то, что аннотации могут сообщить другим разработчикам. Мы так-
же познакомимся с инструментами проверки типов и их использованием для
обнаружения ошибок на ранних стадиях разработки.
Часть II. Определение ваших собственных типов
После изучения типов Python мы сосредоточимся на создании своих собствен-
ных типов данных. Мы подробно рассмотрим перечисления, классы данных
и классы. Проанализируем, как принятые решения относительно типов могут
повысить или понизить надежность вашего кода.
Часть III. Расширяемый код
После того как мы разобрали, как лучше выражать свои идеи в коде, сосредото-
чимся на том, как сделать так, чтобы разработчики могли легко менять ваш код,
опираясь на прочную структуру вашей кодовой базы. В этой части мы рассмот-
рим расширяемость, зависимости и архитектурные шаблоны, позволяющие вно-
сить изменения в вашу систему с минимальными усилиями.
Часть IV. Ваша страховочная сетка
В последней части мы рассмотрим, как натянуть страховочную сетку, чтобы она
спасала ваших будущих коллег, если они все-таки ошибутся и упадут. При этом
вырастет их уверенность, если у них будет сильная, надежная система, которую
они могут легко адаптировать к новым требованиям. Мы рассмотрим различные
инструменты статического анализа и тестирование, которые помогут отлавли-
вать скрытые ошибки.
Эту книгу можно читать последовательно, от корки до корки, или выбирать только
нужные вам главы, при необходимости переходя по ссылкам на другие главы. Гла-
вы внутри одной части перекликаются друг с другом, но между частями книги свя-
зи почти нет.
Все примеры кода создавались с использованием Python 3.9.0, и я постараюсь обя-
зательно указать, если вам где-то понадобится конкретная или более поздняя вер-
сия Python (например, для использования классов данных нужен как минимум
Python 3.7).
На протяжении всей книги я буду использовать командную строку. Все команды я
запускаю в операционной системе Ubuntu, но большинство инструментов хорошо
работают и в операционных системах Mac и Windows. В некоторых случаях я по-
кажу, как определенные инструменты взаимодействуют с интегрированными сре-
дами разработки (IDE), такими как Visual Studio Code (VS Code). Большинство IDE
тоже используют параметры командной строки, поэтому все, что вы узнаете о ко-
мандной строке, можно найти в параметрах IDE.
В этой книге будут рассмотрены различные методы, которые повысят надежность
вашего кода. Однако в разработке программного обеспечения нет универсальных
способов для решения проблем. Компромиссы — это эффективный подход к реше-
нию проблемных вопросов, и методы, которые я описываю в книге, также не явля-
ются исключением. Я открыто расскажу о преимуществах и недостатках разных
Введение | 15
методов. Так как вы знаете о своих системах больше, чем я, то сможете выбрать
более подходящий инструмент для своей работы. А моя задача — повысить ваш
уровень знаний Python.
Условные обозначения
В книге используются следующие условные обозначения:
Курсивный шрифт
Курсивом выделены новые термины или то, на что надо обратить внимание.
Полужирный шрифт
Это URL, адреса электронной почты, имена файлов и их расширения.
Моноширинный шрифт
Используется для фрагментов кода, а также для ссылок на элементы программы
внутри обычного текста, например имена переменных или функций, типы дан-
ных, переменные среды, операторы и ключевые слова.
Моноширинный полужирный шрифт
Используется для выделения команд или другого кода, который нужно набирать
буквально.
Моноширинный курсивный шрифт
Используется для выделения комментариев, а также текста, который следует
заменить своими значениями или значениями, подходящими по контексту.
Такой значок означает совет или рекомендацию.
Это простое примечание.
А это указывает на важное предупреждение или предостережение.
Использование примеров кода
Дополнительные материалы (примеры кода, упражнения и т. д.) можно скачать из
репозитория https://github.com/pviafore/RobustPython.
Если у вас есть вопросы или возникла проблема с примером кода, пишите на элек-
тронную почту [email protected].
16 | Введение
Эта книга создана, чтобы помочь вам делать свою работу. Приведенные примеры
кода вы можете использовать в своих программах и документации. Вам не нужно
связываться с нами для получения разрешения, если вы не копируете значительную
часть кода. Например, для написания программы с использованием нескольких
фрагментов кода из этой книги разрешение не требуется. Однако для продажи или
распространения примеров из книг издательства O’Reilly нужно разрешение. Если
вы хотите процитировать текст или пример кода из книги, разрешение не нужно.
Но при использовании значительного количества примеров из этой книги в доку-
ментации по вашему продукту требуется получить разрешение.
Если вы считаете, что ваше цитирование примеров кода выходит за рамки добросо-
вестного использования, вы всегда можете обратиться к нам с вопросом по адресу
[email protected].
Как с нами связаться
Для этой книги создана специальная веб-страница, где мы перечисляем исправле-
ния, можно скачать примеры и дополнительную информацию. Адрес страницы:
https://oreil.ly/robust-python.
Комментарии вы можете отправлять по адресу [email protected]. Также
вы можете задавать технические вопросы по этой книге.
Новости и информацию о наших книгах и курсах можно найти на сайте
http://oreilly.com.
Благодарности
Я бы хотел выразить благодарность своей удивительной жене Кендалл. Она — моя
поддержка и опора, и я ценю все, что она сделала, чтобы у меня было время и место
для написания этой книги.
Ни одна книга не пишется изолированно, и эта книга не исключение. Я опираюсь
на плечи гигантов индустрии программного обеспечения и благодарен всем, кто
работал в ней до нас.
Я также хочу поблагодарить всех, кто участвовал в рецензировании этой книги,
чтобы убедиться, что все мои мысли изложены последовательно, а примеры —
ясные и понятные. Спасибо Брюсу Дж., Дэвиду К., Дэвиду П. и Дону П. за первые
отзывы и помощь в выборе направления для этой книги. Спасибо техническим обо-
зревателям Чарльзу Живру, Дрю Уинстелу, Дженнифер Уилкокс, Джордану Голд-
майеру, Натану Стоксу и Джессу Мейлзу за их бесценные отзывы, особенно тогда,
когда идеи еще были только в моей голове, а не на бумаге. И, наконец, спасибо
всем, кто прочитал черновой вариант книги и прислал мне свои комментарии, осо-
бенно Дэниелу С. и Франческе.
Я хочу поблагодарить всех, кто помог превратить мой черновой вариант в достой-
ное издание. Спасибо Джастину Биллингу, редактору этой книги, за то, что он
Введение | 17
очень ответственно подошел к своему делу и помог усовершенствовать мою кни-
гу. Спасибо Шеннон Терлингтон за корректуру, благодаря чему книга стала еще
лучше.
В конце я хочу выразить особую благодарность великолепной команде издательст-
ва O’Reilly. Спасибо Аманде Куинн за помощь в процессе подачи заявки и за то,
что помогла мне сосредоточиться на создании книги. Благодарю Кристен Браун
за то, что она сделала для меня невероятно легким весь процесс издания книги.
Благодарю Кейт Даллеа за создание четких иллюстраций. Кроме того, я хотел бы
выразить огромную благодарность редактору по развитию Саре Грей. Она помогла
мне написать книгу для широкой аудитории, но при этом позволила глубоко погру-
зиться в технические детали.
18 | Введение
ГЛАВА 1
Введение в надежный Python
Эта книга посвящена тому, как сделать ваш код на языке Python более управляе-
мым. По мере роста вашей кодовой базы вам придется придерживаться определен-
ных рекомендаций, приемов и стратегий, чтобы ее было легко сопровождать. Эта
книга поможет вам значительно сократить количество ошибок и облегчит работу
разработчиков. Вы научитесь более вдумчиво писать код и оценивать последствия
ваших решений. Когда начинают обсуждать то, как писать код, я всегда вспоминаю
мудрые слова сэра Чарльза Энтони Ричарда Хоара:
Существуют два способа составления проекта программного обеспечения:
один способ — сделать его таким простым, чтобы было очевидно, что недос-
татков нет, а другой — сделать его таким сложным, чтобы не было видно
очевидных недостатков. Первый способ гораздо сложнее1.
Эта книга посвящена в первую очередь разработке программных систем. Да, это
немного сложно, но беспокоиться не стоит. Я буду вашим наставником на пути по-
вышения уровня ваших знаний Python, чтобы, по словам Ч. Э. Р. Хоара, для вашего
кода было очевидно, что недостатков нет. В конечном счете эта книга посвящена
написанию надежного кода на языке Python.
В этой главе мы рассмотрим, что такое надежность и почему так важно о ней за-
ботиться. Разберем, какие плюсы и минусы есть у вашего способа написания кода,
и как можно его улучшить, чтобы другие разработчики могли легко понимать ваш
код. «Дзен Питона»2 гласит, что при разработке кода следует придерживаться пра-
вила: «Должен существовать один и, желательно, только один очевидный способ
сделать что-то». Вы узнаете, как определять, написан ли код таким очевидным спо-
собом, и что можно сделать, чтобы это исправить. Прежде всего, необходимо обра-
титься к основам. И для начала — что такое надежность?
Надежность кодовой базы
В каждой книге должно быть хотя бы одно словарное определение, так что я при-
веду его сразу. Толковый словарь Merriam-Webster предлагает множество опреде-
лений надежности (robustness)3:
1 Чарльз Энтони Ричард Хоар. «Старая одежда императора». Commun. ACM 24, 2 (февраль 1981 г.),
75–83. https://dl.acm.org/doi/10.1145/358549.358561.
2 https://peps.python.org/pep-0020/.
3 https://www.merriam-webster.com/dictionary/robust.
20 | Глава 1. Введение в надежный Python
1. Наличие или демонстрация силы или крепкого здоровья.
2. Наличие или проявление решимости, твердости или стойкости.
3. Прочно сформированный или сконструированный.
4. Способность работать без сбоев в различных условиях.
Это то, к чему нужно стремиться. Нам необходима крепкая система, которая удов-
летворяла бы требованиям пользователей на протяжении многих лет. Мы хотим,
чтобы наше программное обеспечение было надежным и выдержало испытание
временем. Нам нужна хорошо спроектированная система, построенная на прочном
основании. Что особенно важно — нам нужна система, способная работать без
сбоев. Система не должна становиться уязвимой при внесении изменений.
Принято думать, что программное обеспечение — это некое грандиозное сооруже-
ние, не приемлющее никаких изменений и являющееся образцом бессмертия.
В действительности все гораздо сложнее. Программные системы постоянно меня-
ются. Исправляются ошибки, улучшается пользовательский интерфейс, добавляет-
ся новая функциональность, потом она удаляется, потом снова появляется...
Фреймворки меняются, компоненты устаревают и возникают новые уязвимости.
Программное обеспечение эволюционирует. Разработка программного обеспечения
больше напоминает городское планирование, чем строительство какого-то статич-
ного здания. Как при постоянном изменении кодовой базы вы можете сделать свой
код надежным? Как создать прочную и устойчивую к ошибкам систему?
Правда в том, что вам нужно смириться с переменами. Ваш код будет порезан,
перекроен и переписан. Со временем ваш код значительно изменится — и это есте-
ственно. Примите это. Однако иногда простого изменения кода может быть недос-
таточно. Иногда целесообразнее удалить его и переписать заново, если он сильно
устарел. Это не означает, что ваш код теряет свою ценность, он еще долго будет
находиться в центре внимания. Ваша задача — сделать так, чтобы можно было лег-
ко переписать различные части системы. Как только вы смиритесь с недолговечно-
стью своего кода, вы начнете понимать, что недостаточно писать код без ошибок.
Вам необходимо сделать так, чтобы ваши преемники могли легко вносить измене-
ния в кодовую базу. Вот о чем эта книга.
Вы научитесь создавать надежные и прочные системы. Это не означает, что систе-
ма должна быть несгибаемой, как железный лом. Наоборот, она будет достаточно
пластичной. Ваш код должен быть, как ивовый прут, прочный и гибкий. Вашему
программному обеспечению придется сталкиваться с условиями, которые вы не
можете предугадать заранее. И кодовая база должна быть такой, чтобы ее легко
можно было адаптировать к новым обстоятельствам, т. к. вы не всегда будет ее со-
провождать. Ваши преемники должны знать, что они работают с надежной кодовой
базой. Код на Python необходимо писать таким образом, чтобы уменьшить количе-
ство ошибок, даже если будущие разработчики поделят его на части и перепишут.
Написание надежного кода — это сознательная забота о будущем. Вы хотите, что-
бы ваши преемники легко понимали ваш код и ваши идеи, а не тратили уйму вре-
мени на разбор кода и отладку. Вам необходимо правильно передать свои мысли,
Глава 1. Введение в надежный Python | 21
рассуждения и предостережения. Будущим разработчикам придется переделывать
ваш код под новые условия, и они не должны бояться, что какое-то мелкое измене-
ние может привести к тому, что все рухнет как карточный домик.
Проще говоря, вы не хотите, чтобы ваша система выходила из строя при любых
непредвиденных обстоятельствах. Тестирование и контроль качества — важные
составляющие любого проекта, но ни то ни другое не дает абсолютной гарантии.
Они больше подходят для выявления нереализованных требований и проверки
безопасности. А вам необходимо сделать так, чтобы программное обеспечение
выдержало испытание временем. Для этого необходимо писать чистый и легко
поддерживаемый код.
Чистый код должен быть понятным и лаконичным. Когда вы смотрите на строку
кода и понимаете ее смысл — это показатель чистого кода. Чем больше времени вы
тратите на отладку и чтение кода, чем чаще вы останавливаетесь и задумываетесь,
что же тут реализовано, тем хуже этот код. В чистом коде не применяются хитрые
приемчики, если это делает его нечитаемым для других разработчиков. По словам
Хоара, вы не хотите писать свой код таким вязким, чтобы его было трудно понять
при беглом просмотре.
Важность чистого кода
Чистота кода имеет первостепенное значение для надежной кодовой базы. Считайте,
что это обязательное условие для любого значимого проекта. Существуют конкретные
рекомендации для написания чистого кода, в том числе:
создание хорошо структурированного кода;
предоставление хорошей документации;
правильное именование переменных, функций и типов;
короткие и простые функции.
Хотя о важности чистого кода говорится на протяжении всей этой книги, я не буду уде-
лять много времени этим рекомендациям. Есть другие книги, в которых намного лучше
описаны правила чистого кода. Я рекомендую вам почитать «Чистый код» Роберта Мар-
тина (СПб.: Питер, 2018), «Программист-прагматик» Эндрю Ханта и Дэвида Томаса
(М.: Лори, 2012) и «Совершенный код» Стива Макконнелла (М.: Русская редакция, 2019).
Эти три книги значительно улучшили мои навыки и будут очень полезны тем, кто хочет
расти и развиваться как разработчик.
Конечно, вы должны стремиться к тому, чтобы писать чистый код, но будьте готовы ра-
ботать с неидеальными кодовыми базами. Разработка программного обеспечения —
дело сложное, и могут возникать ситуации, когда написание чистого кода невозможно по
разным причинам, как коммерческим, так и техническим. Используйте советы и реко-
мендации, приведенные в этой книге, чтобы стремиться создавать более чистый код.
Поддерживаемый код — это код, который легко сопровождать. Сопровождение
кода начинается сразу после первого коммита и продолжается до тех пор, пока на
проекте не останется ни одного разработчика. Разработчики будут исправлять
ошибки, добавлять функциональность, читать код, использовать его в других биб-
лиотеках и многое другое. Поддерживаемый код упрощает выполнение этих задач.
Программное обеспечение живет годами, если не десятилетиями. Сосредоточьтесь
на создании поддерживаемого кода уже сейчас.
22 | Глава 1. Введение в надежный Python
Вы не хотите быть причиной сбоя системы, независимо от того, работаете ли вы
над ней активно в данный момент или нет. Позаботьтесь об этом заранее, чтобы
ваша система выдержала испытание временем. Вам нужна стратегия тестирования,
которая будет служить страховочной сеткой, но в первую очередь вы должны ста-
раться избегать падения. Итак, учитывая все это, я предлагаю свое определение на-
дежности применительно к кодовой базе:
Кодовая база считается надежной, если она устойчива и не содержит ошибок,
несмотря на постоянно вносимые изменения.
Почему важна надежность?
Чтобы создать программу, выполняющую все поставленные задачи, нужно прило-
жить много усилий, и сложно определить момент, когда она будет готова полно-
стью. Основные этапы разработки спрогнозировать нелегко. Человеческие факто-
ры, такие как пользовательский интерфейс, обеспечение доступом и документация,
только увеличивают трудоемкость. Также добавьте сюда тестирование, которое
проверяет предсказуемое и непредсказуемое поведение, и вот у вас уже достаточно
длительный цикл разработки.
Цель программного обеспечения — приносить прибыль. В интересах каждой заин-
тересованной стороны получить максимум реализации как можно раньше. Учиты-
вая некоторую неопределенность со сроками разработки, часто возникает дополни-
тельное давление, чтобы оправдать ожидания. Мы все сталкивались с нереалистич-
ным графиком или с дедлайнами. К сожалению, многие инструменты, помогающие
сделать программное обеспечение более надежным, добавляются в цикл разработки
только на короткое время.
Существует неустранимое противоречие между быстротой разработки и повыше-
нием надежности кода. Если ваше программное обеспечение «достаточно хоро-
шее», зачем все усложнять? Чтобы ответить на этот вопрос, подумайте, как часто
будет выполняться этот фрагмент кода. Выпуск ценного программного продукта
обычно не является разовым действием. Очень редко бывает так, что готовая сис-
тема выпускается один раз и больше никогда не модифицируется. Программное
обеспечение постоянно развивается. Кодовая база должна быть подготовлена к то-
му, что работа с ней будет вестись постоянно и в течение длительного времени.
Именно поэтому необходимо использовать надежные методы разработки про-
граммного обеспечения. Если вы не можете добавлять функционал быстро и без
ущерба для качества, вам необходимо пересмотреть ваши методы работы, чтобы
сделать свой код более удобным в сопровождении.
Если вы выпустите свою систему с опозданием или с ошибками, вы понесете
дополнительные расходы. Оцените свою кодовую базу. Спросите себя, что будет,
если через год ваш код вдруг перестанет работать, потому что кто-то не смог в нем
разобраться. Сколько вы потеряете? Ваши потери могут измеряться деньгами, вре-
менем или даже жизнями. Спросите себя, что произойдет, если выпуск качествен-
ного продукта не будет сделан вовремя? Какие будут последствия? Если ответы на
эти вопросы вас пугают, то в этом есть и свои плюсы — работа, которую вы делае-
Глава 1. Введение в надежный Python | 23
те, очень ценна. Но это также подчеркивает и то, почему так важно стараться избе-
гать ошибок в будущем.
Над одной и той же кодовой базой одновременно могут трудиться несколько разра-
ботчиков. И разработчики иногда уходят с проекта, и приходят новые. Вам необхо-
димо найти способ делиться информацией с нынешними и будущими коллегами,
часто не имея возможности делать это при личных встречах. Будущие разработчи-
ки будут опираться на ваши решения. Каждый ложный путь, каждая кроличья нора
и каждое «бритье яка»4 будут мешать им, тормозить их работу. Вам необходимо
считаться с теми, кто придет после вас. Поставьте себя на их место. Эта книга по-
может вам позаботиться о ваших коллегах и о тех, кто будет поддерживать ваш
код. Также необходимо подумать о надежных инженерных решениях. Вам нужно
создавать код, который прослужит долго. Первый шаг к созданию такого кода —
это сделать его информативным и читабельным. Убедитесь, что будущим разра-
ботчикам будет понятна ваша реализация.
Обмен полезной информацией
Почему вы должны стараться писать чистый и поддерживаемый код? Почему так
важна надежность? Потому что вам нужно обмениваться информацией с другими
разработчиками. Ваша система не будет статичной. Код будет постоянно меняться.
Также необходимо учитывать, что со временем могут смениться и специалисты по
сопровождению. Ваша цель при написании кода — создать понятный код. Код
необходимо писать таким образом, чтобы другие разработчики могли быстро и эф-
фективно работать с ним. Для этого необходимо правильно донести свои идеи,
даже не встречаясь лично с другими специалистами.
Давайте взглянем на фрагмент кода из условно устаревшей системы. Я хочу, чтобы
вы оценили, сколько времени вам понадобится, чтобы понять, что делает этот код.
Ничего страшного, если вы не знакомы со всеми приведенными здесь концепциями
или вам покажется, что код слишком сложный (это сделано намеренно!).
# Взять рецепт блюда и изменить количество порций,
# пересчитав каждый ингредиент. Параметр recipe – это список,
# где первый элемент – количество порций, а остальные элементы –
# кортежи (название, кол-во, единица измерения),
# например ("flour", 1.5, "cup") – мука, 1.5 стакана
def adjust_recipe(recipe, servings):
new_recipe = [servings]
old_servings = recipe[0]
factor = servings / old_servings
recipe.pop(0)
4 Идиома «бритье яка» (yak shaving) обозначает ситуацию, когда вам постоянно приходится решать
посторонние проблемы, прежде чем приступить непосредственно к выполнению исходной задачи.
О происхождении этой идиомы: https://seths.blog/2005/03/dont_shave_that/.
24 | Глава 1. Введение в надежный Python
while recipe:
ingredient, amount, unit = recipe.pop(0)
# пожалуйста, используйте только легко измеряемые числа
new_recipe.append((ingredient, amount * factor, unit))
return new_recipe
Данная функция принимает на вход рецепт и настраивает каждый его ингредиент
под новое количество порций. Однако код вызывает много вопросов.
Для чего нужен метод pop()?
Что означает recipe[0]? Почему это называется old_servings (старые порции)?
Зачем добавлен комментарий о «легко измеряемых числах»?
Это, конечно, несколько сомнительный код на Python. Я не буду возражать, если
вам кажется, что его надо переписать. Измененный код выглядит намного лучше:
def adjust_recipe(recipe, servings):
old_servings = recipe.pop(0)
factor = servings / old_servings
new_recipe = {ingredient: (amount * factor, unit)
for ingredient, amount, unit in recipe}
new_recipe["servings"] = servings
return new_recipe
Разработчики, предпочитающие чистый код, вероятно, оценят второй вариант
(я определенно за него!). Никаких неопределенных циклов. Переменные не меня-
ются. Теперь я возвращаю словарь вместо списка кортежей. В зависимости от си-
туации все эти изменения можно рассматривать как положительные. Но, возможно,
я только что сделал три незаметные ошибки.
В исходном фрагменте кода я удалял исходный рецепт. Теперь этого не делает-
ся. Даже если это ни на что больше не влияет, я нарушил предположения вызы-
вающего кода.
Поменяв возвращаемое значение на словарь, я убрал возможность дублирования
ингредиентов в списке. Это может повлиять на рецепты, состоящие из несколь-
ких частей (например, из основного блюда и соуса), в которых используется
один и тот же ингредиент.
Если какой-то из ингредиентов вдруг называется servings (порции), я только что
создал конфликт имен.
Ошибки это или нет, зависит от двух взаимосвязанных моментов: как было задума-
но автором этого кода и как реализован вызывающий код. Разработчику нужно
было решить поставленную задачу, но я не уверен, почему он реализовал это имен-
но так. Зачем он вызывает pop()? Почему ингредиенты — это кортежи внутри спи-
ска? Почему вообще используется список? Вероятно, автор кода знал причину и
рассказал об этом своим коллегам в личном порядке. Его коллеги написали вызы-
вающий код на основе этих предположений, но со временем замысел автора был
забыт. В данном случае у меня есть два возможных пути.
Глава 1. Введение в надежный Python | 25
До внесения изменений проверить весь вызывающий код и убедиться, что эти
изменения ни на что не повлияют. Задача усложнится, если это API в общедос-
тупной библиотеке, которую могут использовать сторонние разработчики. Я бы
потратил на эту проверку очень много времени, что привело бы меня в отчаяние.
А можно внести изменения и посмотреть, какие будут последствия (жалобы
пользователей, неработающие тесты и т. д.). Если мне повезет, ничего страшно-
го не случится. В противном случае я бы потратил много времени на исправле-
ния, что тоже бы сильно меня огорчило.
Ни один из этих вариантов не кажется эффективным с точки зрения сопровождения
(особенно, если мне придется изменять этот код). Я не хочу тратить время впустую,
я хочу быстро разобраться с текущей задачей и перейти к следующей. Будет еще
хуже, если я не знаю, как вызывать этот код. Как вы обычно запускаете незнакомый
код? Вы ищете примеры вызова, копируете их, чтобы использовать в своем коде,
и тогда вы никогда не поняли бы, что первым элементом в вашем списке с ингре-
диентами должно быть количество порций.
Это такие нюансы, которые сильно озадачивают. Мы все видели что-то такое в бо-
лее крупных кодовых базах. Все эти кодовые базы были написаны с лучшими на-
мерениями. В самом начале функции всегда выглядят просто, но по мере развития
и при участии нескольких разработчиков код, как правило, изменяют, и первона-
чальный замысел теряется. Это верный признак того, что могут возникнуть про-
блемы с сопровождением. Нужно сразу писать код так, чтобы ваш замысел был
понятен.
Что было бы, если автор кода использовал более понятный стиль именования и
более подходящие типы? Как бы тогда мог выглядеть код?
def adjust_recipe(recipe, servings):
"""
Взять рецепт блюда и изменить количество порций
:param recipe: рецепт, который нужно настроить
:param servings: количество порций
:return Recipe: рецепт с указанием количества порции и
пересчитанным количеством ингредиентов
"""
# создание копии ингредиентов
new_ingredients = list(recipe.get_ingredients())
recipe.clear_ingredients()
for ingredient in new_ingredients:
ingredient.adjust_proportion(Fraction(servings, recipe.servings))
return Recipe(servings, new_ingredients)
Так код выглядит намного лучше, лучше документирован и ясен первоначальный
замысел. Автор выразил свои идеи прямо в коде. Из этого фрагмента можно сде-
лать следующие выводы.
Используется класс Recipe. Это позволяет абстрагироваться от некоторых опера-
ций. Предположим, что внутри самого класса есть инвариант, который позволя-
26 | Глава 1. Введение в надежный Python
ет дублировать ингредиенты. Подробнее о классах и инвариантах я расскажу
в гл. 10. Использование единых типов данных делает поведение функций более
очевидным.
Количество порций servings теперь является членом класса Recipe, а не первым
элементом списка, что приходилось постоянно учитывать. Это изменение значи-
тельно упрощает вызывающий код и предотвращает случайные ошибки.
Совершенно очевидно теперь, что из старого рецепта удаляются ингредиенты
(clear_ingredients()). Нет необходимости вызывать pop(0).
Ингредиенты — это отдельный класс, который работает с дробями5, а не с чис-
лами с плавающей точкой. Для всех участников проекта стало очевидно, что
здесь мы имеем дело с дробными числами, и теперь можно, например, устано-
вить максимальный знаменатель с помощью limit_denominator(), когда нужно
ограничить единицы измерения (вместо того, чтобы полагаться на комментарий
в коде).
Я изменил типы переменных, добавив классы рецепта и ингредиента. Я также
определил дополнительные методы (clear_ingredients(), adjust_proportion()), чтобы
был ясен смысл происходящего. Внеся эти изменения, я сделал поведение кода по-
нятным для будущих разработчиков. Им больше нет необходимости связываться со
мной, чтобы разобраться в коде. Даже не общаясь со мной, они легко его прочита-
ют. Это асинхронное взаимодействие в лучшем виде.
Асинхронное взаимодействие
Странно писать об этом в книге про Python, не упоминая методы async и await. Но я
боюсь, что мне придется обсуждать асинхронное взаимодействие в более сложных
условиях — в реальном мире.
Асинхронное взаимодействие означает, что источник информации и получатель
информации не пересекаются друг с другом и разнесены во времени. Это могут
быть как несколько часов, если разработчики из одного проекта находятся в разных
часовых поясах, так и годы, когда специалисты по сопровождению попытаются
вникнуть во внутреннюю работу кода. Вы не можете предсказать, когда кому-то
понадобится разобраться в логике вашего кода. К тому времени вы, может быть,
уже не будете работать с этой кодовой базой (или в этой компании).
Синхронное взаимодействие — это обмен информацией в режиме реального вре-
мени. Эта форма прямого общения — один из лучших способов коммуникации,
но, к сожалению, вы не всегда можете быть рядом с собеседником, чтобы ответить
на его вопросы.
Чтобы оценить доступность каждого способа обмена информацией, давайте рас-
смотрим два понятия: временная дистанция и затраты.
Временная дистанция — это то, насколько близко по времени должны находиться
собеседники, чтобы общение было плодотворным. Некоторые способы коммуника-
5 https://docs.python.org/3/library/fractions.html.
Глава 1. Введение в надежный Python | 27
ции подходят для передачи информации в реальном времени (короткая временная
дистанция). Другие способы эффективнее для обмена информацией спустя годы
(длинная временная дистанция).
Затраты — это оценка потраченных усилий на обмен информацией. Вы должны
оценить время и деньги, потраченные на коммуникацию. Для ваших будущих
получателей информации будет важно сравнение затрат, связанных с получением
информации, с ценностью, которую она для них представляет. Написание понятно-
го кода без каких-либо иных источников информации — это минимум, который
вам необходимо выполнить. Чтобы оценить затраты на создание дополнительных
источников информации, я учитываю следующее:
Обнаружение
Насколько легко найти эту информацию вне обычного рабочего процесса? На-
сколько эфемерны эти знания?
Расходы на сопровождение
Насколько точна информация? Как часто ее нужно обновлять? Что может слу-
читься, если она устареет?
Производственные затраты
Сколько времени и денег ушло на запись информации?
На рис. 1.1 я постарался оценить необходимые затраты и требуемую временную
дистанцию для некоторых наиболее распространенных способов коммуникации,
опираясь на свой собственный опыт.
Рис. 1.1. График затрат и временной дистанции для некоторых способов коммуникации
28 | Глава 1. Введение в надежный Python
График состоит из четырех областей:
Низкие затраты, короткая временная дистанция
В эту область попали способы коммуникации, которые имеют низкие производ-
ственные затраты, но не масштабируются во времени, такие как личное общение
и переписка в мессенджерах. Однако такой обмен информацией полезен только
тогда, когда собеседник вас активно слушает. Не стоит рассчитывать на эти спо-
собы, когда вы планируете передачу информации будущим специалистам.
Высокие затраты, короткая временная дистанция
Это дорогостоящие мероприятия, которые часто проводятся только один раз
(например, совещания или конференции). Такие мероприятия должны быть
очень информативными в момент проведения, но не представляют особой поль-
зы для будущих коммуникаций. Сколько раз вы были на совещании, которое ка-
залось пустой тратой времени? Это прямые расходы. Конференции тоже требу-
ют от каждого участника множественных затрат (потраченное время, место для
проживания, логистика и т. д.). К сожалению, анализ кода редко выполняют сра-
зу после его написания.
Высокие затраты, длинная временная дистанция
Эти способы коммуникации приводят к большим затратам, но со временем они
могут окупиться, потому что не требуют близкого общения. Переписка по e-mail
и agile-доски содержат большой объем информации, но не доступны другим со-
трудникам. Они отлично подходят для больших концепций, которые не нужда-
ются в частом обновлении. Но нецелесообразно пытаться найти какие-то уточ-
няющие мелочи среди огромного количества информации. Видеозаписи и про-
ектная документация отлично подходят для актуальной информации, но их
обновление обходится дорого. Не рассчитывайте на эти способы коммуникации,
чтобы объяснять повседневные решения.
Низкие затраты, длинная временная дистанция
Эти способы требуют низких производственных затрат. В эту категорию попа-
дают комментарии к коду, история коммитов в системе контроля версий и
внутренняя документация проекта — все, что касается кода, который мы
пишем. Даже спустя годы пользователи смогут просматривать эту информацию.
Все, что разработчик делает ежедневно в рамках рабочего процесса, можно лег-
ко обнаружить. Данные способы обмена информацией в первую очередь подхо-
дят для тех, кто будет сопровождать ваш исходный код. Однако сам код — один
из лучших инструментов для документирования, т. к. это единственный досто-
верный источник информации о том, что делает ваша система.
Тема для обсуждения
График на рис. 1.1 очень обобщенный. Проанализируйте способы коммуникации,
которые используются в вашей организации. Где бы они располагались на графи-
ке? Насколько легко найти актуальную информацию? Насколько дорого обходится
создание информации? Ответив на эти вопросы, у вас может получиться немного
другой график, но единственный достоверный источник — это созданное вами про-
граммное обеспечение.
Глава 1. Введение в надежный Python | 29
Недорогие способы коммуникации на длинной временной дистанции — лучшие
инструменты для передачи информации в будущее. Необходимо стремиться мини-
мизировать затраты за создание информации и на ее поиск. В любом случае вам
приходится создавать работающее программное обеспечение, поэтому самый де-
шевый способ — сделать ваш код основным источником полезной информации.
Ваша кодовая база — лучший вариант для четкого выражения ваших идей, реше-
ний и обходных путей.
Однако чтобы это утверждение было верным, код должен быть легко читаемым.
Ваши замыслы должны быть четко выражены в коде. Ваша цель — минимизиро-
вать время, необходимое другим разработчикам для того, чтобы понять ваш код.
В идеале им не нужно читать вашу реализацию, а только сигнатуры функций. Бла-
годаря использованию правильных типов, комментариев и имен переменных долж-
но быть совершенно очевидно, что делает ваш код.
Самодокументируемый код
Из рис. 1.1 можно сделать неправильный вывод: «Самодокументируемый код — это
все, что мне нужно!». Код должен полностью сам документировать то, что он делает, но
он не может заменить все способы коммуникации. Например, система контроля версий
содержит полезную историю изменений кода. В проектной документации описаны об-
щие проектные решения, которые не являются частными для какого-либо файла кода.
Совещания (если они эффективные) могут быть важны для синхронизации задач по
реализации проекта. Конференции отлично подходят для обмена идеями с большой
аудиторией. Хотя в этой книге основное внимание уделено написанию надежного кода,
стоит обратить внимание и на другие полезные способы коммуникации.
Примеры понятного кода на Python
Теперь, когда я рассказал, как доносить свои замыслы и какое значение это имеет,
давайте рассмотрим примеры на языке Python. Как убедиться, что вы правильно
выражаете свои идеи в коде? Я рассмотрю два разных примера того, как выбранное
решение влияет на читабельность кода, — при выборе типа коллекции и вида ите-
рации.
Коллекции
Когда вы используете коллекцию, вы уже передаете конкретную информацию. Для
поставленной задачи вам необходимо выбрать подходящую коллекцию. В против-
ном случае специалисты по сопровождению поймут ваш замысел неправильно.
Рассмотрим пример кода, который принимает список кулинарных книг и считает
для каждого автора количество написанных им книг:
def create_author_count_mapping(cookbooks: list[Cookbook]):
counter = {}
for cookbook in cookbooks:
if cookbook.author not in counter:
counter[cookbook.author] = 0
30 | Глава 1. Введение в надежный Python
counter[cookbook.author] += 1
return counter
О чем вам говорит такое использование коллекций? Почему я не передаю в функ-
цию словарь или множество? Почему я не возвращаю список? Исходя из того, как я
использовал коллекции, вы можете предположить следующее.
Я передаю в функцию список кулинарных книг. В списке могут быть дубликаты
книг (например, после пересчета книг в магазине, представленных в нескольких
экземплярах).
Я возвращаю словарь. Разработчики, использующие этот код, смогут найти кон-
кретного автора или перебрать весь словарь. При этом мне не нужно беспоко-
иться о наличии дублирующихся записей в возвращаемой коллекции.
Что делать, если мне нужно сообщить, что в эту функцию нельзя передавать дуб-
ликаты? Список уже будет неправильным выбором. Вместо этого необходимо
использовать множество, чтобы пользователи кода поняли, что этот код не обраба-
тывает дубликаты.
Выбор типа коллекции расскажет другим разработчикам о ваших конкретных иде-
ях. Далее приведен список распространенных типов коллекций и особенности их
использования.
Список (list)
Это коллекция, для которой можно выполнить перебор значений. Список изме-
няем: его можно изменить в любой момент. Очень редко возникает необходи-
мость получить определенные элементы из середины списка (используя стати-
ческий индекс). Также в списке могут встречаться повторяющиеся элементы.
Кулинарные книги в магазине могут быть записаны в виде списка.
Строка (str)
Неизменяемая коллекция символов. Название кулинарной книги — это строка.
Генератор
Это коллекция, которую можно перебирать, но нельзя индексировать. Доступ
к каждому элементу выполняется «лениво» (не обсчитывая заранее, по мере на-
добности), поэтому на каждую итерацию цикла может потребоваться время
и/или ресурсы. Генераторы отлично подходят для дорогостоящих вычислений
или бесконечных коллекций. Онлайн-приложение может возвращать рецепты
в виде генератора. Вам нет необходимости сразу выводить все рецепты в мире,
если пользователь собирается просмотреть только первые 10.
Кортеж (tuple)
Это неизменяемая коллекция. Поэтому более вероятно, что потребуется доступ
к элементам из середины кортежа (с помощью индексов или распаковки). Кор-
теж очень редко перебирается. Информация о конкретной кулинарной книге
может быть представлена в виде кортежа, например: (cookbook_name, author,
page_count).
Глава 1. Введение в надежный Python | 31
Множество (set)
Это итерируемая коллекция, не содержащая дубликатов. Вы не можете пола-
гаться на порядок следования элементов. Ингредиенты в рецепте могут хранить-
ся как множество.
Словарь (dict)
Это соотношения типа «ключ : значение». Ключи уникальны во всем словаре.
Словари обычно перебираются или индексируются с помощью динамических
ключей. Предметный указатель в кулинарной книге — отличный пример соот-
ношения «ключ : значение» (тема : номер страницы).
Не используйте в своем коде неправильный тип коллекции. Я очень часто сталки-
вался со списками, в которых не должно было быть дубликатов, или словарями,
которые фактически не использовали сопоставления ключей со значениями. Каж-
дый раз, когда возникает конфликт между тем, что вы имели в виду, и тем, как реа-
лизовали это в коде, вы создаете очень большую нагрузку для дальнейшего сопро-
вождения вашего кода. Специалисты по сопровождению должны будут догады-
ваться, что вы имели в виду на самом деле, а затем исправлять свои ошибки, если
они сделали неправильное предположение.
Динамическое и статическое индексирование
В зависимости от выбранного типа коллекции вы можете захотеть использовать ста-
тический индекс. Статический индекс — это когда вы используете постоянный литерал
для индексации элементов коллекции, например my_list[4] или my_dict["Python"]. Он
редко используется для списков и словарей, потому что у вас нет никакой гарантии, что
в коллекции есть элемент, к которому вы обращаетесь по этому индексу, из-за динами-
ческого характера этих коллекций. Если вы ищете определенные поля в данных типах
коллекций, это хороший признак того, что вам нужно использовать здесь пользователь-
ский тип (см. гл. 8–10). Статическое индексирование в кортеже безопасно, потому что
кортеж имеет фиксированный размер. Множества и генераторы никогда не индексиру-
ются.
Из всех этих правил могут быть следующие исключения:
получение первого или последнего элемента последовательности (my_list[0] или
my_list[-1]);
использование словаря в качестве промежуточного типа данных, например при чте-
нии JSON или YAML;
работа с последовательностью, в которой есть фиксированные фрагменты (напри-
мер, всегда обработка после третьего элемента или проверка определенного симво-
ла в форматированной строке);
из-за производительности при использовании определенного типа коллекции.
Динамическое индексирование выполняется каждый раз, когда вы индексируете кол-
лекцию с помощью переменной, значение которой не известно до времени выполнения.
Такое индексирование лучше подходит для списков и словарей. Вы сами убедитесь
в этом при переборе этих коллекций или при поиске определенного элемента с по-
мощью функции index().
350 | Часть IV. Ваша страховочная сетка
Об авторе
Патрик Виафоре работает в IT-сфере более 14 лет, создавая и поддерживая крити-
чески важное программное обеспечение, такое как системы для обнаружения мол-
ний, для телекоммуникаций, а также операционные системы. Опыт работы со ста-
тически типизированными языками повлиял на его подход к работе с динамической
типизацией, в том числе в Python, и на то, как можно сделать такой код более безо-
пасным и надежным. Патрик — организатор митапа HSV.py, где рассматриваются
разные проблемы Python и всегда рады как новичкам, так и экспертам. Он считает
своей целью сделать обсуждение компьютерных технологий и программной инже-
нерии более доступным для сообщества разработчиков.
В свое время Патрик работал над разработкой инструментов для развертывания
образов Ubuntu для поставщиков облачных сервисов. Он также проводит консуль-
тации по программному обеспечению в рамках своей компании Kudzera, LLC.
Об обложке
Животное, изображенное на обложке книги «Надежный Python», — это нильский
крокодил (Crocodylus niloticus), обитающий в Африке к югу от Сахары вблизи пре-
сноводных озер, рек и болот. Это агрессивный хищник, который охотится, погру-
зившись в воду и поджидая приближающееся водное или наземное животное.
Нильский крокодил питается разнообразной добычей, включая птиц, рыб, млеко-
питающих и других рептилий. Они также опасны для человека: ежегодно насчиты-
вается сотни нападений и смертей.
Крокодилы обладают невероятно большой силой укуса, а также коническими зуба-
ми, предназначенными для того, чтобы крепко держать свою добычу, а не рвать
плоть. Это позволяет им быстро хватать даже крупных животных и держать их под
водой до тех пор, пока они не утонут. Нильский крокодил — самый большой кро-
кодил в Африке, в среднем вырастает до 3–5 м в длину и весом 200–700 кг (самки
примерно на 30 % меньше самцов). У них темная спина и пестрые желто-зеленые
бока, благодаря которым крокодилы маскируются в воде.
Нильские крокодилы — социальные животные, у которых есть общие места для
отдыха и охоты, которые довольно просторны, чтобы питаться в одиночку. Самки
откладывают от 25 до 80 яиц и какое-то время защищают свое потомство (хотя
молодые крокодилы охотятся самостоятельно). Несмотря на заботы самки, по ста-
тистике, только 10 % яиц вылупляются, а 1 % доживает до взрослого возраста из-за
хищничества со стороны нильских варанов, водоплавающих птиц и других кроко-
дилов.
Многие животные, изображенные на обложках издательства O’Reilly, находятся
под угрозой исчезновения. Все они важны для мира.
Иллюстрация на обложке создана Карен Монтгомери на основе черно-белой гра-
вюры из старой энциклопедии Meyers Kleines Lexicon.