Dlaczego nie używałbyś dyrektywy „using” w C #?

71

Istniejące standardy kodowania w dużym projekcie C # zawierają zasadę, że wszystkie nazwy typów są w pełni kwalifikowane, co zabrania stosowania dyrektywy „używającej”. Więc zamiast tego, co znajome:

using System.Collections.Generic;

.... other stuff ....

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

(Prawdopodobnie nie jest to zaskoczeniem, że varjest również zabronione).

Kończę z:

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

To 134% wzrost pisania, przy czym żaden z tych wzrostów nie dostarcza użytecznych informacji. Moim zdaniem 100% wzrostu to hałas (bałagan), który faktycznie utrudnia zrozumienie.

Przez ponad 30 lat programowania widziałem taki standard zaproponowany raz lub dwa, ale nigdy nie został wdrożony. Uzasadnia to uzasadnienie. Osoba narzucająca standard nie jest głupia i nie sądzę, żeby był złośliwy. Które pozostawiają mylne informacje jako jedyna alternatywa, chyba że coś mi umknie.

Czy słyszałeś kiedyś o narzuceniu takiego standardu? Jeśli tak, jaki był tego powód? Czy możesz pomyśleć o argumentach innych niż „to głupie” lub „wszyscy inni zatrudniają using”, które mogą przekonać tę osobę do usunięcia tego zakazu?

Rozumowanie

Przyczyny tego zakazu były:

  1. Najechanie kursorem myszy nad nazwę jest kłopotliwe, aby uzyskać w pełni kwalifikowany typ. Lepiej zawsze mieć zawsze w pełni kwalifikowany typ.
  2. Fragmenty kodu wysyłane pocztą e-mail nie mają w pełni kwalifikowanej nazwy, dlatego mogą być trudne do zrozumienia.
  3. Podczas przeglądania lub edycji kodu poza Visual Studio (na przykład Notepad ++) niemożliwe jest uzyskanie w pełni kwalifikowanej nazwy typu.

Twierdzę, że wszystkie trzy przypadki są rzadkie, a zmuszanie wszystkich do płacenia zaśmieconego i mniej zrozumiałego kodu tylko po to, aby uwzględnić kilka rzadkich przypadków, jest błędne.

Potencjalne problemy konfliktu przestrzeni nazw, które, jak się spodziewałem, były głównym problemem, nie zostały nawet wspomniane. Jest to szczególnie zaskakujące, ponieważ mamy przestrzeń nazw MyCompany.MyProject.Core, co jest szczególnie złym pomysłem. Dawno temu nauczyłem się, że nazywanie czegokolwiek Systemlub Corew języku C # to szybka ścieżka do szaleństwa.

Jak zauważyli inni, konflikty przestrzeni nazw można łatwo rozwiązać przez refaktoryzację, aliasy przestrzeni nazw lub częściową kwalifikację.

Jim Mischel
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
wałek klonowy
1
Prawdziwy przykład, dlaczego może to być dobry pomysł (ale w świecie C ++): odpowiedź na pytanie „ Dlaczego używanie„ przestrzeni nazw std; ”jest uważane za złą praktykę? , w pobliżu „zakazano zarówno„ korzystania ”z dyrektyw, jak i deklaracji”.
Peter Mortensen,
Zanim przeczytałem sekcję Uzasadnienie, odgadłem niepraktyczne powody, ponieważ moja firma ma podobne zasady.
Gqqnbig

Odpowiedzi:

69

Szersze pytanie:

Czy słyszałeś kiedyś o narzuceniu takiego standardu? Jeśli tak, jaki był tego powód?

Tak, słyszałem o tym, a używanie w pełni kwalifikowanych nazw obiektów zapobiega kolizjom nazw. Choć rzadko, kiedy się zdarzają, mogą być wyjątkowo trudni do rozgryzienia.


Przykład: Tego rodzaju scenariusz jest prawdopodobnie lepiej wyjaśniony na przykładzie.

Powiedzmy, że mamy dwa Lists<T>należące do dwóch oddzielnych projektów.

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

Kiedy używamy w pełni kwalifikowanej nazwy obiektu, jasne jest, który z nich List<T>jest używany. Ta jasność oczywiście odbywa się kosztem gadatliwości.

I możesz się kłócić: „Zaczekaj! Nikt nigdy nie użyłby dwóch takich list!” W tym miejscu przedstawię scenariusz konserwacji.

Masz napisany moduł Foodla swojej korporacji, który korzysta z zatwierdzonej, zoptymalizowanej korporacji List<T>.

using MyCorp.CustomCollections.Optimized;

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

Później nowy programista postanawia przedłużyć pracę, którą wykonałeś. Nie znając standardów firmy, aktualizują usingblok:

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

I możesz szybko zobaczyć, jak sprawy się psują.

Trywialne powinno być stwierdzenie, że możesz mieć dwie własnościowe klasy o tej samej nazwie, ale w różnych przestrzeniach nazw w ramach tego samego projektu. Więc nie chodzi tylko o kolizję z klasami dostarczonymi w ramach .NET Framework.

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

Rzeczywistość:
czy jest to prawdopodobne w większości projektów? Nie, nie bardzo. Ale gdy pracujesz nad dużym projektem z dużą ilością danych wejściowych, starasz się zminimalizować ryzyko wybuchu rzeczy.

Duże projekty z wieloma składającymi się zespołami to szczególny rodzaj bestii w świecie aplikacji. Reguły, które wydają się nieuzasadnione w przypadku innych projektów, stają się bardziej odpowiednie ze względu na strumienie danych wejściowych do projektu i prawdopodobieństwo, że osoby wnoszące wkład nie przeczytały wytycznych projektu.

Może się to również zdarzyć, gdy dwa duże projekty zostaną połączone. Jeśli oba projekty miały podobnie nazwane klasy, zobaczysz kolizje, gdy zaczniesz odwoływać się z jednego projektu do drugiego. Projekty mogą być zbyt duże, aby można je było refaktoryzować, lub zarząd nie zatwierdzi wydatków na sfinansowanie czasu poświęconego na refaktoryzację.


Alternatywy:
Chociaż nie pytałeś, warto zauważyć, że nie jest to świetne rozwiązanie problemu. Nie jest dobrym pomysłem tworzenie klas, które zderzą się bez deklaracji przestrzeni nazw.

List<T>, w szczególności należy je traktować jako słowo zastrzeżone i nie należy używać ich jako nazw klas.

Podobnie poszczególne przestrzenie nazw w projekcie powinny dążyć do uzyskania unikalnych nazw klas. Konieczność przypomnienia sobie, z jaką przestrzenią nazw Foo()pracujesz, to narzut umysłowy, którego najlepiej unikać. Powiedział inny sposób: posiadanie MyCorp.Bar.Foo()i MyCorp.Baz.Foo()zamiar potknąć programistów i najlepiej ich unikać.

Jeśli nic więcej, możesz użyć częściowych przestrzeni nazw, aby rozwiązać niejednoznaczność. Na przykład, jeśli absolutnie nie możesz zmienić nazwy żadnej z Foo()klas, możesz użyć ich częściowych przestrzeni nazw:

Bar.Foo() 
Baz.Foo()

Konkretne powody twojego obecnego projektu:

Zaktualizowałeś swoje pytanie, podając konkretne powody, dla których otrzymałeś projekt zgodnie z tym standardem. Rzućmy na nie okiem i naprawdę zanurkujmy szlakiem króliczka.

Najechanie kursorem myszy nad nazwę jest kłopotliwe, aby uzyskać w pełni kwalifikowany typ. Lepiej zawsze mieć zawsze w pełni kwalifikowany typ.

"Nieporęczny?" Yyy ... nie. Być może denerwujące. Przeniesienie kilku uncji plastiku w celu przesunięcia wskaźnika na ekranie nie jest uciążliwe . Ale dygresję.

Ten tok rozumowania wydaje się bardziej ukrywaniem niż czymkolwiek innym. Domyślam się, że klasy w aplikacji są słabo nazwane i musisz polegać na przestrzeni nazw, aby uzyskać odpowiednią ilość informacji semantycznych otaczających nazwę klasy.

Nie jest to uzasadnione uzasadnienie dla w pełni kwalifikowanych nazw klas, być może jest to uzasadnione uzasadnienie dla używania częściowo kwalifikowanych nazw klas.

Fragmenty kodu wysyłane pocztą e-mail nie mają w pełni kwalifikowanej nazwy, dlatego mogą być trudne do zrozumienia.

Ta (kontynuowana?) Linia rozumowania potwierdza moje podejrzenie, że klasy są obecnie źle nazwane. Ponownie, posiadanie złych nazw klas nie jest dobrym uzasadnieniem dla wymagania od wszystkiego, aby używać w pełni kwalifikowanej nazwy klasy. Jeśli nazwa klasy jest trudna do zrozumienia, istnieje znacznie więcej błędów niż to, co mogą naprawić w pełni kwalifikowane nazwy klas.

Podczas przeglądania lub edycji kodu poza Visual Studio (na przykład Notepad ++) niemożliwe jest uzyskanie w pełni kwalifikowanej nazwy typu.

