Czy wystarczy, aby metody można było odróżnić tylko nazwą argumentu (a nie typem)?

36

Czy wystarczy, aby metody można było rozróżnić tylko według nazwy argumentu (a nie typu), czy lepiej nazwać ją bardziej jednoznacznie?

Na przykład T Find<T>(int id)vs T FindById<T>(int id).

Czy jest jakiś dobry powód, aby nazwać go bardziej jawnie (tj. Dodając ById), zamiast utrzymywać tylko nazwę argumentu?

Jednym z powodów, o których mogę myśleć, jest to, że podpisy metod są takie same, ale mają inne znaczenie.

FindByFirstName(string name) i FindByLastName(string name)

Konrad
źródło
4
Więc kiedy przeciążasz Znajdź, aby uwzględnić T Find<T>(string name)lub (int size)jak planujesz rozwiązać nieuniknione problemy?
UKMonkey,
3
@UKMonkey jakie nieuniknione problemy?
Konrad
3
w pierwszym przypadku: jeśli wiele wpisów ma tę samą nazwę, należy zmienić podpis funkcji; co oznacza, że ​​ludzie prawdopodobnie będą myleni z tym, co oznacza powrót; W tym drugim przypadku argument jest taki sam - a zatem nielegalne przeciążenie. Możesz albo nazwać funkcję „byX”, albo utworzyć obiekt dla argumentu, aby uzyskać równoważnik przeciążenia z tym samym argumentem. Albo działa dobrze w różnych sytuacjach.
UKMonkey
2
@UKMonkey możesz opublikować odpowiedź z kilkoma przykładami kodu, jeśli chcesz
Konrad
3
Identyfikatory powinny być prawdopodobnie nieprzezroczystym IDprzedmiotem, a nie tylko int. W ten sposób uzyskaj sprawdzenie czasu kompilacji, czy nie używasz identyfikatora dla int lub viceversa w jakiejś części kodu. I dzięki temu możesz mieć find(int value)i find(ID id).
Bakuriu

Odpowiedzi:

68

Pewnie, że istnieje dobry powód, aby nazwać to bardziej jednoznacznie.

To nie jest przede wszystkim metoda definicja , która powinna być oczywista, ale metoda wykorzystanie . I choć findById(string id)i find(string id)to zarówno oczywiste, istnieje ogromna różnica pomiędzy findById("BOB")a find("BOB"). W pierwszym przypadku wiesz, że literał losowy jest w rzeczywistości Id. W tym drugim przypadku nie jesteś pewien - może to być imię lub coś zupełnie innego.

Kilian Foth
źródło
9
Z wyjątkiem 99% innych przypadków, w których masz zmienną lub właściwość name jest punktem odniesienia: findById(id)vs find(id). Możesz przejść w obie strony.
Greg Burghardt
16
@GregBurghardt: Określona wartość niekoniecznie ma taką samą nazwę dla metody i jej obiektu wywołującego. Rozważmy na przykład double Divide(int numerator, int denominator)wykorzystywane w metodzie: double murdersPerCapita = Divide(murderCount, citizenCount). Nie można polegać na dwóch metodach wykorzystujących tę samą nazwę zmiennej, ponieważ istnieje wiele przypadków, w których tak nie jest (lub gdy tak jest, jest to złe nazewnictwo)
Flater,
1
@Flater: Biorąc pod uwagę kod w pytaniu (znajdowanie rzeczy z pewnego rodzaju trwałej pamięci) Wyobrażam sobie, że możesz wywołać tę metodę z „murdersCitizenId” lub „citizenId” ... Naprawdę nie sądzę, aby nazwy argumentów lub zmiennych były niejednoznaczne tutaj. I szczerze mówiąc, mogłem pójść w obie strony. To właściwie bardzo uparte pytanie.
Greg Burghardt
4
@GregBurghardt: Nie można destylować reguły globalnej z jednego przykładu. Pytanie OP jest ogólnie rzecz biorąc , nie jest specyficzne dla podanego przykładu. Tak, w niektórych przypadkach użycie tej samej nazwy ma sens, ale są też przypadki, w których nie ma takiej możliwości. Stąd ta odpowiedź ,, najlepiej dążyć do spójności, nawet jeśli nie jest to konieczne w podzbiorze przypadków użycia.
Flater
1
Parametry nazw rozwiązują zamieszanie po tym, jak zamieszanie już istniało , ponieważ należy spojrzeć na definicję metody, podczas gdy jawnie nazwane metody całkowicie unikają zamieszania , ponieważ nazwa jest obecna w wywołaniu metody. Ponadto nie możesz mieć dwóch metod o tej samej nazwie i typie argumentu, ale o innej nazwie argumentu, co oznacza, że ​​w niektórych przypadkach będziesz potrzebować wyraźnych nazw.
Darkhogg
36

