Heisenbug: nədir, niyə yaranır və tutma metodları

Müəllif: IT Sectr Dərc olunub: 2026-07-29 Oxuma vaxtı: 10 dəq

Heisenbug — debug etməyə çalışdıqda yox olan səhv. Termin Heisenberg qeyri-müəyyənlik prinsipindən gəlir: müşahidə sistemin davranışına təsir edir. Mobil inkişafda Heisenbug ən çətin problemlərdən biridir, çünki standart debug metodları (loglar, breakpointlər, əlavə kod) proqramın vəziyyətini dəyişir və səhvi gizlədir. Yaranma səbəblərini və tutulmaz səhvlərlə mübarizə metodlarını nəzərdən keçirək.

Əsas məqamlar

  • Race condition — Heisenbug-un əsas səbəbi: debug zamanı timing dəyişikliyi problemi maskalayır
  • Bohrbug — proqnozlaşdırıla bilən səhv, Heisenbug-dan fərqli olaraq asan təkrarlanır
  • Mandelbug — mürəkkəb səbəb-nəticə əlaqəsi olan, ilkin şərtlərə həssas səhv
  • ThreadSanitizer — timing-ə təsir etməyən məlumat yarışlarını aşkarlamaq üçün alət
  • Deterministik testlər — Heisenbug-u təkrarlamağın yeganə etibarlı yolu

Mobil inkişafda Heisenbug nədir?

Heisenbug — istehsal mühitində və ya normal iş zamanı özünü göstərən, lakin debug mühitində təkrarlanmağa çalışdıqda yox olan səhvlər sinfi. Termin 1980-ci illərdə proqramçı Jim Gray tərəfindən paylanmış sistemlər kontekstində təqdim edilmişdir, lakin bu gün asinxron təbiətinə görə mobil tətbiqlər üçün ən aktualdır.

Əsas səbəb: standart debug alətləri icra mühitini dəyişir. Breakpoint axını bir neçə millisaniyə dayandırır, loglama sinxron I/O əlavə edir, əlavə yoxlamalar əməliyyatların sırasını dəyişir. Çoxaxınlı mühitdə hətta mikrosaniyəlik gecikmə belə axınların icra sırasını dəyişə və məlumat yarışını gizlədə bilər.

Microsoft Research (2022) məlumatlarına görə, çoxaxınlı mobil tətbiqlərdə bütün səhvlərin təxminən 15-25%-i Heisenbug kimi təsnif edilir. Bir Heisenbug-u tapmaq və düzəltmək üçün vaxt adi səhvdən orta hesabla 5-10 dəfə çoxdur, çünki birbaşa təkrarlamaq mümkün deyil.

Heisenbug nümunəsi

Tətbiq istehsal mühitində siyahını sürətli çəkərkən çökür, lakin debugger qoşulduqda və ya loglar əlavə edildikdə mükəmməl işləyir. Səbəb: UI axını (RecyclerView yeniləməsi) və fon axını (adapter məlumatlarının yeniləməsi) arasında məlumat yarışı. Loglar gecikmə əlavə edir, bu da axınları təsadüfi şəkildə sinxronlaşdırır.

Bohrbug, Mandelbug, Heisenbug: səhvlərin təsnifatı

Bohrbug — proqnozlaşdırıla bilən, sabit təkrarlanan səhv. Borun atom modelinə bənzətmə ilə adlandırılmışdır: atom kimi, səhv hər müşahidədə eyni davranır. Nümunə: məlumat yüklənməmişdən əvvəl düyməni basdıqda NullPointerException. Standart vahid testi ilə müalicə olunur.

Mandelbug — mürəkkəb, xaotik səbəb-nəticə əlaqəsi olan səhv (Mandelbrot çoxluğuna bənzətmə ilə adlandırılmışdır). Yalnız müəyyən şərtlər kombinasiyasında özünü göstərir: OS versiyası, cihaz modeli, şəbəkə vəziyyəti, ay fazası. Heisenbug-dan fərqi ondadır ki, debug zamanı yox olmur — problem təkrarlamanın mürəkkəbliyindədir, alətlərin davranışı dəyişdirməsində deyil.

Heisenbug — məhz debug alətləri səbəbindən yox olan səhv. Log əlavə etsəniz — səhv yox olur. Breakpoint qoysanız — səhv özünü göstərmir. Hər şeyi silsəniz — səhv qayıdır. Əsas səbəb: debug zamanı dəyişdirilmiş timing.

TipTəkrarlanmaDebug reaksiyasıNümunə
Bohrbug100%DəyişmirBoş siyahıda NPE
MandelbugXaotikDəyişmirAndroid 12, Samsung, aşağı batareyada çökmə
HeisenbugYalnız debug olmadanYox olurLoglarla yox olan race condition
SchrödinbugKodda özünü göstərmirBaxdıqda görünürKodda görünən, lakin heç vaxt işləməyən səhv

