№ 2. Использование операторов печати для отладки

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

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

Запахи — это определенные структуры в коде, которые указывают на нарушение фундаментальных принципов дизайна и негативно влияют на качество дизайна. Запахи кода обычно не являются ошибками; они технически неверны и не мешают работе программы. Вместо этого они указывают на недостатки в дизайне, которые могут замедлить разработку или увеличить риск ошибок или сбоев в будущем. Неприятный запах кода может быть индикатором факторов, способствующих возникновению технического долга».
- [Источник: Википедия]

Простое присутствие запаха кода не приравнивается к ошибке, но его запах дает повод для беспокойства, который стоит исследовать. Все программисты согласятся с тем, что гораздо меньше усилий и затрат времени на предотвращение ошибки до того, как мы с ней столкнемся — избавление от запахов кода — один из способов гарантировать это.

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

# 1 Использование операторов печати для отладки

Операторы печати, вероятно, будут одной из первых встроенных функций, которые вы изучите в своем путешествии по программированию (т. е. первая программа большинства людей — print("Hello World")). В операторах печати нет ничего плохого по своей сути, за исключением того, что разработчики часто слишком к ним привязываются.

Как узнать, слишком ли вы привязаны к печатным заявлениям? Если вы используете его для отладки кода.

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

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

Я предпочитаю использовать файлы журналов, которые можно легко сделать в Python со встроенным модулем logging.

import logging 
logging.basicConfig(
    filename = "log_age.txt", 
    level = logging.DEBUG,
    format = "%(asctime)s - %(levelname)s - %(message)s") 
logging.debug("This is a log message.") 

#2 Дублирующийся код

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

print("What would you like for breakfast?")
breakfast = input()
print(f"One {breakfast} coming up")
print("What would you like for lunch?")
lunch = input()
print(f"One {lunch} coming up")
print("What would you like for dinner?")
dinner = input()
print(f"One {dinner} coming up")

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

Решение этой проблемы довольно простое: дедуплицируйте код. Мы можем легко сделать так, чтобы код появлялся в наших программах один раз, используя возможности функций или циклов.

def ask_meal(meal_of_the_day:str) -> str: 
    print(f"What would you like to eat for {meal_of_the_day}")
    meal = input()
    return f"One {meal} coming up"
    
meals_of_the_day = ["breakfast", "lunch", "dinner"]
for meal in meals_of_the_day: 
    ask_meal(meal)

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

#3 Волшебные числа

Иногда нам приходится использовать числа в нашем коде; Некоторые числа, которые мы используем в нашем исходном коде, могут вызвать сильное замешательство у других разработчиков — и у вас самих, если [или когда] вам придется пересматривать код в будущем. Эти числа называются магическими числами.

Термин «магическое число или магическая константа относится к анти-паттерну использования чисел непосредственно в исходном коде».
- [Источник: Википедия]

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

Рассмотрим следующий сценарий:

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X, 
    y, 
    0.3, 
    0.7, 
    25, 
    True, 
    None
)

В приведенном выше коде мы импортировали функцию train_test_split из Scikit-learn и создали ее экземпляр с некоторыми гиперпараметрами, которые, кажется, не имеют четкого значения.

Одним из способов сделать этот код более читабельным является добавление информативных комментариев, объясняющих, почему мы выбрали именно этот номер.

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X # features array, 
    y # labels, 
    0.3 # test size, 
    0.7 # train size, 
    25 # random state, 
    True # shuffle, 
    None # stratify
)

Гораздо более информативным способом устранения этого запаха кода является использование константы. Константы — это ценные данные, которые остаются неизменными при каждом выполнении программы. [Я не уверен в других языках, но] В Python мы обычно пишем константы заглавными буквами, чтобы сообщить другим (и напомнить себе), что их значения не должны изменяться после их первоначального присвоения.

Вы часто будете видеть константы, определенные в конфигурациях или как глобальные переменные в начале скрипта.

from sklearn.model_selection import train_test_split
TEST_SIZE = 0.3
TRAIN_SIZE = 0.7
RANDOM_STATE = 25
SHUFFLE = True
STRATIFY = None
X_train, X_test, y_train, y_test = train_test_split(
    X, 
    y, 
    TEST_SIZE, 
    TRAIN_SIZE, 
    RANDOM_STATE, 
    SHUFFLE, 
    STRATIFY
)

