طراحی سایت و برنامه نویسی

آموزش طراحی سایت و برنامه نویسی

طراحی سایت و برنامه نویسی

آموزش طراحی سایت و برنامه نویسی

همه چیز درباره Git LFS — به زبان ساده

اغلب ما نخستین باری که با LFS در Git مواجه شدیم، در زمان کار روی پروژه‌های علوم داده بوده است. شاید با خود بپرسید Git خود به اندازه کافی دشوار است، آیا باید یکی دیگر از قابلیت‌های آن را نیز یاد بگیریم؟ شاید هم کنجکاو هستید که Git LFS چیست و چرا باید آن را بیاموزید. در این صورت توصیه می‌کنیم، در ادامه این مقاله با ما همراه باشید تا به پاسخ سؤال‌هایتان برسید.

ابتدا مقدمه کوتاهی در مورد اهمیت LFS و کاربرد آن بیان می‌کنیم. تصور کنید مشغول کار روی یک پروژه تحلیل داده‌های فیلم برای نمونه از وب‌سایت OMDB ،Rotten Tomatoes و IMDB هستید. اما زمانی که کارها را روی گیت‌هاب می‌برید در زمان آپلود کردن مجموعه داده IMDB با خطایی مانند زیر مواجه می‌شوید:

Git LFS

در این زمان است که متوجه می‌شوید گیت و گیت‌هاب محدودیت اندازه فایل 100 مگابایت دارند. فایل‌هایی با اندازه‌های بزرگ‌تر از 50 مگابایت پیام هشدار دریافت می‌کنند، اما می‌توان آن‌ها را push کرد.

نخستین راهکاری که در این حالت به صورت طبیعی به ذهن می‌رسد این است که کار را با ارسال تک به تک فایل‌ها ادامه دهد. اما اگر این راهکار به هر دلیلی ممکن نباشد، باید گزینه بعدی یعنی ذخیره‌سازی به روش Large File Storage را در گیت بررسی کنیم. برای این که Git LFS را درک کنیم باید ابتدا گیت را بشناسیم. بنابراین قبل از بررسی LFS اندکی در مورد گیت توضیح می‌دهیم.

گیت چیست؟

Git LFS

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

اگر بخواهیم این موضوع را کمی بیشتر باز کنیم، باید ابتدا سیستم کنترل نسخه را توضیح دهیم. «سیستم کنترل نسخه» (Version Control System) ابزاری است که به مدیریت تغییرها در کد منبع، فایل‌ها و دیگر اَشکال اطلاعات می‌پردازد. بدین ترتیب تغییرها به صورت commit ردگیری می‌شوند. Commit در واقع تصاویری از ویرایش‌ها در نقاط زمانی مشخص هستند. در سوی دیگر یک سیستم کنترل نسخه توزیع یافته نوعی از سیستم کنترل نسخه است که امکان انتقال همه کدبیس شامل همه سوابق (و همه تغییرها) به رایانه هر توسعه‌دهنده را فراهم می‌سازد. بدین ترتیب هر توسعه‌دهنده می‌تواند روی پروژه‌ای کار کند و همزمان کل تایملاین ویرایش‌های انجام یافته روی پروژه را ببیند.

تاریخچه گیت

گیت نخستین بار در سال 2005 از سوی «لینوس تروالدز» (Linus Torvalds) و در نتیجه توقف استفاده از سیستم کنترل نسخه Bitkeeper خلق شد. دلیل توقف لینوس و همکارانش نیز این بود که Bitkeeper دیگر رایگان نبود و پولی شده بود. بر اساس گزارش‌ها لینوس پس از تلاش برای یافتن یک سیستم کنترل نسخه رایگان جایگزین برای Bitkeeper تصمیم گرفت تا سیستم کنترل نسخه خود را بسازد که کوچک و سریع (کمتر از سه ثانیه زمان بگیرد) باشد و از انشعاب پشتیبانی کند. همچنین این سیستم باید کاملاً مخالف سیستم‌های کنترل نسخه همزمان و شامل safeguards برای صحت داده‌ها می‌بود.

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

با این وجود، رشته‌های زیادی وجود دارند که در آن پروژه‌هایی با فایل‌های بزرگ ایجاد می‌شوند و این حالت شامل حوزه فایل‌های موسیقی، تصویر و مجموعه‌های داده‌های علمی نیز می‌شود. بنابراین اینک سؤال این است که در چنین موقعیت‌هایی چه باید کرد؟ این همان نقطه‌ای است که Git LFS به کارمی آید.

«ذخیره‌سازی فایل بزرگ» Git یا LFS به چه معنا است؟

Git LFS

Git LFS یک اکستنشن گیت است که در Go برنامه‌نویسی شده و از سوی توسعه‌دهندگانی در Atlassian و Github و همچنین مشارکت‌کنندگان اوپن‌سورس جهت دور زدن محدودیت اندازه فایل در گیت ساخته شده است. این کار از طریق ذخیره‌سازی فایل‌های بزرگ در یک مکان مجزا از ریپازیتوری و قرار دادن اشاره‌گرهای فایل در ریپازیتوری که مستقیماً به محل فایل اشاره می‌کنند صورت می‌پذیرد.