Heisenbug-un yaranmasının əsas səbəbləri

Race condition — Heisenbug səbəbləri arasında bir nömrə. İki axın sinxronizasiya olmadan ümumi məlumatlara müraciət edir. Debugger gecikmə tətbiq edir, bunun nəticəsində axınlar təbii şəkildə sinxronlaşmağa vaxt tapır. Debugger olmadan icra sırası proqnozlaşdırıla bilməz.

Timing-dən asılı səhvlər — yalnız müəyyən icra sürətində özünü göstərən səhvlər. Məsələn, növbəti əməliyyat başlamazdan əvvəl bitməli olan animasiya. Debuggerdə animasiya daha yavaş gedir və əməliyyat animasiya bitdikdən sonra başlamağa vaxt tapır. İstehsal mühitində isə əksinə.

kotlin
// Race condition nümunəsi — tipik Heisenbug
class ListViewModel : ViewModel() {
    private var items = mutableListOf<String>()

    fun loadFromNetwork() {
        viewModelScope.launch(Dispatchers.IO) {
            val result = api.fetchItems()
            items.addAll(result) // ❌ Thread-safe deyil
        }
    }

    fun getItems(): List<String> = items.toList()
    // Race condition: getItems oxuması loadFromNetwork yazması ilə üst-üstə düşə bilər
}

Kompilyator optimallaşdırması — kompilyator (JIT, ART, Kotlin/Native) optimallaşdırma üçün təlimatları yenidən sıralaya bilər. Debug build-də optimallaşdırmalar söndürülür və kod “yazıldığı kimi” icra olunur. Release build-də kompilyator əməliyyatların sırasını dəyişir, bu da kodda gizli fərziyyələri üzə çıxara bilər.

  • ThreadLocal — digər axınlara görünməyən thread-local dəyişənlərin səhv istifadəsi
  • İnisializə olunmamış dəyişənlər — sinif sahələrinin standart dəyərlərinə güvənən kod
  • GCD/dispatch queues — iOS-da concurrent queue-lərdə blokların qeyri-müəyyən icra sırası
  • Bufferləşdirilmiş I/O — bufer dolana qədər məlumat diskə yazılmır

Tutulmaz səhvləri yaxalamaq strategiyaları

ThreadSanitizer (TSan) — C/C++ və Kotlin/Native-də məlumat yarışlarını aşkarlamaq üçün Google aləti. Build-ə daxil edilir və sinxronizasiya olmadan paylaşılan yaddaşa hər girişi aşkarlayır. Loglardan fərqli olaraq, TSan timing-ə təsir etmir, çünki I/O vasitəsilə deyil, instrumented code vasitəsilə işləyir.

Deterministik testlər — real asinxronluğu idarə olunan ilə əvəz edin. İcra sırasına tam nəzarət üçün TestDispatcher (Kotlin), RxJava Plugins və ya GCD test queues (iOS) istifadə edin. Konkret ssenarilər təyin edin: axın A icra olunur, sonra B, sonra A yenidən.

Tsiklik loglama — diskə deyil, yaddaşda tsiklik buferə loglama. Səhv baş verdikdə, bufer fayla saxlanılır. Yaddaşa yazma nanosaniyələr çəkdiyindən (disk I/O üçün millisaniyələr əvəzinə), belə log timing-ə təsir etmir və Heisenbug-u maskalamır.

kotlin
class CyclicBuffer(val capacity: Int = 1000) {
    private val buffer = ArrayDeque<String>(capacity)
    private val lock = Any()

    fun log(message: String) {
        synchronized(lock) {
            if (buffer.size >= capacity) buffer.removeFirst()
            buffer.addLast(message)
        }
    }

    fun flush() {
        synchronized(lock) { buffer.forEach { fileWriter.write(it) } }
    }
}

İstehsal mühitində loglama — səhv lokal təkrarlanmırsa, istehsalda məlumat toplayın. Firebase Crashlytics logs, Sentry Breadcrumbs və ya xüsusi tsiklik logger istifadə edin. Vacib: loglama asinxron olmalı və performansa minimal təsir göstərməlidir.

Arxitektura səviyyəsində Heisenbug profilaktikası

Vəziyyətin izolyasiyası — paylaşılan mutable state-i minimallaşdırın. Hər komponent digər komponentlərdən birbaşa yazmaq üçün əlçatmaz olan öz izolyasiya edilmiş vəziyyətinə malik olmalıdır. Unidirectional Data Flow (UDF) istifadə edin — vəziyyət bir istiqamətdə axır: Event → Reducer → State → UI.

