Jak zauważono w komentarzach @ benjamin-gruenbaum, nazywa się to pułapką logiczną:
Powiedz, że mam taką funkcję
UpdateRow(var item, bool externalCall);
a w moim kontrolerze ta wartość externalCall
zawsze będzie PRAWDA. Jak najlepiej wywołać tę funkcję? Zwykle piszę
UpdateRow(item, true);
Ale pytam samego siebie, czy powinienem zadeklarować wartość logiczną, aby wskazać, co oznacza ta „prawdziwa” wartość? Możesz to wiedzieć, patrząc na deklarację funkcji, ale jest to oczywiście szybsze i wyraźniejsze, jeśli zobaczysz coś takiego
bool externalCall = true;
UpdateRow(item, externalCall);
PD: Nie jestem pewien, czy to pytanie naprawdę pasuje tutaj, jeśli nie, gdzie mogę uzyskać więcej informacji na ten temat?
PD2: Nie oznaczyłem żadnego języka, ponieważ myślałem, że to bardzo ogólny problem. W każdym razie pracuję z c # i zaakceptowana odpowiedź działa dla c #
data CallType = ExternalCall | InternalCall
na przykład w haskell.Odpowiedzi:
Nie zawsze jest idealne rozwiązanie, ale masz wiele alternatyw do wyboru:
Użyj nazwanych argumentów , jeśli są dostępne w Twoim języku. Działa to bardzo dobrze i nie ma szczególnych wad. W niektórych językach dowolny argument można przekazać jako nazwany argument, np.
updateRow(item, externalCall: true)
( C # ) lubupdate_row(item, external_call=True)
(Python).Twoja sugestia użycia oddzielnej zmiennej jest jednym ze sposobów symulacji nazwanych argumentów, ale nie ma związanych z tym korzyści bezpieczeństwa (nie ma gwarancji, że użyłeś poprawnej nazwy zmiennej dla tego argumentu).
Używaj różnych funkcji dla swojego publicznego interfejsu, z lepszymi nazwami. Jest to inny sposób symulowania nazwanych parametrów poprzez umieszczenie w nazwie wartości parametrów paremeter.
Jest to bardzo czytelne, ale prowadzi do wielu pytań dla Ciebie, który pisze te funkcje. Nie może również dobrze poradzić sobie z eksplozją kombinatoryczną, gdy istnieje wiele argumentów boolowskich. Istotną wadą jest to, że klienci nie mogą ustawić tej wartości dynamicznie, ale muszą użyć if / else, aby wywołać poprawną funkcję.
Użyj wyliczenia . Problem z booleanami polega na tym, że nazywane są „prawdą” i „fałszem”. Zamiast tego wprowadź typ o lepszych nazwach (np
enum CallType { INTERNAL, EXTERNAL }
.). Dodatkową korzyścią jest zwiększenie bezpieczeństwa typów programu (jeśli Twój język implementuje wyliczenia jako odrębne typy). Wadą wyliczeń jest to, że dodają typ do publicznie widocznego interfejsu API. W przypadku funkcji czysto wewnętrznych nie ma to znaczenia, a wyliczenia nie mają znaczących wad.W językach bez wyliczeń czasami używane są krótkie ciągi znaków . Działa to, a może nawet lepiej niż surowe booleany, ale jest bardzo podatne na literówki. Funkcja powinna następnie natychmiast stwierdzić, że argument pasuje do zestawu możliwych wartości.
Żadne z tych rozwiązań nie ma nadmiernego wpływu na wydajność. Nazwane parametry i wyliczenia można całkowicie rozwiązać w czasie kompilacji (dla skompilowanego języka). Korzystanie z ciągów znaków może wiązać się z porównywaniem ciągów, ale ich koszt jest znikomy w przypadku małych literałów ciągów i większości rodzajów aplikacji.
źródło
Prawidłowym rozwiązaniem jest robienie tego, co sugerujesz, ale zapakuj je w minifasadę:
Czytelność przebija mikrooptymalizację. Możesz sobie pozwolić na dodatkowe wywołanie funkcji, na pewno lepiej niż na wysiłek programisty polegający na konieczności sprawdzenia semantyki flagi logicznej nawet raz.
źródło
true
robi). Byłoby opłacalne, nawet gdyby to kosztują tyle czasu procesora, ile oszczędza czas programisty, ponieważ czas procesora jest znacznie tańszy.UpdateRow(item, true /*external call*/);
byłoby czystsze, w językach, które pozwalają na tę składnię komentarzy. Nadymanie kodu za pomocą dodatkowej funkcji tylko po to, by uniknąć pisania komentarza, nie wydaje się tego warte w prostym przypadku. Może gdyby było wiele innych argumentów do tej funkcji i / lub trochę zaśmieconego + podstępnego otaczającego kodu, zacząłby apelować bardziej. Ale myślę, że jeśli debugujesz, skończysz sprawdzanie funkcji opakowania, aby zobaczyć, co ona robi, i musisz pamiętać, które funkcje są cienkimi opakowaniami wokół interfejsu API biblioteki, a które mają pewną logikę.Dlaczego masz taką funkcję?
W jakich okolicznościach wywołałbyś to z argumentem externalCall ustawionym na inne wartości?
Jeśli jedna pochodzi, powiedzmy, z zewnętrznej aplikacji klienckiej, a druga znajduje się w tym samym programie (tj. Różnych modułach kodu), to argumentowałbym, że powinieneś mieć dwie różne metody, jedną dla każdego przypadku, być może nawet zdefiniowaną odrębnie Interfejsy
Jeśli jednak wykonujesz wywołanie na podstawie pewnej bazy danych pobranej ze źródła innego niż program (powiedzmy, plik konfiguracyjny lub odczyt bazy danych), wówczas metoda przekazywania wartości logicznej byłaby bardziej sensowna.
źródło
Chociaż zgadzam się, że idealnym rozwiązaniem jest użycie funkcji językowej w celu wymuszenia zarówno czytelności, jak i bezpieczeństwa wartości, możesz również zdecydować się na praktyczne podejście: komentarze w czasie rozmowy. Lubić:
lub:
lub (słusznie, jak sugeruje Frax):
źródło
UpdateRow(item, /* externalCall */ true )
. Komentarz w pełnym zdaniu jest znacznie trudniejszy do przeanalizowania, w rzeczywistości jest to głównie szum (szczególnie drugi wariant, który również jest bardzo luźno powiązany z argumentem).Możesz „nazwać” swoje boole. Poniżej znajduje się przykład języka OO (gdzie można go wyrazić w klasie zapewniającej
UpdateRow()
), jednak samą koncepcję można zastosować w dowolnym języku:i na stronie połączenia:
Uważam, że pozycja nr 1 jest lepsza, ale nie zmusza użytkownika do korzystania z nowych zmiennych podczas korzystania z funkcji. Jeśli bezpieczeństwo typu jest dla Ciebie ważne, możesz utworzyć
enum
typ iUpdateRow()
zaakceptować to zamiastbool
:UpdateRow(var item, ExternalCallAvailability ec);
Możesz zmodyfikować nazwę funkcji, aby lepiej odzwierciedlała znaczenie
bool
parametru. Nie jestem pewien, ale może:UpdateRowWithExternalCall(var item, bool externalCall)
źródło
externalCall=false
, jej nazwa nie ma absolutnie żadnego sensu. Reszta twojej odpowiedzi jest dobra.UpdateRowWithUsuallyExternalButPossiblyInternalCall
.Inną opcją, której jeszcze tu nie czytałem, jest: Użyj nowoczesnego IDE.
Na przykład IntelliJ IDEA drukuje nazwę zmiennej w metodzie, którą wywołujesz, jeśli przekazujesz literał taki jak
true
lubnull
lubusername + “@company.com
. Odbywa się to małą czcionką, dzięki czemu nie zajmuje dużo miejsca na ekranie i wygląda bardzo różnie od rzeczywistego kodu.Nadal nie twierdzę, że dobrym pomysłem jest wrzucanie boolczyków wszędzie. Argument mówiący, że czytasz kod znacznie częściej niż piszesz, jest często bardzo silny, ale w tym konkretnym przypadku w dużej mierze zależy on od technologii, której używasz (i twoi koledzy!) Do wspierania twojej pracy. Z IDE jest to o wiele mniejszy problem niż na przykład z vimem.
Zmodyfikowany przykład części mojej konfiguracji testowej:
źródło
2 dni i nikt nie wspomniał o polimorfizmie?
Kiedy jestem klientem, który chce zaktualizować wiersz o element, nie chcę myśleć o nazwie bazy danych, adresie URL mikro-usługi, protokole używanym do komunikacji ani o tym, czy połączenie zewnętrzne jest trzeba będzie użyć, aby tak się stało. Przestańcie narzucać mi te szczegóły. Po prostu weź mój przedmiot i idź już gdzieś zaktualizować wiersz.
Zrób to, a stanie się to częścią problemu konstrukcyjnego. Można to rozwiązać na wiele sposobów. Tu jest jeden:
Jeśli patrzysz na to i myślisz „Ale mój język wymienia argumenty, nie potrzebuję tego”. Dobrze, świetnie, idź ich użyć. Po prostu trzymaj ten nonsens z dala od dzwoniącego klienta, który nie powinien nawet wiedzieć, z czym rozmawia.
Należy zauważyć, że jest to więcej niż problem semantyczny. To także problem projektowy. Podaj wartość logiczną, a będziesz musiał użyć rozgałęzienia, aby sobie z tym poradzić. Niezbyt zorientowany obiektowo. Masz dwa lub więcej booleanów do przejścia? Czy chcesz, aby Twój język miał wiele wysyłek? Sprawdź, co mogą zrobić kolekcje po ich zagnieżdżeniu. Odpowiednia struktura danych może znacznie ułatwić Ci życie.
źródło
Jeśli możesz zmodyfikować
updateRow
funkcję, być może uda ci się przekształcić ją w dwie funkcje. Biorąc pod uwagę, że funkcja przyjmuje parametr boolowski, podejrzewam, że wygląda to tak:To może być trochę zapachowy kod. Funkcja może mieć zupełnie inne zachowanie w zależności od ustawienia
externalCall
zmiennej, w którym to przypadku ma dwie różne obowiązki. Przekształcenie go w dwie funkcje, które mają tylko jedną odpowiedzialność, może poprawić czytelność:Teraz, gdziekolwiek wywołasz te funkcje, możesz od razu stwierdzić, czy wywoływana jest usługa zewnętrzna.
Oczywiście nie zawsze tak jest. Jest to sytuacja i kwestia gustu. Przeformułowanie funkcji, która przyjmuje parametr logiczny na dwie funkcje, jest zwykle warte rozważenia, ale nie zawsze jest najlepszym wyborem.
źródło
Jeśli UpdateRow znajduje się w kontrolowanej bazie kodu, rozważę wzorzec strategii:
Gdzie Request reprezentuje pewnego rodzaju DAO (oddzwanianie w trywialnym przypadku).
Prawdziwy przypadek:
Fałszywy przypadek:
Zwykle implementacja punktu końcowego byłaby skonfigurowana na wyższym poziomie abstrakcji i po prostu użyłbym jej tutaj. Jako przydatny darmowy efekt uboczny daje mi możliwość zaimplementowania innego sposobu dostępu do danych zdalnych, bez żadnych zmian w kodzie:
Zasadniczo taka inwersja kontroli zapewnia niższe sprzężenie między przechowywaniem danych a modelem.
źródło