Masz na koncie 0.30000000004 zł, czyli problemy z fintechem
Jeśli piszesz oprogramowanie do zarządzania pieniędzmi, to współczuję, bo naszej branży zdarza się nie przestrzegać bardzo wysokich standardów dotyczących niezawodności budowanych przez nas rzeczy. Podczas gdy winda jest praktycznie niezdolna do awarii, to w IT jest jak zwykle. W rezultacie oprogramowanie, które zarządza pieniędzmi, często jest zbudowane na niestabilnych fundamentach.
Spróbujemy tu stworzyć spis częstych problemów pojawiających się w oprogramowaniu finansowym. Będziesz mógł sprawdzić, czy Twój system jest wolny od takich problemów.
Najbardziej optymalny punkt na świecie
Błędy arytmetyczne
Liczba zmiennoprzecinkowa
Zacznijmy od oczywistego: pierwszym problemem, z którym prawdopodobnie spotykałem się najczęściej, jest użycie liczb zmiennoprzecinkowych do manipulacji i przechowywania informacji o pieniądzach. Dlaczego to aż taki problem? Cóż, jeśli otworzysz konsolę JavaScript i wykonasz następujące czynności:
"Your balance is $" + (0.1 + 0.2)
To otrzymasz Your balance is $0.30000000004
.
Ponieważ z definicji nie możemy mieć dokładnej reprezentacji 0,1 lub jakiejkolwiek ujemnej potęgi dziesiątki w systemie dwójkowym, nie możemy używać liczb zmiennoprzecinkowych do dokładnego przechowywania wartości pieniężnych i operowania nimi bez konieczności zaokrąglania.
Najprostszym sposobem na uniknięcie tego problemu jest użycie innego typu danych niż liczby zmiennoprzecinkowe. Częstym rozwiązaniem jest użycie liczb całkowitych reprezentujących kwotę pieniężną w jej najmniejszej jednostce (na przykład 100 centów zamiast 1,00 USD).
Dziesiętne typy danych również dobrze działają, pod warunkiem, że Twój język je obsługuje (Java ma BigDecimal, JavaScript nie ma żadnych).
Alokacja
Drugim najczęściej spotykanym problemem arytmetycznym jest użycie dzielenia i zaokrąglania zamiast alokacji. Na przykład, Twoja aplikacja do zgłaszania przejazdów ma niewielką opłatę rezerwacyjną w wysokości 0,99 USD, którą dzielisz równo między Ciebie i kierowcę. Czy należy używać dzielenia i zaokrąglania?
= round(0.99 / 2) + round(0.99 /2)
= round(0.495) + round(0.495)
= 0.5 + 0.5
= 1
Zakładając, że Twoja funkcja zaokrągla do najbliższej liczby całkowitej o połowę w górę, co jest typową wartością domyślną, to do systemu zostanie wprowadzony dodatkowy cent, ponieważ 0,99 USD staje się 1 USD. Powtarzaj tę operację za każdym razem, a system szybko będzie miał błędne dane.
Aby rozwiązać ten problem, musimy przestać brać pod uwagę zaokrąglanie i trzeba zadać sobie następujące pytanie: czy kierowca platformy otrzyma nierozdzielny cent? Nasze rozwiązanie to alokacja i jest ono często implementowane przez biblioteki manipulujące pieniędzmi.
Problemy ze współbieżnością
Młody programista odkrywający potęgę wyścigu
W 2015 roku pewien badacz bezpieczeństwa znalazł sposób na stworzenie nieograniczonych pieniędzy na kartach podarunkowych Starbucks, wykorzystując coś, co określamy mianem wyścigu. Ten rodzaj błędu nie jest bynajmniej jednorazowy, ale jest często spotykany w domorosłych systemach finansowych.
Sytuacja wyścigu zdarza się za każdym razem, gdy kod, który nie został zaprojektowany do współbieżnego działania, musi działać w ten sposób. Podręcznikowym przykładem może być API portfela, które pozwala Ci pobrać więcej pieniędzy, niż są w rzeczywistości dostępne, pod warunkiem, że wiele wypłat jest wykonywanych w tym samym czasie.
Aby przetestować swoje API pod kątem tego problemu, potrzebujesz curl
z seq
i xargs
. Oto przykład:
#!/bin/bashseq 1 10 | \
xargs -n1 -P3 \
curl http://localhost/api/withdraw \
-X POST \
-H 'Content-Type: application/json' \
-d '{"from": "1234", "amount": 100}'
Dzięki temu prostemu skryptowi bash będziemy mieli 10 wywołań do naszego punktu końcowego z maksymalnie 3 zadaniami uruchomionymi równolegle. Jeśli test ten wykracza poza to, co powinno być możliwe w twoim API, to masz wyścig.
Dobra wiadomość jest taka, że w większości przypadków problem ten można łatwo rozwiązać dzięki użyciu blokady. Używając blokady mutex i serializacji przetwarzania wywołań, upewniasz się, że Twój stan, który sprawdza, czy saldo jest wystarczające, czy nie, nie zostanie zmieniony przez inny wątek.
Zdarzają się niestety przypadki, w których wprowadzenie blokady może nie być takie łatwe, ponieważ ich złożoność zacznie tworzyć problemy i trzeba będzie pójść na kompromis.
Przypadki te mogą obejmować aplikacje o dużej przepustowości lub ograniczonym dostępie do sieci, z których wszystkie wykraczają poza zakres tego artykułu.
Źle obsłużone awarie zewnętrzne
Wartości null
Gdy usługa zdalna nie dostarcza wartości używanej w dalszej części systemu, można albo zatrzymać system, albo użyć wcześniej uzyskanej wartości, jeśli to możliwe. To, czego nie powinno się robić, to zapisywanie błędnych wartości i kontynuacja operacji w taki sposób, jakby nic się nie stało.
Przykładem tego jest przewalutowanie. Jeśli współczynnik konwersji nie zostanie pobrany ze zdalnej usługi internetowej, ostatnią rzeczą do zrobienia byłoby zapisanie wartości null, a następnie użycie tej wartości do obliczenia cen.
Jeśli mamy do czynienia z jakąś linią lotniczą, dzięki takiemu błędowi bilety lotnicze można by było kupić za 0 USD.
Częściowe przetwarzanie
Inny rodzaj awarii wynika z częściowego przetwarzania. Powiedzmy, że inicjujesz przelew w swoim API banku, a połączenie TCP zostaje przerwane w jego trakcie.
Jeśli ponownie zainicjujesz wywołanie, zaryzykujesz wykonanie przelewu dwukrotnie, w zależności od tego, czy bank otrzymał żądanie, czy nie.
? Jednym z rozwiązań tego problemu jest sprawdzenie, czy przesyłanie nastąpiło przed ponowną próbą. Chociaż nie jest to uniwersalne, innym prostszym rozwiązaniem jest użycie idempotency keys, o ile API, które wywołujesz, implementuje taki mechanizm.
Ciche awarie
Nieudane przechwycenie
Czasami najgorsze historie dotyczą nie tego, jak stało się coś strasznego, a raczej tego, jeśli nie wydarzyło się coś, co absolutnie musi się wydarzyć.
W oprogramowaniu finansowym rzeczy, które się nie zdarzają, mogą czasami skutkować utratą pieniędzy. Przykładem tego jest workflow autoryzacji i przechwytywania, który można znaleźć w API większości dostawców usług płatności przy użyciu karty. Gdy Twój kod nie przechwyci autoryzacji i zrealizuje sprzedaż, pieniądze zostaną utracone na zawsze.
? Nie ma gotowego rozwiązania tego problemu, ponieważ rozwiązanie w dużej mierze zależy od specyfiki Twojego systemu. Przyzwoite podejście, które zalecamy czerpie inspirację z programowania kontraktowego i ma poboczny system do ciągłej weryfikacji, czy to, co zrobił główny system, spełnia nasze oczekiwania.
Co dalej?
Tworzenie dobrze działającego oprogramowania jest trudne. Stworzenie dobrze działającego oprogramowania finansowego jest absolutnie koniecznie. Spis ten bądź bestiariusz potworów żywiących się złotem, został zbudowany z nadzieją, że pomoże Ci on sprawdzić obecny system i uniknąć katastrof, którym można było zapobiec.
Wszystkie te problemy były albo problemami, których doświadczyliśmy na własnej skórze podczas budowania Selency, albo dotknęły one członków naszej społeczności: specjalne podziękowania dla AssoConnect, Yescapa i Meet in Class za podzielenie się cennymi spostrzeżeniami na ten temat.
Oryginał tekstu w języku angielskim możesz przeczytać tutaj.