۸ رویه مطلوب برای تست دوام کد تایپ اسکریپت — راهنمای کاربردی
در این مقاله به بررسی 8 رویه تست میپردازیم که با استفاده از آنها میتوانید مطمئن شوید کد تایپ اسکریپت شما در طی زمان طولانی دوام خواهد آورد. چه در حال ساخت کتابخانه باشید و چه مشغول ساخت اپلیکیشنهای فرانتاند یا بکاند یا جاوا اسکریپت باشید، با کمی تلاش و سوئیچ کردن به کد تایپ اسکریپت میتوانید گام بزرگی در جهت طولانیتر ساختن عمر کد خود بکنید. در این راهنما با 8 رویه مطلوب برای تست دوام کد تایپ اسکریپت در خدمت شما هستیم.
زمانی که نوعها را به کدبیس خود اضافه میکنید، این احتمال وجود دارد که متوجه کد مرده به دلیل خطاهای منطقی شوید، همچنین ممکن است متوجه دسترسی بررسینشده به متغیرهای بالقوه تهیپذیر، تابعهای با دغدغههای مختلف (پارامترهای ورودی. خروجی متعدد) و مواردی از این دست شوید که بیدرنگ موجب کمک به کاهش مشکلات کد میشود.
به علاوه استفاده از تایپ اسکریپت موجب سهولت در بازسازی کد، کمک به توسعه آتی و کاهش بدهی فنی میشود، زیرا اینک IDE (مانند VS Code) میدانید که کدام نوع از کد باید استفاده شود و میتواند با تغییر نام، افزار و یا جابجایی کد به شما کمک کند و یا تابعهایی که استفاده نشدهاند را به شما نشان دهد.
با این حال، چند ترفند کمهزینه نیز وجود دارند پیادهسازی آنها کمک بزرگی در جهت افزایش عمر کد شما خواهد داشت. این موضوع به طور خاص در زمانی که روی کد به صورت تیمی کار میکنید و یا کدتان چیزی بیشتر از یک پروتوتایپ است اهمیت دوچندان مییابد.
1. فعال کردن بررسیهای Strict
تایپ اسکریپت به صورت پیشفرض از حالت Strict نمیکند. این وضعیت در صورتی که نمیخواهید هنوز کاملاً از تایپ اسکریپت بهره بگیرید و صرفاً بخشهای خاصی از کد را از جاوا اسکریپت انتقال دادهاید، مناسب خواهد بود، اما همزمان به این معنی است که بسیاری از باگهای جاوا اسکریپت از سوی تایپ اسکریپت قابل ردگیری نیستند.
دلیل این مسئله آن است که تایپ اسکریپت از شما انتظار ندارد تا تعریف نوع را به همه چیز اضافه کنید، بلکه میخواهد any را به همه پارامترها و متغیرها اضافه کنید و به هر نوع امکان null یا undefined بودن بدهید و البته چند قاعده دیگر نیز وجود دارد.
صرفاً با فعال کردن فلگ Strict در فایل tsconfig.json پروژه امکان افزایش چشمگیری در پایداری کد فراهم میشود. این که مجبور باشید به صورت صریح، نوع همه چیز را تعیین کنید، موجب بهبود مستندسازی کد میشود و سریعتر میتوان انتظارات کد را درک کرد.
این کار به کامپایلر تایپ اسکریپت کمک میکند که به یاری شما بیاید و مکان استثناهای بالقوه را از قبل تشخیص دهد، چون بررسیهای null یا دیگر مقادیر بالقوه پارامتر/متغیر انجام میگیرد.
بسیاری از توسعهدهندگان از تایپ اسکریپت استفاده نمیکنند یا نوع را به همه چیز اضافه نمیکنند و عموماً چنین استدلال میکنند که به این ترتیب کد طولانیتر میشود و همین حالا هم کد به قدر کافی خوب است.
اما این کار را باید دست کم به عنوان یک لطف به اعضای دیگر تیم در نظر بگیرید که همچنین موجب بهرهمندی از امکان بازسازی خودکار کد و صرفهجویی در زمان کار تیمی میشود.
توجه داشته باشید که به خصوص میتوانید تایپ اسکریپت را طوری پیکربندی کنید که کد جاوا اسکریپت را نادیده بگیرد تا در اثر انواع مفقود در پروژه مسدود نشود و با انواع مفقود به صورت یک بدهی فنی که باید در طی زمان رفع شود رفتار کند.
2. به جای tsc از Babel استفاده کنید
Babel از نسخه 7 به بعد از تایپ اسکریپت پشتیبانی میکند، یعنی دیگر نیازی به استفاده از کامپایلر تایپ اسکریپت (tsc) برای بیلد کردن پروژه خود ندارید، بلکه به جای آن میتوانید از Babel استفاده کنید که به سادگی انواع را از فایلهای تایپ اسکریپت جدا میکند و سپس نتیجه را به صورت جاوا اسکریپت در اختیار شما قرار میدهید.
این کار نه تنها موجب افزایش سرعت نسبت به tsc به خصوص در پروژههای بزرگتر میشود، بلکه میتوانید از اکوسیستم Babel نیز در پروژه خود بهرهمند شوید. در مورد پروژههای بکاند، معنی این حرف آن است که میتوانید نظارت بر اسکریپتها را سادهتر انجام دهید و از babel-node برای رصد تغییرها بهره بگیرید:
nodemon — inspect=0.0.0.0:9229 — exec babel-node — extensions ‘.ts,.tsx’ src/index.ts
برای پروژهها، برای نمونه زمانی که از GraphQL یا دیگر قابلیتهای غیر تایپ اسکریپت استفاده میکنیم، به این معنی است که میتوانیم آنها را به سادگی از طریق Babel اضافه کنیم و مراحل Build تایپ اسکریپت از کار نمیافتد یا نیاز به تغییری نخواهد داشت:
{ "plugins": ["import-graphql"] }
البته در مورد پروژههایی که از ابزارهای خط فرمان ساخته شده با تایپ اسکریپت استفاده میکنند هم دیگر نیازی به ts-node یا مراحل بیلد پیش از اجرا نیاز نخواهیم داشت، بلکه میتوانید CLI را مستقیماً از طریق babel-node اجرا کنید.
معایب این وضعیت ناچیز است. پیادهسازی مربوط به تایپ اسکریپت Babel از enum-های const پشتیبانی نمیکند و در صورتی که میخواهید روی همه فایلها بررسی اجرا کنید، همچنان باید از tsc در مراحل Commit/CI استفاده کنید، زیرا Babel تایپ اسکریپت را تفسیر نمیکند.
3. نسخهها را پین کنید
اگر تاکنون شروع به پین کردن نسخهها در فایل package.json نکردهاید، باید هم اینک این کار را انجام دهید. حتی اگر یک فایل قفل دارید، در نظر بگیرید که فایل package.json شما باید برای انسان خوانا باشد و وابستگیهای پروژه را نشان دهد.
این موضوع به طور خاص در مورد پروژههای تایپ اسکریپت اهمیت مضاعف دارد. با این که برخی پروژههای جاوا اسکریپت، نسخه patch وابستگیها را بهروزرسانی میکنند، اما توجه کنید که اگر نگهداریکننده پکیج از SemVer پیروی نکرده باشد، یا یک باگ در پکیج وارد کرده باشد، همه توسعهدهندگان تایپ اسکریپت در یک نقطه از زمان از بهروزرسانی نسخههای patch مربوط به تعاریف نوع types@ رنج میبرند و با خطاهای جدید زیادی در تایپ اسکریپت مواجه خواهند شد.
این وضعیت به طور عمده به دلیل انواع «شخص چهارم» (Fourth-party) برای وابستگیهای شخص ثالث که در طی زمان تکامل یافتهاند، ناشی میشود و برخی اوقات به روشهای غیر قابل پیشبینی تشدید میشود.
برای مثال تعریف Koa (+) که صرفاً امکان داشتن یک چارچوب پارامتری یا تعریف Express را که امکان استفاده از شیء یا چارچوب کاربر را فراهم میسازد، به جهت این که کلیدهایی به صورت any روی اینترفیسهای خود تعیین کرده است، موجب از کار افتادن دهها مسیر میشود، زیرا انواع صحیح به صورت ناگهانی اضافه شدهاند.
با بلوغ هر چه بیشتر تایپ اسکریپت و اکوسیستم آن، با توجه به استفاده مستقیم هر چه بیشتر از کتابخانههای متن-باز، types@ همچنان یک مشکل محسوب میشود.
این مشکل در مورد تایپ اسکریپت به شدت وجود دارد و باید همانند کاری که در مورد ارتقای وابستگیهای دیگر انجام میشود، از ابزارهایی مانند Greenkeeper.io برای ارتقای یک وابستگی منفرد در یک شاخه مجزا به صورت خودکار استفاده کنید تا فرایند بهروزرسانی پروژه آسان بماند.
به یاد داشته باشد که هرگز نباید این نسخههای پچ /types@ را عصر یک روزِ کاریِ آخرِ هفته بهروزرسانی کنید، چون این همان کاری است که دقیقاً باعث خراب شدن تمام آخر هفته شما خواهد شد.
4. انواع Opaque
در بسیاری از پروژهها در نهایت باید از تعداد زیادی پارامترهای با نوع رشتهای و تعداد کمی انواع دیگر استفاده کنید. این وضعیت موجب میشود که تایپ اسکریپت برای هشدار دادن در مورد انتساب متغیرهای نادرست کار دشواری در پیش داشته باشد:
const trackLogin = (currentDate: string, sessionId: string) => { someCode(); }; const sessionUuid: string = currentSession.getUuid(); const currentDate: string = (new Date()).toISOString(); trackLogin(sessionUuid, currentDate);
جلوگیری از بروز چنین مواردی کار آسانی است، در عین این که امکان بازسازی کد در آینده نیز تسهیل میشود. کافی است انواع opaque را تعریف کرده و مورد استفاده قرار دهید که میتواند بسته به نیاز به پروژهها متفاوت باشد:
type Opaque<K, T> = T & { __TYPE__: K }; type Uuid = Opaque<"Uuid", string>; type DateISOString = Opaque<"DateISOString", string>;
تابع کاربردی <Opaque<K, T یک نوع جدید تعریف میکند که سوای مقدار متغیر یک کلید (یکتا) مانند Uuid یا DateISOString را نیز ذخیره میکند.
بدین ترتیب تایپ اسکریپت میتواند بین انواع متفاوت تمایز قائل شود، هر چند همچنان رشتههای ساده ذخیره میکند و خروجی کامپایلر تفاوت نمیکند.
معنی این حرف آن است که میتوانید هر تعداد که دوست دارید انواع رشتهای با نامهای یکتا اضافه کنید و آنها به صورت مقادیر رشتهای ارسال میشوند، اما اعتبارسنجی نوع به آنها اضافه شده است:
const trackLogin = (currentDate: DateISOString, sessionId: Uuid) => { someCode(); } const sessionUuid = currentSession.getUuid() as Uuid; const currentDate = new Date().toISOString() as DateISOString; trackLogin(sessionUuid, currentDate); // Your IDE / TypeScript will now understand this function call and show an error
5. از انواع کاربردی استفاده کنید
علاوه بر انواع opaque باید با انواع کاربردی دیگری نیز آشنا شده و از آنها استفاده کنیم، چون موجب درک بسیار آسانتر کد میشوند.
تایپ اسکریپت چندین نوع کاربردی داخلی از قبیل Partial<T> دارد که موجب میشود همه مشخصههای T اختیاری باشند. همچنین نوع <Readonly<T وجود دارد که موجب میشود T فقط-خواندنی شود. پکیجهای npm دیگری نیز برای انواع کاربردی شخص ثالث وجود دارند.
زمانی که شروع به استفاده از آنها میکنیم به زودی متوجه میشویم که انواع تایپ اسکریپت تکراری در تایپ اسکریپت کاهش مییابند و انواع با تعریف محدود در کد افزایش مییابند.
برای نمونه اگر توسعهدهنده ریاکت باشید، برای یک ردیوسر ریداکس میتوانید یک حالت ورودی از یک ردیوسر به صورت <Readonly<T تعریف کنید، تا به سادگی ببینید آیا به صورت تصادفی بازنویسی شده یا نه و دیگر نیازی به نوع حالت فقط-خواندنی دیگری ندارید.
با این حال، این انواع کاربردی نه تنها به کاهش تکرار نوع کمک میکنند، بلکه به جلوگیری از اکسپورت اینترفیسهای زیاد و تمیز نگه داشتن کد نیز کمک میکنند. برای این که از همان مثال ریاکت استفاده کنیم، فرض کنید میخواهید اینترفیسهای اکسپورتشده برای props کامپوننتهای ریاکت یا مقادیر بازگشتی سازنده اکشن تعریف کنید.
با این حال اگر اینترفیسها را به صورت کد قالبی (boilerplate) تصور میکنید و کدتان عملاً به کامپوننتهای ریاکت مربوط است، میتوانید به سادگی از <React.ComponentProps<T برای به دست آوردن props کامپوننت به روشی سرراست استفاده کنید و یا نوع یک سازنده اکشن را با <ReturnType<T به دست آورید.
همچنین تنها زمانی اینترفیسها یا نوعها را به اینترفیس یا نوع کوچکتر تجزیه کنید که از نقطه نظر دامنه کد معنیدار باشد. زمانی که این موارد را به طرز فرایندهای افراز کنید، امکان مشاهده ساختار به خصوص در زمان استفاده از تکمیل خودکار کد، دشوار میشود.
برخی اوقات بهتر است یک اینترفیس یکپارچه بماند و از طریق یک براکت به صورت IUser["parents"] به آن ارجاع بدهیم، زیرا یک روش ساده برای ارجاع به شیء مقدار parents است که دو Users دیگر ذخیره میکند و دیگر نیازی به استفاده از IParents یکبارمصرف که کار را پیچیده میکنند، وجود نخواهد داشت.
در نهایت باید اشاره کنیم که رقابتی برای داشتن تعداد هر چه بیشتر از اینترفیس در کار نیست. همواره بررسی کنید معنی یک اینترفیس یا نوع در دامنه محصول چیست و فقط در موارد ضروری آن را تکرار کنید، نه زمانی که برای IDE به آن نیاز دارید.
6. از Prettier استفاده کنید
در هر کدبیسی که چند نفر روی آن کار میکنند یا یک نفر در دورههای زمانی بلندمدت روی کد کار میکند، باید سبک کدنویسی منسجمی وجود داشته باشد.
حتی اگر اجماعی در مورد نبرد قدیمی tab یا space وجود دارد، ممکن است برخی افراد به روشهای متفاوتی از تورفتگیهای فراخوانیهای تابع چندخطی استفاده کنند یا برخی افراد اشیای JSON را به طور متفاوتی بنویسند و یا مواردی از این ناهماهنگیها وجود داشته باشد.
این امر نه تنها موجب بحثهایی در خصوص این که کدام سبک کدنویسی بهتر یا مبهم یا پیچیده است میشود و زمان را به هدر میدهد، بلکه موجب میشود که diff-ها در درخواستهای ادغام بیاستفاده شوند و به این ترتیب امکان مشاهده تفاوتها در کد تغییر یافته، دشوار میشود.
در این موارد میتوان از Prettier (+) برای قالببندی کد استفاده کرد. این ابزار امکانات زیادی دارد و خوشبختانه گزینههای پیکربندی زیادی ندارد که موجب هدر رفتن زمان ما شود. این ابزار آنلاین با قالبهای فایل زیادی مانند HTML و همچنین JSON ،CSS و TypeScript کار میکند.
میتوان یک IDE مانند VS Code را طوری تنظیم کرد که به صورت خودکار در زمان ذخیره فایلها از prettier استفاده کند که در عمل بزرگترین مزیت آن محسوب میشود. به این ترتیب مقداری زیادی از زمان ارزشمند کدنویسی خود را که میتوانست صرف تنظیم تورفتگیهای کد، طول خطوط و موارد این چنین شود، صرفهجویی میکنید. در این حالت صرفاً کد را وارد میکنید و prettier بقیه کار را انجام میدهد.
اکیداً پیشنهاد میکنیم که از prettier به صورت آماده استفاده کنید و تلاش نکنید چیزی را تغییر دهید. در این حالت نیازی به اعمال سبک کدنویسی خود ندارید، زیرا prettier خودش همه قالبهای را به صورت خودکار انجام میدهد.
تنها چیزی که احتمالاً ممکن است نیاز داشته باشید این است که شیوه خواندن کد خود را تطبیق دهید. در واقع این کار صرفاً یک بار انجام مییابد و بسیار آسانتر از خواندن کامل یک تابع و مشاهده پنج سبک مختلف کدنویسی از سوی سه نفر متفاوت است.
شاید یکی از تغییرهای پیکربندی که مفید باشد، استفاده از کاماهای پایانی سبک ES5 است:
{ "trailingComma": "es5" }
به این ترتیب diff-ها در درخواستهای ادغام در موارد افزودن عناصر جدید به انتهای اشیا کاهش مییابد و بنابراین تداخلهای ادغام کم میشوند.
7. از اشیا به عنوان Payload برای تابعها استفاده کنید
در مورد برخی توابع بهتر است که صرفاً یک شیء ارسال شود و متغیرهای ورودی مجزا را به آن نفرستیم. این امر موجب سادهسازی چشمگیری در امضای تابع میشود. به طور سنتی، توابعی که از این قاعده استفاده میکنند، شامل سازندههای اکشن ریداکس هستند:
1const sendMail = (type: string, subject: string, body: string, to: string, attachment?: File) => {
2 return {
3 payload: {
4 attachment, body, subject, to
5 },
6 type,
7 };
8};
در حالی که:
1const sendMail = (type: string, payload: { subject: string, body: string, to: string, attachment?: File }) => {
2 return {
3 payload,
4 type,
5 };
6};
زمانی که لازم باشد یک پارامتر جدید مانند userId گیرنده یک ایمیل در مثال فوق اضافه شود، در روش نخست توصیف تابع، باید امضای تابع تغییر یابد، یعنی باید همه تابعهای کامپوننت و dispatch ریاکت که این را فرامیخوانند تغییر پیدا کنند.
اما در صورتی که صرفاً یک شیء ارسال شود، نه تنها شیء بازگشتی در سازنده اکشن به صورت آسانتری بیان میشود، بلکه دیگر لازم نیست در مورد ترتیب پارامترهای ورودی و این که اختیاری هستند یا نه نگران باشید.
این وضعیت برای نمونه در مورد متدهای کنترلر در اپلیکیشنهای Express یا لایههای مشابه نیز که ممکن است کد دامنه در نهایت کمی تغییر یابد، به خوبی عمل میکند.
8. از ESLint و SonarJS استفاده کنید
همان طور که در بخشهای قبل اشاره کردیم که Prettier برای قالببندی کدنویسی ضروری است، هر پروژه باید از ESLint برای استانداردهای کدنویسی همراه با پلاگین ESLint به نام SonarJS (+) استفاده کند.
نکته: TSLint به نفع typescript-eslint منسوخ شده است و پلاگین SonarTS در TSLint به جای آن به عنوان بخشی از SonarJS استفاده میشود.
ESLint در تنظیمات پیشنهادی خود قواعد زیادی دارد که موجب بهبود کیفیت کد میشود و به جلوگیری از بروز خطاهای رایج کمک میکند. حتی اگر یک برنامهنویس حرفهای هستید، بهتر است کسی به شما یادآوری کند که یک getter مقدار بازگشتی ندارد یا یک متغیر در دامنه جاری، اعلان نشده مانده است، چون همه ما جایزالخطا هستیم.
علاوه بر قابلیتهای ESLint، یک سری بررسیهای پیچیدگی نیز از سوی SonarJS معرفی شده است که برای تجزیه متدها به بخشهای کوچکتر مفید است. این پلاگین برای بازسازی یا اضافه کردن الزامات جدید به قابلیتهای جدید نیز مفید است و موجب میشود به صورت تصادفی در نهایت با تابعهای بسیار پیچیده مواجه نشوید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای جاوا اسکریپت
- مجموعه آموزشهای برنامهنویسی
- راهنمای جامع تایپ اسکریپت (Typescript)
- درک انواع در تایپ اسکریپت — به زبان ساده
- نکات و ترفندهای تایپ اسکریپت — راهنمای کاربردی
==