← Wszystkie artykuły

Czym jest programowanie funkcyjne i dlaczego ma znaczenie?

Burncode Team 3 minuty czytania

O programowaniu funkcyjnym mówi się tak, jakby było akademickim ćwiczeniem dla teoretyków języków programowania. Szkoda, bo jego główna idea jest bardzo praktyczna: im mniej rzeczy w kodzie może się nieoczekiwanie zmienić, tym mniej błędów trzeba potem tropić. I tyle. Cała reszta to szczegóły.

Sedno: funkcje czyste

Funkcja czysta to taka, która dla tych samych danych wejściowych zawsze zwraca ten sam wynik – i przy okazji nie zmienia niczego poza sobą. Żadnej modyfikacji zmiennej globalnej, żadnego zapisu do bazy danych, żadnej zależności od pory dnia. Po prostu: dane wchodzą, wynik wychodzi, nic więcej się nie dzieje.

Porównajcie to z typową funkcją w kodzie obiektowym, która może odczytać coś z bazy danych, zmodyfikować współdzielony obiekt, zapisać log i zwrócić wartość – cztery różne rzeczy naraz, z których każda może pójść nie tak i żadna nie jest oczywista, gdy patrzy się tylko na miejsce wywołania funkcji.

Niemutowalność: dane, które się nie zmieniają

Programowanie funkcyjne mocno opiera się też na danych niemutowalnych – raz utworzonej wartości nie modyfikuje się w miejscu. Potrzebujecie zmienionej wersji? Tworzycie nową. Brzmi to rozrzutnie, dopóki nie spojrzy się na alternatywę: w kodzie, w którym dane można modyfikować z dziesięciu różnych miejsc, ustalenie, które z nich spowodowało błąd, wymaga sprawdzenia wszystkich dziesięciu. W kodzie, w którym danych po utworzeniu nie da się zmienić, cała ta kategoria błędów po prostu nie istnieje.

Dlaczego ma to znaczenie na co dzień

  • Łatwiejsze testowanie. Funkcja czysta nie wymaga żadnego przygotowania poza danymi wejściowymi – bez mockowania bazy danych i podrabiania bieżącej godziny. Wywołujecie ją i sprawdzacie wynik.
  • Łatwiejsze rozumowanie o kodzie. Można zrozumieć, co robi funkcja, czytając ją samą, bez śledzenia reszty kodu w poszukiwaniu miejsc, które mogą dotykać jej danych.
  • Bezpieczniejsza współbieżność. Ogromna część błędów współbieżności bierze się z tego, że dwa elementy modyfikują te same dane w tym samym czasie. Dane niemutowalne sprawiają, że ta klasa błędów jest strukturalnie niemożliwa.
  • Bardziej przewidywalne debugowanie. Gdy stan nie może zmienić się nieoczekiwanie, lista możliwych odpowiedzi na pytanie „dlaczego ta wartość się zmieniła” jest dużo krótsza.

Nie potrzebujecie „języka funkcyjnego”, żeby na tym skorzystać

Języki takie jak Haskell i Clojure od podstaw zbudowano wokół programowania funkcyjnego, ale żeby czerpać realne korzyści z tych idei, nie trzeba zmieniać języka. JavaScript, Python, Java i większość nowoczesnych języków pozwalają pisać funkcje czyste i traktować dane jako niemutowalne tam, gdzie ma to sens – metody tablic takie jak map, filter i reduce to programowanie funkcyjne w przebraniu, a większość programistów używa ich nieustannie, nawet o tym nie myśląc.

Praktyczny wniosek nie brzmi „przepiszcie wszystko funkcyjnie”, tylko: tam, gdzie da się uczynić funkcję czystą, a jej dane niemutowalnymi bez wykręcania projektu, warto to zrobić. Spędzicie wymiernie mniej czasu na debugowaniu stanu, który zmienił się tam, gdzie się tego nie spodziewaliście.

Wnosimy te zasady do oprogramowania na zamówienie, które budujemy, właśnie dlatego, że kod łatwiejszy do testowania i zrozumienia rzadziej psuje się na produkcji – a dla klienta ma to dużo większe znaczenie pół roku po starcie niż pierwszego dnia.