W dużym projekcie nie da się pamiętać, że gdzieś już jest funkcja licząca to samo – więc pisze się ją drugi i trzeci raz. To nie lenistwo, to koszt pamięci. Problem w tym, że kopie z czasem się rozchodzą: ta sama najlepsza próba ucznia potrafiła być inna na ekranie i w PDF-ie, a w aplikacji mobilnej ten sam egzamin pokazywał czas raz jako 12:05, raz jako 12 min 5 s. Ta sama choroba złapała niezależnie backend Django i aplikację w React Native. O tym, dlaczego duplikacja to fabryka takich rozjazdów, i jak posprzątać ją bezpiecznie: najpierw dopisać testy, potem scalać, a zielona suita ma świecić tak samo przed i po.
Trzy lata temu pokazałem na blogu klasyczny SQL Injection: formularz logowania w PHP, sklejane zapytanie i wpisane w pole username „' OR 1=1#. Wróćmy do tamtego kodu i zadajmy inne pytanie: czy dziś dałoby się go tak w ogóle napisać? Bo domyślna ścieżka współczesnego PHP i frameworków – obsługa formularza, ORM, zapytania parametryzowane z pudełka – prowadzi do kodu bezpiecznego, a żeby wstrzyknąć SQL, trzeba dziś z tej ścieżki świadomie zejść. Z uczciwym rozdziałem o tym, gdzie wstrzyknięcie wciąż jest łatwe.
Teza krąży po branży od lat: gdyby programiści porządnie testowali własny kod, testerzy byliby zbędni. Patrzę na nią z nietypowej pozycji, bo jestem programistą po studiach podyplomowych z testowania – i rozkładam ją na łopatki trzema argumentami: moje testy testują moją wyobraźnię, tester patrzy na aplikację inaczej niż ja, a testowanie to zawód z własnym warsztatem. Z uczciwym rozdziałem o tym, co się w tej tezie mimo wszystko broni.
Po serii o testowaniu czas na dowód z produkcji, policzony co do sztuki: backend Django z ponad 100 tysiącami linii kodu i 6,6 tysiąca testów, mobilka React Native z 44 tysiącami linii i ponad 1600 testami, do tego 298 testów Playwright, 7 przepływów Maestro i bramka w GitHub Actions, która nie raz zatrzymała zepsute wdrożenie przed produkcją. Z metodą liczenia, proporcjami kodu do testów i uczciwym rozdziałem o tym, czego wciąż nie pokrywam.
Testy end-to-end miały u mnie zasłużenie złą opinię: Selenium z ręcznymi sleepami i łamliwymi XPathami, Appium z konfiguracją trudniejszą niż same testy. Dlatego latami ich unikałem – i to był błąd, tylko że wtedy racjonalny. Dziś flow na webie klika Playwright, a na mobile Maestro w kilkunastu linijkach YAML-a. Co konkretnie się zmieniło, tabela stare kontra dziś i uczciwy rozdział o tym, czemu E2E wciąż jest drogie.
Skoro testować, to co dokładnie i na którym poziomie? Piramida testów opowiedziana po ludzku: testy jednostkowe w pytest, integracyjne z bazą i widokami Django, testy przepływów na samej górze. Skąd naprawdę biorą się luki w pokryciu, dlaczego sto procent pokrycia niczego nie gwarantuje – i dlaczego moje testy pilnują nie tylko kodu, ale też treści tego bloga.
Tytuł jest lekko na wyrost – testy automatyczne piszę od dawna, a z testowania oprogramowania skończyłem nawet studia podyplomowe. Ale pierwszym odruchem po napisaniu kodu przez ponad ćwierć wieku był ręczny klik: działa, wdrażam. O błędach mówiła mi produkcja – logami, użytkownikami albo wcale, gdy funkcja leżała kilka dni i nikt jej nie zgłosił. Historia trzech nakładających się epok jednego nawyku, z uczciwym rozdziałem o tym, czego test-first nie załatwia i kiedy ręczne testowanie wciąż ma sens.
0,1 + 0,2 to nie 0,3 – a to znaczy, że typu float nie wolno używać do pieniędzy. Nie chodzi o ostrożniejsze zaokrąglanie, tylko o inny typ danych. Przegląd tego, jak liczy się kwoty poprawnie: grosze jako liczby całkowite, DECIMAL w bazie, decimal w Pythonie, BigDecimal w Javie, BCMath w PHP i BigInt w JavaScripcie. Do tego zaokrąglenie bankierskie i słynny spór o VAT liczony od pozycji czy od sumy.
Widziałem rolkę: programiści COBOL-a na bruku, bo AI ich zastąpiło. Postanowiłem sprawdzić, ile w tym prawdy – i okazało się, że mem sklejono z trzech prawdziwych rzeczy w jedno wydarzenie, które się nie zdarzyło. Przy okazji: dlaczego 65-letni język wciąż liczy pieniądze świata, co naprawdę potrafią modele językowe przy starym kodzie, a gdzie obietnica wyprzedza fakty.
We wrześniu 2014 okazało się, że bash – powłoka obecna niemal na każdym serwerze uniksowym – ma błąd pozwalający na zdalne wykonanie kodu. Nie w nowej funkcji, tylko w mechanizmie działającym niezmiennie od 1992 roku, uruchamianym przez wysłużone skrypty CGI. Anatomia podatności, która pokazała, że stare i sprawdzone znaczy bezpieczne tylko do momentu, w którym ktoś każe temu zrobić coś nowego.
Studium przypadku z zawodowe.edu.pl: platforma ruszyła na początku 2025 na Bootstrapie 5, po roku miałem w planach jego usunięcie, po półtora – było zrobione. Nie przez wielki przepis, tylko szablon po szablonie, z bezpiecznym przełącznikiem i testami pilnującymi, żeby Bootstrap nie wrócił tylnymi drzwiami. Co go zastąpiło: details, dialog, :has(), natywne selecty i garść vanilla JS.
Nowy projekt i pierwsza decyzja: React czy Vue? Dla wielu to już nie decyzja, tylko odruch – zaczyna się od frameworka, zanim wiadomo, czy jest potrzebny. Kiedy framework naprawdę się broni (duży współdzielony stan, zespół, ekosystem), a kiedy wystarczy sama platforma: Web Components, moduły ES, Intl, View Transitions. Na przykładzie tego bloga – statyku prawie bez JavaScriptu.
Rozwijane menu, zakładki, akordeon, tooltip, sticky nagłówek, płynne przewijanie, pozycjonowanie dymka względem przycisku – to wszystko pisaliśmy odruchowo w JavaScripcie, bo CSS tego nie potrafił. Dziś potrafi. Przegląd rzeczy przeniesionych ze skryptu do arkusza stylów: :has(), popover, details, scroll-snap, position: sticky, container queries – z przykładami before/after i uczciwym rozdziałem o granicach.
Nie piszę o was, piszę o sobie sprzed lat. Kiedy pojawiło się PHP, trzymałem się CGI i Perla tak długo, że na PHP spojrzałem dopiero koło wydania czwartego. Ten sam odruch – nauczyłem się czegoś raz i tak będę pisał dalej – dziś każe ludziom zaczynać nowe projekty od jQuery. Przegląd tego, po co naprawdę braliśmy tę bibliotekę i co z tego jest już w gołej przeglądarce, z uczciwym rozdziałem o tym, kiedy jQuery wciąż ma sens.
Ostatnia część serii o strukturach danych. Trzy algorytmy przeszukiwania na jednej mapie: BFS liczy kroki, Dijkstra koszt, A* dokłada heurystykę i przestaje błądzić. Do tego reprezentacja grafu, DFS i sortowanie topologiczne, struktura zbiorów rozłącznych z odwrotną funkcją Ackermanna oraz odpowiedź na pytanie, dlaczego prawdziwa nawigacja nie liczy Dijkstry na żywo, tylko przelicza mapę wcześniej.
Piąta część serii o strukturach danych. Kopiec binarny zapisany w zwykłej tablicy, budowa w czasie liniowym i trzy operacje, które z niego wynikają. Po co utrzymywać częściowy porządek zamiast pełnego, jak wybrać dziesięć największych elementów bez sortowania miliona, czym jest heapq.merge przy danych większych niż pamięć i jak z kolejki priorytetowej robi się scheduler oraz ogranicznik ruchu.
Czwarta część serii o strukturach danych. Co się zmienia, gdy dane przestają mieścić się w pamięci: dlaczego drzewo binarne o wysokości 30 jest na dysku katastrofą, jak B-drzewo mieści setki kluczy w jednym węźle i schodzi do trzech poziomów przy miliardzie wierszy. Strona 16 KB w InnoDB, indeks klastrowy i wtórny, reguła lewego prefiksu w indeksie złożonym oraz pięć sytuacji, w których indeks nie pomoże.
Trzecia część serii o strukturach danych. Jak drzewo poszukiwań binarnych trzyma porządek bez przesuwania tablicy, dlaczego na danych posortowanych degeneruje się do listy i wykonuje dokładnie tyle samo porównań co sortowanie bąbelkowe, oraz jak temu zaradzić rotacjami. AVL kontra drzewa czerwono-czarne, animacja obu przypadków i odpowiedź na pytanie, po co komu drzewo, skoro jest szybszy słownik.
Druga część serii o strukturach danych. Kiedy wyszukiwanie liniowe wystarcza, kiedy opłaca się posortować dane, jak napisać wyszukiwanie binarne bez pętli nieskończonej i skąd wziął się błąd przepełnienia, który siedział w bibliotece standardowej Javy przez dziewięć lat. Do tego moduł bisect, wyszukiwanie wykładnicze i rachunek, po ilu zapytaniach sortowanie zwraca się z nawiązką.
Pierwsza część serii o strukturach danych. Jak z funkcji skrótu robi się dostęp w czasie stałym, co się dzieje przy kolizjach, czym różni się łańcuchowanie od adresowania otwartego i dlaczego słownik czasem nagle zwalnia. Kontrakt hash i eq w Pythonie, klucz, który gubi dane, koszt amortyzowany przy rozrastaniu tablicy oraz trzy różne rzeczy nazywane tym samym słowem hash: skrót do wyszukiwania, kryptograficzny i do haseł.
Tabelki złożoności czyta też atakujący. Jak kilka kilobajtów dobranego wejścia zajmuje rdzeń procesora na sekundy: kolizje w tablicach mieszających (HashDoS z 2011 roku), katastrofalny nawrót w wyrażeniach regularnych (27 minut awarii Cloudflare w 2019), pesymistyczny przypadek quicksorta, bomby dekompresyjne i encje XML. Z pomiarami w Pythonie, prawdziwymi liczbami i listą obron, które faktycznie działają.
Ostatnia część cyklu o sortowaniu: co naprawdę dzieje się po wywołaniu sorted() w Pythonie, Arrays.sort w Javie, std::sort w C++, sort() w PHP i JavaScripcie oraz sort_unstable w Ruście. Powersort od CPythona 3.11, dual-pivot quicksort dla typów prostych, stabilność wymuszona specyfikacją, sortowanie zewnętrzne, quickselect zamiast sortowania i klasyczne pułapki: sortowanie liczb jak napisów oraz niespójny komparator.
Trzecia część cyklu o algorytmach sortowania. Skąd bierze się granica n log n dla wszystkich algorytmów porównawczych, dlaczego jest nieprzekraczalna i jak sortowanie przez zliczanie, pozycyjne i kubełkowe przechodzą pod nią suchą stopą, nie porównując ani razu dwóch elementów. Implementacje w Pythonie, animacje z licznikiem porównań ustawionym na zero oraz uczciwy rachunek kosztów: pamięć, zakres kluczy i rozkład danych.
Druga część cyklu o algorytmach sortowania: sortowanie przez scalanie, szybkie, przez kopcowanie i drzewiaste, a potem hybrydy, które naprawdę pracują w bibliotekach – IntroSort z C++ i TimSort z Pythona oraz Javy. Implementacje w Pythonie, animacje, wybór pivota, przypadek pesymistyczny quicksorta na żywo i pytanie, dlaczego szybkie bije scalanie mimo identycznej złożoności.
Wyścig dziewięciu algorytmów sortowania na żywo w przeglądarce, a potem spokojny rozbiór tych najprostszych: bąbelkowego, koktajlowego, grzebieniowego, gnoma, przez wybieranie, przez wstawianie i Shella. Implementacje w Pythonie, liczniki porównań i zapisów, stabilność, sortowanie w miejscu i adaptacyjność. Oraz to, o czym podręczniki milczą: kiedy O(n kwadrat) naprawdę wygrywa.
W kieszeni nosisz babiloński kalkulator sprzed czterech tysięcy lat. Skąd wziął się system sześćdziesiątkowy i liczenie na paliczkach, czemu 60 i 12 wygrywają z 10 liczbą dzielników, jak Egipcjanie podzielili noc na 12 godzin, skąd pochodzą słowa minuta i sekunda – i dlaczego rewolucyjna Francja przegrała z 10-godzinną dobą, choć jej dziesiętny czas przeżył w uniksowym timestampie.
Ostatnia cyfra PESEL-u, NIP-u, numeru karty płatniczej i dwie cyfry w IBAN-ie nie są losowe – to sumy kontrolne oparte na arytmetyce modularnej. Jakie błędy człowiek robi najczęściej, dlaczego wagi i moduł pierwszy łapią przestawione cyfry, algorytm Luhna w 15 linijkach oraz implementacje walidacji PESEL, NIP i IBAN w Pythonie, PHP i JavaScript.
Trzecia część cyklu o czasie: komputery nie liczą dat, tylko sekundy od 1970 roku. Skąd się bierze problem roku 2038 (i kogo naprawdę dotknie), czym są sekundy przestępne i dlaczego znikną do 2035, oraz czemu strefy czasowe to baza danych IANA, a nie matematyka – z dniami, które nie istnieją, i godzinami, które zdarzają się dwa razy.
Dlaczego Wielkanoc skacze po kalendarzu między 22 marca a 25 kwietnia? Computus – najstarszy algorytm świata: reguła paschalna, cykl Metona i kościelna pełnia Księżyca, algorytmy Gaussa i Meeusa z implementacjami w Pythonie i JavaScript, wyliczanie ruchomych świąt oraz wbudowane funkcje PHP easter_date i easter_days.
Pół historii, pół kodu: dlaczego mamy lata przestępne (reguła 4/100/400), jak Europa gubiła dni przy reformie gregoriańskiej – 10 dni w 1582, 11 dni w Anglii w 1752, szwedzki 30 lutego 1712 – oraz algorytmy wiecznego kalendarza: kongruencja Zellera, metoda Sakamoto i Doomsday, z implementacjami i wbudowanymi funkcjami języków.
Przełącznik jasny/ciemny, który naprawdę szanuje ustawienia systemu (także zmieniane w trakcie sesji), i regulacja rozmiaru tekstu – w ~30 liniach czystego JS, bez FOUC i bez bibliotek. Sztuczka: w localStorage nie zapisuję koloru, tylko 'opposite'.
Nie ranking z internetu, tylko moja droga: Flask, gdy chciałem najszybciej wystawić HTML; FastAPI, gdy potrzebowałem szybkiego API z walidacją i /docs; Django, gdy chciałem użytkowników, panel i ORM z pudełka. Trzy frameworki przez pryzmat roboty, do której po nie sięgałem – z kodem i szczerą tabelą decyzyjną.
Dwa drobne easter eggi, które wstawiłem na brylka.net: ASCII-art w komentarzu HTML (dla tych, co robią view-source) i stylizowany console.log w DevTools. Jak je zrobić w Astro, żeby przetrwały minifikację – plus jelonek z ASCII na szlakjelonkow.pl.
Lighthouse dostał piątą kategorię – Przeglądanie agentowe – która ocenia, czy stronę da się obsłużyć agentom AI: drzewo dostępności, CLS, llms.txt i WebMCP. Co dokładnie sprawdza, jak brylka.net zdobywa 3/3 bez specjalnej roboty i czym jest WebMCP (navigator.modelContext).
Analiza i praktyka: czy statyczny blog (bez backendu) da radę wystawić WebMCP i czy da się ubić pełne 6/6 w Lighthouse Przeglądanie agentowe. Imperatywne navigator.modelContext.registerTool, deklaratywne adnotacje formularza (toolname/tooldescription), kliencka wyszukiwarka i szczera analiza, co naprawdę znaczy ten wynik.
jQuery, lodash, moment, Popper, jQuery UI datepicker – całe pokolenie bibliotek łatało dziury w przeglądarce. Dziś tych dziur już nie ma: CSS, HTML i natywny JavaScript przejęły to, co latami doklejaliśmy zależnościami, a miejscami CSS wyprzedził JavaScript. Przegląd z konkretnymi przykładami before/after i aktualnym stanem Baseline.
Zamiana liczb dziesiętnych na binarne – z częścią całkowitą i (rzadko porządnie tłumaczoną) częścią ułamkową. Algorytm mnożenia przez 2, kiedy ułamek jest skończony, a kiedy nieskończony, wykrywanie okresu oraz implementacje w Pythonie, JavaScript, PHP i C++. Plus most do niedokładności liczb zmiennoprzecinkowych.
Ten sam kalendarz miesięczny co w serii z jQuery, ale bez żadnych zależności: Web Component, natywne API DOM, Intl do nazw miesięcy i dni oraz fetch zamiast AJAX-a. Pokazuję, że w 2026 jQuery jest po prostu zbędne.
Czym jest GEO (Generative Engine Optimization) i plik /llms.txt z llmstxt.org. Jak wygenerować go automatycznie z treści w Astro oraz wzmocnić sygnały dla modeli AI danymi strukturalnymi schema.org (Person, Organization, BlogPosting).
Case study migracji bloga z WordPressa (motyw Astra) na statyczny stack Astro 5 + Cloudflare Workers: treść z dumpa bazy, URL-e 1:1, 301-ki, WebP, Lighthouse ~98 i hosting bez serwera.
Jak zamienić liczbę binarną na dziesiętną: algorytm dla części całkowitej i ułamkowej oraz gotowy kod w Pythonie, JavaScripcie, PHP i C++. Krok po kroku.
Narzędzia do monitorowania Debiana: procesy (top, htop), obciążenie CPU (mpstat), pamięć i dysk (vmstat, iostat) oraz ruch sieciowy (iftop). Co i czym sprawdzić.
Jak skonfigurować kartę sieciową w Debianie: statyczny adres IP przez plik /etc/network/interfaces i tymczasowe ustawienia poleceniem ip. Krok po kroku, z przykładami.
Jak postawić i skonfigurować serwer DHCP na Debianie: automatyczne przydzielanie adresów IP w sieci lokalnej, zakres puli, brama i DNS. Praktyczny przewodnik z przykładami.
Konfiguracja ochrony poczty na FreeBSD: SpamAssassin z bazą MySQL (Bayes), ClamAV, Razor oraz Maia-Mailguard (amavisd-new) jako filtr i panel kwarantanny.
Konfiguracja Postfix na FreeBSD: SASL z Dovecot, TLS, domeny i skrzynki wirtualne w MySQL, antyspamowe RBL, oraz PostfixAdmin do zarządzania pocztą przez WWW.
Kompilacja i konfiguracja Dovecot z obsługą MySQL na FreeBSD: POP3/IMAP, SSL/TLS, Maildir, Sieve (Pigeonhole), quota oraz uwierzytelnianie skrzynek z bazy.
Jak działa serwer poczty elektronicznej i z czego się składa: SMTP, POP3/IMAP, Postfix, Dovecot, Apache, MySQL, Roundcube na FreeBSD 13. Część 1 serii.