Главная страница
Навигация по странице:

  • >>> nestedList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]] >>> flatList = [] >>> for sublist in nestedList: ... for num in sublist: ... flatList.append(num)

  • Пустые блоки except и плохие сообщения об ошибках

  • >>> try: ... num = input(Enter a number: ) ... num = int(num) ... except ValueError: ... pass Мифы о запахах кода107...

  • >>> try: ... num = input(Enter a number: ) ... num = int(num) ... except ValueError: ... print(An incorrect value was passed to int())

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

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

  • >>> import os >>> def deleteWithConfirmation(filename): ... try: ... if (input(Delete + filename + , are you sure Y/N) == Y): ... os.unlink(filename)

  • >>> import os >>> def handleErrorForDeleteWithConfirmation(filename): ... try: ... _deleteWithConfirmation(filename) ... except FileNotFoundError

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

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

  • Чистыйкод дляпродолжающи х


    Скачать 7.85 Mb.
    НазваниеЧистыйкод дляпродолжающи х
    Дата13.05.2023
    Размер7.85 Mb.
    Формат файлаpdf
    Имя файлаPython_Chisty_kod_dlya_prodolzhayuschikh_2022_El_Sveygart.pdf
    ТипДокументы
    #1127485
    страница10 из 40
    1   ...   6   7   8   9   10   11   12   13   ...   40
    >>> flatList = [num for sublist in nestedList for num in sublist]
    >>> flatList
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    Это списковое включение содержит два выражения for
    , но даже опытному разра- ботчику Python будет непросто понять его. Развернутая форма с двумя циклами for создает тот же деструктурированный список, но читается намного проще:
    >>> nestedList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]]
    >>> flatList = []
    >>> for sublist in nestedList:
    ... for num in sublist:
    ... flatList.append(num)
    ...
    >>> flatList
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    Включения представляют собой синтаксические сокращения, которые позволяют создавать компактный код. Тем не менее не увлекайтесь и не вкладывайте их друг в друга.
    Пустые блоки except и плохие сообщения
    об ошибках
    Перехват исключений — один из основных способов восстановить работоспособ- ность программы даже при возникновении проблем. Если в программе возникает исключение, но нет блока except для его обработки, программа Python аварийно завершается. Это может привести к потере несохраненной работы или к сохранению полузавершенного кода, который в дальнейшем может стать причиной еще более серьезных ошибок.
    Чтобы предотвратить фатальные ошибки, можно добавить блок except с кодом обработки ошибки. Но иногда бывает трудно решить, как должна обрабатываться ошибка, и у программиста возникает искушение оставить блок except пустым, с одной командой pass
    . Например, в следующем коде команда pass используется для создания блока except
    , который ничего не делает:
    >>> try:
    ... num = input('Enter a number: ')
    ... num = int(num)
    ... except ValueError:
    ... pass

    Мифы о запахах кода
    107
    ...
    Enter a number: forty two
    >>> num
    'forty two'
    В этом коде не происходит сбой, когда функции int()
    передается строка 'forty two'
    , потому что исключение
    ValueError
    , выдаваемое int()
    , обрабатывается командой except
    . Однако если ошибка попросту игнорируется, это может быть хуже, чем ава- рийное завершение. Программы аварийно завершаются, чтобы они не продолжали работать с некорректными данными или в неполном состоянии, что может породить более серьезные ошибки в будущем. При вводе нецифровых символов в нашем примере аварийное завершение не происходит. Но теперь переменная num содержит строку вместо целого числа, что вызовет проблемы при использовании переменной num
    . Наша команда except не столько обрабатывает ошибки, сколько скрывает их.
    Обработка исключений с плохими сообщениями об ошибках также относится к категории запахов кода. Взгляните на следующий пример:
    >>> try:
    ... num = input('Enter a number: ')
    ... num = int(num)
    ... except ValueError:
    ... print('An incorrect value was passed to int()')
    Enter a number: forty two
    An incorrect value was passed to int()
    В этом коде не происходит фатального сбоя, и это хорошо, но он не предоставляет пользователю достаточной информации о том, как решить проблему. Сообщения об ошибках предназначены для пользователей, а не для программистов. В данном случае сообщение не только содержит технические подробности, непонятные пользователю (например, ссылку на функцию int()
    ), но и не сообщает пользова- телю, как можно решить проблему. Сообщения об ошибках должны объяснять, что случилось и что пользователь должен сделать.
    Программисту проще быстро описать произошедшее (что бесполезно), вместо того чтобы подробно пояснить, что стоит сделать пользователю для решения проблемы.
    Но помните, что, если ваша программа не обрабатывает все возможные исключения, эта программа еще не завершена.
    Мифы о запахах кода
    Некоторые запахи кода вообще не являются таковыми. В программировании полно полузабытых плохих советов, которые были вырваны из контекста или про- существовали так долго, что пережили свою полезность. Я виню в этом авторов

    108
    Глава 5.Поиск запахов в коде технических книг, которые пытаются выдать свои субъективные мнения за передо- вые практики. Возможно, вы слышали, что некоторые из них являются причинами ошибок в коде, но в основном в них нет ничего плохого. Я называю их мифами о за- пахах кода: это всего лишь предупреждения, которые можно и нужно игнорировать.
    Рассмотрим несколько примеров.
    Миф: функции должны содержать только одну команду 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
    , она должна быть первой командой и охватывать весь код функции:

    Мифы о запахах кода
    109
    >>> 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()
    теперь помечена как приватная при помощи префикса
    _
    , который указывает, что функция никогда не должна вызываться напрямую — только косвенно, через вызов handleErrorFo rDeleteWithConfirmation()
    . Имя новой функции получилось неудобным, потому что она вызывается для удаления файла, а не для обработки ошибки при удалении.
    Ваши функции должны быть простыми и компактными, но это не значит, что они всегда должны делать что-то одно (как бы вы это ни определяли). Вполне нормаль- но, если ваши функции содержат несколько команд try
    - except и эти команды не охватывают весь код функции.
    Миф: аргументы-флаги нежелательны
    Логические аргументы функций или методов иногда называются аргументами-фла- гами. В программировании флагом называется значение, включающее бинарный выбор «включено — выключено»; для представления флагов часто используются логические значения. Такие настройки можно описать как установленные (
    True
    ) или сброшенные (
    False
    ).
    Ложная уверенность в том, что аргументы-флаги функций чем-то плохи, основана на утверждении, что в зависимости от значения флага функция решает две совер- шенно разные задачи, как в следующем примере:
    def someFunction(flagArgument):
    if flagArgument:
    # Выполнить код...
    else:
    # Выполнить совершенно другой код...
    Действительно, если ваша функция выглядит так, лучше создать две разные функ- ции, вместо того чтобы в зависимости от аргумента выбирать, какая половина кода функции должна выполняться. Но большинство функций с аргументами-флагами работает не так. Например, логическое значение может передаваться в ключевом ар- гументе reverse функции sorted()
    для определения порядка сортировки. Разбиение

    110
    Глава 5.Поиск запахов в коде функции на две функции с именами 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
    print(calculateSlicesPerGuest(42))
    File "partyPlanner.py", line 1506, in calculateSlicesPerGuest return numberOfCakeSlices / numberOfPartyGuests
    ZeroDivisionError: division by zero
    В программе возникает ошибка деления на 0, за которую ответственна строка return numberOfCakeSlices
    /
    numberOfPartyGuests
    . Чтобы это произошло, пере- менная numberOfPartyGuests должна быть равна
    0
    , но где numberOfPartyGuests было присвоено это значение? Так как переменная является глобальной, это могло произойти в любой из тысяч строк программы! Из данных трассировки мы знаем,

    Мифы о запахах кода
    111
    что функция calculateSlicesPerGuest()
    вызывалась в строке 1898 нашей вымыш- ленной программы. Взглянув на строку 1898, можно узнать, какой аргумент пере- давался для параметра numberOfCakeSlices
    . Но значение глобальной переменной numberOfPartyGuests могло быть присвоено где угодно до этого вызова функции.
    Следует заметить, что применение глобальных констант не считается нежела- тельной практикой. Так как их значения никогда не изменяются, они не повы- шают сложность кода так, как это делают другие глобальные переменные. Когда программисты говорят о том, что глобальные переменные нежелательны, они не имеют в виду константы.
    Глобальные переменные увеличивают объем работы по отладке — программист должен найти точку, в которой было присвоено значение, вызвавшее исключение.
    Из-за этого чрезмерное использование глобальных переменных нежелательно.
    Но сама идея о том, что все глобальные переменные плохи, неверна. Глобальные переменные часто используют в небольших программах и для хранения настроек, действующих во всей программе. Если без глобальной переменной можно обой- тись, вероятно, лучше это сделать. Но утверждение «все глобальные переменные плохи» — слишком упрощенное и субъективное.
    Миф: комментарии излишни
    Плохие комментарии хуже, чем отсутствие комментариев. Комментарий с уста- ревшей или ошибочной информацией не разъясняет программу, а только создает лишнюю работу для программиста. Но такая локальная проблема иногда порождает тезис, что все комментарии плохи. Его апологеты считают, что каждый комментарий должен заменяться более понятным кодом вплоть до момента, когда в программе вообще не останется комментариев.
    Комментарии пишутся на английском (или другом языке, на котором общается программист), что дает возможность гораздо более полно и подробно передавать информацию, чем с помощью имен переменных, функций или классов. Тем не менее написать лаконичные и эффективные комментарии непросто. Комментарии, как и код, приходится неоднократно редактировать. Наш код нам абсолютно понятен после того, как он написан, поэтому комментарии могут показаться бессмысленной и лишней работой. Тут и возникает мнение: комментарии излишни.
    Но чаще на практике в программах слишком мало комментариев (или их нет во- обще) или они так запутаны, что могут дезинформировать. Отказываться от коммен- тариев на этом основании все равно что заявлять: «Перелеты через Атлантический океан безопасны только на 99,999991%, поэтому я лучше поплыву на пароходе».
    О том, как пишутся эффективные комментарии, более подробно я расскажу в гла- ве 10.

    112
    Глава 5.Поиск запахов в коде
    Итоги
    Некие признаки, или запахи кода, указывают на то, что, вероятно, этот код можно написать лучше. Они не всегда требуют изменений, но лучше присмотреться к коду еще раз. Самый распространенный запах кода — дублирование — понуждает про- граммиста к тому, чтобы разместить код в функции или в цикле. Это гарантирует, что изменения будет достаточно внести только в одном месте. Еще один признак возможной проблемы — «магические» числа: загадочные значения, которые можно заменить константами с содержательными именами. Также стоит упомянуть о за- комментированном и мертвом коде, который никогда не выполняется компьютером.
    Он может сбить с толку программистов, которые впоследствии будут его читать.
    Лучше удалить такие фрагменты полностью и воспользоваться системой контроля версий (такой как Git), если позднее вам потребуется снова включить их в свою программу.
    Механизм отладочного вывода использует вызовы print()
    для вывода отладочной информации. Несмотря на простоту такого подхода, в долгосрочной перспективе для диагностики ошибок лучше положиться на отладчик и журнальные файлы.
    Переменные с числовыми суффиксами (
    x1
    , x2
    , x3
    и т. д.) стоит заменить одной переменной, содержащей список. В отличие от таких языков, как Java, в Python для группировки функций используются не классы, а модули. Класс, содержащий только один метод или только статические методы, можно также рассматривать как запах кода, предполагающий, что код лучше разместить в модуле, а не в классе.
    И хотя списковые включения предоставляют компактный механизм создания спи- сковых значений, вложенные списковые включения обычно очень плохо читаются.
    Кроме того, любые исключения, обрабатываемые пустыми блоками except
    , указы- вают на то, что вы просто игнорируете ошибку, вместо того чтобы обработать ее.
    Короткое, невразумительное сообщение об ошибке так же бесполезно для пользо- вателя, как и отсутствие сообщения.
    Наряду с этими признаками возможных проблем стоит упомянуть и мифы о них: советы по программированию, которые перестали быть актуальными или со време- нем стали нерациональными. Это и размещение только одной команды return или блока try
    - except в каждой функции, полный отказ от использования аргументов- флагов и глобальных переменных и вера в то, что комментарии в коде излишни.
    Конечно, как это обычно бывает со всеми советами в области программирования, запахи кода, о которых здесь шла речь, могут не пригодиться вам при реализации вашего проекта или не соответствовать вашим предпочтениям. Все весьма субъек- тивно. С появлением практического опыта вы сами сделаете выводы относительно того, какой код хорошо читается или является надежным, но, надеюсь, что вам все- таки пригодятся краткие рекомендации, изложенные в этой главе.

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

    114
    Глава 6.Написание питонического кода всем этим принципам, но их стоит держать в голове. Кроме того, «Дзен Python» — это скрытая шутка, которая появляется при выполнении команды import this
    :
    >>>
    1   ...   6   7   8   9   10   11   12   13   ...   40


    написать администратору сайта