Ze wszystkich powodów ten prawie zmusił mnie do wyplucia drinka. Ale znowu dygresję.

Zastanawiam się, dlaczego zespół często edytuje lub wyświetla kod poza Visual Studio? A teraz patrzymy na uzasadnienie, które całkiem dobrze jest prostopadłe do tego, co mają zapewnić przestrzenie nazw. Jest to argument oparty na oprzyrządowaniu, podczas gdy przestrzenie nazw służą do zapewnienia struktury organizacyjnej kodu.

Wygląda na to, że projekt własny cierpi z powodu złych konwencji nazewnictwa wraz z programistami, którzy nie wykorzystują tego, co może zapewnić narzędzie. Zamiast rozwiązać te rzeczywiste problemy, próbują uderzyć bandażem na jeden z symptomów i wymagają w pełni kwalifikowanych nazw klas. Myślę, że można bezpiecznie zakwalifikować to jako błędne podejście.

Biorąc pod uwagę, że istnieją źle nazwane klasy i przy założeniu, że nie można dokonać refaktoryzacji, poprawną odpowiedzią jest pełne wykorzystanie możliwości Visual Studio IDE. Być może rozważ dodanie wtyczki takiej jak pakiet VS PowerTools. Następnie, kiedy patrzę AtrociouslyNamedClass(), mogę kliknąć nazwę klasy, nacisnąć F12 i przejść bezpośrednio do definicji klasy, aby lepiej zrozumieć, co ona próbuje zrobić. Podobnie, mogę kliknąć Shift-F12, aby znaleźć wszystkie miejsca w kodzie, które obecnie cierpią z powodu konieczności użycia AtrociouslyNamedClass().

Jeśli chodzi o zewnętrzne obawy związane z oprzyrządowaniem - najlepiej po prostu to zatrzymać. Nie wysyłaj fragmentów wiadomości e-mail w tę iz powrotem, jeśli nie są od razu jasne, do czego się odnoszą. Nie używaj innych narzędzi poza Visual Studio, ponieważ narzędzia te nie mają inteligencji otaczającej kod, którego potrzebuje Twój zespół. Notepad ++ to niesamowite narzędzie, ale nie jest przeznaczone do tego zadania.

Zgadzam się zatem z twoją oceną dotyczącą trzech konkretnych uzasadnień, które zostały przedstawione. To powiedziawszy, myślę, że powiedziano ci: „Mamy podstawowe problemy w tym projekcie, które nie mogą / nie mogą rozwiązać i w ten sposób je„ naprawiliśmy ”. I to oczywiście przemawia do głębszych problemów w zespole, które mogą służyć za czerwone flagi.


źródło
1
+1. W naszych wewnętrznych przestrzeniach nazw napotkałem również paskudne kolizje. Zarówno przy przenoszeniu starszego kodu do projektu „2.0”, jak i tylko z kilkoma wybranymi nazwami klas, które są semantycznie poprawne w różnych kontekstach przestrzeni nazw. Naprawdę podstępne jest to, że dwie rzeczy o tej samej nazwie często mają podobne interfejsy, więc kompilator nie będzie narzekał ... Twój środowisko uruchomieniowe będzie!
svidgen
23
Ten problem rozwiązano za pomocą aliasów przestrzeni nazw, a nie narzucając głupie reguły wymagające kodu, zaśmiecone jawnym użyciem przestrzeni nazw.
David Arno,
4
@DavidArno - Nie zgadzam się z tobą. Jednak duże projekty mogą nie mieć tej opcji. Mówię tylko, dlaczego duży projekt może narzucić tę zasadę. Ja nie opowiada się za zasadą, choć. Tyle, że czasem jest to wymagane, ponieważ zespół nie ma innych opcji.
4
@ GlenH7 możesz podać inne dostępne rozwiązania w swojej odpowiedzi. Nienawidzę patrzeć, jak ludzie się na to natkną i myślą „Och. To świetny pomysł!” +1 za pozostanie obiektywnym i pragmatycznym.
RubberDuck,
3
@JimMischel - Wygląda na to, że reguła jest używana jako narzędzie do czegoś . Jednak ustalenie, co to jest, może być niemożliwe. Na wysokim poziomie, tak, czasem uzasadnienie nie jest dozwolone, usingsale nie brzmi to tak, jakby Twój projekt kwalifikował się jako jeden z takich przypadków.
16

Pracowałem w jednej firmie, w której miały wiele klas o tej samej nazwie, ale w różnych bibliotekach klas.

Na przykład,

  • Domena. Klient
  • Legacy.Customer
  • ServiceX.Customer
  • ViewModel.Customer
  • Baza danych Klient