بهترین روش برای درک طرز کار LFS این است که ابتدا برای لحظه‌ای همه چیز را در مورد Github ،Bitbucket ،Gitlab و همه ریپازیتوری‌های ریموت فراموش کنیم. در این مرحله صرفاً روی رایانه محلی تمرکز می‌کنیم که در تصویر زیر با شکل یک نمایشگر با سه بخش Working Copy ،Local Repository و LFS Cache نمایش یافته است.

Git LFS

ریپازیتوری محلی

«ریپازیتوری محلی» (local repository) آن دایرکتوری یا پوشه‌ای است که روی رایانه خود می‌بینید و با استفاده از دستور git init به عنوان ریپازیتوری گیت مقداردهی شده و یا از ریپازیتوری ریموت کلون شده است. «کپیِ کاری» (Working Copy) یک بازنمایی از فایل‌ها و پوشه‌هایی است که در ریپازیتوری محلی ویرایش می‌شوند. LFS Cache مکان ذخیره‌سازی مجزایی است که در ریپازیتوری محلی ویرایش می‌شود. LFS Cache فضای ذخیره‌سازی مجزایی برای فایل‌های بزرگ است که از طریق گیت push می‌شوند. این اصطلاح‌ها را به ذهن خود بسپارید، چون در زمان توضیح طرز کار Git LFS در بخش بعدی به کار خواهند آمد.

کار با Git LFS

نکته مهم در مورد Git LFS این است که می‌توان در آن همچنان از دستورهای عادی و گردش کار معمولی گیت که کاملاً شناخته شده هستند استفاده کرد. تنها تغییر در این مورد برخی دستورهای اضافی و مکان ذخیره‌سازی مجزایی هستند که باید در خاطر داشت.

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

brew install git-lfs

در مورد سیستم‌های غیر مَک نیز امکان دانلود از طریق وب‌سایت (+) وجود دارد.

سناریوی اول

در این سناریو از Git LFS پس از دریافت پیام خطایی در زمان استفاده از دستورهای معمولی گیت استفاده می‌کنیم.

در این مثال یک ریپازیتوری جدید داریم که یک فایل داده بزرگ (1.9 گیگابایت) در آن قرار داده‌ایم. ما می‌خواهیم مطمئن شویم که هر تغییری در مورد فایل‌های داده ردگیری می‌شود و در نهایت به صورت ریموت پشتیبان‌گیری خواهد شد. ابتدا از دستورهای معمولی گیت برای stage کردن فایل (git add) استفاده می‌کنیم، یک کپی از تغییرها را در ریپازیتوری محلی (git commit) ذخیره می‌کنیم و کپی را به ریپازیتوری ریموت ارسال می‌کنیم (git push). خروجی این دستورها به صورت زیر است:

پیام خطایی که پس از استفاده از دستورهای معمول گیت دریافت می‌کنیم؛ دقت کنید که دلیل خطا بزرگ بودن فایل بوده است.

این خطا را چگونه می‌توان حل کرد؟ یک راه‌حل این است که تغییرها را با استفاده از git reset بازگردانی کنیم و یا از ذخیره فایل صرف‌نظر کنیم و آن را به صورت zip و با اندازه کوچک‌تر دربیاوریم و یا این که مسیر خود را با Git LFS ادامه دهیم. گزینه دیگر این است که از همان جایی که کار متوقف شده اقدام به یکپارچه‌سازی Git LFS بکنیم و بدین ترتیب امکان ادامه فرایند وجود خواهد داشت. ما در این بخش این مسیر را دنبال می‌کنیم.

گام 1

نخستین گام در این مسیر آن است که Git LFS را نصب کرده و از طریق اجرای دستور زیر، ریپازیتوری خاصی را برای آن فعال‌سازی کنیم:

git lfs install

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

git lfs install

Git LFS

گام 2

با دستور زیر به Git LFS اعلام کنید که کدام فایل‌ها باید ردگیری شوند:

git lfs track “*.file_extension”

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

برای مثال اگر لازم است همه فایل‌های CSV ردگیری شوند دستور زیر را اجرا کنید:

git lfs track “*.csv”

یا اگر لازم است همه فایل‌های jpeg ردگیری شوند، دستور زیر را وارد کنید:

git lfs track “*.jpg”

کاراکتر ستاره (*) نشان‌دهنده همه فایل‌ها است. استفاده از گیومه نیز برای اجرای این کد ضروری است. بدون وجود گیومه با خطای زیر مواجه می‌شویم:

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

Git LFS

گام 3

در این بخش با دستورهای Git add ،commit و push فایل gitattributes. را در ریپازیتوری خود قرار می‌دهیم.

Git LFS نیز همانند فایل gitattributes.، فایل‌های جدید را ردگیری می‌کند، و تغییرهای صورت گرفته به صورت خودکار در فایل gitattributes. به‌روزرسانی می‌شوند. برای این که مطمئن شویم تغییرها ردگیری می‌شوند، هر بار که فایل gitattributes. به‌روزرسانی می‌شود، باید stage و commit شود چون در غیر این صورت ممکن است در ادامه با خطایی مواجه شویم.

گام 4

اینک می‌خواهیم با رمز اصلی این سناریو که استفاده از git LFS برای انتقال کامیت‌ها از گیت به Git LFS است آشنا شویم.

