Python

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

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

Рассмотрим пять простых способов рефакторинга кода для начинающих в Python с пояснениями преимуществ и демонстрацией кода до и после.

Определение функций для повторяющихся задач

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

Функции предоставляют множество преимуществ, но с точки зрения рефакторинга сокращается объём кода, улучшается читаемость. Кроме того, если нужно обновить задачу, делать это понадобится только на одном участке — в определении функции.

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

До

while True:
  length = int(input("Enter the length: "))
  if length > 0:
    break

while True:
  width = int(input("Enter the width: "))
  if length > 0:
    break

print("The area is", length * width)

Заметьте, что два блока почти идентичны. А что, если понадобится вычислить объём куба? Скорее всего мы скопируем цикл while, изменив имя переменной и приглашение к вводу.

После

def input_positive_integer(prompt):
  while True:
    input_value = int(input(prompt))
    if input_value > 0:
      return input_value

length = input_positive_integer("Enter the length: ")
width = input_positive_integer("Enter the width: ")
print("The area is", length * width)

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

Конструктор списков вместо цикла for

Конструктор списков — один из самых легендарных конструкторов Python. Конструктор списков является более чистой и быстрой реализацией в сравнении с многими простыми циклами for.

Существует множество преимуществ рефакторинга циклов for в конструкторы списков: код будет выполняться быстрее, существующие данные не будут меняться, кроме того, конструктор списков проще читать, как только вы к нему привыкаете.

Давайте рассмотрим два примера, один простой и второй немного посложнее.

В первом примере выберем нечётные числа из списка целых чисел.

До

my_numbers = [1,2,3,4,5]
odd_numbers = []
for item in my_numbers:
  if item % 2 == 1:
    odd_numbers.append(item)

print(odd_numbers) # [1, 3, 5]

В этом примере запустим цикл в my_numbers и используем оператор вычисления остатка, чтобы определить, нужно ли вставлять элемент в odd_numbers.

После рефакторинга

my_numbers = [1,2,3,4,5]
odd_numbers = [item for item in my_numbers if item % 2 == 1]

Подведём итог в этом примере: конструктор списков — это передача элементов в odd_numbers, перебор my_numbers, выполнение return (первый item) и применение оператора if для фильтрации.

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

До

matrix = [[1,2,3],[4,5,6],[7,8,9]]
squared_matrix = []

for row in matrix:
  line = []
  for item in row:
    line.append(item ** 2)
  squared_matrix.append(line)

print(squared_matrix)
# [[1, 4, 9], [16, 25, 36], [49, 64, 81]]

Мы должны использовать вложенный цикл for вместе с временной переменной для хранения каждой строки перед вставкой в окончательную переменную.

После рефакторинга

matrix = [[1,2,3],[4,5,6],[7,8,9]]
squared_matrix = [
  [
    item ** 2
    for item in row
  ]
  for row in matrix
]

print(squared_matrix)
# [[1, 4, 9], [16, 25, 36], [49, 64, 81]]

Здесь немного расширено описание для лучшей читаемости, но все вычисления и вложенный цикл for обрабатываются в одном присвоении.

Логический оператор OR вместо условного оператора

Присвоение одного из двух значений может быть обработано с помощью простого оператора if:

if args['website']:
  website = args['website']
else:
  website = "https://nuancesprog.ru"

Эти типы присвоений “или-или” традиционно могут выполняться с помощью условного оператора, объединяющего присвоения в один.

До

website = args['website'] if args['website'] else "https://nuancesprog.ru"

Структура условного оператора такова:

value if true | expression | value if false

Когда выражение должно оценить, приводит ли “value-if-true” к булеву значению True, логический оператор OR может упростить трудно читаемый условный оператор.

После

website = args['website'] or "https://nuancesprog.ru"

Логический оператор OR пытается присвоить первое значение. Если первое значение приводит к False—None, пустая переменная и т.д.—используется второе значение.

Этот код значительно проще читать, он позволяет эффективно устанавливать значение по умолчанию при присвоении переменной.

Функция enumerate() вместо range()

Цикл for в Python может сбивать с толку, особенно, если вы привыкли работать с другими языками программирования. Вашим первым порывом будет создать индексную переменную и использовать её для перебора списка. 

В результате часто используется функция range(). Этот приём не “в стиле” Python, в отличие от руководства по стилю и проверенных приёмов. Подробнее вы можете прочесть в моей статье “Перестаньте использовать range() в цикле for в Python”.

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

До

races = ["Terran", "Protoss", "Zerg"]
for i in range(len(races)):
  print(i+1, races[i])

# 1 Terran
# 2 Protoss
# 3 Zerg

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

После рефакторинга

races = ["Terran", "Protoss", "Zerg"]
for i, race in enumerate(races, 1):
  print(i, race)

# 1 Terran
# 2 Protoss
# 3 Zerg

Функция enumerate() принимает два аргумента: итерируемое и начальное значение счёта, а возвращает текущее порядковое значение и элемент в итерируемом. Примечание: временная переменная счёта идёт первой в цикле for, а начальное значение счёта идёт вторым в вызове функции. Это распространённая ошибка. 

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

Читайте также:


Перевод статьи Jonathan Hsu: 4 Simple Ways to Refactor Your Python Code