Улучшите свой код с помощью атомарных функций

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

Проблема

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

Функция Python ниже кратко иллюстрирует эту динамику:

import math

def find_prime_factors(number):
    limit = int(math.sqrt(number)) + 1
    factors = [(num, int(number / num)) for num in range(1, limit) if number % num == 0]
    prime_factors = []

    if len(factors) == 1:
        return factors

    for num in factors[1]:
        limit = int(math.sqrt(num)) + 1
        factors2 = [(x, int(num / x)) for x in range(1, limit) if num % x == 0]
        if len(factors2) == 1:
            prime_factors.append(num)
        else: prime_factors.extend(find_prime_factors(num))

    return prime_factors

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

атомарность

Атомарные функции решают вышеуказанные проблемы, сводя каждую функцию к одной операции. Я преобразовал find_prime_factors() в набор атомарных функций ниже:

import math

def find_prime_factors(number):
    if is_prime(number):
        return [1, number]

    return _find_prime_factors(factor_number(number)[1])

def is_prime(number):
    return len(factor_number(number)) == 1

def factor_number(number):
    limit = int(math.sqrt(number)) + 1
    return [(num, int(number / num)) for num in range(1, limit) if number % num == 0]

def _find_prime_factors(factor_list):
    primes = []
    for num in factor_list:
        if is_prime(num):
            primes.append(num)
        else: primes.extend(find_prime_factors(num))

    return primes

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

Атомарные функции приносят нам пользу несколькими способами. Во-первых, их просто проверить. Поскольку каждая функция несет единственную ответственность, мы можем легко предсказать, каким будет результат каждой функции при данном входе. Мы знаем вывод factor_number() для любого заданного числа, не прорабатывая десятки строк логических тестов. Функция проста в том, что она делает. Поскольку у нас есть прямое ожидание от каждой функции, наши тесты могут быть простыми и краткими.

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

В-третьих, точно так же, как атомы объединяются в молекулы, атомные функции объединяются, чтобы иметь разные эффекты. Функция factor_number() используется дважды: один раз, чтобы определить, является ли число простым, и один раз, чтобы вернуть множители числа. Это также означает, что мы можем легко доработать наш дизайн. Мы можем написать еще дюжину функций, использующих эти функции различными способами. Поскольку мы можем использовать их бесконечно, мы также можем удовлетворить принцип DRY, используя эти функции вместо того, чтобы повторяться.

В-четвертых, наш конечный результат легче поддерживать. Если мы обнаружим, что factor_number() работает неправильно, мы можем исправить функцию, и это исправление повлияет на всю программу. Любая заданная проблема может быть прослежена до одной строки. Поскольку атомарные функции приводят к СУХОМУ коду, любая проблема, с которой мы сталкиваемся, может быть прослежена до нескольких строк кода в одной функции. Это значительно сокращает время, необходимое для решения проблемы.

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *