
توی قسمت قبلی دیدیم که برنامه نویس ها یه شورش علیه Dart به راه انداختن و سرانجام جناب Dart دستگیر شد و قرار شد که یه دادگاه برای رسیدگی به اتهاماتش تشکیل بشه.
از اون جایی که اتهامات خیلی زیاد بود چند جلسه دادگاه برای رسیدگی به این اتهامات لازم بود و توی این قسمت داستان جلسه اول دادگاه رو با هم میخونیم.
قاضی دادگاه : از جناب Dart تقاضا میشود که به جایگاه تشریف بیاورن و پاسخگوی اتهامات مطرح شده باشند...
اما اولین اتهام چی بود؟
داستان قسمت قبلی یادته؟ که میرزا قلی کلاس آنلاین برگزار کرد و توی جلسه اول یه اتفاقاتی افتاد...
نتیجه ای که میرزا قلی گرفته بود و به بچها گفت این بود:
ولی قل مراد گفتش که من یه کد اینجوری زدم :

و این کد داره دومین نتیجه ای که تو گرفتی رو نقض میکنه.
آخرش به این نتیجه رسیدن که حتمن Dart مشکل داره و باگ داره...
حالا Dart باید به این اتهام پاسخ میداد؟
از اینجا به بعد داستان رو از زبان Dart بشنویم. قبلش بگم که آقای Dart کتابی صحبت میکنه و دیگه این موضوع دست من نیست...
در ابتدا به عنوان مقدمه باید عرض کنم که در من (منظورش از من همون Dart هست) هر موقع از { } استفاده می کنید یک Scope جدید ایجاد می شود. برای مثال وقتی که یک class جدید مینویسید :

با قرار دادن { } یک Scope جدید ایجاد می شود.یا مثلن وقتی که یک function جدید تعریف میکنید :

همین طور وقتی که یک شرط if مینویسید یا یک حلقه for ایجاد میکنید :

همه این ها به دلیل استفاده از { } یک Scope جدید ایجاد میکنن.
میرزا قلمدون که همراه میرزا قلی از قل آباد به نیویورک رفته بود که در دادگاه شرکت کنه یهو بلند شد و گفت : حالا اینایی که گفتین چه ربطی به موضوع داره؟
قاضی دادگاه : تق تق تق ... نظم جلسه را رعایت کنید. جناب Dart ادامه دهید.
جناب Dart : حال در هر Scope به variable های تعریف شده در Scope های بالاتر از خودش (یا به عبارتی به Scope های پدر) دسترسی داریم ولی به Variable های تعریف شده در Scope های فرزند دسترسی نداریم.
همین طور باید اضافه کنم میتوانیم variable ها را براساس Scope ای که در آن تعریف می شوند به سه دسته کلی تقسیم کنیم:
دسته اول variable هایی هستن که خارج از هر Scope یعنی خارج از هر class یا function یا ... تعریف می شوند و در واقع در بدنه اصلی برنامه تعریف می شوند.

دسته دوم instance field های مربوط به class ها هستن.

میرزا قلمدون باز پرید وسط و گفت : instance field چیه ؟
قاضی دادگاه : تق تق تق ... نظم جلسه را حفظ کنید...
میرزا قلمدون رو بخاطر به هم ریختن نظم جلسه از اتاق بیرون کردن...
دسته سوم variable هایی هستند که داخل بدنه یک function یا داخل Scope های مربوط به if یا ... تعریف می شوند و به آن ها Local می گوییم.
برای مثال :

در کد بالا نوع Global و Local را مشاهده میکنید.نوع Global در بدنه اصلی برنامه و نوع Local داخل function(function Scope) تعریف شده اند.
نوع اول یعنی Global ها داخل function قابل دسترسی و استفاده هستن ولی نوع دوم یعنی Local ها فقط و فقط داخل همون scope ای که تعریف شده اند قابل دسترسی هستن و خارج از آن قابل دسترسی نیستن.
دلیل این امر هم این هست که function Scope در واقع فرزند Global Scope محسوب میشود و میتواند به variable های آن دسترسی داشته باشد ولی Global Scope نمی تواند به variable های فرزند خودش یعنی function Scope دسترسی داشته باشد.
مثالی دیگر :

خروجی کد بالا به صورت زیر هست :

