برنامه نویسی ۱۸۰۱ بازدید

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

مقدمه

نظراتی که در وب سایت‌های پرسش و پاسخ با موضوع برنامه نویسی در خصوص null مطرح می‌شوند، اغلب در افراد ایجاد سردرگمی می‌کنند. حتی برخی از برنامه نویسان سعی می‌کنند کلاً از null استفاده نکنند. چرا که استفاده از آن را به اصطلاح یک «اشتباه میلیون دلاری» (Million Dollar Mistake) تلقی می‌کنند. این اصطلاحی است که توسط «تونی هوار» (Tony Hoare) خالق null ابداع شده است. حال در ادامه و با هدف زمینه‌سازی برای پاسخ به این سوال که null چیست به ارائه یک مثال پرداخته شده است.

مثالی ساده به عنوان پیش درآمد

در این بخش از مقاله «null چیست» یک مثال ساده ارائه می‌شود. فرض بر این است که «نشانی ایمیل فردی به نام آلیس (با نام email_address) به null اشاره می‌کند». این به چه معناست؟ آیا این یعنی آلیس نشانی ایمیلی ندارد؟ یا اینکه نشانی ایمیل او ناشناخته است؟ آیا نشانی ایمیل او محرمانه است؟ یا این به سادگی یعنی اینکه متغیر email_address تعریف نشده (Undefined) یا مقداردهی اولیه نشده (Uninitialized) است؟ در این نوشتار توضیحاتی به بیان ساده ارائه شده است تا هر شخصی با مطالعه این مقاله بتواند بدون تردید به این سوالات پاسخ دهد.

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

null چیست ؟

پیاده سازی null در زمان اجرا

پیش از بحث در خصوص معنی null و پاسخ دادن به این سوال که null چیست ، باید درکی از نحوه پیاده سازی null در حافظه در زمان اجرا به دست آورد.

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

String name = "Bob";

در خط کد فوق، متغیری از نوع String و به همراه نام شناسه (Identifier) آن تعریف شده است که به رشته Bob «اشاره» (Points) می‌کند. در این مضمون، استفاده از لفظ «اشاره کردن» اهمیت دارد؛ زیرا فرض بر این است که با نوع‌های مرجع (Reference Type) سر و کار داریم. در این خصوص بعداً بیش‌تر بحث خواهد شد. برای رعایت سادگی، تنها فرض‌های زیر در نظر گرفته خواهند شد:

  • خط کد بالا در یک پردازنده (CPU) شانزده بیتی به همراه یک فضای آدرس ۱۶ بیتی اجرا شده است.
  • رشته‌ها به صورت UTF-16 کدگذاری شده‌اند. این کدگذاری با یک عدد صفر (۰) منقطع می‌شوند (مثلاً در زبان C و C++‎ این چنین است).

بخشی از حافظه پس از اجرای دستور فوق در تصویر زیر ملاحظه می‌شود:

بخشی از حافظه پس از اجرای یک دستور برای ذخیره رشته در یک متغیر
متغیر name به رشته «Bob» اشاره می‌کند.

نشانی‌های حافظه در تصویر فوق به صورت تصادفی انتخاب شده‌اند و اهمیتی در بحث پیرامون null ندارند. همان‌طور که ملاحظه می‌شود، رشته «Bob» در نشانی B000 ذخیره شده است و ۴ سلول حافظه را اشغال می‌کند. متغیر name در آدرس A0A1 واقع شده است. مقدار B000 در آدرس A0A1 ذخیره شده که آدرس محل شروع ذخیره رشته «Bob» محسوب می‌شود. به همین دلیل است که گفته می‌شود: متغیر «name» به مقدار Bob «اشاره می‌کند».

تا اینجا همه چیز واضح و روشن است. اکنون فرض می‌شود که پس از اجرای دستور فوق، دستور زیر اجرا خواهد شد:

name = null;

حالا در دستور بالا متغیر name به مقدار null اشاره می‌کند و وضعیت جدید در حافظه به صورت تصویر زیر است:

وضعیت حافظه وقتی متغیر به Null اشاره می‌کند.
متغیر name به مقدار null اشاره می‌کند.

می‌توان ملاحظه کرد که هیچ چیزی برای رشته «Bob» که همچنان در حافظه ذخیره شده تغییری نکرده است.

