Projektowanie metod związanych z bazą danych, które lepiej zwrócić: prawda / fałsz czy wpływ na wiersz?

10

Mam kilka metod, które wykonują pewne zmiany danych w bazie danych (wstawianie, aktualizowanie i usuwanie). ORM używam powrót wiersz dotknięte wartości int dla tych rodzaju metody. Co powinienem zwrócić po „mojej metodzie”, aby wskazać stan powodzenia / niepowodzenia operacji?

Rozważ kod, który zwraca int:

A.1

public int myLowerLevelMethod(int id) {
    ...
    int affectedRows = myOrm.deleteById(id)
    ...

    return affectedRows;
}

Następnie użycie:

A.2

public void myOtherMethod() {
    ...
    int affectedRows = myLowerLevelMethod(id)

    if(affectedRows > 0) {
        // Success
    } else {
        // Fail
    }
}

Porównaj z użyciem boolean:

B.1

public boolean myLowerLevelMethod(int id) {
    ...
    int affectedRows = myOrm.deleteById(id)
    ...

    return affectedRows > 0;
}

Następnie użycie:

B.2

public void myOtherMethod() {
    ...
    boolean isSuccess = myLowerLevelMethod(id)

    if(isSuccess) {
        // Success
    } else {
        // Fail
    }
}

Który (A lub B) jest lepszy? A może zalety / wady każdego z nich?

Hoang Tran
źródło
W twoim „A.2”. Jeśli dotyczy to zerowych wierszy, dlaczego jest to awaria, jeśli trzeba wpłynąć na zerowe rzędy? Innymi słowy, jeśli nie ma błędu bazy danych, dlaczego jest to błąd?
Jaydee
5
Czy istnieje znacząca różnica między „nieudanym” a „zmienionym wierszem zerowym”? Na przykład podczas usuwania wszystkich zamówień klienta istnieje różnica między „klientem nie ma” a „klientem nie ma zamówień”.
Głowonóg
Czy uważasz, że usunięcie z zerowymi wierszami jest nieoczekiwane? W takim razie rzuć.
usr
@Arian Myślę, że to dla mnie prawdziwe pytanie. Myślę, że wybrałem B, ponieważ z A, mój kod zawiera teraz sprawdzanie 0 w niektórych miejscach i na -1, w innych
Hoang Tran

Odpowiedzi:

18

Inną opcją jest zwrócenie obiektu wynikowego zamiast podstawowych typów. Na przykład:

OperationResult deleteResult = myOrm.deleteById(id);

if (deleteResult.isSuccess()) {
    // ....
}

Dzięki temu, jeśli z jakiegoś powodu musisz zwrócić liczbę dotkniętych wierszy, możesz po prostu dodać metodę w OperationResult:

if (deleteResult.isSuccess()) {
    System.out.println("rows deleted: " + deleteResult.rowsAffected() );
}

Ten projekt pozwala Twojemu systemowi rozwijać się i zawierać nowe funkcje (znajomość dotkniętych wierszy) bez modyfikowania istniejącego kodu.

AlfredoCasado
źródło
2
+1 „Ten projekt pozwala Twojemu systemowi rozwijać się i zawierać nową funkcjonalność (znajomość dotkniętych wierszy) bez modyfikowania istniejącego kodu” To jest właściwy sposób myślenia o tej kategorii pytań.
InformedA
Zrobiłem podobnie w moim starym projekcie. Mam opakowanie obiektu żądania i wyniku. Oba wykorzystują kompozycje, aby podać więcej szczegółów. i oba zawierają również podstawowe dane. W takim przypadku obiekt Wynik ma kod statusu i pole msg.
InformedA
Zwłaszcza w przypadku systemu wykorzystującego ORM nazwałbym to najlepszą praktyką. Wspomniany ORM może już zawierać takie typy obiektów wynikowych!
Brian S
Patrząc na przykład OP, wszystko, co mogę wymyślić, to to, że deleteById zwróci 2 w pewnym momencie :) więc zdecydowanie typ niestandardowy, tak.
deadsven
Lubię to podejście. Myślę, że jest to nieblokujące, obejmuje oba moje podejścia i można je modyfikować / rozszerzać (w razie potrzeby w przyszłości). Jedynym minusem, jaki mogę wymyślić, jest nieco więcej kodu, szczególnie gdy jest on w środku mojego projektu. Oznaczę to jako odpowiedź. Dziękuję Ci.
Hoang Tran
12

Zwrócenie liczby dotkniętych wierszy jest lepsze, ponieważ daje dodatkowe informacje o przebiegu operacji.

Żaden programista nie będzie cię winił, ponieważ musi napisać to, aby sprawdzić, czy dostały jakieś zmiany podczas operacji:

if(affectedRows > 0) {
    // success
} else {
    // fail
}

ale będą cię winić, gdy będą musieli poznać liczbę dotkniętych rzędów i zdają sobie sprawę, że nie ma metody na uzyskanie tej liczby.

BTW: jeśli przez „niepowodzenie” rozumiesz błąd kwerendy składniowej (w którym to przypadku liczba dotkniętych wierszy wynosi oczywiście 0), wówczas rzucenie wyjątku byłoby bardziej odpowiednie.