میبینید که در کد بالا یک تابع با نام myFunction تعریف کرده ام که برای خودش یک scope ایجاد میکند و داخل این scope به همه variable هایی که در scope خودش و همچنین scope بالاتر (global) تعریف شده اند دسترسی داریم.
همچنین داخل تابع myFunction یک تابع دیگر با نام myInnerFunction تعریف کرده ام که این تابع هم برای خودش یک scope ایجاد میکند({ }) و داخل این scope به همه variable های scope بالاتر (یعنی global و myFunction scope) دسترسی داریم.
ولی داخل globla scope نمیتوانیم به varibale هایی که داخل این دو function تعریف شده اند دسترسی داشته باشیم.همچنین از داخل myFunction نمیتوانیم به variable هایی که داخل myInnerFunction تعریف شده اند دسترسی داشته باشیم.
و یک مثال دیگر :

داخل Instance method های یک کلاس به همه local variable ها (که داخل همان method یا همان scope تعریف شده اند ) و همه instance filed های کلاس به علاوه همه global variable ها ( که خارج از کلاس تعریف شده اند) دسترسی داریم.
و به عنوان مثال آخر :

در کد بالا یک function داریم و داخل آن از یک if استفاده کرده ایم که استفاده از if باعث ایجاد یک Scope جدید می شود ( { } ) و داخل Scope مربوط به myFunction به variable هایی که در if Scope تعریف شده اند دسترسی نداریم ولی داخل Scope مربوط به if به همه ی variable های تعریف شده در Scope های بالاتر دسترسی داریم. در واقع در اینجا if Scope فرزند function Scope محسوب میشود و به variable های آن دسترسی دارد ولی function Scope به variable های فرزند خودش دسترسی ندارد.
پس :
بعد از این مقدمه باید عرض کنم که :
وقتی که یک non-nullable variable را به صورت Local تعریف میکنید نیازی نیست که همان ابتدا آن را مقدار دهی کنید. چون که آنالایزر من (منظورش آنالایزر دارت هست چون خودش دارته) هوشمند هست و میتواند کل بدنه مربوط به آن Scope(مثلن کل بدنه function) را بررسی کند.در این حالت اگر از variable تعریف شده در هیچ کجا استفاده نکنید کار بدون مشکل پیش می رود ولی به محض این که برای اولین بار می خواهید از آن استفاده کنید آنالایزر من کل بدنه مربوط به ان Scope را بررسی و تحلیل می کند و اگر ببینید که شما آن variable را قبل از استفاده مقدار دهی نکرده اید به شما خطا می دهد.
برای مثال :

در کد بالا یک variable از نوع non-nullable تعریف کرده ایم و آن را مقدار دهی نکرده ایم ولی چون که از آن استفاده نکرده ایم هیچ مشکلی رخ نمی دهد.
حالا کد زیر را ببینید :

نوع بازگشتی function رو String کردم و بدون مقداردهی variable آن را return کردم. و یک خطا گرفتم :

همان طور که میبینید این خطا می گوید که که یک non-nullable variable باید قبل از استفاده حتمن مقدار دهی بشود.
به عنوان مثالی دیگر :


میبینید که یک non nullable String با نام StringStatus تعریف کرده ام در موقع return خطا گرفته ام.چون آنالایزر مطمین نیست که در هر شرایطی این variable قبل از استفاده (یعنی قبل از return )مقدار میگیرد.که با افزودن یک else به آنالایزر اطمینان میدهم که در هرصورت این variable قبل از استفاده مقدار دهی میشود و خطا برطرف میشود.

ولی این نکته فقط و فقط برای Local variables صادق است و برای Global variables و Class instance fields نمی توانیم از این نکته استفاده کنیم.چرا که آنالایزر من نمی تواند کل برنامه یا کل یک کلاس را بررسی و تحلیل کند و تشخیص دهد که آیا یک variable که در این Scope ها تعریف شده است قبل از استفاده مقدار دهی شده است یا خیر پس variable هایی از این دو نوع که از نوع non-nullable هستند باید همان ابتدا مقدار دهی شوند.
برای مثال :


در کد بالا یک Global variable داریم که از نوع non-nullable است و باید در همان لحظه تعریف مقداردهی شود, در غیر این صورت با خطا مواجه خواهید شد.
این قضیه برای Field های یک کلاس هم صادق هست :