Funksional yanaşma — yan təsirləri olmayan təmiz funksiyaları test etmək və debug etmək daha asandır. Yan təsirləri (şəbəkə, verilənlər bazası, fayllar) ciddi müəyyən edilmiş təbəqələrdə (repository, data source) izolyasiya edin. Funksional koddaxıl səhvlər praktiki olaraq mümkün deyil.

Strict mode — debug build-də Android StrictMode-u aktivləşdirin. Threading policy pozuntularını (main thread-də şəbəkə, main thread-də disk I/O) aşkarlayır və istisna atır. Bu, potensial Heisenbug-u dərhal görünən deterministik Bohrbug-a çevirir.

kotlin
class DebugApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()
                    .penaltyLog()
                    .build()
            )
        }
    }
}

Asinxronluğa diqqət yetirən code review — prosesin məcburi hissəsi. Hər pull request paylaşılan mutable state, thread-safe olmayan kolleksiyalar, sinxronizasiya olmaması üçün yoxlanmalıdır. Müəyyən nümunələri avtomatik qadağan etmək üçün lint qaydaları istifadə edin (məsələn, synchronized olmadan MutableList-ə giriş).

Tez-tez verilən suallar

Heisenbug-u tapmaq niyə bu qədər çətindir?

Çünki standart metodlar — breakpointlər, loglar, print — icra mühitini o qədər dəyişir ki, səhv özünü göstərməyi dayandırır. Debugger bütün axınları onlarla millisaniyə dayandırır. Bu müddət ərzində səhvə səbəb olan race condition təbii şəkildə həll olunur. İcra timinginə təsir etməyən alətlər lazımdır.

Heisenbug Mandelbug-dan nə ilə fərqlənir?

Mandelbug şərtlərin mürəkkəbliyi səbəbindən çətin təkrarlanır, lakin debug alətləri onun özünü göstərməsinə təsir etmir. Heisenbug isə məhz debug alətlərindən yox olur. Mandelbug nümunəsi: yalnız Android 11, 3 GB RAM və batareya səviyyəsi 15%-dən aşağı olan cihazlarda çökmə. Heisenbug nümunəsi: Log.d() əlavə etdikdə yox olan race condition.

CI/CD-də Heisenbug-u necə test etməli?

Flaky test detection işə salın — bəzən uğursuz, bəzən uğurlu olan testlər. Android-də test izolyasiyası üçün Android Test Orchestrator istifadə edin. Debug testlərinə StrictMode əlavə edin. Build-i ThreadSanitizer ilə instrument edin. Test >5% hallarda flaky-dirsə, onu potensial Heisenbug hesab edin və merge-dən əvvəl araşdırın.

Flow/Coroutines Heisenbug-dan qaçmağa kömək edirmi?

Qismən. Kotlin-də Flowstructured concurrency paylaşılan mutable state miqdarını azaldır və axın idarəçiliyini sadələşdirir. Lakin korutinler thread təhlükəsizliyinə zəmanət vermir: iki korutin paylaşılan state-ə malikdirsə, race condition hələ də mümkündür. Ümumi state-i qorumaq üçün Mutex və ya korutinler arasında məlumat ötürmək üçün Channel istifadə edin.

Heisenbug yalnız istehsal mühitində özünü göstərirsə nə etməli?

Səhv zamanı avtomatik boşaltma ilə yaddaşda tsiklik log buferi istifadə edin. Xüsusi breadcrumbs ilə Crashlytics və ya Sentry vasitəsilə ətraflı monitorinq əlavə edin. Android üçün ANR detection aktivləşdirin və trace-lərə baxın. Səhv race condition-dırsa, istehsal yükünə yaxın debug build-də ThreadSanitizer problemi aşkarlaya bilər.

Nəticə

  • Heisenbug — debug cəhdində yox olan səhv; əsas səbəb — developer alətləri ilə timing dəyişikliyi
  • Race condition — mobil tətbiqlərdə Heisenbug-un əsas səbəbi, xüsusilə asinxron kodda
  • Bohrbug (100% təkrarlanan) və Mandelbug (xaotik) — Heisenbug ilə qarışdırılmamalı olan digər səhv tipləri
  • ThreadSanitizer — icra timinginə təsir etməyən məlumat yarışlarını aşkarlamaq üçün ən yaxşı alət
  • Disk əvəzinə yaddaşda tsiklik loglama — Heisenbug-u maskalamadan məlumat toplama üsulu
  • Unidirectional Data Flow və paylaşılan mutable state-in minimallaşdırılması — bütöv bir səhv sinfinin arxitektura profilaktikası
  • Debug build-də StrictMode potensial Heisenbug-u dərhal görünən deterministik Bohrbug-a çevirir

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun