Ваши функции имеют побочные эффекты.Вызов их дважды с одинаковыми входами может дать разные возвращаемые значения в зависимости от текущего значения *some-global-var*
.Это затрудняет тестирование и анализ, особенно если у вас есть несколько глобальных переменных, плавающих вокруг.
Люди, вызывающие ваши функции, могут даже не знать, что ваши функции зависят от значения глобального varбез проверки источника.Что если они забудут инициализировать глобальную переменную?Это легко забыть.Что если у вас есть два набора кода, оба пытаются использовать библиотеку, которая использует эти глобальные переменные?Они, вероятно, собираются обойти друг друга, если вы не используете binding
.Вы также добавляете накладные расходы каждый раз, когда получаете доступ к данным из ссылки.
Если вы напишите свой код без побочных эффектов, эти проблемы исчезнут.Функция стоит сама по себе.Это легко проверить: пропустите несколько входных данных, проверьте выходные, они всегда будут одинаковыми.Легко увидеть, от каких входов зависит функция: все они находятся в списке аргументов.И теперь ваш код является потокобезопасным.И, вероятно, работает быстрее.
Сложно думать о коде таким образом, если вы привыкли к стилю программирования «мутировать кучу объектов / памяти», но как только вы освоите его, он становитсяОтносительно просто организовать ваши программы таким образом.Ваш код обычно оказывается таким же простым или более простым, чем версия того же кода с глобальной мутацией.
Вот очень надуманный пример:
(def *address-book* (ref {}))
(defn add [name addr]
(dosync (alter *address-book* assoc name addr)))
(defn report []
(doseq [[name addr] @*address-book*]
(println name ":" addr)))
(defn do-some-stuff []
(add "Brian" "123 Bovine University Blvd.")
(add "Roger" "456 Main St.")
(report))
Рассматривая do-some-stuff
в изоляции,какого чёрта это делает?Есть много вещей, происходящих неявно.По этому пути лежит спагетти.Возможно, лучшая версия:
(defn make-address-book [] {})
(defn add [addr-book name addr]
(assoc addr-book name addr))
(defn report [addr-book]
(doseq [[name addr] addr-book]
(println name ":" addr)))
(defn do-some-stuff []
(let [addr-book (make-address-book)]
(-> addr-book
(add "Brian" "123 Bovine University Blvd.")
(add "Roger" "456 Main St.")
(report))))
Теперь понятно, что делает do-some-stuff
даже в изоляции.Вы можете иметь столько адресных книг, сколько хотите.Несколько потоков могут иметь свои собственные.Вы можете безопасно использовать этот код из нескольких пространств имен.Вы не можете забыть инициализировать адресную книгу, потому что вы передаете ее в качестве аргумента.Вы можете легко протестировать report
: просто введите нужную «макетную» адресную книгу и посмотрите, что она печатает.Вам не нужно заботиться о каком-либо глобальном состоянии или о чем-либо, кроме функции, которую вы тестируете в данный момент.
Если вам не нужно координировать обновления структуры данных из нескольких потоков, обычно нетнужно использовать ссылки или глобальные переменные.