آنچه موجب می‌شود بتوانیم در حالت کنونی و بدون نیاز به undo کردن کامیت‌ها و راه‌اندازی مجدد فرایند از Git LFS استفاده کنیم، یک خط کد جالب است که امکان انتقال یا «migrate» کامیت‌ها از گیت به Git LFS را فراهم می‌سازد. برای این که بتوانیم کامیت‌ها را انتقال دهیم باید دستور زیر را اجرا کنیم:

git lfs migrate import — include “*.file_extension”

برای دیدن انواع فایلی که در کامیت‌ها هستند و می‌توانند از سوی Git LFS ردگیری شوند، می‌توانیم دستور زیر را اجرا کنیم:

git lfs migrate info

با انتقال دادن کامیت‌ها، می‌توانیم به مرحله بعدی برویم و تغییرها را به گیت‌هاب push کنیم. در این خصوص در بخش بعدی بیشتر توضیح می‌دهیم:

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

گام 5

در نهایت دستور git push را اجرا می‌کنیم تا تغییرها را به گیت‌هاب پوش کنیم و کامیت بزرگ (یعنی فایل‌های بزرگ) نیز به Gif LFS کامیت شوند.

پس از انتقال کامیت‌ها به Git LFS در حال حاضر یک ریپازیتوری محلی گیت داریم که با یک تغییر به‌روزرسانی شده است. در این مورد یک فایل داده جدید اضافه شده است که به وسیله اشاره‌گر فایل به Git LFS اشاره دارد. همچنین یک کش Git LFS محلی وجود دارد که اینک به ذخیره سای فایل داده می‌پردازد. در مرحله بعدی تغییرها را به گیت‌هاب پوش می‌کنیم. ریپازیتوری گیت محلی که دارای فایل‌هایی در چارچوب محدودیت اندازه تعیین شده باشند (یعنی فایل‌های کد منبع و فایل اشاره‌گر) در گیت‌هاب ذخیره می‌شوند که میزبان گیت مشخص شده در تصویر زیر است و کش Git LFS در فضای ذخیره‌سازی Git LFS روی کلود ذخیره می‌شود.

Git LFS

سناریوی دوم: استفاده از Git LFS از ابتدا

اگر بدانیم که فایل‌های بزرگی در ریپازیتوری وجود دارند، می‌توانیم از Git LFS از همان ابتدا استفاده کنیم و مراحل 1 تا 3 بررسی شده فوق را طی کنیم. پس از طی این مراحل به دستورهای معمولی گیت به صورت git add ،git commit بازمی‌گردیم تا تغییرها را در ریپوی محلی stage و ذخیره کنیم. سپس گام 5 فوق را اجرا می‌کنیم تا تغییرها را به گیت‌هاب و دیگر میزبان‌های گیت و فضای ذخیره ریموت Git LFS پوش کنیم.

سخن پایانی

چنانکه مشاهده کردید روش Git LFS برای دور زدن مشکل ذخیره‌سازی فایل‌های بزرگ در سیستم کنترل نسخه گیت کاملاً سرراست است. کافی است پنج گام فوق را به خاطر بسپارید تا این فرایند را طی کنید. pull کردن تغییرها از یک ریپاریتوزی ریموت نیز کاملاً سر راست است. در این حالت همان دستورهای گیت که معمولاً استفاده می‌کنیم، یعنی git pull یا git fetch و git merge مورد نیاز هستند.

Git LFS

با استفاده از git pull می‌توان تغییرها را که شامل فایل داده ذخیره شده در Git LFS می‌شود، روی سیستم محلی بارگذاری کرد. زمانی که تغییرها را از ریپازیتوری ریموت pull می‌کنیم، تغییرهای ریپازیتوری ریموت و هر شیء دیگری که در Git LFS ذخیره شده باشد، نیز روی رایانه محلی pull می‌شوند.

شاید همه موارد فوق در مواجهه نخست کمی سردرگم‌کننده به نظر برسند، بنابراین در ادامه نکاتی را که می‌توان در این مقاله جمع‌بندی کرد، ارائه کرده‌ایم.

  • افرادی که به نظرشان گیت پیچیده می‌آید، باید بدانند که این مطلب بر پیچیدگی موضوع می‌افزاید. این مهم‌ترین چالش این افراد در زمان یادگیری Git LFS خواهد بود. یادگیری دقیق دستورهای گیت، گردش کار گیت و شیوه تطبیق آن با Git LFS کلید یادگیری گام‌های طرح‌شده در این آموزش است.
  • حتی در زمان استفاده از Git LFS نیز محدودیت اندازه فایل 2 گیگابایت وجود دارد که از سوی گیت‌هاب تعیین شده است. هر چیزی بزرگ‌تر از این، باید روی فضای ذخیره‌سازی ابری دیگری قرار گیرد.
  • Git LFS یک پروژه فعال اپن‌سورس است که به صورت مداوم بهبود می‌یابد. در گیت‌هاب این پروژه لیستی از issue-های جاری وجود دارد (+) که در حال حل هستند.
  • همچنین برخی issue-ها وجود دارند که در زمان تلاش برای ادغام تداخل‌ها بروز می‌یابند. بنابراین بهتر است پیش از push کردن تغییرها و ادغام آن‌ها یک مشورت درون تیمی وجود داشته باشد.
  • توجه داشته باشید که فایل‌های بزرگ‌تر در زمان push شدن به ریپازیتوری ریموت ممکن است کمی ‌کند باشند.