Konstabl
źródło
4
-1 z powodu powodu, który podałeś. Udzielenie dodatkowych informacji nie zawsze jest dobrym pomysłem. Często lepszy projekt prowadziłby do przekazania dzwoniącemu tylko tego, czego potrzebuje i nic więcej.
Arseni Mourzenko
1
@MainMa nic nie stoi na przeszkodzie, aby stworzyć przeciążone metody. Dodatkowo w językach ojczystych (na przykład rodzina C) liczba wierszy może być używana bezpośrednio w logice (0 to fałsz, wszystko inne jest prawdą).
PTwr
@PTwr: aby poradzić sobie z faktem, że metoda zwraca zbyt wiele informacji, sugerujesz utworzenie przeciążenia? To nie wydaje się właściwe. Jeśli chodzi o „zero to fałsz, niezerowe to prawda”, nie o to chodzi w moim komentarzu i jest to zła praktyka w niektórych językach.
Arseni Mourzenko
1
Prawdopodobnie jestem jednym z zepsutych programistów, ponieważ nie sądzę, aby stosowanie jakichkolwiek sztuczek można uznać za dobrą praktykę. W rzeczywistości, ilekroć mam do czynienia z czyimś kodem, jestem wdzięczny każdemu, kto go napisał i nie zastosował żadnych sztuczek.
proskor
4
Zawsze powinieneś tutaj zwrócić liczbę dotkniętych wierszy !!! Skoro nie możesz wiedzieć, czy liczba wierszy = 0 to błąd, czy liczba wierszy = 3 to sukces? Jeśli ktoś chce wstawić 3 wiersze i wstawić tylko 2, zwrócisz wartość true, ale to nie jest poprawne! A jeśli ktoś chce update t set category = 'default' where category IS NULLnawet 0 wierszy, których dotyczy zmiana, byłby sukcesem, ponieważ teraz nie ma elementu bez kategorii, nawet jeśli nie wpłynęło to na żadne wiersze!
Falco
10

Nie poleciłbym żadnego z nich. Zamiast tego nie zwracaj nic (void) po sukcesie i rzucaj wyjątek w przypadku niepowodzenia.

Jest to dokładnie z tego samego powodu, dla którego zdecydowałem się ogłosić niektórych członków klasy prywatnymi. Ułatwia także korzystanie z funkcji. Bardziej operacyjny kontekst nie zawsze oznacza lepszy, ale z pewnością oznacza bardziej złożony. Im mniej obiecujesz, tym bardziej abstrahujesz, tym łatwiej jest klientowi zrozumieć i masz więcej swobody w wyborze sposobu jego realizacji.

Pytanie brzmi, jak wskazać sukces / błąd. W takim przypadku wystarczy zasygnalizować awarię, zgłaszając wyjątek i niczego nie zwracając po sukcesie. Dlaczego muszę podać więcej niż potrzebuje użytkownik?

Awarie / wyjątkowe sytuacje mogą się zdarzyć i wtedy będziesz musiał sobie z nimi poradzić. Niezależnie od tego, czy używasz metody try / catch, czy sprawdzasz kody zwrotne, jest to kwestia stylu / osobistych preferencji. Ideami try / catch są: oddzielenie normalnego przepływu od wyjątkowego przepływu i pozwolenie, by wyjątki pojawiały się aż do warstwy, w której można sobie z nimi poradzić w najbardziej odpowiedni sposób. Tak więc, jak już wielu zauważyło, zależy to od tego, czy porażka jest naprawdę wyjątkowa, czy nie.

proskor
źródło
4
-1 Dlaczego nic nie zwracasz tam, gdzie mógłbyś coś zwrócić, bez negatywnych skutków ubocznych, co zapewnia dodatkowy kontekst operacyjny?
FreeAsInBeer
7
Z tego samego powodu zdecydowałem się ogłosić niektórych członków klasy prywatnymi. Ułatwia także korzystanie z funkcji. Bardziej operacyjny kontekst nie zawsze oznacza lepszy, ale z pewnością oznacza bardziej złożony. Im mniej obiecujesz, tym bardziej abstrahujesz, tym łatwiej jest klientowi zrozumieć i masz więcej swobody w wyborze sposobu jego realizacji.
proskor
1
Jakie dane tak naprawdę potrzebuje użytkownik? Pytanie brzmi, jak wskazać sukces / błąd. W takim przypadku wystarczy zasygnalizować awarię, zgłaszając wyjątek i niczego nie zwracając po sukcesie. Dlaczego muszę podać więcej niż potrzebuje użytkownik?
proskor
4
@proskor: Wyjątki dotyczą wyjątkowych przypadków. „Niepowodzenie” w tym scenariuszu może być oczekiwanym rezultatem. Z całą pewnością przedstaw to jako potencjalną alternatywę, ale nie ma tutaj wystarczających informacji, aby sformułować zalecenie.
Nick Barnes
1
-1 Wyjątki nie powinny być częścią normalnego przebiegu programu. Nie jest jasne, co oznacza „awaria” w kontekście błędu, ale wyjątek w kontekście wywołania bazy danych powinien wynikać z wyjątku występującego w bazie danych. Wpływ na zero wierszy nie powinien być wyjątkiem. Zakłócone zapytanie, którego nie można przeanalizować, odwołuje się do nieistniejącej tabeli itp. Byłoby wyjątkiem, ponieważ silnik bazy danych dusiłby się na nim i wyrzucał.
2

