Várias classes com o mesmo nome, mas com namespaces diferentes?

11

Corri para algum código (c #, se for o caso) que possui classes que têm o mesmo nome, mas diferem em seus namespaces. Todos eles tendem a representar a mesma coisa lógica, mas geralmente são "visões" diferentes do mesmo objeto. Os diferentes namespaces às vezes fazem parte da mesma solução e até da mesma dll.

Tenho meus pensamentos, mas não consegui encontrar nada definitivo sobre essa prática para ser considerado um uso inteligente das ferramentas disponíveis ou um padrão a ser evitado.

Essa pergunta sobre a nomeação de smurfs toca nisso, mas de outra direção. Desambiguar os nomes provavelmente exigiria um prefixo arbitrário e difundido, que essa pergunta queria eliminar.

Quais são as diretrizes em torno dessa prática?

Telastyn
fonte
1
No OOP, existem muitas exceções, mas no começo considero esse design ruim, especialmente se todas as classes compartilharem o mesmo nível de acessibilidade. Quando leio o nome de uma classe, em qualquer arquivo, quero saber em que espaço para nome a classe está localizada imediatamente sem precisar consultar as diretivas do espaço para nome.
Leopold Asperger
Há um caso válido para ter vários objetos relacionados que lidam com o mesmo estado, mas geralmente eles são separados por função. Talvez você tenha um objeto que adapta um modelo para uma visualização. Outro pode realizar um cálculo no objeto. Outro pode ser um adaptador ou fachada. Então você pode ter FooAdapter, FooViewer, FooCalculator, etc., mas certamente não chamado a mesma coisa. Porém, sem mais informações sobre as classes específicas sobre as quais você está falando, é difícil fornecer uma resposta.
@ JohnGaughan - considere Employeecomo o exemplo. Há um funcionário que é para outros funcionários, um para RH, outro para exposição externa. Todos eles são chamados de "funcionários" e todos têm variedades de auxiliares (EmployeeRepository, EmployeeView, etc.). Todos eles estão na mesma DLL e, embora compartilhem algumas propriedades comuns (nome, título), todos diferem (número de telefone, salário).
Telastyn
Tendo trabalhado com sistemas que modelam Employeevárias vezes, parece que você precisa de um modelo com a união de todos os atributos e inclui um typeou departmentatributo. Caso contrário, haverá duplicação desnecessária de código.
5
Não era esse um dos motivos dos namespaces para começar? Para permitir colisões de nomes?
Dan Pichelman 31/07

Respostas:

5

Tentarei dar uma resposta contra esse uso, depois de ter visto e trabalhado nisso em um dos meus projetos também.

Legibilidade do código

Antes de tudo, considere que a legibilidade do código é importante e essa prática é contrária a isso. Alguém lê um pedaço de código e digamos que ele tem uma função doSomething(Employee e). Isso não é mais legível, porque, devido às 10 Employeeclasses diferentes existentes em pacotes diferentes, você precisará primeiro recorrer à declaração de importação para descobrir qual é realmente a sua entrada.

Essa é, no entanto, uma visão de alto nível e, geralmente, temos conflitos de nomes aparentemente aleatórios, com os quais ninguém se importa ou que encontra, porque o significado pode ser derivado do código restante e do pacote em que você está. Portanto, pode-se argumentar que, localmente, não há problema, porque é claro que, se você vir Employeedentro de um hrpacote, precisará saber que estamos falando de uma visão de RH do funcionário.

As coisas se desfazem assim que você deixa esses pacotes. Depois de trabalhar em um módulo / pacote / etc diferente e precisar trabalhar com um funcionário, você já estará sacrificando a legibilidade se não qualificar totalmente seu tipo. Além disso, ter 10 Employeeclasses diferentes significa que o preenchimento automático do IDE não funcionará mais e você deverá escolher manualmente o tipo de funcionário.

Duplicação de código

Devido à natureza de cada uma dessas classes estar relacionada entre si, seu código provavelmente se deteriorará devido a muita duplicação. Na maioria dos casos, você terá algo como o nome de um funcionário ou algum número de identificação, que cada uma das classes precisa implementar. Embora cada classe inclua seu próprio tipo de visão, se elas não compartilharem os dados subjacentes dos funcionários, você terá uma enorme quantidade de códigos inúteis, mas caros.

Complexidade do código

O que pode ser tão complexo que você pode perguntar? Afinal, cada uma das classes pode mantê-lo tão simples quanto desejar. O que realmente se torna um problema é como você propaga as mudanças. Em um software razoavelmente rico em recursos, você pode alterar os dados dos funcionários - e deseja refletir isso em qualquer lugar. Digamos que uma senhora acabou de se casar e você precise renomeá-la de XparaYdevido a isso. Difícil o suficiente para fazer isso em todos os lugares, mas ainda mais quando você tem todas essas classes distintas. Dependendo de suas outras opções de design, isso pode facilmente significar que cada uma das classes precisa implementar seu próprio tipo de ouvinte ou ser notificado sobre alterações - o que basicamente se traduz em um fator aplicado ao número de classes com as quais você deve lidar. . E mais duplicação de código, é claro, e menos legibilidade de código. Fatores como esse podem ser ignorados na análise de complexidade, mas certamente são irritantes quando aplicados ao tamanho da sua base de código.

Comunicação de código

Além dos problemas acima com a complexidade do código, que também estão relacionados às opções de design, você também reduz a clareza de design de nível superior e perde a capacidade de se comunicar adequadamente com especialistas em domínio. Ao discutir arquitetura, projetos ou requisitos, você não é mais livre para fazer declarações simples como given an employee, you can do .... Um desenvolvedor não saberá mais o que employeerealmente significa nesse momento. Embora um especialista em domínio saiba disso, é claro. Todos nós fazemos. tipo de. Mas, em termos de software, não é mais tão fácil se comunicar.

Como se livrar do problema

Depois de tomar conhecimento disso e se sua equipe concordar que é um problema grande o suficiente para ser resolvido, você terá que encontrar uma maneira de lidar com isso. Normalmente, você não pode pedir ao seu gerente que dê uma semana inteira à equipe para que ele possa retirar o lixo. Portanto, em essência, você terá que encontrar uma maneira de eliminar parcialmente essas classes, uma de cada vez. A parte mais importante disso é decidir - com toda a equipe - o que Employeerealmente é. Você não integrar todos os atributos individuais em um deus-empregado. Pense mais em um funcionário principal e, mais importante, decida onde essa Employeeclasse residirá.

Se você faz revisões de código, é particularmente fácil garantir que o problema não melhore ainda mais, ou seja, interrompa todo mundo no caminho certo quando quiser adicionar outro Employeenovamente. Também verifique se os novos subsistemas aderem ao acordado Employeee não têm permissão para acessar as versões antigas.

Dependendo da sua linguagem de programação, convém marcar as classes que eventualmente serão eliminadas com algo como @Deprecatedpara ajudar sua equipe a perceber imediatamente que eles estão trabalhando com algo que precisará ser alterado.

Quanto a se livrar das classes de funcionários desatualizadas, você pode decidir, para cada caso individual, como é melhor eliminá-las ou simplesmente concordar com um padrão comum. Você pode afixar o nome da classe e envolvê-lo em torno do funcionário real, pode usar padrões (decorador ou adaptador), ou, ou ou.

Para resumir uma longa história: Essa "prática" é tecnicamente sólida, mas está cheia de custos ocultos que só ocorrerão mais adiante. Embora você não consiga se livrar imediatamente do problema, você pode começar imediatamente contendo os efeitos nocivos.

Frank
fonte
Ter uma classe Core parece ser uma boa solução. Outras classes podem se estender a partir dele. Sobre o IDE, o preenchimento automático é inteligente o suficiente para saber qual classe sugerir para você corresponder melhor ao contexto. No geral, não vejo por que esse é um problema tão grande, especialmente se a base de código é usada internamente.
InformedA
1
Não é inteligente o suficiente quando você está fora de um contexto. Considere uma classe, que realmente precisa das duas classes problemáticas - o preenchimento automático não ajudará em nada. E nem sequer as diferencia das outras dez classes. Mas o problema real é esse novo membro da equipe, que nem sabe o que são todos esses domínios de pacote e qual ele deve escolher.
31414 Frank
Se várias classes com o mesmo nome estiverem sendo usadas no mesmo contexto, será difícil. Eu não vi esse caso. Eu já vi várias classes com o mesmo nome, mas elas não são frequentemente usadas no mesmo contexto que você mencionou.
InformedA
@ randomA - infelizmente, esse caso é bastante comum nessa base de código.
Telastyn 31/07
Bons pontos, mas você realmente não deu uma solução para o problema principal, apenas a metodologia depois que o problema principal foi resolvido ("o que realmente é um Funcionário"). A única "solução" óbvia ("integra todos os atributos individuais em um empregado divino") que você descartou, com razão. Digamos que você tenha DB.Employee, Marshalling.Employee, ViewModels.Employee, GUI.Views.Employee, etc., qual é a sua solução?
Pablo H
9

O Scala Collection Framework é um bom exemplo disso. Existem coleções mutáveis ​​e imutáveis, bem como coleções seriais e paralelas (e no futuro talvez distribuídas também). Portanto, existem, por exemplo, quatro Mapcaracterísticas:

scala.collection.Map
scala.collection.immutable.Map
scala.collection.mutable.Map
scala.collection.concurrent.Map

mais três ParMaps:

scala.collecion.parallel.ParMap
scala.collecion.parallel.immutable.ParMap
scala.collecion.parallel.mutable.ParMap

Ainda mais interessante:

scala.collection.immutable.Map            extends scala.collection.Map
scala.collection.mutable.Map              extends scala.collection.Map
scala.collection.concurrent.Map           extends scala.collection.mutable.Map

scala.collecion.parallel.ParMap           extends scala.collection.Map
scala.collecion.parallel.immutable.ParMap extends scala.collecion.parallel.ParMap
scala.collecion.parallel.mutable.ParMap   extends scala.collecion.parallel.ParMap

Então, ParMapestende ParMapestende Mape Mapestende Mapestende Map.

Mas, vamos enfrentá-lo: o que mais se você chamá-los? Isso faz todo o sentido. É para isso que servem os namespaces!

Jörg W Mittag
fonte
3
"É para isso que servem os namespaces!" => +1
svidgen
Eu gosto disso, mas no mundo C # / .NET quando mudei as classes paralelas para um subespaço Parallel, isso entra em conflito com a classe auxiliar System.Threading.Parallel, que por acaso estou usando muito para obter paralelismo. Eu não queria usar o namespace 'Concurrent', pois isso já está sendo usado para significar 'acesso simultâneo' nas classes de coleção. Como compromisso, optei pelo subdomínio 'Parallelized'.
Redcalx #
2

Essa é uma pergunta realmente interessante, onde as duas respostas essencialmente opostas estão corretas. Aqui está um exemplo real dessa discussão que estamos tendo em um projeto meu.

Eu acho que existem duas considerações muito importantes até agora que poderiam fazer com que você seguisse uma direção ou outra, baseando-se nas duas respostas de @Jorg e @Frank:

  1. Se você antecipar uma situação em que mais de uma das classes com o mesmo nome possa precisar ser usada no mesmo contexto de código, esse é um motivo para não usar o mesmo nome. Ou seja, se você precisa trabalhar, hr.Employeemas ao mesmo tempo precisa acessar as permissões security.Employee, isso será muito rápido e irritante.
  2. Por outro lado, se todas as suas classes com o mesmo nome tiverem APIs iguais ou muito semelhantes, esse é um bom exemplo de quando você deve usar o mesmo nome com espaços para nome diferentes. O exemplo de Scala é precisamente este, onde todas as Mapclasses implementam a mesma interface ou são subclasses uma da outra. Nesse caso, a ambiguidade ou "confusão" pode ser considerada uma coisa boa, porque o usuário pode tratar corretamente todas as implementações da classe ou interface da mesma maneira ... qual é o ponto das interfaces e subclasses.
S'pht'Kr
fonte