منبع: فرادرس


پیاده سازی درخت دودویی در کاتلین (Kotlin) — از صفر تا صد

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

تعریف

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

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

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

  1. درخت شامل هیچ کلید تکراری نیست.
  2. در هر گره، کلید بزرگ‌تر از کلید گره‌های زیردرخت چپ آن است.
  3. در هر گره، کلید کمتر از کلید گره‌های زیردرخت راست آن است.

عملیات مقدماتی

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

  • جستجو برای یک گره با مقدار مفروض
  • درج یک مقدار جدید
  • حذف یک مقدار موجود
  • بازیابی گره‌ها با ترتیب معین

گشتن

زمانی که درخت مرتب باشد، فرایند گشتن (Lookup) بسیار کارآمد می‌شود. اگر مقدار مورد جستجو برابر با مقدار گره جاری باشد، در این صوت گشتن به پایان می‌رسد. اگر مقدار مورد جستجو بزرگ‌تر از گره جاری باشد، در این صورت باید زیردرخت چپ کنار گذاشته شود و صرفاً زیردرخت راست جستجو شود:

توجه کنید که مقدار مورد نظر ممکن است در میان کلیدهای درخت حضور نداشته باشد و از این رو نتیجه‌ی جستجو ممکن است مقدار null بازگشت دهد. همچنین توجه داشته باشید که ما از کلیدواژه when در Kotlin استفاده کرده‌ایم که معادل جاوای آن به همراه گزاره switch-case است، اما قدرت و انعطاف‌پذیری بسیار بیشتری دارد.

درج

از آنجا که درخت امکان حضور هیچ نوع کلید تکراری را نمی‌دهد، درج یک مقدار جدید کاملاً آسان است:

  1. اگر مقدار قبلاً موجود باشد، هیچ اقدامی صورت نمی‌گیرد.
  2. اگر مقدار موجود نباشد، در گرهی درج می‌شود که جایگاه راست یا چپ آن خالی است.

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

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

حذف

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

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

هر دو گره فرزند تهی باشند

مدیریت این حالت کاملاً آسان است و تنها حالتی است که ممکن است نتوانیم گره را حذف کنیم، چون اگر گره همان گره ریشه باشد امکان حذف آن وجود نخواهد داشت. در غیر این صورت کافی است گره متناظر والد را برابر با null قرار دهیم.

درخت دودویی در کاتلین

پیاده‌سازی این رویکرد به صورت زیر است:

یک فرزند تهی و دیگری غیر تهی باشد

در این حالت، باید همواره تنها گره فرزند را به گرهی که قرار است حذف شود انتقال دهیم.

درخت دودویی در کاتلین

پیاده‌سازی آن به صورت زیر است:

هر دو گره غیر تهی هستند

این حالت پیچیده‌تر است، زیرا باید گرهی را پیدا کنیم که جایگزین گرهی می‌شود که می‌خواهیم حذف کنیم. یک روش برای یافتن این گره جایگزین آن است که یک گره را از زیردرخت چپ با بزرگ‌ترین کلید برداریم. روش دیگر دقیقاً متقارن روش فوق است. ما باید گرهی را در زیردرخت راست با کوچک‌ترین کلید برداریم. ما در این نوشته از رویکرد اول استفاده می‌کنیم:

درخت دودویی در کاتلین

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

پیمایش

روش‌های مختلفی برای بازدید از گره‌ها وجود دارد. رایج‌ترین روش‌ها شامل روش «عمق-اول» (depth-first)، «عرض-اول» (breadth-first) و «سطح-اول» (level-first) است. در این مقاله ما تنها روش عمق-اول را بررسی می‌کنیم که می‌تواند یکی از حالت‌های زیر را داشته باشد:

  1. پیش‌ ترتیبی (اول گره والد و سپس گره چپ و سپس گره راست را بازدید می‌کنیم)
  2. میان‌ ترتیبی (ابتدا فرزند چپ، سپس گره والد و سپس فرزند راست را بازدید می‌کنیم)
  3. پس‌ ترتیبی (ابتدا فرزند چپ را بازدید می‌کنیم و سپس فرزند راست و در نهایت گره والد)

همه انواع پیمایش در کاتلین می‌توانند به روشی ساده اجرا شوند. به عنوان نمونه برای پیمایش پیش‌ترتیبی می‌توان به صورت زیر استفاده کرد:

توجه کنید که کاتلین امکان الحاق آرایه‌ها را با استفاده عملگر + فراهم می‌سازد. البته این پیاده‌سازی فاصله زیادی با یک روش کارآمد دارد، چون tail-recursive نیست و در مورد درخت‌های با عمق زیاد ممکن است استثنای سرریز پشته را ایجاد کند.

سخن پایانی

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


منبع: فرادرس


آموزش سوئیفت (Swift) — مجموعه مقالات مجله فرادرس

