برنامه نویسی 29 بازدید

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

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

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

برای مثال برخی برنامه نویسان به مدتی در حدود 6 ماه کدنویسی مداوم به صورت صبح تا شب نیاز دارند تا به این روند عادت کنند. بنابراین یک بار دیگر تأکید می‌کنیم که تمرین مداوم کدنویسی و مطالعه و آموزش، کلید حل مشکلات هستند.

ساختار کد

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

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

کلاس‌ها، پروتکل‌ها و اکستنشن‌ها

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

struct-ها و enum-ها

این موارد تقریباً نهادهای سطح بالا محسوب می‌شوند، اما آن‌ها را می‌توان درون struct-ها و enum-های دیگر نیز قرار داد.

تابع‌ها

تابع‌ها می‌توانند شیءهای سطح بالا باشند؛ اما این وضعیت صرفاً در برنامه‌های کنسول توصیه می‌شود. در مورد برنامه‌های macOS ،iOS و watchOS بهتر است که همواره تابع‌ها را درون کلاس‌ها نگهداری کنیم.

ثابت‌ها و متغیرها

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

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

خوانایی

خوانایی

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

تقسیم کردن همه چیز

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

ViewController+NameOfDelegate.swift

یا

ViewController+NameOfDataSource.swift

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

با استفاده از مثالی که در بخش فوق مطرح کردیم، در ادامه تلاش می‌کنیم که enum-ها را جدا کرده و آن‌ها را در فایل مخصوص خود قرار دهیم. اگر تلاش کنیم فایلی به نام Enums.swift برای enum-های سراسری بسازیم، در ادامه می‌توانیم با استفاده از //MARK: آن را به بخش‌های مختلف تقسیم کنیم تا در به‌روزرسانی‌های بعدی به راحتی بتوانیم به آن بازگردیم. اگر enum-ها در فایل تنها به این کنترلر ویو مرتبط باشند، کنترل دسترسی را طوری تنظیم می‌کنیم که به صورت داخلی باشد؛ اما فایلی را تحت همان گروه به نام کلاس ViewController ایجاد می‌کنیم.

ما می‌توانیم پروتکل‌ها و اکستنشن‌های آن‌ها را نیز انتخاب کرده و در فایل‌های جداگانه خود قرار دهیم؛ مگر این که پروتکل در مدل تعریف شده باشد و در این صورت در کلاس‌های دیگر قابل استفاده نیست. اگر پروتکل دارای یک اکستنشن خاص کلاس یا struct باشد باید از //MARK: برای جداسازی منطقی آن‌ها در فایل Protocols.swift بکنیم. در این مورد نیز فایل‌ها را به همان ترتیبی که در مورد Enums.swift عمل کردیم، قرار می‌دهیم.

در ادامه struct را نیز جدا می‌کنیم و آن را به عنوان یک مدل به نام House در فایلی به نام House.swift قرار می‌دهیم. همچنین آن را در یک گروه متفاوت به نام Models قرا می‌دهیم، زیرا یک مدل برای خانه محسوب می‌شود.

کلاس و همه متدهای آن نیز می‌توانند کنار هم بمانند؛ اما باید اکستنشن‌های خاص کلاس را جدا کرده و در فایل جدیدی به نام ViewController+Extensions.swift قرار دهیم. در این مورد کافی است مطمئن شویم که همه متدهای ما در ViewController.swift که باید از سوی یک اکستنشن فراخوانی شوند به صورت internal تنظیم شده‌اند، چون وضعیت‌های private و fileprivate جلوی دیدن متدهایی که در یک اکستنشن فایل دیگر قرار دارند را می‌گیرند.

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

استفاده از نام‌های گویا برای متغیرها

اغلب افراد با توجه به سابقه کدنویسی در زبان‌های C و ++C معمولاً از نام‌های تک‌حرفی برای متغیرها استفاده می‌کنند که این وضعیت منجر به درهم‌ریختگی کد و ناخوانایی آن می‌شود. این یک رویه نامناسب محسوب می‌شود و نباید از آن استفاده کرد. در طی سال‌ها، حروف i ،j ،k ،l ،m ،n ،t ،x ،y و z چنان معانی مختلف در محیط‌های برنامه‌نویسی داشته‌اند که اینک تقریباً هیچ معنی خاصی به ذهن متبادر نمی‌کنند. فرض کنید برنامه‌ای نوشته‌اید که با استفاده از سرعت میانگین در طی دوره زمانی خاص، مسافت پیموده شده را با متغیرهای j ،k و m محاسبه می‌کند. اسامی این متغیرها هیچ سرنخی به ما نمی‌دهد و مگر معجزه‌ای رخ بدهد که بتوانیم بفهمیم k به معنی متغیر سرعت است.

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

