Ответ 1
У ваших функций есть побочные эффекты. Вызов их дважды с одинаковыми входами может давать разные значения возврата в зависимости от текущего значения *some-global-var*
. Это затрудняет проверку и рассуждение, особенно после того, как вы плаваете вокруг одного из этих глобальных варов.
Люди, вызывающие ваши функции, могут даже не знать, что ваши функции зависят от значения глобального var, не проверяя источник. Что делать, если они забывают инициализировать глобальный var? Легко забыть. Что делать, если у вас есть два набора кода, которые пытаются использовать библиотеку, которая опирается на эти глобальные вары? Вероятно, они будут переходить друг на друга, если вы не используете binding
. Вы также добавляете накладные расходы каждый раз, когда получаете доступ к данным из ссылки.
Если вы напишете свой побочный эффект кода, эти проблемы исчезнут. Функция стоит сама по себе. Это легко проверить: передайте ему несколько входов, проверьте выходы, они всегда будут одинаковыми. Легко понять, от каких функций зависит функция: все они находятся в списке аргументов. И теперь ваш код является потокобезопасным. И, вероятно, работает быстрее.
Трудно думать об этом коде, если вы привыкли к стилю программирования "mutate the bunch of objects/memory", но как только вы его повесили, становится довольно просто организовать ваши программы таким образом, Ваш код обычно заканчивается так же просто или проще, чем версия с глобальным мутацией того же кода.
Вот очень надуманный пример:
(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
: просто передайте нужную "макет" адресную книгу и посмотрите, что она печатает. Вам не нужно заботиться о каком-либо глобальном состоянии или о чем-либо, кроме функции, которую вы тестируете на данный момент.
Если вам не нужно координировать обновления структуры данных из нескольких потоков, обычно нет необходимости использовать refs или глобальные vars.