سوئیفت یک زبان برنامه‌نویسی چندمنظوره‌ی چند پارادایمی و کامپایل شونده است که از سوی شرکت اپل توسعه یافته است. از این زبان جهت برنامه‌نویسی سیستم‌های عامل تحت مالکیت این شرکت مانند iOS ،macOS ،watchOS و tvOS استفاده می‌شود. تا قبل از سوئیفت، زبان رسمی برنامه‌نویسی اپل Objective-C بود که سوئیفت در سال 2014 جایگزین آن شد. این زبان به وسیله فریمورک کامپایلر متن-باز LLVM ساخته شده و از نسخه 6 Xcode در IDE رسمی اپل جای گرفته است. در این مقاله به جمع‌بندی مجموعه مقالات آموزش سوئیفت مجله فرادرس پرداخته‌ایم.

تاریخچه نسخه‌های مختلف

چنان که اشاره شد سوئیفت در کنفرانس WWDC اپل در سال 2014 معرفی شد و این زبان که در ابتدا تحت مالکیت اپل قرار داشت در ادامه و در نسخه 2 خود در سال 2015 تحت لایسنس آپاچی 2 به صورت متن-باز برای پلتفرم‌های اپل و لینوکس عرضه شد.

از نسخه 3 به بعد ساختار سوئیفت دچار تحولات زیادی شد و پایداری کد در اولویت توسعه تیم اصلی قرار گرفت. نسخه 4 سوئیفت در سال 2017 چند تغییر در کلاس‌ها و ساختارهای درونی آن معرفی کرد. کد نوشته شده در نسخه‌های قبلی سوئیفت را می‌توان با استفاده از کارکرد مهاجرت داخلی Xcode به نسخه جدید ارتقا داد. نسخه 5 سوئیفت در مارس 2019 عرضه شده و یک اینترفیس باینری پایدار روی پلتفرم‌های اپل ارائه کرده که به محیط زمان اجرای سوئیفت امکان مشارکت در سیستم‌های عامل اپل را می‌دهد. کدهای این نسخه با نسخه 4 سازگار است.

فهرست مجموعه مقالات آموزش سوئیفت

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

در بخش نخست مطالب آموزش برنامه‌نویسی سوئیفت برخی توضیحات کلی در مورد این سری مقالات آموزشی عرضه کردیم و توضیح دادیم که شامل چه مواردی می‌شود و نمی‌شود و هدف از این سری مقالات آموزشی چیست. همچنین با برخی مفاهیم مقدماتی برنامه‌نویسی مانند انواع داده و میزان مصرف حافظه آن‌ها آشنا شدیم.

در بخش دوم سری مقالات آموزش برنامه‌نویسی سوئیفت به بررسی عمیق‌تر انواع داده که شامل: «انواع مقداری» (Value Types)، «انواع ارجاعی» (Reference Types) و همچنین اشاره‌گرها (Pointers) می‌شود پرداخته‌ایم. همچنین توضیح دادیم که اشاره‌گرها احتمالاً یکی از دشوارترین مفاهیم برنامه‌نویسی محسوب می‌شوند و سعی کردیم آن‌ها را به ساده‌ترین زبان ممکن بیان کنیم.

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

در بخش چهارم از سری مقالات آموزش برنامه‌نویسی سوئیفت با مفهوم تصمیم‌گیری در فرایند برنامه‌نویسی آشنا شدیم و نقش گزاره‌های شرطی در پیاده‌سازی این تصمیم‌گیری‌ها را توضیح دادیم. همچنین با انواع حلقه‌ها شامل حلقه while و حلقه‌های for-in آشنا شدیم.

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

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

در این بخش از سری مقالات آموزش سوئیفت تمرکز ما روی این بوده است که شما آن دانشی را کسب کنید که وقتی کلاس‌ها را در برنامه‌های خودتان می‌بینید، ایده‌ای از چگونگی آغاز به کار با آن‌ها داشته باشید. لذا در این نوشته به بررسی مفاهیم Initialization و De-initialization،Override و Reference Counting پرداخته‌ایم.

در این بخش از سری مقالات آموزش زبان سوئیفت مفهوم تبدیل نوع به همراه باز کردن امن Optional-ها و کنترل دسترسی را مورد بررسی قرار داده‌ایم. به این منظور برخی از ابزارهایی که به طور مکرر در کدها استفاده می‌شوند را معرفی کرده‌ایم.

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

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

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

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

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

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

در این بخش از سری مقالات آموزش برنامه‌نویسی سوئیفت به صورت فشرده برخی از مفاهیم مهم این زبان برنامه‌نویسی شامل استفاده از Enum به همراه ژنریک و بستارها را مرور کرده‌ایم و با روش عملی استفاده از آن‌ها در کدنویسی آشنا می‌شویم.

در این بخش از مقالات سوئیفت در مورد چند موضوع صحبت می‌کنیم که موجب می‌شوند کد سوئیفت کارایی بیشتری پیدا کند. بدین ترتیب به بررسی مفاهیم Getter و Setter و همچنین inout و lazy می‌پردازیم. در عین حال روش استفاده از آن‌ها و بهترین کاربردشان را بررسی می‌کنیم.

در این بخش از سری مقالات آموزش زبان سوئیفت با مفهوم تست کردن اپلیکیشن و روش‌های آن آشنا می‌شویم. تست کردن مهم است و ارتباط تنگاتنگی با رویکرد TDD دارد. TDD اختصاری برای عبارت «Test-driven Development» (توسعه تست-محور) است. توسعه تست-محور یک روش رایج برای نوشتن اپلیکیشن است و به‌خاطرسپاری این فرمول نیز آسان است.