با مراجعه به تاریخچه استفاده از متغیرهای با نام تک‌حرفی متوجه می‌شویم که دلیل استفاده برنامه‌نویسان از چنین نام‌هایی این بوده است که چیزی به نام امکان «تکمیل خودکار» (autocomplete) وجود نداشته است و توسعه‌دهندگان نمی‌خواسته‌اند کد زیادی را تایپ کنند. بدین ترتیب این اطمینان نیز حاصل می‌شده است که نام یک متغیر اشتباه درج نشده است. این وضعیت شبیه نوشتن SMS روی گوشی‌های تلفن همراه در ابتدای دهه 2000 بوده است که از ترکیب‌هایی مانند lol ،c u l8r ، < 3 u و ttyl استفاده می‌شده است. اما اینک همه چیز تغییر یافته است و همه محیط‌های برنامه‌نویسی گزینه تکمیل خودکار را دارند که موجب می‌شود زحمت نوشتن نام‌های طولانی و گویا برای متغیرها تا حدود زیادی کاهش یابد.

البته در برخی موارد مثلاً زمانی که از متغیرهای موقت در گزاره‌های if استفاده می‌کنیم و اعلان متغیر صرفاً پنج خط با محل استفاده آن فاصله دارد، استفاده از نام‌های تک‌حرفی و کوتاه برای متغیرها اشکالی نخواهد داشت. اما وقتی که از یک متغیر به نام a در سراسر یک کلاس یا struct استفاده می‌کنید، به طور جدی به خوانایی کد خودتان آسیب می‌زنید. این وضعیت شبیه این است که امروزه ببینیم فردی در آخرین مدل از گوشی آیفون به جای «سلام، چطوری؟» عبارت «سلم. چطری؟» را تایپ کرده باشد! همین موضوع در مورد متغیرهای سراسری تک‌حرفی نیز صدق می‌کند. زمانی که پس از 8 ماه بخواهید بخشی از کد را تغییر دهید، ابتدا باید بررسی کنید که این متغیر به چه منظور نوشته شده است تا بتوانید آن را تغییر دهید و گاهی حتی بخش اول به زمان و انرژی بیشتری نسبت به بخش دوم نیاز خواهد داشت.

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

فاصله خالی

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

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

همه این موارد به افزایش خوانایی کد کمک می‌کنند. «دیوید هاینمایر هنسن» (David Heinemeier Hansson) خالق «روبی آن ریلز» (Ruby on Rails) یک سخنرانی در RailsConf دارد که در آن اشاره می‌کند برنامه‌نویسان بسیار کمی دانشمند علوم کامپیوتر هستند. در عوض بهتر است برنامه نویسان را نویسندگان نرم‌افزار بدانیم، زیرا این همان کاری است که انجام می‌دهیم. ما نرم‌افزار را می‌نویسیم. شما ممکن است با این گفته موافق نباشید و همچنان به عنوان «دانشمند کامپیوتر» علاقه داشته باشید. مشکلی وجود ندارد؛ اما پیشنهاد می‌کنیم ویدئوی این سخنرانی (+) را ببینید و سپس در این مورد تصمیم‌گیری کنید. در ادامه ویدئو در مورد «توسعه مبتنی بر تست» (TDD) صحبت می‌شود که نشانه خوبی برای اشاره به آن در بخش‌های آتی این سری مقالات آموزشی است؛ اما این کار را به بخش‌های انتهایی این دوره آموزشی موکول می‌کنیم.

نکته بعدی این است که باید همه تابع‌ها را در کلاس ابتدا بر اساس «پدیداری» (Visibility) و سپس بر اساس ترتیبی که فراخوانی می‌شوند قرار دهید. متأسفانه در صورتی که کد شما درون یک کلاس کپسوله‌سازی نشده باشد، لازم خواهد بود که همه تابع‌ها را پیش از کدی که هر تابع مورد ارجاع قرار می‌گیرد قرار دهید. بهترین راهنمایی به این منظور آن است که تابع‌های خود را در ابتدا قرار دهید و در این مورد نیز تابع‌هایی که به تابع‌های دیگر وابسته نیستند را ابتدا بنویسید و سپس تابع‌های دیگر را در ادامه آن‌ها قرار دهید. در واقع اگر تابع a تابع b را فراخوانی می‌کند، و تابع b به تابع c و d وابسته است، ترتیب آن‌ها از بالا به پایین باید به صورت c ،d ،b و a باشد.

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

