Por que você não usaria a diretiva 'using' em C #?


71

Os padrões de codificação existentes em um grande projeto C # incluem uma regra de que todos os nomes de tipos sejam totalmente qualificados, proibindo o emprego da diretiva 'using'. Então, ao invés do familiar:

using System.Collections.Generic;

.... other stuff ....

List<string> myList = new List<string>();

(Provavelmente não é surpresa que vartambém seja proibido.)

Eu acabo com:

System.Collections.Generic.List<string> myList = new System.Collections.Generic.List<string>();

Isso representa um aumento de 134% na digitação, e nada disso fornece informações úteis. Na minha opinião, 100% do aumento é ruído (desordem) que realmente impede a compreensão.

Em mais de 30 anos de programação, eu já vi esse padrão proposto uma ou duas vezes, mas nunca foi implementado. A lógica por trás disso me escapa. A pessoa que impõe o padrão não é estúpida, e não acho que seja malicioso. O que deixa equivocada como a única outra alternativa, a menos que esteja faltando alguma coisa.

Você já ouviu falar desse padrão sendo imposto? Se sim, qual foi o motivo por trás disso? Você consegue pensar em outros argumentos além de "é estúpido" ou "todo mundo emprega using" que podem convencer essa pessoa a remover essa proibição?

Raciocínio

Os motivos desta proibição foram:

  1. É complicado passar o mouse sobre o nome para obter o tipo totalmente qualificado. É melhor sempre ter o tipo totalmente qualificado visível o tempo todo.
  2. Os trechos de código enviados por email não têm o nome completo e, portanto, podem ser difíceis de entender.
  3. Ao visualizar ou editar o código fora do Visual Studio (Notepad ++, por exemplo), é impossível obter o nome completo do tipo.

Meu argumento é que todos os três casos são raros e que fazer com que todos paguem o preço de códigos desordenados e menos compreensíveis apenas para acomodar alguns casos raros é equivocado.

Os possíveis problemas de conflito de espaço para nome, que eu esperava ser a principal preocupação, nem sequer foram mencionados. Isso é especialmente surpreendente porque temos um espaço para nome MyCompany.MyProject.Core, o que é uma péssima idéia. Aprendi há muito tempo que nomear qualquer coisa Systemou Coreem C # é um caminho rápido para a loucura.

Como outros já apontaram, os conflitos de espaço para nome são facilmente manipulados por refatoração, aliases de espaço para nome ou qualificação parcial.


Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
maple_shaft

11
Um exemplo do mundo real de por que poderia ser uma boa idéia (mas no mundo C ++): uma resposta para Por que “usar o espaço para nome std;” é considerado uma má prática? , próximo a "que ambas as diretivas e declarações 'usando' são proibidas".
22416 Peter Mortensen

Antes de ler a seção Raciocínio, eu meio que adivinhei as razões práticas, porque minha empresa tem regras semelhantes.
Gqqnbig

Respostas:


69

A questão mais ampla:

Você já ouviu falar desse padrão sendo imposto? Se sim, qual foi o motivo por trás disso?

Sim, ouvi falar disso e o uso de nomes de objetos totalmente qualificados evita colisões de nomes. Embora raro, quando eles acontecem, eles podem ser excepcionalmente espinhosos para descobrir.


Um exemplo: esse tipo de cenário provavelmente é melhor explicado com um exemplo.

Digamos que temos dois Lists<T>pertencentes a dois projetos separados.

System.Collections.Generic.List<T>
MyCorp.CustomCollections.Optimized.List<T>

Quando usamos o nome completo do objeto, fica claro qual List<T>está sendo usado. Essa clareza obviamente vem à custa da verbosidade.

E você pode estar argumentando: "Espere! Ninguém jamais usaria duas listas como essa!" É aí que vou apontar o cenário de manutenção.

Você é um módulo escrito Foopara sua empresa que usa a empresa aprovada, otimizada List<T>.

using MyCorp.CustomCollections.Optimized;

public class Foo {
    List<object> myList = ...;
}