این بخش هجدهم و پایانی سری مقالات آموزش زبان سوئیفت مجله فرادرس محسوب می‌شود. شما با مطالعه هفده بخش قبلی این سری مقالات آموزش زبان برنامه‌نویسی سوئیفت با مبانی آن آشنا شدید. اینک و با مطالعه این بخش با موضوع معماری MVC می‌توانید شروع به نوشتن عملی اپلیکیشن‌های خود بکنید. این مقاله به این منظور نوشته شده است که شیوه استفاده مؤثر از معماری MVC را به شما آموزش دهد. MVC اختصاری برای عبارت «مدل، نما، کنترلر» (Model-View-Controller) است.

جمع‌بندی

بدین ترتیب شما با مطالعه هجده مقاله فوق با ساختار زبان برنامه‌نویسی سوئیفت آشنا می‌شوید. سوئیفت جایگزینی برای زبان برنامه‌نویسی قدیمی‌تر اپل یعنی Objective-C است که در آن از مفاهیم نظری برنامه‌نویسی مدرن استفاده شده و ساختار ساده‌تری دارد. سوئیفت به طور پیش‌فرض از اشاره‌گرها و دیگر عوامل دسترسی نا ایمن برخلاف Objective-C بهره نمی‌گیرد. همچنین ساختار شبیه Smalltalk برای ساخت فراخوانی‌های متد با سبک نمادگذاری نقطه‌ای و سیستم «فضای نام» (namespace) دارد که برای برنامه نویسان مسلط به زبان‌های شیءگرا مانند جاوا یا سی شارپ آشناتر است. ضمناً در آن از پارامترهای با نام استفاده می‌شود و مفاهیم کلیدی زبان Objective-C مانند پروتکل‌ها، بستارها و دسته‌بندی‌ها حفظ شده و در اغلب موارد با نسخه‌های مدرن‌تری جایگزین شده که امکان استفاده از این مفاهیم در ساختارهای زبان مانند انواع شمارشگر (enums) را فراهم می‌سازد.

منبع: فرادرس


چگونه یک پایگاه داده مناسب انتخاب کنیم؟ — راهنمای مقدماتی

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

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

درک موازنه

دلیل این که امروزه گزینه‌های پایگاه داده زیادی وجود دارند ناشی از نظریه CAP است. CAP اختصاری برای سه عبارت «consistency ،availability ،partition tolerance» یعنی سازگاری، در دسترس بودن و تحمل تسهیم است.

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

در هر لحظه از زمان تنها دو مورد از این سه الزام می‌توانند برقرار باشند.

پایگاه‌های داده رابطه‌ای

پایگاه‌های داده رابطه‌ای به طور سنتی، سازگاری و در دسترس بودن بالایی دارند و این به هزینه کاهش تحمل تسهیم به دست می‌آید. این پایگاه‌های داده برای نوشتن بهینه‌سازی شده‌اند. مثال‌هایی از پایگاه‌های داده رابطه‌ای به صورت SQL Server ،MySQL ،PostgresSQL و IBM DB2 است.

پایگاه‌های داده غیر رابطه‌ای

پایگاه‌های داده غیر رابطه‌ای جهت افزایش «در دسترس بودن و تحمل تسهیم» و یا «سازگاری و تحمل تسهیم» توسعه یافته‌اند. این نوع از پایگاه‌های داده جهت خواندن بهینه‌سازی شده‌اند. مثال‌هایی از پایگاه‌های داده غیر رابطه‌ای شامل Memcached ،Redis ،Coherence ،Hbase ،BigTable ،Accumulo ،MongoDB و CouchDB است.

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

سؤال‌های مهمی که باید پرسید

گام بعدی در مسیر انتخاب پایگاه داده مناسب این است که فهرستی از سؤال‌ها در رابطه با الزامات کسب‌وکار خود بپرسید. برخی از این سؤال‌ها در فهرست زیر ارائه شده‌اند:

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

درک مزایا و معایب

پایگاه‌های داده رابطه‌ای برای نوشتن بهینه‌سازی شده‌اند. بدین ترتیب بهترین عملکرد آن‌ها در زمینه سازگاری و در دسترس بودن است.

مزیت‌های پایگاه‌های داده رابطه‌ای

  • سادگی
  • سهولت بازیابی داده‌ها
  • یکپارچگی داده‌ها
  • انعطاف‌پذیری

معایب پایگاه‌های داده رابطه‌ای

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

پایگاه‌های داده غیر رابطه‌ای برای عملیات خواندن بهینه‌سازی شده‌اند. آن‌ها خصوصیت‌های «در دسترس بودن و تحمل تسهیم» و یا «سازگاری و تحمل تسهیم» را عرضه می‌کنند.

مزیت‌های پایگاه‌های داده غیر رابطه‌ای

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

معایب پایگاه‌های داده غیر رابطه‌ای

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

درک انواع مختلف پایگاه‌های داده غیر رابطه‌ای

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

کلید/مقدار: این نوع از پایگاه‌های داده بهترین عملکرد خود را در طرح‌بندی‌های ساده برای پایگاه‌های داده نشان می‌دهند. این نوع برای عملیات خواندن و نوشتن زیاد و با به‌روزرسانی کم مناسب است. بهترین عملکرد این نوع پایگاه داده در زمانی است که کوئری‌ها یا منطق تجاری غیر پیچیده‌ای وجود داشته باشند. مثال‌هایی از این نوع پایگاه‌های داده شامل Redis ،Dynamo DB و Cosmos DB است.

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

