Existe um nome para o (anti-) padrão de passagem de parâmetros que somente serão usados ​​em vários níveis da cadeia de chamadas?

209

Eu estava tentando encontrar alternativas para o uso da variável global em algum código legado. Mas esta questão não é sobre as alternativas técnicas, estou principalmente preocupada com a terminologia .

A solução óbvia é passar um parâmetro para a função em vez de usar um global. Nesta base de código herdada, isso significa que eu tenho que alterar todas as funções na longa cadeia de chamadas entre o ponto em que o valor será eventualmente usado e a função que recebe o parâmetro primeiro.

higherlevel(newParam)->level1(newParam)->level2(newParam)->level3(newParam)

onde newParamanteriormente era uma variável global no meu exemplo, mas poderia ter sido um valor anteriormente codificado anteriormente. O ponto é que agora o valor de newParam é obtido em higherlevel()e precisa "viajar" até o fim level3().

Eu queria saber se havia um nome para este tipo de situação / padrão em que você precisa adicionar um parâmetro a muitas funções que apenas "passam" o valor não modificado.

Felizmente, usar a terminologia adequada me permitirá encontrar mais recursos sobre soluções para redesenhar e descrever essa situação para os colegas.

ecerulm
fonte
94
Esta é uma melhoria em relação ao uso de variáveis ​​globais. Isso deixa claro exatamente em que estado cada função depende (e é um passo no caminho para funções puras). Ouvi dizer que "threading" é um parâmetro, mas não sei quão comum é essa terminologia.
gardenhead
8
Esse é um espectro muito amplo para ter uma resposta específica. Nesse nível, eu chamaria isso apenas de "codificação".
Machado
38
Eu acho que o "problema" é apenas a simplicidade. Isso é basicamente injeção de dependência. Eu acho que poderia haver mecanismos que injetariam automaticamente a dependência através da cadeia, se algum membro mais profundamente aninhado a possuir, sem inchar as listas de parâmetros das funções. Talvez analisar estratégias de injeção de dependência com diferentes níveis de sofisticação possa levar à terminologia que você está procurando, se houver.
null,
7
Embora eu aprecie a discussão sobre se é um bom padrão / antipadrão / conceito / solução, o que eu realmente queria saber é se existe um nome para ele.
Ecerulm 31/10/16
3
Eu também ouvi isso chamado threading com mais frequência, mas também encanamento , como na redução de uma linha de prumo em toda a pilha de chamadas.
wchargin

Respostas:

202

Os dados em si são chamados de "dados vagabundos" . É um "cheiro de código", indicando que um pedaço de código está se comunicando com outro pedaço de código à distância, por intermediários.

  • Aumenta a rigidez do código, especialmente na cadeia de chamadas. Você está muito mais restrito em como refatorar qualquer método na cadeia de chamadas.
  • Distribui conhecimento sobre dados / métodos / arquitetura para locais que não se importam nem um pouco. Se você precisar declarar os dados que estão passando e a declaração exigir uma nova importação, você poluiu o espaço para nome.

A refatoração para remover variáveis ​​globais é difícil, e os dados vagabundos são um método para fazê-lo, e geralmente a maneira mais barata. Tem seus custos.

BobDalgleish
fonte
73
Ao pesquisar por "tramp data", consegui encontrar o livro "Code Complete" na minha assinatura do Safari. Há uma seção no livro chamada "Razões para usar dados globais" e uma das razões é "O uso de dados globais pode eliminar dados vagabundos". :). Sinto que "dados vagabundos" me permitirão encontrar mais literatura sobre como lidar com globais. Obrigado!
Ecerulm 31/10/16
9
@ JimmyJames, essas funções fazem coisas, é claro. Só que não com esse novo parâmetro específico que antes era apenas global.
Ecerulm 31/10/16
174
Em 20 anos de programação, eu nunca ouvi esse termo antes, nem seria imediatamente óbvio o que isso significava. Não estou reclamando da resposta, apenas sugerindo que o termo não é tão amplamente usado / conhecido. Talvez seja só eu.
Derek Elkins
6
Alguns dados globais estão bem. Em vez de chamá-lo de "dados globais", você pode chamá-lo de "ambiente" - porque é isso que é. O ambiente pode incluir, por exemplo, um caminho de cadeia de caracteres para appdata (no windows) ou, no meu projeto atual, todo o conjunto de pincéis, canetas, fontes e assim por diante, usados ​​por todos os componentes.
Robinson
7
@ Robinson Não é bem assim. Por exemplo, você realmente deseja que seu código de gravação de imagem toque em% AppData% ou prefere que seja necessário um argumento para onde escrever? Essa é a diferença entre estado global e um argumento. O "ambiente" pode facilmente ser uma dependência injetada, presente apenas para os responsáveis ​​pela interação com o ambiente. Os pincéis GDI + etc. são mais razoáveis, mas esse é realmente um caso de gerenciamento de recursos em um ambiente que não pode fazer isso por você - praticamente uma deficiência das APIs subjacentes e / ou do seu idioma / bibliotecas / tempo de execução.
Luaan
102