Można powiedzieć, że jest to zły projekt, ale wiesz, jak te rzeczy rozwijają się organicznie i jaka jest alternatywa - zmień nazwy wszystkich klas, aby uwzględnić przestrzeń nazw? Poważne refaktoryzowanie wszystkiego?

W każdym razie istniało kilka miejsc, w których projekty odwołujące się do wszystkich tych różnych bibliotek musiały używać wielu wersji tej klasy.

Z usingbezpośrednio na górze był to poważny ból w dupie, ReSharper zrobiłby dziwne rzeczy, aby spróbować, aby działał, przepisując usingdyrektywy w locie, można dostać klienta, którego nie można obsadzić jako Błędy w stylu klienta i nie wiadomo, który typ był prawidłowy.

Mając przynajmniej częściową przestrzeń nazw,

var cust = new Domain.Customer

lub

public void Purchase(ViewModel.Customer customer)

znacznie poprawiło czytelność kodu i wyraźnie wskazało, który obiekt chcesz.

To nie był standard kodowania, nie była pełna przestrzeń nazw i nie była stosowana do wszystkich klas jako standard.

Ewan
źródło
13
„jaka jest alternatywa, zmień nazwy wszystkich klas, aby uwzględnić przestrzeń nazw? poważne refaktoryzowanie wszystkiego?” Tak, to (poprawna) alternatywa.
David Arno,
2
Tylko w krótkim okresie. Jest o wiele tańszy niż długotrwałe używanie jawnych przestrzeni nazw w kodzie.
David Arno,
3
Z przyjemnością przyjmę ten argument, o ile płacisz mi gotówką, a nie akcjami.
Ewan,
8
@David: Nie, to NIE jest poprawna alternatywa. W pełni kwalifikujące się wszystkie identyfikatory lub przynajmniej te, które faktycznie kolidują, jest prawidłowym sposobem, a powodem istnienia przestrzeni nazw jest przede wszystkim zapewnienie kolizji, gdy ta sama nazwa jest używana w różnych modułach. Chodzi o to, że niektóre moduły mogą pochodzić od podmiotów zewnętrznych, których bazy kodu nie można modyfikować. Co więc robisz, gdy kolidują identyfikatory z dwóch różnych bibliotek stron trzecich i musisz korzystać z obu bibliotek, ale nie możesz zrefaktoryzować żadnej z nich? Istnieją przestrzenie nazw, ponieważ refaktoryzacja nie zawsze jest możliwa.
Kaiserludi
4
@CarlSixsmith, jeśli zgadzasz się z poglądami Martina Fowlera na temat refaktoryzacji, to jeśli zmiana wpływa na użytkownika, to masz rację, to nie refaktoryzacja; to kolejna forma restrukturyzacji. Rodzi to jednak dwie kwestie: 1. czy wszystkie metody publiczne są automatycznie częścią API? 2. Sam Fowler przyznaje, że stoczył przegraną bitwę o tę definicję, a rosnąca liczba osób używa terminu „refaktoryzacja” w celu uogólnionej restrukturyzacji, w tym zmian publicznych.
David Arno,
7

Nie ma sensu być zbyt gadatliwym ani zbyt krótkim: złoty środek powinien być wystarczająco szczegółowy, aby zapobiec subtelnym błędom, unikając jednocześnie pisania zbyt dużej ilości kodu.

W języku C # przykładem tego są nazwy argumentów. Kilka razy napotkałem problem, w którym spędziłem godziny na szukaniu rozwiązania, a bardziej szczegółowy styl pisania może przede wszystkim zapobiec błędowi. Problem polega na błędzie w kolejności argumentów. Na przykład, czy to ci odpowiada?

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

Na pierwszy rzut oka wygląda idealnie, ale jest błąd. Podpisy konstruktorów wyjątków to:

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

Zauważyłeś kolejność argumentów?

Przyjmując bardziej szczegółowy styl, w którym nazwa argumentu jest określana za każdym razem, gdy metoda akceptuje dwa lub więcej argumentów tego samego typu , zapobiegamy ponownemu wystąpieniu błędu, a więc:

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

jest oczywiście dłuższy, aby pisać, ale powoduje mniej czasu traconego na debugowanie.

Doprowadzony do skrajności, można zmusić do używania nazwanych argumentów wszędzie , w tym w metodach takich jak doSomething(int, string)tam, gdzie nie ma sposobu na pomieszanie kolejności parametrów. Prawdopodobnie zaszkodzi to projektowi w dłuższej perspektywie, skutkując znacznie większym kodem bez korzyści zapobiegania opisanemu powyżej błędowi.

