C # - Dlaczego nie zaleca się używania prefiksów na polach?

19

W dawnych czasach robiliśmy zapis węgierski. To jest teraz uważane za passé i przez większość czasu już go nie używam, ale nadal znajduję zastosowanie m_prefiksu do wskazania pól członków.

Dla mnie, jeśli czytam czyjś kod i widzę to:

count = 3;

Zakładam, że countjest to zmienna lokalna dla tej funkcji i robię coś, co zostanie użyte w innym miejscu funkcji; jeśli to zobaczę:

m_count = 3;

Natychmiast zdaję sobie sprawę, że aktualizuję stan obiektu.

Wytyczne stylu Microsoft mówią, że to zły sposób na robienie rzeczy. Mam do nazwania moje niepubliczne pola dokładnie tak, jak zmienne tymczasowe zdefiniowane w funkcji. Nie m_, nawet prosty podkreślnik.

Tak, możemy zdefiniować własny styl kodowania, ale potem muszę zmagać się z różnymi narzędziami do analizy kodu statycznego, aby przekonać ich, że nasz sposób robienia rzeczy jest OK.

Cieszę się, że zmieniłem styl Microsoft, ale chciałbym wiedzieć, dlaczego wszystko jest tak, jak jest.

Dlaczego uważa się teraz za zły fakt, że można stwierdzić, czy zmienna jest funkcją lokalną czy członkiem?

PS Jest to bardzo podobne do Jakie są obecnie uważane najlepsze praktyki dotyczące słowa kluczowego „this” przed polem i metod w c #? , ale pytam o m_niethis.

PPS Zobacz także Dlaczego Microsoft sprawił, że parametry, zmienne lokalne i pola prywatne mają tę samą konwencję nazewnictwa nazw?

Betty Crokker
źródło
9
Czy C # nie ma thissłowa kluczowego? Czy nie możesz odwoływać się do członków korzystających this, takich jak this.count?
FrustratedWithFormsDesigner
3
Prefiks m_ jest izomerem C ++, w którym unika się kolizji nazw między polami i metodami. W języku C # nie stanowi to problemu, ponieważ nazwy metod powinny zaczynać się dużymi literami, a pola małymi literami.
amon
4
Jeśli drukujesz kod w 2017 roku, robisz coś złego. W pozostałych dwóch scenariuszach powinno być dość oczywiste, że countnie pojawiło się nie wiadomo skąd, ponieważ nie zostało zadeklarowane w metodzie. Szczerze mówiąc argument „poza IDE” jest NAPRAWDĘ słaby. Zwłaszcza, że ​​istnieją nawet narzędzia do przeglądania kodu, które działają w IDE.
Andy
4
Wciąż aktualny post od Joela .
Kasey Speakman

Odpowiedzi:

19

Kiedy pracowali nad interfejsem API dla Windows, Microsoft zalecił użycie notacji węgierskiej, używając „m_”, a także „i”, „sz” itp. W tym czasie było to przydatne, ponieważ IDE nie było solidne wystarczająco, aby pokazać Ci te informacje.

Z drugiej strony C # od samego początku ma bardzo solidne IDE.

Dlatego wydali zalecenie w wytycznych Microsoft dotyczących nazewnictwa dla C # dla publicznych pól statycznych lub chronionych:

 DO use PascalCasing in field names.
 DO name fields using a noun, noun phrase, or adjective.
X DO NOT use a prefix for field names.

Teraz, gdy możesz najechać myszką na myszy lub naciśnij CTRL + K + W i uzyskać wszystkie informacje o elemencie, Microsoft doszedł do wniosku, że prefiksy są złej formy; są ręcznym rozwiązaniem już rozwiązanego problemu.

Pola prywatne są wyraźnie wyłączone z wytycznych, ale wydaje się, że Microsoft doszedł do wniosku, że jest tak nawet w przypadku pól prywatnych rozwijanych wewnętrznie, co można zobaczyć, jeśli wskażesz Resharper na .NET 4.5 lub .NET Core.

Używaj narzędzi, nie rób tego ręcznie.

