Para aqueles que têm a sorte de não trabalhar em uma linguagem com escopo dinâmico, deixe-me fazer uma pequena revisão sobre como isso funciona. Imagine uma pseudo-linguagem, chamada "RUBELLA", que se comporta assim:
function foo() {
print(x); // not defined locally => uses whatever value `x` has in the calling context
y = "tetanus";
}
function bar() {
x = "measles";
foo();
print(y); // not defined locally, but set by the call to `foo()`
}
bar(); // prints "measles" followed by "tetanus"
Ou seja, as variáveis se propagam para cima e para baixo na pilha de chamadas livremente - todas as variáveis definidas em foo
são visíveis (e mutáveis por) ao seu chamador bar
, e o inverso também é verdadeiro. Isso tem sérias implicações para a refatorabilidade do código. Imagine que você tenha o seguinte código:
function a() { // defined in file A
x = "qux";
b();
}
function b() { // defined in file B
c();
}
function c() { // defined in file C
print(x);
}
Agora, as chamadas para a()
serão impressas qux
. Mas então, um dia, você decide que precisa mudar b
um pouco. Você não conhece todos os contextos de chamada (alguns dos quais podem, de fato, estar fora da sua base de código), mas isso deve ser bom - suas alterações serão completamente internas b
, certo? Então você reescreve assim:
function b() {
x = "oops";
c();
}
E você pode pensar que não mudou nada, pois acabou de definir uma variável local. Mas, de fato, você quebrou a
! Agora, a
imprime em oops
vez de qux
.
Trazendo isso de volta para o mundo das pseudo-línguas, é exatamente assim que o MUMPS se comporta, embora com sintaxe diferente.
As versões modernas ("modernas") do MUMPS incluem a chamada NEW
instrução, que permite impedir que variáveis vazem de um chamado para um chamador. Assim, no primeiro exemplo acima, se tivéssemos feito NEW y = "tetanus"
em foo()
, em seguida, print(y)
em bar()
imprimiria nada (em MUMPS, todos os nomes apontam para a cadeia vazia a menos que explicitamente definido para algo mais). Mas não há nada que possa impedir que variáveis vazem de um chamador para um chamado: se tivermos function p() { NEW x = 3; q(); print(x); }
, pelo que sabemos, q()
poderia sofrer mutação x
, apesar de não recebermos explicitamente x
como parâmetro. Essa ainda é uma situação ruim, mas não tão ruim quanto costumava ser.
Com esses perigos em mente, como podemos refatorar com segurança o código no MUMPS ou em qualquer outro idioma com escopo dinâmico?
Existem algumas boas práticas óbvias para facilitar a refatoração, como nunca usar variáveis em uma função diferente daquelas que você inicializa ( NEW
) ou são passadas como um parâmetro explícito e documentar explicitamente quaisquer parâmetros que são implicitamente passados pelos chamadores de uma função. Mas em uma base de código de ~ 10 8- LOC de décadas, esses são luxos que geralmente não se tem.
E, é claro, essencialmente todas as boas práticas para refatoração em idiomas com escopo lexical também são aplicáveis em idiomas com escopo dinâmico - testes de gravação e assim por diante. A questão, então, é a seguinte: como mitigamos os riscos especificamente associados ao aumento da fragilidade do código de escopo dinâmico ao refatorar?
(Observe que, embora Como você navegue e refatore o código escrito em um idioma dinâmico? Tenha um título semelhante a esta pergunta, ele não tem nenhuma relação.)
fonte
Respostas:
Uau.
Como não conheço o MUMPS como idioma, não sei se meu comentário se aplica aqui. De um modo geral - você deve refatorar de dentro para fora. Os consumidores (leitores) do estado global (variáveis globais) devem ser refatorados em métodos / funções / procedimentos usando parâmetros. O método c deve ficar assim após a refatoração:
todos os usos de c devem ser reescritos (o que é uma tarefa mecânica)
isso é para isolar o código "interno" do estado global usando o estado local. Quando terminar, você terá que reescrever b em:
a tarefa x = "oops" existe para manter os efeitos colaterais. Agora devemos considerar b como poluidor do estado global. Se você tiver apenas um elemento poluído, considere esta refatoração:
final reescreva cada uso de b com x = b (). A função b deve usar apenas métodos já limpos (você pode querer renomear co-esclarecer isso) ao fazer essa refatoração. Depois disso, você deve refatorar b para não poluir o ambiente global.
renomeie b para b_cleaned. Eu acho que você terá que brincar um pouco com isso para se acostumar com essa refatoração. Claro que nem todo método pode ser refatorado por isso, mas você terá que começar pelas partes internas. Tente isso com Eclipse e java (métodos de extração) e "estado global", também conhecido como membros da classe, para ter uma idéia.
hth.
Pergunta: Com esses perigos em mente, como podemos refatorar com segurança o código no MUMPS ou em qualquer outro idioma com escopo dinâmico?
Pergunta: Como mitigamos os riscos especificamente associados ao aumento da fragilidade do código de escopo dinâmico ao refatorar?
fonte
EXECUTE
), às vezes até na entrada higienizada do usuário - o que significa que pode ser impossível encontrar e reescrever estaticamente todos os usos de uma função.Eu acho que sua melhor chance é colocar toda a base de código sob seu controle e ter uma visão geral sobre os módulos e suas dependências.
Portanto, pelo menos você tem a chance de fazer pesquisas globais e a possibilidade de adicionar testes de regressão para as partes do sistema em que espera um impacto causado por uma alteração no código.
Se você não tiver a chance de realizar o primeiro, meu melhor conselho é: não refatorar nenhum módulo reutilizado por outros módulos ou para o qual você não sabe que outros dependem deles . Em qualquer base de código de tamanho razoável, as chances são altas de encontrar módulos dos quais nenhum outro módulo depende. Portanto, se você possui um mod A dependendo de B, mas não vice-versa, e nenhum outro módulo depende de A, mesmo em uma linguagem com escopo dinâmico, você pode fazer alterações em A sem quebrar B ou quaisquer outros módulos.
Isso permite que você substitua a dependência de A a B por uma de A a B2, onde B2 é uma versão reescrita e higienizada de B. B2 deve ser uma nova escrita com as regras em mente mencionadas acima para criar o código mais evolutível e mais fácil de refatorar.
fonte
Para afirmar o óbvio: como fazer a refatoração aqui? Prossiga com muito cuidado.
(Como você descreveu, o desenvolvimento e a manutenção da base de código existente já deve ser bastante difícil, e muito menos tentar refatorá-la.)
Acredito que aplicaria retroativamente uma abordagem orientada a testes aqui. Isso envolveria a criação de um conjunto de testes para garantir que a funcionalidade atual permaneça funcionando quando você inicia a refatoração, primeiro apenas para facilitar o teste. (Sim, espero um problema de galinha e ovo aqui, a menos que seu código já seja modular o suficiente para ser testado sem alterá-lo.)
Em seguida, você pode prosseguir com outra refatoração, verificando se não quebrou nenhum teste à medida que avança.
Finalmente, você pode começar a escrever testes que esperam novas funcionalidades e, em seguida, escrever o código para fazer esses testes funcionarem.
fonte