Warum würden Sie die Direktive 'using' in C # nicht verwenden?

71

Die bestehenden Codierungsstandards für ein großes C # -Projekt enthalten die Regel, dass alle Typnamen vollständig qualifiziert sein müssen, und verbieten die Verwendung der Direktive 'using'. Also eher als das Vertraute:

using System.Collections.Generic;

.... other stuff ....

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

(Es ist wahrscheinlich keine Überraschung, dass varauch verboten ist.)

Am Ende habe ich:

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

Das ist eine Steigerung der Eingabe um 134%, wobei keine dieser Steigerungen nützliche Informationen liefert. Meiner Ansicht nach ist 100% der Zunahme Rauschen (Unordnung), das das Verstehen tatsächlich behindert .

In über 30 Jahren Programmierung habe ich ein oder zwei Mal einen solchen Standard vorgeschlagen, aber nie implementiert. Die Begründung dahinter entgeht mir. Die Person, die den Standard auferlegt, ist nicht dumm, und ich glaube nicht, dass er bösartig ist. Was als einzige Alternative fehlgeleitet bleibt, es sei denn, mir fehlt etwas.

Haben Sie jemals von einem solchen Standard gehört? Wenn ja, aus welchem ​​Grund? Können Sie sich andere Argumente als "es ist dumm" oder "jeder andere beschäftigt using" vorstellen, die diese Person davon überzeugen könnten, dieses Verbot aufzuheben?

Argumentation

Die Gründe für dieses Verbot waren:

  1. Es ist umständlich, mit der Maus über den Namen zu fahren, um den vollqualifizierten Typ zu erhalten. Es ist besser, immer den vollständig qualifizierten Typ sichtbar zu haben.
  2. Codeausschnitte per E-Mail haben nicht den vollständigen Namen und sind daher möglicherweise schwer zu verstehen.
  3. Wenn Sie den Code außerhalb von Visual Studio anzeigen oder bearbeiten (z. B. Notepad ++), ist es nicht möglich, den vollständig qualifizierten Typnamen abzurufen.

Meine Behauptung ist, dass alle drei Fälle selten sind und dass es falsch ist, alle den Preis für überfüllten und weniger verständlichen Code zahlen zu lassen, nur um ein paar seltene Fälle zu berücksichtigen.

Mögliche Probleme mit Namespace-Konflikten, von denen ich erwartet hatte, dass sie die Hauptsorge sind, wurden nicht einmal erwähnt. Das ist besonders überraschend, weil wir einen Namespace haben MyCompany.MyProject.Core, was eine besonders schlechte Idee ist. Ich habe vor langer Zeit gelernt, dass es ein schneller Weg zum Wahnsinn ist , irgendetwas Systemoder Corein C # zu benennen .

Wie bereits erwähnt, können Namespace-Konflikte leicht durch Refactoring, Namespace-Aliase oder teilweise Qualifizierung gelöst werden.

Jim Mischel
quelle
Kommentare sind nicht für längere Diskussionen gedacht. Diese Unterhaltung wurde in den Chat verschoben .
maple_shaft
1
Ein Beispiel aus der Praxis, warum dies eine gute Idee sein könnte (aber in der C ++ - Welt): eine Antwort auf die Frage , warum die Verwendung des Namespace std als schlechte Praxis angesehen wird. , in der Nähe von "dass sowohl Verwendungsrichtlinien als auch Erklärungen verboten sind".
Peter Mortensen
Bevor ich den Abschnitt über die Argumentation las, habe ich die unpraktischen Gründe erraten, weil meine Firma ähnliche Regeln hat.
Gqqnbig

Antworten:

69

Die allgemeinere Frage:

Haben Sie jemals von einem solchen Standard gehört? Wenn ja, aus welchem ​​Grund?

Ja, ich habe davon gehört und die Verwendung von vollständig qualifizierten Objektnamen verhindert Namenskollisionen. Wenn sie vorkommen, sind sie zwar selten, können aber außerordentlich schwierig zu ermitteln sein.


Ein Beispiel: Dieser Typ eines Szenarios wird wahrscheinlich besser mit einem Beispiel erklärt.

Nehmen wir an, wir haben zwei, Lists<T>die zu zwei separaten Projekten gehören.

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

Wenn wir den vollständig qualifizierten Objektnamen verwenden, ist klar, welcher List<T>verwendet wird. Diese Klarheit geht natürlich zu Lasten der Ausführlichkeit.

Und Sie könnten argumentieren: "Warten Sie! Niemand würde jemals zwei Listen wie diese verwenden!" An dieser Stelle werde ich auf das Wartungsszenario hinweisen.

Sie haben ein Modul Foofür Ihr Unternehmen geschrieben, das das genehmigte, optimierte Unternehmen verwendet List<T>.

using MyCorp.CustomCollections.Optimized;

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

Später beschließt ein neuer Entwickler, die von Ihnen geleistete Arbeit zu erweitern. Da sie die Standards des Unternehmens nicht kennen, aktualisieren sie den usingBlock:

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

Und Sie können sehen, wie die Dinge in Eile schlecht laufen.

Es sollte trivial sein, darauf hinzuweisen, dass Sie zwei proprietäre Klassen mit demselben Namen, jedoch in unterschiedlichen Namespaces innerhalb desselben Projekts haben können. Es geht also nicht nur darum, mit von .NET Framework bereitgestellten Klassen zu kollidieren.

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

Die Realität:
Tritt dies wahrscheinlich in den meisten Projekten auf? Nein nicht wirklich. Wenn Sie jedoch an einem großen Projekt mit vielen Eingaben arbeiten, tun Sie Ihr Bestes, um die Gefahr von Explosionen auf Sie zu minimieren.

Große Projekte mit mehreren beitragenden Teams sind eine besondere Art von Tier in der Anwendungswelt. Regeln, die für andere Projekte unangemessen erscheinen, werden aufgrund der Eingabeströme für das Projekt und der Wahrscheinlichkeit, dass die Beitragenden die Projektrichtlinien nicht gelesen haben, relevanter.

Dies kann auch auftreten, wenn zwei große Projekte zusammengeführt werden. Wenn beide Projekte ähnlich benannte Klassen hatten, werden Kollisionen angezeigt, wenn Sie von einem Projekt zum anderen referenzieren. Und die Projekte sind möglicherweise zu umfangreich, um sie umzugestalten, oder das Management genehmigt nicht die Kosten für die Finanzierung der für die Umgestaltung aufgewendeten Zeit.


Alternativen:
Sie haben zwar nicht gefragt, aber es lohnt sich darauf hinzuweisen, dass dies keine großartige Lösung für das Problem ist. Es ist keine gute Idee, Klassen zu erstellen, die ohne ihre Namespace-Deklarationen kollidieren.

List<T>Insbesondere sollte es als reserviertes Wort behandelt und nicht als Name für Ihre Klassen verwendet werden.

Ebenso sollten einzelne Namespaces innerhalb des Projekts nach eindeutigen Klassennamen streben. Wenn Sie versuchen, herauszufinden, mit welchem ​​Namespace Foo()Sie arbeiten, ist dies ein mentaler Aufwand, der am besten vermieden wird. Anders gesagt: Sie müssen MyCorp.Bar.Foo()und werden MyCorp.Baz.Foo()Ihre Entwickler aus der Ruhe bringen und dies am besten vermeiden.

Wenn nicht anders angegeben, können Sie teilweise Namespaces verwenden, um die Mehrdeutigkeit aufzulösen. Wenn Sie beispielsweise keine der Foo()Klassen umbenennen können, können Sie deren Teil-Namespaces verwenden:

Bar.Foo() 
Baz.Foo()

Spezifische Gründe für Ihr aktuelles Projekt:

Sie haben Ihre Frage mit den spezifischen Gründen aktualisiert, die Sie für Ihr aktuelles Projekt angegeben haben. Lassen Sie uns einen Blick auf sie werfen und wirklich den Hasenpfad entlang schweifen.

Es ist umständlich, mit der Maus über den Namen zu fahren, um den vollqualifizierten Typ zu erhalten. Es ist besser, immer den vollständig qualifizierten Typ sichtbar zu haben.

"Schwerfällig?" Ähm, nein. Vielleicht ärgerlich. Das Bewegen einiger Unzen Plastik, um einen Bildschirmzeiger zu verschieben, ist nicht umständlich . Aber ich schweife ab.

Diese Argumentation scheint eher eine Vertuschung zu sein als alles andere. Auf den ersten Blick würde ich vermuten, dass die Klassen in der Anwendung schwach benannt sind und Sie sich auf den Namespace verlassen müssen, um die entsprechende Menge an semantischen Informationen zu erhalten, die den Klassennamen umgeben.

Dies ist keine gültige Rechtfertigung für vollständig qualifizierte Klassennamen. Vielleicht ist es eine gültige Rechtfertigung für die Verwendung von teilweise qualifizierten Klassennamen.

Codeausschnitte per E-Mail haben nicht den vollständigen Namen und sind daher möglicherweise schwer zu verstehen.

Diese (Fortsetzung?) Argumentation untermauert meinen Verdacht, dass Klassen derzeit schlecht benannt sind. Auch hier ist mit Namen schlecht Klasse keine gute Begründung für die Forderung , alles über ein voll qualifizierten Klassennamen zu verwenden. Wenn der Klassenname schwer zu verstehen ist, ist viel mehr falsch, als mit vollständig qualifizierten Klassennamen behoben werden kann.

Wenn Sie den Code außerhalb von Visual Studio anzeigen oder bearbeiten (z. B. Notepad ++), ist es nicht möglich, den vollständig qualifizierten Typnamen abzurufen.

Aus all den Gründen hätte ich beinahe mein Getränk ausgespuckt. Aber ich schweife ab.

Ich frage mich, warum das Team häufig Code außerhalb von Visual Studio bearbeitet oder anzeigt. Und jetzt sehen wir uns eine Rechtfertigung an, die ziemlich orthogonal zu den Namespaces ist, die bereitgestellt werden sollen. Dies ist ein werkzeuggestütztes Argument, während Namespaces dazu dienen, dem Code eine Organisationsstruktur zu geben.

Es hört sich so an, als ob Ihr eigenes Projekt unter schlechten Namenskonventionen leidet, zusammen mit Entwicklern, die nicht die Vorteile nutzen, die die Tools für sie bieten können. Und anstatt diese tatsächlichen Probleme zu lösen, haben sie versucht, ein Pflaster über eines der Symptome zu legen und benötigen vollständig qualifizierte Klassennamen. Ich denke, es ist sicher, dies als einen fehlgeleiteten Ansatz zu kategorisieren.

Angesichts der Tatsache, dass es schlecht benannte Klassen gibt und Sie nicht umgestalten können, ist die richtige Antwort, die Visual Studio-IDE zu ihrem vollen Vorteil zu nutzen. Erwägen Sie möglicherweise, ein Plugin wie das VS PowerTools-Paket hinzuzufügen. Wenn ich mir dann anschaue, AtrociouslyNamedClass()kann ich auf den Klassennamen klicken, F12 drücken und direkt zur Definition der Klasse weitergeleitet werden, um besser zu verstehen, was sie versucht zu tun. Ebenso kann ich Shift-F12 drücken, um alle Stellen im Code zu finden, die gerade benutzt werden müssen AtrociouslyNamedClass().

In Bezug auf die Probleme mit den Außenwerkzeugen ist es am besten, diese einfach zu beenden. Schicken Sie keine Schnipsel per E-Mail hin und her, wenn nicht sofort klar ist, worauf sich der Verweis bezieht. Verwenden Sie keine anderen Tools außerhalb von Visual Studio, da diese Tools nicht über die für Ihr Team erforderlichen Informationen zum Code verfügen. Notepad ++ ist ein großartiges Tool, aber es ist nicht für diese Aufgabe geeignet.

Daher stimme ich Ihrer Einschätzung hinsichtlich der drei spezifischen Begründungen zu, die Ihnen vorgelegt wurden. Ich denke, Ihnen wurde jedoch gesagt: "Wir haben grundlegende Probleme in diesem Projekt, die wir nicht lösen können / wollen, und auf diese Weise haben wir sie" behoben "." Und das spricht offensichtlich für tiefere Probleme innerhalb des Teams, die für Sie als rote Fahnen dienen können.


quelle
1
+1. Ich habe auch tatsächlich einige böse Kollisionen in unseren internen Namespaces festgestellt. Sowohl beim Portieren von Legacy-Code in ein "2.0" -Projekt als auch mit wenigen ausgewählten Klassennamen, die in verschiedenen Namespace-Kontexten semantisch gültig sind. Das wirklich schwierige ist, dass zwei Dinge mit dem gleichen Namen oft ähnliche Schnittstellen haben, so dass der Compiler sich nicht beschwert ... Ihre Laufzeit wird!
Svidgen
23
Dieses Problem wird durch die Verwendung von Namespace-Aliasen gelöst, nicht durch das Auferlegen alberner Regeln, die Code erfordern, der durch die explizite Verwendung von Namespaces überladen ist.
David Arno
4
@DavidArno - Ich bin nicht anderer Meinung als Sie. Bei großen Projekten ist diese Option möglicherweise nicht verfügbar. Ich weise nur darauf hin, warum ein großes Projekt diese Regel auferlegen kann. Ich plädiere jedoch nicht für die Regel. Nur, dass es manchmal erforderlich ist, weil das Team keine anderen Optionen hat.
4
@ GlenH7 Vielleicht möchten Sie die anderen verfügbaren Lösungen in Ihrer Antwort angeben. Ich würde es hassen, wenn Leute darüber stolpern und denken "Oh. Das ist eine großartige Idee!" +1 für objektiv und pragmatisch bleiben.
RubberDuck
3
@ JimMischel - Es hört sich so an, als würde die Regel als Krücke für etwas verwendet . Es ist jedoch möglicherweise nicht möglich zu bestimmen, um was es sich handelt. Auf hoher Ebene gibt es zwar manchmal Gründe, dies nicht zuzulassen, usingsaber es hört sich nicht so an, als würde Ihr Projekt als einer dieser Fälle eingestuft.
16

Ich habe in einer Firma gearbeitet, in der es viele Klassen mit demselben Namen gab, aber in verschiedenen Klassenbibliotheken.

Zum Beispiel,

  • Domain.Customer
  • Legacy.Customer
  • ServiceX.Customer
  • ViewModel.Customer
  • Datenbank.Kunde

Schlechtes Design, könnte man sagen, aber Sie wissen, wie sich diese Dinge organisch entwickeln und was die Alternative ist - benennen Sie alle Klassen um, um den Namespace einzuschließen? Großes Refactoring von allem?

Auf jeden Fall gab es mehrere Stellen, an denen Projekte, die auf all diese verschiedenen Bibliotheken verweisen, mehrere Versionen der Klasse verwenden mussten.

Mit dem usingdirekt an der Spitze, das war ein großer Schmerz im Arsch, ReSharper würde seltsame Dinge tun, um es zum usingLaufen zu bringen , die Direktiven im laufenden Betrieb umschreiben , man würde Kunden-kann-nicht-als-Cast bekommen Kundenstilfehler und nicht wissen, welcher Typ der richtige war.

Also, mit mindestens dem partiellen Namespace,

var cust = new Domain.Customer

oder

public void Purchase(ViewModel.Customer customer)

Verbesserte die Lesbarkeit des Codes erheblich und machte deutlich, welches Objekt Sie wollten.

Es war kein Codierungsstandard, es war nicht der vollständige Namespace, und es wurde nicht standardmäßig auf alle Klassen angewendet.