پس :
ولی اگر یک non-nullable variable از نوع class instance field باشد در شرایط خاصی میتوانیم آن را همان موقع تعریف مقدار دهی نکنیم.
معمولن class instance field ها را می خواهیم در constructor مقدار دهی کنیم ولی آنالایزر باید این امکان را داشته باشد که بفهمد که آیا non-nullable variable تعریف شده, مقدار دهی شد یا خیر؟
در واقع وقتی که یک Local variable تعریف میکینم چون که آنالایزر باید یک سطح کوچک از کد (مثلن بدنه یک function) را بررسی کند و اطمینان حاصل کند که non-nullable variable تعریف شده قبل از اولین استفاده مقدار دهی شده است یا خیر. بررسی و آنالیز این سطح کوچک برای آنالایزر کار دشواری نیست و به همین دلیل به ما اجازه می دهد که در همان ابتدای تعریف non-nullable variable آن را مقدار دهی نکنیم.
ولی در مورد Global variable ها یا class instance field ها چون که آنالایزر باید یک سطح بزرگ از کد را بررسی کند, این عمل راحت نیست و به همین دلیل وقتی non-nullable variable هایی داخل این Scope ها تعریف میکنید شما را اجبار میکند که در همان ابتدا آن رو مقدار دهی کنید.
ولی در مورد class instance field ها میتوانیم دو حالت استثنا را در نظر بگیریم و در آن موارد اجازه داریم که non-nullable variable را همان اول مقدار دهی نکنیم.
قبل از گفتن این دو مورد استثنا باید بگوییم که سه روش برای initialize کردن یک instance field داخل constructor وجود دارد.

?این قسمت توسط آنالایزر قابل رصد شدن هست و آنالایزر میتواند آرگومان های ورودی سازنده های کلاس ها را چک کند.
روش دوم : استفاده از لیست مقدار دهی(initialize list).

?این قسمت هم توسط آنالایزر قابل رصد هست.

?این قسمت یعنی constructor body توسط آنالایزر قابل رصد نیست.
پس با توجه به موارد بالا میتوان گفت که میتوانیم یک instance field از نوع non-nullable تعریف کنیم و آن را همان ابتدا مقداردهی نکنیم به شرط اینکه از روش های اول یا دوم برای مقدار دهی آن استفاده کنیم.
روش اول :

روش دوم :

ولی در صورت استفاده از روش سوم آنالایزر خطا میدهد:


ولی در صورتی که خواستید :
می توانید از کلمه کلیدی late استفاده کنید.
مثال :

یک non-nullable variable در Global Scope که همان ابتدا مقدار دهی نشده است و آنالایزر هم هیچ خطایی اعلام نکرده است.

یک non-nullable instance field داخل یک class که همان ابتدا مقدار دهی نشده است و آنالایزر هم هیچ خطایی اعلام نکرده است.
و اما کلمه کلیدی late چه کار میکند؟
تا این جا دیدیم که بعد از اضافه شدن قابلیت null safety به Dart یک سری از خطاهایی که قبلن در زمان اجرای کد(runtime) دریافت میکردیم به زمان compile منتقل شدند و همان موقعی که در حال کدنویسی هستیم آنالایزر به ما هشدار میدهد.
ولی کلمه کلیدی late به compiler میگوید که دیگر با این variable در زمان compile کد کاری نداشته باش و در واقع برنامه نویس به compiler اعلام میکند که من خودم مدیریت این variable و مقداردهی آن را به عهده میگیرم و لازم نیست که تو در این مورد دخالت کنی.
پس با استفاده کردن از late دیگر compiler کاری به مقداردهی یا عدم مقداردهی این non-nullable variable ندارد و مدیریت آن را به عهده برنامه نویس میگذارد.
حال با این تفاصیر دو اتفاق میوفتد :
مثال :

میبینید که یک non-nullable global variable با استفاده از late تعریف کردم و بدون مقدار دهی از آن استفاده کردم ولی آنالایزر هیچ خطایی مبنی بر اینکه این variable را مقداردهی نکرده ای اعلام نکرد (به دلیل استفاده از late).
پس در زمان نوشتن کد و compile هیچ خطایی نداریم.ولی اگر کد را اجرا کنیم :

همان طور که میبینید یک خطای زمان اجرا یا runtime error دریافت میکنیم.
مثال :

در یک class یک non-nullable field را با late تعریف کردم و بدون مقدار از آن استفاده کردم ولی به دلیل استفاده از late هیچ خطایی در زمان compile نداریم.ولی با اجرای کد :

ولی اگر همین variable را در بدنه constructor مقدار دهی کنم مشکل برطرف میشود :