Opłata Kevin
źródło
2
Ta sama odpowiedź, co Andy ... tak, ale ... istnieje wiele sytuacji, w których patrzę na kod poza IDE. Przykłady - (1) narzędzia do scalania. (2) narzędzia do przeglądania kodu. (3) wydruki (z trudem łapią powietrze).
Betty Crokker
Andy napisał dosłownie w tej samej chwili co ja. Gdy kliknąłem „dodaj odpowiedź”, pojawiło się okno „1 nowy komentarz”. Jeśli chodzi o te narzędzia, dwa z nich są już w środowisku IDE. Wydruki ... dlaczego to robisz?
Kevin Fee
1
Moje początkowe pytanie pozostaje bez odpowiedzi ... tak, Microsoft powiedział, że nie używaj m_, a producenci narzędzi zastosowali się do tego, ale to nie jest techniczny powód, aby używać m_, to jest często nazywane „odwołaniem do władzy”. Jedyny techniczny powód, dla którego nie korzystałem z m_, pochodzi od Timothy'ego Truckle'a i nie rozumiem tego ...
Betty Crokker
8
@BettyCrokker Ponieważ m_ nie dodaje absolutnie żadnej wartości. IDE mówi, że jesteś członkiem, czy nie, więc m_ jest redundantne. Jakieś przyczyny, dla których jesteś naprawiony na m_? Dlaczego nie l_ (lokalnie)? Dlaczego nie afsdfjdskfjas_? Będę głosować za pytaniem, ponieważ i tak jest to głupi argument religijny. Rób co chcesz. Wytyczne jesteś pukanie również powiedzieć „Wytyczne polowe nazewnictwa stosuje się do statycznych pól publicznych i chronionych . Wewnętrzne i pola prywatne _nie_ objęte wytycznymi , a pola publiczne lub chronione instancji nie są dozwolone w wytycznych projektowych członkiem.”
Andy
1
@BettyCrokker OK, ale pytasz o wytyczne, które są oparte na tym założeniu. Narzekasz także na narzędzia statyczne, które prawdopodobnie również używają tego założenia, ponieważ „nikt” (w dopuszczalnym marginesie błędu statystycznego) nie drukuje swojego kodu C #. Nie wiem, jakich narzędzi analizy statystycznej używasz, ale Resharper jest dość specyficzny: Prywatne pola to „_lowerCase”. Członkowie publiczni dowolnego typu lub „UpperCase”, a miejscowi to „lowerCase”. W każdym razie oszczędzanie na „m” wygląda całkiem ładnie.
Kevin Fee
17

C # nie ma żadnych zmiennych globalnych, więc pozostawia tylko zmienne lokalne i pola klasy.

A jeśli masz tak wiele zmiennych lokalnych, że tracisz kontrolę nad tym, czy identyfikator jest jeden, najpewniej naruszyłeś jedną z wytycznych dotyczących najbardziej złożonego zarządzania złożonością.

Spróbuj wyodrębnić parametr-obiekt, spróbuj podzielić swoją funkcję na wiele prostszych, z pewnością powinieneś refaktoryzować swój kod, chyba że lubisz zagłuszać drobiazgi i złożoność, a także nie zwracasz uwagi na łatwość konserwacji.

Teraz, gdy ustaliliśmy, że cel dekoracji „to jest członkiem” nie ma już miejsca, ponieważ zakres funkcji powinien być zbyt mały, a zakres globalny nie istnieje, istnieje jedna wada tych znaczników: hamują przenoszenie argumentów / zmiennych lokalnych do członków lub na odwrót.

Deduplikator
źródło
Po dotarciu do niego może to być również klasa lub przestrzeń nazw.
CodesInChaos
3
Nie sądzę, że to naprawdę odpowiada na pytanie.
Vladimir Stokic
5
@FrankHileman Właściwie to jest. Wyjaśnia, dlaczego nie ma dobrego powodu, by uglifikować nazwę.
Deduplicator
2
„uglify”? W takim przypadku jest to pytanie wyłącznie opiniujące. Każdy może mieć inne zdanie na temat brzydoty. Istnieją powody, by preferować jedną konwencję nad inną, ale w tym przypadku nie ma standardowej konwencji.
Frank Hileman,
1
@Deduplicator beauty jest w oku patrzącego. Konwencje, które kiedyś uważałem za brzydkie, teraz oceniam jako przydatne.
Frank Hileman,
11

Dlaczego programiści unikają przygotowywania przestrzeni nazw za pomocą dyrektywy using namespace?

System.DateTime()jest o wiele bardziej wyraźny niż DateTime(). Mówi mi dokładnie, z czym mam do czynienia. Czy tylko dlatego, że jesteśmy leniwymi maszynistkami?

Nie. Jesteśmy leniwymi maszynistkami, ale nie dlatego.

Preferujemy style kodowania, które pozwalają nam ignorować szczegóły i skupiać się na abstrakcjach. Zmuszanie nazw do przekazywania szczegółów struktury rozprasza uwagę. Kiedy pracuję nad skomplikowanym algorytmem, nie chcę nazw, które nalegają, aby przypominać mi każdy drobiazg o tym, z czym pracuję. Potrzebuję tylko tego imienia, żeby nie pomylić Timeri DateTime. Będę się martwić, czy są one kompatybilne gdzie indziej.

To z tego samego powodu, dla którego nie chcesz dwóch facetów o imieniu Jon. Fakt, że mają nazwiska, nie jest powodem do nadawania im prefiksów lub sufiksów. Zadzwonię do ciebie Skeet. Coś więcej to całkowity ból.

candied_orange
źródło
2
To nie wydaje się odpowiadać na pytanie. W .net nie ma ogólnych preferencji ani konwencji dotyczących pól prywatnych.
Frank Hileman
12
@FrankHileman Ogólnie preferujemy dobre nazwiska. Konwencje nie tworzą dobrych nazwisk. Konwencje tworzą nazwy takie jak McOhPlease Von StopItAlreadyStine.
candied_orange
Konwencje mają jedynie ułatwić pracę z innym kodem programisty. Twoja odpowiedź dotyczy węgierskiego, ale nie ma konwencji dla pól prywatnych, więc pytanie opiera się na fałszywym założeniu.
Frank Hileman,
7

Rozwińmy trochę.

Istnieją 3 popularne style prefiksów, z których wszystkie są czasami spotykane w kodzie c #:

  • m_
  • _
  • to.

Takie prefiksy są powszechnie używane w prywatnej implementacji klasy . Zalecenie Microsoft dotyczy przede wszystkim odsłoniętych części klasy.

Zaletą używania m_over _jest to, że prefiks może być taki sam w całej bazie kodu, jeśli używasz c #, a także języków, w których używanie _jako prefiksu jest niedozwolone . * Zaletą m_over this.jest to, że jest krótszy i że nie przypadkowo zapomnij o prefiksie.

Zaletą _przejęcia m_jest to, że krótsze i że wszystko, zaczynając od prefiksu jest sortowana na górze są sortowane alfabetycznie według narzędzia. Zaletą _przejęcia this.jest to, że krótsze i że nie będzie przypadkowo zapomnieć o prefiksie.

Przewaga this.nad m_i _jest to, że można go używać w kodzie, gdzie _albo m_nie są akceptowane i uniwersalna konwencja. Oznacza to również, że nikt nie musi wiedzieć o konwencji _lub m_konwencji, co można postrzegać jako uproszczenie. Wadą jest to, że ponieważ kompilator nie dba o ten prefiks, this.czasami prefiks będzie go brakować i jako taki nie będzie tak niezawodny jak wskaźnik.


* Gdy zaczniesz uzyskiwać dostęp do klas C # korzystających _z C ++ / CLI (które tak naprawdę nie powinny używać _), zauważysz, że uzgodnienie wspólnego przedrostka jest bardziej skomplikowane niż powinno. Decyzja o braku prefiksu pozbywa się tego hałasu. Połącz to z odpowiedzią Deduplicatora, którą sparafrazuję jako „korzyść z przedrostka jest prawie nieistotna”, i daje nam to: „Istnieje niewielki problem podczas używania przedrostków i niewielka zaleta, dlatego dobrym wyborem jest po prostu nie Użyj ich".


Z tym związane jest starsze pytanie dotyczące przepływu stosu.

Peter - Unban Robert Harvey
źródło
1
Niezłe porównanie. Uważam jednak, że nieużywanie żadnego prefiksu działa dobrze w praktyce.
Phil1970,
6

Należy zauważyć, że przewodnik po stylu MS mówi tylko o publicznych i chronionych metodach i zmiennych. tj. Ci inni deweloperzy zobaczą, czy używają twojej biblioteki.

Pomysł polega na tym, że biblioteki Microsoft mają standardowy wygląd i sposób korzystania z nich przez deweloperów.

Możesz dowolnie stosować dowolny styl w zmiennych prywatnych lub lokalnych.

Powiedziawszy, że zmienne prefiksy są ogólnie odradzane z wielu powodów

  • Mogą się mylić, a zatem wprowadzać w błąd
  • Jeśli prefiksujesz według typu zmiennej, nie jest jasne, w jaki sposób postępujesz z utworzonymi przez siebie klasami.
  • Istnieje wiele konwencji i nie zawsze są one zgodne. To, co uznasz za pomocne dla innego dewelopera, może okazać się nieprzydatne
  • W przypadku zmiennych składowych możesz użyć tego „tego”. słowo kluczowe, aby wskazać zakres, a moduł kompilatora go wymusi.