مثال‌هایی این نوع پایگاه‌های داده شامل MongoDB ،DynamoDB و Couchbase است.

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

Neo4j ،Cosmos Db و Amazon Neptune مثال‌هایی از این نوع پایگاه داده محسوب می‌شوند.

اکنون که درک نسبتاً جامعی از موارد لازم در هنگام انتخاب پایگاه داده برای پروژه‌های سازمانی خود به دست آوردید، می‌توانید بسته به نیازهای اپلیکیشن‌تان یک یا چند مورد از آن‌ها را انتخاب کنید. بدین ترتیب انتخاب شما انتخاب فردی خواهد بود که همه نیازهای تجاری مختلفی که ممکن است داشته باشد را لحاظ می‌کند.

ا

منبع: فرادرس

آموزش سوئیفت (Swift): نوشتن تست — بخش هفدهم

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

نوشتن تست

تست کردن مهم است و ارتباط تنگاتنگی با رویکرد TDD دارد. TDD اختصاری برای عبارت «Test-driven Development» (توسعه تست-محور) است. توسعه تست-محور یک روش رایج برای نوشتن اپلیکیشن است و به‌خاطرسپاری این فرمول نیز آسان است.

  • یک حالت تست شکست بنویسید.
  • کد کافی بنویسید و از نوشتن کد اضافی برای پاس کردن تست اجتناب کنید.
  • تست کنید تا پاس شدن حالت را تضمین کنید.

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

ما در این مقاله صرفاً به پوشش روش TDD می‌پردازیم. پیش از آن که کار خود را آغاز کنیم باید با دو اصطلاح جدید آشنا شوید: «Test Ratio» (نسبت تست) و «Code Coverage» (پوشش کد).

نسبت تست به نسبت تعداد خطوط کد به خطوط تست‌های نوشته‌شده گفته می‌شود. افراد زیادی هستند که می‌گویند هر 1 خط کد باید با 3 خط تست شود، یعنی این نسبت باید 1:3 باشد.

پوشش کد یعنی چه میزان از کد برحسب درصد تست شده است. IDE-های زیادی به نمایش پوشش کد می‌پردازند که کدبیس را برحسب وضعیت تست نمایش می‌دهد. رنگ سبز به معنی وجود تست برای کد و رنگ قرمز به معنی عدم وجود تست است.

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

نحوه نوشتن تست چیزی شبیه زیر است:

کد فوق را خط به خط بررسی می‌کنیم. import XCTest اقدام به ایمپورت کردن کتابخانه ارائه شده از سوی اپل برای تست کردن می‌کند. testable import ViewController @testable یک خصوصیت است که دامنه دسترسی این ماژول را افزایش می‌دهد. در واقع این دستور سطح دسترسی را از internal یا private به open تغیر می‌دهد اما تنها برای تست‌های لوکال کار می‌کند. دستور import ViewController گزاره ایمپورتی است که شامل کلاس مورد نظر برای تست است.

{ … } class ViewControllerTests: XCTestCase  کلاسی تعریف می‌کند که همه کارکردهای خود را از XCTestCase به ارث می‌برد. اگر از هرکدام از مشخصه‌ها استفاده کنید، چه کلاس دیگر باشد و چه برخی از ثابت‌ها یا متغیرهایی که غالباً در کاربردهای تست استفاده می‌شود به جای Testing properties here// آن مشخصه‌ها را بنویسید.

()override func setUp را می‌توان به عنوان ()viewDidLoad برای حالت‌های تست تصور کرد. زمانی که شروع به تست کردن می‌کنید، این کد کلاس‌ها را ایجاد می‌کند یا از متغیرها وهله می‌سازد.

()override func tearDown معادل {} deinit در کنترلرهای نما است. هر چیزی را که ممکن است موجب ایجاد نشت حافظه شود پاک کنید، Timer مثال خوبی از آن چیزی است که باید حذف شود.

()func test_multiplyByTwoReturnsFour یکی از قراردادهای نامگذاری فراوانی است که وجود دارد، اما این نام گذاری مقصود تست را مشخص می‌کند. موارد تست را همواره با عبارت test آغاز کنید. استفاده از _ اختیاری است، اما به افزایش خوانایی کمک می‌کند. در ادامه multiplyByTwo آن چیزی است که قرار است انجام دهیم و ReturnsFour آن چیزی است که انتظار داریم دریافت کنیم. اگر موارد تست را به این ترتیب بنویسید همواره می‌دانید که هر مورد تست برای چه چیزی استفاده می‌شود و چه نتیجه‌ای از آن انتظار می‌رود. اما اگر در نهایت صرفاً اعداد فرد و گرد شده بازگشت یابند چطور؟ بدین ترتیب می‌توان مورد تستی مانند {} func test_getOddFromMultiplyByTwo_ReturnsFive نوشت.