نکته: بخشی از حافظه که باید رشته Bob را ذخیره می‌کرد در صورت وجود یک بازیافت کننده حافظه ممکن بود بعداً آزادسازی شود و آنگاه هیچ مرجع دیگری به Bob اشاره نمی‌کرد. اما این مسئله، ارتباطی با بحث مطرح شده در این مقاله ندارد.

آنچه اهمیت دارد این است که در دستور name = null، محل A0A1 در حافظه که بازنمایی از مقدار متغیر name محسوب می‌شود، حاوی مقدار 0000 است. بنابراین، متغیر name دیگر به مقدار رشته‌ای Bob اشاره نمی‌کند. مقدار صفر (همه بیت‌ها صفر هستند) یک مقدار معمول است که در حافظه بر null دلالت دارد. مقدار 0000 یعنی هیچ مقداری برای متغیر name‌ وجود ندارد. در واقع، می‌توان آن را به عنوان «غیبت داده» یا «عدم وجود داده» تلقی کرد.

نکته: مقدار واقعی که در حافظه برای مشخص کردن null استفاده می‌شود، مربوط به پیاده سازی این مفهوم است. برای مثال در مشخصات ماشین مجازی جاوا در پایان بخش ۲.۴ با عنوان «انواع مرجع و مقادیر» این گونه شرح داده شده است که:

مشخصه ماشین مجازی جاوا کدگذاری محرزی را برای null تحمیل نمی‌کند.

باید به خاطر داشت که اگر یک مرجع به null اشاره کند، خیلی ساده یعنی اینکه هیچ مقداری برای آن وجود ندارد. به بیان فنی، محل حافظه‌ای که به آن مرجع اختصاص داده شده است، حاوی مقدار صفر است (یعنی همه بیت‌ها صفر هستند)؛ همچنین، ممکن است مقداری که null را در یک محیط خاص مشخص می‌کند، مقدار دیگری باشد.

معرفی فیلم های آموزش برنامه نویسی فراردس

در این بخش از مقاله null چیست به معرفی دوره‌های برنامه نویسی فرادرس پرداخته شده است. در وب سایت فرادرس یک مجموعه جامع و کاربردی از دوره‌های آموزش برنامه نویسی گردآوری شده است که علاقه‌مندان می‌توانند برای یادگیری مهارت برنامه نویسی به زبان‌های مختلف از آن استفاده کنند. در زمان تدوین این نوشتار، نزدیک به ۵۰ دوره آموزشی مختلف در مجموعه آموزش برنامه نویسی فرادرس موجود است. در مجموع، نزدیک به ۴۴۰ ساعت محتوای آموزش تصویری در این مجموعه در دسترس علاقه‌مندان قرار دارد. دوره‌های آموزشی این مجموعه در سطوح مقدماتی، متوسط و پیشرفته برای انواع زبان‌های برنامه نویسی و فناوری‌های مرتبط با برنامه نویسی در زمینه‌ها و کاربردهای مختلف ارائه شده‌اند و بسته به نیاز و مورد استفاده هر فرد می‌تواند از دوره مناسب خود استفاده کند.

برای مشاهده فیلم های آموزش برنامه نویسی فرادرس + اینجا کلیک کنید.

عملکرد null چگونه است؟

همان‌طور که در بخش قبلی توضیح داده شد، عملیاتی که در آن‌ها از null شده، بسیار سریع هستند و اجرای آن‌ها در زمان اجرا (Run-Time) ساده است. تنها دو نوع عملیات وجود دارد:

  • اولین نوع از عملیات می‌تواند به این صورت باشد که:‌ یک مرجع با null مقداردهی اولیه یا مقدارش به null تغییر داده شود. (مثلاً: name = null): تنها کاری که باید انجام شود این است که محتوای یک سلول حافظه تغییر کند. (برای مثال مقدار آن به صفر تغییر کند).
  • دومین نوع از عملیات مربوط به null این گونه است که بررسی شود آیا یک مرجع (Reference) به null اشاره می‌کند یا خیر (مثال: if name == null): تنها کاری که در چنین شرایطی باید انجام شود این است که بررسی شود آیا مقدار داخل سلول حافظه مرجع صفر هست یا خیر.

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

مقایسه نوع مرجع با نوع مقداری