Насколько это читабельнее?

Примечание. Было бы также полезно вызвать имена параметров и установить их равными константе для дополнительной безопасности.

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

# 4 Оставить закомментированный код на месте

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

Проблема становится проблемой, когда программисты становятся ленивыми. Одним из таких примеров такой лени является комментирование кода, но оставление закомментированного кода на месте.

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

walk()
# run()
sprint()
stop()

Почему run() закомментировано? Когда можно будет раскомментировать run()? Если он не нужен, удалите код.

#5 Мертвый код

Весь мёртвый код в вашей программе должен быть устранён для экономии вычислений и памяти.

мертвый код — это часть исходного кода программы, которая выполняется, но результат которой никогда не используется в каких-либо других вычислениях.
— [Источник: Википедия]

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

# Code source: https://twitter.com/python_engineer/status/1510165975253069824?s=20&t=VsOWz55ZILPXCz6NMgJtEg
class TodoItem: 
    def __init__(self, state=None):
        self.state = state if state else -1 
 
    def __str__(self): 
        if self.state == -1: 
            return "UNDEFINED"
        elif self.state == 0: 
            return "UNSET" 
        else: 
            return "SET"

На первый взгляд этот код выглядит нормально, но в нем есть ошибка: код никогда не может быть установлен в 0, потому что оценка в переменной self.state установит 0 в False. Таким образом, установка состояния в 0 вернет UNDEFINED вместо UNSET.

class TodoItem: 
    def __init__(self, state=None):
        self.state = state if state is not None else -1 
 
    def __str__(self): 
        if self.state == -1: 
            return "UNDEFINED"
        elif self.state == 0: 
            return "UNSET" 
        else: 
            return "SET"

Примечание. Посмотрите это видео от Python Engineer, чтобы получить полное объяснение.

#6 Хранение переменных с числовыми суффиксами

Меня несколько раз ловил этот запах кода — до сих пор я от него полностью не избавился; Иногда нам может понадобиться отслеживать несколько экземпляров одного и того же типа данных. В таких обстоятельствах невероятно заманчиво повторно использовать имя и добавить к нему суффикс, чтобы оно сохранялось в другом пространстве имен в программе.

person_1 = "John" 
person_2 = "Doe"
person_3 = "Michael

Причина, по которой этот запах кода является запахом кода, заключается в том, что суффикс не служит хорошим описанием того, что содержится в каждой переменной, или различия между переменными. Он также не дает никаких указаний на то, сколько переменных присутствует в вашей программе — вы не хотите искать более 1000 строк кода, чтобы убедиться, что нет других чисел.

Лучшим решением будет:

people = ["John", "Doe", "Michael"] 

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

# 7 Ненужные классы (специфичные для Python)

Языки программирования, такие как Java, используют классы для организации кода в программе. Вместо этого Python использует модули. Таким образом, попытка использовать классы в Python так же, как в Java (для организации кода), не будет эффективной.

Код на Python не обязательно должен существовать в классе, и иногда использование классов может быть излишним.

Возьмем, к примеру, этот класс:

class Human:
    def __init__(self, name: str): 
        self.name = name
    def introduction(self): 
        return f"Hi, my name is {self.name}"
person = Human("Kurtis")
print(person.introduction()) 
"""
Hi, my name is Kurtis
"""

Главный определяющий фактор того, почему этому классу не обязательно быть классом, заключается в том, что он имеет только одну функцию. Как правило, класс не обязательно должен быть классом в Python, если он содержит только один метод или только статические методы. Вместо этого лучше написать функцию.

Чтобы узнать больше об этой концепции, ознакомьтесь с выступлением Джека Дидериха на PyCon 2012 о том, почему мы должны прекратить писать уроки.

Спасибо, что прочитали.

Свяжитесь со мной:
LinkedIn
Twitter
Instagram

Если вам нравится читать истории, подобные этой, и вы хотите поддержать меня, подумайте о том, чтобы стать участником Medium. Взяв 5 долларов в месяц, вы открываете неограниченный доступ к историям на Medium. Если вы воспользуетесь моей ссылкой для регистрации, я получу небольшую комиссию.

Уже вступил? Подпишитесь, чтобы получать уведомления, когда я опубликую.