Kod pisze się raz, a czyta setki razy. Programista, który pisze funkcję, rzadko jest tym, który debuguje ją sześć miesięcy później, rozszerza ją o nowe wymagania lub musi zrozumieć jej skrajne przypadki podczas incydentu produkcyjnego. Każda decyzja podjęta podczas pisania kodu – nazwa zmiennej, długość funkcji, struktura klasy – albo ułatwia, albo utrudnia pracę kolejnemu czytelnikowi. Czysty kod to dyscyplina systematycznego upraszczania go.
Koncepcję tę sformalizował Robert C. Martin, znany jako „Uncle Bob”, w książce „Clean Code: A Handbook of Agile Software Craftsmanship ” (2008), która pozostaje kanonicznym źródłem wiedzy w tym temacie. Książka Martina Fowlera „Refactoring: Improving the Design of Existing Code” porusza uzupełniający problem: co zrobić, gdy istniejący kod nie jest czysty, a powinien taki być. Razem te dwie prace definiują intelektualne podstawy praktyki czystego kodu. Niniejszy przewodnik omawia kluczowe zasady wraz z przykładami działającego kodu w najpopularniejszych językach programowania, w tym RPG i PL/I, dla programistów utrzymujących starsze systemy korporacyjne.
Czyste naruszenia kodu, których nie widać
SMART TS XL wykrywa złożoność, duplikacje i martwy kod w językach COBOL, Java, Python, RPG i innych.
Więcej informacjiCzym jest czysty kod?
Czysty kod to kod źródłowy, który jest łatwy do odczytania, zrozumienia, przetestowania i zmiany. W tej definicji określenie „łatwy” oznacza realną pracę. Kod akceptowany przez kompilator niekoniecznie jest czystym kodem. Kod, który przechodzi wszystkie testy, niekoniecznie jest czystym kodem. Kod jest czysty, gdy inny programista, osoba, która go nie napisała, w kontekście nieprzewidzianym podczas jego pisania, może szybko zrozumieć jego przeznaczenie, pewnie go modyfikować i rozszerzać bez nieoczekiwanych konsekwencji.
Definicja Martina Fowlera jest najczęściej cytowana: „Każdy głupiec potrafi napisać kod zrozumiały dla komputera. Dobrzy programiści piszą kod zrozumiały dla ludzi”. Miarą czystości kodu jest ludzkie zrozumienie, a nie wykonanie przez maszynę.
Czysty kod nie jest kwestią estetyki. Nie chodzi o przestrzeganie konkretnego przewodnika stylistycznego dla samego przestrzegania. Chodzi o strukturalne właściwości kodu, które decydują o tym, ile wysiłku będzie wymagała każda przyszła zmiana. Baza kodu, która narusza zasady czystego kodu, konsekwentnie kumuluje dług techniczny, kumulując koszty wcześniejszych skrótów, aż do momentu, gdy każda nowa funkcja wymaga zrozumienia i ostrożnego poruszania się po istniejącej złożoności przed dodaniem czegokolwiek nowego.
Zasady czystego kodu: krótki przewodnik
Zasady czystego kodu Roberta C. Martina można podsumować jako zbiór praktycznych wytycznych. Poniższa tabela przedstawia każdą zasadę wraz z jej główną zasadą i problemem, któremu zapobiega:
| Zasada | Zasada podstawowa | Problem, któremu zapobiega |
|---|---|---|
| Znaczące nazwy | Nazwy powinny ujawniać intencję, zmienne, funkcje i klasy | Dekodowanie narzutu poznawczego x, tmplub obj faktycznie reprezentuje |
| Małe funkcje | Funkcje robią jedno: mieszczą się na jednym ekranie | Niemożliwe do przetestowania i odczytania monolity, które mieszają obowiązki |
| Pojedyncza odpowiedzialność | Każda klasa/moduł ma jeden powód do zmiany | Klasy Boga, które ulegają zniszczeniu, gdy zmienia się jakakolwiek z kilkunastu niezwiązanych ze sobą rzeczy |
| SUCHY (nie powtarzaj się) | Każda część wiedzy ma jedną reprezentację | Poprawki błędów zastosowano w jednej kopii, ale nie w pozostałych; błąd logiczny |
| KISS (Keep It Simple) | Wolę najprostsze rozwiązanie, które działa | Zbyt skomplikowany kod rozwiązujący problemy, których nikt nie ma |
| YAGNI (Nie będziesz tego potrzebować) | Nie buduj funkcji, dopóki nie będą potrzebne | Martwy, spekulacyjny kod, który dodaje złożoności bez żadnych korzyści |
| Zasada otwarta/zamknięta | Otwarte na rozszerzenie, zamknięte na modyfikację | Kod, który psuje istniejące zachowanie podczas dodawania nowego zachowania |
| Separacja obaw | Różne obowiązki w różnych miejscach | Splątany kod, w którym zmiana jednej rzeczy powoduje uszkodzenie niezwiązanych z nią rzeczy |
| Unikaj komentarzy typu „co”; używaj ich do „dlaczego” | Kod wyjaśnia co; komentarze wyjaśniają dlaczego | Nieaktualne komentarze, które wprowadzają w błąd bardziej niż informują |
| Zasada skauta | Pozostaw kod w stanie czystszym niż go zastałeś | Stopniowy spadek jakości poprzez stopniowe zaniedbywanie |
Wyjaśnienie podstawowych zasad czystego kodu
DRY, nie powtarzaj się
Zasada DRY głosi, że każdy element wiedzy musi mieć pojedynczą, jednoznaczną i autorytatywną reprezentację w systemie. Gdy ta sama logika pojawia się w wielu miejscach, reprezentacje te nieuchronnie się rozchodzą. Zmiana reguły biznesowej staje się wyszukiwaniem i zamianą w wielu plikach, a pominięcie jednej kopii powoduje błąd.
Zasada DRY dotyczy nie tylko kodu skopiowanego i wklejonego. Dotyczy konfiguracji, dokumentacji i schematów danych. Jeśli te same informacje muszą być przechowywane w dwóch miejscach, naruszasz zasadę DRY, niezależnie od tego, czy jakikolwiek kod został dosłownie skopiowany.
KISS, Keep It Simple
KISS argumentuje, że systemy działają najlepiej, gdy są proste, i że prostota powinna być głównym celem projektowania. Złożoność nie jest oznaką wyrafinowania, lecz oznaką, że coś można było wyrazić jaśniej.
Wniosek praktyczny: jeśli dwa rozwiązania rozwiązują ten sam problem, wybierz prostsze. Bardziej złożone rozwiązanie może obsłużyć przypadki brzegowe, z którymi jeszcze się nie spotkałeś. Z pewnością utrudni to zrozumienie kodu każdemu, kto się z nim zetknie.
YAGNI, nie będziesz tego potrzebować
YAGNI odradza dodawanie funkcjonalności, dopóki nie będzie to potrzebne. Jest to odpowiedź na powszechny impuls do budowania elastycznych, rozszerzalnych systemów, które spełniają przewidywane przyszłe wymagania, które nigdy się nie zmaterializują. Każda abstrakcja, interfejs i opcja konfiguracji, które istnieją dla hipotetycznego przyszłego przypadku użycia, stanowią obciążenie poznawcze dla każdego programisty, który czyta kod dzisiaj.
Znaczące nazwy
Nazwy stanowią podstawowy mechanizm komunikacji w kodzie źródłowym. Funkcja o nazwie process() nie komunikuje niczego na temat tego, co przetwarza, kiedy i co zwraca. Funkcja o nazwie calculateMonthlyInterest() komunikuje się precyzyjnie.
Kluczowe testy nazwy: Czy potrafisz stwierdzić, co ona reprezentuje bez czytania jej implementacji? Czy nazwa ujawnia swój cel, parametry i wartość zwracaną? Czy potrzebujesz komentarza wyjaśniającego, co ona robi?
Małe funkcje
Zasada praktyczna Roberta C. Martina: funkcje powinny być małe, mniejsze niż uważasz za konieczne. Funkcja, która wykonuje jedną czynność, może być jasno nazwana, przetestowana w izolacji i zrozumiana bez czytania jej implementacji. Funkcja, która wykonuje kilka czynności, wymaga zrozumienia ich wszystkich jednocześnie.
Zasada pojedynczej odpowiedzialności dotyczyła funkcji: funkcja powinna wykonywać jedną rzecz, robić ją dobrze i tylko ją. Jeśli do opisania funkcji potrzebujesz słowa „i”, prawdopodobnie robi ono za dużo.
Czysty kod w Javie
Rozwlekłość Javy sprawia, że czysta dyscyplina w kodzie jest szczególnie ważna. Standardowe elementy wymagane przez język mogą przysłonić cel kodu, jeśli nie będą odpowiednio zarządzane.
Jawa
// Before: unclear names, mixed responsibilities, magic numbers
public double calc(int x, int y) {
double r = 0;
if (y > 1000) {
r = x * y * 0.1;
} else {
r = x * y * 0.05;
}
return r;
}
// After: meaningful names, single responsibility, named constants
private static final double PREMIUM_DISCOUNT_RATE = 0.10;
private static final double STANDARD_DISCOUNT_RATE = 0.05;
private static final int PREMIUM_THRESHOLD = 1000;
public double calculateDiscount(int quantity, int unitPrice) {
double subtotal = quantity * unitPrice;
return isPremiumOrder(unitPrice)
? subtotal * PREMIUM_DISCOUNT_RATE
: subtotal * STANDARD_DISCOUNT_RATE;
}
private boolean isPremiumOrder(int unitPrice) {
return unitPrice > PREMIUM_THRESHOLD;
}
Kluczowe zasady czystego kodu w Javie: preferowanie kompozycji nad dziedziczeniem, używanie strumieni do przetwarzania danych zamiast rozwlekłych pętli, przekształcanie magicznych liczb w nazwane stałe oraz koncentrowanie klas na pojedynczej odpowiedzialności. Zasady czystego kodu Roberta C. Martina w Javie obejmują zasadę, że klasy powinny być małe, mierzone nie w wierszach, ale w odpowiedzialnościach.
Jawa
// Clean Java: streams over imperative loops
List<String> activeUserEmails = users.stream()
.filter(User::isActive)
.map(User::getEmail)
.collect(Collectors.toList());
Czysty kod w Pythonie
Filozofia projektowania Pythona, zgodnie z którą jawność jest lepsza od niejawności, prostota jest lepsza od złożoności, naturalnie wpisuje się w zasady czystego kodu. PEP 8 to oficjalny podręcznik stylu Pythona i punkt odniesienia dla czystego kodu w tym języku.
pyton
# Before: vague names, long function, no separation
def do_stuff(d):
res = []
for i in d:
if i['a'] > 18:
res.append(i['n'].upper())
return res
# After: meaningful names, separated concerns, Pythonic style
def get_adult_names_uppercase(users: list[dict]) -> list[str]:
return [
user["name"].upper()
for user in users
if user["age"] > 18
]
pyton
# Clean Python: context managers for resource handling
# Before: manual, error-prone
f = open("data.txt")
data = f.read()
f.close()
# After: guaranteed cleanup, self-documenting intent
with open("data.txt") as f:
data = f.read()
Czysty kod Pythona wykorzystuje wyrażenia listowe zamiast pętli ręcznych do prostych transformacji, wskazówki typu do samodokumentujących się sygnatur funkcji, menedżerów kontekstu do zarządzania zasobami oraz klasy danych lub nazwane krotki zamiast gołych słowników do danych strukturalnych.
Czysty kod w JavaScript i TypeScript
Elastyczność JavaScriptu jest zarówno jego zaletą, jak i głównym wyzwaniem w zakresie czystego kodu. Bez dyscypliny, bazy kodu JavaScript gromadzą niespójne wzorce, niejawne wymuszanie typów i splątane łańcuchy wywołań zwrotnych.
javascript
// Before: var, callback hell, no error handling
function getUser(id, cb) {
db.query('SELECT * FROM users WHERE id = ' + id, function(err, rows) {
if (err) cb(err);
cb(null, rows[0]);
});
}
// After: async/await, parameterized query, proper error handling
async function getUserById(userId: number): Promise<User | null> {
const [rows] = await db.execute(
'SELECT * FROM users WHERE id = ?',
[userId]
);
return rows[0] ?? null;
}
maszynopis
// Clean TypeScript: explicit types replace implicit any
// Before
function process(data) {
return data.map(x => x.v * 2);
}
// After
interface DataPoint {
value: number;
label: string;
}
function doubleValues(dataPoints: DataPoint[]): number[] {
return dataPoints.map(point => point.value * 2);
}
Czyste użycie JavaScript const domyślnie, let gdy ponowne wiązanie jest konieczne i nigdy varCzyste funkcje, czyli takie same dane wejściowe zawsze generują takie same dane wyjściowe, bez efektów ubocznych, są najczystszym elementem składowym logiki JavaScript.
Czysty kod w C#
C# oferuje zaawansowane funkcje do pisania czystego, ekspresyjnego kodu. Ewolucja języka, LINQ, rekordy, dopasowywanie wzorców, typy referencyjne dopuszczające wartości null, konsekwentnie zmierza w kierunku bardziej deklaratywnej i czytelnej składni.
csharp
// Before: magic numbers, verbose loop, mutable state
public double CalculateTotal(List<OrderItem> items)
{
double total = 0;
foreach (var item in items)
{
if (item.Quantity > 10)
total += item.UnitPrice * item.Quantity * 0.9;
else
total += item.UnitPrice * item.Quantity;
}
return total;
}
// After: named constant, LINQ, single expression
private const double BulkDiscountRate = 0.9;
private const int BulkDiscountThreshold = 10;
public double CalculateTotal(IEnumerable<OrderItem> items) =>
items.Sum(item => item.Quantity > BulkDiscountThreshold
? item.UnitPrice * item.Quantity * BulkDiscountRate
: item.UnitPrice * item.Quantity);
Zasady czystego kodu w języku C#: używaj właściwości zamiast pól publicznych do hermetyzacji, wykorzystuj LINQ do deklaratywnych operacji na danych, używaj rekordów do niezmiennych struktur danych, preferuj interfejsy nad konkretnymi typami w sygnaturach metod i używaj typów referencyjnych dopuszczających wartości null (string? vs string) aby wyraźnie określić bezpieczeństwo zerowe.
Czysty kod w Kotlinie
Konstrukcja języka Kotlin ma na celu ograniczenie szablonu, który utrudnia utrzymanie czystości kodu w Javie, a jednocześnie dodanie funkcji, klas danych, funkcji rozszerzeń i zabezpieczeń null, które w naturalny sposób wspomagają utrzymanie czystości kodu.
kotlina
// Before: verbose Java-style Kotlin
class User {
var name: String = ""
var email: String = ""
var age: Int = 0
}
fun processUsers(users: List<User>): List<String> {
val result = mutableListOf<String>()
for (user in users) {
if (user.age >= 18) {
result.add(user.email)
}
}
return result
}
// After: idiomatic clean Kotlin
data class User(val name: String, val email: String, val age: Int)
fun getAdultEmails(users: List<User>): List<String> =
users.filter { it.age >= 18 }.map { it.email }
Kotlin's data class Automatycznie udostępnia funkcje equals, hashCode, copy i toString, eliminując szablon, który sprawia, że klasy danych Java są rozwlekłe i podatne na błędy. Funkcje rozszerzające pozwalają dodawać czyste metody narzędziowe do istniejących klas bez dziedziczenia.
Czysty kod w grach RPG i PL/I: języki starsze, nowoczesne zasady
Zasady czystego kodu dotyczą każdego języka, w tym języków korporacyjnych, które obsługują systemy finansowe, platformy ubezpieczeniowe i aplikacje rządowe na całym świecie. RPG (Report Program Generator) i PL/I są nadal aktywnie utrzymywane w wielu organizacjach, a ta sama dyscyplina, która sprawia, że Java czy Python są czyste, sprawia, że RPG i PL/I są zrównoważone.
Czysty kod w RPG (ILE RPG) :
rpg
// Before: cryptic two-character names, magic numbers
C EVAL D = Q * P * 1.05
C IF Q > 100
C EVAL D = Q * P * 0.95
C ENDIF
// After: meaningful names, named constants, clear intent
/free
dcl-c BULK_DISCOUNT_THRESHOLD 100;
dcl-c BULK_DISCOUNT_RATE 0.95;
dcl-c STANDARD_RATE 1.05;
dcl-proc CalculateOrderTotal;
dcl-pi *N packed(15:2);
quantity packed(7:0) value;
unitPrice packed(9:2) value;
end-pi;
if quantity > BULK_DISCOUNT_THRESHOLD;
return quantity * unitPrice * BULK_DISCOUNT_RATE;
else;
return quantity * unitPrice * STANDARD_RATE;
endif;
end-proc;
/end-free
Kluczowe praktyki czystego RPG: korzystaj z darmowego formatu ILE RPG (/free) składnia zamiast stałego formatu dla czytelności, procedury nazywaj opisowo, zastąp zakodowane na stałe wartości nazwanymi stałymi za pomocą dcl-ci rozkładać długie programy na skoncentrowane procedury, używając dcl-proc.
Czysty kod w PL/I :
zakręt
/* Before: single-letter names, no structure */
CALC: PROC(X, Y) RETURNS(FLOAT);
DCL (X, Y, R) FLOAT;
IF Y > 1000 THEN R = X * Y * 0.1;
ELSE R = X * Y * 0.05;
RETURN(R);
END CALC;
/* After: meaningful names, named constants, clear intent */
DCL PREMIUM_THRESHOLD FIXED DECIMAL(7) INIT(1000);
DCL PREMIUM_RATE FLOAT INIT(0.10);
DCL STANDARD_RATE FLOAT INIT(0.05);
CALCULATE_DISCOUNT: PROC(QUANTITY, UNIT_PRICE) RETURNS(FLOAT);
DCL (QUANTITY, UNIT_PRICE) FLOAT;
DCL SUBTOTAL FLOAT;
SUBTOTAL = QUANTITY * UNIT_PRICE;
IF UNIT_PRICE > PREMIUM_THRESHOLD
THEN RETURN(SUBTOTAL * PREMIUM_RATE);
ELSE RETURN(SUBTOTAL * STANDARD_RATE);
END CALCULATE_DISCOUNT;
Zasady czystego kodu w PL/I odzwierciedlają zasady stosowane w nowoczesnych językach: zrozumiałe identyfikatory (limit 31 znaków w PL/I jest wystarczający dla nazw opisowych), nazwane stałe zamiast magicznych liczb, procedury skoncentrowane na pojedynczym zadaniu i jawna obsługa błędów za pomocą systemu warunkowego ON.
Narzędzia do czystego kodu i analiza statyczna
Ręczne stosowanie zasad czystego kodu poprzez przegląd kodu jest konieczne, ale niewystarczające na dużą skalę. Narzędzia do analizy statycznej automatycznie wymuszają metryki czystego kodu, sygnalizując naruszenia przed ich scaleniem.
| Narzędzie | Języki | Co egzekwuje |
|---|---|---|
| SonarQube / SonarCloud | 30+ języków | Złożoność, duplikacja, zapachy kodu, bezpieczeństwo |
| Styl w kratkę | Java | Konwencje nazewnictwa, formatowanie, struktura |
| PMD | Java, Apex | Duplikat kodu, nieużywane zmienne, złożoność |
| ESLint + typescript-eslint | JavaScript, TypeScript | Styl, złożoność, nieużywany kod, wzorce asynchroniczne |
| Pylint + Radon | Python | PEP 8, złożoność, wskaźnik utrzymywalności |
| Wyostrzanie | C# | Styl kodu, powtarzający się kod, sugestie dotyczące refaktoryzacji |
| Clippy | Rdza | Wzory idiomatyczne, typowe błędy |
| SMART TS XL | COBOL, RPG, PL/I, Java, Python i inne | Złożoność międzyjęzykowa, duplikacja, martwy kod |
Najpopularniejsze wskaźniki czystego kodu mierzone przez narzędzia: złożoność cyklomatyczna (liczba gałęzi decyzyjnych; powyżej 10 oznacza ostrzeżenie, powyżej 20 jest wartością krytyczną), złożoność poznawcza (stopień trudności zrozumienia kodu; wskaźnik specyficzny dla SonarQube, który stanowi udoskonalenie złożoności cyklomatycznej w zakresie pomiaru czytelności) i wskaźnik duplikacji (procent kodu, który jest zduplikowany; powyżej 3% należy zwrócić uwagę).
W jaki sposób SMART TS XL Wymusza czysty kod w bazach kodu przedsiębiorstwa
Wymienione powyżej narzędzia do czystego kodu działają w ramach jednego języka. W środowiskach korporacyjnych, w których współistnieją usługi Java, potoki Pythona, programy wsadowe COBOL, moduły RPG i strumienie zadań JCL, naruszenia czystego kodu w każdym języku muszą być mierzone jednocześnie, a relacje między komponentami w różnych językach są równie ważne, jak jakość pojedynczego pliku.
SMART TS XL'S statyczna analiza kodu Stosuje metryki czystego kodu dla wszystkich języków w środowisku jednocześnie. Złożoność cyklomatyczna, wskaźniki duplikacji, identyfikacja martwego kodu i sprzężenia strukturalne są obliczane dla programów COBOL przy użyciu tej samej metodologii, co dla klas Java, co pozwala uzyskać porównywalne, ujednolicone pomiary jakości w całym portfolio aplikacji.
Funkcja analizy wpływu sprawia, że naruszenia czystego kodu są możliwe do wyegzekwowania na poziomie architektury: gdy akapit w COBOL-u ma silne powiązanie z dziesiątkami innych programów, analiza wpływu pokazuje dokładnie, które komponenty są naruszone, zanim rozpocznie się refaktoryzacja. To przekształca paraliż refaktoryzacji, którego doświadczają zespoły w dużych bazach kodu, w ustrukturyzowany program naprawczy o zdefiniowanym zakresie.
Mapowanie zależności aplikacji identyfikuje naruszenia architektury czystego kodu na poziomie systemu: które komponenty stały się klasami boskimi w skali przedsiębiorstwa, gdzie zależności cykliczne naruszają separację zadań w różnych językach oraz gdzie ta sama logika biznesowa została niezależnie zaimplementowana w wielu systemach, bez wiedzy żadnej z kopii o istnieniu drugiej. To naruszenie DRY w wielu językach, czyli ta sama reguła biznesowa utrzymywana oddzielnie w programie COBOL, usłudze Java i potoku Pythona, jest najkosztowniejszą kategorią naruszenia czystego kodu w systemach korporacyjnych i jest niewidoczne dla żadnego narzędzia jednojęzycznego.
Dla zespołów stosujących zasady czystego kodu podczas modernizacja dziedziczna programy SMART TS XL zapewnia analizę przed refaktoryzacją, dzięki której praca staje się łatwiejsza do wykonania: martwy kod zostaje wykluczony z zakresu przed rozpoczęciem konwersji, komponenty o najwyższym stopniu złożoności zostają zidentyfikowane i traktowane priorytetowo, a pełny graf zależności jest dostępny przed wprowadzeniem jakiejkolwiek zmiany.
Czysty kod to dyscyplina zespołowa, a nie indywidualna praktyka
Zasady zawarte w tym przewodniku łatwiej sformułować niż utrzymać. Każdy programista może napisać czystą funkcję w oderwaniu od reszty. Wyzwaniem jest utrzymanie czystego kodu w całym zespole, w czasie i w bazie kodu, która rośnie wraz ze zmianami w jej twórcach. Wymaga to trzech rzeczy: wspólnych standardów, które wszyscy znają i na które wszyscy się zgadzają, narzędzi, które automatycznie egzekwują te standardy, oraz kultury stopniowego doskonalenia – konsekwentnie stosowanej Reguły Harcerza, dzięki której każdy fragment kodu pozostaje nieco czystszy, niż go zastał.
Najbardziej użytecznym pozostaje sformułowanie Roberta C. Martina: „Czysty kod zawsze wygląda, jakby napisał go ktoś, komu zależy”. Dowodem na to, że komuś zależy, nie jest brak błędów, lecz czytelne nazwy, skupione funkcje, przejrzysta struktura i brak niespodzianek. To właśnie te dowody sprawiają, że warto pracować nad bazą kodu, warto się do niej przyczyniać i warto ją utrzymywać przez lata i zmiany w zespole, które z czasem nagromadzą się w każdym udanym systemie.


