Делаем ваши функции более читабельными и надежными

Когда-то унаследованная мудрость заключалась в том, что функции должны иметь один и только один оператор return. Это эволюционировало, чтобы покончить с безумием операторов goto, но менее актуально для современных языков программирования, а защитные предложения представляют собой прекрасную современную альтернативу этой догме.

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

def calculate_area(width):
  return width * width

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

def calculate_area(width):
  if width >= 0:
    area = width * width
  else:
    area = 0

  return area

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

def calculate_area(width, height):
  if width >= 0:
    if height >= 0:
      area = width * height
    else:
      area = 0
  else:
    area = 0

  return area

Что эти вложенные операторы if/else пытаются сделать, так это проверить, что входные аргументы имеют смысл, и если они имеют, то продолжить. Проблема с проверкой правильности и подобными действиями заключается в том, что в конечном итоге вы получаете глубоко вложенную логику управления, которой трудно следовать.

Введите защитную оговорку

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

def calculate_area(width):
  if width < 0:
    return 0

  return width * width
  

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

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

Этот код также быстрее, потому что вместо того, чтобы оценивать оба блока if/else, мы имеем дело только с одним блоком if. У компилятора меньше областей действия и меньше кода для интерпретации.

Увеличение масштаба

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

def calculate_area(width, height):
  if width < 0:
    return 0
  if height < 0:
    return 0

  return width * height

Красивый! Теперь наша условная логика стала менее глубокой и запутанной.

Проверьте правильность ввода, затем выйдите

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

Предложения Guard обеспечивают более простую и удобочитаемую логику управления, чем альтернативы, в дополнение к более быстрому выполнению.

Исключения по возврату

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

С использованием исключений наш более сложный пример может выглядеть так:

def calculate_area(width, height):
  if width < 0:
    raise ValueError("Width cannot be less than zero")
  if height < 0:
    return ValueError("Height cannot be less than zero")

  return width * height

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

Сначала обработайте исключительный случай

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

def calculate_area(width, height):
  if width < 0 or height < 0:
    area = 0
  else:
    area = width * height

  return area

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

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

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

Вы ожидаете, что этот код будет выполнен, если не произойдет что-то странное, тогда как не сразу очевидно, что то, что находится внутри оператора «else», — это то, что вы ожидаете нормально выполнить.

Заключительные мысли

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