Pergunta

Eu me encontrei usando a seguinte expressão ultimamente em clojure código.

(def *some-global-var* (ref {}))

(defn get-global-var []
  @*global-var*)

(defn update-global-var [val]
  (dosync (ref-set *global-var* val)))

A maior parte do tempo isso não é mesmo multi-threaded código que pode precisar a semântica transacional que refs dar-lhe.Ele só se sente como árbitros são mais de rosca código, mas, basicamente, para qualquer global que requer a imutabilidade.Há uma melhor prática para isso?Eu poderia tentar para refatorar o código para usar apenas a ligação ou deixe, mas que pode ficar particularmente complicado para algumas aplicações.

Foi útil?

Solução

Suas funções têm efeitos colaterais.Chamando-os duas vezes com as mesmas entradas pode dar retorno a diferentes valores, dependendo do valor atual de *some-global-var*.Isso torna as coisas difíceis para teste de razão e respeito, especialmente quando você tem mais do que uma destas variáveis globais flutuando.

As pessoas chamando de suas funções não pode mesmo saber que suas funções estão de acordo sobre o valor da var global, sem inspecionar o código fonte.O que se esquece de inicializar o var global?É fácil esquecer.O que se você tem dois conjuntos de código de tanto tentar utilizar uma biblioteca que depende dessas variáveis globais?Elas são, provavelmente, vai para o passo sobre todas as outras, a menos que você use binding.Você também adicionar as despesas gerais de cada vez que aceder a dados a partir de um ref.

Se você escrever seu código livre de efeitos secundários, estes problemas vão embora.Uma função está em seus próprios.É fácil de testar:passar algumas entradas, inspecionar as saídas, que vai ser sempre o mesmo.É fácil ver que as entradas de uma função depende de:eles todos estão na lista de argumento.E agora, o seu código é thread-safe.E, provavelmente, é mais rápido.

É complicado pensar sobre o código desta forma, se você está acostumado a "transformar um monte de objetos/memória" estilo de programação, mas depois que você pegar o jeito dele, torna-se relativamente simples para organizar seus programas desta forma.O código geralmente acaba tão simples quanto ou mais simples do que o global-mutação versão do mesmo código.

Aqui está um altamente artificial exemplo:

(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))

Olhando para do-some-stuff no isolamento, o que diabos está fazendo?Há um monte de coisas acontecendo de forma implícita.Por este caminho fica espaguete.Uma dúvida a melhor versão:

(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))))

Agora, é claro que do-some-stuff está fazendo, mesmo no isolamento.Você pode ter como muitos livros de endereços flutuante em torno de como você deseja.Vários threads poderia ter a sua própria.Você pode usar este código a partir de múltiplos espaços de nomes com segurança.Você não pode esquecer de inicializar o livro de endereços, porque você passá-lo como argumento.Você pode testar report facilidade:basta passar o desejado "zombar" de livro de endereços e veja o que ele imprime.Você não tem de se preocupar com o estado global ou qualquer coisa, mas a função que você está testando no momento.

Se você não precisa de coordenar as atualizações para uma estrutura de dados a partir de vários threads, geralmente não há necessidade de usar refs ou variáveis globais.

Outras dicas

Eu sempre uso um átomo ao invés de uma ref quando vejo esse tipo de padrão que - se você não precisa de transações, apenas partilhado mutável local de armazenamento e, em seguida, os átomos parecem ser o caminho a percorrer.

exemplo:para um mutável mapa de pares chave/valor que eu usaria:

(def state (atom {}))

(defn get-state [key]
  (@state key))

(defn update-state [key val]
  (swap! state assoc key val))
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top