Perché non dovresti usare la direttiva "using" in C #?


71

Gli standard di codifica esistenti in un grande progetto C # includono una regola secondo la quale tutti i nomi dei tipi devono essere pienamente qualificati, vietando l'impiego della direttiva "utilizzo". Quindi, piuttosto che il familiare:

using System.Collections.Generic;

.... other stuff ....

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

(Probabilmente non è una sorpresa che sia varanche proibito.)

Finisco con:

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

Si tratta di un aumento del 134% nella digitazione, senza che tale aumento fornisca informazioni utili. A mio avviso, il 100% dell'aumento è il rumore (disordine) che in realtà impedisce la comprensione.

In oltre 30 anni di programmazione, ho visto uno standard simile proposto una o due volte, ma mai implementato. La logica alla base mi sfugge. La persona che impone lo standard non è stupida e non penso che sia maliziosa. Il che lascia erroneamente l'unica alternativa a meno che non mi manchi qualcosa.

Hai mai sentito parlare di un simile standard imposto? In tal caso, qual era il motivo dietro? Riesci a pensare ad argomenti diversi da "è stupido" o "tutti gli altri impiegano using" che potrebbero convincere questa persona a rimuovere questo divieto?

Ragionamento

Le ragioni di questo divieto erano:

  1. È ingombrante passare il mouse sul nome per ottenere il tipo completo. È meglio avere sempre il tipo completo qualificato sempre visibile.
  2. Gli snippet di codice inviati tramite email non hanno il nome completo e quindi possono essere difficili da capire.
  3. Quando si visualizza o si modifica il codice al di fuori di Visual Studio (Notepad ++, ad esempio), è impossibile ottenere il nome del tipo completo.

La mia tesi è che tutti e tre i casi sono rari e che far pagare a tutti il ​​prezzo del codice ingombrante e meno comprensibile solo per accogliere alcuni casi rari è fuorviante.

Non sono stati nemmeno menzionati potenziali problemi di conflitto nello spazio dei nomi, che mi aspettavo fosse la preoccupazione principale. Ciò è particolarmente sorprendente perché abbiamo uno spazio dei nomi MyCompany.MyProject.Core, che è un'idea particolarmente negativa. Ho imparato molto tempo fa che nominare qualcosa Systemo Corein C # è un percorso rapido verso la follia.

Come altri hanno sottolineato, i conflitti nello spazio dei nomi possono essere facilmente gestiti da refactoring, alias dello spazio dei nomi o qualificazione parziale.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
maple_shaft

1
Un esempio reale del perché potrebbe essere una buona idea (ma nel mondo C ++): una risposta a Perché "usare lo spazio dei nomi std;" è considerata una cattiva pratica? , vicino a "che entrambe le direttive e le dichiarazioni" che utilizzano "sono vietate".
Peter Mortensen,

Prima di leggere la sezione Ragionamento, ho indovinato i motivi poco pratici perché la mia azienda ha regole simili.
Gqqnbig

Risposte:


69

La domanda più ampia:

Hai mai sentito parlare di un simile standard imposto? In tal caso, qual era il motivo dietro?

Sì, ne ho sentito parlare e l'utilizzo di nomi di oggetti completi impedisce le collisioni di nomi. Sebbene rari, quando accadono, possono essere eccezionalmente spinosi da capire.


Un esempio: quel tipo di scenario è probabilmente meglio spiegato con un esempio.

Diciamo che ne abbiamo due Lists<T>appartenenti a due progetti separati.

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

Quando utilizziamo il nome oggetto completo, è chiaro su quale List<T>viene utilizzato. Questa chiarezza ovviamente viene a scapito della verbosità.

E potresti dire: "Aspetta! Nessuno userebbe mai due liste del genere!" È qui che indicherò lo scenario di manutenzione.

Sei un modulo scritto Fooper la tua società che utilizza la società approvata, ottimizzata List<T>.

using MyCorp.CustomCollections.Optimized;

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

Più tardi, un nuovo sviluppatore decide di estendere il lavoro svolto. Non essendo a conoscenza degli standard dell'azienda, aggiornano il usingblocco:

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