در نهایت دستور (XCTAssertEqual(value، value را می‌بینیم که مورد تست واقعی است که برابر بودن هر دو مقدار را تست می‌کند. XCTAssert یک پیشوند رایج است و از این رو اگر شروع به نوشتن XCT بکنید امکان تکمیل خودکار در Xcode تعدادی از متدهایی که می‌توانید استفاده کنید را به شما پیشنهاد می‌کند. در این حالت اگر هر دو مقدار برابر باشند تست پاس می‌شود و در غیر این صورت ناموفق خواهد بود.

برخی تست‌های رایج دیگر شامل XCTAssertNotEqual ،XCTAssertNil و XCTAssertNotNil هستند.

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

برخلاف تست‌های تأییدی ما، نیازی نیست که بخش بازگشتی مورد انتظار را در اعلان متد بگنجانیم. به جای آن کافی است آن را با بلوک { }test_functionNamePerformance(). measure  عوض کنیم که باید صرفاً شامل کدی باشد که می‌خواهیم اندازه‌گیری کنیم. اگر قرار باشد یک متغیر به (:getFactorial(of ارسال کنیم، در این صورت آن را خارج از بلوک اندازه‌گیری وهله‌سازی می‌کنیم، مگر این که بخواهیم آن را در اندازه‌گیری‌های خود بگنجانیم.

(getFactorial(of: 45 =_ به فراخوانی متد getFactorial با ورودی 45 می‌پردازد. از آنجا که نیازی به ذخیره‌سازی مقدار نداریم از   =_ استفاده می‌کنیم که نوعی جابجایی نتایج به /dev/null محسوب می‌شود. برای ما مهم نیست که این مقدار چیست و در اینجا به آن اهمیتی نمی‌دهیم.

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

نوشتن تست

در اجرای نخست این کد تابع را در طی 0.00000263 اجرا می‌کند. این زمان کاملاً سریع است اما در طی این مدت بهینه‌سازی‌هایی رخ می‌دهند. در ادامه اجراهای بعد را می‌بینیم.

نوشتن تست

در اجرای دوم، ما این تابع را 39 درصد بهتر اجرا کرده‌ایم، اما هیچ چیز تغییر نیافته است. دلیل این امر آن است که بهینه‌سازی‌ها قبلاً صورت گرفته‌اند و صرفاً از آن‌ها مجدداً استفاده کرده‌ایم. این جا مکان خوبی برای تنظیم مبدأ جدید است بنابراین ویرایش را کلیک می‌کنیم و مبدأ جدید را پذیرفته و آن را ذخیره می‌کنیم. در اجراهای بعدی به ترتیب 6% عملکرد بدتر، 0% بهتر، 2% بهتر، و 7% بهتر بدون تغییر دادن هیچ خطی از کد به دست آمدند. بنابراین مبدأ خوبی برای ما محسوب می‌شود. از این جا می‌توانیم تغییرات خود را ایجاد کرده و سپس اندازه‌گیری‌ها را تست کنیم تا ببینیم آیا تغییرات ما موجب عملکرد بهتر یا بدتر می‌شوند.

با این که راهنمای صریحی در مورد آن چه بهینه‌سازی خوب شمرده می‌شود وجود ندارد اما تصور ما این است هر تغییری که موجب بهبود در طی 10 اندازه‌گیری (100 اجرا) شود مناسب است. اگر کدی در طی چند اجرای نخست بهبود مناسبی نشان دهد بهتر است آن تغییر را حفظ کنید.

زمانی که در مورد تست کردن Unit Testing صحبت می‌کنیم، در واقع همان فرایندی است که در بخش فوق توضیح دادیم. همچنان یک حالت تست کردن عمومی نیز وجود دارد که به تست کارکرد کلی عملکرد اپلیکیشن چنان که انتظار می‌رود می‌پردازید. UI Testing نوع دیگری از تست کردن است که بخش UI را در برمی‌گیرد، اما از آنجا که این راهنما صرفاً در مورد مفاهیم مقدماتی تست کردن است بررسی آن خارج از دامنه این مقاله خواهد بود. در نهایت باید اشاره کنیم که یک تست پذیرش کاربر (UAT) نیز وجود دارد که برای اطمینان یافتن از این که کاربر از امکانات اپلیکیشن راضی است اجرا می‌شود. این تست عموماً از طریق تیم‌های پرسش و پاسخ (QA) اجرا می‌شود و کسب‌وکار یا مخاطبان منتخب از کاربران نهایی را شامل می‌شود. این تست امکان اجرای سناریوهای بیشتری در اپلیکیشن را می‌دهد که برای تست کردن بیشتر استفاده می‌شوند و به سؤالاتی در مورد شیوه استفاده از اپلیکیشن و زمان عرضه نهایی آن پاسخ می‌دهد.

سخن پایانی

ما در این مقاله با مفاهیم مقدماتی تست کردن آشنا شدیم و دیدیم که چگونه می‌تواند به نوشتن کدهای بهتر کمک کند، چه اهمیتی در چرخه توسعه دارد و چگونه عملکرد کد را اندازه‌گیری می‌کند. تست‌های بیشتر شامل استفاده از ابزارهای Xcode است که می‌توان از آن‌ها برای اندازه‌گیری حافظه، CPU و استفاده از دیسک بهره گرفت، اما این‌ها جزء مباحث پیشرفته هستند. در بخش بعدی در مورد معماری Model View Controller صحبت می‌کنیم.

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

منبع: فرادرس