Eu não acho que isso, por si só, seja um anti-padrão. Acho que o problema é que você está pensando nas funções como uma cadeia, quando na verdade você deve pensar em cada uma delas como uma caixa preta independente ( NOTA : métodos recursivos são uma exceção notável a esse conselho.)

Por exemplo, digamos que eu precise calcular o número de dias entre duas datas do calendário para criar uma função:

int daysBetween(Day a, Day b)

Para fazer isso, crio uma nova função:

int daysSinceEpoch(Day day)

Então minha primeira função se torna simplesmente:

int daysBetween(Day a, Day b)
{
    return daysSinceEpoch(b) - daysSinceEpoch(a);
}

Não há nada antipadrão nisso. Os parâmetros do método daysBetween estão sendo passados ​​para outro método e nunca são referenciados no método, mas ainda são necessários para que esse método faça o que precisa.

O que eu recomendaria é analisar cada função e começar com algumas perguntas:

  • Essa função tem uma meta clara e focada ou é um método "faça algumas coisas"? Normalmente, o nome da função ajuda aqui e, se houver algo que não seja descrito pelo nome, isso é uma bandeira vermelha.
  • Existem muitos parâmetros? Às vezes, um método pode legitimamente precisar de muita entrada, mas ter tantos parâmetros torna oneroso o uso ou a compreensão.

Se você estiver visualizando uma confusão de códigos sem um único objetivo agrupado em um método, comece por desvendar isso. Isso pode ser entediante. Comece com as coisas mais fáceis de usar, passe para um método separado e repita até obter algo coerente.

Se você tiver muitos parâmetros, considere a refatoração de Método para objeto .

JimmyJames
fonte
2
Bem, eu não quis ser controverso com o (anti). Mas ainda me pergunto se existe um nome para a "situação" de ter que atualizar muitas assinaturas de funções. Eu acho que é mais um "cheiro de código" do que um antipadrão. Diz-me que há algo a ser corrigido neste código legado, se eu precisar atualizar 6 assinaturas de funções para acomodar a eliminação de um global. Mas eu acho que a aprovação de parâmetros normalmente é boa, e agradeço a orientação de como lidar com o problema subjacente.
Ecerulm 31/10/16
4
@ecerulm Não conheço um, mas direi que minha experiência me diz que converter os globais em parâmetros é absolutamente o caminho certo para começar a eliminá-los. Isso elimina o estado compartilhado para que você possa refatorar ainda mais. Suponho que haja mais problemas com esse código, mas não há descrição suficiente na sua descrição para saber o que são.
31416 JimmyJames
2
Eu também costumo seguir essa abordagem e provavelmente também o fará neste caso. Eu só queria melhorar meu vocabulário / terminologia em torno disso para poder pesquisar mais sobre isso e fazer perguntas melhores e mais focadas no futuro.
Ecerulm 31/10/16
3
@ecerulm Acho que não há um nome para isso. É como um sintoma comum a muitas doenças, bem como a não doenças, por exemplo, boca seca. Se você detalhar a descrição da estrutura do código, isso pode apontar para algo específico.
31416 JimmyJames
@ecerulm Diz a você que há algo a ser corrigido - agora é muito mais óbvio que algo deve ser corrigido do que era quando era uma variável global.
immibis 31/10/16
61

BobDalgleish já observou que esse (anti-) padrão é chamado de " dados vagabundos ".

Na minha experiência, a causa mais comum de dados excessivos de tramp é ter um monte de variáveis ​​de estado vinculadas que realmente devem ser encapsuladas em um objeto ou uma estrutura de dados. Às vezes, pode até ser necessário aninhar um monte de objetos para organizar adequadamente os dados.

Para um exemplo simples, considere um jogo que tem um personagem personalizável player, com propriedades como playerName, playerEyeColore assim por diante. Obviamente, o jogador também tem uma posição física no mapa do jogo e várias outras propriedades como, por exemplo, o nível de saúde atual e máximo, e assim por diante.

Em uma primeira iteração de um jogo desse tipo, pode ser uma escolha perfeitamente razoável transformar todas essas propriedades em variáveis ​​globais - afinal, existe apenas um jogador e quase tudo no jogo de alguma forma envolve o jogador. Portanto, seu estado global pode conter variáveis ​​como:

playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100

Mas, em algum momento, você pode achar que precisa mudar esse design, talvez porque queira adicionar um modo multiplayer ao jogo. Como primeira tentativa, você pode tentar tornar todas essas variáveis ​​locais e passá-las para funções que precisam delas. No entanto, você pode descobrir que uma ação específica do seu jogo pode envolver uma cadeia de chamadas de funções como, digamos:

mainGameLoop()
 -> processInputEvent()
     -> doPlayerAction()
         -> movePlayer()
             -> checkCollision()
                 -> interactWithNPC()
                     -> interactWithShopkeeper()

... e a interactWithShopkeeper()função faz o lojista endereçar o jogador pelo nome, de modo que agora você precisa repentinamente passar playerNamecomo dados vagabundos por todas essas funções. E, é claro, se o lojista achar que os jogadores de olhos azuis são ingênuos e cobrar preços mais altos por eles, será necessário passar playerEyeColorpor toda a cadeia de funções e assim por diante.

A solução adequada , nesse caso, é definir um objeto de jogador que encapsule o nome, a cor dos olhos, a posição, a saúde e quaisquer outras propriedades do personagem do jogador. Dessa forma, você só precisa passar esse único objeto para todas as funções que de alguma forma envolvem o jogador.

Além disso, várias das funções acima podem ser naturalmente transformadas em métodos desse objeto de jogador, o que lhes daria acesso automaticamente às propriedades do jogador. De certa forma, isso é apenas um açúcar sintático, já que chamar um método em um objeto efetivamente passa a instância do objeto como um parâmetro oculto para o método, mas faz com que o código pareça mais claro e natural se usado corretamente.

Obviamente, um jogo típico teria muito mais estado "global" do que apenas o jogador; por exemplo, você quase certamente teria algum tipo de mapa no qual o jogo ocorre, e uma lista de personagens não-jogadores que se movem no mapa, e talvez itens colocados nele, e assim por diante. Você também pode passar todos esses objetos ao redor como objetos vagabundos, mas isso atrapalhará novamente seus argumentos de método.

Em vez disso, a solução é fazer com que os objetos armazenem referências a quaisquer outros objetos com os quais eles tenham relacionamentos permanentes ou temporários. Assim, por exemplo, o objeto jogador (e provavelmente também qualquer objeto NPC) provavelmente deve armazenar uma referência ao objeto "mundo do jogo", que teria uma referência ao nível / mapa atual, para que um método como player.moveTo(x, y)esse não precise receber explicitamente o mapa como parâmetro.

Da mesma forma, se o personagem do jogador tivesse, digamos, um cachorro de estimação que os seguisse, naturalmente agruparíamos todas as variáveis ​​de estado que descrevem o cachorro em um único objeto, e forneceríamos ao objeto do jogador uma referência ao cachorro (para que o jogador possa , digamos, chame o cachorro pelo nome) e vice-versa (para que ele saiba onde está o jogador). E, é claro, provavelmente quereríamos fazer com que o jogador e o cachorro objetem as duas subclasses de um objeto "ator" mais genérico, para que possamos reutilizar o mesmo código para, digamos, mover os dois ao redor do mapa.

Ps. Embora eu tenha usado um jogo como exemplo, há outros tipos de programas em que esses problemas também surgem. Na minha experiência, porém, o problema subjacente tende a ser sempre o mesmo: você tem um monte de variáveis ​​separadas (locais ou globais) que realmente desejam agrupar-se em um ou mais objetos interligados. Se os "dados de rastreamento" que se intrometem em suas funções consistem em configurações de opções "globais" ou consultas em banco de dados em cache ou vetores de estado em uma simulação numérica, a solução é sempre identificar o contexto natural ao qual os dados pertencem e transformá-lo em um objeto (ou o equivalente mais próximo no idioma escolhido).