E puoi vedere come le cose vanno male in fretta.

Dovrebbe essere banale sottolineare che potresti avere due classi proprietarie con lo stesso nome ma in spazi dei nomi diversi all'interno dello stesso progetto. Quindi non è solo una preoccupazione sulla collisione con le classi fornite da .NET framework.

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

La realtà:
ora, è probabile che ciò accada nella maggior parte dei progetti? No, non proprio. Ma quando lavori su un grande progetto con molti input, fai del tuo meglio per ridurre al minimo le possibilità che le cose esplodano su di te.

I grandi progetti con più team che contribuiscono sono un tipo speciale di bestia nel mondo delle applicazioni. Regole che sembrano irragionevoli per altri progetti diventano più pertinenti a causa dei flussi di input al progetto e della probabilità che coloro che contribuiscono non abbiano letto le linee guida del progetto.

Ciò può verificarsi anche quando due grandi progetti vengono uniti. Se entrambi i progetti avevano classi con nomi simili, vedrai delle collisioni quando inizi a fare riferimento da un progetto all'altro. E i progetti potrebbero essere troppo grandi per essere sottoposti a refactoring o la direzione non approverà le spese per finanziare il tempo impiegato per il refactoring.


Alternative: anche
se non lo hai chiesto, vale la pena sottolineare che questa non è un'ottima soluzione al problema. Non è una buona idea creare classi che si scontreranno senza le loro dichiarazioni dello spazio dei nomi.

List<T>, in particolare, dovrebbe essere trattato come una parola riservata e non utilizzato come nome per le tue classi.

Allo stesso modo, i singoli spazi dei nomi all'interno del progetto dovrebbero cercare di avere nomi di classe univoci. Dover cercare di ricordare con quale spazio dei nomi Foo()stai lavorando è il sovraccarico mentale che è meglio evitare. Detto in altro modo: avere MyCorp.Bar.Foo()e MyCorp.Baz.Foo()farà triplicare i tuoi sviluppatori e evitarlo al meglio.

Se non altro, puoi usare spazi dei nomi parziali per risolvere l'ambiguità. Ad esempio, se non puoi assolutamente rinominare nessuna delle due Foo()classi, puoi usare i loro spazi dei nomi parziali:

Bar.Foo() 
Baz.Foo()

Motivi specifici del tuo attuale progetto:

Hai aggiornato la tua domanda con i motivi specifici che ti sono stati dati per il tuo progetto attuale seguendo quello standard. Diamo un'occhiata a loro e divagiamo davvero lungo il sentiero dei coniglietti.

È ingombrante passare il mouse sul nome per ottenere il tipo completo. È meglio avere sempre il tipo completo qualificato sempre visibile.

"Ingombrante?" Uhm, no. Forse fastidioso. Spostare qualche oncia di plastica per spostare un puntatore sullo schermo non è complicato . Ma sto divagando.

Questa linea di ragionamento sembra più un insabbiamento che altro. Direttamente, immagino che le classi all'interno dell'applicazione abbiano un nome debole e che tu debba fare affidamento sullo spazio dei nomi per raccogliere la quantità appropriata di informazioni semantiche che circondano il nome della classe.

Questa non è una giustificazione valida per i nomi di classe pienamente qualificati, forse è una giustificazione valida per l'utilizzo di nomi di classe parzialmente qualificati.

Gli snippet di codice inviati tramite email non hanno il nome completo e quindi possono essere difficili da capire.

Questa linea di ragionamento (continua?) Rafforza il mio sospetto che le classi siano attualmente scarsamente nominate. Ancora una volta, avere nomi di classe scadenti non è una buona giustificazione per richiedere a tutto di utilizzare un nome di classe completo. Se il nome della classe è difficile da capire, c'è molto di più di quello che possono essere corretti dai nomi di classe pienamente qualificati.

Quando si visualizza o si modifica il codice al di fuori di Visual Studio (Notepad ++, ad esempio), è impossibile ottenere il nome del tipo completo.

Di tutti i motivi, questo quasi mi ha fatto sputare il mio drink. Ma di nuovo, sto divagando.

