Rozważ następującą metodę:
public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{
}
I następujące połączenie:
var ids = ReturnEmployeeIds(true);
Dla programistów, którzy są nowicjuszami w systemie, odgadnięcie, co się stało, byłoby dość trudne true
. Najpierw najedź kursorem na nazwę metody lub przejdź do definicji (z których żadne nie jest dużym zadaniem). Ale dla czytelności sensowne jest napisanie:
var ids = ReturnEmployeeIds(includeManagement: true);
Czy jest gdzieś, gdzie formalnie dyskutuje się, czy jawnie określić parametry opcjonalne, gdy kompilator nie wymaga tego?
W poniższym artykule omówiono niektóre konwencje kodowania: https://msdn.microsoft.com/en-gb/library/ff926074.aspx
Coś podobnego do powyższego artykułu byłoby świetne.
c#
coding-style
readability
parameters
JᴀʏMᴇᴇ
źródło
źródło
boolean
argumentów metod i jak?Odpowiedzi:
Powiedziałbym, że w świecie C # wyliczenie byłoby jedną z najlepszych opcji tutaj.
Dzięki temu będziesz musiał przeliterować swoje działania, a każdy użytkownik zobaczy, co się dzieje. Jest również bezpieczny dla przyszłych rozszerzeń;
Moim zdaniem tutaj:
enum> opcjonalne enum> opcjonalne bool
Edycja: Ze względu na dyskusję z LeopardSkinPillBoxHat poniżej dotyczącą
[Flags]
wyliczenia, które w tym konkretnym przypadku może być dobre dopasowanie (ponieważ konkretnie mówimy o włączaniu / wyłączaniu rzeczy), zamiast tego proponuję użyćISet<WhatToInclude>
jako parametru.Jest to bardziej „nowoczesna” koncepcja z kilkoma zaletami, wynikającymi głównie z faktu, że pasuje do rodziny kolekcji LINQ, ale
[Flags]
ma także maksymalnie 32 grupy. Główną wadą używaniaISet<WhatToInclude>
jest to, jak źle obsługiwane są składnie w C #:Niektóre z nich mogą zostać złagodzone przez funkcje pomocnicze, zarówno do generowania zestawu, jak i tworzenia „zestawów skrótów”, takich jak
źródło
enum
podejście, ale nie jestem wielkim fanem mieszaniaInclude
iExclude
w taki samenum
, jak to jest trudniejsze do uchwycenia tego, co się dzieje. Jeśli chcesz, aby było to naprawdę rozszerzalne, możesz uczynić to a[Flags]
enum
, uczynić je wszystkimiIncludeXxx
i zapewnić ustawienie,IncludeAll
które jest ustawione naInt32.MaxValue
. Następnie możesz podać 2ReturnEmployeeIds
przeciążenia - jeden, który zwraca wszystkich pracowników, i drugi, który przyjmujeWhatToInclude
parametr filtru, który może być kombinacją flag wyliczeniowych.[Flags]
to relikt i powinien być używany tylko ze względu na dziedzictwo lub wydajność. Myślę, że aISet<WhatToInclude>
jest znacznie lepszą koncepcją (niestety w C # brakuje cukru syntaktycznego, aby był przyjemny w użyciu).Flags
podejście, ponieważ pozwala dzwoniącemu na przejścieWhatToInclude.Managers | WhatToInclude.Cleaners
i bitowe lub wyraża dokładnie, w jaki sposób dzwoniący je przetworzy (tj. Menedżerowie lub sprzątacze). Intuicyjny cukier syntaktyczny :-)Jest to dyskusyjne, jeśli jest to „dobry styl”, ale IMHO nie jest co najmniej złym stylem i jest użyteczne, o ile linia kodu nie staje się „zbyt długa”. Bez nazwanych parametrów powszechnym stylem jest również wprowadzanie zmiennej objaśniającej:
Ten styl jest prawdopodobnie bardziej powszechny wśród programistów C # niż nazwany wariant parametru, ponieważ nazwane parametry nie były częścią języka przed wersją 4.0. Wpływ na czytelność jest prawie taki sam, wymaga jedynie dodatkowej linii kodu.
Więc moja rada: jeśli uważasz, że użycie wyliczenia lub dwóch funkcji o różnych nazwach to „przesada” lub nie chcesz lub nie możesz zmienić podpisu z innego powodu, skorzystaj z nazwanego parametru.
źródło
includeManagement
jako lokalnyconst
i w ogóle unikać używania dodatkowej pamięci.Jeśli napotkasz kod taki jak:
możesz właściwie zagwarantować, że to, co jest w środku,
{}
będzie wyglądało tak:Innymi słowy, wartość logiczna jest używana do wyboru między dwoma kierunkami działania: metoda ma dwa obowiązki. Jest to prawie gwarantowany zapach kodu . Zawsze powinniśmy dążyć do tego, aby metody robiły tylko jedną rzecz; ponoszą tylko jedną odpowiedzialność. Dlatego twierdzę, że oba rozwiązania są niewłaściwe. Korzystanie z opcjonalnych parametrów jest znakiem, że metoda robi więcej niż jedną rzecz. Parametry boolowskie są również tego znakiem. Oba powinny zdecydowanie ustawić dzwonki alarmowe. W związku z tym powinieneś podzielić dwa różne zestawy funkcji na dwie osobne metody. Podaj nazwy metod, które wyjaśniają, co robią, np .:
Zarówno poprawia to czytelność kodu, jak i zapewnia lepsze przestrzeganie zasad SOLID.
EDYCJA Jak wskazano w komentarzach, w tym przypadku przypadek, w którym te dwie metody prawdopodobnie będą miały wspólną funkcję, i że ta wspólna funkcjonalność najprawdopodobniej będzie najlepiej zapakowana w funkcję prywatną, która będzie wymagała parametru logicznego do kontrolowania niektórych aspektów tego, co robi .
Czy w takim przypadku należy podać nazwę parametru, nawet jeśli kompilator jej nie potrzebuje? Sugerowałbym, że nie ma prostej odpowiedzi na to pytanie, a wyrok jest potrzebny w poszczególnych przypadkach:
Czy plik źródłowy i metody są małe i łatwe do zrozumienia? Jeśli tak, to nazwany parametr prawdopodobnie oznacza hałas. Jeśli nie, może to być bardzo pomocne. Zastosuj regułę odpowiednio do okoliczności.
źródło
public List<Guid> ReturnEmployeeIds(bool includeManagement) { calculateSomethingHereForBothBranches; if (includeManagement) return something else return something else }
podział na dwie metody prawdopodobnie oznacza, że te dwie metody będą wymagały wyniku pierwszego obliczenia jako parametru.Tak, prawda. Kod samodokumentujący to piękna rzecz. Jak wskazały inne odpowiedzi, istnieją sposoby na usunięcie dwuznaczności wywołania
ReturnEmployeeIds
za pomocą wyliczenia, różnych nazw metod itp. Jednak czasami tak naprawdę nie można uniknąć tej sprawy, ale nie chcesz odejść i użyć je wszędzie (chyba że podoba ci się gadatliwość wizualna).Na przykład może pomóc wyjaśnić jedno połączenie, ale niekoniecznie drugie.
Argument nazwany może być pomocny tutaj:
Nie dodawaj dużo dodatkowej jasności (zgaduję, że to jest analizowanie wyliczenia):
Może faktycznie zmniejszyć przejrzystość (jeśli dana osoba nie rozumie, jaka jest pojemność na liście):
Lub gdzie to nie ma znaczenia, ponieważ używasz dobrych nazw zmiennych:
Nie AFAIK. Zajmijmy się jednak obiema częściami: jedynym jawnym zastosowaniem parametrów nazwanych jest to, że wymaga tego kompilator (zazwyczaj jest to usunięcie niejednoznaczności instrukcji). Poza tym możesz z nich korzystać, kiedy tylko chcesz. W tym artykule od MS znajduje się kilka sugestii, kiedy możesz z nich skorzystać, ale nie jest to szczególnie ostateczne https://msdn.microsoft.com/en-us/library/dd264739.aspx .
Osobiście uważam, że używam ich najczęściej podczas tworzenia niestandardowych atrybutów lub usuwania nowej metody, w której moje parametry mogą się zmieniać lub zmieniać kolejność (atrybuty nazwane b / c mogą być wymienione w dowolnej kolejności). Dodatkowo używam ich tylko wtedy, gdy podaję wartość statyczną inline, w przeciwnym razie jeśli przekazuję zmienną, staram się używać dobrych nazw zmiennych.
Ostatecznie sprowadza się to do osobistych preferencji. Oczywiście istnieje kilka przypadków użycia, w których musisz użyć nazwanych parametrów, ale potem musisz wykonać wywołanie osądu. IMO - używaj go w dowolnym miejscu, które Twoim zdaniem pomoże udokumentować kod, zmniejszy dwuznaczność lub pozwoli chronić podpisy metod.
źródło
Jeżeli parametr jest oczywiste ze względu na (w pełni kwalifikowana) Nazwa metody i to istnienie jest naturalną metodą, co ma robić, należy nie używać nazwie składni parametrów. Na przykład,
ReturnEmployeeName(57)
to jest cholernie oczywiste, że57
jest to identyfikator pracownika, więc zbędne jest dodawanie adnotacji za pomocą składni o nazwie parametru.Z
ReturnEmployeeIds(true)
, tak jak powiedziałeś, chyba że spojrzysz na deklarację / dokumentację funkcji, nie można ustalić, co totrue
znaczy - więc powinieneś użyć nazwanej składni parametru.Teraz rozważ ten trzeci przypadek:
Składnia nazwanego parametru nie jest tutaj używana, ale ponieważ przekazujemy zmienną do
ReturnEmployeeIds
, a ta zmienna ma nazwę, a nazwa ta oznacza to samo, co parametr, do którego ją przekazujemy (często tak jest - ale nie zawsze!) - nie potrzebujemy składni nazwanego parametru, aby zrozumieć jego znaczenie z kodu.Zasada jest więc prosta - jeśli z samego wywołania metody można łatwo zrozumieć, co ten parametr ma znaczyć, nie używaj nazwanego parametru. Jeśli nie możesz - to jest brak czytelności kodu i prawdopodobnie chcesz je naprawić (oczywiście nie za wszelką cenę, ale zwykle chcesz, aby kod był czytelny). Poprawka nie musi nazywać się parametrami - możesz najpierw umieścić wartość w zmiennej lub użyć metod sugerowanych w innych odpowiedziach, o ile wynik końcowy ułatwia zrozumienie działania parametru.
źródło
ReturnEmployeeName(57)
że natychmiast staje się oczywiste, że57
to identyfikator.GetEmployeeById(57)
z drugiej strony ...ReturnEmployeeName
zademonstrować przypadki, w których nawet bez wyraźnego podania parametru w nazwie metody, całkiem rozsądne jest oczekiwanie, że ludzie zrozumieją, co to jest. W każdym razie warto zauważyć, że w przeciwieństwie do tegoReturnEmployeeName
, nie można rozsądnie zmienić nazwyReturnEmployeeIds
na coś, co wskazuje na znaczenie parametrów.Tak, myślę, że to dobry styl.
Ma to sens w przypadku stałych logicznych i stałych całkowitych.
Jeśli podajesz zmienną, nazwa zmiennej powinna wyjaśnić znaczenie. Jeśli nie, powinieneś nadać zmiennej lepszą nazwę.
Jeśli przekazujesz ciąg, znaczenie jest często jasne. Na przykład, jeśli widzę wywołanie GetFooData („Oregon”), myślę, że czytelnik może zgadnąć, że parametr jest nazwą stanu.
Oczywiście nie wszystkie łańcuchy są oczywiste. Jeśli widzę GetFooData („M”), to jeśli nie znam funkcji, nie mam pojęcia, co oznacza „M”. Jeśli jest to jakiś kod, na przykład M = „pracownicy na poziomie zarządzania”, prawdopodobnie powinniśmy mieć wyliczenie zamiast literału, a nazwa wyliczenia powinna być jasna.
I przypuszczam, że sznurek może wprowadzać w błąd. Może „Oregon” w powyższym przykładzie odnosi się nie do stanu, ale do produktu, klienta lub czegokolwiek innego.
Ale GetFooData (prawda) lub GetFooData (42) ... to może znaczyć wszystko. Nazwane parametry to wspaniały sposób na samodokumentowanie. Używałem ich do tego celu przy wielu okazjach.
źródło
Nie ma żadnych super jasnych powodów, dla których warto używać tego typu składni.
Ogólnie staraj się unikać argumentów logicznych, które z daleka mogą wyglądać na arbitralne.
includeManagement
(najprawdopodobniej) znacznie wpłynie na wynik. Ale argument wygląda na to, że ma „małą wagę”.Wykorzystanie wyliczenia zostało omówione, nie tylko będzie wyglądało, że argument „ma większy ciężar”, ale zapewni także skalowalność metody. Może to jednak nie być najlepsze rozwiązanie we wszystkich przypadkach, ponieważ twoja
ReturnEmployeeIds
metoda będzie musiała skalować się wraz zWhatToInclude
-enum (patrz odpowiedź NiklasJ ). Może to powodować ból głowy później.Zastanów się: jeśli
WhatToInclude
skalujesz -enum, ale nie -metodReturnEmployeeIds
. Może wtedy wyrzucićArgumentOutOfRangeException
(najlepszy przypadek) lub zwrócić coś całkowicie niechcianego (null
lub pustegoList<Guid>
). Co w niektórych przypadkach może mylić programistę, szczególnie jeśliReturnEmployeeIds
znajdujesz się w bibliotece klas, w której kod źródłowy nie jest łatwo dostępny.Można założyć, że
WhatToInclude.Trainees
zadziała, jeśliWhatToInclude.All
tak, ponieważ stażyści są „podzbiorem” wszystkich.Zależy to (oczywiście) od sposobu
ReturnEmployeeIds
implementacji.W przypadkach, w których można przekazać argument boolowski, staram się zamiast tego podzielić go na dwie metody (lub więcej, jeśli to konieczne), w twoim przypadku; można by wyodrębnić
ReturnAllEmployeeIds
,ReturnManagementIds
aReturnRegularEmployeeIds
. Obejmuje to wszystkie podstawy i jest całkowicie zrozumiałe dla każdego, kto je wdraża. Nie będzie to również miało wspomnianego wyżej problemu „skalowania”.Ponieważ są tylko dwa wyniki dla czegoś, co ma logiczny argument. Wdrożenie dwóch metod wymaga bardzo niewiele dodatkowego wysiłku.
Mniej kodu, rzadko lepiej.
Z tym powiedział, nie są pewne przypadki, w których wyraźnie deklarując argument poprawia czytelność. Zastanów się na przykład.
GetLatestNews(max: 10)
.GetLatestNews(10)
jest wciąż dość oczywiste, wyraźna deklaracjamax
pomoże wyjaśnić wszelkie nieporozumienia.A jeśli absolutnie musisz mieć argument boolowski, którego użycia nie można wywnioskować po prostu czytając
true
lubfalse
. Powiedziałbym wtedy, że:.. jest absolutnie lepszy i bardziej czytelny niż:
Ponieważ w drugim przykładzie
true
może to oznaczać absolutnie wszystko . Ale starałbym się uniknąć tego argumentu.źródło