Rozdział wuja Boba na temat nazw w Czystym Kodzie zaleca unikanie kodowania nazw, głównie w odniesieniu do notacji węgierskiej. On także wyraźnie wymienia usunięcie I
prefiksu z interfejsów, ale nie pokazują przykłady.
Załóżmy, że:
- Wykorzystanie interfejsu służy głównie do testowania poprzez wstrzykiwanie zależności
- W wielu przypadkach prowadzi to do posiadania jednego interfejsu z jednym implementatorem
Na przykład, jak należy nazwać tych dwóch? Parser
i ConcreteParser
? Parser
i ParserImplementation
?
public interface IParser {
string Parse(string content);
string Parse(FileInfo path);
}
public class Parser : IParser {
// Implementations
}
A może powinienem zignorować tę sugestię w przypadkach takich jak jedno wdrożenie?
c#
naming
clean-code
naming-standards
Vinko Vrsalovic
źródło
źródło
Odpowiedzi:
Chociaż wiele osób, w tym „Wujek Bob”, odradza stosowanie
I
jako prefiksów interfejsów, jest to dobrze znana tradycja w języku C #. Ogólnie należy tego unikać. Ale jeśli piszesz w C #, naprawdę powinieneś przestrzegać konwencji tego języka i używać go. Niezastosowanie się do tego spowoduje ogromne zamieszanie z kimkolwiek znającym C #, który próbuje odczytać twój kod.źródło
Interfejs jest ważną logiczną koncepcją, dlatego interfejs powinien nosić nazwę ogólną. Wolałbym więc
niż
Ten ostatni ma kilka problemów:
źródło
Default
lubReal
czyImpl
w tak wielu moich nazw klasIList<T>
i tak naprawdę nie obchodzi go, jak jest przechowywany wewnętrznie; możliwość porzuceniaI
i nadania nazwy klasy użytecznej wydaje się milsza niż konieczność magicznej wiedzy (jak w Javie), że kod, który chce zwykłej implementacji,List<T>
powinien użyćArrayList<T>
.CFoo : Foo
. Ta opcja nie jest tak naprawdę dostępna. Po drugie , pojawia się dziwna niespójność, ponieważ niektóre klasy miałyby znacznik, a inne nie, w zależności od tego, czy mogą istnieć inne implementacje tego samego interfejsu.CFoo : Foo
aleSqlStore : Store
. Z tym mam jeden problemImpl
, który jest po prostu brzydszym markerem. Może gdyby istniał język z konwencją zawsze dodawaniaC
(lub cokolwiek), mogłoby to być lepsze niżI
Nie chodzi tylko o konwencje nazewnictwa. C # nie obsługuje wielokrotnego dziedziczenia, więc to starsze użycie notacji węgierskiej ma niewielką, choć przydatną zaletę, gdy dziedziczysz od klasy podstawowej i implementujesz jeden lub więcej interfejsów. Więc to...
... jest lepszy od tego ...
IMO
źródło
inherits
Vs.implements
extends
.Nie nie nie.
Konwencje nazewnictwa nie są funkcją twojego fandomu wujka Boba. Nie są one funkcją języka, języka C # ani żadnego innego.
Są funkcją twojej bazy kodu. W znacznie mniejszym stopniu twój sklep. Innymi słowy, pierwszy w bazie kodu ustanawia standard.
Tylko wtedy możesz zdecydować. Potem bądź konsekwentny. Jeśli ktoś zacznie być niekonsekwentny, zdejmij pistolet do krojenia i każ mu aktualizować dokumentację, aż się nawróci.
Czytając nową bazę kodu, jeśli nigdy nie widzę
I
przedrostka, czuję się dobrze, niezależnie od języka. Jeśli widzę jeden na każdym interfejsie, czuję się dobrze. Jeśli czasem go zobaczę, ktoś zapłaci.Jeśli znajdziesz się w rzadkim kontekście ustawiania tego precedensu dla bazy kodu, zachęcam do rozważenia tego:
Jako klasa klienta zastrzegam sobie prawo do tego, żeby nie obchodzić mnie to, z czym rozmawiam. Potrzebuję tylko imienia i listy rzeczy, które mogę zadzwonić pod to imię. Nie mogą się one zmienić, chyba że tak powiem. Jestem właścicielem tej części. To, o czym rozmawiam, interfejs czy nie, nie jest moim działem.
Jeśli wślizgniesz się
I
w to imię, klient nie będzie się tym przejmował. Jeśli nieI
, klient nie dba o to. To, o czym mówi, może być konkretne. Może nie. Ponieważ nie wzywanew
go w żaden sposób, nie ma znaczenia. Jako klient nie wiem. Nie chcę wiedziećTeraz, gdy małpa kodowa patrzy na ten kod, nawet w Javie, może to być ważne. W końcu, jeśli muszę zmienić implementację na interfejs, mogę to zrobić bez powiadamiania klienta. Jeśli nie ma
I
tradycji, mogę nawet jej nie zmienić. Co może być problemem, ponieważ teraz musimy ponownie skompilować klienta, ponieważ chociaż źródło nie przejmuje się plikiem binarnym. Coś łatwego do przeoczenia, jeśli nigdy nie zmieniłeś nazwy.Może to nie jest dla ciebie problem. Może nie. Jeśli tak, to dobry powód, aby się tym przejmować. Ale ze względu na zamiłowanie do zabawek biurkowych nie twierdzimy, że powinniśmy to robić na ślepo, ponieważ jest to sposób, w jaki robi się to w pewnym języku. Albo mają cholernie dobry powód, albo ich to nie obchodzi.
Nie chodzi o życie z nim na zawsze. Chodzi o to, żeby nie dać mi bzdury, że baza kodu nie jest zgodna z twoją konkretną mentalnością, chyba że jesteś przygotowany na rozwiązanie „problemu” wszędzie. Jeśli nie masz dobrego powodu, nie powinienem podążać drogą najmniejszego oporu wobec jednolitości, nie przychodź do mnie, głosząc zgodność. Mam lepsze rzeczy do zrobienia.
źródło
I
. Jeśli Twój kod ich używa, zobaczysz je wszędzie. Jeśli Twój kod ich nie używa, zobaczysz je z kodu frameworka, co prowadzi dokładnie do miksu, którego nie chcesz.Jest jedna drobna różnica między Javą a C #, która jest tutaj istotna. W Javie domyślnie każdy członek jest wirtualny. W języku C # każdy element jest domyślnie zaplombowany - z wyjątkiem elementów interfejsu.
Założenia, które się z tym wiążą, mają wpływ na wytyczną - w Javie każdy typ publiczny powinien być uważany za niekończący, zgodnie z zasadą substytucji Liskowa [1]. Jeśli masz tylko jedną implementację, nazwiesz klasę
Parser
; jeśli uznasz, że potrzebujesz wielu implementacji, po prostu zmienisz klasę na interfejs o tej samej nazwie i zmienisz nazwę konkretnej implementacji na coś opisowego.W języku C # głównym założeniem jest to, że kiedy dostajesz klasę (nazwa nie zaczyna się od
I
), to jest klasa, którą chcesz. Pamiętaj, że nie jest to w przybliżeniu 100% dokładne - typowym kontrprzykładem byłyby takie klasyStream
(które naprawdę powinien byłby być interfejsem lub kilkoma interfejsami), a każdy ma swoje własne wytyczne i pochodzenie z innych języków. Istnieją również inne wyjątki, takie jak dość powszechnie stosowanyBase
przyrostek oznaczający klasę abstrakcyjną - podobnie jak w przypadku interfejsu, wiesz, że typ powinien być polimorficzny.Istnieje również przyjemna funkcja użyteczności polegająca na pozostawieniu nazwy bez prefiksu I dla funkcjonalności związanej z tym interfejsem bez konieczności uciekania się do tworzenia interfejsu jako klasy abstrakcyjnej (co zaszkodziłoby z powodu braku wielokrotnego dziedziczenia klas w języku C #). Zostało to spopularyzowane przez LINQ, który wykorzystuje
IEnumerable<T>
jako interfejs iEnumerable
jako repozytorium metod mających zastosowanie do tego interfejsu. Nie jest to konieczne w Javie, gdzie interfejsy mogą również zawierać implementacje metod.Ostatecznie
I
prefiks jest szeroko stosowany w świecie C #, a przez to w świecie .NET (ponieważ zdecydowanie większość kodu .NET jest napisana w języku C #, warto stosować się do wytycznych C # dla większości publicznych interfejsów). Oznacza to, że prawie na pewno będziesz pracować z bibliotekami i kodem, który jest zgodny z tą notacją, i warto przyjąć tradycję, aby zapobiec niepotrzebnym nieporozumieniom - to nie tak, że pominięcie prefiksu poprawi kod :)Zakładam, że rozumowanie wuja Boba było takie:
IBanana
jest abstrakcyjnym pojęciem banana. Jeśli może istnieć jakaś klasa implementująca, która nie miałaby lepszej nazwy niżBanana
, abstrakcja jest całkowicie pozbawiona znaczenia i powinieneś porzucić interfejs i po prostu użyć klasy. Jeśli istnieje lepsza nazwa (powiedzmyLongBanana
lubAppleBanana
), nie ma powodu, aby nie używać jejBanana
jako nazwy interfejsu. Dlatego użycieI
przedrostka oznacza, że masz niepotrzebną abstrakcję, co sprawia, że kod jest trudniejszy do zrozumienia bez żadnych korzyści. A ponieważ ścisłe OOP sprawi, że zawsze będziesz kodować przeciwko interfejsom, jedynym miejscem, gdzie nie zobaczyszI
prefiksu na typie, będzie konstruktor - dość bezsensowny hałas.Jeśli zastosujesz to do przykładowego
IParser
interfejsu, możesz wyraźnie zobaczyć, że abstrakcja znajduje się całkowicie na terytorium „bez znaczenia”. Albo jest coś specyficznego o konkretnej implementacji parsera (npJsonParser
,XmlParser
...), czy też po prostu użyć klasy. Nie ma czegoś takiego jak „domyślna implementacja” (chociaż w niektórych środowiskach rzeczywiście ma to sens - zwłaszcza COM), albo istnieje konkretna implementacja, albo potrzebujesz abstrakcyjnej metody klasy lub rozszerzenia dla „domyślnych”. Jednak w języku C #, chyba że twoja baza kodu już pomijaI
prefiks -f, zachowaj go. Po prostu zanotuj za każdym razem, gdy zobaczysz kod podobnyclass Something: ISomething
- oznacza to, że ktoś nie jest zbyt dobry w przestrzeganiu YAGNI i tworzeniu rozsądnych abstrakcji.[1] - Technicznie rzecz biorąc, nie jest to specjalnie wspomniane w pracy Liskova, ale jest to jedna z podstaw oryginalnej pracy OOP i podczas mojego czytania Liskova nie zakwestionowała tego. W mniej ścisłej interpretacji (tej przyjętej przez większość języków OOP) oznacza to, że każdy kod używający typu publicznego, który jest przeznaczony do podstawienia (tj. Nie-końcowy / zapieczętowany) musi działać z dowolną zgodną implementacją tego typu.
źródło
W idealnym świecie nie powinieneś poprzedzać swojego imienia krótką ręką („Ja ...”), ale po prostu pozwól, aby nazwa wyrażała pojęcie.
Przeczytałem gdzieś (i nie mogę go zdobyć) opinię, że ta konwencja ISomething prowadzi do zdefiniowania koncepcji „IDollar”, gdy potrzebna jest klasa „Dollar”, ale poprawnym rozwiązaniem byłoby pojęcie zwane po prostu „Walutą”.
Innymi słowy, konwencja daje łatwość nazywania rzeczy, a łatwość jest subtelnie błędna .
W prawdziwym świecie klienci twojego kodu (który może być po prostu „ty z przyszłości”) muszą być w stanie przeczytać go później i zapoznać się z nim tak szybko (lub przy minimalnym wysiłku), jak to możliwe (daj klientom kodu, czego oczekują).
Konwencja ISomething wprowadza cruft / bad names. To powiedziawszy, cruft nie jest prawdziwym cruft *), chyba że konwencje i praktyki stosowane przy pisaniu kodu nie są już aktualne; jeśli konwencja mówi „użyj ISomething”, najlepiej użyć go, aby dostosować się (i lepiej pracować) do innych programistów.
*) Mogę uciec od powiedzenia tego, ponieważ „cruft” nie jest zresztą dokładną koncepcją :)
źródło
Jak wspomniano w innych odpowiedziach, prefiks nazwy interfejsu
I
jest częścią wytycznych kodowania dla platformy .NET. Ponieważ w klasach C # i VB.NET częściej występują interakcje z konkretnymi klasami niż z ogólnymi interfejsami, są one najwyższej klasy w schematach nazewnictwa i dlatego powinny mieć najprostsze nazwy - stądIParser
dla interfejsu iParser
domyślnej implementacji.Ale w przypadku języków Java i podobnych, w których preferowane są interfejsy zamiast konkretnych klas, wujek Bob ma rację, że
I
powinny zostać usunięte, a interfejsy powinny mieć najprostsze nazwy -Parser
na przykład dla interfejsu analizatora składni. Ale zamiast nazwy opartej na rolachParserDefault
klasa powinna mieć nazwę opisującą sposób implementacji parsera :Jest to, na przykład, w jaki sposób
Set<T>
,HashSet<T>
iTreeSet<T>
są nazywane.źródło
sealed
domyślnie i musisz je jawnievirtual
. Wirtualność to szczególny przypadek w języku C #. Oczywiście, ponieważ zalecane podejście do pisania kodu zmieniało się na przestrzeni lat, wiele reliktów przeszłości musiało pozostać w celu zachowania zgodności :) Kluczowe jest to, że jeśli otrzymujesz interfejs w C # (który zaczyna się odI
), wiesz, że jest to całkowicie typ abstrakcyjny; jeśli dostaniesz brak interfejsu, typowym założeniem jest to, że tego typu chcesz, LSP niech będzie przeklęty.Masz rację, ponieważ ta konwencja interfejsu I = łamie (nowe) reguły
W dawnych czasach poprzedzałbyś wszystko swoim typem intCounter boolFlag itp.
Jeśli chcesz nazywać rzeczy bez prefiksu, ale także unikaj „ConcreteParser”, możesz użyć przestrzeni nazw
źródło
intCounter
lubboolFlag
. To są niewłaściwe „typy”. Zamiast tego piszeszpszName
(wskaźnik na zakończony znakiem NUL ciąg zawierający nazwę),cchName
(liczba znaków w ciągu „nazwa”),xControl
(x współrzędna formantu),fVisible
(flaga wskazująca, że coś jest widoczne),pFile
(wskaźnik do plik),hWindow
(uchwyt do okna) i tak dalej.xControl
docchName
. Oba są typuint
, ale mają bardzo różną semantykę. Przedrostki czynią tę semantykę oczywistą dla człowieka. Wskaźnik do łańcucha zakończonego przez NUL wygląda dokładnie tak jak wskaźnik do łańcucha w stylu Pascala dla kompilatora. PodobnieI
prefiks interfejsów pojawił się w języku C ++, w którym interfejsy są tak naprawdę tylko klasami (a tak naprawdę w przypadku C, gdzie nie ma czegoś takiego jak klasa lub interfejs na poziomie języka, to tylko konwencja).