Mi chiedo perché il team sta frequentemente modificando o visualizzando il codice al di fuori di Visual Studio? E ora stiamo esaminando una giustificazione che è abbastanza ben ortogonale a ciò che gli spazi dei nomi dovrebbero fornire. Questo è un argomento supportato da strumenti mentre gli spazi dei nomi sono lì per fornire una struttura organizzativa al codice.

Sembra che il tuo progetto soffra di scarse convenzioni di denominazione insieme a sviluppatori che non stanno sfruttando ciò che gli strumenti possono fornire loro. E piuttosto che risolvere quei problemi reali, hanno tentato di schiaffeggiare un cerotto su uno dei sintomi e richiedono nomi di classe pienamente qualificati. Penso che sia sicuro classificarlo come un approccio errato.

Dato che esistono classi con un nome mediocre e supponendo che non sia possibile effettuare il refactoring, la risposta corretta è utilizzare l'IDE di Visual Studio a proprio vantaggio. Forse prendere in considerazione l'aggiunta di un plug-in come il pacchetto VS PowerTools. Quindi, quando guardo, AtrociouslyNamedClass()posso fare clic sul nome della classe, premere F12 ed essere portato direttamente alla definizione della classe per capire meglio cosa sta cercando di fare. Allo stesso modo, posso fare clic su Shift-F12 per trovare tutti i punti nel codice che attualmente soffrono di dover usare AtrociouslyNamedClass().

Per quanto riguarda le preoccupazioni esterne relative agli utensili, la cosa migliore da fare è semplicemente fermarlo. Non inviare e-mail snippet avanti e indietro se non sono immediatamente chiari a cosa si riferiscono. Non utilizzare altri strumenti al di fuori di Visual Studio poiché tali strumenti non dispongono dell'intelligenza che circonda il codice di cui il tuo team ha bisogno. Notepad ++ è uno strumento fantastico, ma non è adatto a questo compito.

Quindi sono d'accordo con la tua valutazione in merito alle tre giustificazioni specifiche che ti sono state presentate. Detto questo, ciò che penso ti sia stato detto è "Abbiamo problemi di fondo su questo progetto che non possono / non verranno affrontati ed è così che li abbiamo risolti". E questo ovviamente parla di problemi più profondi all'interno del team che possono servire come bandiere rosse per te.


1
+1. Ho anche riscontrato alcune brutte collisioni nei nostri spazi dei nomi interni. Sia nel porting del codice legacy su un progetto "2.0", sia solo con alcuni nomi di classe selezionati che sono semanticamente validi in contesti di spazi dei nomi diversi. La cosa davvero complicata è che due cose con lo stesso nome avranno spesso interfacce simili, quindi il compilatore non si lamenterà ... il tuo runtime lo farà!
svidgen,

23
Questo problema viene risolto utilizzando gli alias dello spazio dei nomi, non imponendo regole stupide che richiedono codice ingombra di utilizzo esplicito dello spazio dei nomi.
David Arno,

4
@DavidArno - Non sono in disaccordo con te. Tuttavia, i grandi progetti potrebbero non avere questa opzione. Sto solo sottolineando perché un grande progetto può imporre questa regola. Non sto sostenendo la regola, però. Solo che a volte è richiesto perché il team non ha altre opzioni.

4
@ GlenH7 potresti voler specificare le altre soluzioni disponibili nella tua risposta. Odierei vedere la gente inciampare in questo e pensare "Oh. È una grande idea!" +1 per rimanere obiettivi e pragmatici però.
RubberDuck,

3
@JimMischel - Sembra che la regola venga utilizzata come stampella per qualcosa . Potrebbe non essere possibile determinare cosa sia quella cosa. Ad un livello elevato, sì, a volte c'è una giustificazione per non consentire, usingsma non sembra che il tuo progetto si qualifichi come uno di quei casi.

16

Ho lavorato in un'azienda in cui avevano molte classi con lo stesso nome, ma in librerie di classi diverse.

Per esempio,

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