خوانایی

اصول

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

سری که درد نمی‌کند را دستمال نمی‌بندند

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

پنهان‌سازی اطلاعات

این اصل صرفاً به این معنی است که باید مطمئن شوید که دسترس‌پذیری (که بر اساس کنترل‌های دسترسی تعریف می‌شود) تا حد امکان روی هر بخش از کد که می‌نویسید محدود شده است. اگر هیچ کس دیگری قرار نیست تابع ()mySuperSecretFunction را ببیند، چرا باید آن را به صورت public اعلان کنیم؟ اگر یک کد شبکه‌بندی دارید که یک url برای شما می‌سازد، باید کلاس شبکه‌بندی آن را بسازد. از کنترلر view ی خود نخواهید که این کار را انجام دهد و این متد را به صورت private یا fileprivate در کلاس شبکه‌بندی تعیین کنید.

تزویج سست

اصل «تزویج سست» (Loosely Coupling) گرچه تا حدودی انتزاعی به نظر می‌رسد؛ اما چنین نیست. زمانی که کلاسی دارید که بخشی از کلاس دیگر است تنها باید آنچه را می‌خواهید از آن کلاس دریافت کنید و آن کلاس را کلاً در کلاس خود قید نکنید، چون در این صورت یک ارجاع قوی خواهید داشت. تزویج سست صرفاً در نتیجه پایین نگهداشتن وابستگی‌های بین کلاس پدید می‌آید. از struct-ها، delegation یا پروتکل‌هایی که به سست شدن تزویج بین شیءها کمک می‌کنند بهره بگیرید. تاکنون در مورد Delegation صحبت نکرده‌ایم؛ اما به طور خلاصه باید اشاره کنیم که ایجاد امکان delegate شدن به یک کلاس از سوی کلاس دیگر باعث می‌شود آن کلاس بتواند تابع‌های خاصی را برای کلاس delegate شده افشا کند.

قانون دیمیتر (Demeter Law)

این اصل باید در مورد همه کدهایی که می‌نویسید برقرار باشد:

  • هر واحد باید اطلاعات محدودی در مورد واحدهای دیگر داشته باشد و این اطلاعات باید صرفاً در اختیار واحدهای کاملاً مرتبط با واحد جاری باشد.
  • هر واحد باید تنها با دوستان خود صحبت کند و ارتباطی با واحدهای غریبه نداشته باشد.
  • تنها با دوستان نزدیک خود صحبت کنید.

در نهایت به اصل باز/بسته اشاره می‌کنیم و همه نهادها باید برای اکستنشن باز باشند؛ اما برای تغییر بسته بمانند.

  • کلاس‌ها می‌توانند وراثت را از طریق کلیدواژه final پیش از کلیدواژه class ببندند.
  • متغیرها می‌توانند در موارد ممکن به ثابت تبدیل شوند و در این صورت این مقدار می‌تواند در ادامه در متغیری که قرار است تغییر یابد استفاده شود. ثابت همچنان همان مقدار را خواهد داشت.
  • از اکستنشن‌ها به جای به‌روزرسانی نهاد اصلی با منطق سفارشی، برای ایجاد چنین منطقی برای کارکرد خاص استفاده کنید.

جمع‌بندی

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

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

در بخش بعدی این مقاله در مورد Grand Central Dispatch و Closure-ها صحبت خواهیم کرد. اگر از ابتدا پیگیر سری مقالات آموزش سوئیفت ما بوده‌اید، باید به اطلاع برسانیم که بخش بعدی بسیار مهم است چون به معرفی بستارها (Closure) می‌پردازد و با استفاده از آن‌ها می‌توانید کارهای بسیار شگفت‌انگیزی انجام دهید. در اغلب موارد از بستارها برای تراکنش‌های ناهمگام استفاده می‌شود و به همین منظور بحث Grand Central Dispatch را نیز در بخش آینده گنجانده‌ایم، چون این دو موضوع باید با هم آموخته شوند تا معنی و کارکرد بستارها بهتر درک شود.

Grand Central Dispatch یا به اختصار (GCD) در مقایسه با Closure موضوع ساده‌ای است. با این حال صحبت کردن در مورد Closure-های ناهمگام بدون اشاره به GCD کار دشواری خواهد بود. در بخش بعدی این سری مقالات موارد زیادی در مورد هردوی این مفاهیم خواهیم آموخت:

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

==

آیا این مطلب برای شما مفید بود؟

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *