Каждый раз, когда вы создаете глобальную переменную, которую планируете повторно связать, вы добавляете дополнительный неявный аргумент к каждой функции, которая обращается к этой переменной. В отличие от правильных (явных) аргументов, этот скрытый аргумент не отображается в сигнатуре функции, и может быть мало признаков того, что функция его использует. Ваш код становится менее «функциональным»; вызов одной и той же функции с одинаковыми аргументами может привести к разным возвращаемым значениям в зависимости от текущего состояния этих глобальных динамических переменных.
Преимущество глобальных переменных заключается в том, что вы можете легко указать значение по умолчанию, и оно позволяет вам быть ленивым, не передавая эту переменную каждой функции, которая ее использует.
Недостатком является то, что ваш код труднее читать, тестировать, использовать и отлаживать. И ваш код становится потенциально более подверженным ошибкам; легко забыть связать или повторно связать переменную перед вызовом функции, которая ее использует, но не так легко забыть передать параметр session
, когда он находится прямо в списке аргументов.
Таким образом, вы получите загадочные ошибки и странные неявные зависимости между функциями. Рассмотрим этот сценарий:
user> (defn foo [] (when-not (:logged-in *session*) (throw (Exception. "Access denied!"))))
#'user/foo
user> (defn bar [] (foo))
#'user/bar
user> (defn quux [] (bar))
#'user/quux
user> (quux)
; Evaluation aborted. ;; Access denied!
Поведение quux
неявно зависит от того, имеет ли сеанс значение, но вы не узнаете этого, если не будете копаться во всех вызовах функций quux
и каждой функции, вызываемой этими функциями. Представьте себе цепочку вызовов глубиной 10 или 20, с одной функцией внизу в зависимости от *session*
. Получайте удовольствие отлаживая это.
Если бы вместо этого у вас были (defn foo [session] ...)
, (defn bar [session] ...)
, (defn quux [session] ...)
, для вас сразу было бы очевидно, что если вы позвоните quux
, вам лучше подготовить сеанс.
Лично я бы использовал явные аргументы, если бы у меня не было строгого, нормального значения по умолчанию, которое использовалось бы тоннами функций, которое я планировал очень редко или никогда не перепроверял. (например, было бы глупо передавать STDOUT в качестве явного аргумента каждой функции, которая хочет что-либо напечатать.)