Zalety FindById () .

  1. Future pokryć : Jeśli początek Find(int), a później trzeba dodać inne metody ( FindByName(string), FindByLegacyId(int), FindByCustomerId(int), FindByOrderId(int), etc), ludzie tacy jak ja spędzają wieki szukając FindById(int). Naprawdę nie jest to problem, jeśli możesz i zmienisz Find(int)go, FindById(int)gdy stanie się to konieczne - w przyszłości sprawdzimy, czy jest to konieczne .

  2. Łatwiejszy do odczytania . Findjest całkowicie w porządku, jeśli wygląda na to połączenie record = Find(customerId);Jeszcze FindByIdjest nieco łatwiejszy do odczytania, jeśli tak jest record = FindById(AFunction());.

  3. Spójność . Możesz konsekwentnie stosować wzór FindByX(int)/ FindByY(int)wszędzie, ale Find(int X)/ Find(int Y)nie jest możliwe, ponieważ powodują konflikty.

Zalety Find ()

  • POCAŁUNEK. Findjest prosty i jednoznaczny, a obok operator[]tego jest jedną z 2 najbardziej oczekiwanych nazw funkcji w tym kontekście. (Niektóre popularne alternatywy bycia get, lookuplub fetch, w zależności od kontekstu).
  • Zasadniczo, jeśli masz nazwę funkcji, która jest pojedynczym dobrze znanym słowem, które dokładnie opisuje jej działanie, użyj jej. Nawet jeśli istnieje dłuższa nazwa składająca się z wielu słów, która jest nieco lepsza w opisywaniu działania funkcji. Przykład: Długość vs NumberOfElements . Istnieje kompromis, a gdzie wytyczyć granicę, jest przedmiotem ciągłej debaty.
  • Na ogół dobrze jest unikać redundancji. Jeśli spojrzymy na to FindById(int id), możemy łatwo usunąć redundancję, zmieniając ją na Find(int id), ale istnieje kompromis - tracimy trochę przejrzystości.

Alternatywnie możesz uzyskać zalety obu , używając silnie napisanych identyfikatorów:

CustomerRecord Find(Id<Customer> id) 
// Or, depending on local coding standards
CustomerRecord Find(CustomerId id) 

Implementacja Id<>: Mocne wpisywanie wartości identyfikatora w C #

Komentarze tutaj, a także w powyższym linku, wzbudziły wiele obaw związanych z Id<Customer>tym, co chciałbym rozwiązać:

  • Obawa 1: To nadużycie generyczne. CustomerIdi OrderIDsą różnymi typami ( customerId1 = customerId2;=> dobra, customerId1 = orderId1;=> zła), ale ich implementacja jest prawie identyczna, więc możemy zaimplementować je za pomocą kopiowania wklej lub metaprogramowania. Podczas gdy dyskusja na temat ujawniania lub ukrywania generyków ma wartość, o tyle metaprogramowanie jest tym, do czego służą.
  • Obawa 2: Nie zatrzymuje prostych błędów. / Jest to rozwiązanie problemu . Głównym problemem, który został usunięty przy użyciu silnie wpisanych identyfikatorów ID, jest niewłaściwa kolejność argumentów w wywołaniu DoSomething(int customerId, int orderId, int productId). Silnie wpisane identyfikatory zapobiegają również innym problemom, w tym zapytaniu o OP.
  • Obawa 3: Naprawdę po prostu zaciemnia kod. Trudno powiedzieć, czy identyfikator jest zatrzymany int aVariable. Łatwo jest stwierdzić, że identyfikator jest przechowywany Id<Customer> aVariable, a nawet możemy stwierdzić, że jest to identyfikator klienta.
  • Obawa 4: Te identyfikatory nie są silnymi typami, są tylko opakowaniami. Stringto tylko opakowanie byte[]. Zawijanie lub enkapsulacja nie stoi w sprzeczności z silnym pisaniem na klawiaturze.
  • Obawa 5: To już koniec. Oto minimalna wersja, chociaż polecam dodanie, operator==a operator!=także, jeśli nie chcesz polegać wyłącznie na Equals:

.

public struct Id<T>: {
    private readonly int _value ;
    public Id(int value) { _value = value; }
    public static explicit operator int(Id<T> id) { return id._value; }
}
Piotr
źródło
10

Innym sposobem myślenia na ten temat jest użycie bezpieczeństwa typu języka.

Możesz zaimplementować metodę taką jak:

Find(FirstName name);

Gdzie FirstName jest prostym obiektem, który otacza ciąg zawierający imię i oznacza, że ​​nie może być pomyłki co do tego, co robi metoda, ani argumentów, z którymi jest wywoływana.

3DPrintScanner
źródło
4
Nie jestem pewien, jaka jest twoja odpowiedź na pytanie PO. Czy zalecamy używanie nazwy takiej jak „Znajdź”, polegając na typie argumentu? Czy też zaleca się używanie takich nazw tylko wtedy, gdy istnieje wyraźny typ argumentów i użycie bardziej wyraźnej nazwy, takiej jak „FindById” w innym miejscu? A może zalecasz wprowadzenie jawnych typów, aby uczynić nazwę bardziej znaną „Znajdź” bardziej wykonalną?
Doc Brown
2
@DocBrown Myślę, że to drugie i podoba mi się. W rzeczywistości jest podobny do odpowiedzi Petera, iiuc. Uzasadnienie, jakie rozumiem, jest dwojakie: (1) Z argumentu wynika jasno, co robi funkcja; (2) Nie możesz popełniać błędów takich jak. Co string name = "Smith"; findById(name);jest możliwe, jeśli używasz ogólnych typów nieopisowych.
Peter - Przywróć Monikę
5
Podczas gdy ogólnie jestem fanem bezpieczeństwa typu kompilacji, bądź ostrożny z typami opakowań. Wprowadzenie klas opakowań dla bezpieczeństwa typu może czasami poważnie skomplikować interfejs API, jeśli zostanie to wykonane w nadmiarze. np. cały problem „artysty dawniej znany jako int”, który ma winapi; na dłuższą metę powiedziałbym, że większość ludzi patrzy na niekończące się DWORD LPCSTRklony itp. i myśli „to int / string / itp.”, dochodzi do momentu, w którym spędzasz więcej czasu na rozwijaniu swoich narzędzi niż na projektowaniu kodu .
jrh
1
@jrh True. Mój test lakmusowy na wprowadzenie typów „nominalnych” (różniących się tylko nazwą) jest wtedy, gdy wspólne funkcje / metody nie mają żadnego sensu w naszym przypadku użycia, np. intS są często sumowane, mnożone itp., Co nie ma znaczenia dla identyfikatorów; tak bym zrobić IDodrębny od int. Może to uprościć interfejs API, zawężając to, co możemy zrobić, biorąc pod uwagę wartość (np. Jeśli mamy ID, będzie działać tylko z find, a nie np. ageLub highscore). I odwrotnie, jeśli przekonamy się, że dużo konwertujemy lub piszemy tę samą funkcję / metodę (np. find) Dla wielu typów, to znak, że nasze rozróżnienia są zbyt dobre
Warbo
1

Głosuję za wyraźną deklaracją, taką jak FindByID .... Oprogramowanie powinno być tworzone na potrzeby zmian. Powinien być otwarty i zamknięty (SOLID). Tak więc klasa jest otwarta na dodanie podobnej metody find, np. FindByName .. itd.

Ale FindByID jest zamknięty, a jego implementacja jest testowana jednostkowo.

Nie będę sugerować metod z predykatami, są one dobre na poziomie ogólnym. Co zrobić, jeśli na podstawie pola (ByID) masz pełną inną metodologię.

BrightFuture1029
źródło
0

Dziwi mnie, że nikt nie sugerował użycia predykatu, takiego jak:

User Find(Predicate<User> predicate)

Dzięki takiemu podejściu nie tylko zmniejszasz powierzchnię swojego API, ale także dajesz większą kontrolę użytkownikowi, który go używa.

Jeśli to nie wystarczy, zawsze możesz rozszerzyć go na swoje potrzeby.

Aybe
źródło
1
Problem polega na tym, że jest mniej wydajny ze względu na fakt, że nie może korzystać z takich rzeczy jak indeksy.
Solomon Ucko,