نکته خیلی مهم :
نیازی نیست برای Local variable از late استفاده کنید.
همچنین نیازی نیست برای nullable variable ها از late استفاده کنید چون در صورت عدم مقداردهی, مقدار پیش فرض یعنی null را میگیرند.
استفاده از late فقط برای non-nullable global variable که میخواهید همان ابتدای تعریف آن را مقدار دهی نکنید یا non-nullable class field که می خواهید ان را همان ابتدای تعریف یا با یکی از دو روش گفته شده برای مقدار دهی در constructor مقداردهی نکنید, مناسب است.
این نکته را هم باید اضافه کنم که نکاتی که در مورد class instance field ها گفتیم در مورد static field های یک class هم صادق است برای تعریف یک non-nullable static field داخل یک class میتوان از late استفاده کرده و مقدار دهی را به بعدن موکول کرد.
و اما نکته پایانی در مورد final variable ها است.تا قبل از null safety اگر یک final variable تعریف میکردیم باید آن را همان ابتدا مقدار دهی میکردیم یا در مورد class instance field باید آن را به یکی از دو روش گفته شده که توسط آنالایزر قابل تحلیل است مقداردهی میکردیم.
مثال :


در مثال بالا یک global variable داریم که از نوع nullable است پس تا اینجا نیازی نیست که آن را همان ابتدا مقدار دهی کنیم چون که میتواند مقدار پیش فرض null را بگیرد. همچنین نیازی به استفاده از کلمه late نیست.
اما به دلیل اینکه این variable از نوع final است باید همان ابتدای تعریف مقدار دهی شود و به همین دلیل آنالایزر خطا میدهد.
ولی میتوان با استفاده از late این مشکل را حل کرد :

پس از کلمه کلیدی late را علاوه بر کاربرد های قبلی میتوان :
استفاده کرد.
عرایض بنده تمام شد و از دادگاه محترم و حاضرین در جلسه تقاضا دارم در صورتی که سوال یا ابهامی دارند مطرج کنند.
با توجه به توضیحات کامل Dart کسی از حاضرین سوالی نبپرسی و همه قانع شدن و دادگاه هم اولین اتهام رو از Dart رد کرد.
خب جلسه اول دادگاه به این صورت به پایان رسید و مثل اینکه Dart خیلی خوب تونست اتهامات وارد شده را از خودش رد کنه. ولی هنوز جلسات دیگه ای برای بررسی سایر اتهامات باقی مونده که داستان اون ها رو توی قسمت های بعدی با هم میخونیم.
ولی حالا من خلاصه مواردی که Dart گفت رو یه بار دیگه براتون میگم :
این ها چون که میتونن مقدار null رو بگیرن.

توی هر Scope ای که تعریف بشن میتونن همون اول مقدار دهی نشن و در این صورت مقدار پیش فرض null رو میگیرن.
این مورد یک استثنا داره و اون هم این که این variable خودش final باشه که توی بخش final میگم.
این ها نمیتونن مقدار null رو بگیرن.
این ها چه nullable باشن و چه non-nullable اگه :
شرط استفاده ازش هم اینه که :
و به هر دلیلی نخوای که توی این شرایط بهش مقدار بدی.
در این صورت از late استفاده کن و با این کار به compiler میگی که برو وایسا کنار و دخالت نکن و compiler هم گوش میکنه و میگه من میرم کنار و کاری به کارت ندارم ولی مسیولیتش با خودت و اگه اینو مقدار ندادی و ازش استفاده کردی موقع اجرای برنامه خطا میگیری و آبروت جلوی کاربر میره.
پس اگه از late استفاده کردی مراقب باش که آبرو ریزی نشه.
درواقع با استفاده از late قابلیت compile time error رو از دست میدیم و موقع کدنویسی هیچ error ای دریافت نمیکنیم و در صورت بی دقتی موقع اجرا خطا میگیریم(runtime)
حالا در پایان این قسمت یه چالش مطرح میکنم و ازت میخوام که با دلیل و منطق و تحلیل درست و حسابی اون رو حلش کنی و جوابش رو بدی...

کد بالا چرا خطا داره و چکار کنیم که درست شه؟
امیدوارم که لذت برده باشی و کلی نکته جدید یاد گرفته باشی.
لایک یادت نره....
تا قسمت بعدی و ادامه ماجرای های null در Dart خدافظ.