„Czy to jest lepsze niż to?” nie jest użytecznym pytaniem, gdy dwie alternatywy nie robią tego samego.

Jeśli potrzebujesz znać liczbę wierszy, których dotyczy problem, musisz użyć wersji A. Jeśli nie musisz, możesz użyć wersji B - ale wszelkie korzyści, które możesz uzyskać w postaci mniejszego wysiłku pisania kodu, już minęły zadałeś sobie trud, aby opublikować obie wersje na forum online!

Chodzi mi o to, które rozwiązanie jest lepsze, zależy całkowicie od twoich wymagań dotyczących tej aplikacji i znasz te okoliczności znacznie lepiej niż my. Nie ma ogólno branżowej, bezpiecznej w użyciu, najlepszej praktyki, nie wymagającej zwolnienia z pracy, która byłaby ogólnie lepsza ; musisz o tym pomyśleć. A dla decyzji tak łatwej do zrewidowania jak ta, nie musisz też spędzać tyle czasu na myśleniu.

Kilian Foth
źródło
Zgadzam się, że zależy to w dużej mierze od wymagań aplikacji. Wydaje się jednak, że taka sytuacja nie jest bardzo wyjątkowa i nie szukam srebrnej kuli, ale po prostu doświadczenia innych w radzeniu sobie z tą samą / podobną rzeczą (być może pytanie jest nieco mylące, lubię sugestie inne niż A / B)
Hoang Tran
1

Dwie najważniejsze zasady projektowania oprogramowania, które można utrzymać, to KISS i YAGNI .

  • KISS : Niech to będzie proste, głupku
  • YAGNI : Nie będziesz go potrzebował

Jest to prawie nigdy nie jest dobry pomysł, aby umieścić w logice nie od razu trzeba teraz . Pośród wielu innych osób Jeff Atwood (współzałożyciel StackExchange) napisał o tym , i z mojego doświadczenia on i inni zwolennicy tych koncepcji mają całkowitą rację.

Każda złożoność, którą dodasz do programu, wiąże się z kosztami, płaconymi przez długi okres. Program staje się trudniejszy do odczytania, bardziej skomplikowany do zmiany i łatwiejsze do wkradania się błędów. Nie wpadnij w pułapkę dodawania rzeczy „na wszelki wypadek”. To fałszywe poczucie bezpieczeństwa.

Rzadko kiedy pierwszy raz dostaniesz odpowiedni kod. Zmiany są nieuniknione; dodanie logiki spekulatywnej, aby obronnie przygotować się na nieznane przyszłe nieprzewidziane zdarzenia , tak naprawdę nie ochroni Cię przed koniecznością zmiany kodu w przyszłości, gdy okaże się, że przyszłość jest inna niż się spodziewałeś. Utrzymanie niepotrzebnej / warunkowej logiki jest bardziej problemem związanym z utrzymaniem niż późniejszym refaktoryzowaniem w celu dodania brakującej funkcjonalności.

Zatem, ponieważ wydaje się, że wszystko, czego na razie potrzebuje Twój program, to wiedzieć, czy operacja się powiodła, czy nie, zaproponowane rozwiązanie B (zwrócenie pojedynczej wartości logicznej) jest poprawnym podejściem. Zawsze możesz go refaktoryzować później, jeśli zmieni się wymaganie. To rozwiązanie jest najprostsze i ma najniższą złożoność (KISS) i robi dokładnie to, czego potrzebujesz i nic więcej (YAGNI).

Ben Lee
źródło
-1

Całe wiersze lub status błędu

Rozważ zwrócenie całych wierszy, przynajmniej jako opcję wykonania. We wstawkach DB może być konieczne sprawdzenie danych, które zostały wstawione - ponieważ często będą się różnić od tych, które wysłałeś do DB; typowe przykłady obejmują automatycznie generowane identyfikatory wierszy (których aplikacja prawdopodobnie będzie natychmiast potrzebować), wartości domyślne określone przez DB oraz wyniki wyzwalaczy, jeśli ich użyjesz.

Z drugiej strony, jeśli nie trzeba zwróconych danych, to również nie potrzebują skażoną liczba wierszy, ponieważ nie jest pomocne w wyniku 0. Jeśli są błędy, to trzeba wrócić jaką stanowi wystąpił błąd, w sposób zgodny z zasadami obsługi błędów projektu (wyjątki, numeryczne kody błędów, cokolwiek); ale istnieją prawidłowe zapytania, które poprawnie wpłyną na 0 wierszy (tj. „usuń wszystkie wygasłe zamówienia”, jeśli w rzeczywistości ich nie ma).

Piotr jest
źródło