Ewan
źródło
2
Pierwotnie nie użyłem żadnego prefiksu dla pól. Później przełączyłem się na używanie pojedynczego znaku „_”, ponieważ widziałem, jak inni go używają, ponieważ przenosi wszystkie pola na górę listy członków alfabetycznie, gdy używasz rozwijanych pól IDE. Brak standardowej konwencji może być denerwujący podczas odczytywania kodu napisanego przez wielu różnych programistów.
Frank Hileman
po chwili widzisz tak wiele różnych stylów, że po prostu je ignorujesz. Gdybyś próbował narzucić styl, cały czas refaktoryzowałbyś wszystko
Ewan
4
Jeśli zastosujesz „Mogą się mylić, a przez to wprowadzać w błąd” do większości czegokolwiek, szybko odkryjesz, że nie powinieneś nazywać zmiennych ani funkcji - w końcu mogą się mylić! I zgaduję, że twój drugi punkt ma powiedzieć „klasy, których NIE stworzyłeś”? W przeciwnym razie po prostu tego nie rozumiem ... W każdym razie zaczyna to brzmieć, jakby posiadanie pojedynczego prefiksu podkreślenia dla pól niepublicznych jest nieoficjalnym standardem, prawdopodobnie w tym kierunku pójdziemy.
Betty Crokker
niektóre rzeczy są sprawdzane przez kompilator, więc m_count może nie być zmienną składową, ale this.count zawsze będzie
Ewan
dużo użycia ppl _ ale rzeczy się zmieniają z czasem, przekonasz się, że kod napisany w zeszłym roku do „najlepszych praktyk” nie pasuje do tegorocznych konwencji
Ewan
4

Dla mnie, jeśli czytam czyjś kod i widzę to:

count = 3;

Zakładam, że „count” jest zmienną lokalną dla tej funkcji i robię coś, co zostanie użyte w innym miejscu funkcji

I masz całkowitą rację, jeśli czytasz kod źródłowy, który przestrzega konwencji stylu C #. W takim kodzie:

this.count = 3;

przypisuje wartość do pola.

count = 3;

przypisuje wartość do zmiennej lokalnej.

Dlatego konwencje w stylu C # są jasne i jednoznaczne. Zmuszają cię, abyś nie używał żadnego prefiksu po prostu dlatego, że go nie potrzebujesz.

Jeśli chodzi o kod źródłowy, dla którego autorzy zdecydowali się zamiast tego zastosować swoje osobiste konwencje, należy zapytać ich osobiście, dlaczego zdecydowali się to zrobić. Jeśli nie używają prefiksów, takich jak podkreślenia, a jednocześnie ich nie używają this., doprowadziłoby to nie tylko do trudnego do odczytania kodu, ale także do przypadków, w których potrzebna byłaby dodatkowa konwencja:

public class Hello
{
    private string greetings;

    public Hello(string greetings)
    {
        greetings = greetings; // Wait, what is that?!
    }
}

Czy C # nie ma tego słowa kluczowego? Czy nie możesz odwołać się do członków korzystających z tego, na przykład this.count?

Tak, ale (1) to więcej pisania i (2) łatwo zapomnieć. Jeśli pole ma nazwę m_count, nie muszę pamiętać, aby wpisać this.m_count

Tutaj jednak się mylisz.

Więcej pisze się, gdy piszesz kod w Notatniku. W przypadku IDE z autouzupełnianiem lub, lepiej, IntelliSense, porównanie między this.counti m_countnie jest tak oczywiste. Zwróć uwagę na Shift+ -wymagane do wpisania podkreślenia.

Jeśli chodzi o „łatwe do zapomnienia”, dotyczy to tylko użytkowników Notatnika. StyleCop i Resharper nie tylko pozwolą zapomnieć o wkładaniu w this.razie potrzeby, ale po kilku godzinach programowania wkładanie w this.razie potrzeby staje się nawykiem.

Arseni Mourzenko
źródło
6
Uważam, że używanie thistylko wtedy, gdy jest to potrzebne, powoduje naprawdę niespójne odczucie i zbyt często mnie rozprasza. Jeśli zamierzasz używać thiszamiast prefiksu, uważam, że zawsze powinieneś go używać. W takim przypadku staje się prefiksem i równie dobrze możesz go użyć _.
RubberDuck,
1
RubberDuck ma rację. "to." jest prefiksem, chociaż ma znaczenie kompilatora.
Frank Hileman
4