Mais tarde, um novo desenvolvedor decide estender o trabalho que você está fazendo. Sem conhecer os padrões da empresa, eles atualizam o usingbloco:

using MyCorp.CustomCollections.Optimized;
using System.Collections.Generic;

E você pode ver como as coisas correm mal com pressa.

Deve ser trivial ressaltar que você pode ter duas classes proprietárias com o mesmo nome, mas em espaços para nome diferentes no mesmo projeto. Portanto, não é apenas uma preocupação em colidir com as classes fornecidas pela estrutura .NET.

MyCorp.WeightsAndLengths.Measurement();
MyCorp.TimeAndSpace.Measurement();

A realidade:
Agora, é provável que isso ocorra na maioria dos projetos? Não, na verdade não. Mas quando você está trabalhando em um projeto grande com muitas entradas, você faz o possível para minimizar as chances de as coisas explodirem em você.

Grandes projetos com várias equipes contribuintes são um tipo especial de fera no mundo das aplicações. As regras que parecem irracionais para outros projetos se tornam mais pertinentes devido aos fluxos de entrada do projeto e à probabilidade de que aqueles que contribuem não tenham lido as diretrizes do projeto.

Isso também pode ocorrer quando dois projetos grandes são mesclados. Se os dois projetos tiverem classes com nomes semelhantes, você verá colisões quando começar a fazer referência de um projeto para o outro. E os projetos podem ser grandes demais para serem refatorados ou a gerência não aprovará a despesa para financiar o tempo gasto na refatoração.


Alternativas:
Embora você não tenha perguntado, vale ressaltar que essa não é uma ótima solução para o problema. Não é uma boa ideia criar classes que colidirão sem suas declarações de namespace.

List<T>, em particular, deve ser tratado como uma palavra reservada e não usado como o nome para suas classes.

Da mesma forma, os espaços para nome individuais no projeto devem se esforçar para ter nomes de classe exclusivos. Ter que tentar lembrar com que namespace Foo()você está trabalhando é uma sobrecarga mental que é melhor evitar. Disse de outra maneira: ter MyCorp.Bar.Foo()e MyCorp.Baz.Foo()vai enganar seus desenvolvedores e é melhor evitar.

Se nada mais, você pode usar espaços para nome parciais para resolver a ambiguidade. Por exemplo, se você absolutamente não puder renomear nenhuma das Foo()classes, poderá usar seus espaços de nomes parciais:

Bar.Foo() 
Baz.Foo()

Razões específicas para o seu projeto atual:

Você atualizou sua pergunta com os motivos específicos que lhe foram dados para o seu projeto atual seguindo esse padrão. Vamos dar uma olhada neles e realmente desviar a trilha dos coelhos.

É complicado passar o mouse sobre o nome para obter o tipo totalmente qualificado. É melhor sempre ter o tipo totalmente qualificado visível o tempo todo.

"Pesado?" Hum, não. Irritante, talvez. Mover algumas onças de plástico para deslocar um ponteiro na tela não é complicado . Mas eu discordo.

Essa linha de raciocínio parece mais um encobrimento do que qualquer outra coisa. De antemão, eu acho que as classes no aplicativo têm nomes fracos e você precisa confiar no namespace para coletar a quantidade apropriada de informações semânticas em torno do nome da classe.

Essa não é uma justificativa válida para nomes de classe totalmente qualificados, talvez seja uma justificação válida para o uso de nomes de classe parcialmente qualificados.

Os trechos de código enviados por email não têm o nome completo e, portanto, podem ser difíceis de entender.

Essa linha de raciocínio (continuação?) Reforça minha suspeita de que as classes sejam atualmente mal nomeadas. Novamente, ter nomes de classes ruins não é uma boa justificativa para exigir que tudo use um nome de classe totalmente qualificado. Se o nome da classe é difícil de entender, há muito mais coisas erradas do que o que os nomes de classe totalmente qualificados podem corrigir.

Ao visualizar ou editar o código fora do Visual Studio (Notepad ++, por exemplo), é impossível obter o nome completo do tipo.

