Сорок семь передовых методов рефакторинга для улучшения кода Python

Мы рассмотрим методы и фрагменты кода для документации Python, кодирования, тестирования, проверки и непрерывной интеграции.

Есть примеры кода Python до и после, в которых применяется каждый метод.

Техники делятся на пять категорий:

  • Методы документирования
  • Методы кодирования
  • Методы тестирования
  • Методы проверки
  • Методы непрерывной интеграции (CI)

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

 PyCharm — это IDE, используемая в этом блоге. Все методы, показанные здесь, также могут быть реализованы в MS Visual Basic IDE.

Что такое PHOTONAI?

PHOTONAI включает в себя sci-kit-learn и другие платформы машинного обучения (ML) или глубокого обучения (DL) с одной объединяющей парадигмой. PHOTONAI использует архитектуру научно-обучаемого Transformer класса.

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

Я использую PHOTONAI не как хороший (или плохой) пример ML-фреймворка, а скорее для демонстрации рефакторинга кода.

Я привожу примеры кода до и после Python, чтобы показать преобразование PHOTONAI с использованием этой техники.

Методы документирования

Метод №1–3: Создайте новую версию и задокументируйте основные изменения.

  1. Создайте новую версию, a+1.0.0, если имеется значительное количество архитектурных изменений, значительное количество измененного кода и/или добавлено значительное количество нового поведения.
  2. Создайте новую подверсию, a.b+1.0, если есть незначительные архитектурные изменения, незначительное изменение кода и/или добавленное незначительное количество нового поведения.
  3. Создайте новую под-подверсию, abc+1, если добавляется небольшой объем кода и/или есть исправления ошибок.

Методы №4: Я использую PyCharm для git add всех изменений и новых файлов проекта PHOTONAI 1.1.0.

PyCharm git добавляет для всех измененных файлов в проекте PHOTONAI 1.1.0.
PyCharm git добавляет для всех измененных файлов в проекте PHOTONAI 1.1.0.

 Вы можете git add создать отдельный файл так же, как и проект. Щелкните левой кнопкой мыши имя файла вместо имени каталога проекта.

Показанные здесь изменения приводят к новой подрывной версии, 1.1.0* вместо 1.0.0).

Метод  №5: используем Git для контроля версий и совместной работы:

git checkout -b 1.1.0
git status

Выход терминала:

On branch 1.1.0

Changes to be committed:

(use "git reset HEAD <file>..." to unstage)
modified:   photonai/base/registry/element_dictionary.py
modified:   photonai/base/registry/registry.py
new file:   photonai/custom_elements/CustomElements.json
new file:   photonai/driver.py
modified:   photonai/processing/metrics.py
modified:   photonai/tests/processing_tests/metrics_test.py
new file:   photonai/tests_ext/CustomElements/CustomElements.json
new file:   photonai/tests_ext/base/test_hyperpipe.py
new file:   photonai/tests_ext/base/test_register.py
new file:   photonai/tests_ext/processing/test_metrics.py
new file:   photonai/util.py

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

Метод №6: локально документируйте любые существенные дополнения или изменения в коде.

class Scorer(object):
    """
    Transforms a string literal into a callable instance of a
    particular metric
    BHC 1.1.0 - support cluster scoring by add clustering 
    scores and type
            - added METRIC_METADATA dictionary
            - added ML_TYPES = ["Classification", "Regression",
                                "Clustering"]
            - added METRIC_<>ID Postion index of metric 
              metadata list
            - added SCORE_TYPES
            - added SCORE_SIGN
    """

Метод №7: Создавайте длинные и описательные имена для констант, переменных, функций и методов класса.

Метод №8: Преобразование поведенчески неуместных комментариев в поведенчески корректные, чтобы снизить затраты на обслуживание и количество ошибок.

Метод №9: Добавьте комментарии, чтобы повысить читабельность кода.

Комментарии имеют стоимость обслуживания, а неприемлемые комментарии требуют более высокой стоимости обслуживания.

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

Комментарий перед изменением:

@staticmethod
def get_json(photon_package):
"""
Load JSON file in which the elements for the PHOTON submodule are stored.
The JSON files are stored in the framework folder by the name convention 'photon_package.json'
Parameters:
-----------
* 'photon_package' [str]:
  The name of the photonai submodule
Returns:
--------
JSON file as dict, file path as str
"""

Комментарий после замены:

@staticmethod
def load_json(photon_package: str) -> Any:
    """
    load_json Loads JSON file.
The class init PipelineElement('name',...)
    stores the element metadata in a json file.
The JSON files are stored in the framework folder
    by the name convention 'photon_<package>.json'.
    (example:$HOME/PROJECTS/photon/photonai/base/registry/PhotonCore.json)
The file is of format
    { name-1: ['import-pkg-class-path-1', class-path-1)],
      name-2: ['import-pkg-class-path-2', class-path-2)],
     ....}
Parameters
    ----------
        photon_package:  The name of the photonai package of element metadata
    Returns
    -------
        
[file_content, file_name]

Notes ——- if JSON file does not exist, then create blank one. “””

Метод №10: Выберите стиль строки документации и последовательно используйте его во всем проекте (в пакете, библиотеке и т. д.). Мы используем стиль NumPy, потому что он подходит для подробной документации классов, методов, функций и параметров.

Метод №11: Формат строки документации PyCharm задается из верхней последовательности ленты PyCharm|Preferences|Tools|Python Integrated Tools, как показано в следующем GIF. Макинтош отличается от Windows или Linux.

PyCharm установил стиль строки документации для проекта PHOTONAI 1.1.0
PyCharm установил стиль строки документации для проекта PHOTONAI 1.1.0

Используя стиль NumPy и исправляя любые пробелы, комментарий становится следующим:

@staticmethod
def get_json(photon_package: str) -> Any:
    """
    get_json Loads JSON file.
The class init PipelineElement('name',...)
    stores the element metadata in a json file.
The JSON files are stored in the framework folder
    by the name convention 'photon_<package>.json'.
    (example:$HOME/PROJECTS/photon/photonai/base/registry/PhotonCore.json)
The file is of format
    { name-1: ['import-pkg-class-path-1', class-path-1)],
      name-2: ['import-pkg-class-path-2', class-path-2)],
     ....}
Parameters
    ----------
    photon_package:  The name of the photonai package
    of element metadata.
Returns
    -------
    
[file_content, file_name]

Notes ——- If JSON file does not exist, then create a blank one. “””

Метод №12: Подсказки типа сокращают документацию функции или метода.

Метод №13: PyCharm входит в шаблон строки документации NumPy. PyCharm вставляет только return потому, что функция не имеет параметров. Если щелкнуть правой кнопкой мыши, отобразятся все разрешенные ключевые слова строки документации NumPy. 

Метод №14: Применение соглашений об именах PEP-8

В приведенном ниже примере глобальная константа вводится ELEMENT_TYPE в верхнем регистре, а переменная machine_learning_type — в нижнем.

Globals:

ELEMENT_TYPE -> ML_TYPE
ELEMENT_DICTIONARY   ->  METRIC_METADATA

variables:

machine_learning_type -> element_type

Метод №15: PyCharm может запускать внешний инструмент для форматирования. Я использую черный, который форматирует .py файл или весь проект в формат, совместимый с PEP-8. В результате все файлы форматируются одинаково. Последующий результат — повышенная оценка удобочитаемости.

Внешний инструмент PyCharm: черный цвет применен ко всем файлам .py проекта PHOTONAI 1.1.0.
Внешний инструмент PyCharm: черный цвет применен ко всем файлам .py проекта PHOTONAI 1.1.0.

Метод №16: PyCharm может менять имена в любом месте пакета. Я понимаю, что слово «рефакторинг» может быть сочтено некоторыми вредным. Тем не менее, полезно менять имя на протяжении всего проекта. Пожалуйста, не обращайте внимания на то, как PyCharm классифицирует свои функции. Используйте rename с самого начала, чтобы вам не пришлось рефакторить код.

Функция переименования PyCharm git.
Функция переименования PyCharm git.

Метод №17: PyCharm find usage находит все файлы в вашей ссылке на проект — например, константу, переменную, функцию, класс или метод класса.

Функция поиска использования PyCharm. 
Функция поиска использования PyCharm. 

Метод №18: Добавьте подсказку типа к каждой функции или методу класса.

Python — это язык с динамической типизацией. Python 3.5 и выше позволяет включать подсказки типов (PEP 484). Я хочу подчеркнуть подсказки слов, потому что подсказки типов не влияют на интерпретатор Python. Насколько вам известно, интерпретатор Python игнорирует подсказки типов.

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

def is_machine_learning_type (ml_type: str) -> bool:

Метод №19: Строка документации больше не требует документирования каждого типа arg и return типа данных. Подпись становится самодокументируемой.

Метод №20: Увеличилась читабельность, а также информативность инструментов post-code-checking.

Метод №21: Превратите нерелевантные комментарии в релевантные.

Версия 1.0.0 (исходная)

# register new object
PhotonRegister.save("ABC1", "namespace.filename.ABC1", "Transformer")

Версия 1.1.0

# register new element
PhotonRegister.register("ABC1", "namespace.filename.ABC1", "Transformer")

Метод №22: Сохраняйте только архитектурные #todo комментарии

Метод №23: PyCharm нашел все #Todo для проекта PHOTONAI 1.1.0.

PyCharm находит все Todo проекта PHOTONAI 1.1.0.
PyCharm находит все Todo проекта PHOTONAI 1.1.0.

Метод 24: Удалите старый закомментированный код.

Версия 1.1.0 (до закомментированного кода 1.1.0):

def __post_init__(self): 
    if self.custom_elements_folder: 
        self._load_custom_folder(self.custom_elements_folder)
# base_PHOTON_REGISTRIES = ["PhotonCore", "PhotonNeuro"] 
# PHOTON_REGISTRIES = ["PhotonCore", "PhotonNeuro"]
# def __init__(self, custom_elements_folder: str = None): 
# if custom_elements_folder: 
# self._load_custom_folder() 
# else: 
# self.custom_elements = None 
# self.custom_elements_folder = None 
# self.custom_elements_file = None

Версия 1.1.0 (после удаления закомментированного кода 1.1.0):

def __post_init__(self): 
    if self.custom_elements_folder: 
        self._load_custom_folder(self.custom_elements_folder)

Сложность строк кода (LOC) снижается за счет устранения закомментированного старого кода. Повышается показатель читабельности.

Методы кодирования

Метод №25: Не изобретайте велосипед

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

Метод №26: Учитесь на чужом коде.

Пакеты NumPy, pandas, Keras, fast.ai, TensorFlow, PyTorch, Kubernetes, Hadoop, PySpark и сотни других пакетов обучают передовым информационным технологиям и лучшим методам кодирования.

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

Метод №27: Log; don’t print

Метод №28: Не кодируйте глобальные структуры данных; используйте файлы параметров

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

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

Метод №29: Создайте функцию или метод для инкапсуляции проверки значений.

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

@staticmethod
def is_machine_learning_type(ml_type: str) -> bool:
    """
    :raises
    if not known machine_learning_type
:param machine_learning_type
    :return: True
    """
    if ml_type in Scorer.ML_TYPES:
        return True
    else:
        logger.error(
            "Specify valid ml_type to choose best config: {}".format(ml_type)
        )
    raise NameError(becomes

Метод №30: Создайте тип ошибки пакета

Для пакета Photoai мы создали тип ошибки Photonai.

Метод №31: Создайте функцию повышения с типом ошибки пакета

Для типа ошибки мы создали функцию raise_PhotonaiError.

import logging
class PhotoaiError(Exception):
    pass
def raise_PhotoaiError(msg):
    logger.error(msg)
    raise PhotoaiError(msg)

Метод is_machine_learning_type становится:

@staticmethod
def is_machine_learning_type(ml_type: str) -> bool:
    """
    Parameters:
    -----------
    machine_learning_type
Returns
   -----------
   True
Raises
   -----------
   if not known machine_learning_type
   """
   if ml_type in Scorer.ML_TYPES:
        return True
raise_PhotoaiError(
        "Specify valid ml_type:{} of [{}]".format(ml_type,
        Scorer.ML_TYPES))

Примечание: raise_PhotoaiError объединяет журнал и ошибку времени выполнения. В случае не true останавливается и отображает стек.Как ошибка времени выполнения, она ведет себя ожидаемым образом, если передает неизвестное значение. Обратите внимание на улучшение читаемости и затрат на обслуживание.

Метод №32: Использование faulthandler

Начиная с Python 3.3, библиотека, называемая обработчиком ошибок, отображает трассировку стека вызовов в случае ошибки или сбоя сегментации Python.

Метод №33: Исключите глобальные переменные — или хотя бы попробуйте

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

Метод №34: Удалите все неиспользуемые локальные переменные

Обычно ваша IDE идентифицирует за вас все оборванные переменные. Если нет, пользуйтесь locals() и пользуйтесь поиском.

Метод № 35: Примените декоратор Python 3.7+ @dataclass перед определением класса.

@dataclass — это дополнительная функция Python 3.7. Главной движущей силой было устранение шаблона, связанного с состоянием в def class определении.

@dataclass украшает def class определение и автоматически генерирует пять методов двойного дандера: __init__()__repr__()__str__, __eq__()и __hash__().

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

class PhotonRegistry:
    """
    Helper class to manage the PHOTON Element Register
    ...
    """
base_PHOTON_REGISTRIES = ['PhotonCore', 'PhotonNeuro']
PHOTON_REGISTRIES = ['PhotonCore', 'PhotonNeuro']
def __init__(self, custom_elements_folder: str = None):
    if custom_elements_folder:
        self._load_custom_folder(custom_elements_folder)
    else:
        self.custom_elements = None
        self.custom_elements_folder = None
        self.custom_elements_file = None

После @dataclass декоратора:

@dataclass
class PhotonRegistry:
    """
    Helper class to manage the PHOTON Element Register
    ...
    """
    custom_elements_folder: str = None
    custom_elements: str = None
    custom_elements_file: str = None
    base_PHOTON_REGISTRIES: ClassVar[List[str]] =/
   ["PhotonCore", "PhotonNeuro"]
    PHOTON_REGISTRIES: ClassVar[List[str]] =/
   ["PhotonCore", "PhotonNeuro"]
def __post_init__(self):
    if self.custom_elements_folder:
        self._load_custom_folder(self.custom_elements_folder)

Примечание. Удобочитаемость значительно улучшилась при использовании @dataclass with type hinting.

Методы тестирования

Метод №36: Разработчики кода разрабатывают модульные тесты.

Метод №37: Установите инструмент pytest в PyCharm.

Настройка инструмента pytest в PyCharm для проекта PHOTONAI 1.1.0.
Настройка инструмента pytest в PyCharm для проекта PHOTONAI 1.1.0.

Метод №38: Модульные тесты имеют покрытие 80%+. 

Метод №39: Интеграционные тесты должны иметь 100% покрытие всех внешних (API) функций, классов, атрибутов классов и методов классов.

Метод №40: Разработчики не проводят приемочное тестирование.

Например, мы выбрали эти модульные тесты из 24 модульных тестов в test_metrics.py.

# 22 
def test_is_machine_learning_type_bad(): 
    with pytest.raises(PhotoaiError): 
        assert Scorer.is_machine_learning_type("fred") # 23 def test_is_machine_learning_type():     assert Scorer.is_machine_learning_type("Clustering") 

Метод №41: После выбора инструмента тестирования PyCharm помечает отдельные тесты, которые можно запустить. Они отмечены закрашенной стрелкой на левой панели. Пример индивидуального тестирования с использованием pytest:

С помощью PyCharm вы можете запустить тестовый файл или всю тестовую папку с помощью pytest и увидеть результаты. Эталонная нижняя панель-лента (пройден тест 39/40 — 32 мс).

Инструмент тестирования PyCharm указан для проекта PHOTONAI 1.1.0.
Инструмент тестирования PyCharm указан для проекта PHOTONAI 1.1.0.

Метод №42: Проверка подсказок типа кода.

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

Метод №43: Тестовое покрытие

Метод №44: Профилирование производительности

Метод №45: Проверка безопасности

Метод №46: Надежность кода

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

Методы проверки

Метод №47: Проверка кода

Вывод

Я собрал эти сорок приемов рефакторинга из Python 3.5 и более ранних версий. Большинство из этих методов по-прежнему применимы к Python 3.9. Некоторые из этих методов включены в Python 3.9.

Ответить