W twoim przypadku, motywacja za „No usingzasady” jest to, że powinno to utrudnić korzystanie z niewłaściwego typu, takie jak MyCompany.Something.List<T>w porównaniu System.Collections.Generic.List<T>.

Osobiście unikałbym takiej reguły, ponieważ, według IMHO, koszt trudnego do odczytania kodu znacznie przewyższa korzyść nieznacznie zmniejszonego ryzyka niewłaściwego użycia niewłaściwego typu, ale przynajmniej istnieje uzasadniony powód tej reguły.

Arseni Mourzenko
źródło
Takie sytuacje można wykryć za pomocą narzędzi. ReSharper znaki paramNamez InvokerParameterNameatrybutem i emituje ostrzeżenie, jeśli nie ma takiego parametru istnieje.
Johnbot,
2
@Johnbot: z pewnością mogą, ale w bardzo ograniczonej liczbie przypadków. Co jeśli mam SomeBusiness.Something.DoStuff(string name, string description)? Żadne narzędzie nie jest wystarczająco inteligentne, aby zrozumieć, co jest nazwą, a co opisem.
Arseni Mourzenko
@ArseniMourzenko w tym przypadku nawet człowiek potrzebuje czasu, aby dowiedzieć się, czy wywołanie jest prawidłowe, czy nie.
Gqqnbig,
6

Przestrzenie nazw nadają kodowi hierarchiczną strukturę, pozwalając na krótsze nazwy.

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

Jeśli zezwolisz na słowo kluczowe „używanie”, nastąpi ciąg zdarzeń ...

  • Wszyscy naprawdę używają jednej globalnej przestrzeni nazw
    (ponieważ obejmują każdą przestrzeń nazw, jakiej mogliby chcieć)
  • Ludzie zaczynają się starć z nazwami lub
  • martw się o konflikty nazwisk.

Aby uniknąć ryzyka, wszyscy zaczynają używać naprawdę długich nazw:

   HeadquartersTeamLeader
   WarehouseTeamLeader

Sytuacja nasila się ...

  • Ktoś inny mógł już wybrać imię, więc używasz dłuższego.
  • Imiona stają się coraz dłuższe.
  • Długości mogą przekraczać 60 znaków, bez maksimum - robi się śmieszne.

Widziałem, jak to się dzieje, więc mogę współczuć firmom, które zakazują „używania”.


źródło
11
Zakaz usingjest opcją nuklearną. Preferowane są aliasy przestrzeni nazw i częściowa kwalifikacja.
Jim Mischel,
1

Problem usingpolega na tym, że magicznie dodaje do przestrzeni nazw bez wyraźnej deklaracji. Jest to o wiele większy problem dla programisty zajmującego się konserwacją, który napotyka coś takiego List<>i bez znajomości domeny, nie wie, która usingklauzula go wprowadziła.

Podobny problem występuje w Javie, jeśli ktoś pisze, import Java.util.*;a nie import Java.util.List;na przykład; ta ostatnia deklaracja dokumentuje, jaka nazwa została wprowadzona, aby w przypadku List<>późniejszego wystąpienia w źródle jej pochodzenie było znane z importdeklaracji.

Michael Montenero
źródło
6
Chociaż prawdą jest, że ktoś, kto nie zna tej domeny, będzie miał pewne trudności, wystarczy, że najedziesz myszką na nazwę typu i uzyska w pełni kwalifikowaną nazwę. Lub może kliknąć prawym przyciskiem myszy i przejść bezpośrednio do definicji typu. W przypadku typów .NET Framework prawdopodobnie już wie, gdzie są rzeczy. W przypadku typów specyficznych dla domeny może minąć tydzień, zanim poczuje się komfortowo. W tym momencie w pełni kwalifikowane nazwy są tylko hałasem, który utrudnia zrozumienie.
Jim Mischel,
Jim .. Właśnie próbowałem najechać kursorem myszy na definicję i to nie zadziałało. Czy rozważałeś możliwość, że niektórzy programiści na świecie nie używają Microsoft IDE? W każdym razie twój licznik nie unieważnia mojego twierdzenia, że ​​źródło przy użyciu nie jest już samo-dokumentujące
Michael Montenero
2
Tak, są osoby pracujące w C #, które upośledzają się, nie używając najlepszych dostępnych narzędzi. I chociaż argument, że pełna kwalifikacja jest „samok dokumentowaniem”, ma pewne zalety, taki kod ma ogromny koszt w zakresie czytelności. Cały ten obcy kod powoduje hałas, który przesłania naprawdę ważne części kodu.
Jim Mischel,