„Przybijać gwoździami” i „zahardkodować” to żargonowe terminy oznaczające sztywne ustalanie wartości bezpośrednio w kodzie programu, zamiast wynoszenia ich do ustawień lub konfiguracji. Hardkod to jeden z najbardziej znanych antywzorców w programowaniu, ponieważ obniża elastyczność i możliwość ponownego wykorzystania kodu. Według Refactoring Guru, hardkod utrudnia testowanie, utrzymanie i adaptację aplikacji do różnych środowisk. Świadome używanie stałych zamiast hardkodu to oznaka dojrzałej architektury.
Najważniejsze
Zahardkodować (przybijać gwoździami) – wbudować konkretną wartość w kod programu tak, że do jej zmiany będzie wymagane edytowanie kodu źródłowego i ponowna kompilacja aplikacji. Metafora „przybijania gwoździami” dokładnie oddaje istotę: wartość jest zamocowana na sztywno i można ją oderwać od kodu tylko z wysiłkiem.
Przykład hardkodu – adres URL serwera zapisany jako ciąg znaków bezpośrednio w treści funkcji. Jeśli serwer przenosi się na inny adres, programista musi znaleźć ten ciąg w kodzie, zmienić go, ponownie zbudować aplikację i wdrożyć wydanie. W aplikacji z prawidłową architekturą taki URL zostałby wyniesiony do pliku konfiguracyjnego, zmiennej środowiskowej lub serwisu konfiguracji.
Termin „przybijać gwoździami” ma bardziej emocjonalne zabarwienie: podkreśla, że wartość jest wstawiona na sztywno i bez możliwości szybkiej wymiany. W polskojęzycznym środowisku oba wyrażenia są używane jako pełne synonimy z negatywną konotacją. Czasami hardkod jest ironicznie nazywany „stałą wyniesioną do osobnej stałej ze stałej”.
Hardkod to antywzorzec, ponieważ narusza zasady utrzymywalności, testowalności i rozszerzalności kodu. W kodzie, w którym wartości są „przybite gwoździami”, każda zmiana środowiska, projektu lub logiki wymaga ręcznego wyszukiwania i zastępowania w źródłach. Zwiększa to ryzyko błędów i spowalnia rozwój.
Rozważmy konkretne konsekwencje hardkodu na przykładzie typowej aplikacji mobilnej. Jeśli odstęp wszystkich przycisków jest określony liczbą w kodzie, a nie przez zasób – zmiana projektu będzie wymagać znalezienia wszystkich wystąpień i zastąpienia. Jeśli URL punktu końcowego jest sztywno wpisany – przełączanie między środowiskami (dev, stage, prod) jest niemożliwe bez ponownej kompilacji.
| Konsekwencja | Opis | Poziom krytyczności |
|---|---|---|
| Trudność utrzymania | Zmiana wymaga przeszukania całego kodu | Wysoki |
| Błędy przy kopiowaniu | Nie wszystkie wystąpienia są znajdowane i zastępowane | Wysoki |
| Brak możliwości testowania | Nie można podstawić danych testowych | Średni |
| Problemy z lokalizacją | Teksty w kodzie nie są tłumaczone | Średni |
| Utrudniony code-review | Osoba sprawdzająca musi pamiętać wszystkie konteksty | Niski |
Funkcja, która używa magicznych liczb i sztywno wpisanych ciągów znaków, to klasyka hardkodu. Po miesiącu autor nie będzie pamiętać, co oznaczają 18, 0.07 i 2.5. Po roku – nikt w zespole nie odważy się zmieniać tych liczb, obawiając się zepsucia logiki. Wyniesienie wartości do nazwanych stałych sprawia, że kod staje się samodokumentujący.
// Źle: magiczne liczby i ciągi znaków
fun calculatePrice(base: Double): Double {
val tax = base * 0.07
val tip = base * 0.15
val discount = if (base > 100) 10 else 0
return base + tax + tip - discount
}
Zahardkodowany URL bazy danych nie pozwoli uruchomić testów na lokalnej in-memory bazie danych. Programista będzie musiał uruchomić pełnoprawny serwer lub modyfikować kod przed testowaniem. Wyniesienie konfiguracji z kodu rozwiązuje problem: testy używają parametrów testowych, produkcja – produkcyjnych, a kod się przy tym nie zmienia.
Hardkod to antywzorzec, ale istnieją legitymowane wyjątki, kiedy sztywno wpisana wartość jest nie tylko dopuszczalna, ale i preferowana. Granica przebiega na osi zmienności: jeśli wartość nigdy lub prawie nigdy nie zmienia się w cyklu życia aplikacji, można ją zahardkodować. Jeśli choć potencjalnie może się zmienić – wynieś do konfiguracji.
Stałe matematyczne i fizyczne – liczba Pi, przyspieszenie grawitacyjne, liczba milisekund w sekundzie – są bezpieczne dla hardkodu. Są określone przez naturę lub standardy i nie ulegną zmianie. Rozmiary tablic-stałych określone przez specyfikację również można sztywno ustalić, ale z komentarzem o pochodzeniu liczby.
Liczba milisekund w sekundzie to stabilna stała określona przez standard czasu. Nie ma sensu wynosić jej do konfigu, ponieważ nigdy się nie zmieni. Jednak nawet takie stałe lepiej deklarować z zrozumiałą nazwą, aby kod nie zawierał „magicznych liczb”: zamiast 1000 pisać MILLISECONDS_IN_SECOND.
// Uzasadniony hardkod: stabilne stałe
private const val MILLIS_IN_SECOND = 1000
private const val LOGIN_TIMEOUT_SECONDS = 30
fun formatDuration(ms: Long): String {
val seconds = ms / MILLIS_IN_SECOND
return "${seconds} sec."
}
Istnieje kilka sprawdzonych sposobów uniknięcia hardkodu, z których każdy jest odpowiedni dla swojego typu wartości. Wybór alternatywy zależy od tego, jak często zmienia się wartość i kto ją zmienia: programista, devops czy użytkownik końcowy.
Dla URL serwerów, kluczy API i flag funkcji używaj plików konfiguracyjnych w formatach JSON, YAML lub TOML. W Androidzie to build.gradle z buildConfigField lub res/values/config.xml. W iOS – Info.plist lub xcconfig. Konfigi są kompilowane razem z aplikacją, ale mogą być różne dla różnych schematów budowania.
Dla sekretów (tokeny, hasła) i parametrów środowiska używaj zmiennych środowiskowych. Nie trafiają one do repozytorium i mogą się różnić na serwerach dev, stage i prod. W programowaniu mobilnym zmienne środowiskowe często emuluje się przez schematy budowania Xcode lub build flavors w Gradle.
Ciągi znaków, kolory, rozmiary, obrazy powinny być wyniesione do plików zasobów: strings.xml w Androidzie, Localizable.strings w iOS, plików ARB we Flutter. Upraszcza to lokalizację, adaptację do różnych ekranów i trybu ciemnego. Zmiana ciągu w zasobach nie wymaga przepisywania kodu.
<!-- Android: res/values/strings.xml -->
<resources>
<string name="app_name">MyApp</string>
<string name="api_base_url">https://api.example.com</string>
</resources>
Dla serwisów i dostawców używaj Dependency Injection przez Dagger, Hilt lub Koin w Androidzie, Swinject w iOS. Frameworki DI pozwalają podmieniać implementacje w locie – do testów, dla różnych środowisk, dla różnych użytkowników. To najwyższy poziom abstrakcji, gdzie „przybicie” wartości zastępuje się wstrzyknięciem z zewnątrz.
Refaktoryzacja hardkodu to proces wynoszenia sztywno wpisanych wartości do konfiguracji lub zasobów. To jedna z najbezpieczniejszych operacji refaktoryzacji, jeśli wykonuje się ją metodycznie. Opisana poniżej sekwencja jest odpowiednia dla każdego języka i platformy.
Wyszukiwanie można wykonać przez IDE (Search in Project) lub skryptem. Szukaj ciągów znaków, URL, literałów liczbowych, rozmiarów, timeoutów. Szczególną uwagę zwróć na powtarzające się wartości: jeśli ta sama liczba występuje w pięciu miejscach, to kandydat do wyniesienia do stałej. Użyj grep lub wbudowanego wyszukiwania IDEA / Xcode.
Dla każdej znalezionej wartości utwórz stałą z sensowną nazwą. Grupuj stałe według modułów lub klas. Nazwa powinna wyjaśniać, co oznacza wartość, a nie jak jest używana: API_TIMEOUT, a nie TIMEOUT_30. Po zastąpieniu żadna liczba w kodzie nie powinna pozostać bez wyjaśnienia.
// Przed: magiczna liczba 0.4
let cardHeight = screenHeight * 0.4
// Po: nazwana stała
private let cardHeightRatio: CGFloat = 0.4
let cardHeight = screenHeight * cardHeightRatio
Jeśli wartość może się zmieniać między kompilacjami lub środowiskami – wynieś ją do pliku konfiguracyjnego lub zasobów aplikacji. Dla ciągów znaków używaj plików lokalizacyjnych. Dla URL – build config lub xcconfig. Dla rozmiarów – pliki zasobów (dimens.xml w Androidzie). Sprawdź, że aplikacja kompiluje się i działa poprawnie po wyniesieniu.
Po refaktoryzacji napisz test, który sprawdza, że konfiguracja ładuje się poprawnie i wartości odpowiadają oczekiwanym. Jeśli w przyszłości ktoś zmieni konfig, test wskaże na niezgodność. Test konfiguracji to szybki i niezawodny sposób zapobiegania regresji.
Po wyniesieniu do konfigu sprawdź, że wszystkie miejsca, w których używana była stara wartość, odnoszą się do jednego źródła. Usuń zakomentowany kod i stare stałe, które nie są już używane. Sfinalizuj refaktoryzację commitem z wiadomością opisującą, jakie wartości i dokąd zostały wyniesione.
Często zadawane pytania
Zahardkodować – sztywno wpisać wartość w kodzie źródłowym zamiast wyniesienia jej do konfiguracji lub zasobów. To sprawia, że kod jest mniej elastyczny i bardziej skomplikowany w utrzymaniu.
Hardkod utrudnia zmianę zachowania aplikacji, przeszkadza w testowaniu, tworzy duplikację i zwiększa ryzyko błędów przy kopiowaniu. Zmiana zahardkodowanej wartości wymaga ponownej kompilacji i wydania aplikacji.
Dopuszczalny dla stałych matematycznych, stabilnych wartości, które nie zmieniają się w cyklu życia aplikacji, oraz dla tymczasowych prototypów. W produkcji nawet stałe warto wynosić do nazwanych zmiennych.
Znajdź wszystkie magiczne liczby przez wyszukiwanie, zastąp je nazwanymi stałymi lub wynieś do pliku konfiguracyjnego. Napisz test sprawdzający ładowanie konfiguracji. Usuń duplikaty i zrób commit z opisem zmian.
Stała to nazwana wartość w kodzie, dostępna do zmiany w jednym miejscu. Hardkod to nienazwane wartości rozrzucone po kodzie. Dobra praktyka: zawsze używaj nazwanych stałych z sensownymi nazwami.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również