У ваших функций есть побочные эффекты. Вызов их дважды с одними и теми же входными данными может дать разные возвращаемые значения в зависимости от текущего значения *some-global-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
протестировать: просто передайте желаемую «фиктивную» адресную книгу и посмотрите, что она напечатает. Вам не нужно заботиться ни о каком глобальном состоянии или чем-либо другом, кроме функции, которую вы тестируете в данный момент.
Если вам не нужно координировать обновления структуры данных из нескольких потоков, обычно нет необходимости использовать ссылки или глобальные переменные.
person
Community
schedule
16.07.2010