Sparround

Null safety əsasları

Sound null safety-də tip sistemi iki qrupa bölünür: non-nullable Tnullable T?. String name heç vaxt null ola bilməz — bunu compiler zəmanət verir, ona görə çağırış yerində yoxlamaya ehtiyac qalmır. String? name isə null ola bilər və compiler ondan istifadə etməzdən əvvəl yoxlamanı tələb edir.

"Sound" sözü burada mühümdür: yoxlama həm compile-time-da aparılır, həm də runtime-da tip məlumatı buna uyğun gəlir — nəticədə compiler null yoxlamalarını tam silə bilir və kod daha sürətli olur. Bu, TypeScript-in strictNullChecks-indən fərqlidir: orada yoxlama yalnız compile-time-dadır.

Praktik nəticə: null xətası artıq "runtime sürprizi" deyil, dizayn qərarı olur. Sahə T?-dirsə, bu, "bu dəyər həqiqətən yox ola bilər" deməkdir və sən bunu şüurlu seçmisən.

OperatorNə edirNümunə
`?.`Null-aware çıxış — obyekt `null`-dırsa, nəticə `null``user?.name`
`??`Default dəyər — sol tərəf `null`-dırsa sağı qaytarır`name ?? 'Qonaq'`
`??=`Yalnız `null` olduqda mənimsədir`cache ??= compute();`
`!`Bang — "bu `null` deyil" iddiası; səhvdirsə runtime-da atır`user!.name`
`?..`Null-aware cascade`user?..save()..log()`
`?[]`Null-aware indeks`list?[0]`

Flow analysis və promotion. Compiler kodun axınını izləyir: if (name != null) blokunun içində name artıq String? yox, String kimi davranır — buna type promotion deyilir. ?.! yazmağa ehtiyac qalmır.

Promotion həm də erkən qayıtma ilə işləyir: if (name == null) return; sətrindən sonra qalan bütün kodda name promote olunmuş sayılır. Bu, "guard clause" üslubunu Dart-da xüsusilə səmərəli edir.

Promotion işləməyən hallar da var və interview-da məhz onlar soruşulur:

  • Public sahələr — nə final olmayan instans sahəsi, nə də getter promote olunmur; public final sahə də olunmur, çünki alt sinif onu getter kimi override edə bilər
  • Top-level və static dəyişənlər
  • Closure-də dəyişdirilən lokal dəyişənlər

Səbəb: compiler yalnız aralıqda dəyişməyəcəyinə zəmanət verə bildiyi dəyəri promote edir. Public sahə üçün belə zəmanət yoxdur — arada başqa metod onu dəyişə bilər, alt sinif isə onu getter kimi override edə bilər və getter hər çağırışda fərqli dəyər qaytarır.

Bir istisna var: Dart 3.2-dən private `final` sahələr (final String? _token;) promote olunur, çünki compiler bütün kitabxananı görür və sahənin override oluna bilmədiyinə əmindir. Praktik nəticə: modeli private final sahələr üzərində qursan, ! operatoruna ehtiyac özü-özünə azalır.

`!` operatoru nə vaxt dizayn iyidir? Bang compiler-ə "mən daha yaxşı bilirəm" demək deməkdir. Bəzən doğrudan da bilirsən (məsələn, dərhal yuxarıda yoxlanmış lokal dəyər), amma kodda ! sıxlığının artması adətən modelin səhv olduğunu bildirir.

Tipik səbəblər və düzgün həllər:

  • Sahə T?-dir, çünki konstruktorda hazır deyil → `late final` və ya iki ayrı state sinfi
  • Sahə T?-dir, çünki bəzi hallarda mövcud olmur → `sealed` iyerarxiya ilə halları ayır
  • Dəyər null gəlirsə default lazımdır → `??`
  • Sahədən oxuyub istifadə edirsən → lokal dəyişənə köçür (final n = this.name; if (n != null) ...)

Praktikada komandalar avoid_null_checks_in_equality_operators və oxşar lint-lərlə yanaşı, code review qaydası kimi "yeni ! üçün şərh tələb olunur" prinsipini tətbiq edir.

Interview ipucu. "Nullable sahə niyə promote olunmur, lokal dəyişən isə olunur?" — bu, null safety mövzusunun ən çox verilən sualıdır və səthi cavab ("belədir") kifayət etmir. Güclü cavab səbəbi izah edir: "Compiler yalnız aralıqda dəyişməyəcəyinə zəmanət verə bildiyi dəyəri promote edir. Lokal dəyişən üçün bu zəmanət var. Sahə isə başqa metoddan dəyişdirilə bilər, `getter` hər çağırışda fərqli dəyər qaytara bilər, alt sinif onu override edə bilər — ona görə iki oxunuş arasında əlaqə yoxdur." Sonra iki praktik həlli göstər: final local = field; if (local != null) { ... } — bu, ! yazmadan promotion qazandırır; və sahəni private final etmək — Dart 3.2-dən belə sahələr promote olunur.

📚 Mənbələr və sənədlər