Ilmari Karonen
fonte
11
Esta resposta fornece algumas soluções para uma classe de problemas que podem existir. Pode haver situações em que os globais foram usados, o que indicaria uma solução diferente. Aceito um problema com a ideia de que tornar os métodos parte da classe player é equivalente a passar objetos para métodos. Isso ignora o polimorfismo que não é facilmente replicado dessa maneira. Por exemplo, se eu quiser criar tipos diferentes de jogadores que possuam regras diferentes sobre movimentos e tipos diferentes de propriedades, simplesmente passar esses objetos para uma implementação de método exigirá muita lógica condicional.
21416 JimmyJames #
6
@JimmyJames: Seu ponto de vista sobre polimorfismo é bom, e pensei em fazer isso sozinho, mas deixei de fora para impedir que a resposta ficasse ainda mais longa. O ponto que eu estava tentando (talvez mal) destacar era que, embora puramente em termos de fluxo de dados, haja pouca diferença entre , foo.method(bar, baz)e method(foo, bar, baz)há outras razões (incluindo polimorfismo, encapsulamento, localidade etc.) para preferir o primeiro.
Ilmari Karonen
@IlmariKaronen: também o benefício óbvio de proteger os protótipos de funções de futuras alterações / adições / exclusões / exclusões / refatorações nos objetos (por exemplo, playerAge). Isso por si só é inestimável.
SMCI
34

Não conheço um nome específico para isso, mas acho que vale a pena mencionar que o problema que você descreve é ​​apenas o problema de encontrar o melhor compromisso para o escopo desse parâmetro:

  • como variável global, o escopo é muito grande quando o programa atinge um determinado tamanho

  • como um parâmetro puramente local, o escopo pode ser muito pequeno quando leva a muitas listas de parâmetros repetitivas nas cadeias de chamadas

  • portanto, como uma troca, muitas vezes você pode transformar esse parâmetro em uma variável de membro em uma ou mais classes, e isso é o que eu chamaria de design de classe adequado .

Doc Brown
fonte
10
+1 para o design adequado da classe. Isso soa como um problema clássico, aguardando uma solução OO.
L0b0
21

Acredito que o padrão que você está descrevendo é exatamente a injeção de dependência . Vários comentaristas argumentaram que esse é um padrão , não um antipadrão , e eu tenderia a concordar.

Também concordo com a resposta de @ JimmyJames, onde ele afirma que é uma boa prática de programação tratar cada função como uma caixa preta que aceita todas as suas entradas como parâmetros explícitos. Ou seja, se você estiver escrevendo uma função que faz um sanduíche de manteiga de amendoim e geléia, você pode escrevê-la como

Sandwich make_sandwich() {
    PeanutButter pb = get_peanut_butter();
    Jelly j = get_jelly();
    return pb + j;
}
extern PhysicalRefrigerator g_refrigerator;
PeanutButter get_peanut_butter() {
    return g_refrigerator.get("peanut butter");
}
Jelly get_jelly() {
    return g_refrigerator.get("jelly");
}

mas seria uma prática melhor aplicar a injeção de dependência e escrevê-la assim:

Sandwich make_sandwich(Refrigerator& r) {
    PeanutButter pb = get_peanut_butter(r);
    Jelly j = get_jelly(r);
    return pb + j;
}
PeanutButter get_peanut_butter(Refrigerator& r) {
    return r.get("peanut butter");
}
Jelly get_jelly(Refrigerator& r) {
    return r.get("jelly");
}

Agora você tem uma função que documenta claramente todas as suas dependências em sua assinatura de função, o que é ótimo para facilitar a leitura. Afinal, é verdade que, para make_sandwichvocê precisar de acesso a um Refrigerator; portanto, a assinatura antiga da função era basicamente falsa por não levar a geladeira como parte de suas entradas.

Como bônus, se você fizer sua hierarquia de classes corretamente, evitar fatias e assim por diante, você pode até testar a make_sandwichfunção da unidade passando em a MockRefrigerator! (Pode ser necessário testá-lo dessa maneira, porque seu ambiente de teste de unidade pode não ter acesso a nenhum PhysicalRefrigerators.)

Entendo que nem todos os usos da injeção de dependência exigem o encanamento de um parâmetro com nome semelhante em muitos níveis na pilha de chamadas, por isso não estou respondendo exatamente à pergunta que você fez ... mas se você estiver procurando por mais leituras sobre esse assunto, "injeção de dependência" é definitivamente uma palavra-chave relevante para você.

Quuxplusone
fonte
10
Isso é claramente um anti- padrão. Não há absolutamente nenhum pedido para que uma geladeira seja aprovada. Agora, passar um IngredientSource genérico pode funcionar, mas e se você conseguir o pão da caixinha de pão, o atum da despensa, o queijo da geladeira ... injetando uma dependência da fonte de ingredientes na operação não relacionada de formar essas ingredientes em um sanduíche, você violou a separação de preocupações e fez um código cheirar.
Dewi Morgan
8
@ DewiMorgan: Obviamente, você poderia refatorar ainda mais para generalizar o Refrigeratorem um IngredientSource, ou até generalizar a noção de "sanduíche" em template<typename... Fillings> StackedElementConstruction<Fillings...> make_sandwich(ElementSource&); isso é chamado de "programação genérica" ​​e é razoavelmente poderoso, mas certamente é muito mais misterioso do que o OP realmente deseja entrar agora. Sinta-se à vontade para abrir uma nova pergunta sobre o nível adequado de abstração para programas sanduíche. ;)
Quuxplusone 1/11/16
11
Não se engane, o usuário não privilegiado não deve ter acesso make_sandwich().
dotancohen
2
@Dewi - XKCD link
Gavin Lock
19
O erro mais grave no seu código é que você está mantendo manteiga de amendoim na geladeira.
Malvolio
15

Essa é praticamente a definição de acoplamento do livro , um módulo com uma dependência que afeta profundamente outro e que cria um efeito cascata quando alterado. Os outros comentários e respostas estão corretos, dizendo que isso é uma melhoria em relação ao global, porque o acoplamento agora é mais explícito e mais fácil para o programador ver, em vez de subversivo. Isso não significa que não deva ser consertado. Você deve refatorar para remover ou reduzir o acoplamento, embora se ele estiver lá há um tempo, pode ser doloroso.

Karl Bielefeldt
fonte
3
Se level3()necessário newParam, isso é acoplamento, com certeza, mas de alguma forma diferentes partes do código precisam se comunicar. Eu não chamaria necessariamente um parâmetro de função de mau acoplamento se essa função fizesse uso do parâmetro. Penso que o aspecto problemático da cadeia é o acoplamento adicional introduzido level1()e level2()que não tem utilidade newParamsenão transmiti-lo. Boa resposta, +1 para acoplamento.
null,
6
@ null Se eles realmente não o usassem, poderiam criar um valor em vez de receber do chamador.
precisa saber é o seguinte
3

Embora essa resposta não responda diretamente à sua pergunta, acho que seria uma negligência deixar passar sem mencionar como melhorá-la (já que, como você diz, pode ser um antipadrão). Espero que você e outros leitores possam obter valor com este comentário adicional sobre como evitar "tramp data" (como Bob Dalgleish o nomeou tão útil para nós).

Concordo com as respostas que sugerem fazer algo mais OO para evitar esse problema. No entanto, outra maneira de ajudar a reduzir profundamente essa transmissão de argumentos sem simplesmente pular para " apenas passar uma classe em que você costumava passar muitos argumentos! " É refatorar para que algumas etapas do seu processo ocorram no nível mais alto, em vez de no nível mais baixo. 1. Por exemplo, aqui estão alguns códigos anteriores :

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   FilterAndReportStuff(stuffs, desiredName);
}

public void FilterAndReportStuff(IEnumerable<Stuff> stuffs, string desiredName) {
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   ReportStuff(stuffs.Filter(filter));
}

public void ReportStuff(IEnumerable<Stuff> stuffs) {
   stuffs.Report();
}

Observe que isso se torna ainda pior quanto mais coisas precisam ser feitas ReportStuff. Você pode ter que passar na instância do Reporter que deseja usar. E todos os tipos de dependências que precisam ser entregues, funcionam em função aninhada.

Minha sugestão é puxar tudo isso para um nível superior, onde o conhecimento das etapas requer vida em um único método, em vez de se espalhar por uma cadeia de chamadas de método. Claro que seria mais complicado em código real, mas isso lhe dá uma idéia:

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   var filteredStuffs = stuffs.Filter(filter)
   filteredStuffs.Report();
}

Observe que a grande diferença aqui é que você não precisa passar as dependências por uma longa cadeia. Mesmo se você nivelar não apenas um nível, mas alguns níveis de profundidade, se esses níveis também atingirem algum "nivelamento", de modo que o processo seja visto como uma série de etapas nesse nível, você terá feito uma melhoria.

Embora isso ainda seja processual e nada tenha sido transformado em objeto ainda, é um bom passo para decidir que tipo de encapsulamento você pode obter transformando algo em classe. O método profundamente encadeado chama no cenário anterior oculta os detalhes do que realmente está acontecendo e pode tornar o código muito difícil de entender. Embora você possa exagerar e acabar divulgando o código de nível superior sobre coisas que não deveriam, ou criar um método que faça muitas coisas violando o princípio da responsabilidade única, em geral, descobri que achatar as coisas um pouco ajuda com clareza e em fazer alterações incrementais em direção a um código melhor.

Observe que enquanto você faz tudo isso, considere a testabilidade. As chamadas do método encadeado realmente tornam o teste de unidade mais difícil, porque você não possui um bom ponto de entrada e ponto de saída na montagem para a fatia que deseja testar. Observe que, com esse achatamento, como seus métodos não precisam mais de tantas dependências, eles são mais fáceis de testar, não exigindo tantas zombarias!

Recentemente, tentei adicionar testes de unidade a uma classe (que não escrevi) que usava algo como 17 dependências, e todas tinham que ser ridicularizadas! Ainda não resolvi tudo, mas dividi a classe em três classes, cada uma lidando com um dos substantivos separados com os quais se preocupava, e reduzi a lista de dependências para 12 para o pior e para 8 para o pior. o melhor.

A testabilidade forçará você a escrever um código melhor. Você deve escrever testes de unidade, porque descobrirá que ele faz com que você pense sobre seu código de maneira diferente e escreverá um código melhor desde o início, independentemente de quantos erros você possa ter antes de escrever os testes de unidade.

ErikE
fonte
2

Você não está literalmente violando a Lei de Demeter, mas seu problema é semelhante ao de algumas maneiras. Como o objetivo da sua pergunta é encontrar recursos, sugiro que você leia sobre a Lei de Demeter e veja quanto desses conselhos se aplica à sua situação.

comida de gato
fonte
11
Um pouco fraco nos detalhes, o que provavelmente explica os votos negativos. No entanto, em espírito, essa resposta é exata: OP deve ler a Lei de Deméter - esse é o termo relevante.
Konrad Rudolph
4
FWIW, eu não acho que a Lei de Deméter (também conhecida como "menor privilégio") seja relevante. O caso do OP é onde sua função não seria capaz de fazer seu trabalho se não tivesse os dados do vagabundo (porque o próximo cara na pilha de chamadas precisa, porque o próximo cara precisa, e assim por diante). O privilégio mínimo / Lei de Demeter é relevante apenas se o parâmetro for realmente não utilizado e, nesse caso, a correção é óbvia: remova o parâmetro não utilizado!
Quuxplusone 1/11/16
2
A situação dessa pergunta não tem exatamente nada a ver com a Lei de Demeter ... Há uma similaridade superficial em relação à cadeia de chamadas de método, mas, caso contrário, é muito diferente.
Eric Rei
@Quuxplusone Possível, embora neste caso a descrição seja bastante confusa porque as chamadas em cadeia não fazem realmente sentido nesse cenário: elas devem ser aninhadas .
Konrad Rudolph
11
O problema é muito semelhante às violações de LoD, pois a refatoração usual sugerida para lidar com violações de LoD é a introdução de dados de vagabundo. IMHO, este é um bom ponto de partida para reduzir o acoplamento, mas não é suficiente.
Jørgen Fogh
1

Há casos em que o melhor (em termos de eficiência, capacidade de manutenção e facilidade de implementação) para ter determinadas variáveis ​​como globais, em vez da sobrecarga de sempre passar tudo ao redor (digamos que você tenha 15 ou mais variáveis ​​que precisam persistir). Portanto, faz sentido encontrar uma linguagem de programação que ofereça suporte ao escopo melhor (como variáveis ​​estáticas privadas do C ++) para aliviar a confusão potencial (de espaço para nome e que as coisas sejam adulteradas). Claro que isso é apenas conhecimento comum.

Mas, a abordagem declarada pelo OP é muito útil se alguém estiver fazendo Programação Funcional.

kozner
fonte
0

Não existe nenhum anti-padrão aqui, porque o chamador não conhece todos esses níveis abaixo e não se importa.

Alguém está chamando o upperLevel (params) e espera que o upperLevel faça seu trabalho. O que o upperLevel faz com os parâmetros não é da conta dos chamadores. upperLevel lida com o problema da melhor maneira possível, nesse caso, passando params para level1 (params). Tudo bem.

Você vê uma cadeia de chamadas - mas não há uma cadeia de chamadas. Existe uma função no topo fazendo seu trabalho da melhor maneira possível. E há outras funções. Cada função pode ser substituída a qualquer momento.

gnasher729
fonte