De todas as razões, essa quase me fez cuspir minha bebida. Mas, novamente, eu discordo.

Fico imaginando por que a equipe está frequentemente editando ou visualizando o código fora do Visual Studio? E agora estamos analisando uma justificativa bastante ortogonal ao que os namespaces devem fornecer. Esse é um argumento suportado por ferramentas, enquanto os espaços para nome existem para fornecer estrutura organizacional ao código.

Parece que o seu projeto sofre de más convenções de nomenclatura, além de desenvolvedores que não estão tirando proveito do que as ferramentas podem oferecer para eles. E, em vez de resolver esses problemas reais, eles tentaram dar um soco em um dos sintomas e estão exigindo nomes de classe totalmente qualificados. Eu acho que é seguro categorizar isso como uma abordagem equivocada.

Dado que há classes mal nomeadas e supondo que você não possa refatorar, a resposta correta é usar o IDE do Visual Studio para obter todas as vantagens. Possivelmente considere adicionar um plugin como o pacote VS PowerTools. Então, quando estou olhando, AtrociouslyNamedClass()posso clicar no nome da classe, pressionar F12 e ser levado diretamente para a definição da classe, a fim de entender melhor o que ela está tentando fazer. Da mesma forma, posso clicar em Shift-F12 para encontrar todos os pontos no código que atualmente sofrem com o uso AtrociouslyNamedClass().

Em relação às preocupações externas com ferramentas - a melhor coisa a fazer é parar com isso. Não envie trechos por e-mail se eles não estiverem imediatamente claros a que se referem. Não use outras ferramentas fora do Visual Studio, pois elas não possuem a inteligência que envolve o código de que sua equipe precisa. O Notepad ++ é uma ferramenta incrível, mas não é recortada para esta tarefa.

Por isso, concordo com a sua avaliação em relação às três justificativas específicas que lhe foram apresentadas. Dito isto, o que eu acho que lhe disseram foi "Temos problemas subjacentes neste projeto que não podem / não vão resolver e é assim que os 'consertamos'". E isso obviamente aborda questões mais profundas da equipe que podem servir de sinalizador vermelho para você.


11
+1. Na verdade, também encontrei algumas colisões desagradáveis ​​em nossos espaços para nome internos. Tanto na portabilidade do código legado para um projeto "2.0", como apenas com alguns poucos nomes de classe que são semanticamente válidos em diferentes contextos de namespace. O mais complicado é que duas coisas com o mesmo nome geralmente têm interfaces semelhantes, para que o compilador não se queixe ... seu tempo de execução terá!
Svidgen

23
Esse problema é resolvido usando aliases de espaço para nome, não impondo regras tolas que exigem código por causa do uso explícito do espaço para nome.
David Arno

4
@ DavidArno - Eu não discordo de você. No entanto, projetos grandes podem não ter essa opção. Estou apenas apontando por que um grande projeto pode impor essa regra. Não estou defendendo a regra, no entanto. Só que às vezes é necessário porque a equipe não tem outras opções.

4
@ GlenH7, você pode querer especificar as outras soluções disponíveis na sua resposta. Eu odiaria ver as pessoas tropeçarem nisso e pensarem "Oh. Essa é uma ótima idéia!" +1 por restante objetivo e pragmático.
RubberDuck

3
@ JimMischel - Parece que a regra está sendo usada como muleta para alguma coisa . Pode não ser possível determinar o que é essa coisa. Em um nível alto, sim, às vezes há justificativa para não permitir, usingsmas não parece que seu projeto se qualifique como um desses casos.

16

Eu trabalhei em uma empresa onde eles tinham muitas classes com o mesmo nome, mas em bibliotecas de classes diferentes.

Por exemplo,

  • Domain.Customer
  • Legacy.Customer
  • ServiceX.Customer
  • ViewModel.Customer
  • Database.Customer

Design ruim, você pode dizer, mas sabe como essas coisas se desenvolvem organicamente e qual é a alternativa - renomeie todas as classes para incluir o espaço para nome? Refatoração importante de tudo?