تا اینجا فرض بر این بود که متغیرهای نوع مرجع (Reference Type) مد نظر هستند. دلیلش هم ساده است: null برای انواع مقداری وجود ندارد. اما چرا؟ همان‌طور که پیش‌تر هم ملاحظه شد، یک مرجع (Reference) اشاره‌گری (Pointer) به آدرس حافظه خاصی است که مقداری را ذخیره می‌کند. برای مثال یک رشته، یک تاریخ، یک مشتری یا هر چیز دیگری می‌تواند یک مرجع باشد. در صورتی که یک مرجع به null اشاره کند، آنگاه هیچ مقداری برای آن تخصیص داده نمی‌شود. از طرف دیگر، یک مقدار طبق تعریف، یک مقدار است و هیچ اشاره‌گری در کار نیست. یک نوع مقداری خودش به عنوان یک مقدار ذخیره می‌شود. بنابراین، مفهوم null برای انواع مقداری وجود ندارد.

این تفاوت در تصویر زیر نشان داده شده است. در سمت چپ تصویر می‌توان باز هم وضعیت حافظه را در حالتی ملاحظه کرد که متغیر Name به عنوان یک مرجع به مقدار رشته‌ای «Bob» اشاره کرده است. در سمت راست تصویر نیز حافظه در حالتی نشان داده شده که متغیر name یک نوع مقداری است.

برای نوع مقداری Null وجود ندارد. | مقایسه نوع مرجع با نوع مقداری