Un cattivo design, potresti dire, ma sai come si sviluppano organicamente queste cose e qual è l'alternativa: rinominare tutte le classi per includere lo spazio dei nomi? Importante refactoring di tutto?

In ogni caso, c'erano diversi luoghi in cui i progetti che facevano riferimento a tutte queste diverse librerie dovevano utilizzare più versioni della classe.

Con il usingdirettamente in alto, questo è stato un grande dolore nel culo, ReSharper avrebbe fatto cose strane per provare a farlo funzionare, riscrivendo le usingdirettive al volo, avresti avuto il cliente non può essere scelto come Errori di tipo cliente e non sapere quale sia il tipo corretto.

Quindi, avendo almeno lo spazio dei nomi parziale,

var cust = new Domain.Customer

o

public void Purchase(ViewModel.Customer customer)

notevolmente migliorata la leggibilità del codice e reso esplicito quale oggetto desiderato.

Non era uno standard di codifica, non era lo spazio dei nomi completo e non era applicato a tutte le classi come standard.


13
"qual è l'alternativa, rinominare tutte le classi per includere lo spazio dei nomi? refactoring principale di tutto?" Sì, questa è l'alternativa (corretta).
David Arno,

2
Solo a breve termine. È molto meno costoso rispetto all'utilizzo di spazi dei nomi espliciti nel codice a lungo termine.
David Arno,

3
Felice di accettare tale argomento, purché mi paghi in contanti e non in azioni.
Ewan,

8
@ David: No, NON è l'alternativa corretta. La qualificazione completa di tutti gli identificatori o almeno di quelli che si scontrano effettivamente è il modo corretto e il motivo per cui gli spazi dei nomi esistono in primo luogo è quello di fornire collisioni quando lo stesso nome viene utilizzato in moduli diversi. Il punto è che alcuni dei moduli potrebbero essere di terze parti, quale base di codice non è possibile modificare. Quindi cosa fai, quando gli identificatori di due diverse librerie di terze parti si scontrano e devi usare entrambe le librerie, ma non riesci a riformattare nessuna delle due? Gli spazi dei nomi esistono, perché il refactoring non è sempre possibile.
Kaiserludi,

4
@CarlSixsmith, se uno si abbona alle opinioni di Martin Fowler sul refactoring, quindi se la modifica influisce sull'utente, allora hai ragione, non è refactoring; è un'altra forma di ristrutturazione. Ciò solleva tuttavia due punti: 1. tutti i metodi pubblici fanno automaticamente parte dell'API? 2. Lo stesso Fowler riconosce che sta combattendo una battaglia persa su questa definizione, con un numero crescente di persone che usano il termine "refactoring" per indicare una ristrutturazione generalizzata, compresi i cambiamenti pubblici.
David Arno,

7

Non c'è nulla di buono per essere troppo prolisso o troppo breve: si dovrebbe trovare un mezzo d'oro nell'essere abbastanza dettagliati per prevenire bug sottili, evitando allo stesso tempo di scrivere troppo codice.

In C #, un esempio sono i nomi degli argomenti. Ho riscontrato diverse volte un problema in cui ho trascorso ore a cercare una soluzione, mentre uno stile di scrittura più dettagliato potrebbe prevenire il bug in primo luogo. Il problema consiste in un errore nell'ordine degli argomenti. Ad esempio, ti sembra giusto?

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

A prima vista, sembra perfettamente a posto, ma c'è un bug. Le firme dei costruttori delle eccezioni sono:

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

Hai notato l'ordine degli argomenti?

Adottando uno stile più dettagliato in cui viene specificato il nome di un argomento ogni volta che il metodo accetta due o più argomenti dello stesso tipo , impediamo che l'errore si ripeta, e quindi:

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

è ovviamente più lungo da scrivere, ma si traduce in un minore tempo dedicato al debug.

Spinto agli estremi, si potrebbe forzare l'uso di argomenti denominati ovunque , anche in metodi come doSomething(int, string)dove non c'è modo di mescolare l'ordine dei parametri. Questo probabilmente danneggerà il progetto a lungo termine, con conseguente molto più codice senza il vantaggio di prevenire il bug che ho descritto sopra.

Nel tuo caso, la motivazione dietro il “No usingregola” è che dovrebbe rendere più difficile utilizzare il tipo sbagliato, come ad esempio MyCompany.Something.List<T>contro System.Collections.Generic.List<T>.

Personalmente, eviterei tale regola perché, IMHO, il costo del codice difficile da leggere è molto superiore al beneficio di un rischio leggermente ridotto di uso improprio del tipo sbagliato, ma almeno c'è un motivo valido per questa regola.


Situazioni come queste possono essere rilevate dagli strumenti. ReSharper contrassegna il paramNamecon l' InvokerParameterNameattributo ed emette un avviso se non esiste tale parametro.
Johnbot,

2
@Johnbot: certamente possono, ma in un numero molto limitato di casi. E se avessi un SomeBusiness.Something.DoStuff(string name, string description)? Nessuno strumento è abbastanza intelligente da capire cos'è un nome e cos'è una descrizione.
Arseni Mourzenko,

@ArseniMourzenko in questo caso anche l'uomo ha bisogno di tempo per capire se l'invocazione è corretta o meno.
Gqqnbig

6

Namespace dà al codice una struttura gerarchica, consentendo nomi più brevi.

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

Se consenti la parola chiave "utilizzo", esiste una catena di eventi ...

  • Tutti usano davvero uno spazio dei nomi globale
    (perché includono tutti gli spazi dei nomi che potrebbero desiderare)
  • Le persone iniziano a ricevere scontri di nome o
  • preoccuparsi di ottenere scontri di nomi.

Quindi, per evitare il rischio, tutti iniziano a usare nomi davvero lunghi:

   HeadquartersTeamLeader
   WarehouseTeamLeader

La situazione si intensifica ...

  • Qualcun altro potrebbe aver già scelto un nome, quindi ne usi uno più lungo.
  • I nomi diventano sempre più lunghi.
  • Le lunghezze possono superare i 60 caratteri, senza un massimo - diventa ridicolo.

L'ho visto accadere, quindi posso simpatizzare con le aziende che vietano "l'utilizzo".


11
Il divieto usingè l'opzione nucleare. Gli alias dello spazio dei nomi e la qualificazione parziale sono preferibili.
Jim Mischel,

1

Il problema usingè che si aggiunge magicamente allo spazio dei nomi senza una dichiarazione esplicita. Questo è molto più di un problema per il programmatore di manutenzione che incontra qualcosa di simile List<>e senza avere familiarità con il dominio, non sa quale usingclausola lo abbia introdotto.

Un problema simile si verifica in Java se si scrive import Java.util.*;piuttosto che import Java.util.List;ad esempio; quest'ultima dichiarazione documenta quale nome è stato introdotto in modo tale che quando si List<>verifica più avanti nella fonte, la sua origine sia nota dalla importdichiarazione.


6
Mentre è vero che qualcuno che non ha familiarità con il dominio avrà qualche difficoltà, tutto ciò che deve fare è passare il mouse sul nome del tipo e otterrà il nome completo. Oppure può fare clic con il tasto destro e andare direttamente alla definizione del tipo. Per i tipi di .NET Framework, probabilmente sa già dove sono le cose. Per i tipi specifici di dominio, gli occorrerà forse una settimana per sentirsi a proprio agio. A quel punto, i nomi pienamente qualificati sono solo rumori che impediscono la comprensione.
Jim Mischel,

Jim .. Ho appena provato a passare il mouse su una definizione e non ha funzionato. Hai considerato la possibilità che alcuni programmatori nel mondo non utilizzino gli IDE di Microsoft? In ogni caso, il tuo contatore non invalida il mio punto, che la fonte con l' utilizzo non è più autocompattante
Michael Montenero,

2
Sì, ci sono persone che lavorano in C # che si stanno handicappando non usando i migliori strumenti disponibili. E, mentre l'argomentazione secondo cui la piena qualificazione è "auto-documentazione" ha qualche merito, tale codice ha un costo enorme in leggibilità. Tutto quel codice estraneo crea rumore che oscura le parti del codice che contano davvero.
Jim Mischel,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.