Прекратите писать функции Python, на понимание которых уходит больше трех минут

Мотивация

Вы когда-нибудь смотрели на функцию, которую написали месяцем ранее, и не понимали ее за 3 минуты? Если это так, пора провести рефакторинг вашего кода. Если на понимание кода у вас уходит больше 3 минут, представьте, сколько времени потребуется вашим товарищам по команде, чтобы понять ваш код.

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

Вы хотите, чтобы ваша функция Python:

  • быть маленьким
  • сделать одно дело
  • содержать код с таким же уровнем абстракции
  • иметь менее 4 аргументов
  • не иметь дублирования
  • используйте описательные имена

Эти методы сделают ваши функции более читабельными и упростят обнаружение ошибок.

Вдохновленный книгой Роберта К. Мартина Чистый код: руководство по созданию гибкого программного обеспечения с примерами кода, написанными на Java, я решил написать статью о том, как писать чистый код на Python для специалистов по данным.

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

Начать

Давайте начнем с рассмотрения функции load_data ниже.

Функцияload_data пытается загрузить данные с Google Диска и извлечь данные. Несмотря на то, что в этой функции много комментариев, сложно понять, что эта функция делает за 3 минуты. Это потому что:

  • Функция ужасно долгая
  • Функция пытается делать несколько вещей
  • Код внутри функции находится на нескольких уровнях абстракции.
  • Функция имеет более 3 аргументов
  • Есть несколько дубликатов
  • Название функции не является описательным

Мы проведем рефакторинг этого кода, используя 6 практик, упомянутых выше.

Небольшой

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

Выполнить одну задачу

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

Таким образом, его следует разделить на несколько функций, как показано ниже.

И каждая функция должна делать только одно:

Функция download_zip_data_from_google_drive загружает только zip-файл с Google Диска и больше ничего не делает.

Один уровень абстракции

Код внутри функции extract_texts_from_multiple_files находится на другом уровне абстракции от функции.

Уровень абстракции - это степень сложности, с которой система просматривается или программируется. Чем выше уровень, тем меньше деталей. Чем ниже уровень, тем больше деталей. - PCMag

Поэтому:

  • Функция extract_texts_from_multiple_files находится на высоком уровне абстракции.
  • Код list_of_text_in_one_file =[r.text for r in ET.parse(join(path_to_file, file_name)).getroot()[0]] находится на низком уровне абстракции.

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

Теперь код extract_texts_from_each_file(path_to_file, file) находится на высоком уровне абстракции, который является тем же уровнем абстракции, что и функция extract_texts_from_multiple_files.

Дублирование

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

Мы должны избегать дублирования, потому что:

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

Мы можем устранить дублирование, поместив дублированный код в функцию.

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

Описательные имена

Длинное описательное имя лучше, чем короткое загадочное имя. Длинное описательное имя лучше, чем длинный описательный комментарий. - Чистый код Роберта К. Мартина

Пользователи могут понять, что делает functionextract_texts_from_multiple_files, посмотрев на ее имя.

Не бойтесь писать длинные имена. Лучше писать длинные имена, чем расплывчатые. Если вы попытаетесь сократить свой код, написав что-то вроде get_texts, другим будет сложно понять, что именно делает эта функция, не глядя на исходный код.

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

Меньше 4 аргументов

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

Например, функция load_data имеет 4 аргумента: url, output_path, path_train иpath_test . Итак, мы можем предположить, что он пытается делать несколько вещей одновременно:

  • Используйте url для загрузки данных
  • Сохраните его на output_path
  • Извлеките файлы поездов и тестов в output_path и сохраните их в path_train, path_test.

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

Например, мы могли бы разделить load_data на 3 разные функции:

Поскольку все функции download_zip_data_from_google_drive, unzip_data и get_train_test_docs пытаются достичь одной цели: получить данные, мы могли бы поместить их в один класс с именем DataGetter.

Боковое примечание: в приведенном выше коде я использую staticmethod в качестве декораторов для некоторых методов, потому что эти методы не используют никаких атрибутов класса или методов класса. Подробнее об этих методах здесь.

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

Как мне написать такую ​​функцию?

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

Заключение

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

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

Исходный код этой статьи можно найти здесь.

Мне нравится писать об основных концепциях науки о данных и играть с различными алгоритмами и инструментами анализа данных. Вы могли связаться со мной в LinkedIn и Twitter.

Пометьте это репо, если хотите проверить коды всех моих статей. Следуйте за мной на Medium, чтобы быть в курсе моих последних статей по науке о данных, таких как:









использованная литература

Мартин, Р. К. (2009). Чистый код: руководство по созданию гибкого программного обеспечения. Река Верхнее Седл: Зал Прентис.