Ewan
quelle
13
"Was ist die Alternative, benennen Sie alle Klassen um, um den Namespace einzuschließen? Major Refactoring von allem?" Ja, das ist die (richtige) Alternative.
David Arno
2
Nur kurzfristig. Es ist auf lange Sicht weitaus günstiger als die Verwendung expliziter Namespaces im Code.
David Arno
3
Gerne akzeptieren Sie dieses Argument, solange Sie mir in bar keine Aktien zahlen.
Ewan
8
@ David: Nein, es ist NICHT die richtige Alternative. Das vollständige Qualifizieren entweder aller oder zumindest der tatsächlich kollidierenden Bezeichner ist der richtige Weg, und der Grund, warum Namespaces überhaupt existieren, besteht darin, Kollisionen bereitzustellen, wenn derselbe Name in verschiedenen Modulen verwendet wird. Der Punkt ist, dass einige der Module von Drittanbietern stammen, deren Codebasis Sie nicht ändern können. Was tun Sie also, wenn Bezeichner aus zwei verschiedenen Bibliotheken von Drittanbietern kollidieren und Sie beide Bibliotheken verwenden müssen, aber keine von beiden refaktorisieren können? Namespaces existieren, da Refactoring nicht immer möglich ist.
Kaiserludi
4
@CarlSixsmith, wenn man Martin Fowlers Ansichten zum Refactoring abonniert, wenn sich die Änderung auf den Benutzer auswirkt, dann sind Sie richtig, es ist kein Refactoring. Es ist eine andere Form der Umstrukturierung. Dies wirft jedoch zwei Punkte auf: 1. Sind alle öffentlichen Methoden automatisch Teil der API? 2. Fowler selbst räumt ein, dass er wegen dieser Definition einen verlorenen Kampf führt , wobei immer mehr Menschen den Begriff "Refactoring" für allgemeine Umstrukturierungen, einschließlich öffentlicher Änderungen, verwenden.
David Arno
7

Es ist nicht gut, zu ausführlich oder zu kurz zu sein: Man sollte eine goldene Mitte darin finden, detailliert genug zu sein, um subtile Fehler zu verhindern und gleichzeitig zu vermeiden, zu viel Code zu schreiben.

In C # sind dies beispielsweise die Namen von Argumenten. Ich bin mehrmals auf ein Problem gestoßen, bei dem ich stundenlang nach einer Lösung gesucht habe, während eine ausführlichere Schreibweise den Fehler an erster Stelle verhindern konnte. Das Problem besteht in einem Fehler in der Reihenfolge der Argumente. Sieht es zum Beispiel für Sie richtig aus?

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

Auf den ersten Blick sieht es ganz gut aus, aber es gibt einen Fehler. Die Signaturen der Konstruktoren der Ausnahmen sind:

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

Bemerkt die Reihenfolge der Argumente?

Durch die Verwendung eines ausführlicheren Stils, bei dem der Name eines Arguments jedes Mal angegeben wird, wenn die Methode zwei oder mehr Argumente desselben Typs akzeptiert , wird verhindert, dass der Fehler erneut auftritt.

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

ist offensichtlich länger zu schreiben, führt aber zu weniger Zeitverschwendung beim Debuggen.

Extreme Anforderungen können dazu führen, dass überall benannte Argumente verwendet werden , auch in Methoden, bei doSomething(int, string)denen es nicht möglich ist, die Reihenfolge der Parameter zu mischen. Dies wird wahrscheinlich das Projekt langfristig schädigen, was zu viel mehr Code führt, ohne den oben beschriebenen Fehler zu verhindern.

In Ihrem Fall ist die Motivation hinter der „Nein usingist“ Regel , dass es sollte es die falsche Art zu verwenden, wie zum Beispiel erschweren MyCompany.Something.List<T>vs. System.Collections.Generic.List<T>.

Persönlich würde ich eine solche Regel vermeiden, da die Kosten für schwer lesbaren Code meiner Meinung nach weit über dem Nutzen eines geringfügig verringerten Risikos für den Missbrauch des falschen Typs liegen, aber zumindest gibt es einen triftigen Grund für diese Regel.