Prefiks „członek” jest rodzajem szczegółów implementacyjnych. Odwraca twoją uwagę od dziedziny problemowej do dziedziny rozwiązań technicznych, która znieważa twój umysł, gdy myślisz o możliwych refaktoryzacjach. - ja

czy możesz wyjaśnić dalej? Twój jest jedynym powodem, dla którego widziałem, dlaczego nie używać prefiksu i nie rozumiem go ... (przez co rozumiem, inne rzeczy, o których mówią ludzie, to powody, dla których nie muszę go używać, co nie jest tym samym, dlaczego nie powinienem) - Betty Crokker

Chodzi mi o rozszerzenie odpowiedzi @CandiedOrange i @Ewan.

Prefiksy utrudniają refaktoryzację

W miarę ewolucji kodu zmienne i metody mogą zmieniać ich zakres. Na przykład. możesz stworzyć prywatną metodę, a później dowiedzieć się, że powinna ona być dostępna także dla innych (nowych) klas. Podobnie może się stać z parametrami metody i zmiennymi lokalnymi.

Załóżmy, że tworzysz kalkulator podatkowy. Zgodnie z zasadą zakresu widoczności leasingu dla zmiennych zaczynasz od metody, która przyjmuje zarówno stawkę podatkową, jak i wartość bazową jako parametry:
(przykład może wyglądać jak Java ...)

class TaxCalculator{
   double Calculate(double p_TaxRateInpercent, double p_BaseValue){
     return (100+p_TaxRateInpercent)/100 * p_BaseValue;
   }
} 

następnie musisz wykonać operację odwrotną i zgodnie z TDD robisz to przy najmniejszym wysiłku:

class TaxCalculator{
   double Calculate(double p_TaxRateInpercent, double p_BaseValue){
     return (100+p_TaxRateInpercent)/100 * p_BaseValue;
   }
   double CalculateBase(double p_TaxRateInpercent, double p_TaxedValue){
     return p_TaxedValue/((100+p_TaxRateInpercent)/100);
   }
} 

Następnie TDD chce, aby refaktoryzować kod, aby stał się bardziej przejrzysty, co ma zmniejszyć listę parametrów obu metod.
(Tak, najpierw wyodrębnisz podwójne obliczenia, ale pozwól, że zrozumiem o co chodzi ...)
Oczywistym rozwiązaniem jest zmiana stawki podatkowej na zmienną składową poprzez przekazanie jej jako parametru konstruktora.

class TaxCalculator{
   double m_TaxRateInpercent;
   TaxCalculator(double p_TaxRateInpercent){
     m_TaxRateInpercent = p_TaxRateInpercent;
   }
   double Calculate(double p_BaseValue){
     return (100+m_TaxRateInpercent)/100 * p_BaseValue;
   }
   double CalculateBase(double p_TaxedValue){
     return p_TaxedValue/((100+m_TaxRateInpercent)/100);
   }
} 

Jak widać musieliśmy się zmienić wszystkie wiersze naszych istniejących metod (wszystkie wiersze p_TaxRateInpercentbyły dokładne.

Problem polega na tym, że nie masz wsparcia ze strony IDE do zmiany nazwy w całej klasie. Jedyną pomocą jest wyszukiwanie / zamiana, która również zmieni konstruktor lub dowolną część, która przypadkowo zawiera ciąg p_TaxRateInpercent.
IDE może zaoferować zmianę ... refaktoryzacji dla niezdefiniowanych nazw zmiennych, ale może to być ograniczone do zakresu metody

Bez prefiksu zmieniłyby się tylko podpisy metod. Zmiana nazwy nie byłaby wcale potrzebna.


Przedrostki zaśmiecają historię SCM po zmianie

Również SCM rejestruje zmianę prefiksu jak zmiany w logice chociaż logika nie zmieni! Historia SCM jest zaśmiecona zmianami technicznymi, które ukrywają to, co ważne, i zwiększają ryzyko konfliktów z (rzeczywistymi zmianami logicznymi) innych.

Timothy Truckle
źródło
1

Używam przedrostka _ dla wszystkich zmiennych składowych. Nienawidzę „tego” przedrostka, ponieważ choć niektórzy twierdzą, że jest to właściwy sposób robienia rzeczy - dodaje dużo szumu / bałaganu do twojej bazy kodu. Pojedynczy znak podkreślenia jest również powszechną praktyką w próbkach Microsoftu.

W twoim przykładzie miałbym:

int _count = 10;
Eternal21
źródło