De qualquer forma, havia vários locais onde os projetos que referenciavam todas essas bibliotecas diferentes precisavam usar várias versões da classe.

Com o usingdiretamente no topo, essa foi uma grande dor de cabeça, o ReSharper faria coisas estranhas para tentar fazê-lo funcionar, reescrevendo as usingdiretrizes em tempo real, você obteria o Cliente não pode ser escolhido como Erros no estilo do cliente e não sabemos qual o tipo correto.

Então, tendo pelo menos o espaço para nome parcial,

var cust = new Domain.Customer

ou

public void Purchase(ViewModel.Customer customer)

melhorou bastante a legibilidade do código e tornou explícito qual objeto você queria.

Não era um padrão de codificação, não era o espaço para nome completo e não era aplicado a todas as classes como padrão.


13
"qual é a alternativa, renomeie todas as classes para incluir o namespace? grande refatoração de tudo?" Sim, essa é a alternativa (correta).
David Arno

2
Somente no curto prazo. É muito mais barato do que usar espaços de nome explícitos no código a longo prazo.
David Arno

3
É um prazer aceitar esse argumento, desde que você me pague em dinheiro, não em ações.
Ewan

8
@ David: Não, não é a alternativa correta. Qualificar totalmente todos os identificadores ou pelo menos aqueles que realmente colidem é a maneira correta e a razão pela qual os espaços para nome existem em primeiro lugar é fornecer colisões quando o mesmo nome é usado em módulos diferentes. O ponto é que alguns dos módulos podem ser de terceiros, cuja base de código você não pode modificar. Então, o que você faz quando identificadores de duas bibliotecas de terceiros diferentes colidem e você precisa usar as duas bibliotecas, mas não pode refatorar nenhuma delas? Os espaços para nome existem, porque a refatoração nem sempre é possível.
Kaiserludi

4
@CarlSixsmith, se alguém subscreve as opiniões de Martin Fowler sobre refatoração, se a mudança afeta o usuário, então você está correto, não é refatoração; é outra forma de reestruturação. Isso levanta dois pontos: 1. todos os métodos públicos fazem parte automaticamente da API? 2. O próprio Fowler reconhece que está travando uma batalha perdida por causa dessa definição, com um número crescente de pessoas usando o termo "refatoração" para significar reestruturação generalizada, incluindo mudanças públicas.
David Arno

7

Não é bom ser muito detalhado ou muito curto: deve-se encontrar um meio de ouro sendo detalhado o suficiente para evitar erros sutis, evitando escrever muito código.

Em C #, um exemplo disso são os nomes dos argumentos. Eu encontrei várias vezes um problema em que passei horas procurando uma solução, enquanto um estilo de escrita mais detalhado poderia impedir o bug em primeiro lugar. O problema consiste em um erro na ordem dos argumentos. Por exemplo, parece certo para você?

if (...) throw new ArgumentNullException("name", "The name should be specified.");
if (...) throw new ArgumentException("name", "The name contains forbidden characters.");

À primeira vista, parece perfeitamente bem, mas há um erro. As assinaturas dos construtores das exceções são:

public ArgumentException(string message, string paramName)
public ArgumentNullException(string paramName, string message)

Percebeu a ordem dos argumentos?

Ao adotar um estilo mais detalhado, em que o nome de um argumento é especificado toda vez que o método aceita dois ou mais argumentos do mesmo tipo , impedimos que o erro ocorra novamente e, portanto:

if (...) throw new ArgumentNullException(paramName: "name", message: "The name should be specified.");
if (...) throw new ArgumentException(paramName: "name", message: "The name contains forbidden characters.");

é obviamente mais demorado para escrever, mas resulta em menos tempo de depuração perdida.

Levado ao extremo, pode-se forçar o uso de argumentos nomeados em todos os lugares , inclusive em métodos como doSomething(int, string)onde não há como misturar a ordem dos parâmetros. Provavelmente, isso prejudicará o projeto a longo prazo, resultando em muito mais código sem o benefício de impedir o bug descrito acima.

No seu caso, a motivação por trás do “não usingregra” é que deve torná-lo mais difícil de usar o tipo errado, como MyCompany.Something.List<T>vs. System.Collections.Generic.List<T>.

Pessoalmente, eu evitaria essa regra porque, IMHO, o custo do código de difícil leitura está muito acima do benefício de um risco ligeiramente reduzido de usar mal o tipo errado, mas pelo menos, há uma razão válida para essa regra.


Situações como essas podem ser detectadas por ferramentas. ReSharper marca o paramNamecom o InvokerParameterNameatributo e emite um aviso se existe tal parâmetro existe.
Johnbot 9/10/2015

2
@ Johnbot: eles certamente podem, mas em um número muito limitado de casos. E se eu tiver um SomeBusiness.Something.DoStuff(string name, string description)? Nenhuma ferramenta é inteligente o suficiente para entender o que é um nome e o que é uma descrição.
Arseni Mourzenko

@ArseniMourzenko, neste caso, mesmo o ser humano precisa levar tempo para descobrir se a invocação está correta ou não.
Gqqnbig

6

Os espaços para nome fornecem ao código uma estrutura hierárquica, permitindo nomes mais curtos.

   MyCompany.Headquarters.Team.Leader
   MyCompany.Warehouse.Team.Leader

Se você permitir a palavra-chave "using", haverá uma cadeia de eventos ...

  • Todo mundo está realmente usando um espaço para nome global
    (porque inclui todos os espaços que poderia desejar)
  • As pessoas começam a ter conflitos de nome ou
  • se preocupe em obter conflitos de nome.

Portanto, para evitar o risco, todo mundo começa a usar nomes realmente longos:

   HeadquartersTeamLeader
   WarehouseTeamLeader

A situação aumenta ...

  • Outra pessoa já pode ter escolhido um nome, então você usa um mais longo.
  • Os nomes ficam cada vez mais longos.
  • Os comprimentos podem exceder 60 caracteres, sem máximo - isso é ridículo.

Eu já vi isso acontecer, então posso simpatizar com empresas que proíbem "usar".


11
A proibição usingé a opção nuclear. Os aliases de namespace e a qualificação parcial são preferíveis.
Jim Mischel

1

O problema usingé que ele é adicionado magicamente ao espaço para nome sem uma declaração explícita. Isso é muito mais um problema para o programador de manutenção que encontra algo como List<>e sem estar familiarizado com o domínio, não sabe qual usingcláusula o introduziu.

Um problema semelhante ocorre em Java se alguém escreve em import Java.util.*;vez de import Java.util.List;por exemplo; a última declaração documenta qual nome foi introduzido para que, quando List<>ocorrer mais tarde na fonte, sua origem seja conhecida na importdeclaração.


6
Embora seja verdade que alguém não familiarizado com o domínio tenha alguma dificuldade, tudo o que ele precisa fazer é passar o mouse sobre o nome do tipo e ele obterá o nome completo. Ou ele pode clicar com o botão direito e ir diretamente para a definição de tipo. Para os tipos do .NET Framework, ele provavelmente já sabe onde estão as coisas. Para tipos específicos de domínio, levará talvez uma semana para ficar confortável. Nesse ponto, os nomes totalmente qualificados são apenas ruídos que impedem a compreensão.
Jim Mischel

Jim .. Eu apenas tentei passar o mouse sobre uma definição e não funcionou. Você considerou a possibilidade de alguns programadores no mundo não usarem IDEs da Microsoft? De qualquer forma, seu contador não invalida meu argumento, de que a fonte com o uso não é mais auto-documentada
Michael Montenero

2
Sim, existem pessoas trabalhando em C # que estão se prejudicando por não usar as melhores ferramentas disponíveis. E, embora o argumento de que a qualificação completa seja "auto-documentada" tenha algum mérito, esse código tem um custo enorme em legibilidade. Todo esse código estranho cria ruídos que obscurecem as partes do código que realmente importam.
Jim Mischel
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.