popov . dev

Main

Library

Articles

Хватит писать та...

Хватит писать такие функции на Python!

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

Ужасный Код, о котором пойдет речь

Это был код, который просто заставил меня почувствовать себя так:

def process_data(data, flag1, flag2, flag3):
    if flag1:
        data = [item for item in data if item.isdigit()]
    if flag2:
        data = [item.upper() for item in data]
    if flag3:
        data = sorted(data)
    result = "Обработка: " + ", ".join(data)
    return result

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

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

Слишком много опций

Функция использует слишком много опций (flag1, flag2, flag3) для управления поведением, что делает функцию немного запутанной.

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

Позвольте мне объяснить это с помощью этого примера:

res = process_data(my_data, True, False, True)

Вы поняли, что означает каждое значение True, False, True? Это тот же случай, что и с вышеупомянутой функцией.

2. Отсутствие единой задачи

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

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

3. Отсутствие аннотаций к типу

В этой функции ничего не объясняется. Что это за данные? Это список, строка или словарь? Мы должны догадаться об этом.

4. Ее трудно расширить

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

Как я исправляю эту функцию

Я стараюсь переписать функцию понятным и простым способом.

1. Использовать описательные названия функций

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

def filter_digits(data):
    return [item for item in data if item.isdigit()]

def to_uppercase(data):
    return [item.upper() for item in data]

def sort_data(data):
    return sorted(data)

def format_result(data):
    return "Processed: " + ", ".join(data)

Теперь каждая функция выполняет только одну единственную задачу.

2. Создаем пайплайн

Затем я пытаюсь связать эти функции воедино, используя подход с последовательностью действий. Так становится понятнее.

def process_data_pipeline(data):
    data = filter_digits(data)
    data = to_uppercase(data)
    data = sort_data(data)
    return format_result(data)

3. Делаем код более гибким

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

def process_data(data, steps):
    for step in steps:
        data = step(data)
    return data

steps = [filter_digits, to_uppercase, sort_data, format_result]
result = process_data(my_data, steps)

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

4. Добавляю аннотации к типу

Я просто добавил аннотации к типу, чтобы сделать этот код более читабельным и менее подверженным ошибкам.

from typing import List, Callable

def process_data(data: List[str], steps: List[Callable[[List[str]], List[str]]]) -> str:
    for step in steps:
        data = step(data)
    return data

Почему такой подход является лучшим

  • Теперь у каждой функции есть четко определенное назначение. Это повышает удобочитаемость функции
  • Если мы хотим отфильтровать цифры в другом месте. Нам просто нужно вызвать filter_digits. Это помогает повторно использовать функцию.
  • Если мы хотим протестировать эту функцию, то теперь это стало еще проще. Тестировать одну функцию проще, чем перегруженную.
  • Мы улучшили возможность масштабируемости функции. Мы можем просто написать новую функцию и добавить ее в список, если хотим добавить новые операции.

Подведем итог. В следующий раз, когда вы будете писать функцию, задайте себе следующие вопросы:

Не требует ли это слишком многого? Если кто-то другой будет разобраться в этом коде, поймет ли он его, не нуждаясь в подробных объяснениях?

Если ответ будет отрицательным, то вам придется реорганизовать этот код.

Comments

In order to leave your opinion, you need to register on the website