„Заковване“ и „хардкод“ са жаргонни термини, означаващи твърдо фиксиране на стойности директно в кода на програмата, вместо да ги изнасяме в настройки или конфигурация. Хардкодът е един от най-известните анти-шаблони в разработката, тъй като намалява гъвкавостта и повторната използваемост на кода. Според Refactoring Guru, хардкодът затруднява тестването, поддръжката и адаптирането на приложението към различни среди. Осъзнатото използване на константи вместо хардкод е признак на зряла архитектура.
Основни
Хардкодвам (заковавам) – вграждам конкретна стойност в кода на програмата така, че за промяната ѝ е необходимо редактиране на изходния код и прекомпилиране на приложението. Метафората „заковаване“ точно отразява същността: стойността е здраво фиксирана и може да бъде отделена от кода само с усилие.
Пример за хардкод – URL на сървър, записан като низ директно в тялото на функция. Ако сървърът се премести на друг адрес, разработчикът трябва да намери низа в кода, да го промени, да изгради наново приложението и да пусне нова версия. В приложение с правилна архитектура такъв URL би бил изнесен в конфигурационен файл, променлива на средата или услуга за конфигурация.
Терминът „заковаване“ има по-емоционален заряд: подчертава, че стойността е вградена здраво и без възможност за бърза смяна. В българскоезичната среда и двата израза се използват като пълни синоними с отрицателна конотация. Понякога хардкодът се нарича иронично „константа, изнесена в отделна константа от константа“.
Хардкодът е анти-шаблон, защото нарушава принципите на поддръжка, тестване и разширяемост на кода. В код, където стойностите са „заковани“, всяка промяна на средата, дизайна или логиката изисква ръчно търсене и заместване в източниците. Това увеличава риска от грешки и забавя разработката.
Нека разгледаме конкретните последици от хардкода на примера на типично мобилно приложение. Ако разстоянието на всички бутони е зададено с число в кода, а не чрез ресурс – промяната на дизайна ще изисква намиране на всички срещания и замяната им. Ако URL на крайната точка е твърдо записан – превключването между среди (dev, stage, prod) е невъзможно без прекомпилиране.
| Последица | Описание | Ниво на критичност |
|---|---|---|
| Трудност при поддръжка | Промяната изисква търсене в целия код | Високо |
| Грешки при копиране | Не всички срещания се намират и заменят | Високо |
| Невъзможност за тестване | Не могат да се подменят тестови данни | Средно |
| Проблеми с локализацията | Текстовете в кода не се превеждат | Средно |
| Усложняване на code-review | Проверяващият трябва да помни всички контексти | Ниско |
Функция, която използва магически числа и твърдо записани низове, е класика на хардкода. След месец авторът няма да помни какво означават 18, 0.07 и 2.5. След година – никой в екипа няма да смее да промени тези числа, страхувайки се да не счупи логиката. Изнасянето на стойности в именувани константи прави кода самодокументиращ се.
// Лошо: магически числа и низове
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
}
Хардкоднатият URL на базата данни няма да позволи пускането на тестове върху локална in-memory база данни. Разработчикът ще трябва да вдигне пълноценен сървър или да коригира кода преди тестване. Изнасянето на конфигурацията от кода решава проблема: тестовете използват тестови параметри, продукцията – реални параметри, а кодът не се променя.
Хардкодът е анти-шаблон, но съществуват легитимни изключения, когато твърдо записаната стойност не само е допустима, но и е за предпочитане. Границата минава по оста на променливост: ако стойността никога или почти никога не се променя в жизнения цикъл на приложението, може да се хардкодне. Ако поне потенциално може да се промени – изнесете в конфигурация.
Математически и физически константи – числото Пи, ускорението на гравитацията, броят милисекунди в секунда – са безопасни за хардкод. Те са определени от природата или стандартите и няма да се променят. Размерите на константни масиви, определени от спецификацията, също могат да бъдат твърдо фиксирани, но с коментар за произхода на числото.
Броят милисекунди в секунда е стабилна константа, определена от стандарта за време. Няма смисъл да се изнася в конфиг, защото никога няма да се промени. Въпреки това, дори такива константи е по-добре да се декларират с разбираемо име, така че кодът да не съдържа „магически числа“: вместо 1000 пишете MILLISECONDS_IN_SECOND.
// Оправдан хардкод: стабилни константи
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."
}
Съществуват няколко доказани начина да се избегне хардкод, всеки от които е подходящ за своя тип стойности. Изборът на алтернатива зависи от това колко често се променя стойността и кой я променя: разработчикът, devops или крайният потребител.
За URL на сървъри, API ключове и функционални флагове използвайте конфигурационни файлове във формати JSON, YAML или TOML. На Android това е build.gradle с buildConfigField или res/values/config.xml. На iOS – Info.plist или xcconfig. Конфиговете се компилират заедно с приложението, но могат да бъдат различни за различните схеми на компилиране.
За тайни (токени, пароли) и параметри на средата използвайте променливи на средата. Те не попадат в хранилището и могат да се различават на dev, stage и prod сървърите. В мобилната разработка променливите на средата често се емулират чрез Xcode схеми за компилиране или build flavor-и в Gradle.
Низове, цветове, размери, изображения трябва да бъдат изнесени в ресурсни файлове: strings.xml на Android, Localizable.strings на iOS, ARB файлове във Flutter. Това опростява локализацията, адаптирането към различни екрани и тъмния режим. Промяната на низ в ресурсите не изисква пренаписване на кода.
<!-- Android: res/values/strings.xml -->
<resources>
<string name="app_name">MyApp</string>
<string name="api_base_url">https://api.example.com</string>
</resources>
За услуги и доставчици използвайте Dependency Injection чрез Dagger, Hilt или Koin на Android, Swinject на iOS. DI рамките позволяват подмяна на имплементации в движение – за тестове, за различни среди, за различни потребители. Това е най-високото ниво на абстракция, при което „заковаването“ на стойността се заменя с инжектиране отвън.
Рефакторирането на хардкод е процес на изнасяне на твърдо записани стойности в конфигурация или ресурси. Това е една от най-безопасните операции по рефакториране, ако се изпълнява методично. Описаната по-долу последователност е подходяща за всеки език и платформа.
Търсенето може да се извърши чрез IDE (Search in Project) или скрипт. Търсете низове, URL, числови литерали, размери, таймаути. Особено внимание на повтарящите се стойности: ако едно и също число се среща на пет места, това е кандидат за изнасяне в константа. Използвайте grep или вграденото търсене на IDEA / Xcode.
За всяка намерена стойност създайте константа с осмислено име. Групирайте константите по модули или класове. Името трябва да обяснява какво означава стойността, а не как се използва: API_TIMEOUT, а не TIMEOUT_30. След замяната нито едно число в кода не трябва да остане без обяснение.
// Преди: магическо число 0.4
let cardHeight = screenHeight * 0.4
// След: именувана константа
private let cardHeightRatio: CGFloat = 0.4
let cardHeight = screenHeight * cardHeightRatio
Ако стойността може да се променя между компилации или среди – изнесете я в конфигурационен файл или ресурси на приложението. За низове използвайте файлове за локализация. За URL – build config или xcconfig. За размери – ресурсни файлове (dimens.xml на Android). Проверете, че приложението се компилира и работи коректно след изнасянето.
След рефакториране напишете тест, който проверява, че конфигурацията се зарежда правилно и стойностите съответстват на очакваните. Ако в бъдеще някой промени конфига, тестът ще посочи несъответствието. Тестът за конфигурация е бърз и надежден начин за предотвратяване на регресия.
След изнасяне в конфиг проверете, че всички места, където се е използвала старата стойност, сочат към единен източник. Премахнете коментирания код и старите константи, които вече не се използват. Финализирайте рефакторирането с комит, описващ кои стойности и къде са изнесени.
Често задавани въпроси
Хардкодвам – твърдо записвам стойност в изходния код, вместо да я изнеса в конфигурация или ресурси. Това прави кода по-малко гъвкав и по-труден за поддръжка.
Хардкодът затруднява промяната на поведението на приложението, пречи на тестването, създава дублиране и увеличава риска от грешки при копиране. Промяната на хардкодната стойност изисква прекомпилиране и преиздаване на приложението.
Допустим за математически константи, стабилни стойности, които не се променят в жизнения цикъл на приложението, и за временни прототипи. В продукция дори константите е по-добре да се изнасят в именувани променливи.
Намерете всички магически числа чрез търсене, заменете ги с именувани константи или ги изнесете в конфигурационен файл. Напишете тест, проверяващ зареждането на конфигурацията. Премахнете дубликатите и направете комит с описание на промените.
Константа – именувана стойност в кода, достъпна за промяна на едно място. Хардкод – неименувани стойности, разпръснати из кода. Добра практика: винаги използвайте именувани константи с осмислени имена.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също