رهام رفیعی تهرانی
رهام رفیعی تهرانی
خواندن ۴ دقیقه·۱ سال پیش

استفاده از is برای آزمایش تایپ متغیر در typescript

مقدمه

مارتین فاولر تو کتاب ریفکتورینگش یک جمله ناب داره که خیلی جاها نقل قول شده ازش:

هر کسی میتونه یک برنامه ای بنویسه که ماشین بفهمه. برنامه نویس خوب کدی مینویسه که بقیه راحت بفهمند. من یک برنامه نویس عالی نیستم. من یک برنامه نویس خوب با عادت های عالی هستم.

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

صورت مسئله

چند روز پیش از یک کد ساده و اولیه، به یک الگوی تکراری در typescript رسیدم و بر مبنای اون، تصمیم گرفتم این مقاله رو راجع بهش بنویسم. داستان از این قرار بود که جایی از کد منتظر دریافت جواب سرویس بودم. این جواب میتونست از تایپ IOrder (محتوای یک سفارش) باشه یا null. اگر سرویس کارش رو درست انجام میداد و به خطا نمیخورد متغیری از تایپ IOrder برمیگردوند و اگر به خطا خورده بود مقدار null رو برمیگردوند.


پیاده سازی منطق با معنی غیرواضح، بوی ریفکتور میده

یه نگاهی به کل کد انداختم و متوجه شدم من چند جای کد برای اینکه چک کنم متغیرم سفارشه یا به خطا خورده (به عبارتی مقدار null برگشته)، دارم از عبارت res !== null استفاده میکنم.

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

تصمیم گرفتم کد رو ریفکتور کنم و از یک تابع برای بررسی اینکه جواب یک آبجکت سفارشه یا نه استفاده کنم. یک تابع تعریف کردم به اسم isOrder با خروجی boolean:

و این تابع رو بجای همه عبارت های order !== null که معنی تست سفارش بودن متغیر رو داخل خودشون مستتر داشتند، قرار دادم:

بعلاوه اینکه عبارت console.log رو داخل نمونه کدهای این مقاله اضافه کردم که متغیر res رو بهتر توضیح بدم:

مشکل حل شد و همه تست ها پاس شد و کد هم درست کار میکنه، ولی دو تا مشکل دیگه داره!


پیاده سازی درست مفهوم و منطق با هم

مشکل اول اینکه من از عبارت res?.order استفاده کردم تا کامپایلر به تایپ متغیرم گیر نده. یعنی برای کامپایلر هنوز مشخص نیست که این متغیر حتما از تایپ IOrder هست. از دید کامپایلر، ممکنه از نوع null باشه. در واقع اگر آپشنال بودن res رو ازش بگیرم کامپایلر ارور میده:


مشکل دوم: اسم ( هدف ) تابع اینه که چک کنه ببینه متغیر از نوع IOrder هست یا نه، ولی این تابع داره چک میکنه متغیر null نباشه. از نظر منطقی با شرایط فعلی تابع درست کار میکنه ولی از نظر معنایی مشکل داره.


برای همین از اپراتور is استفاده کردم و به شکل زیر تایپ متغیر رو تست کردم:

کامپایلر داخل بلاک if به تایپ متغیرم گیر نمیده و رسما با تایپ IOrder به کارش ادامه میده:


از اون مهمتر اینکه کد هم از نظر منطق و هم از نظر مفهوم درست پیاده سازی شده. مثلا اگر فردا روزی سرویس بجای null از آبجکت error استفاده کنه یا هر اتفاقی بیفته، کد همچنان درست کار میکنه چون علاوه بر منطق درست، معنای کد هم به درستی پیاده شده.


نتیجه

وقتی یک عبارت منطقی با خودش یک مفهوم رو حمل میکنه ولی در نگاه به کد خبری از اون مفهوم نیست یعنی کد باید طوری ریفکتور بشه که مفهوم کاملا واضح و مشخص دیده بشه. حتی اگر اون عبارت منطقی یک مقایسه ساده باشه. حتی اگر یک true یا false خیلی خیلی ابتدایی باشه.

وقتی یک برنامه نویس دیگه کد رو میخوانه باید همون اندازه درکی رو داشته باشه که ما به عنوان خالق کد، توسعه ش دادیم. این یعنی ما وظیفه داریم مفهوم رو مثل منطق درست پیاده کنیم.

این الگوی استفاده از اپراتور is، الگوی رایجی در تایپ اسکریپت هست. استفاده از این الگوها بجای روش های من در آوردی باعث میشه کدهای حرفه ای تری تولید بشه و زبان مشترک ساده تری بین توسعه دهنده ها گسترش پیدا کنه.


موفق باشید :)


typescriptریفکتورینگrefactoringتایپ اسکریپتآموزش تایپ اسکریپت
برنامه نویسی یک شغل نیست، یک هنره.
شاید از این پست‌ها خوشتان بیاید