Arseni Mourzenko
quelle
Solche Situationen können mit Werkzeugen erkannt werden. ReSharper markiert das paramNamemit dem InvokerParameterNameAttribut und gibt eine Warnung aus, wenn kein solcher Parameter vorhanden ist.
Johnbot
2
@ Johnbot: Das können sie sicherlich, aber in einer sehr begrenzten Anzahl von Fällen. Was ist, wenn ich eine habe SomeBusiness.Something.DoStuff(string name, string description)? Kein Werkzeug ist schlau genug, um zu verstehen, was ein Name und was eine Beschreibung ist.
Arseni Mourzenko
@ArseniMourzenko In diesem Fall muss sich auch der Mensch einige Zeit nehmen, um herauszufinden, ob der Aufruf korrekt ist oder nicht.
Gqqnbig
6

Namespaces geben Code eine hierarchische Struktur, die kürzere Namen ermöglicht.

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

Wenn Sie das Schlüsselwort "using" zulassen, gibt es eine Ereigniskette ...

  • Jeder verwendet wirklich einen globalen Namespace
    (weil er jeden Namespace enthält, den er sich nur wünschen kann).
  • Leute fangen an, Namenskonflikte zu bekommen, oder
  • Sorgen Sie sich um Namenskonflikte.

Um das Risiko zu vermeiden, beginnen alle, sehr lange Namen zu verwenden:

   HeadquartersTeamLeader
   WarehouseTeamLeader

Die Situation eskaliert ...

  • Möglicherweise hat bereits eine andere Person einen Namen gewählt, daher verwenden Sie einen längeren Namen.
  • Namen werden immer länger.
  • Die Länge darf 60 Zeichen nicht überschreiten - es wird lächerlich.

Ich habe dies gesehen, kann also mit Firmen sympathisieren, die das "Verwenden" verbieten.


quelle
11
Das Verbot usingist die nukleare Option. Namespace-Aliase und Teilqualifikationen sind vorzuziehen.
Jim Mischel
1

Das Problem dabei usingist, dass es ohne explizite Deklaration auf magische Weise zum Namespace hinzugefügt wird. Dies ist ein weitaus größeres Problem für den Wartungsprogrammierer, der auf so etwas stößt List<>und nicht mit der Domäne vertraut ist und nicht weiß, welche usingKlausel dies eingeführt hat.

Ein ähnliches Problem tritt in Java auf, wenn man import Java.util.*;nicht import Java.util.List;zum Beispiel schreibt ; Die letztgenannte Deklaration dokumentiert, welcher Name eingeführt wurde, so dass List<>der Ursprung bei einem späteren Auftreten in der Quelle aus der importDeklaration bekannt ist.

Michael Montenero
quelle
6
Während es wahr ist, dass jemand, der mit der Domain nicht vertraut ist, Schwierigkeiten haben wird, muss er nur mit der Maus über den Typennamen fahren und erhält den vollqualifizierten Namen. Oder er kann mit der rechten Maustaste klicken und direkt zur Typdefinition gehen. Bei .NET Framework-Typen weiß er wahrscheinlich bereits, wo sich die Dinge befinden. Für domänenspezifische Typen wird es vielleicht eine Woche dauern, bis er es sich bequem gemacht hat. Zu diesem Zeitpunkt sind die vollständig qualifizierten Namen nur Störgeräusche, die das Verständnis beeinträchtigen.
Jim Mischel
Jim ... Ich habe nur versucht, mit der Maus über eine Definition zu fahren, aber es hat nicht funktioniert. Hatten Sie die Möglichkeit in Betracht gezogen, dass einige Programmierer auf der Welt keine Microsoft IDEs verwenden? In jedem Fall entkräftet Ihr Zähler nicht meinen Standpunkt, dass die Quelle mit der Verwendung nicht mehr
Michael Montenero
2
Ja, es gibt Leute, die in C # arbeiten und sich selbst behindern, indem sie nicht die besten verfügbaren Tools verwenden. Und während das Argument, dass die vollständige Qualifizierung "selbstdokumentierend" ist, einen gewissen Wert hat, hat ein solcher Code enorme Kosten für die Lesbarkeit. All dieser fremde Code erzeugt Rauschen, das die Teile des Codes verdeckt, die wirklich wichtig sind.
Jim Mischel