همان‌طور که می‌توان ملاحظه کرد، در مورد یک نوع مقداری، مقدار مربوطه خودش مستقیماً‌ در آدرس A0A1 ذخیره شده که مربوط به متغیر name است. می‌توان توضیحات و مباحث بیش‌تری را پیرامون تفاوت‌های انواع مقداری و انواع مرجع ارائه کرد؛ اما این توضیحات خارج از دامنه موضوعی این مقاله هستند. همچنین باید در نظر داشت که برخی از زبان‌های برنامه نویسی تنها از انواع مرجع پشتیبانی می‌کنند و برخی (مثلاً C#‎ و جاوا) هر دوی آن‌ها را پشتیبانی می‌کنند. اکنون پس از اتمام مقدمات لازم، زمان آن فرا رسیده است تا به سوال اصلی و کلیدی این مقاله پاسخ و شرح داده شود که null چیست ؟

مفهوم null چیست ؟

مفهوم null در برنامه نویسی به معنی عدم وجود مقدار برای یک موجودیت یا مرجع است. به فرض، یک نوع person وجود دارد که دارای فیلدی به نام emailAddress است. همچنین فرض می‌شود که برای یک شخص که نامش آلیس است، emailAddress به null اشاره می‌کند. این به چه معناست؟ آیا این یعنی آلیس آدرس ایمیل ندارد؟ لزوماً این‌طور نیست. همان‌طور که تا کنون ملاحظه شده است، آنچه می‌توان ادعا کرد این است که هیچ مقداری به emailAddress نسبت داده نشده است. اما چرا هیچ مقداری وجود ندارد؟ در صورتی که اطلاعاتی از گذشته در این خصوص وجود نداشته باشد، آن‌گاه تنها می‌توان به حدس و گمان پناه برد. می‌توان فرض کرد که آلیس هیچ آدرس ایمیلی ندارد؛ یا اینکه آلیس نشانی ایمیل دارد، اما:

  • ایمیل آلیس هنوز وارد پایگاه داده نشده است.
  • بنابر دلال امنیتی، آدرس ایمیل آلیس محرمانه و افشا نشده است.
  • در یک روال (Routine)، نقصی (باگی) وجود دارد که منجر به ایجاد یک شی از نوع person را بدون تنظیم فیلد emailAddress شده است.

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

for ( Person person: persons ) {    if ( person.getEmailAddress() != null ) {        // code to send email    } else {        logger.warning("No email address for " + person.getName());    }}

در حلقه فوق، دلیل null بودن اهمیتی ندارد. تنها این حقیقت به رسمیت شناخته می‌شود که هیچ آدرس ایمیلی وجود ندارد، یک اخطار در این خصوص صادر می‌شود و سپس ادامه کار انجام خواهد شد. در اکثر مواقع null معنای مشخص‌تری دارد که این مسئله به مضمون و زمینه مربوطه بستگی دارد. حال در ادامه مقاله null چیست به این سوال پاسخ داده شده است که چرا به این مفهوم (تهی) در برنامه نویسی null گفته می‌شود؟

علت اینکه به این مفهوم گفته می‌شود null چیست؟

گاهی دلیل اینکه چرا یک مرجع به null اشاره می‌کند اهمیت دارد. برای مثال می‌توان الگوی تابع زیر را در یک کاربرد پزشکی یعنی دریافت فهرستی از آلرژی‌های یک بیمار در نظر گرفت:

List<Allergy> getAllergiesOfPatient ( String patientId )

در مورد تابع فوق، بازگرداندن مقدار null (یا یک لیست خالی) ابهام‌آمیز است. آیا این یعنی بیمار مربوطه آلرژی ندارد یا اینکه آیا هنوز آزمایش آلرژی برای فرد مربوطه انجام شده است؟ به لحاظ مفهومی دو حالت کاملاً متفاوت وجود دارد که باید به صورت متفاوتی با این دو حالت برخورد شود. در غیر اینصورت نتیجه ممکن است برای جان بیماران مخاطره‌آمیز باشد. اگر فرد مربوطه آلرژی داشته باشد، اما آزمایش آلرژی هنوز انجام نشده باشد و نرم‌افزار به پزشک نشان دهد «هیچ آلرژی وجود ندارد» به احتمال زیاد اتفاق مهلکی روی خواهد داد. بنابراین، نیاز به اطلاعات بیش‌تری وجود دارد و باید مشخص باشد که چرا تابع مقدار null را بازمی‌گرداند.

می‌توان برای ایجاد تمایز، اگر یک تست آلرژی هنوز انجام نشده باشد null بازگردانده شود و یک فهرست خالی هم در صورت عدم وجود آلرژی بازگردانده شود. اما بهتر است این کار به این صورت انجام نشود. این شیوه به چند دلیل در طراحی داده چندان مطلوب نیست. مفاهیم مختلف برای بازگرداندن مقدار null در مقایسه با بازگرداندن یک لیست خالی باید به خوبی مستندسازی شوند. در این خصوص، کامنت‌گذاری می‌تواند اشتباه باشد (با کدها ناسازگار باشد)، به‌روزرسانی نشده باشد یا حتی ممکن است دسترسی به کامنت‌ها وجود نداشته باشد.

هیچ محافظتی برای جلوگیری از سوءاستفاده در کدهای کلاینتی وجود ندارد که تابع را فراخوانی می‌کنند. برای مثال، کدهای زیر اشتباه هستند، اما بدون خطا کامپایل می‌شوند:

List<Allergy> allergies = getAllergiesOfPatient ( "123" );				if ( allergies == null ) {    System.out.println ( "No allergies" );             // <-- WRONG!} else if ( allergies.isEmpty() ) {    System.out.println ( "Test not done yet" );        // <-- WRONG!} else {    System.out.println ( "There are allergies" );}

علاوه بر این، در کدهای بالا دیدن خطا برای خواننده انسان دشوار است. نمی‌توان تنها با نگاه کردن به کدها متوجه وجود این خطا شد، مگر اینکه کامنت درج شده برای getAllergiesOfPatient هم در نظر گرفته شود. کدهای زیر هم اشتباه هستند:

List<Allergy> allergies = getAllergiesOfPatient ( "123" );if ( allergies == null || allergies.isEmpty() ) {    System.out.println ( "No allergies" );             // <-- WRONG!} else {    System.out.println ( "There are allergies" );}

در صورتی که منطق null‌ یا خالی بودن getAllergiesOfPatient در آینده تغییر کند، آنگاه کامنت مربوطه و همچنین همه کدهای کلاینت باید به‌روزرسانی شوند. علاوه بر این هیچ حفاظتی در برابر فراموش کردن هر یک از این تغییرات وجود ندارد. در صورتی که بعداً مورد دیگری برای تمایز قایل شدن وجود داشته باشد (مثلاً یک آزمایش آلرژی در دست اقدام باشد، نتایج همچنان در دسترس نخواهند بود) یا اینکه اگر قصد افزودن داده‌های مشخصی برای هر مورد وجود داشته باشد، آنگاه چالش ایجاد خواهد شد. بنابراین، تابع باید علاوه بر یک فهرست، اطلاعات بیش‌تری را بازگرداند. بسته به زبان برنامه نویسی که استفاده می‌شود، راه‌های متفاوتی برای انجام این کار وجود دارد. بنابراین، بهتر است به راهکارهای ممکن در جاوا نگاهی انداخته شود. برای تمایز قایل شدن میان این دو مورد، باید یک نوع والد، AllergyTestResult، تعریف شود.

interface AllergyTestResult {}

همچنین باید سه زیرنوع هم تعریف کرد که سه حالت NotDone, Pending, و Done را بازنمایی می‌کنند:

interface NotDoneAllergyTestResult extends AllergyTestResult {}
interface PendingAllergyTestResult extends AllergyTestResult {    public Date getDateStarted();}
interface DoneAllergyTestResult extends AllergyTestResult {    public Date getDateDone();    public List<Allergy> getAllergies(); // null if no allergies                                         // non-empty if there are                                         // allergies}

همان‌طور که می‌توان ملاحظه کرد، برای هر مورد داده‌های مشخصی با آن‌ها در ارتباط است. به جای اینکه تنها یک فهرست بازگردانده شود، اکنون getAllergiesOfPatient یک شی AllergyTestResult را بازمی‌گرداند:

AllergyTestResult getAllergiesOfPatient ( String patientId )

اکنون کدهای کلاینت کم‌تر مستعد خطا هستند و شبیه به کدهای زیرند:

AllergyTestResult allergyTestResult = getAllergiesOfPatient("123");
if (allergyTestResult instanceof NotDoneAllergyTestResult) {    System.out.println ( "Test not done yet" );   } else if (allergyTestResult instanceof PendingAllergyTestResult) {    System.out.println ( "Test pending" );   } else if (allergyTestResult instanceof DoneAllergyTestResult) {    List<Allergy> list = ((DoneAllergyTestResult)         allergyTestResult).getAllergies();    if (list == null) {        System.out.println ( "No allergies" );    } else if (list.isEmpty()) {        assert false;    } else {        System.out.println ( "There are allergies" );    }} else {    assert false;}

نکته: اگر این احساس وجود دارد که کدهای بالا طولانی هستند و نوشتن آن‌ها کمی دشوار است، باید گفت این احساس درستی است. برخی از زبان‌های برنامه نویسی مدرن این امکان را فراهم می‌سازند که برنامه نویسان از لحاظ مفهومی کدهای مشابهی را به طور بسیار مختصرتری بنویسند. زبان‌هایی که در خصوص null بی‌خطر و به اصطلاح «null-Safe» هستند، می‌توانند بین مقادیر تهی‌پذیر (nullable) و غیر تهی‌پذیر (Non-nullable) به شکل قابل اطمینانی تمایز قائل شوند. در این زبان‌ها هیچ نیازی به کامنت‌گذاری برای اطلاع‌رسانی در خصوص تهی‌پذیری یک مرجع وجود ندارد یا حتی بررسی اینکه آیا یک مرجع به گونه‌ای تعریف شده است که غیرتهی باشد نیز ضرورتی نخواهد داشت.

بنابراین، از این بخش می‌توان نتیجه گرفت که اگر لازم باشد بدانیم که چرا هیچ مقداری به یک مرجع نسبت داده نشده، آنگاه داده‌های بیش‌تر و اضافه‌ای باید فراهم شوند تا بتوان میان حالت‌های مختلف تمایز قائل شد. در ادامه پاسخ به این سوال که null چیست، به نقش null در مقداردهی اولیه در برنامه نویسی پرداخته شده است.

مقداردهی اولیه

می‌توان دستورات خط کد زیر را در نظر گرفت:

String s1 = "foo";String s2 = null;String s3;

در اولین دستور یک متغیر رشته‌ای به نام S1 تعریف می‌شود و مقدار «foo» را به آن تخصیص می‌دهد. در دستور دوم نیز مقدار null به S2 اختصاص داده می‌شود. دستور آخر از همه جالب‌تر است؛ هیچ مقداری به طور مشخص به متغیر S3 اختصاص داده نشده است. از این رو، پرسیدن این سوال منطقی است که وضعیت متغیر S3 پس از تعریف (اعلان) آن به چه صورت خواهد بود؟ اگر مقدار متغیر S3 در خروجی فراخوانی شود چه اتفاقی در خروجی سیستم عامل رخ خواهد داد؟

به نظر می‌رسد وضعیت متغیری (یا فیلد کلاس) که بدون تخصیص دادن یک مقدار تعریف شده است به زبان برنامه نویسی تکیه دارد. علاوه بر این، هر زبان برنامه نویسی ممکن است قوانین مشخصی برای حالت‌های مختلف داشته باشد. برای مثال، قوانین متفاوتی برای انواع مرجع، انواع مقداری، اعضای ایستا و غیرایستای یک کلاس، متغیرهای سراسری و محلی و سایر موارد بکار بسته می‌شود.

برخی از حالت‌های مختلفی که ممکن است مواجهه با آن‌ها رخ بدهد در ادامه فهرست شده‌اند:

  • تعریف (اعلان) یک متغیر بدون اختصاص دادن یک مقدار به آن غیر مجاز است.
  • حالتی که یک مقدار دلخواه در s3 ذخیره شده باشد، بسته به محتوای حافظه در زمان اجرا، هیچ مقدار پیش‌فرضی وجود ندارد.
  • حالتی که یک مقدار پیش‌فرض به صورت خودکار به S3 اختصاص داده می‌شود. در مورد یک نوع مرجع، و مقدار پیش‌فرض null است. در خصوص یک نوع مقداری هم مقدار پیش‌فرض بستگی به نوع متغیر دارد. مثلاً برای اعداد صحیح، مقدار پیش‌فرض صفر، برای نوع دودویی، مقدار پیش‌فرض false و به همین ترتیب برای سایر موارد هم مقدارهایی به صورت پیش‌فرض در نظر گرفته می‌شود.
  • حالتی که وضعیت S3 به صورت «تعریف نشده» (Undefined) باشد.
  • حالتی که وضعیت S3 به صورت «مقداردهی اولیه نشده» (Uninitialized) باشد و هر تلاشی برای استفاده از S3 منجر به یک خطای زمان کامپایل شود.

بهترین گزینه، آخرین مورد است. تمام گزینه‌های دیگر مستعد خطا و/یا ناکارآمد هستند. با توجه به اینکه تمرکز این مقاله مفهوم null است، دلایل این موضوع در اینجا شرح داده نمی‌شود.

به عنوان مثال، جاوا آخرین گزینه را برای متغیرهای محلی اعمال می‌کند. بنابراین، اجرای کدهای زیر منجر به بروز خطای زمان کامپایل می‌شود:

String s3;System.out.println ( s3 );

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

error: variable s3 might not have been initialized

بنابراین باید به یاد داشت که در صورتی که یک متغیر تعریف می‌شود، اما هیچ مقدار خاصی به آن تخصیص داده نمی‌شود، آنگاه وضعیت آن به فاکتورهای بسیاری بستگی دارد. در برخی از زبان‌ها، null به عنوان مقدار پیش‌فرض برای انواع مرجع در نظر گرفته می‌شود. حال در ادامه مقاله null چیست باید به این سوال پاسخ داده شود که بهتر است چه زمانی از null در برنامه نویسی استفاده کرد و چه زمانی استفاده از آن توصیه نمی‌شود؟

چه زمانی نباید و چه زمانی باید از null استفاده کرد؟

قانون اساسی در این خصوص بسیار ساده است: تنها در صورتی null مجاز است که تخصیص هیچ مقداری برای یک متغیر مرجع منطقی باشد. باید در نظر داشت که یک شی مرجع می‌تواند چنین مواردی باشد:

  • متغیر
  • ثابت (Constant)
  • خصیصه (Property) یا همان فیلد کلاس
  • آرگومان ورودی/خروجی
  • و سایر موارد…

برای مثال می‌توان در نظر گرفت که نوع Person با فیلدهای name و dateOfFirstMarriage (مربوط به تاریخ اولین ازدواج) است:

interface Person {    public String getName();    public Date getDateOfFirstMarriage();}

هر فردی دارای یک نام است. بنابراین، منطقی نیست که هیچ مقداری به آن نسبت داده نشود. فیلد name به اصطلاح غیر تهی‌پذیر است. اختصاص دادن null به آن غیرمجاز است.

از طرف دیگر، فیلد dateOfFirstMarriage همیشه نیازی نیست مقدار داشته باشد. این مسئله به این دلیل است که همه افراد ازدواج نکرده‌اند و برخی مجرد هستند. بنابراین، برای فیلد dateOfFirstMarriage معقول است که هیچ مقداری به آن نسبت داده نشده باشد. بنابراین، dateOfFirstMarriage یک فیلد قابل تهی‌پذیر (null‌پذیر) به حساب می‌آید. در صورتی که فیلد dateOfFirstMarriage مربوط به یک شخص به null اشاره کند، آنگاه این به سادگی یعنی این شخص هرگز ازدواج نکرده است.

نکته: متاسفانه اکثر زبان‌های برنامه نویسی رایج قابلیت تشخیص فیلدهای پوچ‌پذیر (nullable) و غیر پوچ‌پذیر (Non-nullable) را ندارند. هیچ راهی نیست که به طور قطع بتوان گفت هرگز امکان تخصیص null به یک شی مرجع وجود ندارد. در برخی از زبان‌های برنامه نویسی امکان استفاده از برچسب‌نویسی (Annotation) وجود دارد. به عنوان مثال می‌توان برچسب‌نویسی‌های غیراستاندارد ‎@nullable‎ و ‎@Nonnullable در جاوا را نام برد. در ادامه مثالی در این زمینه ملاحظه می‌شود:

interface Person {    public @Nonnull String getName();    public @Nullable Date getDateOfFirstMarriage();}

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

public Config readConfigFromFile ( File file )

در صورت بروز یک خطای خواندن فایل چه اتفاقی باید رخ دهد؟ آیا باید به سادگی null را بازگرداند؟ پاسخ قطعاً منفی است. چرا که هر زبانی روش استاندارد مربوط به خودش را برای مخابره شرایط خطا و فراهم کردن داده‌هایی پیرامون آن خطا از جمله توصیف خطا، نوع خطا، ردیابی پشته و سایر موارد دارد. بسیاری از زبان‌ها مثل C#‎، جاوا و سایر موارد، از یک ساز و کار استثناء استفاده می‌کنند. در این زبان‌ها از استثناها برای مخابره خطاهای زمان اجرا استفاده می‌شود. readConfigFromFile نباید برای مشخص کردن یک خطا مقدار null را بازگرداند. در عوض، قالب اعلان تابع را باید به گونه‌ای تغییر داد که احتمال مواجه شدن تابع با خطا شفاف‌سازی شود:

public Config readConfigFromFile ( File file ) throws IOException

به این ترتیب باید به یاد داشت که تنها باید وقتی از null استفاده کرد که پوچ بودن و نداشتن هیچ مقداری برای یک شی مرجع منطقی باشد. همچنین برای مخابره شرایط خطا هم نباید از null استفاده کرد. در ادامه مقاله «null چیست» به این سوال پاسخ داده شده است که چه زمانی استفاده از null خطری ندارد؟

چه زمانی استفاده از null خطری ندارد؟

مثلاً اگر خط کد زیر در نظر گرفته شود:

String name = null; int l = name.length();

در زمان اجرای کدهای فوق، «خطای اشاره‌گر null» رخ می‌دهد؛ زیرا سعی شده است تا یک متُد از مرجعی اجرا شود که به null اشاره دارد. برای مثال در C#‎ یک استثنا به نام nullReferenceException رخ می‌دهد. این استثنا در جاوا nullPointerException نام دارد. خطای اشاره‌گر null بسیار ناخوشایند است؛ چرا که، این خطا رایج‌ترین نقطه ضعف (رخنه) در بسیاری از کاربردهای نرم افزاری به حساب می‌آید و دلی اصلی برای بسیاری از مشکلات پرشمار در تاریخ توسعه نرم افزار بوده است.

برخلاف برخی از باورهای رایج، در اصل مقصر اصلی خود null نیست. مشکل کمبود پشتیبانی برای اداره کردن null در بسیاری از زبان‌های برنامه نویسی است. برای مثال، در سال ۲۰۱۸ هیچ یک از ۱۰ زبان برتر در شاخص تیوبی به صورت بومی میان انواع پوچ‌پذیر و پوچ‌ناپذیر تفاوتی قائل نبودند.

بنابراین، برخی از زبان‌های برنامه نویسی جدید ایمنی در برابر null و برخی سینتکس‌های خاص را برای رسیدگی به null و سهولت در استفاده از آن در کدهای منبع فراهم کرده‌اند. در این زبان‌ها کیفیت نرم افزار و قابلیت اطمینان به میزان قابل توجهی افزایش پیدا کرده است؛ چرا که در این زبان‌های جدید خطای اشاره‌گر null به طور خوشایندی از بین رفته است.

ایمنی در برابر null‌ یا به اصطلاح null-safety موضوع مهمی است که ارزش آن را دارد تا به آن در یک مقاله مجزا پرداخته شود. پس باید به یاد داشت که در صورت امکان از زبانی استفاده شود که از null-safety زمان اجرا پشتیبانی کند.

نکته: برخی از زبان‌های برنامه نویسی (بیش‌تر زبان‌های برنامه نویسی تابع‌محور مثل Haskell) از مفهوم null پشتیبانی نمی‌کنند. در عوض، آن‌ها از «الگوی شاید/اختیاری» (Maybe/Optional Pattern) برای بازنمایی «عدم وجود مقدار» استفاده می‌کنند. کامپایلر اطمینان حاصل می‌کند که وضعیت بدون مقدار به صورت مخصوص مدیریت شود. به همین دلیل، امکان بروز خطاهای اشاره‌گر null وجود نخواهد داشت. در پایان نوشته null چیست به معرفی دوره‌های آموزش جاوا در وب سایت فرادرس پرداخته شده است.

معرفی فیلم های آموزش جاوا

معرفی فیلم های آموزش جاوا

یکی دیگر از مجموعه دوره‌های آموزشی در وب سایت فرادرس، مجموعه آموزش جاوا است که بررسی دوره‌های موجود در آن به علاقه‌مندان به برنامه نویسی جاوا پیشنهاد می‌شود. با توجه به اینکه در این مقاله برای پاسخ به این سوال که null چیست و پرداختن به مسائل مهم پیرامون مفهوم تهی در برنامه نویسی از مثال‌هایی به زبان جاوا استفاده شده است، شاید افراد بخواهند جاوا را دقیق‌تر یاد بگیرند. بنابراین، این مجموعه با هدف آشنایی این افراد با آن در این بخش معرفی شده است. مجموعه آموزش جاوا در زمان انتشار این مقاله دارای ۸۵ ساعت محتوای آموزش ویدیویی است. این محتوای تصویری در قالب بیش از ۱۵ دوره آموزشی مختلف در این مجموعه ارائه شده‌اند. در این مجموعه آموزش‌هایی در سطح مقدماتی و بالاتر ارائه شده و علاوه بر آن، دوره‌های پروژه محور و برخی از فناوری‌های مهم مبتنی بر جاوا نیز در این مجموعه در دسترس قرار دارند.

جمع‌بندی

در این بخش پایانی از مقاله «null چیست» خلاصه‌ای از نکات کلیدی بیان شده در این مقاله فهرست شده است:

  • در صورتی که یک مرجع به null اشاره کند، همیشه به این معناست که هیچ مقداری به آن نسبت داده نشده است.
  • در اکثر مواقع، null معنی مشخص‌تری دارد که این معنی به بافت و زمینه وابسته است.
  • در صورتی که لازم باشد فردی بداند چرا هیچ مقداری به یک متغیر مرجع نسبت داده نشده است، آنگاه باید داده‌های بیش‌تری برای متمایز کردن موارد ممکن فراهم شود.
  • تنها زمانی باید null‌ را مجاز دانست که برای یک شی مرجع «نداشتن هیچ مقداری» قابل توجیه و منطقی باشد.
  • از null برای مخابره شرایط خطا نباید استفاده شود.
  • مفهوم null تنها برای انواع مرجع وجود دارد و برای انواع مقداری چنین مفهومی معنا ندارد.
  • در برخی از زبان‌های برنامه نویسی، null مقدار پیش‌فرض برای انواع مرجع به حساب می‌آید.
  • عملیات مربوط به null به میزان زیادی سریع و کم هزینه هستند.
  • هر زمان که ممکن باشد، باید از زبانی استفاده کرد که از فراهم‌سازی ایمنی در برابر null در زمان اجرا پشتیبانی می‌کند.

اگر این مطلب برای شما مفید بوده است، آموزش‌ها و مطالب زیر نیز به شما پیشنهاد می‌شوند:

بر اساس رای ۱۱ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

بابک خوش‌نویس دانش‌آموخته کارشناسی ارشد رشته علوم کامپیوتر گرایش علوم تصمیم و مهندسی دانش است. او به حوزه‌های علم داده و برنامه نویسی علاقه دارد و در حال حاضر به عنوان دبیر تحریریه علوم کامپیوتر مجله فرادرس فعالیت می‌کند.