Poznaj poglądy Johna Ousterhouta na praktyczne projektowanie oprogramowania, dziedzictwo Tcl, debatę Ousterhout vs Brooks i to, jak złożoność zatapia produkty.

John Ousterhout to informatyk i inżynier, którego praca obejmuje zarówno badania, jak i realne systemy. Stworzył język programowania Tcl, przyczynił się do kształtowania nowoczesnych systemów plików, a później skondensował dekady doświadczeń w proste, nieco niewygodne twierdzenie: złożoność jest głównym wrogiem oprogramowania.
To przesłanie jest wciąż aktualne, bo większość zespołów nie upada przez brak funkcji czy wysiłku — upada, gdy ich systemy (i organizacje) stają się trudne do zrozumienia, trudne do zmiany i łatwe do złamania. Złożoność nie tylko spowalnia inżynierów. Przenika decyzje produktowe, pewność roadmapy, zaufanie klientów, częstotliwość incydentów, a nawet rekrutację — bo wdrożenie staje się miesięcznym przedsięwzięciem.
Ujęcie Ousterhouta jest praktyczne: gdy system gromadzi przypadki specjalne, wyjątki, ukryte zależności i „tylko tę jedną poprawkę”, koszt nie ogranicza się do kodu. Cały produkt staje się droższy w rozwijaniu. Funkcje zajmują więcej czasu, QA robi się trudniejsze, wydania stają się ryzykowne, a zespoły zaczynają unikać ulepszeń, bo dotknięcie czegokolwiek wydaje się niebezpieczne.
To nie apel o akademyczną czystość. To przypomnienie, że każdy skrót ma swoje odsetki — a złożoność to najdroższy dług.
Aby uczynić ideę konkretną (a nie tylko motywacyjną), przyjrzymy się przesłaniu Ousterhouta przez trzy kąty:
To nie jest tekst tylko dla maniaków języków. Jeśli budujesz produkty, prowadzisz zespoły lub podejmujesz decyzje o roadmapie, znajdziesz tu praktyczne sposoby, by wcześnie wykrywać złożoność, zapobiegać jej instytucjonalizacji i traktować prostotę jako ograniczenie pierwszej wagi — nie jako miły dodatek po starcie.
Złożoność to nie „dużo kodu” ani „trudne równania”. To luka między tym, co myślisz, że system zrobi po zmianie, a tym, co on rzeczywiście robi. System jest złożony, gdy mała edycja wydaje się ryzykowna — bo nie potrafisz przewidzieć zasięgu.
W zdrowym kodzie potrafisz odpowiedzieć: „Jeśli to zmienimy, co jeszcze może się złamać?” Złożoność sprawia, że to pytanie staje się kosztowne.
Często ukrywa się w:
Zespoły odczuwają złożoność jako wolniejsze wypuszczanie (więcej czasu na badanie), więcej błędów (bo zachowanie zaskakuje) oraz kruchsze systemy (zmiany wymagają koordynacji wielu osób i usług). Obciąża też wdrożenie: nowi członkowie nie potrafią zbudować modelu mentalnego, więc unikają dotykania kluczowych przepływów.
Część złożoności jest istotna: reguły biznesowe, wymagania zgodności, przypadki brzegowe w rzeczywistym świecie. Nie da się ich usunąć.
Ale dużo jest przypadkowe: mylące API, zduplikowana logika, „tymczasowe” flagi, które stają się trwałe, i moduły, które przeciekają szczegóły wewnętrzne. To złożoność tworzoną przez decyzje projektowe — i jedyna, którą da się systematycznie spłacać.
Tcl powstał z praktycznego celu: ułatwić automatyzację oprogramowania i rozszerzanie istniejących aplikacji bez przepisywania ich. John Ousterhout zaprojektował go tak, żeby zespoły mogły dodać „wystarczającą programowalność” do narzędzia — a potem oddać tę moc użytkownikom, operatorom, QA czy komukolwiek, kto musiał skryptować przepływy.
Tcl spopularyzował pojęcie języka kleju: małej, elastycznej warstwy skryptowej, która łączy komponenty napisane w szybszych, niższych językach. Zamiast budować każdą funkcję w monolicie, można wystawić zestaw poleceń i komponować je w nowe zachowania.
Model ten okazał się wpływowy, bo pasował do tego, jak praca naprawdę wygląda. Ludzie nie tylko budują produkty; tworzą systemy budowania, harnessy testowe, narzędzia administracyjne, konwertery danych i jednorazowe automatyzacje. Lekka warstwa skryptowa zamienia te zadania z „złóż bilet” w „napisz skrypt”.
Tcl uczynił osadzenie interpretera sprawą pierwszorzędną. Można było wrzucić interpreter do aplikacji, wyeksportować czysty interfejs poleceń i natychmiast zyskać konfigurowalność i szybkie iteracje.
Ten sam wzorzec widać dziś w systemach wtyczek, językach konfiguracji, API rozszerzeń i osadzonych runtime’ach skryptowych — niezależnie od tego, czy składnia skryptu przypomina Tcl. Wzmacniał też ważny nawyk projektowy: oddzielaj stabilne prymitywy (rdzeń aplikacji) od zmiennej kompozycji (skrypty). Gdy to działa, narzędzia ewoluują szybciej, nie destabilizując jądra.
Składnia Tcl i model „wszystko jest stringiem” mogły wydawać się nieintuicyjne, a duże baz kodu w Tcl bywały trudne do zrozumienia bez silnych konwencji. W miarę jak ekosystemy oferowały bogatsze biblioteki standardowe, lepsze narzędzia i większe społeczności, wiele zespołów naturalnie migrowało.
To jednak nie kasuje dziedzictwa Tcl: ugruntował on przekonanie, że rozszerzalność i automatyzacja nie są dodatkami — są funkcjami produktu, które mogą znacznie zredukować złożoność dla osób używających i utrzymujących system.
Tcl powstał wokół pozornie surowej idei: trzymaj jądro małe, uczyn kompozycję potężną i spraw, by skrypty były czytelne tak, by ludzie mogli współpracować bez stałego tłumaczenia.
Zamiast dostarczać ogromny zestaw wyspecjalizowanych funkcji, Tcl opierał się na kompaktowym zestawie prymitywów (stringi, polecenia, proste reguły ewaluacji) i oczekiwał, że użytkownicy połączą je.
Ta filozofia popycha projektantów w stronę mniejszej liczby koncepcji, wykorzystywanych w wielu kontekstach. Lekcja dla projektowania produktu i API jest prosta: jeśli możesz rozwiązać dziesięć potrzeb przy pomocy dwóch–trzech spójnych bloków, zmniejszasz powierzchnię, którą ludzie muszą poznać.
Kluczową pułapką jest optymalizacja pod wygodę budującego. Funkcja może być łatwa do zaimplementowania (skopiuj istniejącą opcję, dodaj flagę specjalną, załatuj narożnik), a jednocześnie uczynić produkt trudniejszym w użyciu.
Tcl stawiał odwrotnie: trzymaj spójny model mentalny, nawet jeśli implementacja musi wykonać więcej pracy w tle.
Gdy oceniasz propozycję, zapytaj: czy to zmniejsza liczbę koncepcji, które użytkownik musi zapamiętać, czy dodaje kolejne wyjątki?
Minimalizm pomaga tylko wtedy, gdy prymitywy są spójne. Jeśli dwa polecenia wyglądają podobnie, ale zachowują się inaczej w przypadkach brzegowych, użytkownicy zaczynają zapamiętywać drobne szczegóły. Mały zestaw narzędzi może stać się „ostrymi krawędziami”, gdy reguły różnią się subtelnie.
Pomyśl o kuchni: dobry nóż, patelnia i piekarnik pozwalają zrobić wiele potraw, łącząc techniki. Gadżet do krojenia awokado jest jednorazowy — łatwo go sprzedać, ale zaśmieca szufladę.
Filozofia Tcl zachęca do noża i patelni: ogólnych narzędzi, które czysto się komponują, żeby nie potrzebować nowego gadżetu przy każdym przepisie.
W 1986 roku Fred Brooks napisał esej z prowokacyjnym wnioskiem: nie ma jednego przełomu — żadnej „srebrnej kuli” — która w jednym kroku uczyniłaby tworzenie oprogramowania o rząd wielkości szybszym, tańszym i bardziej niezawodnym.
Jego punkt nie brzmiał, że postęp jest niemożliwy. Chodziło o to, że oprogramowanie jest medium, w którym możemy robić niemal wszystko, a ta wolność niesie ze sobą unikalny ciężar: ciągle definiujemy rzecz w trakcie jej budowy. Lepsze narzędzia pomagają, ale nie likwidują najtrudniejszej części pracy.
Brooks podzielił złożoność na dwie kategorie:
Narzędzia mogą zdusić złożoność przypadkową. Pomyśl o tym, co zyskaliśmy dzięki językom wysokiego poziomu, kontroli wersji, CI, kontenerom, zarządzanym bazom danych i dobrym IDE. Brooks twierdził jednak, że to złożoność istotna dominuje i nie zniknie tylko dlatego, że narzędzia się poprawiły.
Nawet z nowoczesnymi platformami zespoły nadal spędzają większość czasu negocjując wymagania, integrując systemy, obsługując wyjątki i utrzymując spójne zachowanie w czasie. Powierzchnia problemu się zmienia (API chmury zamiast sterowników urządzeń), ale podstawowe wyzwanie pozostaje: przetłumaczyć potrzeby ludzi na precyzyjne, utrzymywalne zachowanie.
To stwarza napięcie, w które wchodzi Ousterhout: jeśli złożoności istotnej nie da się wyeliminować, czy zdyscyplinowany projekt może znacząco zmniejszyć to, ile z niej przecieka do kodu — i do głów developerów na co dzień?
Ludzie czasem przedstawiają „Ousterhout kontra Brooks” jako starcie optymizmu z realizmem. Lepiej czytać to jako dwóch doświadczonych inżynierów opisujących różne aspekty tego samego problemu.
Brooks w „No Silver Bullet” twierdzi, że nie ma jednego przełomu, który magicznie usunie trudną część tworzenia oprogramowania. Ousterhout nie zaprzecza temu. Jego riposta jest węższa i praktyczna: zespoły często traktują złożoność jako nieuniknioną, choć wiele z niej jest przez nie wywołane.
W oczach Ousterhouta dobry projekt może znacząco zmniejszyć złożoność — nie po to, by uczynić oprogramowanie „łatwym”, lecz by uczynić je mniej mylącym do zmiany. To duże twierdzenie, i ma znaczenie, bo to zagubienie zamienia codzienną pracę w powolną pracę.
Brooks koncentruje się na tym, co nazywa trudnością istotną: oprogramowanie musi modelować złożone realia, zmienne wymagania i przypadki brzegowe istniejące poza kodem. Nawet z doskonałymi narzędziami tego nie usuniesz. Możesz to jedynie opanować.
Zgodności jest więcej niż sugeruje debata:
Zamiast pytać „Kto ma rację?”, zapytaj: Jaką złożoność możemy kontrolować w tym kwartale?
Zespoły nie mogą kontrolować zmian rynkowych czy podstawowej trudności domeny. Mogą natomiast kontrolować, czy nowe funkcje dodają przypadki specjalne, czy API zmuszają wywołujących do pamiętania ukrytych reguł, i czy moduły ukrywają złożoność czy ją przeciekają.
To wykonalne, średnie podejście: zaakceptuj złożoność istotną i bezwzględnie selekcjonuj tę przypadkową.
Głęboki moduł to komponent, który robi dużo, a jednocześnie wystawia mały, łatwy do zrozumienia interfejs. „Głębokość” to ilość złożoności, którą moduł zabiera z Twoich barków: wywołujący nie muszą znać brudnych szczegółów, a interfejs ich do tego nie zmusza.
Płytki moduł to odwrotność: może owijać drobny kawałek logiki, ale wypycha złożoność na zewnątrz — przez wiele parametrów, specjalne flagi, wymaganą kolejność wywołań lub „musisz pamiętać…”.
Pomyśl o restauracji. Głęboki moduł to kuchnia: zamawiasz „makaron” z prostego menu i nie interesują Cię wybory dostawców, czasy gotowania ani sposób podania.
Płytki moduł to „kuchnia”, która wręcza surowe składniki z 12-punktową instrukcją i prosi o przyniesienie własnej patelni. Praca nadal jest wykonywana — tylko przeniesiono ją na klienta.
Dodatkowe warstwy są świetne, jeśli składają wiele decyzji w jedną oczywistą.
Na przykład warstwa przechowywania, która wystawia save(order) i sama zajmuje się retryami, serializacją i indeksowaniem, jest głęboka.
Warstwy szkodzą, gdy głównie nazywają rzeczy inaczej albo dodają opcje. Jeśli nowa abstrakcja wprowadza więcej konfiguracji niż usuwa — np. save(order, format, retries, timeout, mode, legacyMode) — prawdopodobnie jest płytka. Kod może wyglądać na „zorganizowany”, ale obciążenie poznawcze wraca przy każdym miejscu wywołania.
useCache, skipValidation, force, legacy.Głębokie moduły nie tylko „enkapsulują kod”. Enkapsulują decyzje.
„Dobre” API to nie tylko takie, które potrafi dużo. To takie, które ludzie mogą utrzymać w głowie podczas pracy.
Soczewka Ousterhouta zmusza do oceniania API przez wysiłek mentalny, jaki wymaga: ile reguł trzeba zapamiętać, ile wyjątków przewidzieć i jak łatwo przypadkowo zrobić źle.
Przyjazne API zwykle są małe, spójne i trudne do błędnego użycia.
Małe nie znaczy pozbawione mocy — oznacza, że powierzchnia skupia się na niewielu pojęciach, które dobrze się komponują. Spójne znaczy, że ten sam wzorzec działa w całym systemie (parametry, obsługa błędów, nazewnictwo, typy zwracane). Trudne do błędnego użycia oznacza, że API prowadzi ku bezpiecznym ścieżkom: jasne invariants, walidacja na granicach i typy lub runtime checks, które zawodzą wcześnie.
Każda dodatkowa flaga, tryb czy konfiguracja staje się podatkiem na wszystkich użytkowników. Nawet jeśli tylko 5% wywołujących potrzebuje tej opcji, 100% musi wiedzieć, że istnieje, zastanawiać się, czy jej potrzebuje i interpretować zachowanie, gdy wchodzi w interakcję z innymi opcjami.
Tak API akumulują ukrytą złożoność: nie w pojedynczym wywołaniu, lecz w kombinatoryce.
Domyślne ustawienia to uprzejmość: pozwalają większości wywołujących pominąć decyzje i nadal uzyskać sensowne zachowanie. Konwencje (jedno oczywiste podejście) zmniejszają rozgałęzienia w umyśle użytkownika. Nazwy wykonują realną pracę: wybieraj czasowniki i rzeczowniki, które odpowiadają intencji użytkownika i trzymaj podobne operacje przy podobnych nazwach.
Jeszcze jedno przypomnienie: API wewnętrzne są tak samo ważne jak publiczne. Większość złożoności produktów żyje za kulisami — granice serwisów, biblioteki współdzielone i „pomocnicze” moduły. Traktuj te interfejsy jak produkty: z przeglądami i dyscypliną wersjonowania (patrz też /blog/deep-modules).
Złożoność rzadko przychodzi jako jedna „zła decyzja”. Akumuluje się przez małe, rozsądnie wyglądające poprawki — szczególnie gdy zespoły działają pod presją i celem jest wysłanie produktu.
Jedna z pułapek to flag wszystkiego. Flagi są użyteczne do kontrolowanego rollout’u, ale gdy trwają długo, każda flaga mnoży liczbę możliwych zachowań. Inżynierowie przestają myśleć o „systemie” i zaczynają o „systemie, z wyjątkiem gdy flaga A jest włączona i użytkownik jest w segmencie B”.
Inną jest logika przypadków specjalnych: „Klienci enterprise potrzebują X”, „Z wyjątkiem regionu Y”, „Chyba że konto ma ponad 90 dni”. Te wyjątki często rozprzestrzeniają się po kodzie i po kilku miesiącach nikt nie wie, które są dalej potrzebne.
Trzecią jest ciekąca abstrakcja. API, które zmusza wywołujących do zrozumienia wewnętrznych szczegółów (timingi, formaty przechowywania, reguły cache), wypycha złożoność na zewnątrz. Zamiast jeden moduł nosić ciężar, każdy wywołujący zna sztuczki.
Programowanie taktyczne to optymalizacja na ten tydzień: szybkie poprawki, minimalne zmiany, „popraw to”.
Programowanie strategiczne to optymalizacja na następny rok: małe przebudowy zapobiegające tej samej klasie błędów i redukujące przyszłą pracę.
Niebezpieczeństwem jest „oprocentowanie utrzymania”. Szybkie obejście wydaje się tanie teraz, ale płacisz je z odsetkami: wolniejsze wdrożenie, kruche wydania i rozwój napędzany strachem, gdzie nikt nie chce dotykać starego kodu.
Dodaj lekkie pytania do code review: „Czy to dodaje nowy przypadek specjalny?” „Czy API może ukryć ten szczegół?” „Jaką złożoność tu zostawiamy?”
Prowadź krótkie zapisy decyzji dla nietrywialnych kompromisów (kilka punktów wystarczy). I rezerwuj mały budżet refaktoryzacyjny w każdym sprincie, żeby strategiczne poprawki nie były traktowane jako działalność dodatkowa.
Złożoność nie zostaje w inżynierii. Przecieka do harmonogramów, niezawodności i sposobu, w jaki klienci doświadczają produktu.
Gdy system jest trudny do zrozumienia, każda zmiana zajmuje więcej czasu. Time-to-market się ślizga, bo każde wydanie wymaga więcej koordynacji, więcej testów regresji i więcej przeglądów „na wszelki wypadek”.
Niezawodność też cierpi. Złożone systemy tworzą interakcje, których nikt nie potrafi w pełni przewidzieć, więc błędy pojawiają się jako przypadki brzegowe: checkout zawodzi tylko wtedy, gdy kupon, zapisany koszyk i reguła podatkowa regionu zadziałają razem. To incydenty najtrudniejsze do odtworzenia i najszybciej naprawiane.
Wdrożenie staje się ukrytym obciążeniem. Nowi członkowie nie potrafią zbudować użytecznego modelu mentalnego, więc unikają ryzykownych obszarów, kopiują wzorce których nie rozumieją i nieumyślnie dodają więcej złożoności.
Klienci nie dbają o to, czy zachowanie wynika z „przypadku specjalnego” w kodzie. Odczuwać je będą jako niespójność: ustawienia, które nie działają wszędzie, ścieżki zależne od sposobu dotarcia, funkcje działające „większość czasu”.
Zaufanie spada, churn rośnie, adopcja hamuje.
Zespoły wsparcia płacą za złożoność poprzez dłuższe tickety i więcej wymiany z klientem, by zebrać kontekst. Operacje płacą przez więcej alertów, więcej runbooków i ostrożniejsze wdrożenia. Każdy wyjątek staje się rzeczą do monitorowania, dokumentowania i wyjaśniania.
Wyobraź sobie prośby o „jeszcze jedno regułowanie powiadomień”. Dodanie wydaje się szybkie, ale wprowadza kolejny rozgałęzienie zachowania, więcej tekstów w UI, więcej przypadków testowych i więcej sposobów, w jakie użytkownicy mogą to źle skonfigurować.
Porównaj to z uproszczeniem istniejącego przepływu powiadomień: mniej typów reguł, czytelne domyślne ustawienia i spójne zachowanie na webie i mobilu. Możesz wysłać mniej pokrętnych opcji, ale zmniejszysz niespodzianki — ułatwiając użytkownikom korzystanie, wsparciu pracę i dalszy rozwój produktu.
Traktuj złożoność jak wydajność czy bezpieczeństwo: planuj ją, mierz i chroń. Jeśli zauważysz złożoność dopiero, gdy dostawy zwalniają, już płacisz odsetki.
Obok zakresu funkcji zdefiniuj, ile nowej złożoności wydanie może wprowadzić. Budżet może być prosty: „brak nowych koncepcji, chyba że usuniemy jedną” lub „każda nowa integracja musi zastąpić starą ścieżkę”.
Stawiaj kompromisy jawnie podczas planowania: jeśli funkcja wymaga trzech nowych trybów konfiguracji i dwóch wyjątków, powinna „kosztować” więcej niż funkcja mieszcząca się w istniejących pojęciach.
Nie potrzebujesz idealnych liczb — wystarczą sygnały, które pokazują trend:
Śledź to na wydanie i powiąż z decyzjami: „Dodaliśmy dwie nowe publiczne opcje; co usunęliśmy lub uprościliśmy, żeby zrekompensować?”
Prototypy często ocenia się pytaniem „Czy możemy to zbudować?” Zamiast tego używaj ich, by odpowiedzieć: „Czy to jest proste w użyciu i trudne do niewłaściwego użycia?”
Poproś kogoś niezwiązanego z projektem, by wykonał realistyczne zadanie z prototypem. Mierz czas do sukcesu, pytania i miejsca, gdzie robią błędne założenia. To są miejsca, gdzie złożoność gryzie.
To też obszar, gdzie nowoczesne workflowy mogą zmniejszyć złożoność przypadkową — jeśli utrzymują szybką iterację i łatwość cofania błędów. Na przykład, gdy zespoły używają platformy takiej jak Koder.ai do szkicowania narzędzia wewnętrznego lub nowego przepływu przez chat, funkcje takie jak planning mode (doprecyzowanie intencji przed generacją) i snapshots/rollback (cofanie ryzykownych zmian) mogą uczynić eksperymentowanie bezpieczniejszym — bez zaciągania stosu niedokończonych abstrakcji. Jeśli prototyp awansuje, nadal możesz wyeksportować kod źródłowy i zastosować tę samą dyscyplinę „głębokich modułów” i projektowania API opisaną powyżej.
Rób „sprzątanie złożoności” okresowo (co kwartał albo po każdym większym wydaniu) i zdefiniuj, co oznacza „zrobione”:
Cel to nie ładniejszy kod jako taki — to mniej koncepcji, mniej wyjątków i bezpieczniejsze zmiany.
Oto kilka ruchów, które przekształcają ideę Ousterhouta „złożoność jest wrogiem” w cotygodniowe nawyki zespołowe.
Wybierz jeden podsystem, który regularnie powoduje zamieszanie (ból wdrożenia, powtarzające się bugi, dużo pytań „jak to działa?”).
Wewnętrzne follow-upy, które można przeprowadzić: „przegląd złożoności” w planowaniu (/blog/complexity-review) oraz szybkie sprawdzenie, czy wasze narzędzia zmniejszają złożoność przypadkową, zamiast dokładać warstwy (/pricing).
Jaką jedną część złożoności usunąłbyś najpierw, będąc w sytuacji: możesz w tym tygodniu usunąć tylko jeden przypadek specjalny?
Złożoność to różnica między tym, czego się spodziewasz, że system zrobi po zmianie, a tym, co on faktycznie robi.
Czujesz ją, gdy drobne edycje wydają się ryzykowne, bo nie możesz przewidzieć zasięgu zmian (testy, serwisy, konfiguracje, klienci czy przypadki brzegowe, które możesz złamać).
Szukaj sygnałów, że rozumowanie staje się kosztowne:
Złożoność istotna pochodzi z domeny (regulacje, rzeczywiste przypadki brzegowe, zasady biznesowe). Nie da się jej usunąć — można ją jedynie dobrze zamodelować.
Złożoność przypadkowa to złożoność, którą sami tworzymy (lejące abstrakcje, powielona logika, zbyt wiele trybów/flag, niejasne API). To tę część zespoły mogą konsekwentnie redukować przez projekt i upraszczanie.
A głęboki moduł robi dużo, a jednocześnie wystawia mały, stabilny interfejs. „Głębokość” to ilość skomplikowanych spraw, którą moduł bierze na siebie: wywołujący nie musi znać szczegółów, a interfejs ich nie narzuca.
Praktyczny test: jeśli większość wywołujących potrafi użyć modułu poprawnie nie znając reguł wewnętrznych, moduł jest głęboki; jeśli wywołujący muszą zapamiętywać reguły i sekwencje, moduł jest płytki.
Typowe objawy:
legacy, skipValidation, force, mode).Preferuj API, które są:
Zanim dodasz „jeszcze jedną opcję”, zapytaj, czy nie można przeprojektować interfejsu tak, by większość wywołujących w ogóle o tej decyzji nie musiała myśleć.
Używaj flag do bezpiecznego wdrożenia, ale traktuj je jak dług z terminem spłaty:
Długowieczne flagi mnożą liczbę „systemów”, o których muszą myśleć inżynierowie.
Uczyń złożoność widoczną podczas planowania, nie tylko w code review:
Celem jest zmuszenie do jawnych kompromisów, zanim złożoność się utrwali.
Programowanie taktyczne optymalizuje pod ten tydzień: szybkie poprawki, minimalne zmiany, „wyślij”.
Programowanie strategiczne optymalizuje pod następny rok: małe przeprojektowania, które usuwają powtarzające się klasy błędów i zmniejszają przyszłą pracę.
Prosta heurystyka: jeśli poprawka wymaga wiedzy wywołującego („pamiętaj, by najpierw wywołać X” lub „ustaw tę flagę tylko w prod”), prawdopodobnie potrzebna jest bardziej strategiczna zmiana, żeby ukryć tę złożoność wewnątrz modułu.
Główne przesłanie Tcl to moc małego zestawu prymitywów plus silnej kompozycji — często jako wbudowana warstwa „kleju”.
Nowoczesne odpowiedniki to:
Cel projektowy pozostaje niezmienny: utrzymaj jądro proste i stabilne, a zmiany pozwól zachodzić przez czyste interfejsy.
Płytkie moduły często wyglądają na uporządkowane, lecz przenoszą złożoność na każdego wywołującego.