<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های سارا رضائی</title><link>https://virgool.ir/feed/@sara.rezaei</link><description>linkedin.com/in/sara-rez</description><language>fa</language><pubDate>2026-11-10 16:30:39</pubDate><image><url>https://files.virgool.ir/upload/users/692348/avatar/bCZaLG.jpeg</url><title>سارا رضائی</title><link>https://virgool.ir/@sara.rezaei</link></image><item><title>طراحی برای پرفورمنس</title><link>https://virgool.ir/@sara.rezaei/طراحی-برای-پرفورمنس-joqbs1eynom2</link><description>سوال:در طولِ فرآیند توسعه ی نرم افزار، چقدر باید به performance فکر کرد؟اگر در هنگام توسعه ی نرم افزار، برنامه نویس بخواهد هر خط از کد را برای بیشترین سرعت، بهینه سازی کند، احتمالا سرعتِ توسعه بسیار کاهش خواهد یافت و پیچیدگی بالا می رود.از سوی دیگر، اگر مواردِ مربوط به performance به طور کلی نادیده گرفته شود، در نهایت، سیستمی با ناکارآمدی زیاد وجود خواهد داشت، که احتمالا 5 تا 10 برابر، سرعت اجرای کمتری نسبت به آن چه که می توانست داشته باشد، دارد.به جای انتخاب یکی از دو گزینه ی بالا، باید جایی در میانه قرار گرفت. بهترین رویکرد این است که، در هنگام توسعه ی نرم افزار، در انتخاب طراحی ها، آن هایی که به طور ذاتی، بهینه هستند و در عین حال، ساده و تمیز هستند را برگزینیم.نکته ی اصلی، داشتنِ آگاهی درباره ی این موضوع است که، کدام عملیات، به طور اساسی، هزینه بر هستند.مثال هایی از عملیات هزینه بر:ارتباط شبکه ایحتی درون یک دیتاسنتر، یک تبادل پیام، می تواند چیزی حدود 10 تا 15 میکرو ثانیه طول بکشد. حتما می دانید که این زمان، برابر با زمانِ مورد نیاز برای اجرای ده ها هزار دستور توسط پردازشگر است. در نظر داشته باشید که این رفت و برگشت ها، می توانند از 10 تا 100 میلی ثانیه (و نه میکرو ثانیه) طول بکشند.ورودی و خروجی به دیسکعملیات I/O دیسک، معمولا 5 تا 10 میلی ثانیه زمان می برد. این زمان برابر با اجرای میلیون ها دستور توسط پردازشگر است. البته تکنولوژی های جدید در زمینه ی حافظه، می تواند این زمان را تا 1 میکروثانیه کاهش دهد، ولی هنوز هم در مقایسه با سرعت اجرای دستور توسط پردازشگر، این زمان بسیار زیاد است.تخصیص حافظهوقتی از این دستور در زبان C استفاده می شود:malloc(size);و یا در ++C و Java یک شی ساخته می شود:ClassName object = new ClassName(); اجرای این دستورات، معمولا شامل سربارِ قابل توجهی برای تخصیص، آزادسازی و GC است.فقدان اطلاعات در cacheانتقال داده ها از DRAM به cache پردازشگر، به اندازه ی اجرای هزاران دستور، زمان می برد. در بسیاری از نرم افزار ها، برای اندازه گیری performance کلی، فقدان اطلاعات cache، به اندازه ی هزینه های محاسباتی، اهمیت دارد.یک راهِ خوب برای یادگیری این که اجرای کدام عملیات بیشتر هزینه دارد، اجرا کردن micro-benchmark ها است، که نرم افزار های کوچکی هستند که هزینه ی اجرای یک عملیاتِ بخصوص را به صورت مجزا از سایر عملیات، اندازه می گیرند.هنگامی که یک درکِ کلی از این که چه چیزی هزینه بر است و چه چیزی بهینه است به دست آوردید، می توانید هر زمان که امکان دارد، عملیاتِ کم هزینه را نسبت به سایر روش ها، ترجیح داده و انتخاب کنید.در بسیاری مواقع، یک رویکردِ بهینه، به سادگیِ یک روشِ کمتر بهینه است. مثلا این مورد را در نظر بگیرید:برای ذخیره ی یک مجموعه ی بزرگ از object ها که قرار است توسط یک key، جستجو شوند، می توان از یک hash table یا یک ordered map استفاده کرد. هر دو در library های زبان Java قابل دسترس هستند و استفاده از هر دو به یک اندازه ساده است. با این حال، hash table حدود 5 تا 10 برابر سریع تر است. بنابراین در مواردِ شبیه به این، باید همیشه از hash table استفاده کنید مگر این که از ویژگی مرتب بودن ordered map استفاده کنید.اگر بهینه نوشتنِ بخشی از کد، تنها از راهی امکان پذیر است، که باعث افزایش پیچیدگی می شود، برنامه نویس در یک دوراهی سخت قرار می گیرد. در این مواقع می توان اینگونه تصمیم گرفت: اگر طراحیِ بهینه تر، فقط مقدار کمی بر پیچیدگی سیستم می افزاید، و آن پیچیدگی، پنهان است (یعنی در واسط ها دیده نمی شود، بلکه فقط در پیاده سازی ها وجود دارد) می توان آن پیچیدگی را پذیرفت. ولی اگر طراحیِ بهینه، پیچیدگی را بسیار زیاد می کند، یا باعثِ ایجادِ واسط های پیچیده تر می شود، بهتر است، performance را فدا کرده و طراحی با پیچیدگی کمتر و performance پایین تر را انتخاب کرد و در آینده اگر performance تبدیل به یک مشکل شد، درباره ی بهینه کردن آن تصمیم گرفت. البته ممکن است با آگاهی از این که در یک موردِ بخصوص، اهمیت performance بسیار زیاد است، تصمیم گرفته شود که از ابتدا، طراحیِ بهینه تر انتخاب شود و بر پیچیدگی سیستم افزوده گردد.فرض کنید با وجود این که در هنگام طراحیِ نرم افزار، به موارد performance دقت کرده و آن ها را لحاظ کرده اید، باز هم سیستم، عملکردِ لازم را ندارد. معمولا در این مواقع، اولین راه حلی که به ذهنِ افراد می رسد این است که بر اساس شهودِ خود، نسبت به این که کدام بخش از سیستم نیاز به اصلاح دارد، تصمیم می گیرند. این رویکرد بسیار اشتباه است! هیچ گاه نمی توان به شهودِ برنامه نویس، در مورد performance اعتماد کرد، حتی برنامه نویس های باتجربه. اگر شروع به انجامِ تغییرات بر اساس شهودِ خود کنید، احتمالا زمانِ زیادی روی مواردی که به بهبودِ عملکردِ سیستم کمک نمی کنند هزینه خواهد شد و در نهایت یک سیستم با پیچیدگیِ بیشتر و همان عملکرد قبلی (یا حتی بدتر) وجود خواهد داشت.قبل از اعمالِ هر تغییری، رفتارِ فعلیِ سیستم را اندازه گیری کنید. اندازه گیری از دو جهت مفید خواهد بود:1. جاهایی که بهبودِ آن ها تاثیری زیادی روی عملکردِ کلی سیستم می گذارد را مشخص می کند.2. یک معیار مشخص می کند، که بعد از اعمالِ تغییرات، بتوان با توجه به مقادیرِ اندازه گیری های قبلی، تعیین کرد که چقدر بهبود در عملکرد حاصل شده است.فرض کنید عملکرد بخش های مختلف را با دقت اندازه گیری کردید و بخش هایی از کد که آنقدر کند است که روی عملکرد کلی سیستم تاثیر محسوسی دارد شناسایی شد. در این مرحله، بهترین کار برای بهینه سازی سیستم، تغییرات ابتدایی است. مثلا استفاده از cache یا استفاده از یک الگوریتم بهتر (مثلا استفاده از BTree به جای list).ولی گاهی اوقات، شرایط به گونه ای است که، یک تغییر ساده جوابگو نیست و نیاز به طراحی مجددِ بخش هایی از سیستم است. در این شرایط، ایده ی اصلی، پیدا کردن &#34;مسیر بحرانی&#34; و طراحی، پیرامونِ آن است.مسیر بحرانی عبارتی است که در مدیریت پروژه استفاده می شود و طولانی ترین فاصله بین شروع و پایان یک پروژه را مشخص می کند. این شامل تمام وظایف، منابع و فعالیت هایی است که باید برای اتمام پروژه به موقع انجام شود. مسیر بحرانی همچنین اساس برنامه زمانبندی پروژه را تشکیل می دهد و زمان تکمیل کار را نشان می دهد.با پاسخ دادن به این سوال شروع کنید:کوچکترین مقدارِ کد، که برای انجامِ کل آن وظیفه باید اجرا شود، چیست؟ساختارِ فعلیِ کد را به طور کامل فراموش کنید و در عوض، تصویر کنید که قرار است فقط آن &#34;مسیر بحرانی&#34; را با کمترین میزان کدنویسی، پیاده سازی کنید.ممکن است کدِ فعلی، شرط های خاصی را چک کند؛ آن ها را کلا کنار بگذارید. ممکن است در کدِ فعلی، متدهای زیادی صدا زده شود، آن ها را هم کنار گذاشته و سعی کنید همه ی کد را در یک متد بگنجانید. همچنین احتمالا کدِ موجود، از متغیرها و ساختار داده هایی استفاده کرده است؛ در این صورت، باید فقط آن متغیرها و ساختار داده هایی که برای &#34;مسیر بحرانی&#34; ضروری هستند نگه دارید. حتی ممکن است چندین متغیر را در قالب یک متغیر ترکیب کنید.فرض کنید بعد از انجام این کارها، به یک &#34;کد ایده آل&#34; رسیدید. یعنی کمترین مقدار کدی که برای اجرای &#34;مسیر بحرانی&#34; مورد نیاز است. این قطعه کد، ممکن است با ساختارِ فعلی نرم افزار مغایرت داشته باشد، ولی فایده اش این است که، یک نقطه ی هدف برای شما مشخص می کند و ساده ترین و سریع ترین کدی که می تواند اجرا شود را به نمایش می گذارد.قدم بعدی این است که، به دنبال طراحیِ جدیدی باشید، که با در نظر گرفتنِ ساختارِ فعلیِ کد، نزدیک ترین حالت به آن &#34;کدِ ایده آل&#34; باشد. ممکن است مجبور شوید انتزاعِ جدیدی به &#34;کدِ ایده آل&#34; اضافه کنید یا متدهای دیگری تعریف کنید. مهم این است که در همه ی مراحلِ کار، سعی کنید تا جای ممکن از &#34;کدِ ایده آل&#34; فاصله نگیرید.خلاصهکدهای پیچیده معمولاً کند هستند زیرا کارهای اضافی انجام می دهند. از سوی دیگر، اگر کدِ ساده و تمیزی بنویسید، احتمالاً سیستم شما به اندازه‌ای سریع خواهد بود که نیازی به نگرانی در مورد performance نداشته باشید. در موارد معدودی که نیاز به بهینه سازی performance دارید، نکته کلیدی دوباره سادگی است:مسیرهای بحرانی را که برای عملکرد مهم هستند پیدا کنید و آنها را تا حد امکان ساده کنید.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Thu, 30 Dec 2021 17:22:40 +0330</pubDate></item><item><title>ثبات در توسعه نرم افزار</title><link>https://virgool.ir/@sara.rezaei/ثبات-در-توسعه-نرم-افزار-aldzwj9qns5y</link><description>ثبات، یک ابزار قدرتمند در کاهش پیچیدگی سیستم و افزایش شفافیت در رفتار آن است. منظور از ثبات، این است که چیزهای مشابه، با روش های مشابه انجام می شوند و چیزهای متفاوت، با روش های متفاوت.اگر نرم افزار ثبات داشته باشد، زمانی که برنامه نویس، یاد گرفت در نرم افزار، چه چیزی چگونه انجام می شود، می تواند در موقعیت های مشابه، به سرعت، رویکرد مشابه را تشخیص دهد.ولی اگر در توسعه ی نرم افزار، ثبات وجود نداشته باشد، برنامه نویس ها باید در هر موقعیت، چیزهای جدید یاد بگیرند و زمان توسعه ی نرم افزار، بیشتر خواهد شد.ثبات همچنین باعث کاهش خطا می شود. اگر ثبات وجود نداشته باشد، دو موقعیتِ متفاوت، به اشتباه، یکسان قلمداد خواهد شد. در نتیجه یک برنامه نویس، ممکن است الگویی را ببیند که آشنا به نظر می رسد و بر اساس برخوردهای قبلی با آن الگو، فرض های نادرستی داشته باشد. از سوی دیگر، اگر سیستم ثبات داشته باشد، مفروضاتی که بر اساس موقعیت‌های آشنا به نظر می‌رسند، به احتمال زیاد، درست خواهند بود. ثبات به توسعه دهندگان اجازه می دهد تا با اشتباهات کمتری و با سرعت بیشتری کار کنند.اگر برای شما سخت است که معنی &#34;ثبات&#34; در نرم افزار را متوجه شوید، این مثال ها به شما کمک خواهند کرد:ثبات در نام هاهمانطور که در نوشته ی نام گذاری گفته شد، ثبات در نام گذاری سه شرط اصلی دارد:همیشه برای یک هدف خاص، از یک نام مشترک استفاده شود.هیچ وقت آن نام مشترک، برای هدف دیگری به کار برده نشود.آن نام مشترک باید آن قدر مرتبط باشد، که همه ی متغیرهای با این نام، رفتار یکسانی داشته باشند.ثبات در سبک کدنویسیدرست است که کامپایلرها، قوانینی دارند که برنامه نویس ها را تا حد زیادی محدود می کنند. با این حال، امروزه اکثر شرکت ها، فراتر از این محدودیت ها، قوانینی وضع می کنند که سبک کدنویسی برنامه نویس ها را به هم نزدیک کنند و برای آن محدودیت هایی بگذارند. این محدودیت ها شامل نام گذاری، ترتیب تعریف متغیرها، فاصله گذاری در کد و همچنین نحوه و میزان استفاده از ویژگی های زبان برنامه نویسی خاص آن شرکت است. مثلا ممکن است اگر در نرم افزارهای شرکت از زبان C# استفاده می شود، بعضی ویژگی های خاص این زبان، ممنوع باشد.ثبات در پیاده سازی، به کمک واسط هایک interface که پیاده سازی های مختلف دارد، مثال دیگری از ثبات است. وقتی برنامه نویس، یک پیاده سازی از یک interface را می بیند و می شناسد، فهمیدنِ سایر پیاده سازی های آن interface برایش ساده تر خواهد بود. بنابراین، interface ها به افزایش ثبات نرم افزار کمک خواهند کرد.ثبات در پیاده سازی، با استفاده الگوی های طراحیالگوهای طراحی (Design Patterns)، راه حل های عمومی، برای مسائلِ عمومی هستند. اگر الگوهای طراحی در بخشی از کد استفاده شوند، پیاده سازی آن بخش، سریع تر پیش خواهد رفت، همچنین آن بخش، مشابهِ سایر بخش ها با مساله ی یکسان خواهد بود و کسی که می خواهد بعدا کد را بخواند، با سرعت بیشتری آن را خواهد فهمید.تضمینِ ثباتاز آن جا که معمولا تعداد افراد زیادی روی یک پروژه ی نرم افزاری کار می کنند، تضمین کردن ثبات، کار سختی است. کسانی که در یک تیم کار می کنند، ممکن است از قراردادها و قوانینِ تیم های دیگر اطلاع نداشته باشند. افرادِ جدیدی که به تیم اضافه می شوند، قوانین را نمی دانند، بنابراین ممکن است آن ها را بشکنند و سبک های جدیدی به کد اضافه کنند.روش هایی هست که می توان با استفاده از آن ها، ثبات را در نرم افزار ایجاد و حفظ کرد:مستنداتبهتر است لیست تمام قوانین، شامل سبک کدنویسی، نام گذاری و ... مستند شود. این مستندات باید در جایی قرار بگیرند که به سادگی قابل دسترس و حتما در معرض دید برنامه نویس ها باشد. افرادِ جدیدی که به تیم ملحق می شوند، باید تشویق به خواندن این مستندات شوند و افرادِ قدیمی تیم، تشویق به بازخوانی و در صورت نیاز، اصلاح این مستندات. برای شروعِ تهیه ی این سندها، می توان از نمونه های بسیاری که شرکت های معتبر روی اینترنت قرار داده اند استفاده کرد.اجبارحتی اگر مستندات کامل باشند، برای برنامه نویس ها، به خاطر داشتن تمام قوانین سخت است. بهترین راه برای اطمینان از اجرا شدن قراردادها، استفاده از ابزاری است که تخلف ها را شناسایی کرده و به کدی که قوانین در آن رعایت نشده، اجازه ی commit شدن نمی دهد. علاوه بر ابزارها، یکی دیگر از روش های متعهد کردن برنامه نویس ها به قوانین و همچنین آموزش افرادِ تازه کار، code review است. در این مورد، کسانی که مسئول code review هستند، باید از بین افراد بادقت و تیزبین انتخاب شوند.خواهی نشوی رسوا، همرنگ جماعت شواین ضرب المثل، بهترین نصیحت برای برنامه نویس ها است. وقتی روی یک بخش جدید از نرم افزار کار می کنید ببینید کدی که قبلا نوشته شده چه ساختاری دارد. برای مثال، آیا همه متدهای public قبل از private ها تعریف شده اند؟ آیا متدها در یک کلاس، به ترتیب حروف الفبا چیده شده اند؟ نام گذاری ها به چه صورت است؟اگر به نظرتان رسید که یک قرارداد و الگوی مشابه پیدا کرده اید، آن را دنبال کنید. زمانی که یک تصمیمِ طراحی می گیرید، از خودتان بپرسید آیا بخش های مشابه، به همین ترتیب طراحی شده است یا نه؟ اگر جایی، کاری شبیه به کار شما انجام شده بود، آن را پیدا کنید و رویکرد مشابه ای را در کد خودتان برگزینید.نکته ی مهم این است:قرارداد ها را تغییر ندهید.در برابرِ میل به بهبود قرارداد های موجود، مقاومت کنید. داشتن &#34;ایده ی بهتر&#34; بهانه ی کافی برای ایجاد ناسازگاری نیست. ایده ی جدیدِ شما ممکن است واقعاً بهتر باشد، اما ارزشِ ثبات در برابر ناسازگاری، تقریباً همیشه بیشتر از ارزش یک رویکرد نسبت به رویکرد دیگر است.قبل از این که به سیستم بی ثباتی تحمیل کنید، این دو سوال را از خودتان بپرسید: آیا شما اطلاعاتِ جدیدِ قابل توجهی برای توجیه رویکردِ خودتان دارید که قبلا در دسترس نبوده است؟آیا رویکردِ جدید، آنقدر خوب است که ارزشِ صرف زمان برای تغییر تمام کدهای قبلی را داشته باشد؟اگر سازمانِ شما موافق است که پاسخ به هر دو سوال &#34;بله&#34; است، پس تغییرات را اعمال کنید. وقتی کارتان تمام شد، نباید اثری از قراردادهای قدیمی وجود داشته باشد. با این حال، همچنان این خطر وجود دارد، که افرادِ دیگر، از قراردادِ جدید اطلاعی نداشته باشند، بنابراین ممکن است در آینده رویکرد قدیمی را مجدداً استفاده کنند. به طور کلی، بهتر است وقتِ برنامه نویس ها، در موارد مهم تری صرف شود، تا بازنگری در قرادادهای موجود.خلاصهثبات نه تنها به این معنی است که، کارهای مشابه باید به روش های مشابه انجام شود، بلکه به این معناست که کارهای غیر مشابه، باید به روش های مختلف انجام شود. اگر در مورد ثبات بیش از حد حساسیت نشان دهید و سعی کنید چیزهای غیر مشابه را با یک رویکرد انجام دهید، مثلاً استفاده از نام متغیرِ یکسان برای چیزهایی که واقعاً متفاوت هستند یا استفاده از یک الگوی طراحیِ موجود برای کاری که با آن الگو متناسب نیست، پیچیدگی و سردرگمی ایجاد خواهد شد.ثبات، نمونه ی دیگری از طرز فکر سرمایه گذاری است.برای اطمینان از ثبات، باید کمی وقت برای این موارد سرمایه گذاری کنیم:تصمیم گیری در مورد قراردادهاایجاد ابزار برای چک کردن خودکارِ کد در هنگام commitجستجوی موقعیت های مشابه در کدِ موجود و استفاده از آن ها در کد جدیدبررسیِ کد (code review)، برای آموزش تیمسودِ این سرمایه گذاری این است که، کد شفاف تر خواهد بود. برنامه نویس ها می‌توانند رفتار کد را سریع تر و دقیق تر درک کنند، و این به آنها اجازه می‌دهد کار خود را با خطای کمتری به انجام برسانند.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Fri, 03 Dec 2021 16:22:36 +0330</pubDate></item><item><title>نام گذاری</title><link>https://virgool.ir/@sara.rezaei/نام-گذاری-ubhkshokyvis</link><description>انتخاب نام برای متغیرها، متدها و سایر موجودیت ها، یکی از جنبه های مهم طراحی نرم افزار است. نام گذاریِ درست، در واقع نوعی مستندسازی است و به فهم بهتر کد کمک می کند. در مقابل، نام گذاریِ ضعیف، پیچیدگی کد را بالا می برد و باعث ابهام، کج فهمی و در نهایت، تولید خطا در نرم افزار می گردد.اگر مطلب پیچیدگی در نرم افزار را خوانده باشید، می دانید که پیچیدگی، حاصل انباشته شدن مسائل کوچک، به تعداد زیاد، روی هم است. انتخاب یک نام نامناسب، برای یک متغیر، تاثیر زیادی رو پیچیدگی کل سیستم ندارد. با این حال، یک سیستم نرم افزاری قطعا از یک متغیر تشکیل نشده، بلکه شامل هزاران متغیر است؛ و انتخاب نام مناسب برای همه ی آن ها، قطعا تاثیر زیادی روی پیچیدگی نرم افزار خواهد داشت.تصویرسازیهنگام انتخابِ نام، مثلا برای یک متغیر، هدف اصلی، ساخت یک تصویر در ذهن خواننده ی کد است. این تصویر باید تا حد امکان، به آن مفهوم نزدیک باشد. یک نام خوب، اطلاعات کافی درباره ی این که موجودیت مورد نظر چیست، و از آن مهم تر، چه چیزی نیست، در خود دارد.هنگام انتخاب نام از خودتان بپرسید:اگر کسی این نام را به تنهایی ببیند، بدون مشاهده ی مستندات کد، یا جایی که از آن استفاده شده، و بدون هیچ توضیحی، چقدر قادر خواهد بود حدس بزند که این نام، به چه چیز اشاره دارد؟ آیا نام دیگری وجود دارد که تصویر شفاف تری از این مفهوم ارائه دهد؟قطعا، در میزان اطلاعاتی که می توان فقط با یک نام فراهم کرد، محدودیت وجود دارد. همچنین، اگر یک نام شامل بیشتر از دو یا سه کلمه باشد، گیج کننده خواهد شد. بنابراین چالش اصلی این است که، تعداد محدودی کلمه پیدا کنیم، که مهم ترین جنبه های موجودیت مورد نظر را، به تصویر بکشند.نام ها، فرمی از انتزاع هستند. نام ها، یک راه ساده برای فکر کردن به یک موجودیت پیچیده فراهم می کنند. مانند سایر فرم های انتزاع، بهترین نام ها آن هایی هستند که بر روی آن جنبه هایی از موجودیت که مهم تر است تمرکز دارند و هر جزئیاتی که اهمیت کمتری دارد را حذف می کنند.یک نام خوب، دو ویژگی دارد: دقت و ثباتدقت:بیشترین مشکل در نام گذاری این است که، نام ها خیلی عمومی یا مبهم هستند. در نتیجه برای خواننده ی کد مشکل است که حدس بزند که این نام به چه مفهومی اشاره می کند.مثال:/** * Returns the total number of indexlets this object is managing. */&#xA;int IndexletManager::getCount() {...}کلمه ی count خیلی عمومی است. منظور تعداد چه چیزی است؟ اگر کسی نام این متد را ببیند، بدون خواندن کامنت، یا خواندن پیاده سازی متد، نمی تواند بفهمد که این متد چه کاری انجام می دهد. تعداد چه چیزی را بر می گرداند؟ شاید یک نام بهتر برای این متد این باشد:/** * Returns the total number of indexlets this object is managing. */&#xA;IndexletManager::getActiveIndexlets() {...}مثال:// Blink state: true when cursor visible.&#xA;private boolean blinkStatus = true;نام blinkStatus اطلاعات کافی به خواننده نمی دهد. کلمه ی status برای یک متغیر boolean خیلی مبهم است. این کلمه هیچ سرنخی درباره ی معنیِ مقادیر true و false نمی دهد. کلمه ی blink هم مبهم است، اصلا مشخص نیست که چه چیزی قرار است &#34;چشمک بزند&#34;. همین مثال، به این صورت بهتر می شود:private boolean cursorVisible = true;نام cursorVisible اطلاعات بیشتری را فراهم می کند. به خواننده اجازه می دهد که به درستی حدس بزند، معنی true و false چیست.مثال:// Value representing that the server has not voted (yet) for&#xA;// anyone for the current election term.&#xA;private static final String VOTED_FOR_SENTINEL_VALUE = &#34;null&#34;نام این متغیر، نشان می دهد که این یک متغیر اختصاصی برای مشخص کردن یک موضوع است، ولی نمی گوید که آن موضوع چیست. یک نام مشخص تر می تواند بهتر باشد:private static final String NOT_YET_VOTED = &#34;null&#34;مثال:در یک متد که  خروجی ندارد، استفاده از یک متغیر به نام result، می تواند مشکل ایجاد کند. اول این که، این تصویرِ گمراه کننده را ایجاد می کند، که این متغیر، خروجی متد است. دوم این که، هیچ اطلاعاتی از این که این متغیر چه چیز را قرار است در خود داشته باشد، فراهم نمی کند. نام این متغیر، باید دقیقا بگوید، result چه چیز قرار است در آن قرار گیرد، برای مثال، mergedLine یا totalChars. البته اگر متد خروجی داشته باشد، استفاده از کلمه ی result برای خروجی متد قابل قبول است.قوانین نامگذاری، بعضی جاها استثنا دارند. مثلا استفاده از نام های عمومی مثل i و j در حلقه ها، مناسب است. البته تا زمانی که حلقه، تعداد محدودی خط داشته باشد. به عنوان یک قانون کلی، اگر در یک نگاه، همه ی محدوده ی استفاده از یک متغیر دیده می شود، حتی اگر از کلمات عمومی برای نام گذاری استفاده شود، می‌توان معنی آن متغیر را حدس زد. مثلا:for  (i = 0; i &lt; numLines; i++) {&#xA; ...&#xA;}اگر درون این حلقه، تعداد خطوط کمی قرار داشته باشند، با یک نگاه کلی می توان فهمید که i به چه چیزی اشاره می کند. ولی اگر تعداد خطوط حلقه زیاد است یا به هر دلیلی، معنی متغیر، واضح نیست، بهتر است به جای i یا j، از یک کلمه که مفهوم را بهتر برساند استفاده کرد.دقت کنید که نمی توان گفت همیشه، هر چقدر که می توانیم باید نام متغیر را اختصاصی تعیین کنیم. برای مثال متد زیر را در نظر بگیرید:void delete(Range selection) {...}نام selection، خیلی اختصاصی است. این نام تاکید بر این دارد که، متنی که قرار است حذف شود، حتما در UI توسط کاربر، select شده است. در حالی که این متد می تواند از جاهای دیگر هم صدا زده شود، چه متن را کاربر انتخاب کرده باشد یا نه. یک نام عمومی تر مثل range در این مثال، مناسب تر است.اگر پیدا کردن یک نام برای یک متغیر برایتان سخت است، این یک نشانه است. نشانه ای از این که احتمالا آن متغیر، یک هدف یا تعریف مشخص ندارد. در این مواقع، سعی کنید از زاویه ی دیگری به مساله نگاه کنید. مثلا شاید سعی دارید از یک متغیر برای اهداف گوناگون استفاده می کنید. اگر اینطور است، بهتر است چندین متغیر تعریف کنید و هر کدام یک تعریف ساده تر و مشخص تر داشته باشند. در مسیرِ انتخاب نام های بهتر، شما تبدیل به یک طراح بهتر خواهید شد.اگر پیدا کردن یک نام ساده برای یک متغیر یا یک متد، که یک تصویر شفاف از موجودیت مورد نظر بسازد، سخت است، این نشانه ای است از اینکه آن موجودیت، طراحی مناسبی ندارد.ثبات:بعد از دقت، دومین ویژگیِ یک نام خوب، ثبات است. در هر نرم افزاری، متغیرهایی وجود دارند که بارها و بارها استفاده می شوند. مثلا کلمه ی block ممکن است در یک سیستم مدیریت فایل، با اهداف مختلف استفاده شود. ممکن است اشاره به یک block حافظه داشته باشد، یا یک block در یک فایل. در این مثال، باید جایی که block فایل مورد نظر است، از نامی مانند fileBlock استفاده شود. این کار باعث می شود &#34;بار شناختی&#34; کاهش یابد، چرا که اگر فرد یک بار این متغیر را ببیند، هر جای دیگر هم که آن را مشاهده کند، می داند مربوط به چه مفهومی است و نیازی نیست به تعریف متغیر رجوع کند.ثبات در نام گذاری سه شرط اصلی دارد: همیشه برای یک هدف خاص، از یک نام مشترک استفاده شود.هیچ وقت آن نام مشترک، برای هدف دیگری به کار برده نشود.آن نام مشترک باید آن قدر مرتبط باشد، که همه ی متغیرهای با این نام، رفتار یکسانی داشته باشند.در حلقه ها هم بهتر است به ثبات نام متغیر ها توجه کنید. اگر از اسامی i و j برای شمارنده ی حلقه استفاده می کنید، همیشه i برای حلقه ی بیرونی و j برای درونی باشد. این باعث می شود خواننده ی کد هر جا این متغیرها را دید، بداند منظور شمارنده ی کدام حلقه است. یک دیدگاه متفاوتبعضی از برنامه نویس های زبان Go معتقد هستند که نام ها باید بسیار کوتاه باشند، در حدِ فقط یک کاراکتر. در یک ارائه، درباره ی انتخاب نام در Go، آقای Andrew Gerrand می گوید:&#34;نام های طولانی، کاری را که کد انجام می دهد مبهم می کند.&#34;او این مثال را می آورد:func RuneCount(b []byte) int {&#xA;        i, n := 0, 0&#xA;        for i &lt; len(b) {&#xA;              if b[i] &lt; RuneSelf {&#xA;                    i++&#xA;              } else {&#xA;                    _, size := DecodeRune(b[i:])&#xA;                    i += size&#xA;              }&#xA;              n++&#xA;        }&#xA;        return n&#xA;}در این قطعه کد، برای نام همه ی متغیرها، فقط از یک کاراکتر استفاده شده است. این قطعه کد را با نمونه ای با نام های طولانی تر مقایسه کرده:func RuneCount(buffer []byte) int {&#xA;        index, count := 0, 0&#xA;        for index &lt; len(buffer) {&#xA;              if buffer[index] &lt; RuneSelf {&#xA;                    index++&#xA;              } else {&#xA;                    _, size := DecodeRune(buffer[index:])&#xA;                    index += size&#xA;              }&#xA;              count++&#xA;        }&#xA;        return count&#xA;}و معتقد است که قطعه کد اول، با نام های تک کاراکتری، خوانایی بیشتری دارد.در زبان Go، فرهنگ استفاده از نام های کوتاه، غالب است. مثلا به جای کلمه ی character یا channel از ch، به جای data یا difference یا distance از d استفاده می شود.آیا این نام های کوتاه، بیشتر باعث مبهم شدن کد نمی شوند؟ آیا خواننده، برای این که بداند منظور از d چه چیزی است، نباید بیشتر تلاش کند؟البته اگر قراردادی داشته باشیم، که مثلا همیشه n به count اشاره می کند، این ابهام برطرف می شود و در همه ی قسمت های نرم افزار، برنامه نویس ها به راحتی با دیدن n متوجه هدف و منظور آن قسمت از کد می شوند.به طور کلی، میزان خوانایی کد، باید توسط خواننده ی کد تعیین شود نه نویسنده ی آن. اگر شما کدی با متغیرهای تک کاراکتری نوشتید و کسانی که آن را می خوانند به راحتی متوجه هدف و معنای نام گذاری ها می شوند، پس آن کد خوانایی بالایی دارد. ولی اگر خواننده ها شکایت کردند که این کد نیاز به رمزگشایی! دارد، باید در نام هایی که انتخاب کرده اید تجدید نظر کنید. حتی اگر از نام های طولانی استفاده کرده اید و این شکایت ها را گرفتید، باید به سمت نام های کوتاه تر بروید. هدف، بالا بودن میزان خوانایی کد است، چه این هدف با نام های تک کاراکتری براورده شود یا نام های طولانی. جایی مانند حلقه ها، ممکن است نام هایی مانند i و j پذیرفته شده باشد، و جایی، استفاده از حرف b برای اشاره به block، باعث ابهام در کد شود.آقای Andrew Gerrand می گوید:&#34;هرچه فاصله بین تعریف یک نام و موارد استفاده آن بیشتر باشد، نام باید طولانی تر باشد.&#34;خلاصهنام گذاری مناسب، به واضح تر شدنِ کد کمک می کند؛ چرا که وقتی کسی برای اولین بار یک متغیر را می بیند، اولین حدس او درباره ی رفتار آن متغیر، به احتمال زیاد درست خواهد بود. انتخابِ نام خوب، یک مثال از سرمایه گذاری است که در برنامه نویسی استراتژیک و تاکتیکی مطرح شد. اگر کمی زمان بیشتر برای انتخاب نام مناسب بگذارید، در آینده، کار کردن روی کد راحت تر خواهد بود و زمان کمتری خواهد برد. همچنین احتمال خطا کمتر می شود.ایجاد مهارت برای نامگذاری هم یک سرمایه گذاری است. احتمالا روزهای اولی که سعی می کنید به نام های ضعیف یا متوسط بسنده نکنید و یک نام خوب پیدا کنید، انرژی و زمان زیادی از شما گرفته می شود. با این حال، هرچقدر در این مسیر تجربه کسب کنید و پیش بروید، این کار ساده تر خواهد شد. در نهایت به نقطه ای خواهید رسید که انتخاب یک نام مناسب، به هیچ زمان اضافه ای نیاز ندارد و به راحتی انجام می شود.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Fri, 19 Nov 2021 13:00:14 +0330</pubDate></item><item><title>کامنت های بهتر</title><link>https://virgool.ir/@sara.rezaei/کامنت-های-بهتر-djpnaxscfmvb</link><description>وقتی یک برنامه نویس بخشی از نرم افزار را توسعه می دهد، اطلاعات مهمی در ذهن دارد، که همه ی آن ها را نمی توان در کد نشان داد. کامنت ها این اطلاعات را حفظ می کنند، تا برنامه نویس بعدی بتواند راحت تر کد را بفهمد و آن را تغییر دهد.بنابراین، کامنت ها باید اطلاعاتی را در خود داشته باشند، که کد آن ها را آشکار نمی کند.برای مثال، این که چرا در یک بخش از نرم افزار، یک طراحیِ مشخص استفاده شده است. یا این که چرا برای صدا زدن متدِ a باید حتما قبل از آن، متدِ b صدا زده شود. البته قطعا این ها را می توان با حدس زدن، یا خواندنِ خط به خط کد فهمید، ولی این روش، خطاخیز و پردردسر است.همانطور که در مطلب قبلی مربوط به کامنت گذاری گفته شد، یکی از دلایل اصلی کامنت گذاری، انتزاع است. انتزاع یعنی فراهم آوردن یک راهِ ساده، برای فهمیدن یک چیز. این راهِ ساده را، مثلا در مورد یک ماژول، از طریق واسط آن فراهم می آوریم. واسط رسمی یک ماژول، مثلا یک متد، همان signature آن است. ولی واسط غیر رسمی، یعنی اطلاعاتی که در ذهن برنامه نویس هست، ولی نمی تواند در نام متد و پارامترهای ورودی و خروجی آن منعکس کند، می تواند از طریق کامنت فراهم شود.اگر افراد مجبور شوند برای فهمیدن یک ماژول، از واسط عبور کرده و پیاده سازی آن را ببینند، در واقع انتزاعی که ساخته شده، بی فایده بوده است و همه ی پیچیدگی ماژول، به دنیای بیرون تحمیل شده است.برنامه نویس ها باید بتوانند، بدون خواندن پیاده سازی یک ماژول، و فقط از طریق واسط (یعنی آنچه که در اختیار دنیای بیرون قرار گرفته)، انتزاعِ فراهم شده توسط آن ماژول را بفهمند.تنها راهِ انجام این مهم، تکمیلِ نمای بیرونی ماژول، با کامنت است.اولین قدم برای نوشتن کامنت، این است که یک سری قرارداد مشخص کنیم. مثلا این که در چه شرایطی کامنت بنویسیم و فرمت آن چگونه باشد. اگر از زبان های برنامه نویسی استفاده می کنید، که برای آن ها ابزار مستندسازی وجود دارد، مثل Javadoc برای Java یا godoc برای Go، بهتر است از چهارچوب و قوانین همان ابزار تبعیت کنید، و اگر هیچ ابزاری ندارید، باید قراردادهای اختصاصی خودتان را مشخص کنید.قراردادها از دو جنبه به ما کمک می کنند:1. ثبات و هماهنگی فراهم می کنند و این باعث می شود خواندن و فهمیدن کامنت ها ساده تر باشد.2. کمک می کنند که برنامه نویس تمایل بیشتری برای کامنت گذاری داشته باشد. چرا که اگر فرد هیچ ایده ای درباره ی نحوه ی کامنت گذاری نداشته باشد، احتمال این که کامنت ننویسد خیلی بیشتر است.از یک منظر می توان کامنت ها را به 4 نوع تقسیم کرد. این تقسیم بندی کمک می کند در تعیین قرادادها، بتوان شفاف تر عمل کرد:1. نوع اول کامنت های مربوط به واسط هستند که دقیقا قبل از شروع یک ماژول، مانند یک کلاس یا یک متد قرار می گیرد و بخشی از واسط آن ماژول است. مثلا برای کلاس، این کامنت ها، یک توضیح کلی در مورد انتزاعی که آن کلاس فراهم می آورد، در خود دارند. یا برای یک متد، رفتار آن متد، پارامتر ها و خروجی آن، خطاهای احتمالی و عوارض جانبی صدا زدن آن متد و هر اطلاعات دیگری که صدا زننده ی آن متد باید بداند شامل این نوع از کامنت می شود.2. نوع دوم از کامنت ها، دقیقا قبل از تعریف یک عضو از ماژول قرار می گیرند. مثلا در یک کلاس، برای هر کدام از instance variable ها یا فیلد های static کلاس ممکن است توضیحی قبل از آن بنویسیم.3. نوع سوم کامنت های پیاده سازی هستند، که در پیاده سازی ماژول قرار می گیرند. مثلا در یک متد، با استفاده از این نوع کامنت ها، توضیح داده می شود که کد چگونه کار می کند.4. نوع چهارم، کامنت های مربوط به وابستگی هستند، که وابستگی های بین ماژول های مختلف را توضیح می دهند. مثلا در طراحی یک پروتکل شبکه، دو ماژولِ وابسته وجود دارد، گیرنده و فرستنده، که هردوی این ها از طراحی پروتکل تاثیر می پذیرند.مهم ترین چالش در نوشتنِ این نوع از کامنت، تصمیم گیری درباره ی جای قرار گرفتنِ آن است. یک راه حل این است که، در یک فایل مشترک، توضیحات مربوط به وابستگی را کامنت کنیم و در همه ی ماژول های وابسته، به آن کامنت ارجاع دهیم.مثلا در یک فایل با نام theFile این توضیح را می نویسیم:Zombies&#xA;-------&#xA;A zombie is a server that is considered dead by the rest of the cluster; any data stored on the server has been recovered and will be managed by other servers.و در ماژول ها:// See &#34;Zombies&#34; in theFile.خیلی از کامنت هایی که نوشته می شوند، بی فایده هستند، چرا که دقیقا کپی شده ی کد هستند. یعنی همه ی اطلاعاتی که در کامنت نوشته شده، به راحتی از کدی که دقیقا بعد از آن آمده قابل استنباط است.مثال:ptr_copy = get_copy(obj)                         # Get pointer copy&#xA;if is_unlocked(ptr_copy):                         # Is obj free?&#xA; return obj                                                   # return current obj&#xA;if is_copy(ptr_copy):                                 # Already a copy?&#xA; return obj                                                   # return obj&#xA;thread_id = get_thread_id(ptr_copy)&#xA;if thread_id == ctx.thread_id:                  # Locked by current ctx&#xA; return ptr_copy                                         # Return copyدر این قطعه کد، کامنت ها (که با # مشخص شده اند) هیچ اطلاعات مفیدی در خود ندارند. فقط هر آنچه در کد بوده، در کامنت کپی شده است. برای فهمیدن این که کامنت مفید است یا نه، بعد از این که کامنت را نوشتید، این سوال را از بپرسید:آیا کسی که برای اولین بار این قطعه از کد را می بیند، بعد از خواندن کد، می تواند همین کامنت را بنویسد؟ اگر جواب بله است، یعنی آن کامنت بی فایده است و بهتر است حذف شود.یک اشتباه دیگر این است که در کامنت، دقیقا همان کلماتی را استفاده کنیم که در نام ماژول استفاده شده است. این نوع کامنت ها هم هیچ ارزشی ایجاد نمی کنند. مثلا:/*&#xA;* Obtain a normalized resource name from REQ.&#xA;*/&#xA;private static String[] getNormalizedResourceNames(HTTPRequest req)در این مثال، اطلاعات مهم تری هست که می توانست در کامنت قرار داده شود. مثلا این که منظور از عبارت normalized resource name چیست؟ توضیح دادن این نکته، در این کامنت، می توانست آن را به یک کامنت مفید تبدیل کند.یک مثال دیگر، این کامنت، یک کامنت بی فایده است:/*&#xA;* The horizontal padding of each line in the text.&#xA;*/&#xA;private static final int textHorizontalPadding = 4;ولی همین کامنت، می تواند به این صورت بهتر شود:/*&#xA;* The amount of blank space to leave on the left and&#xA;* right sides of each line of text, in pixels.&#xA;*/&#xA;private static final int textHorizontalPadding = 4;کلمه ی padding، که در نام فیلد هم آمده، به جای تکرار، در کامنت، توضیح داده شده است. در واقع حالا، این کامنت، اطلاعاتی را فراهم می کند، که از خواندن کد به دست نمی آید. مثلا این که واحد به پیکسل است، و این که padding قرار است هم در سمت چپ و هم در سمت راست متن اعمال شود.علاوه بر تقسیم بندی که بالاتر گفته شد، در یک نمای کلی تر، می توان کامنت ها را به دو نوع تقسیم کرد:1. کامنت های سطح پایین، که جزئیات بیشتری از کد را آشکار می کنند.این کامنت ها، معمولا در تعریف متغیرها، پارامترهای متدها یا خروجی آن ها استفاده می شوند و جزئیاتی مانند این ها را آشکار می کنند:برای یک متغیر، واحد اندازه گیری آن چیست؟ مثلا در مورد متغیری که زمان را در خود نگه می دارد، آیا ثانیه است، دقیقه است یا میلی ثانیه؟اگر برای یک متغیر، مقدار null قابل قبول است، معنی آن چیست؟ در صورتی که null باشد، چه شرایطی پیش می آید؟اگر در استفاده از یک متغیر، resource خاصی وجود دارد که باید بعد از استفاده آزاد شود، مسئول آزاد کردن آن، چه بخشی از کد است؟آیا اقدام خاصی هست که در استفاده از یک متغیر باید لحاظ شود؟ مثلا این که یک لیست، حتما باید حداقل یک آیتم در خودش داشته باشدیک مثال از کامنتِ سطح پایینِ بد:// Current offset in resp Buffer&#xA;uint32_t offset; و همان کامنت به این صورت می تواند بهتر شود://  Position in this buffer of the first object that hasn&#39;t&#xA;//  been returned to the client.&#xA;uint32_t offset;در حالت اول، مشخص نیست منظور از Current چیست. در واقع این کامنت می توانست با تغییر نام متغیر offset به currentOffset حذف شود. ولی در حالت دوم، توضیحی که کامنت می دهد، چیزی است که کد قادر به گفتن آن نیست.2. کامنت های سطح بالا، که درک شهودی بیشتری به برنامه نویس می دهند. این کامنت ها جزئیات را حذف می کنند و به خواننده ی کد کمک می کنند که درک بهتری از ساختار و مفهوم کد داشته باشد.یک مثال از کامنت سطح بالای بد:// If there is a LOADING readRpc using the same session&#xA;// as PKHash pointed to by assignPos, and the last PKHash&#xA;// in that readRPC is smaller than current assigning&#xA;// PKHash, then we put assigning PKHash into that readRPC.&#xA;int readActiveRpcId = RPC_ID_NOT_ASSIGNED;&#xA;for (int i = 0; i &lt; NUM_READ_RPC; i++) {&#xA; if (session == readRpc[i].session&#xA; &amp;&amp; readRpc[i].status == LOADING&#xA; &amp;&amp; readRpc[i].maxPos &lt; assignPos&#xA; &amp;&amp; readRpc[i].numHashes &lt; MAX_PKHASHES_PERRPC) {&#xA; readActiveRpcId = i;&#xA; break;&#xA; }&#xA;}چرا این کامنت بد است؟ چون خط به خط کد را توضیح داده و در واقع تکرار کد است.نسخه ی بهتر همان کامنت:// Try to append the current key hash onto an existing&#xA;// RPC to the desired server that hasn&#39;t been sent yet.همانطور که مشخص است، جزئیات حذف شده و به جای آن، عملکرد کلی کد توضیح داده شده است.نوشتن این کامنت ها سخت تر از کامنت های سطح پایین است، چون باید به کد از جهت های مختلف نگاه کرد. برای نوشتن آن ها، این سوال ها را از خودتان بپرسید:این کد چه کاری می خواهد انجام دهد؟ساده ترین جمله ای که می تواند همه چیز را در مورد این کد توضیح دهد چیست؟مهم ترین مساله درباره ی این کد چیست؟مهندس ها معمولا به جزئیات، بیشتر اهمیت می دهند. البته که این برای یک مهندسِ خوب بودن لازم است، ولی یک طراح نرم افزار، باید بتواند گاهی از جزئیات بگذرد و به سیستم از یک نگاه بالاتر بنگرد. یعنی تصمیم گیری درباره ی این که کدام جنبه های سیستم از همه مهم تر هستند و توانایی نادیده گرفتن جزئیات و شناخت سیستم از طریق پایه ای ترین ویژگی های آن. اگر مطلب برنامه نویسی ماژولار را خوانده باشید، حتما می دانید که این، ذات انتزاع است؛ پیدا کردنِ یک راهِ ساده، برای فکر کردن به یک موجودیت پیچیده.خلاصههدف از کامنت گذاری، اطمینان از این مساله است، که ساختار و رفتار سیستم، برای خواننده ی کد آنقدر واضح است که وی می تواند به سرعت اطلاعاتی که نیاز دارد را به دست آورد و تغییرات را روی کد اعمال کند. قسمتی از این اطلاعات می تواند در کد نمایش داده شود، به صورتی که نیاز به کامنت نباشد. ولی همیشه مقداری اطلاعات هست، که قابل بیان کردن در کد نیست. کامنت ها قرار است این خلاء را پر کنند.وقتی می گوییم کامنت قرار است اطلاعات را واضح کند، &#34;واضح&#34; از سوی کسی که اولین بار است کد را می خواند، نه کسی که کد را نوشته است. وقتی کامنت می نویسید، سعی کنید خودتان را جای کسی بگذارید که در حال خواندن کد است و از خودتان بپرسید اطلاعات کلیدی که خواننده لازم خواهد داشت چه چیزهایی هستند.اگر کسی کد شما را خواند و به شما گفت که کدتان ابهام دارد، هیچ جای بحثی نیست. اگر خواننده احساس می کند که کد به اندازه ی کافی گویا نیست، پس قطعا کد به اندازه ی کافی گویا نیست. به جای بحث با او، سعی کنید بفهمید کدام بخش های کد، واضح نیستند و سعی کنید آن بخش ها را شفاف تر کنید؛ یا با کد بهتر یا با کامنت بهتر.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Thu, 28 Oct 2021 23:34:35 +0330</pubDate></item><item><title>بهانه هایی برای گریز از کامنت گذاری</title><link>https://virgool.ir/@sara.rezaei/بهانه-هایی-برای-گریز-از-کامنت-گذاری-smtwpvpfouek</link><description>کامنت گذاری در کد، یک شمشیر دو لبه است. اگر به درستی انجام شود، می تواند طراحی سیستم را بهتر کند و در غیر این صورت، بسیار آسیب زننده است.کامنت گذاری در واقع مستندسازی کد است. ولی بسیاری از برنامه نویس ها معتقدند که کامنت فقط باعث اتلاف وقت می شود. حتی کسانی که هم معتقدند کامنت گذاری مفید است، خیلی کم این کار را انجام می دهند. به همین دلیل، در اکثر سازمان ها، بخش های بسیاری از کد بدون کامنت است.معمولا برنامه نویس ها 4 بهانه برای ننوشتن کامنت دارند: کد خوب نیازی به مستندسازی نداردبرای کامنت گذاری زمان ندارمکامنت ها بعد از گذشت زمان، منقضی شده و باعث اشتباه می شوندهمه ی کامنت هایی که من دیده ام بی فایده بوده انداین بهانه ها را بررسی کنیم:1. کد خوب نیاز به مستندسازی نداردبعضی افراد باور دارند که اگر کد خوب نوشته شود، به کامنت گذاری نیازی نیست. قطعا همه ی ما دوست داریم این جمله را باور کنیم، ولی در نهایت، اشتباه است. البته قطعا کارهایی هست که می توان انجام داد تا نیاز به کامنت گذاری کمتر شود. مثلا این که برای متغیرها اسم مناسب انتخاب کنیم. ولی باز هم اطلاعات زیادی وجود دارد، که آن ها را نمی توان در کد نشان داد.برای مثال، در یک کلاس، فقط بخش کوچکی از واسط آن، مثل signature متدها را می توان در کد مشخص کرد. ولی این که هر متد دقیقا چه کاری انجام می دهد، یا معنی خروجی آن چیست، تنها از طریق کامنت گذاری قابل انتقال به خواننده ی کد است. چیزهای دیگری هم هست که در کد قابل توضیح دادن نیست، مثلا این که چرا یک طراحیِ به خصوص برای بخشی از کد انتخاب شده و یا چرا یک متدِ بخصوص صدا زده شده است.بعضی برنامه نویس ها معتقدند که برای دانستن این که یک متد چه کاری انجام می دهد، باید کد آن را خواند نه این که از طریق کامنت متوجه آن شد. ولی آیا این کار عقلانی است؟ آیا باعث اتلاف وقت نمی شود؟ شاید در یک نرم افزار کوچک این کار امکان پذیر باشد، ولی در سیستم های بزرگ، خواندن کدِ متد برای فهمیدن رفتار آن، روش کاربردی نیست.علاوه بر این، کامنت ها برای انتزاع، ضروری هستند. همانطور که در مطلب برنامه نویسی ماژولار اشاره شد، هدف از انتزاع، پنهان کردن پیچیدگی است. انتزاع، یک نمای ساده شده از یک موجودیت است، که اطلاعات ضروری (برای آن انتزاع) را حفظ می کند، و هر اطلاعات اضافه ای را دور می ریزد. اگر برای استفاده از یک قطعه کد، افراد مجبور باشند کد آن را بخوانند، پس انتزاعی وجود ندارد و همه ی پیچیدگی آن قطعه از کد، در معرض دنیای بیرون قرار گرفته است. در مورد متدها، اگر یک متد کامنت نداشته باشد، تمام انتزاع آن، شامل نام متد و نوع پارامتر های ورودی و خروجی آن است.برای مثال فرض کنید یک متد داریم، که یک بخش از یک string را استخراج می کند. این متد دو ورودی دارد، start و end که مشخص کننده ی شروع و پایان ِ قسمتی از string هستند که باید جدا شود. قطعا از ظاهر این متد، نمی توان تشخیص داد که آیا string خروجی، شامل کاراکترهایی که در start و end هستند هم می شود؟ یا فقط هر آنچه بین این دو عدد است را جدا می کند؟ برای فهمیدن این سوال، استفاده کننده، باید کد را بخواند تا بتواند از این متد استفاده کند.بنابراین اگر می خواهید از انتزاع استفاده کنید تا پیچیدگی را پنهان کنید، با استفاده از کامنت بهتر می توانید این کار را انجام دهید.2. برای کامنت گذاری زمان ندارممعمولا برنامه نویس ها، اولویت کامنت گذاری را پایین تر از انجام سایر وظایف در نظر می گیرند. بین این که یک بخش جدید را توسعه دهند، یا این که برای کدی که قبلا نوشته اند کامنت بنویسند، معمولا اولی را انتخاب می کنند. همه می دانیم که پروژه های نرم افزاری، تقریبا همیشه از نظر زمانی تحت فشار هستند و در اکثر مواقع، کارهای زیادی هست که اولویت آن ها از کامنت گذاری بالاتر به نظر می رسد.ولی باید از دیدگاه سرمایه گذاری به این مساله نگاه کرد. اگر یک نرم افزار با ساختار تمیزتر می خواهید، که در بلندمدت، عملکردِ شما را بهتر می کند و سرعت توسعه را بیشتر، باید در کوتاه مدت، زمانی را به ساختن این ساختار تمیز اختصاص دهید. کامنت های خوب، کمک بسیاری به نگهداری یک سیستم نرم افزاری می کنند و زمانی که برای کامنت گذاری صرف می شود، در آینده سود ده خواهد بود.علاوه بر این، کامنت گذاری قرار نیست زمانِ زیادی از شما بگیرد. در نظر بگیرید که شما برای تایپ کردن کد چقدر زمان می گذارید و مقایسه کنید با زمانِ تخصیص داده شده به تحلیل، طراحی، کامپایل کردن کد و تست آن. احتمالا این زمان بیشتر از 10 درصد نخواهد بود. تایپ کردن کامنت هم دقیقا همینطور است. نهایتا باید 10 درصد از زمان را به آن اختصاص داد، و این مستند سازی، اگر به درستی انجام شود، این هزینه را به خوبی جبران خواهد کرد.3. کامنت ها بعد از گذشت زمان، منقضی و باعث اشتباه می شونددرست است. بعضی کامنت ها ممکن است در طول زمان اعتبار خود را از دست بدهند. ولی دقت کنید زمانی کامنت بی اعتبار می شود، که تغییرات گسترده در کد اعمال شده باشد و اعمال این تغییرات، قطعا زمان خیلی بیشتری از تغییر کامنت ها از شما خواهد گرفت. اگر کامنت ها، از نظر جایگاه در کد، نزدیک کدی باشند که به آن مربوط است، می توان در زمان تغییر کد، به سادگی و با صرف زمان کم، کامنت را هم تغییر داد و از منقضی شدن آن جلوگیری کرد.4. همه ی کامنت هایی که من دیده ام بی فایده و زحمت اضافه بوده اندقطعا هر مهندس نرم افزار، در طول دوره ی کاری خود، کامنت هایی را دیده است که هیچ اطلاعات مفیدی در خود نداشته اند و هیچ کمکی به مستندسازی نرم افزار نکرده اند. ولی این دلیلی برای حذف کامنت گذاری از روال توسعه ی نرم افزار نیست. وقتی افراد یاد بگیرند چگونه کامنت بنویسند و یک چهارچوب کلی برای این کار داشته باشند، این مشکل حل خواهد شد. مساله این است که افراد برای نوشتن کامنت نیاز به آموزش دارند و این کاری نیست که صرفا با سلیقه ی شخصی و بدون داشتن چهارچوب و روش های مشخص انجام شود.مزایای نوشتن کامنتدر مطلب پیچیدگی در نرم افزار گفته شد که پیچیدگی، به سه صورت خودش را در سیستم نشان می دهد:تشدید تغییر (change amplification)یعنی برای یک تغییر کوچک، بخش های زیادی از سیستم را باید تغییر داد.بارِ شناختی (cognitive load)یعنی برای اعمال یک تغییر، برنامه نویس باید اطلاعات وسیعی از بخش های مختلف سیستم به دست آورد.ندانسته های ناشناخته (unknown unknowns)یعنی این که چه بخش هایی باید تغییر کنند، یا برای اعمال تغییرات، چه ملاحظاتی باید در نظر گرفته شود، مشخص و شفاف نیست.کامنت گذاری خوب، می تواند در دو مورد آخر کمک کند. هم می تواند بار شناختی را کم کند. چرا که اطلاعاتی که برنامه نویس برای تغییر بخش هایی از کد باید داشته باشد، به سرعت به او می دهد و چون نیازی به خواندن کد نیست، از هجوم اطلاعات اضافه به ذهن برنامه نویس جلوگیری می شود. همچنین میزان ناشناخته ها کمتر می شود و به راحتی می توان تاثیرِ تغییرات را پیش بینی کرد و آن بخش هایی که به کد فعلی مرتبط است را شناسایی کرد. علت اصلی پیچیدگی در نرم افزار، وابستگی ها هستند و با کامنت گذاری می توان این وابستگی ها را شفاف کرد و ابهام را کاهش داد.خلاصهایده ی کلی کامنت گذاری، نمایش اطلاعاتی است، که در هنگام طراحیِ یک بخش از کد، در ذهن طراح و برنامه نویس بوده است، ولی امکان نشان دادن آن در کد نیست. این اطلاعات شامل جزئیات سطح پایین مانند محدودیت های سخت افزاری که افراد را وادار به یک طراحی خاص می کند، تا جزئیات سطح بالاتر، مثل علت وجودی یک کلاس یا یک ماژول خاص می شود.وقتی یک برنامه نویسِ دیگر، بعد از گذشت زمان، می خواهد آن ماژول را تغییر دهد، با خواندن کامنت ها، کار خود را بسیار سریعتر انجام می دهد. ولی بدون کامنت، باید آن چه که در ذهن برنامه نویس قبلی بوده را حدس بزند. این اتفاق هم باعث اتلاف وقت می شود، هم این که با حدسِ اشتباه، ممکن است برنامه نویس جدید، کدی بنویسد که منجر به خطا شود. حتی اگر قرار است همیشه! همان برنامه نویس قبلی روی یک بخش از کد کار کند، باز هم کامنت گذاری لازم است. کافی است فقط چند هفته از زمانی که یک کد را نوشته اید بگذرد، تا جزئیات طراحی و پیاده سازی آن بخش از کد را فراموش کنید.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Thu, 07 Oct 2021 00:47:44 +0330</pubDate></item><item><title>دوباره فکر کن</title><link>https://virgool.ir/@sara.rezaei/دوباره-فکر-کن-astxkwmjdrhr</link><description>طراحی نرم افزار، سخت است. در طراحی نرم افزار، به احتمال خیلی زیاد، اولین فکری که به ذهن می رسد، بهترین فکر نیست. اگر به جای پیاده سازیِ اولین فکر، سعی کنیم گزینه های دیگری را بررسی کنیم، و از ابعاد دیگر به مساله نگاه کنیم، قطعا به نتایج خیلی بهتر خواهیم رسید.باید دوباره و چندباره فکر کرد!تصور کنید می خواهیم برای مدیریت متنِ یک فایل، کلاسی طراحی کنیم، که قرار است توسط یک ویرایشگر متن استفاده شود.اولین کار این است که واسط کلاس را طراحی کنیم (کلاس یک ماژول است، شامل واسط و پیاده سازی). نخستین فکری که ممکن است به ذهن برسد این است: یک طراحی بر اساس سطرهای متن، شامل 3 متد افزودن، ویرایش و حذف خطوط از متن. یک برنامه نویسِ معمولی، ممکن است بلافاصله، با همین ایده شروع به کدنویسی بکند.اما یک برنامه نویس حرفه ای، این فکر اولیه را کنار گذاشته، و به طراحی جایگزین می اندیشد. طراحی جایگزین می تواند به این شکل باشد: متدهای کلاس، کاراکتر را به عنوان ورودی گرفته، آن را در خطوط مشخصی از متن اضافه می کنند یا از آن حذف می کنند.ولی این برنامه نویس حرفه ای، به همین دو ایده اکتفا نمی کند، طراحی سوم می تواند اینگونه باشد: متدهای کلاس، در یک position مشخص (فارغ از خطوط متن)، مثلا در position شماره 14، یک تعداد کاراکتر را اضافه یا حذف می کنند.نکته ی مهم در این طراحی ها این است که تا جای ممکن با هم تفاوت های ریشه ای داشته باشند. حتی اگر مطمئن هستید که فقط یک راه وجود دارد، سعی کنید یک طراحی دیگر پیدا کنید، هر چقدر هم که بد باشد و نامربوط به نظر برسد. با این روش هم بیشتر یاد می گیرید، هم نقطه ضعف های طراحی اولیه را پیدا می کنید.همچنین می توان یک لیست از مزایا و معایب هر کدام از طراحی ها تهیه کرد. مثلا یکی از موارد این لیست می تواند میزان سادگی استفاده از ماژول، توسط ماژول های دیگر باشد. در مثالی که بررسی کردیم، طراحی بر اساس خطوط، زحمت زیادی را به ماژول های دیگر تحمیل می کند تا از متدهای کلاس استفاده کنند. چرا که آن ها باید بدانند متن از چه خطوطی تشکیل شده، دانش مربوط به متن های چند خطی را داشته باشند و بدانند چگونه خطی که را می خواهند ویرایش کنند شناسایی کنند. همچنین در طراحی بر اساس یک کاراکتر (طراحی دوم)، اگر استفاده کننده بخواهد چند کاراکتر را تغییر دهد، باید در یک loop، عملیات را انجام دهد.موارد دیگری که می توان در این لیست داشت:از بین طراحی ها، کدام یک واسط ساده تری دارد؟آیا یکی از طراحی ها، عام منظوره تر از بقیه است؟کدام طراحی از نظر پرفورمنس، بهتر عمل خواهد کرد؟بعد از این که لیست مزایا و معایب را تهیه کردید، بهتر می توانید در مورد انتخاب یک طراحی تصمیم گیری کنید. ممکن است یکی از طراحی ها را انتخاب کنید، یا ویژگی های چندتا از آن ها را با هم ترکیب کنید و یک طراحی به دست بیاورید که از تک تک طراحی های قبلی بهتر باشد.حتی ممکن است بعد از بررسی، متوجه شوید هیچ کدام از طراحی ها به اندازه ی کافی جذاب نیست. در این صورت، از مشکلات و معایب طراحی های قبلی که کشف کرده اید استفاده کنید، تا بتوانید به یک طراحی جدید برسید. فقط در طراحی یک کلاس نیست که باید چندبار فکر کنید. مثلا وقتی واسط یک ماژول را چندبار طراحی کردید و به نتیجه رسیدید، در زمان پیاده سازی هم باید از این روش استفاده کنید. در مثالِ ویرایشگر متن، ممکن است زمان پیاده سازی، اولین فکری که به ذهن می رسد استفاده از یک linked list باشد، ولی بعد باید پیاده سازی های جایگزین پیدا کنید. مثلا array با تعداد مشخص، یا هر ساختار داده ی دیگری که می توان در آن پیاده سازی خاص استفاده کرد. بعد با روال قبلی، برای آن ها مزایا و معایب مشخص کنید و از بین آن ها بهترین را انتخاب کنید.در انتخاب معیارهای خود دقت کنید. مثلا در پیاده سازی، ممکن است مساله ی مهم، پرفورمنس و سادگی باشد. ولی در انتخابِ واسط، معیارِ مهم، نحوه ی استفاده ی دیگران از ماژول است. اگر یک سطح بالاتر برویم، در انتخاب طراحی های معماری کلان، معیارها و وزنی که به آن ها می دهیم، به طور کلی تفاوت دارد.دقت کنید که طراحیِ دوباره، زمان زیادی نمی گیرد. برای یک ماژول کوچک مثل یک کلاس، شاید تنها یک یا دو ساعت زمان لازم باشد، تا طراحی های جایگزین پیدا کنیم و از بین آن ها انتخاب کنیم. در مقایسه با زمان پیاده سازی، که ممکن است چند روز طول بکشد، این یکی دو ساعت اصلا مقدار زیادی نیست. در عوض، صرف کردن این دو ساعت، قطعا باعث می شود به طراحی بهتری برسیم و در بلندمدت، در زمان صرفه جویی کرده ایم. در ماژول های بزرگتر، ممکن است این دو ساعت تبدیل به چند روز شود، ولی فواید آن هم بیشتر خواهد بود چون تاثیر آن را در بخش های بیشتری از سیستم خواهیم دید.برای برنامه نویس های باهوش، در مسیرِ رشد شغلی شان، یک خطر بالقوه وجود دارد. آن ها ممکن است در ابتدای کارشان، که با سیستم های ساده تر کار می کنند، همیشه اولین ایده را به کار ببرند و نتیجه هم بگیرند. در طول زمان، ممکن است فکر کنند که همیشه قرار است اولین ایده، بهترین ایده باشد و این طرز فکر، آسیب زننده است. وقتی فرد این عادت را در خودش پرورش دهد که همیشه با اولین ایده کار را شروع کند، در طول زمان که کار روی سیستم های پیچیده تر و بزرگ تر را در دست می گیرد، ممکن است دیگر اولین فکری که به ذهنش می رسد بهترین نباشد. در آن صورت، تغییر عادت شاید بسیار سخت باشد و کارایی فرد پایین بیاید، چرا که طراحی سیستم های نرم افزاری بزرگ، یک قانون دارد:هیچ کس به آن اندازه باهوش نیست، که با اولین فکر به بهترین نتیجه برسد.این که شما چندبار به یک مساله فکر کنید، به این معنی نیست که شما انسان باهوشی نیستید، بلکه به این معنی است که مساله واقعا سخت است، و کار کردن روی یک مساله ی سخت، بسیار جذاب تر از حل کردن مساله ی آسان است، که احتمالا خیلی به فکر کردن نیاز ندارد و با اولین ایده می توان به راه حل رسید.خلاصهاگر در طراحی نرم افزار، با این رویکرد پیش بروید که اولین ایده را کنار گذاشته و ایده های جایگزین پیدا کنید، نه تنها در نهایت به یک طراحی بهتر برای نرم افزار می رسید، بلکه مهارت های شما در طراحی نرم افزار بهتر خواهد شد. طی کردن این فرایند، که رویکرد ها و روش های مختلف را کشف کنید و آن ها را با هم مقایسه کنید، به شما در شناسایی فاکتورهایی که یک طراحی را خوب یا بد می سازد، کمک خواهد کرد. در طول زمان، شما قادر خواهید بود به راحتی یک طراحی بد را تشخیص دهید و آن را به یک طراحی خوب تبدیل کنید.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Fri, 01 Oct 2021 14:27:06 +0330</pubDate></item><item><title>مدیریت خطا</title><link>https://virgool.ir/@sara.rezaei/مدیریت-خطا-eoc0uavglejr</link><description>یکی از منابع مهم پیچیدگی در سیستم های نرم افزاری، exception ها هستند. نوشتن کدی که با شرایط خاص سر و کار دارد، خیلی سخت تر از کدی است که یک کار ساده را در شرایط نرمال انجام می دهد. در این نوشته، این موضوع را بررسی می کنیم که exception ها چگونه بر پیچیدگی نرم افزار تاثیر می گذارند و چطور می توان آن ها را بهتر مدیریت کرد.چرا exception ها به پیچیدگی می افزایند؟ابتدا exception را تعریف کنیم. به شرایطِ غیرعادی، که جریانِ نرمالِ نرم افزار را مختل می کند exception گفته می شود. خیلی از زبان های برنامه نویسی، مکانیزمی دارند که به کد اجازه می دهد یک exception تولید کند تا توسط کدی که از آن استفاده می کند گرفته و مدیریت شود. ولی exception ها می توانند بدون این مکانیزم هم اتفاق بیفتند. برای مثال، در نظر بگیرید یک متد، زمانی که نتواند رفتار نرمال خودش را به درستی انجام دهد و دچار مشکل می شود، در خروجی خود یک مقدار مشخص را بر می گرداند (مثلا یک کد خطا)، که به صدازننده ی خود بگوید، کار درست انجام نشده است. به این شرایط هم exception می گوییم.اگر با زبان هایی مثل #C یا Java کار کرده اید، احتمالا exception برای شما بخشی از syntax زبان است. در خواندن این نوشته، زبان را فراموش کنید و با مفهومِ exception، به صورتی که توضیح داده شد برخورد کنید.چند مثال از دلایل اتفاق افتادن exception:قطعه کدی که یک متد را صدا زده، ممکن است پارامترهای اشتباهی به آن پاس داده باشد.متد ممکن است نتواند آن چه که پیاده سازی شده را انجام دهد. مثلا یک عملیات IO به دلیل پیدا نشدن فایل به مشکل می خورد یا یک منبع خارجی که متد می خواهد با آن کار کند، مثل دیتابیس، در دسترس نیست.در سیستم های توزیع شده، packet های شبکه ممکن است گم شوند یا تاخیر داشته باشند یا ممکن است server در زمانِ مناسب پاسخ ندهد.کد دچار bug شود یا با موقعیت هایی برخورد کند که برای رویارویی با آن ها آماده نیست.هنگام پیاده سازی نرم افزار، نوشتن کدی که exception را مدیریت می کند، بسیار سخت تر از نوشتن کدی است که قرار است فقط با شرایط نرمال کار کند. یک exception، جریان عادی کد را قطع و در آن اختلال ایجاد می کند.وقتی یک exception اتفاق می افتد، برنامه نویس می تواند دو کار انجام دهد: به exception توجه نکند و کار را جلو ببرد. مثلا، اگر یک packet شبکه گم شد، آن را دوباره بفرستد. اگر دیتابیس در دسترس نیست، دیتای قدیمی تری که جای دیگری ذخیره شده را برگرداند (مثلا موقتا از cache بخواند، در حالی که می داند زمان انقضای آن گذشته است)این روش مشکلاتی دارد. در مثالِ گم شدن packet، ممکن است که آن packet گم نشده بوده و تنها تاخیر داشته است. در این صورت، اگر از روشِ ارسالِ دوباره استفاده کرده باشیم، یک packet، دو بار به مقصد می رسد. در نتیجه، برای گیرنده ی packet، یک شرایط exception خیز فراهم کردیم و گیرنده باید آن را مدیریت کند. (برای مثال، چک کند که packet تکراری است یا نه، و در صورت تکراری بودن، تصمیم بگیرد که این شرایط را چطور مدیریت کند)یا در مثالِ در دسترس نبودن دیتابیس، اگر دیتای قدیمی تر هم در دسترس نباشد چه؟ اگر همه ی کپی هایی که از دیتابیس داشتیم، از دسترس خارج شده بودند، چگونه exception را مدیریت کنیم؟روش دوم این است که هر جا exception اتفاق افتاد، عملیات را متوقف کند و exception را به سطح بالاتر انتقال دهد. استفاده از این روش هم مشکلاتی دارد. ممکن است در آن نقطه که عملیات متوقف می شود، سیستم در یک وضعیت (state) نامناسب باشد. در این صورت، کدی که exception را مدیریت میکند، وظیفه ی برگرداندنِ وضعیت سیستم به یک وضعیت پایدار را هم دارد. مثلا شاید یک سری تغییرات که قبل از اتفاق افتادنِ exception اعمال شده بودند، باید rollback شوند.همچنین، در زبان هایی که مکانیزم های مدیریت exception دارند (مانند C# و Java)، ممکن است خواندن کد سخت شود. قطعه کد زیر را در نظر بگیرید:try   {&#xA;       ...&#xA;      }&#xA;catch (FileNotFoundException e) {&#xA;      // Do Something&#xA;}&#xA;catch (ClassNotFoundException e) {&#xA;       // Do Something&#xA;}&#xA;catch (EOFException e) {&#xA;       // Not a problem&#xA;}&#xA;catch (IOException e) {&#xA;       // Do Something&#xA;}&#xA;catch (ClassCastException e) {&#xA;       // Do Something&#xA;}توجه کنید که تعداد خطوط، چقدر بیشتر از زمانی است که بخواهیم فقط برای عملیات نرمال کد بنویسیم. همچنین مشخص کردن این که هر exception دقیقا کجا رخ داده است، کار سختی است.یک راه دیگر این است که برای هر قسمت از کد که امکانِ اتفاق افتادنِ خطا دارد، یک try جداگانه بنویسیم. در این صورت مشخص خواهد شد که exception دقیقا کجا رخ داده است. ولی هم خواندن کد سخت می شود و هم جریان اجرای کد مختل خواهد شد.مساله ی دیگر این است که بسیاری مواقع، کدهایی برای مدیریت exception می نویسیم، ولی از کار کردن آن ها اطمینان حاصل نمی کنیم. مثلا خطاهای IO را نظر بگیرید. معمولا در محیط تست، این خطاها مشخص نمی شوند و کدی که exception مربوط به این بخش ها را مدیریت می کند هیچ وقت تست نمی شود. یکی از عادت های بد برنامه نویس ها این است که، در هنگام مدیریت خطا، با تعریف exception های غیرضروری، اوضاع را بدتر می کنند. گویی آن ها معتقدند: &#34;هرچقدر exception های بیشتری شناسایی شود، بهتر است&#34;. به هر چیز کوچکی که مشکوک شوند، یکی exception تولید می کنند و این باعث می شود پیچیدگی سیستم بیشتر شود.راه حل های بهتری هم وجود دارد. هر بار که اتفاق غیرنرمالی در سیستم می افتد، ناچار نیستیم حتما exception تولید کنیم. مثلا فرض کنید متدی داریم، و در بخشی از عملکرد آن، می خواهیم یک فایل را حذف کنیم. در این سناریو، شرایطِ نرمال این است که فایل وجود دارد، و نرم افزار به راحتی آن را حذف می کند. یکی از شرایط غیرنرمال، این است که فایل وجود نداشته باشد. برای مدیریت این شرایط غیرنرمال، دو راه حل می توانیم داشته باشیم:1. یک exception تولید کرده و به صدازننده ی این متد اعلام کنیم که فایل موجود نیست. در این صورت، وظیفه ی حل مساله را به آن صدازننده ی بیچاره محول کرده ایم، در حالی که ممکن است او هم نداند که این خطا را چگونه مدیریت کند. این روش به پیچیدگی سیستم می افزاید.2. به جای تولید exception، به بیزنس متد فکر کنیم. مثلا، شاید بیزنس به گونه ای باشد، که هدف نهایی، عدم وجود آن فایل روی دیسک است، بنابراین، اگر فایل وجود دارد، آن را حذف می کنیم و اگر وجود ندارد، چه بهتر! . با این تصمیم گیری، برنامه نویس، راه حل راحت را (که پرتاب کردن یک exception به سمت یک کد دیگر است) انتخاب نکرده، بلکه آگاهانه به این فکر کرده، که منطقِ آن قطعه از کد، چه پتانسیل هایی برای مدیریت این خطا دارد.در نوشته ی مربوط به برنامه نویسی ماژولار اشاره شد که هر ماژول دو نوع واسط می تواند داشته باشد:واسط رسمی: آنچه که در ظاهر آن ماژول مشخص است. مثلا signature یک متدواسط غیر رسمی: آنچه که به دنیای بیرون از ماژول مربوط است، ولی در رفتار ماژول پنهان شده است.یک exception، یک عنصر پیچیده از واسط ماژول است. نه فقط در خود آن ماژول تاثیر می گذارد، بلکه بر تمام کسانی که از آن استفاده می کنند موثر است و بر پیچیدگی آن ها می افزاید.تولید یک exception ساده است و handle کردن آن سخت. بهترین راه برای کم کردن آسیب ناشی از exception ها، این است که تعداد قسمت هایی از کد که باید به exception رسیدگی کنند را کاهش دهیم.در ادامه می خواهیم چهار روش را بررسی کنیم، که به ما کمک می کنند، نیاز به مدیریت خطا را در سیستم کاهش دهیم (در واقع، تعداد بخش هایی از سیستم، که باید به exception رسیدگی کنند را تا جای ممکن کم کنیم):روش اول: حذف خطابهترین روش برای کم کردن پیچیدگی ناشی از exception ها، این است که اصلا exception ای نداشته باشیم. در واقع باید به جای این که در شرایط غیر نرمال، یک exception به بیرون پرتاب کنیم، به این فکر کنیم که چطور می شود که این خطا اصلا وجود نداشته باشد.این روش را با یک مثال بررسی کنیم:در سیستم عامل ویندوز، اگر یک فایل در حال استفاده باشد، امکان حذف آن وجود ندارد. حال اگر کاربر بخواهد یک فایلِ در حال استفاده را حذف کند، باید زمان زیادی را صرف پیدا کردن آن process که در حال استفاده از فایل است کند و حتی گاهی وقت ها کاربر ترجیح می دهد کل سیستم را reboot کند تا فایل آزاد شود. بنابراین یک مدیریت خطای اشتباه، کاربر را به دردسر می اندازد.در سیستم عامل Unix، اگر کاربر بخواهد فایلی که در حال استفاده است را حذف کند، هیچ خطایی دریافت نمی کند و فایل هم در لحظه حذف نمی شود. در عوض، آن فایل برای حذف شدن علامتگذاری می شود و هیچ process دیگری نمی تواند آن را باز کند و تنها process هایی که از قبل آن را باز کرده اند می تواند write و read روی آن داشته باشند. به محض این که فایل بسته شد، سیستم عامل آن را حذف می کند.رویکرد سیستم عامل Unix دو نوع خطا را حذف می کند:1. آن process ای که می خواهد فایل را حذف کند، با exception مواجه نمی شود. عملیات حذف با موفقیت انجام می شود و فایل هم &#34;در نهایت&#34; حذف خواهد شد.2. آن process که دارد از فایل استفاده می کند، با این خطا مواجه نمی شود که &#34;فایل حذف شده است&#34;، بلکه می تواند به کار خود ادامه دهد.با این رویکرد، Unix توانسته، دو process را، از زحمتِ مدیریت exception در امان بدارد. شاید برای کسانی که با سیستم عامل windows بیشتر کار کرده اند، این روال عجیب باشد، ولی نکته ی اصلی در این مثال، این است که گاهی اگر به شرایط غیر نرمال، از نگاه دیگری بنگریم، روش های خوبی برای مدیریت آن پیدا می کنیم.روش دوم: پوشاندن خطادر این روش، شرایطی در کد که ممکن است به exception منتهی شوند، در ماژول های سطح پایین تر مدیریت می شوند، به گونه ای که کد سطح بالا، اصلا اطلاعی از این شرایط و exception های آن ندارد. این روش معمولا برای سیستم های توزیع شده مناسب است. برای مثال، پروتکل TCP، به جای این که زمانی که یک packet گم می شود، یک exception به سطح بالاتر پرتاب کند، خطا را می پوشاند و packet های گم شده را مجددا ارسال می کند. در واقع در این مثال، شرایطِ غیر نرمالی که در سطح پایین تر (یعنی شبکه) اتفاق افتاده، برای سطح بالاتر (مثلا نرم افزار) مزاحمتی ایجاد نمی کند.روش سوم: تجمیع خطاهاایده ی اصلی این روش این است که، تعداد زیادی exception را، فقط در یک قطعه از کد مدیریت کنیم. به جای این که تعداد زیادی رسیدگی کننده به exception داشته باشیم، تنها یک قطعه کد، آن ها را مدیریت کند.برای مثال، فرض کنید تعدادی کلاس handler داریم که توسط یک کد سطح بالاتر استفاده می شوند. به جای این که exception ها را در کلاس های handler مدیریت کنیم، آن را به کد سطح بالاتر می سپاریم. تفاوت این روش با روش پوشاندن خطا در این است که، در روش پوشاندن خطا، یک ماژول داریم که توسط چندین ماژول دیگر استفاده می شود، پس به جای انتشار exception به تمام آن ها، در خود آن ماژول، exception را می پوشانیم، ولی در روش تجمیع، چندین ماژول داریم که توسط یک ماژول استفاده می شود. به طور کلی دقت کنید هدف این است که تعداد جاهایی که exception را مدیریت می کنند به حداقل برسانیم.روش چهارم: توقف اجرادر بیشتر نرم افزارها، exception هایی وجود دارد که ارزش مدیریت کردن ندارد. یا به این دلیل که خیلی کم اتفاق می افتند و یا مدیریت آن ها خیلی سخت است. بهترین روش برای این نوع exception ها این است که اجرای نرم افزار متوقف شود و exception به بیرون پرتاب شود.برای مثال، خطای out of memory را در نظر بگیرید. زمانی که حافظه فضا ندارد، نرم افزار چه کاری می تواند انجام دهد؟ شاید بتوان با راه های عجیب، به نحوی این exception را مدیریت کرد، ولی احتمالا آن قدر که پیچیدگی به سیستم اضافه می کند، فایده ندارد. بنابراین بهترین کار، توقف اجرای برنامه و ایجاد exception است.خلاصهمهم ترین مساله در مدیریت شرایط غیرنرمال، این است که تعداد جاهایی که exception باید مدیریت شود را کاهش دهیم. در بسیاری مواقع، منطقِ یک عملیات می تواند طوری تغییر کند، که رفتار نرمال، همه ی شرایط را پوشش دهد و نیازی به کشف و مدیریت شرایط استثنا نباشد.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Fri, 24 Sep 2021 21:56:45 +0330</pubDate></item><item><title>با هم یا جدا از هم؟</title><link>https://virgool.ir/@sara.rezaei/با-هم-یا-جدا-از-هم-ecxdoorluedp</link><description>یکی از سوالات بنیادی در طراحی نرم افزار این است:اگر بخواهیم دو عملکرد را در نرم افزار پیاده سازی کنیم، بهتر است آن ها را یک جا قرار دهیم، یا در دو جای متفاوت؟این سوال در همه ی سطوح سیستم ممکن است مطرح شود، در متدها، کلاس ها و ...برای مثال، اگر یک کلاس داریم که مسئولیت مدیریت فایل را بر عهده دارد، آیا buffering را در همان کلاس بگذاریم یا در یک کلاس مجزا؟آیا همه ی عملکردِ مربوط به پردازش کردن یک HTTP request، در یک متد پیاده سازی شود؟ یا آن را بین چند متد مختلف پخش کنیم؟هدف از این تصمیم گیری، کاهش پیچیدگی سیستم و افزایش modularity است. شاید به نظر برسد بهترین کار این است که تا حد ممکن سیستم را به بخش های کوچکتر بشکنیم ولی این کار می تواند بر پیچیدگی سیستم اضافه کند. چرا؟تعداد ماژول های بیشتر، بر پیچیدگی سیستم اضافه می کند. هر چقدر تعداد ماژول ها بیشتر باشد، نگهداری از آن ها سخت تر، و پیدا کردن یک ماژول مشخص بین این همه، دشوار است. همچنین به تعداد ماژول ها، واسط به سیستم اضافه می شود و هر واسط، بر پیچیدگی سیستم می افزاید (توضیح بیشتر درباره ی واسط، در مطلب برنامه نویسی ماژولار)شکستن سیستم به بخش های بیشتر، باعث می شود مدیریت آن بخش ها سخت تر باشد و نیاز به کد بیشتری داشته باشیم. برای مثال بخشی از کد که قبلا فقط با یک object کار می کرد، الان باید با چندین object کار کند و آن ها را مدیریت کند.شکستن باعث جدایی می شود. بخش های مختلف کد، قبل از شکستن، نزدیک هم بودند، ولی حالا از هم دور هستند. مثلا متدهایی که در یک کلاس بودند حالا در کلاس های متفاوت و احتمالا فایل های مجزایی  قرار دارند. این باعث می شود برنامه نویس ها، همه ی component هایی که نیاز دارند را یکجا نبینند و شاید حتی از وجود بعضی از آن ها آگاهی نداشته باشند. اگر component ها واقعا مستقل هستند، شکستن خوب است. باعث می شود برنامه نویس روی یک component، در یک زمان تمرکز کند و توسط تعداد زیادی component گیج نشود. ولی اگر بین component ها وابستگی وجود دارد، جداسازی خوب نیست. در این صورت، جداسازی باعث می شود برنامه نویس ناچار شود بین فایل ها و بخش های مختلف مدام جا به جا شود و حتی بدتر از آن ممکن است که اصلا از بعضی وابستگی ها اطلاع نداشته باشد.جداسازی می تواند باعث duplication شود. ممکن است مجبور شویم کدی را که در یک component وجود دارد، در دیگری هم بنویسیم.به عنوان یک قانون کلی، اگر دو قطعه کد به هم مرتبط هستند، بهتر است در کنار هم باشند، در غیر این صورت آن ها را جدا کنید.از کجا بفهمیم دو قطعه کد به هم مرتبط هستند؟اگر اطلاعاتی را به اشتراک می گذارند و هر دو وابسته به یک دانش خاص هستند.مثلا فرض کنید بخواهیم یک Http Request را پردازش و بخش های مختلف آن را از هم جدا کنیم. اگر این بیزنس را در چند متد پخش کنیم، احتمالا همه ی آن ها باید فرمت Http Request را بشناسند و بتوانند بخش های مختلف آن را از هم جدا کنند. بنابراین دانشی که اینجا مشترک است، نحوه ی کار با Http Request است و بهتر است آن را فقط در یک ماژول قرار دهیم.اگر با همدیگر استفاده می شوند.منظور این است که، هر کسی که می خواهد از یکی از این قطعات کد استفاده کند، به احتمال زیاد دیگری را هم لازم دارد. البته این نوع از ارتباط باید حتما دو طرفه باشد تا شامل این قانون شود. برای مثال، ممکن است یک کلاس که مسئول cache کردن دیتا است، از Hash table استفاده کند، ولی Hash table ها در خیلی جاهای دیگر هم استفاده می شوند، پس این ارتباط دو طرفه نیست و باید این دو ماژول جدا از هم باشند.اگر از نظر مفهوم، همپوشانی دارند.یعنی می توانیم یک دسته بندی سطح بالاتر در نظر بگیریم، که این دو قطعه کد هر دو به آن تعلق دارند. مثلا اگر دو قطعه کد داریم، یکی برای جستجوی یک string و دیگری برای تبدیل حروف کوچک و بزرگ به هم، هر دوی آن ها در دسته بندی ویرایش string قرار می گیرند.اگر فهمیدن یک قطعه از کد، بدون نگاه کردن به دیگری، سخت است.متدی را در نظر بگیرید که در پیاده سازی خودش از متد دیگری استفاده می کند. اگر هنگام خواندن کد این متد، مجبور شویم حتما کد متد داخلی را هم نگاه کنیم، یعنی این دو به درستی جداسازی نشده اند و با هم ارتباط دارند.بعضی از برنامه نویس ها اعتقاد دارند، متدی که بیشتر از 20 خط کد دارد، باید حتما به چند متد دیگر تقسیم شود!این جمله همیشه درست نیست. طولانی بودن یک متد، اصلا دلیل خوبی برای شکستن آن نیست. اگر بدون دلیل کافی، یک متد را بشکنیم، تمامی آن متدها واسط خود را به سیستم تحمیل می کنند و این یعنی افزودن پیچیدگی. تنها زمانی مجاز به شکستن یک متد هستیم، که پیچیدگی سیستم را کمتر کند.متدهای طولانی همیشه بد نیستند. فرض کنید یک متد داریم که 4 قطعه کد دارد، هر قطعه کد هم 10 خط است. کسی که این متد را می خواند، می تواند از قطعه ی بالایی شروع کند و هر قطعه را بفهمد و به آخر متد برسد. اگر این قطعات به هم وابستگی زیاد داشته باشند و هر کدام از آن ها را تبدیل به یک متد دیگر کنیم، برای فهمیدن متد parent، باید مدام بین آن متد و متدهای child رفت و آمد داشته باشیم. در طراحی متدها، مساله ی مهم این است که متد، فقط و فقط یک کار را، به صورت کامل انجام دهد. همچنین باید یک انتزاع تمیز و ساده فراهم کند و شامل یک واسط ساده باشد که بخش های دیگر کد به راحتی از آن استفاده کند. اگر متد این ویژگی ها را داشته باشد، تعداد خطوط آن، اصلا مهم نیست.شکستن متد به دو روش قابل انجام است:1. در کد آن متد، یک بخش از کد را به عنوان یک sub-task تشخصی بدهیم و آن را به یک متد child انتقال دهیم که توسط متد parent استفاده می شود.اگر از این  روش استفاده می کنیم، باید این دو شرط رعایت شود:کسی که متد child را می خواند نیازی نداشته باشد هیچ چیزی درباره ی متد parent بداند.کسی که متد parent را می خواند نیاز نداشته باشد پیاده سازی متد child را نگاه کند تا کد متد parent را بفهمد.2. یک متد را به دو متد کاملا مجزا بشکنیم که هیچ کدام از دیگری استفاده نمی کنند.اگر از این  روش استفاده می کنیم، باید این شرط رعایت شود:استفاده کننده از این متدها، مجبور نباشد که همیشه هر دوی آن ها را با هم صدا بزند.خلاصهتصمیم گیری در مورد جدا کردن ماژول ها، یا یکی کردن آن ها، باید بر اساس پیچیدگی سیستم باشد. ساختاری را انتخاب کنید که نتیجه ی آن پنهان سازیِ بهترِ اطلاعات، کمترین وابستگی و عمیق ترین ماژول ها باشد.منابعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Fri, 03 Sep 2021 11:47:42 +0430</pubDate></item><item><title>انتخاب سختِ درست</title><link>https://virgool.ir/@sara.rezaei/انتخاب-سختِ-درست-a1ytiavx18vg</link><description>فرض کنید در حال توسعه ی یک ماژول جدید هستید و به یک پیچیدگی غیر قابل اجتناب بر می خورید.کدام را انتخاب می کنید؟1. مدیریت این پیچیدگی را بر عهده ی استفاده کنندگان از ماژول می گذارید.2. این پیچیدگی را درون ماژول، حل می کنید.اگر این پیچیدگی، مربوط به business داخلی ماژول است، بهتر است راه حل دوم را انتخاب کنید. به عنوان یک برنامه نویس، شما باید به این فکر کنید، که برای استفاده کنندگان از ماژولی که در حال نوشتن آن هستید، چگونه می توانید زندگی را ساده تر کنید، حتی اگر این تصمیم باعث شود که کار خودتان بیشتر گردد. جمله ی اخیر را به یک روش دیگر هم می توان بیان کرد:برای یک ماژول، داشتن یک واسط ساده، مهم تر از یک پیاده سازی ساده است.یک برنامه نویس، همیشه وسوسه دارد که کار ساده تر را انجام دهد و کار سخت تر را به عهده ی دیگران بگذارد. مثلا اگر در کد شرایطی پیش آید که نداند با آن چه کار کند، ساده ترین راه، تولید یک exception است، که مدیریت آن بر عهده ی استفاده کننده از آن ماژول است. مثال دیگر زمانی است که در مورد یک موضوع، مثلا بخشی از یک فرمول، نمی توانیم تصمیم بگیریم، ساده ترین راه، قرار دادن آن بخش از فرمول در تنظیمات (configuration)، و سپردن بقیه ی کار (یعنی فراهم آوردن مقدار مناسب برای آن configuration) به مدیر سیستم است.رویکردهای این چنینی، در کوتاه مدت، کمک می کنند که آسان تر زندگی کنیم، ولی در نهایت باعث افزایش پیچیدگی می شوند، چرا که با این روش، انسان های بیشتری باید با یک مشکل دست و پنجه نرم کنند، نه فقط یک نفر. مثلا اگر یک متد، یک exception بدهد، هر صدا زننده ی آن، باید بداند با آن چه کار کند. یا در مورد مثالِ configuration، به ازای هر استقرار جدید نرم افزار، مدیر سیستم باید بداند چه مقادیری برای چه تنظیماتی فراهم کند. این ها همه بر پیچیدگی سیستم می افزایند.بیایید دو مثال را با هم بررسی کنیم:مثال اول:فرض کنید یک کلاس برای مدیریت متن داریم. این کلاس قرار است متدهایی برای خواندن یک فایل متنی، ویرایش آن و ذخیره ی آن روی دیسک داشته باشد. دو رویکرد می توان برای نوشتن این کلاس در نظر گرفت: رویکرد اول این است که متدهای کلاس، بر اساس خط (line) کار کنند و استفاده کنندگان از این متدها، وظیفه ی مدیریت خطوط و دانشِ چگونگی استفاده از آن ها را داشته باشند. این رویکرد، باعث می شود پیاده سازی این ماژول ساده تر شود، ولی برای لایه های دیگر، پیچیدگی ایجاد می کند. برای مثال اگر یک لایه ی دیگر که از این ماژول استفاده می کند، بخواهد بخشی از متن را حذف کند، باید بداند آن بخش از متن، در کدام خطوط قرار گرفته، محاسباتی انجام دهد و سپس دستور حذف آن خطوط را اجرا کند.رویکرد دوم کاراکتر محور است، یعنی لایه های دیگر هیچ نیازی به دانستن اطلاعات در مورد خطوط متن ندارند، آن ها فقط می دانند که کدام کاراکترها (بر اساس position) باید حذف شوند. در این صورت، پیاده سازی کلاس Text سخت تر است، چون خودش باید خطوط را مدیریت کند، در عوض واسط ساده تر است و پیچیدگی به بیرون از ماژول منتقل نشده است.مثال دوم:یک متد را در نظر بگیرید که در بخشی از جریان کاری خودش، نیاز دارد عملیاتی را retry کند. این retry کردن، وابسته به یک تعداد است، مثلا 4 بار retry و سپس در صورت انجام نشدن کار، تولید خطا. دو رویکرد می توان برای پیاده سازی این متد در نظر گرفت: رویکرد اول این است که، این متد، تعداد retry را از configuration بخواند و یا در پارامتر های ورودی خودش دریافت کند. در هر دو حالت، تعداد retry، در واسط متد قرار گرفته، و تصمیم گیری درباره ی مقدار آن، بر عهده ی استفاده کنندگان است. در شرایطی، مثلا زمانی که در domain های زیادی، قرار است از این متد استفاده شود، ممکن است این رویکرد مناسب باشد، چرا که برنامه نویسی که در آن domain بخصوص کد می نویسد، احتمالا بهتر از هر شخص دیگری می تواند در مورد نیازهای آن domain فکر کند و تصمیم درستی در مورد تعداد retry این متد بگیرد.مثلا ممکن است جایی که performance مهم است تعداد کمتری در نظر بگیرد و جایی که انجام شدن کار مهم است، فارغ از این که چقدر آن عملیات طول می کشد، تعداد retry بیشتری در نظر بگیرد. رویکرد دوم به این صورت است که، در پیاده سازی این متد، مکانیزمی در نظر بگیریم، که به صورت خودکار، تعداد retry محاسبه شود. در واقع، هر پارامتری که توقع داریم، استفاده کننده از این متد در نظر بگیرد و با استفاده از آن تعداد retry را محاسبه کند، در خودِ متد، برای محاسبه ی retry، در نظر گرفته شود. مثلا یک فرمول شامل تعداد دفعاتِ اجرای موفقیت آمیز، که در یک ضریب ثابت ضرب می شود و تعداد retry را به دست می آورد. خوبی این رویکرد این است که، در صورت تغییر پارامترهای محاسبه، یک بار این پارامترها را در همین متد تغییر می دهیم، و نیازی نیست که تمام استفاده کنندگان از این متد تغییر کنند. همچنین، این روش، پیچیدگی را به درون ماژول منتقل می کند و از انتشار آن در کل سیستم جلوگیری می کند.قبل از انتخاب یکی از این دو رویکرد باید به یک سوال پاسخ دهیم:آیا ماژول های دیگر (هر کسی که قرار است از این متد استفاده کند)، در محاسبه و انتخابِ یک مقدار مناسب برای تعداد retry، بهتر از خود این متد عمل خواهند کرد؟اگر پاسخ به این سوال بلی است، یعنی ما نمی توانیم یک روش بهینه و مناسب برای محاسبه تعداد retry، در این ماژول پیاده سازی کنیم، پس بهتر است مسئولیت محاسبه ی آن را به استفاده کننده واگذار کنیم. در غیر این صورت، باید خودِ ماژول، مقادیری که نیاز دارد را محاسبه کند و اطلاعات مربوط به retry کردن را (که در این مثال، تعداد است) از دید دنیای بیرون پنهان سازد (پنهان سازی اطلاعات).یک روش میانه هم این است که، در پیاده سازی متد، یک مقدار پیش فرض محاسبه شود، و در موقعیت های خاص، به استفاده کننده اجازه دهیم که این مقدار پیش فرض را با مقدار مورد نظر خودش جایگزین کند، که در این صورت باز هم اطلاعات ماژول به دنیای بیرون نشت کرده است.ایده آل این است که، هر ماژول، مسائل خودش را به صورت کامل حل کند و آن را به عهده ی استفاده کنندگان نگذارد.خلاصهتصمیم گیری در این مورد که کدام پیچیدگی ها در پیاده سازی ماژول مدیریت شوند و کدام ها را باید به بیرون از ماژول منتقل کرد، نیازمند بصیرت است. مانند یک بندباز، باید بتوانیم تعادل را در این زمینه حفظ کنیم. به طول کلی، می توان معیارهای زیر را، برای زمان هایی که پیچیدگی باید درون ماژول مدیریت شود در نظر گرفت:زمانی که پیچیدگی مورد نظر، به عملکرد بیزنسی آن ماژول مرتبط است.زمانی که مدیریت پیچیدگی درون ماژول، باعث می شود که بخش های دیگر نرم افزار بسیار ساده شوند.زمانی که با مدیریت پیچیدگی درون ماژول، واسط ماژول بسیار ساده می شود.زمانی که در حال توسعه ی یک ماژول هستید، به دنبال فرصت هایی باشید، که با تحمل سختی بیشتر، بتوانید زحمتِ استفاده کنندگان از آن ماژول را کم کنید.منابعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Fri, 16 Jul 2021 21:48:01 +0430</pubDate></item><item><title>لایه ی متفاوت، انتزاع متفاوت</title><link>https://virgool.ir/@sara.rezaei/لایه-ی-متفاوت-انتزاع-متفاوت-bpfiiepbheat</link><description>سیستم های نرم افزاری، معمولا از لایه هایی تشکیل می شوند. این لایه ها در سطوح گوناگونی قرار می گیرند، و لایه های بالاتر، از امکاناتی که لایه های پایین تر فراهم می کنند، استفاده می کنند. اگر سیستم به درستی طراحی شده باشد، هر لایه، انتزاع متفاوتی از لایه های بالایی و پایینی اش فراهم می کند.انتزاع، یعنی حذف جزئیاتِ اشیاء یا سیستم ها و تمرکز روی اجزایی که برای یک هدف خاص، مهم هستند.بنابراین، اگر در یک سیستم نرم افزاری، یک عملیات بخصوص را، از ابتدا تا انتها دنبال کنیم و متدهایی که در هر لایه صدا زده می شود را بررسی کنیم، در فراخوانی هر متد، انتزاع باید تغییر کند.مثال:در یک سیستمِ فایل، سه لایه می توانیم داشته باشیم:1. بالاترین لایه، یک انتزاع از فایل فراهم می آورد. انتزاعی شامل یک آرایه از بایت ها، که قابلیت نوشتن و خواندن دارد. 2. لایه ی بعدی (پایین تر)، مسئول مدیریت یک حافظه ی موقت برای نگهداری بایت های فایل است. لایه های دیگر می توانند مطمئن باشند، بلاک های حافظه که بیشتر استفاده شده، در این حافظه ی موقت نگهداری می شود تا دسترسی دوباره به آن ها سریع باشد.3. پایین ترین لایه، مسئولیت مدیریت جا به جایی بلاک های حافظه، بین memory و disk را بر عهده دارد.نشانه هایی وجود دارد که از طریق آن می توانیم بفهمیم انتزاع لایه ها شبیه به هم است و سیستم درست طراحی نشده است:نشانه ی اول: وجود متدهایی، که هیچ عملکردی ندارند و فقط یک متد دیگر را صدا می زنند. مثلا کلاس زیر را در نظر بگیرید:public class TextDocument {&#xA;         private TextArea textArea;&#xA;         public Character getLastTypedCharacter() {&#xA;                 return textArea.getLastTypedCharacter();&#xA;         }&#xA;         public int getCursorOffset() {&#xA;                 return textArea.getCursorOffset();&#xA;         }   &#xA;}این کلاس دو متد دارد، که هر دو متد، هیچ کاری به جز صدا زدن متدهای شی textArea انجام نمی دهند. در نتیجه، می توان گفت که انتزاع این کلاس، هیچ تفاوتی با انتزاع کلاس TextArea ندارد و مسئولیت ها به درستی تقسیم نشده است.این متدها، واسط کلاس را پیچیده می کنند و به پیچیدگی کل سیستم می افزایند، ولی برای عملکرد کلی سیستم هیچ فایده ای ندارند. همچنین این متدها، وابستگی بین کلاس ها را بیشتر می کنند و به coupling سیستم می افزایند.در مثال بالا، متد getLastTypedCharacter را در کلاس TextDocument در نظر بگیرید. این متد، قرار است کاری را انجام دهد، که پیاده سازیِ عملکرد آن، به صورت کامل در کلاس TextArea است. یعنی پیاده سازی در یک کلاس، و واسط در یک کلاس دیگر قرار دارد. این یک نشانه ی خیلی بد از طراحی نامناسب است. به عنوان یک قانون کلی:برای هر عملکرد، در سیستم نرم افزاری، واسط باید در همان کلاسی باشد، که پیاده سازی آنجاست.هر زمان متدهای شبیه به این مثال را در کد دیدید، از خودتان بپرسید: هر کدام از این کلاس ها، دقیقا مسئول چه عملکردی است؟  به احتمال خیلی زیاد، متوجه خواهید شد که مسئولیت ها به درستی تقسیم نشده و بین مسئولیت های دو کلاس، همپوشانی وجود دارد.راه حل این است، که کلاس ها را طوری تغییر دهیم، که هر کلاس، تعدادی مسئولیتِ مرتبط به هم داشته باشد. این کار را از چند طریق می توان انجام داد:به تصویر بالا دقت کنید، در حالت (a) کلاس C1 تعدادی از متدهای کلاس C2 را کال کرده و نتیجه را از طریق واسط خودش به بیرون منتقل کرده است. می توانیم این مشکل را به سه طریق حل کنیم:روش (b): هرکس که متدهای C1 را صدا می زند، به جای آن، متدهای مشابه در C2 را صدا بزند.روش (c): متدها (در واقع مسئولیت ها) بین دو کلاس تقسیم شده استروش (d): دو کلاس با هم ادغام شده اندنشانه ی دوم: نمایان شدن جزئیات پیاده سازی، در واسطهر ماژول، باید در پیاده سازی اش، جزئیاتی داشته باشد، که توسط واسط پنهان شده است. اگر جزئیات پیاده سازی، از طریق واسط به دنیای بیرون نشت کند، احتمالا انتزاعِ فراهم شده توسط ماژول، انتزاع درستی نیست.یک کلاس را در نظر بگیرید، که قرار است عملکردهایی برای مدیریت متن را فراهم کند (text editor). فرض کنید این کلاس را طوری طراحی کنیم، که بر اساس خطوط متن کار کند، مثلا متدهایی مانند GetLine و PutLine داشته باشد. در این صورت کدهای لایه های دیگر، اگر بخواهند که متنی را در میان یک خط کد حذف کنند، و یا متنی را در میان خط های یک متن دیگر اضافه کنند، باید بدانند که چگونه خطوط را از هم جدا کرده و استفاده از آن ها را مدیریت کنند. در واقع، کار با خطوط، که باید درون کلاس text پنهان می شد، از طریق واسط های نامناسب مانند GetLine، به بیرون نشت داده شده و جزئیات درونی کلاس را آشکار کرده است.در این مثال، اگر بخواهیم طراحی را اصلاح کنیم، کلاس Text باید در واسط خودش، متدهایی مانند Insert و Delete را ارائه دهد، که یک متن را در یک موقعیت مناسب از متن وارد می کند یا حذف می کند. قطعا این متن، دارای خطوطی است، ولی چون کلاس به درستی طراحی شده، پیچیدگی های مدیریت خطوط متن را در خودش پنهان می کند و انتزاع درستی را در لایه ی خودش برای لایه های دیگر فراهم می کند. نشانه ی سوم: متغیرهای آوارهگاهی اوقات متدهایی داریم، که یک متغیر را به عنوان پارامتر ورودی می گیرد و آن را به متد بعدی پاس می دهد، بدون این که در عملکرد خودش از آن استفاده کند. این متغیرها، معمولا بین زنجیره ای از توابع دست به دست می شوند، و احتمالا فقط متد آخر قرار است از آن ها استفاده کند.تصویر زیر را در نظر بگیرید:همانطور که در (a) می بینیم، یک متغیر به نام cert داریم، که فقط در متد m3 مورد نیاز است. ولی چون این متغیر در main تعریف شده، بین چهار متد main و m1 و m2 و m3 جا به جا شده، بدون این که سه متد دیگر به آن نیازی داشته باشند. بنابراین، واسط متدهای m1 و m2، بیهوده پیچیده شده است و شامل پارامتری است که هیچ نیازی به آن نیست. این مساله به پیچیدگی سیستم می افزاید و تغییرات آینده روی این بیزنس را سخت می کند.برای حل این مشکل، راه حل (b)، یک object مشترک بین main و m3 در نظر می گیرد. هر اطلاعاتی که m3 به آن ها نیاز دارد را، main در این شی مشترک قرار می دهد تا بعدا m3 از آن استفاده کند. ولی یه مشکل وجود دارد! همین object خودش تبدیل به یک متغیر خواهد شد، که باید بین متدها دست به دست شود، در غیر این صورت چگونه به دست m3 برسد؟راه حل (c)، یک global variable تعریف می کند و هر اطلاعاتی که m3 نیاز دارد را در آن قرار می دهد. ولی این راه هم مشکلاتی دارد، مثلا اگر دو instance از یک کلاس بخواهیم در یک process داشته باشیم، دسترسی به متغیرهای global چالش هایی خواهد داشت.راه حل (d)، استفاده از یک متغیر context است، که state کل application را در خودش ذخیره می کند. معمولا اطلاعاتی مثل تنظیمات (configurations) و یا اطلاعاتی که باید بین بخش های مختلف سیستم share شود را در این متغیر ها نگهداری می کنیم. مشکل این راه حل این است که context ممکن است تبدیل به یک شی خیلی سنگین شود و خودش به پیچیدگی و وابستگی در سیستم اضافه کند.هیچ کدام از این راه حل ها، ایده آل نیستند. اگر راه حل بهتری می شناسید، در بخش نظرات بیان کنید.خلاصههر ماژول که به سیستم اضافه می شود (مانند متد، کلاس و ...) به پیچیدگی سیستم می افزاید. برای این که آن قطعه از کد، ارزشِ این پیچیدگی افزوده را داشته باشد، باید میزانی از پیچیدگی را که در صورت نبود این ماژول، در سیستم قابل مشاهده بود، در خودش پنهان کند. اگر این کار را انجام ندهد، بهتر است که وجود نداشته باشد و سیستم بدون آن ماژول کار کند. برای مثال، یک کلاس، باید عملکردی را در خودش کپسوله کند، و وجود این کلاس باعث شود که استفاده کنندگان، نیازی به دانستن جزئیات آن عملکرد، نداشته باشند. در این صورت این ارزشِ افزوده، هزینه ی وجود آن کلاس را توجیه می کند. مواردی که در این نوشته بررسی کردیم، نشانه هایی هستند، که مشخص می کنند ماژولی که طراحی کرده ایم، به اندازه ی کافی، جزئیات را از دنیای بیرون پنهان نکرده است. بنابراین، شاید هزینه ای که وجودش بر سیستم تحمیل می کند، از هزینه ی نبودنش بیشتر باشد، پس نیاز به اصلاح، یا در صورت نیاز، حذف آن ماژول، قابل بررسی است.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Mon, 12 Jul 2021 00:02:46 +0430</pubDate></item><item><title>ماژول های عام یا خاص</title><link>https://virgool.ir/@sara.rezaei/ماژول-های-عام-یا-خاص-nrc8htu0t4id</link><description>به عنوان برنامه نویس، یکی از تصمیماتی که در نوشتن هر ماژول جدید با آن روبرو هستیم، این است که ماژول را به صورت عام منظوره (general-purpose) طراحی کنیم یا خاص منظوره (special-purpose). بعضی ها معتقد هستند، ماژول ها باید عام منظوره طراحی شوند، طوری که مکانیزمی را فراهم کنند که نه تنها پاسخگوی نیازهای امروز باشد، بلکه نیازهای بیشمار آینده را هم پیش بینی کند. این افراد معتقدند که با این روش، در وقت صرفه جویی خواهد شد، چرا که در آینده می توانیم به راحتی از ماژول هایی که قبلا نوشته شده اند، برای پاسخ به نیازهای جدید نرم افزار، استفاده کنیم.از سوی دیگر، می دانیم که پیش بینی نیازهای آینده ی یک سیستم نرم افزاری، بسیار دشوار است، بنابراین یک راه حلِ عام منظوره، ممکن است شامل مواردی باشد، که هرگز استفاده نخواهند شد. علاوه بر این، ممکن است این راه حل ها روش خوبی برای حل مساله ای که امروز داریم نباشند و احتمال دارد که، کیفیت کار امروز را فدای پیش بینی های آینده کنیم. به همین دلیل، بعضی دیگر معتقدند، که بهتر است روی نیازهای امروز تمرکز کنیم، طراحی را بر مبنای آنچه امروز می دانیم انجام دهیم و &#34;چو فردا رسد کار فردا کنیم&#34;. در آینده هر زمان که نیازهای دیگری کشف شد، می توان کد را refactor کرد و آن نیاز را پاسخ داد.ولی شاید خیلی نباید به این مساله سیاه و سفید نگاه کرد. در این نوشته می خواهیم یک روشِ میانه برای طراحی ماژول ها را بررسی کنیم، به این صورت که عملکرد (functionality) ماژول، فقط منعکس کننده ی نیاز فعلی باشد، ولی واسط آن، به اندازه ای عام منظوره طراحی شود که موارد استفاده ی متعددی را پاسخگو باشد. در واقع واسط باید برای نیازهای فعلی، به راحتی قابل استفاده باشد، بدون این که به آن ها گره خورده باشد. یک نکته ی مهم این است که میزان عام منظوره بودن را به درستی تشخیص دهیم، آن قدر واسط را عام منظوره طراحی نکنیم که حتی به درد نیازهای فعلی هم نخورد.مهم ترین فایده ی رویکرد عام منظوره این است که نتیجه ی آن، واسط های ساده تر و ماژول های عمیق تر خواهد بود. همچنین در آینده می توان از آن ماژول برای اهداف دیگر استفاده کرد و در زمان صرفه جویی کرد، ولی این نباید هدف ما از طراحی ماژول های عام منظوره باشد. یعنی حتی اگر نخواهیم در آینده از یک ماژول استفاده کنیم، طراحی عام منظوره ی آن، به خاطر کمک به سادگی، بهتر است.شاید بررسی این مساله با یک مثال، به فهم بهتر آن کمک کند. فرض کنید می خواهیم یک نرم افزار ویرایشگر متن (مانند Notepad یا Word) طراحی کنیم. یک راه حل این است که، یک UI برای آن در نظر بگیریم و یک کلاس برای مدیریت متن. فرض کنیم نام کلاس را Text می گذاریم. این کلاس قرار است تمام متدهای مورد نیاز برای عملکرد نرم افزار را فراهم کند.روش اولقطعا هر کاربر که با نرم افزار ویرایشگر متن کار می کند، نیاز دارد که قسمتی از متن را پاک کند. فرض کنیم UI نرم افزار فرضی ما، دو دکمه برای پاک کردن متن دارد، یکی Delete و یکی Backspace (مانند صفحه کلید). عملکرد Delete به این صورت است که یک کاراکتر به سمت راست را حذف می کند و Backspace یک کاراکتر به سمت چپ.در روش اول، کلاس Text شامل دو متد زیر است:void Backspace(Cursor cursor);&#xA;void Delete(Cursor cursor);متد Backspace قرار است یک کاراکتر به سمت چپ را پاک کند و متد Delete، یک کاراکتر به سمت راست. در این متدها، ورودی، از نوع Cursor است، یعنی مکانی که نشانگر در آن قرار دارد (که از آن جا قرار است یک کاراکتر شمارش شود).احتمالا زمان هایی هم هست که نیاز داریم یک بخش از متن را حذف کنیم نه فقط یک کاراکتر، پس متد زیر را هم به واسط کلاس Text اضافه می کنیم:void DeleteSelection(Selection selection);کاری که تا اینجا کردیم این است که، به ازای تمام نیازهای UI، یعنی تمام ویژگی هایی که کاربر قرار است ببیند، متدهایی در کلاس Text اضافه کرده ایم. اگر بخواهیم به همین صورت پیش برویم، کلاس Text، تبدیل به یک کلاس کم عمق خواهد شد، که فقط برای آن UI به خصوص قابل استفاده خواهد بود (یعنی خاص منظوره است). مشکل دیگر این کلاس این است که، برنامه نویسی که روی کلاس Text کار می کند، باید بار شناختی زیادی را تحمل کند، تا با متدهایی آشنا شود، که بنا بر نیازهای UI نوشته شده اند و این برنامه نویس هیچ دانشی از آنها ندارد. همچنین برنامه نویسی که روی UI کار می کند، باید تعداد زیادی متد را از کلاس Text بشناسند و بداند هر کدام را کجا استفاده کند.این رویکرد، باعث نشت اطلاعات، بین UI و کلاس Text می شود. مفاهیمی که کاملا مرتبط با UI هستند، مثل Delete و Backspace و تفاوت این دو، در کلاس Text منعکس شده اند. هر تغییری در UI، منتج به تغییراتی روی کلاس Text خواهد شد و توسعه ی یکی، بدون تغییر دیگری امکان پذیر نخواهد بود.یک روش بهترروش دوم این است که کلاس Text را عام منظوره بنویسیم. واسط آن، باید شامل عملکردهایِ ابتداییِ مرتبط با یک متن باشد و اصلا دغدغه ی این که UI چه عملیاتی قرار است انجام دهد نداشته باشد. برای مثال فرض کنید فقط دو متد زیر را در کلاس Text داشته باشیم:Position changePosition(Position position, int numChars);&#xA;void Delete(Position start, Position end);متد اول، یک position جدید، که به اندازه ی تعداد مشخصی کاراکتر با position ورودی فرق دارد را به عنوان خروجی بر می گرداند. منفی یا مثبت بودن مقدار numChars مشخص کننده ی جهت حرکت position است. متد دوم، کاراکترهایی که بعد از پارامتر start و قبل از پارامتر end هستند را حذف می کند. همچنین در این متدها، از نوع Position به جای Cursor استفاده شده، که به یک UI خاص اشاره نمی کند (چرا که ممکن است یک UI وجود داشته باشد که cursor در آن معنی ندارد).با این دو متد که تعریف کردیم، هر UI می تواند به روش زیر، Delete را پیاده سازی کند:text.delete(cursor, text.changePosition(cursor, 1));و Backspace را به روش زیر:text.delete(text.changePosition(cursor, -1), cursor);درست است که کدها طولانی تر به نظر می رسند، ولی با روش جدید، برنامه نویسی که روی UI کار می کند، با توجه به عملکردی که قرار است برای کاربر طراحی شود، می تواند تصمیم بگیرد که چگونه از ویژگی های کلاس Text استفاده کند. در روش قبلی، برنامه نویس UI، برای این که بداند متد Backspace دقیقا چه می کند، باید به کلاس Text مراجعه می کرد و اسناد یا کد آن را مطالعه می کرد تا رفتار آن را بفهمد. همچنین در روش جدید، کلاس Text تعداد کمی متد خواهد داشت که می توانند تعداد زیادی از نیازهای UI های مختلف را پاسخ بدهند.در این مثال، با استفاده از رویکرد عام منظوره، توانستیم بین کلاس Text و UI، جداسازی انجام دهیم، و این به پنهان سازی اطلاعات کمک می کند. کلاس Text نیازی ندارد جزئیات UI را بداند، مثلا این که تفاوت کلید Delete و BackSpace چیست. همچنین، می توانیم بدون این که کلاس Text را تغییر دهیم، به UI ویژگی های جدید اضافه کنیم، برای مثال، کاربر بتواند با یک دستور، 4 کاراکتر را به سمت راست حذف کند. برای پاسخ دادن به این نیاز UI، برنامه نویس UI بدون نیاز به دانش جدیدی از کلاس Text ، می تواند این بخش از UI را توسعه دهد.یکی از مهم ترین عناصر طراحی نرم افزار، این است که تشخیص دهیم، چه کسی، چه چیزی را باید در چه زمانی بداند. وقتی جزئیات مهم هستند، بهتر است تا جای ممکن آن ها را شفاف کنیم. مثلا در روش اول، پیاده سازیِ متد Backspace، یک اشتباه در طراحی است. در نگاه اول، به نظر می رسد این متد برای این به وجود آمده، که اطلاعاتی را پنهان کند، اطلاعات درباره ی این که چه کاراکترهایی قرار است حذف شوند، ولی در واقع UI برای استفاده از این متد، باید دقیقا همان اطلاعاتی که قرار است پنهان شود را بداند! برنامه نویس UI، باید کد مربوط به این متد را ببیند، تا بداند کجا باید از آن استفاده کند.چه سوال هایی بپرسیم؟برای پیدا کردن یک تعادل بین ماژول های عام منظوره یا خاص منظوره، سوال های زیر را از خودتان بپرسید: ساده ترین واسطی که نیازهای فعلی نرم افزار را پوشش می دهد کدام است؟اگر بتوانیم تعداد متدهای یک ماژول را، بدون کم کردن قابلیت های آن ماژول کم کنیم، احتمالا یک ماژول عام منظوره تر داریم. در مثال قبلی، رویکرد خاص منظوره، شامل سه متد برای حذف متن بود: Backspace و Delete و DeleteSelection. رویکرد عام منظوره تر، فقط یک متد برای حذف متن دارد که هر سه نیاز قبلی را پاسخ می دهد. دقت کنید که کم کردن تعداد متدها تا زمانی معنی دارد که واسط متد ساده بماند. اگر برای کاهش متدها مجبور شدیم تعداد زیادی argument به آن ها اضافه کنیم، احتمالا به جای ساده تر کردن کارها، در حال افزودن پیچیدگی هستیم.2. یک متد در چه تعداد موقعیت استفاده می شود؟اگر یک متد فقط برای استفاده ی مشخص طراحی می شود (مانند متد backspace)، احتمالا خیلی خاص منظوره است. پس شاید بتوان آن را به همراه چند متد خاص منظوره ی دیگه ادغام کرد و یک متد عام منظوره تر نوشت.3. آیا استفاده از این ماژول برای استفاده ی فعلی نرم افزار، ساده است؟اگر برای استفاده از یک کلاس، مجبور شویم مقدار زیادی کد بنویسیم، احتمالا واسط مناسبی طراحی نکرده ایم. در مثال خودمان، اگر فقط متد delete وجود داشت، استفاده کنندگان از این کلاس باید جهت حذف تعداد زیادی کاراکتر، loop های زیادی داشته باشند. بنابراین بهتر است این کلاس، متدهایی برای عملیات روی تعداد بیشتری از 1 کاراکتر داشته باشد. خلاصهواسط های عام منظوره، فوایدی نسبت به واسط های خاص منظوره دارند. آن ها ساده تر هستند، تعداد اجزای کمتری دارند و عمیق ترند. آن ها از نشت اطلاعات بین ماژول ها جلوگیری می کنند و به مدیریت پیچیدگی سیستم کمک خواهند کرد.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Fri, 18 Jun 2021 00:49:55 +0430</pubDate></item><item><title>پنهان سازی اطلاعات</title><link>https://virgool.ir/@sara.rezaei/پنهان-سازی-اطلاعات-pq1l4fza4yrc</link><description>در نوشته ی مربوط به برنامه نویسی ماژولار، بحث &#34;عمق ماژول&#34; مطرح شد و بهترین ماژول ها، ماژول های عمیق معرفی شدند. در این نوشته می خواهیم در مورد تکنیکی برای ساخت ماژول های عمیق بحث کنیم.پنهان سازی اطلاعات (Information hiding)این تکنیک اولین بار توسط David Parnas مطرح شد. ایده ی اصلی این است که، هر ماژول، با توجه به تصمیماتِ طراحی، باید میزان مشخصی از دانش را کپسوله کند. این دانش، در پیاده سازی ماژول تعبیه می شود و نباید در واسط آن مشاهده شود، بنابراین از دید سایر ماژول ها پنهان است.شما به عنوان یک سیستم، که بین خودتان و دنیای بیرون مرزهایی دارید، از این تکنیک بهره می برید. نام و سایر اطلاعات شخصی شما، در مغز شما مخفی است و امکان دسترسی مستقیم به آن وجود ندارد. برای دستیابی به آن اطلاعات، افراد باید از شما سوال بپرسند و شما هر میزان از اطلاعات را که مناسب تشخصی دهید به آن ها می گویید.پنهان سازی اطلاعات یعنی، تنها جزئیاتی که برای جهانِ بیرون (از ماژول) ضروری است، به جهانِ بیرون نمایش داده شود و غیر از آن، سایر اطلاعات، از دنیای بیرون مخفی شود.اطلاعاتی که پنهان می شود، معمولا شامل جزئیاتِ انجام یک کار است. برای مثال:چگونه یک کار مشخص، توسط چند thread انجام شودچطور یک TCP Connection ایجاد و مدیریت شودچگونه یک فایل JSON پردازش شود و اطلاعات آن بازیابی گرددداده ها چگونه در یک B-tree ذخیره شوددر واقع این اطلاعات، شامل ساختار داده ها و الگوریتم هایی است که به مکانیزمِ انجام کار مرتبط هستند.این پنهان سازی از دو جهت به مدیریت پیچیدگی در نرم افزار کمک می کند: واسطِ ماژول ساده می شود و یک نمای انتزاعیِ بسیار ساده شده از ماژول را به دنیای بیرون نشان می دهد. در نتیجه برنامه نویس ها، بار شناختی کمتری در استفاده از این ماژول خواهند داشت. برای مثال، برنامه نویسی که از کلاس B-tree استفاده می کند، نیازی ندارد که در مورد چگونگی balance ماندن tree دانشی داشته باشد.توسعه ی سیستم ساده تر است. اطلاعاتی که درون ماژول پنهان شده اند، اگر نیازی به تغییر یا توسعه ی جدید داشته باشند، هیچ وابستگی و تاثیری روی ماژول های دیگر ندارند. مثلا اگر درون ماژول به جای استفاده از یک پروتکل برای ارتباط با شبکه استفاده می شده، و الان تصمیم به تغییر پروتکل داشته باشیم، استفاده کنندگان از این ماژول، از این تغییر متاثر نخواهند شد و اصلا تغییری احساس نخواهند کرد.زمانی که یک ماژول را طراحی می کنید، باید با دقت، به اطلاعاتی که می توانند در ماژول پنهان شوند فکر کنید، هرچقدر مقداری بیشتری اطلاعات در ماژول مخفی شود، واسطِ آن ساده تر می شود و ماژول عمیق تر خواهد بود.بهترین فرم پنهان سازی اطلاعات، زمانی است که اطلاعات، کاملا در پیاده سازی ماژول مخفی شده اند، به صورتی که به طور کلی، از دید استفاده کنندگان غیر قابل مشاهده هستند. با این حال، شرایطی هم وجود دارد که اطلاعات، فقط از دیدِ بخشی از استفاده کنندگان مخفی است، و سایرین، آن ها را می بینند. در این شرایط هم تا اندازه ای پنهان سازی اطلاعات وجود دارد، و به اندازه ی خودش باعث کم کردن وابستگی می شود و ارزشمند است.پنهان سازی اطلاعات در سطح یک کلاس هم می تواند وجود داشته باشد. می توانیم متدهای private در کلاس تعریف کنیم، که هر یک دانشی را در خودش کپسوله می کند و از بقیه ی اعضای کلاس آن را مخفی می کند. همچنین اگر متغیرهایی در سطح کلاس دارید، تا جای ممکن، موارد استفاده از آن را محدود کنید.دقت کنید، تنها تعریف متغیرها و متدها در داخل یک کلاس به صورت private، پنهان سازی اطلاعات نیست. البته که این کار می تواند به پنهان سازی اطلاعات کمک کند، چرا که دسترسی به آن آیتم ها از بیرون کلاس غیر ممکن می شود، اما هنوز هم می توان از طریق متدهای public کلاس، امکان مشاهده ی این متغیرها را از دنیای بیرون ممکن ساخت. اگر این اتفاق بیفتد، در واقع این متغیرها پنهان نشده اند و هیچ فرقی با متغیرهای public ندارند.پنهان سازی اطلاعات تنها زمانی معنی دارد، که اطلاعاتِ مخفی شده، در بیرون از ماژول مورد نیاز نباشد. اگر اطلاعات در بیرون از ماژول مورد نیاز است، نباید پنهان شود. برای مثال، اگر پرفورمنس یک ماژول، بستگی به تنظیماتی دارد که از بیرون باید تعیین شود، این تنظیمات باید در واسط ماژول قرار گیرد، تا هر سیستم بیرونی، بتواند بر اساس نیاز خودش تنظیمات مناسب را به ماژول بدهد. با این حال ممکن است، راهی باشد تا این تنظیمات درون خود ماژول مدیریت شود و به سیستم بیرون نمایش داده نشود، در این صورت باید حتما از این روش استفاده کرد تا واسط ساده تر باشد.تلاش ما به عنوان طراح نرم افزار باید این باشد که میزان اطلاعاتی را که از بیرون ماژول قابل مشاهده است به حداقل برسانیم.نشت اطلاعات (Information leakage)نشت اطلاعات زمانی اتفاق می افتد، که یک تصمیمِ طراحی، در چندین ماژول منعکس می شود و باعث ایجاد وابستگی بین ماژول ها می شود. در نتیجه، در آینده، هر تغییر در آن تصمیمِ طراحی، باعث تغییر در تمام آن ماژول ها خواهد شد.به عنوان یک قانون کلی، اگر یک بخش از اطلاعات، در واسط ماژول قرار گیرد، بنابراین آن اطلاعات نشت کرده است. به همین دلیل است که اصرار بر ساده بودن واسطِ ماژول داریم. البته ممکن است اطلاعات در واسطِ ماژول قرار نگیرد ولی باز هم نشت کند. برای مثال، تصور کنید دو کلاس داریم که هر دو در مورد کار با یک نوع خاص از فایل اطلاعات دارند، مثلا یکی فایل را می خواند و یکی روی آن می نویسد. با این که شاید هیچ اطلاعاتی از فایل، در واسط این دو کلاس مشاهده نشود، ولی هر دوی آن ها به فرمت فایل وابستگی دارند و اگر فرمت تغییر کند، هر دو کلاس باید تغییر کنند. این نوع از نشتِ اطلاعات، از نوعی که در واسط مشاهده می شود خطرناک تر است، چرا که واضح نیست و به راحتی می تواند از دید ما مخفی بماند.یکی از Code Smell های معروف، Feature Envy است، که زمانی اتفاق می افتد که یک کلاس، بیشتر از این که با فیلد های یا متدهای خودش کار کند، با دیتای یک کلاس دیگر کار داشته باشد. در واقع به اطلاعاتِ آن کلاسِ دیگر، حسادت کند. به نظر من این مفهوم، با مفهومی که در حال توضیح آن هستیم، ارتباط نزدیکی دارد.اگر Code Smell دیگری به ذهن شما می رسد که مرتبط به مفهوم نشت اطلاعات است، در قسمت نظرات بنویسید.نشتِ اطلاعات، یکی از مهم ترین مسائلی است که ما به عنوان برنامه نویس باید به آن توجه کنیم و نسبت به آن حساسیت داشته باشیم. اگر در کد، با نشتِ اطلاعات بین کلاس ها مواجه شدید، این سوال را بپرسید: &#34;چطور می توان این کلاس ها را دوباره طراحی کرد، به صورتی که این اطلاعاتِ خاص، تنها روی یک کلاس تاثیر بگذارد؟&#34; اگر کلاس هایی که درگیر این اطلاعات هستند، کوچک هستند و وابستگی شدیدی به اطلاعاتِ نشت شده دارند، شاید ادغامِ آن ها به صورتِ یک کلاس، منطقی باشد. یک روش دیگر هم این است که اطلاعاتِ نشت شده را از هر دو کلاس خارج کرده و درون یک کلاس دیگر، کپسوله کنیم. ولی در استفاده از این روش، باید دقت کنیم که واسطِ کلاس جدید ساده باشد و شامل جزئیات نباشد. اگر کلاس جدید بخواهد مقدار زیادی جزئیات را در واسط خودش به بیرون معرفی کند، ارزشی ندارد، چرا که نشتِ اطلاعات هنوز هم وجود دارد (این بار از طریق واسط).تفکیک زمانیتفکیکِ زمانی، یک اشتباه طراحی است که می تواند باعث نشت اطلاعات شود، و موقعی اتفاق می افتد که طراحیِ سیستم، به ترتیب زمانیِ عملیاتِ اجرایی، بستگی داشته باشد. نرم افزاری را در نظر بگیرید که یک فایل را می خواند، محتویاتش را تغییر می دهد و فایل را ذخیره می کند. اگر این نرم افزار شامل سه کلاس باشد، یکی برای خواندن فایل، یکی برای اعمال تغییرات و یکی برای ذخیره ی نسخه ی جدید، دچار تفکیکِ زمانی شده ایم. اشکال اینجاست که، هر دو کلاسی که مرتبط با خواندن و ذخیره ی فایل هستند، درباره ی فرمت فایل دانش دارند و این یعنی نشتِ اطلاعات. راه حل این است که مکانیزم مربوط به خواندن فایل و ذخیره ی آن را در یک کلاس بگذاریم. این کلاس در فازهای خواندن و نوشتن فایل، مورد استفاده قرار می گیرد. در واقع باید به عملیات فکر کنیم، نه به ترتیب انجام کارها.افتادن در تله ی تفکیکِ زمانی بسیار ساده است، به این علت که زمانِ نوشتن کد، چیزی که در ذهن شماست، ترتیب زمانی عملیات است. با این حال، نباید بگذاریم که ترتیبِ انجام کارها، در ساختار ماژول های ما تاثیر بگذارد.زمانی که ماژول ها را طراحی می کنیم، باید به دانشی که برای انجام هر عملیات مورد نیاز است توجه کنیم، نه ترتیب زمانی عملیات.خلاصهماژول های عمیق و پنهان سازی اطلاعات، مفاهیمی هستند که بسیار به هم مرتبط هستند. اگر یک ماژول، مقدار زیادی اطلاعات را پنهان کند، بنابراین پیاده سازی آن پیچیده و واسط آن ساده می شود، پس ماژول عمیقی است. زمانی که ماژول ها را طراحی می کنید، سعی کنید از ترتیبِ اجرا شدنِ عملیات تاثیر نپذیرید. به جای آن، به دانشی که برای عملیاتِ نرم افزار نیاز است توجه کنید و هر ماژول را طوری طراحی کنید که تکه ای از این دانش را کپسوله سازد. در این صورت یک طراحی ساده و تمیز و ماژول هایی عمیق خواهید داشت.منابعA philosophy of software designInformation Hiding and Encapsulation in OOP</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Thu, 20 May 2021 13:18:52 +0430</pubDate></item><item><title>برنامه نویسی ماژولار</title><link>https://virgool.ir/@sara.rezaei/برنامه-نویسی-ماژولار-xouohqi2h7zl</link><description>یک تکنیک مهم در مدیریتِ پیچیدگی در نرم افزار، این است که سیستم را طوری طراحی کنیم، که برنامه نویس  هنگام انجام یک کار، فقط با یک بخش کوچک از کل پیچیدگیِ نرم افزار روبرو باشد. در واقع سیستمِ نرم افزاری را، به صورت مجموعه ای از تعدادی ماژول که از هم مجزا هستند، طراحی کنیم. این ماژول ها می توانند کلاس های ما باشند یا زیر سیستم ها یا سرویس ها.تاریخچه ی برنامه نویسی ماژولار، به دهه ی 1960 میلادی بر می گردد، زمانی که برنامه نویس ها، نرم افزارهای بزرگ را به بخش های کوچکتر شکستند. هرچند بیشتر از 6 دهه از عمر این رویکردِ برنامه نویسی می گذرد، اما امروزه هم برای توسعه ی نرم افزار بسیار مفید است و احتمالا اکثر برنامه نویس های امروزی، بدون این که بدانند، از برنامه نویسی ماژولار بهره می گیرند. ساده ترین مثالِ آن، زمانی است که (در زبان های شی گرا) یک کلاس، یا یک متد تعریف می کنند.در یک دنیای ایده آل، هر ماژول کاملا از دیگر ماژول ها مستقل است. برنامه نویس ها می توانند بدون هیچ دانشی از ماژول های دیگر، روی یک ماژول خاص کار کنند. در چنین دنیایی، پیچیدگی سیستم، به اندازه ی پیچیدگیِ پیچیده ترین ماژول است.ولی ما در دنیای واقعی زندگی می کنیم. دنیایی که هر ماژول نیاز دارد با دیگران کار کند. در نتیجه باید از ماژول های دیگر باخبر باشد و آن ها را بشناسد. این یعنی وابستگی بین ماژول ها وجود دارد و وابستگی باعث می شود تغییر در یک ماژول، روی سایرین تاثیر بگذارد. برای مثال، یک متد را در نظر بگیرید که 2 پارامتر دارد. اگر یکی از پارامترهای این متد حذف شود، کسانی که از آن استفاده می کنند باید تغییر کنند و خود را با signature جدید انطباق دهند. در طراحی ماژولار، چشم انداز ما باید به حداقل رساندنِ وابستگی بین ماژول ها باشد و تا حد ممکن در این زمینه تلاش کنیم.برای مدیریت وابستگی ها، هر ماژول باید شامل دو عنصر مهم باشد:واسط (interface)در یک تعریف کلی می توان گفت، واسط، مشخص کننده ی نحوه ی استفاده از یک ماژول است. وسیله ای است که یک ماژول را به دیگری متصل می کند و مشخص می کند که دیگران، از یک ماژول، چه انتظاری باید داشته باشند. در واقع، شامل تمام چیزهایی است که یک برنامه نویس، که روی یک ماژول دیگر کار می کند، برای استفاده از ماژول فعلی باید بداند.برای درک بهتر مطالب این نوشته، هر آنچه که از کلمات واسط (interface) و انتزاع (abstraction) در ذهن دارید را به صورت موقت، فراموش کنید و سعی کنید با مفاهیم این عبارات، در این نوشته، همراه شوید.پیاده سازی (implementation)هر ماژول، باید شامل کدی باشد که قول و قراری که توسط واسط هایش به دیگر ماژول ها داده شده را پیاده سازی کند. می توان گفت، هرچیزی غیر از واسط ماژول، پیاده سازی آن است.یک برنامه نویس که روی یک ماژول کار می کند، باید تمام واسط ها و پیاده سازی های آن ماژول، به علاوه ی واسط های سایر ماژول های وابسته را بشناسد. ولی نیازی به شناختن پیاده سازی ماژول های دیگر ندارد.با این دیدگاه، هر قطعه ای از کد، که یک واسط و یک پیاده سازی دارد یک ماژول محسوب می شود. در یک زبان برنامه نویسی شی گرا، کلاس ها و method های آن ها، و در یک زبان برنامه نویسی غیر شی گرا، function ها را می توان ماژول در نظر گرفت. در سطوح بالاتر، service ها و subsystem ها هم ماژول هستند.اگر با زبان های برنامه نویسی شی گرا مثل #C کار کرده اید، یک نکته ی مهم که باید به آن توجه کنید این است که interface در طراحی ماژولار، مفهوم وسیع تری از کلمه ی interface در زبان های برنامه نویسی مثل #C دارد.وقتی به برنامه نویسی ماژولار فکر می کنیم، باید فراتر از شی گرای بودن یا نبودن به آن نگاه کنیم. در یک زبان شی گرا، ممکن است جداسازی ماژول ها را به وسیله ی کلاس ها انجام دهیم، در یک زبان دیگر ممکن است این کار را از طریق function ها انجام دهیم.بهترین ماژول ها، آن هایی هستند که واسط شان، بسیار ساده تر از پیاده سازی آن هاست. ساده بودنِ واسط از دو جهت مفید است:1. پیچیدگی ای که ماژول به دنیای بیرون تحمیل می کند، به حداقل می رسد2. تغییرات و اصلاحاتِ آینده، بیشتر در پیاده سازیِ ماژول تاثیر خواهد داشت و واسط از این تغییرات متاثر نخواهد شد، در نتیجه سایر ماژول ها تغییر نخواهند کرد.از جنبه های مهم طراحی ماژولار، می توان به Separation of concerns یا SoC اشاره کرد که یکی از اصول پایه ای طراحی نرم افزار است. بر اساس این اصل، سیستم نرم افزاری، باید به بخش های مستقلی تقسیم شود، که هر بخش concern جداگانه ای را پاسخ می دهد.این اصل را می توانید به شکل های مختلف در سیستم های نرم افزاری ببینید:1. کپسوله سازی اطلاعات در objectها، در زبان های شی گرا.2. در طراحی لایه ای، که در آن سیستم دارای لایه های مختلف مثل Business layer یا DataAccess layer است.3. در MVC که concern های UI از ساختار اصلی نرم افزار جدا می شوند.4. در طراحی سرویس گرا، سرویس ها، این جداسازی را انجام می دهند.5. در زبان هایی مانند C یا Pascal، به وسیله ی function ها concern ها را جدا می کنیم.6. در AOP این جداسازی توسط aspect ها انجام می شود.7. در بستر شبکه، لایه های مختلف وجود دارد که هر کدام وظایف و concern های خود را دارند.شاید بتوان گفت، هر جا مرزبندی دیدیم، احتمالا بر اساس اصل SoC رفتار شده است. منظور از مرز، هر محدودیت منطقی یا فیزیکی است که مجموعه ی مشخصی از مسئولیت ها را ترسیم می کند.دو نوع واسط را می توان برای یک ماژول تعریف کرد:رسمیواسطِ رسمی یک ماژول، کاملا در ظاهر آن مشخص است. برای مثال، ورودی ها و نوع خروجیِ یک متد، واسط رسمی آن هستند. برای یک کلاس، همه ی اعضای public آن (شامل method ها و property ها) واسطِ رسمی آن هستند. معمولا واسط رسمی، توسط زبان برنامه نویسی قابل شناسایی و بررسی است، برای مثال در زبان #C اگر ورودی یک method شامل 4 پارامتر باشد و یک ماژول آن را با 3 پارامتر صدا بزند، خطای compile خواهد داشت.غیر رسمیاین نوع واسط، توسط زبان برنامه نویسی قابل تشخیص نیست، زیرا در رفتار آن ماژول پنهان شده است. برای مثال فرض کنید یک method، در ورودی خود 3 پارامتر می گیرد و پارامتر دوم نام فایلی است که قرار است حذف شود و این از signature متد قابل تشخیص نیست. یا این که در یک کلاس، قبل از این که یک method صدا زده شود، باید یک property مقدار دهی شود. در واقع اگر یک برنامه نویس که روی یک ماژول دیگر کار می کند، برای استفاده از یک ماژول، نیاز به دانستن یک سری اطلاعات داشته باشد، آن اطلاعات، واسط غیر رسمی آن ماژول است.یک راه برای مشخص کردن این واسط های غیر رسمی، استفاده از comment است. در خیلی از واسط ها، جنبه های غیر رسمی بسیار وسیع تر و پیچیده تر از جنبه های رسمی آن است.یکی از فوایدِ وجود واسط هایی که واضح و مشخص هستند، این است که برنامه نویس ها هنگام استفاده از یک ماژول، دقیقا می دانند چه باید بکنند. این باعث کم کردن &#34;ندانسته های ناشناخته&#34; که در پیچیدگی در نرم افزار به آن اشاره شد می شود.انتزاع (Abstractions)انتزاع، یک نمای ساده شده از یک موجودیت است، که تمام جزئیات بی اهمیت را حذف می کند و فقط اطلاعات مهم را باقی می گذارد. تشخیص مهم بودن یا نبودنِ جزئیات، به هدف آن انتزاع و دیدگاهش نسبت به آن موجودیت بستگی دارد. انتزاع به ما کمک می کند درباره ی مسائل پیچیده فکر کنیم، آن ها را ساده کنیم و در نتیجه بتوانیم آن ها را مدیریت کنیم.انتزاع، مفهومی نیست که فقط مربوط به نرم افزار باشد. در هنر، زمانی که هنرمند، یک تصویر از آنچه در ذهن دارد خلق می کند، بدون این که جزئیات آن سوژه در دنیای واقعی را ترسیم کند، انتزاع وجود دارد. هنر انتزاعی هنری است که سعی در نمایش دقیق واقعیت بصری ندارد اما در عوض از اشکال ، رنگ ها ، فرم ها و علائم حرکتی برای دستیابی به تأثیر آن استفاده می کند.در واقع فرم ها، در هنر انتزاعی، بسیار ساده شده اند و گرچه ممکن است یک هنرمند هنگام نقاشی، یک شی واقعی در ذهن داشته باشد، اما ممکن است آن شی با استفاده از رنگها و بافت ها برای ایجاد یک احساس، تلطیف ، تحریف یا اغراق شود، به نوعی که به سختی بتوان با نگاه اولیه به اثر، object دنیای واقعی را شناسایی کرد.انتزاع، در ریاضی هم کاربرد دارد. بسیاری از زمینه های ریاضیات، قبل از اینکه قوانین و مفاهیم اساسی شناسایی، و بعنوان ساختارهای انتزاعی شناخته شوند، با مطالعه ی مسائل دنیای واقعی آغاز شدند. به عنوان مثال ، هندسه، ریشه در محاسبه فواصل و مساحت ها در دنیای واقعی دارد و جبر با روش های حل مسائل در حساب آغاز شده است.ولی هنگامی که کسی از شما یک مساله ی ریاضی می پرسد، برای حل آن نیازی به داشتن اشیاء واقعی ندارید، تنها با استفاده از انتزاعات، یعنی صورتی از واقعیت که جزئیات آن حذف شده است، می توانید مساله را حل کنید.ایده ی اصلی انتزاع این است که:1. اشیاء و موقعیت های مشابه را شناسایی کنیم2. تفاوت های آن ها را نادیده بگیریم 3. روی یک جنبه ی خاص و مشترک (که مربوط به مساله ای است که در حال حل آن هستیم و تمام آن اشیاء یا موقعیت ها آن را منعکس می کنند) تمرکز کنیم.ما در زندگی روزانه هم برای مدیریت پیچیدگی به انتزاع وابسته هستیم. مایکروویو، شامل  قطعات الکترونیکی پیچیده ای است که جریان متناوب را به تابش مایکروویو تبدیل کرده و آن تابش را درون مایکروویو توزیع می کند.  خوشبختانه لازم نیست که ما برای استفاده از این وسیله، تمام جزئیات الکترونیکی آن را بدانیم، بلکه تنها از طریق یک انتزاع، یعنی یک مدل ساده شده از مایکروویو، شامل تعدادی دکمه، زمان بندی و سایر مسائل مربوط به آن را مدیریت می کنیم.اما ارتباط انتزاع با برنامه نویسی ماژولار چیست؟هر ماژول از طریق واسط هایش، یک انتزاع فراهم می کند. واسط، یک چشم اندازِ ساده شده از عملکرد ماژول ارائه می دهد. از نظرِ انتزاع، جزئیات پیاده سازی &#34;بی اهمیت&#34; هستند، بنابراین از واسط حذف می شوند. وقتی از انتزاع صحبت می کنیم، کلمه ی &#34;بی اهمیت&#34; حیاتی است. هرچقدر مسائل بی اهمیت بیشتری از یک انتزاع حذف شوند، بهتر است و نکته ی مهم این است که فقط موارد بی اهمیت از انتزاع حذف شوند. بی دقتی در این زمینه، می تواند دو مشکل ایجاد کند: ممکن است جزئیاتی که واقعا مهم نیستند، در انتزاع وجود داشته باشند. این مساله باعث می شود برنامه نویس هایی که می خواهند از این انتزاع استفاده کنند، دچار بارِ شناختیِ زیاد شوند.امکان دارد جزئیاتی که واقعا مهم هستند، از انتزاع حذف شوند. این مساله باعثِ ابهام می شود. برنامه نویس ها ممکن است تمام اطلاعات لازم برای استفاده از این انتزاع را نداشته باشند. به این نوع انتزاع &#34;false abstraction&#34; گفته می شود.نکته ی کلیدی در طراحی انتزاع، این است که بفهمیم چه چیزی واقعا مهم است، و به دنبال طراحی ای باشیم، که میزان اطلاعاتی که مهم هستند را به حداقل می رساند.چه زمانی می فهمیم به یک انتزاع خوب از یک ماژول دست یافته ایم؟زمانی که استفاده کنندگان آن ماژول، نیاز به دانستن هیچ چیزی بیشتر از آنچه که در واسط اش فراهم شده نداشته باشند.عمق ماژول بهترین ماژول ها آن هایی هستند که واسط های ساده ای دارند و عملکرد قدرتمندی را فراهم می کنند.تصویر زیر را در نظر بگیرید:ماژول های عمیق بهتر هستند. آن ها دسترسی به عملکرد پیچیده ی خود را از طریق یک واسط ساده امکان پذیر می سازند. در مقابل، ماژول های کم عمق یک واسط خیلی پیچیده دارند که عملکرد بسیار ساده ای را پنهان کرده است.  در این تصویر، هر ماژول، به شکل یک مستطیل نمایش داده شده است. مساحت هر مستطیل، نشان دهنده ی پیاده سازیِ آن است. ضلع بالایی، نشان دهنده ی واسط ماژول است و اندازه ی آن، میزان پیچیدگی واسط را نشان می دهد. بهترین ماژول ها، ماژول های عمیق هستند که عملکرد زیادی را پشت یک واسط ساده پنهان کرده اند. یک ماژول عمیق، یک انتزاع خوب است چرا که مقدار بسیار کمی از پیچیدگی درونی خودش را برای دنیای بیرون، آشکار می کند.مفهوم &#34;عمق ماژول&#34;، راهی برای فکر کردن به سود و هزینه است. سود ارائه شده توسط ماژول، عملکرد آن است و هزینه ی آن (از نظر پیچیدگی سیستم)، واسط های آن هستند. واسطِ یک ماژول، نشان دهنده ی پیچیدگی ای است که آن ماژول به سایر بخش های سیستم تحمیل می کند، بنابراین هرچقدر واسط، ساده تر و کوچک تر باشد، پیچیدگیِ تحمیل شده کمتر است. بهترین ماژول ها آن هایی هستند که بیشترین سود و کمترین هزینه را دارند.به عنوان مثالی از ماژول عمیق، می توانیم به garbage collector زبان Java اشاره کنیم. این ماژول، هیچ واسطی ندارد و به صورت نامرئی، در پشت صحنه کار می کند. با این که پیچیدگیِ پیاده سازی آن بسیار زیاد است، ولی اضافه کردن آن به یک سیستم، هیچ پیچیدگی (از نظر واسط ها) به سیستم اضافه نمی کند. در واقع پیچیدگیِ آن، به صورت کامل از دید برنامه نویس هایی که از این زبان استفاده می کنند پنهان شده است.یک مثال از ماژول کم عمق، این method است:private void AddNullValueForAttribute(String attribute) {&#xA;        data.put(attribute, null);&#xA;}از نظر مدیریت پیچیدگی، این ماژول نه تنها کمکی نمی کند، بلکه اوضاع را بدتر می کند. این متد هیچ انتزاعی ندارد و استفاده کننده ها باید بدانند که ورودی متد، در متغیر data ذخیره می شود. در واقع، برای یک برنامه نویس، شناختن واسط این ماژول، اصلا ساده تر از دانستن پیاده سازی آن نیست (در حالی که این دقیقا کاری است که واسط باید انجام دهد و شناخت ماژول را برای دنیای بیرون ساده تر کند). اگر برای این متد، مستند تهیه شود، احتمالا مستندات آن، طولانی تر از پیاده سازی آن خواهند بود! در واقع اگر این متد نبود، زحمت کمتری برای استفاده کنندگان وجود داشت و آن ها می توانستند خودشان به راحتی متغیر data را مقداردهی کنند. این ماژول، نه تنها هیچ سودی ندارد، بلکه یک هزینه هم اضافه می کند و آن زحمت یاد گرفتن یک واسط جدید توسط برنامه نویس ها است.ماژول کم عمق، ماژولی است که واسط آن، به نسبت پیاده سازی اش، پیچیدگی بیشتری دارد. سودی که این ماژول ها ارائه می دهند، (این که کسی مجبور نیست بداند در داخل آن ها چه کاری انجام می شود) کمتر از هزینه ی یادگیری و استفاده از واسط های آن هاست.کلاس های کم عمقیکی از اشتباهات رایج میان برنامه نویس ها، این است که، سعی می کنند کلاس ها را &#34;کوچک&#34; در نظر بگیرند، نه عمیق. هنر را در این می دانند که کلاس های بزرگ را به چندین کلاس کوچک بشکنند. درباره ی متدها هم همینطور است، هر متدی که بیشتر از n خط کد دارد را به متدهای کوچکتر تقسیم می کنند. این رویکرد، به تعداد زیادی کلاس و method منتهی می شود، که همه به هم وابسته اند و به پیچیدگی سیستم می افزایند.در این سیستم ها، برنامه نویس ها علاقه دارند که میزان عملکرد هر کلاس را به حداقل برسانند و برای هر عملکرد بیزنسی جدید، کلاس جدید تعریف می کنند. در نتیجه، تعداد زیادی کلاس داریم، که به هر کدام به تنهایی ساده هستند، ولی وقتی در سیستم قرار می گیرند، به پیچیدگی آن می افزایند، چرا که هر کدام، واسط خود را دارند و این واسط ها باید در جاهای مختلف استفاده شوند.برای مثال کد زیر را در نظر بگیرید:FileInputStream fileStream = new FileInputStream(fileName);&#xA;BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);&#xA;ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);کلاس FileInputStream فقط عملیات مربوط به IO را فراهم می کند و امکان buffering ندارد. اگر بخواهیم از امکان buffering بهره مند شویم، باید از کلاس BufferedInputStream استفاده کنیم. همچنین اگر بخواهیم با اشیاء serialize شده کار کنیم، ناچار به استفاده از کلاس ObjectInputStream هستیم. اگر یک برنامه نویس فراموش کند که یک شی از کلاس BufferedInputStream بسازد، buffering نخواهد داشت و عملیات کند خواهد بود.شاید در دفاع از این مدل برنامه نویسی، بتوان این مساله را مطرح کرد که احتمالا، همه نیاز به استفاده از buffering نخواهد داشت، بنابراین آن را جدا کنیم تا استفاده کنندگان در مورد بودن یا نبودنش تصمیم بگیرند. قطعا این جداسازی خوب است، ولی دقت کنیم که واسط ها، باید طوری طراحی شوند، که موارد معمول را تا حد ممکن ساده کنند. یعنی در این مورد ما باید فکر کنیم، احتمالا اکثر کسانی که از این کلاس استفاده می کنند، به buffering نیاز خواهند داشت، پس برای آن عده ی کمی که نیاز ندارند، باید یک مکانیزم برای غیر فعال کردنش فراهم کنیم. مثلا شاید از طریق یک constructor مجزا یا یک متد که این ویژگی را غیرفعال می کند. مهم این است که نیاز اکثریت استفاده کنندگان را شناسایی کنیم و برای آن ها بهترین واسط را طراحی کنیم.خلاصهبرای مدیریت پیچیدگی، باید نرم افزار را به بخش های مستقل تقسیم کنیم. این تقسیم بندی می تواند از جنبه های گوناگون انجام شود. به این بخش ها، که ممکن است کلاس، method یا یک سیستم باشد، ماژول گفته می شود. با این تقسیم بندی، به اصل SoC وفادار مانده ایم.انتزاع، یعنی حذف جزئیات. با جداسازی واسط یک ماژول از پیاده سازی اش، یک انتزاع برای آن تعریف می کنیم و به این ترتیب می توانیم پیچیدگیِ درونی یک ماژول را از سایر بخش های سیستم پنهان کنیم. استفاده کنندگان از یک ماژول، باید فقط آن انتزاعی را که ماژول برای دنیای بیرون فراهم کرده است بشناسند.یک موضوع مهم در طراحی کلاس ها و سایر ماژول ها، این است که تا جای ممکن عمق آن ها را بیشتر کنیم، تا برای اکثریتِ مواردِ استفاده، واسط ساده ای را، برای یک پیاده سازی پیچیده، فراهم کنند.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Thu, 13 May 2021 18:39:56 +0430</pubDate></item><item><title>برنامه نویسی استراتژیک و تاکتیکی</title><link>https://virgool.ir/@sara.rezaei/برنامه-نویسی-استراتژیک-و-تاکتیکی-etfy1mqubtye</link><description>یکی از مسائل مهمی که در برنامه نویسی می توان مطرح کرد، طرز فکر (mindset) برنامه نویس، در هنگام انجام یک کار است. خیلی از سازمان ها، افراد را تشویق می کنند که کارها را با طرز فکر تاکتیکی پیش ببرند، یعنی با بیشترین سرعت ممکن، پیاده سازی ها را انجام دهند و کار را تحویل دهند. اما طرز فکری دیگری هم وجود دارد و آن طرز فکر استراتژیک است که نیازمند صرف زمان، برای طراحیِ تمیز و فکر شده است.استراتژی و تاکتیک، کلمه هایی هستند که زیاد شنیده می شوند و گاهی به جای هم استفاده می شوند. هر چند این دو کلمه، ابتدا از اصطلاحات نظامی گرفته شده اند، ولی امروزه در بسیاری از زمینه های زندگی کاربرد دارند.حدود 2500 سال پیش، سان تزو در کتاب &#34;هنر جنگ&#34; گفت:استراتژی بدون تاکتیک، کند‌ترین راه رسیدن به پیروزی است.تاکتیک بدون استراتژی، سر و صدای قبل از شکست است.این دو مفهوم را می توان به این صورت تعریف کرد:استراتژی، اهداف بلند مدت و نحوه برنامه ریزی برای رسیدن به آن اهداف را مشخص می کند.تاکتیک، گام های کوچک و  اقدام های مشخصی است که برای رسیدن به آن اهداف انجام می دهیم.استراتژی مفهومی بلند مدت و کلان است. تاکتیک، کوتاه مدت است و تاثیر کوچک تر و مشخص تری دارد.تغییر استراتژی مانند تغییر مسیر یک ناو هواپیمابر است، قابل انجام است ولی نه به سرعت. در مقابل، تغییر تاکتیک می تواند به سرعت انجام شود.برنامه نویسی تاکتیکیاگر شما با رویکرد تاکتیکی برنامه نویسی می کنید، همه ی تمرکزتان روی تمام کردن کار است. برای مثال این که یک خطا برطرف شود یا یک feature کار کند (در واقع فقط کار کند). شاید در نگاه اول خیلی منطقی به نظر برسد، شاید این سوال پیش بیاید که چه چیزی از نوشتن کدی که کار می کند مهم تر است؟مشکل این رویکرد این است که بسیار کوتاه نگر است. برنامه نویسی که با رویکرد تاکتیکی کد می نویسد، احتمالا می خواهد یک تسک را در سریع ترین زمان ممکن تمام کند. احتمالا از سمت مدیرش یک deadline دارد و در نتیجه هیچ دغدغه ای نسبت به آینده ی آن قطعه از کد ندارد. به این فکر نمی کند که بهترین طراحی را انتخاب کند. به کاهش پیچیدگی فکر نمی کند. به وابستگی ها دقت نمی کند. هر چیزی که بخواهد سرعت کارش را کاهش دهد حذف می کند تا آن کار مشخص را به انجام برساند.همانطور که در نوشته ی مربوط به پیچیدگی در نرم افزار توضیح داده شد، پیچیدگی در طول زمان به وجود می آید، نه بر اثر یک اشتباه، بلکه بر اثر صدها و هزاران بار، که برنامه نویس به سرعت کار می کند تا پیاده سازی یک بخش را به پایان برساند و کاملا بدون فکر این کار را انجام می دهد.به مرور زمان یک اتفاق بدتر هم می افتد. پیچیدگی هایی که ایجاده شده اند، مشکلاتی به وجود می آورند که نیاز به اصلاح دارند، ولی همه ی افراد، در حال کار کردن روی وظایف دیگری هستند که باید هرچه سریع تر به انجام برسد، پس هیچکس وقت برای refactor کردن کد ندارد. به زودی وضعیت کدهای موجود طوری می شود که برای اصلاح آن باید ماه ها وقت صرف کرد و به علت نبود نیرو، هیچ اصلاحی صورت نمی گیرد. به همین دلیل وقتی یک بار برنامه نویسیِ تاکتیکی شروع شد، متوقف کردن آن کار ساده ای نیست.هر شرکتی که در زمینه ی توسعه نرم افزار مشغول است، حداقل یک برنامه نویس تاکتیکی حرفه ای دارد که کارها را سریع تر از هر موجود زنده ی دیگر در آن شرکت به انجام می رساند. در بعضی سازمان ها، با این افراد به عنوان یک قهرمان برخورد می شود و همه باید از آن ها یاد بگیرند!ولی این قهرمان ها، معمولا پشت سر خودشان یک ویرانی عظیم بر جا می گذارند. کسانی که بعد از آن ها، روی این ویرانی کار می کنند، معمولا آن ها را قهرمان نمی دانند. آن ها باید هم این هرج و مرج بزرگ را اصلاح کنند و هم به مدیران جواب بدهند که چرا سرعتشان به خوبی برنامه نویس قبلی نیست.برنامه نویسی استراتژیکاولین قدم در راه تبدیل شدن به یک برنامه نویس خوب، این است که بفهمیم کار کردن کد کافی نیست. این که مقدار زیادی ابهام و وابستگی به کد تحمیل کنیم، فقط برای این که یک پیاده سازی را به انجام برسانیم، به هیچ وجه قابل قبول نیست. مهم ترین مساله، ساختار نرم افزار در بلند مدت است.برای مثال، احتمالا بسیاری از تغییرات در آینده، شامل extend شدنِ قابلیت های فعلی نرم افزار است. پس یک برنامه نویس، باید در طراحیِ اکنون، به این مساله فکر کند که در آینده شاید این ویژگی بخواهد گسترش یابد. پس طوری آن را طراحی می کند که برای extend شدنش، نیاز به تغییرات گسترده در سیستم نباشد. در واقع باید تسهیلِ extend های آینده را به عنوان یکی از اصلی ترین وظایف خود بداند.یک برنامه نویس، نباید هدف خود را &#34;کار کردن کد&#34; یا &#34;تمام کردن تسک&#34; بگذارد، بلکه هدف اصلی او باید تولید یک طراحیِ عالی باشد که در نهایت &#34;کار هم می کند&#34;. این طرز فکرِ استراتژیک است.برنامه نویسیِ استراتژیک، یک مدل ذهنی &#34;سرمایه گذاری&#34; نیاز دارد. به جای این که سریع ترین راه تمام کردن پروژه را انتخاب کنیم، زمان را &#34;سرمایه گذاری&#34; می کنیم تا بهترین طراحی را انتخاب کنیم. در کوتاه مدت قطعا سرعت کاهش خواهد یافت ولی در دراز مدت، کار بسیار سریع تر پیش خواهد رفت.این سرمایه گذاری به دو صورت می تواند باشد:سرمایه گذاری فعالفرض کنید یک ویژگی جدید قرار است به سیستم اضافه شود. به جای این که اولین ایده ی طراحی که به ذهنمان می رسد را پیاده سازی کنیم، کمی وقت سرمایه گذاری می کنیم و تعدادی طراحی دیگر پیدا کرده و از بین آن ها بهترین را انتخاب می کنیم. به آینده ی سیستم فکر می کنیم و نکات مهم را در نظر می گیریم. شاید کمی وقت هم برای مستندسازی سرمایه گذاری کنیم.سرمایه گذاری واکنشیهرچقدر هم که در زمان پیاده سازی یک ویژگی جدید، سعی کنیم بهترین طراحی را انتخاب کنیم، باز هم احتمالا جایی در میانه ی راه مجبور به تغییر تصمیمات گذشته می شویم، چرا که بعضی اشتباهات، فقط با گذشت زمان خود را نشان می دهند. مهم ترین مساله این که است که این اعمال این تغییرات را به تاخیر نیندازیم. کمی وقت سرمایه گذاری کنیم و آن را اصلاح کنیم. در طول زمان، این بهبود های کوچک باعث بهتر شدن سیستم می شوند. آیا با روش تاکتیکی نمی توان موفق شد؟معمولا در startup ها، روی روش تاکتیکی بسیار تاکید می شود. در این شرکت ها، تمرکز روی تمام کردن پروژه است و به این فکر می کنند که اگر پروژه به درامدزایی برسد، برای تمیزکاری وقت خواهند داشت. نکته ای که باید به آن توجه کرد این است که اگر کد تبدیل به اسپاگتی شود، اصلاح آن تقریبا غیر ممکن است. احتمالا هزینه های نگهداری نرم افزار بسیار بالا خواهد بود و توسعه ی بخش های بعدی بسیار کند پیش خواهد رفت.کدِ اسپاگتی (Spaghetti Code) یک عبارت منفی است که برای توصیف کدِ بدون ساختار و غیر قابل نگهداری، استفاده می شود. در واقع این یک ضد الگو (AntiPattern) است که از ابتدای تاریخ برنامه نویسی وجود داشته است.اصولا کدهایی که به زبان های غیر شی گرا نوشته می شود، بیشتر مستعد اسپاگتی شدن هستند، با این حال این مساله بین برنامه نویس هایی که به زبان های شی گرا کد می نویسند، ولی بر مفاهیم شی گرایی تسلط ندارند هم رایج است.کد اسپاگتی، آنقدر ساختار ضعیف و غیرواضحی دارد که حتی برنامه نویس هایی که قبلا روی این کد کار کرده اند، اگر مدت زمان کمی از آن دور باشند، برای تسلطِ دوباره روی آن، باید زمان زیادی صرف کنند.یک نکته ی مهم دیگر این است که، موفقیت یک شرکت بستگی زیادی به کیفیت کار مهندسین آن شرکت دارد. بهترین راهِ کم کردن هزینه های توسعه ی نرم افزار، استفاده از برنامه نویس های حرفه ای است و مساله این است که برنامه نویس های حرفه ای، به طراحی خوب اهمیت می دهند. اگر نرم افزار کد کثیفی داشته باشد، استخدام و کار طولانی مدت با برنامه نویس های ماهر ممکن نیست. در نتیجه شرکت باید به برنامه نویس های معمولی رضایت دهد و در درازمدت هزینه ی آن را بپردازد.به عنوان نمونه می توان به فیسبوک اشاره کرد. تا زمان زیادی، شعار شرکت این بود:&#34;Move fast and break things&#34;برنامه نویس های تازه نفس، که از دانشگاه فارغ التحصیل شده بودند سریع کارها را به دست می گرفتند و در همان هفته ی اول ورودشان، تغییرات را روی نسخه ی عملیاتی commit می کردند. نتیجه ی مثبت این امر آن بود که، فیسبوک به عنوان شرکتی که کارمندهایش را قدرتمند می سازد مطرح شد. آزادی عمل بسیار بود و قوانین و محدودیت های کمی وجود داشت. در نتیجه فیسبوک به عنوان یک شرکت، خوب عمل کرد، ولی روش تاکتیکی که در پیش گرفته بود، به code base آسیب زد. قسمت های بسیاری از کد، بی ثبات و غیر قابل فهم و بدون تست بود و کار کردن روی آن دشوار بود. پس از مدتی، شرکت شعارش را تغییر داد:&#34;Move fast with solid (stable) infrastructure&#34;با این شعار، برنامه نویس ها را تشویق به سرمایه گذاریِ زمان برای طراحی بهتر کرد.شرکت های دیگری مثل گوگل و VMware، که رویکرد استراتژیک داشتند، تقریبا همزمان با فیسبوک رشد کردند. هر دوی این شرکت ها، تاکید زیادی روی کیفیت کد و طراحی خوب داشتند و محصولاتی با قابلیت اطمینان بالا تولید کردند که پاسخگوی نیازهای پیچیده است. فرهنگ استراتژیک این شرکت ها در Silicon Valley به خوبی شناخته شده است و تعداد کمی شرکت می توانند در زمینه ی استخدام افراد فنیِ ماهر، با آن ها رقابت کنند.این مثال ها نشان می دهد که یک شرکت با هر دو رویکرد (استراتژیک و تاکتیکی) می تواند موفق شود. با این حال، قطعا کار کردن در شرکتی که به طراحی نرم افزار اهمیت می دهد و code base تمیزتری دارد، جذاب تر خواهد بود.خلاصهطراحی خوب، راحت به دست نمی آید. برای یک طراحی خوب، باید به طور پیوسته سرمایه گذاری کنیم تا مشکلات کوچک، تبدیل به فاجعه های بزرگ نشوند. باید به رویکرد استراتژیک بها دهیم و سرمایه گذاری خود را از امروز شروع کنیم نه فردا. اگر مشکلاتی که امروز در کد می بینیم را اصلاح نکنیم، این ها روی هم انباشته می شوند و احتمالا فردا اصلاحشان دشوارتر است، پس این اهمال کاری تا پایان پروژه ادامه خواهد یافت.بهتر است هر روز 10 تا 20 درصد از زمان خود را برای یک طراحی بهتر سرمایه گذاری کنیم. با این که اتمام کارها و پروژه ها به همین میزان طولانی تر می شود، ولی در بلند مدت قطعا سرعت توسعه ی نرم افزار بیشتر می شود. باید یاد بگیریم بلندمدت فکر کنیم.منبعA philosophy of software design</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Fri, 30 Apr 2021 16:01:35 +0430</pubDate></item><item><title>پیچیدگی در نرم افزار</title><link>https://virgool.ir/@sara.rezaei/پیچیدگی-در-نرم-افزار-reedvkojjjzx</link><description>تولید نرم افزار، یکی از خلاقانه ترین فعالیت های بشر در طول تاریخ است. برنامه نویس ها، به محدودیت هایی مثل قوانین فیزیک مقید نیستند. آن ها می توانند دنیاهای مجازی بی نظیری خلق کنند که هیچ وقت قبل از آن وجود نداشته است. برنامه نویسی نیاز به مهارت های جسمانی یا تناسب فیزیکی خاصی ندارد. تمام آن چیزی که لازم است، یک ذهن خلاق و توانایی سازماندهیِ افکار است.بنابراین، بزرگ ترین محدودیت در تولید نرم افزار، توانایی ما در فهم سیستم هایی است که در حال ساختن آنها هستیم. در طول زمان که نرم افزار تکامل می یابد و ویژگی های جدید به دست می آورد، وابستگی بین اجزاء آن، باعث ایجاد پیچیدگی می شود. رفته رفته، پیچیدگی ها روی هم انباشته می شود و برای برنامه نویس ها، تغییر و نگهداری سیستم سخت تر و سخت تر می شود.این رویه باعث می شود، توسعه ی نرم افزار آهسته تر پیش رود و منجر به تولید خطا می شود. این خطاها، باز هم روند تولید را آهسته تر می کنند و هزینه ها را بالا می برند.این که پیچیدگی، در طی پیدایش یک نرم افزار، به مرور بیشتر می شود، یک امر ناگزیر است. هر چقدر نرم افزار بزرگ تر باشد و تعداد افراد بیشتری روی آن کار کنند، مدیریت این پیچیدگی سخت تر است. این پیچیدگی را نمی توان از بین برد ولی می توان آن را مدیریت کرد. اگر می خواهیم تولید نرم افزار را راحت تر و ارزان تر انجام دهیم، باید راه هایی پیدا کنیم که سادگی را در نرم افزار بیشتر کنیم.بنابراین، هر چه قدر هم که ما تلاش کنیم، در طول زمان، پیچیدگی بیشتر می شود، ولی اگر سادگی را در طراحی خود رعایت کنیم، می توانیم سیستم های بزرگ تر و قدرتمند تری را به وجود آوریم، قبل از این که پیچیدگی غیر قابل کنترل شود.چرا پیچیدگی در نرم افزار مهم است؟نرم افزار، بر خلاف بسیاری از سازه های فیزیکی، مثل ساختمان ها، کشتی ها و پل ها، شکل پذیر است. یعنی در طول زمان زندگی یک سیستم نرم افزاری، بارها و بارها ممکن است در طراحی آن تجدید نظر کنیم.البته همیشه اینطور نبوده، بیشتر تاریخِ برنامه نویسی اینگونه گذشته است که، در شروعِ پروژه، تمرکز روی طراحی بود و در فازهای بعد روی پیاده سازی، تست و نگهداری. به این روش، روش آبشاری (waterfall) گفته می شود و برای سایر رشته های مهندسی مناسب است، اما در حوزه ی نرم افزار به ندرت خوب کار می کند. چرا که  تجسم کردن و فهم همه ی ابعادِ یک سیستم نرم افزاری بزرگ، در ابتدای کار، غیر ممکن است و تا زمانی که نرم افزار عملیاتی و اجرا نشود، مشکلاتِ طراحی اولیه، خود را نشان نمی دهند.در نتیجه، در حال حاضر، بیشتر تیم های نرم افزاری، از رویکرد چابک (Agile) بهره می برند. در این روش، تمرکز اولیه، روی طراحی یک بخش کوچک از تمام عملکردهای سیستم است. این بخش کوچک طراحی، پیاده سازی و ارزیابی می شود. زمانی که مشکلات این طراحی اولیه، مشخص و برطرف شد، تعداد کمی ویژگی های دیگر پیاده سازی می شوند. در هر دوره ی زمانی (iteration)، مشکلات شناسایی شده، برطرف می شوند و ویژگی های جدید با توجه به تجربه های کارهای قبلی طراحی می شوند. از آنجا که ویژگی های جدید بر پایه ی تجربه ی ویژگی های قبلی و مشکلاتِ پیش آمده به وجود می آیند، مشکلات کمتری خواهند داشت.قطعا این رویکرد، برای سازه های فیزیکی مناسب نیست. مثلا در میانه ی ساختن یک پل، نمی توانیم تعداد پایه های آن را تغییر دهیم. ولی در نرم افزار، به علت انعطاف پذیریِ ذاتی آن، بهترین رویکردِ ممکن است.بنابراین، اگر قرار است در بازه ی زمانیِ ساختِ یک نرم افزار، به صورت مرتب، در طراحی آن تجدید نظر کنیم، کاهش پیچیدگی، یکی از موارد مهم است که باید در ذهن داشته باشیم، در غیر این صورت، ممکن است ناگهان با یک سیستم خیلی پیچیده مواجه شویم که نگهداری آن اگر غیرممکن نباشد به سختی امکان پذیر است.پیچیدگی چیست؟پیچیدگی، رفتار یک سیستم یا مدل را، که دارای اجزای متعددی است و این اجزا به چندین روش با هم ارتباط دارند، توصیف می کند. به عنوان مثال مغز انسان یک سیستم پیچیده است، چرا که از میلیون ها نورون تشکیل شده که با هم در ارتباط هستند. همچنین سیستم های حمل و نقل، سازمان های اقتصادی و در نهایت جهان به صورت یک کل، سیستم های پیچیده هستند.در سال 1987، Fred Brooks در مقاله ای با عنوان No Silver Bullet دو نوع پیچیدگی را در نرم افزار مطرح کرد:پیچیدگی ذاتی یا ضروری (Essential complexity)این پیچیدگی، در ذات مساله ای که می خواهیم آن را حل کنیم وجود دارد و قابل حذف شدن نیست. اگر یک قطعه از کد، باید شامل 3 عملکرد باشد، به اندازه ی این 3 عملکرد پیچیدگی خواهد داشت و نمی توانیم مثلا با حذف یکی از این عملکرد ها از پیچیدگی بکاهیم.پیچیدگی تصادفی (Accidental complexity)این نوع از پیچیدگی ناشی از مشکلاتی است که خود برنامه نویس ها باعث می شوند. این پیچیدگی را می توان کمتر یا حذف کرد.در این مقاله، Brooks معتقد است که مقدار زیادی از پیچیدگی های تصادفی نرم افزارها، با روی کار آمدن زبان های سطح بالای برنامه نویسی و معرفی طراحی شی گرا، حذف شده است.در کتاب A philosophy of software design پیچیدگی به این صورت تعریف شده:پیچیدگی، هر چیز مرتبط با یک سیستم نرم افزاری است، که درک و تغییر سیستم را سخت می کند.برای مثال، ممکن است فهم این که یک قطعه از کد چگونه کار می کند دشوار باشد. یا برای بهبود یک بخش کوچک از سیستم نیاز به تلاش بسیار باشد. یا شاید اصلاح یک خطا، بدون دستکاری سایر قسمت های سیستم امکان پذیر نباشد.از منظر دیگر می توان به عنوان هزینه و سود به این مساله نگاه کرد. در یک سیستم پیچیده، هزینه ی زیادی برای یک تغییر کوچک در سیستم پرداخت می شود. در یک سیستم ساده، تغییرات بزرگ با هزینه ی کم اعمال می شوند.پیچیدگی به اندازه ی سیستم ارتباط مستقیم ندارد. ممکن است یک سیستم بسیار کوچک، بسیار پیچیده طراحی شود و یا یک سیستم بزرگ به ساده ترین حالت ممکن. همچنین اگر در یک سیستم بزرگ، بخش های کوچکی که خیلی هم مورد استفاده نیستند پیچیدگی زیادی داشته باشند، نمی توان گفت کل سیستم پیچیده است. در واقع می توان گفت پیچیدگی کل سیستم، شامل مجموعِ پیچیدگی های بخش های مختلف سیستم است، که وزن آن بخش ها در این مجموع لحاظ شده است.نشانه های پیچیدگیپیچیدگی در نرم افزار خودش را به سه شکل نشان می دهد:تشدید تغییر (change amplification) اولین نشانه ی پیچیدگی این است که تغییراتِ خیلی ساده، نیاز به اصلاحات گسترده در کد دارد. به این امر rigidity گفته می شود به این معنا که نرم افزار سخت و صلب است و هر تغییری در آن به دشواری انجام می شود. مفهوم دیگری که به این مساله مرتبط است، شکنندگی (fragility) نرم افزار است. شکننده بودن نرم افزار به این معنی است که با تغییر یک قسمت از کد، بخش های دیگری که هیچ ارتباط منطقی با کدِ تغییر داده شده ندارند، دچار خطا می شوند. اگر نرم افزار شکنندگی بالایی داشته باشد، هر خطایی که اصلاح می شود باعث به وجود آمدن چندین خطای دیگر شده و به نظر می رسد که عدم اصلاح خطاها به صرفه تر است!بارِ شناختی (cognitive load)بارِ شناختی یعنی این که یک برنامه نویس، برای انجام دادن یک تسک، چقدر باید بداند. اگر نرم افزار طوری طراحی شده باشد، که برای انجام یک کار ساده، افراد ناچار شوند بخش های زیادی از نرم افزار را کشف کنند و نسبت به آن ها دانش به دست آورند، این نشانه ای از پیچیدگی بالاست.بار شناختی (Cognitive load) عبارتی است که در روانشناسی شناختی استفاده می شود و مفهوم آن، میزان استفاده از حافظه ی کاری (Working memory) در زمان پردازش اطلاعات است. حافظه ی کاری یک حافظه ی کوتاه مدت است، که می تواند 4 تا 5 آیتم را حداکثر برای 10 ثانیه نگه دارد.طراحی خوب در نرم افزار، با محدود کردن میزان مفاهیمی که باید در حافظه ی کاری خود نگه دارید، باعث می شود ایجاد تغییرات در کد ساده تر باشد. در مقابل آن طراحی های پیچیده هستند که تغییرات را بسیار دشوار می کنند. به عنوان نمونه ای از طراحی بد، اگر برای تغییر یک کلاس، باید 9 کلاس دیگر را که از آن استفاده می کنند را بفهمید، نگه داشتن این همه اطلاعات در حافظه ی کاری شما بسیار دشوار است و احتمال این که اشتباه کنید زیاد است.یک روش در طراحی که بار شناختی را کم می کند، کپسوله سازی (encapsulation) است. می توانیم با استفاده از abstraction و interface ها، پیچیدگی های پیاده سازی را مخفی کنیم. در نتیجه برای تغییر کدها، نیازی به دانستن این که client چکونه از این interface استفاده می کند، یا نیازی به دانستن جزئیات پیاده سازی این abstraction ها نداریم. پس مغز ما فقط اطلاعات مهم را پردازش می کند و از ظرفیتِ محدودِ حافظه ی کاری، به بهترین شکل استفاده می شود.یک اشتباه رایج این است که، پیچیدگی می تواند با توجه به تعداد خط های کد اندازه گیری شود و اگر یک پیاده سازی، از پیاده سازی دیگر کوتاه تر است (بر اساس تعداد خط) پس پیچیدگی کمتری دارد. اما این فرضیه، هزینه ی بارِ شناختی را در نظر نمی گیرد. گاهی اوقات برای یک قطعه کد، تمام سعی خود را می کنیم که تعداد خطوط کمتری بنویسیم، ولی در نهایت فهم آن قطعه کد سخت تر می شود، در واقع، قابلیت خوانایی کد (readability) کمتر می شود.برای نمونه این کد:const firstNumb = 100;&#xA;let secondNumb;&#xA;const secondNumb = firstNumb &gt; 50 ? &#34;Number is greater than 50&#34; : &#34;Number is less than 50&#34;در مقایسه با این کد:const firstNumb = 100;&#xA;let secondNumb;&#xA;if (firstNumb &gt; 50) {&#xA;  secondNumb = &#34;Number is greater than 50&#34;&#xA;} else {&#xA;  secondNumb = &#34;Number is less than 50&#34;&#xA;}کد دوم، با وجود تعداد خط های بیشتر، قابلیت خوانایی بالاتری دارد و فهم آن بار شناختی کمتری دارد.ندانسته های ناشناخته (unknown unknowns)سومین نشانه ی پیچیدگی این است که قسمت هایی از کد که باید برای انجام یک تسک مشخص تغییر کنند، واضح نیست. در واقع تا زمانی که نرم افزار بعد از اعمال یک تغییر، دچار خطا نشود، هیچ کس متوجه این ناشناخته ها نمی شود.شاید بحث دانسته ها و ندانسته ها، در ابتدا جمله ی معروف سقراط را به یاد ما می آورد:دانم که ندانم!اما برای اولین بار، ابن یمین، در این شعر به این مساله پرداخت: آنکس که بداند و بداند که بداند / اسب شرف از گنبد گردون بجهاند (known knowns) آنکس که بداند و نداند که بداند / با کوزه ی آب است ولی تشنه بماند  (unknown knowns)آنکس که نداند و بداند که نداند / لنگان خرک خویش به مقصد برساند  (known unknowns)آنکس که نداند و نداند که نداند / در جهل مرکب ابدالدهر بماند (unknown unknowns)دقت کنید که این مورد، با مورد اول یعنی تشدید تغییر تفاوت دارد. در مورد اول، ما می دانیم که یک تغییر، باعث تغییر در چه بخش های دیگری می شود، تمام آن تغییرات (که تعدادشان ممکن است خیلی زیاد باشد) را اعمال می کنیم و سیستم به درستی کار می کند. ولی در این مورد یعنی ندانسته های ناشناخته، حتی نمی دانیم کدام بخش ها از این تغییر متاثر می شوند و تنها زمانی می فهمیم که خطایی رخ بدهد.علت های پیچیدگیحال که نشانه های پیچیدگی را شناختیم، ببینیم چه چیزی باعث آن می شود:وابستگی هادر اینجا منظور از وابستگی، هر نوع ارتباط بین یک کد با کد دیگر است، به این صورت که برای تغییر در یکی، دیگری هم باید تغییر کند. به عنوان مثال، signature یک متد، بین کدی که متد را تعریف کرده و کدی که آن را استفاده می کند، وابستگی به وجود می آورد و مثلا اگر یک پارامترِ اجباری به تعریف متد اضافه شود، تمام کدهایی که از آن استفاده می کنند باید تغییر کنند. همچنین اگر دو قطعه کد به صورت فرستنده و گیرنده عمل کنند، پروتکل ارتباطی بین آن ها یک وابستگی است. اگر فرستنده تصمیم به تغییر پروتکل بگیرد، گیرنده هم باید تغییر کند.قطعا وابستگی ها بخشی از نرم افزار هستند و نمی توانیم آن ها را از بین ببریم. هدف ما به عنوان طراح نرم افزار باید این باشد که تا حد ممکن وابستگی ها را کم کنیم و همچنین آن ها را ساده و واضح طراحی کنیم.وابستگی ها معمولا باعث &#34;تشدید تغییر&#34; و &#34;بار شناختی&#34; بالا می شوند.ابهامابهام زمانی اتفاق می افتد که اطلاعات مهم، واضح و روشن نباشد. مثلا ممکن است نام یک متغیر طوری تعریف شود که با خواندن آن هیچ اطلاعات مفیدی درباره ی آن متغیر به دست نیاوریم. ابهام به وابستگی هم ارتباط دارد، وقتی که وجود یک وابستگی، واضح نیست. مثلا اگر یک نوع پیام جدید به سیستم اضافه شود، ولی برنامه نویس هیچ جا به متن آن پیام دسترسی نداشته باشد.یک روشِ اشتباه برای کم کردن ابهام در نرم افزار، مستندسازی است.  معمولا اگر در طراحی نرم افزار، نیاز به مستندسازی های پرهزینه و زیاد داریم، یعنی طراحی نرم افزار به درستی انجام نشده است، یک طراحیِ خوب، قطعا به مستندسازی کمتری نیاز خواهد داشت.ابهام در نرم افزار، باعث ایجاد &#34;ندانسته های ناشناخته&#34; و &#34;بار شناختی&#34; زیاد می شود.خلاصهپیچیدگی، چیزی نیست که با یک اشتباه بزرگ در سیستم ایجاد شود، بلکه نیازمند انباشته شدن مسائل کوچک  زیادی روی هم است. یک وابستگی یا یک ابهام نمی تواند تاثیر زیادی روی قابلیت نگهداری سیستم داشته باشد. پیچیدگی وقتی زیاد می شود که صدها و هزاران وابستگی و ابهام کوچک در طول زمان در سیستم ایجاد شوند. به همین علت مدیریت پیچیدگی سخت می شود. هر برنامه نویس فرض می کند که این تکه کد کوچک که در حال نوشتن آن است، اگر هم دارای ابهام باشد یا وابستگی غیر ضروری به سیستم تحمیل کند، مشکلی ایجاد نخواهد کرد. ولی مجموع کار برنامه نویس های مختلف که با این رویکرد سیستم را توسعه می دهند، می تواند تبدیل به فاجعه شود.منابعA philosophy of software designNo Silver Bullet</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Wed, 21 Apr 2021 22:30:11 +0430</pubDate></item><item><title>در باب (NET (Core. - قسمت دوم</title><link>https://virgool.ir/@sara.rezaei/dot-net-core-hello-world-kcat2f2bg4rz</link><description>به دنیا سلام کن ...در عنوان و متن این نوشته، اگر جایی کلمه ی core نوشته شده، در پرانتز قرار گرفته، به این علت که از ماه نوامبر سال 2020، مایکروسافت کلمه ی core را حذف کرد و NET 5. را منتشر کرد. ولی به جهت اهمیت تفکیک اسامی، من در این نوشته - بعضی جاها - از عبارت NET Core. استفاده کرده ام تا از NET Framework. جدا شود.برنامه نویس ها، زمانی که قصد یاد گرفتن یک زبان جدید را دارند، با یک برنامه ی خیلی ساده شروع می کنند که تنها یک متن در خروجی نمایش می دهد: Hello Worldاین یک رسم در دنیای برنامه نویسی است، و ما هم می خواهیم به همین رسم، یک کنسول اپلیکیشن ساده، با استفاده از NET Command-Line Interface. یا CLI، بسازیم و deploy کنیم.اگر هیچ آشنایی با NET (Core). ندارید، خواندن قسمت اول به فهم بهتر این مطلب کمک می کند:آشنایی با NET Core.نصب NET SDK.اولین قدم برای شروع، دانلود و نصب NET SDK. است.برای دانلود NET SDK. یک آدرس خیلی سرراست وجود دارد: https://dot.netکلمه SDK کوتاه شده ی عبارت Software Development Kit است. Kit، به مجموعه ای از آیتم ها گفته می شود، که برای انجام کار مشخصی، یا برای رسیدن به یک هدف استفاده می شوند. یکی از Kit های معروف، که همه می شناسیم First Aid Kit است که مجموعه ای از وسایل مورد استفاده در کمک های اولیه ست، که همه در یک بسته قرار گرفته اند.قطعا برای نوشتن نرم افزار هم یک سری ابزار، library، سند، نمونه کد و ... وجود دارد که به برنامه نویس ها اجازه می دهد در یک platform بخصوص، نرم افزار تولید کنند. به همین دلیل، زمانی که می خواهیم شروع کنیم به ایجاد یک نرم افزار تحت یک platform خاص، باید ابتدا SDK مناسب رو تهیه کنیم که ابزار لازم را برای تولید نرم افزار، در اختیارمان بگذارد.با مراجعه به آدرسی که معرفی شد، امکان دانلود SDK برای سیستم عامل های مختلف وجود دارد:ساخت یک کنسول اپلیکیشنیک نمونه برنامه ی Hello World ساده در NET SDK. وجود دارد که دستورالعمل ساخت آن را با هم می بینیم:در این نوشته، برای ساخت و اجرای برنامه ها، از CLI استفاده می کنیم. ولی همه ی این عملیات، با VisualStudio هم قابل انجام است.اگر تصمیم گرفتید که از CLI استفاده کنید، فقط دو ابزار زیر را لازم دارید:1. Command Line (در ویندوز کافی است یک command prompt باز کنید)2. text editor (برای مثال notepad در ویندوز)فرض کنیم می خواهیم یک فولدر به اسم  DotNetConsoleSample ایجاد کنیم و فایل های اپلیکیشن را در آن قرار دهیم. برای این کار می توانیم از امکانات ویندوز برای ساختن فولدر استفاده کنیم، یا این که یک cmd باز کنیم و دستورهای زیر را تایپ کنیم:mkdir DotNetConsoleSample&#xA;cd DotNetConsoleSampleیک فولدر با نام DotNetConsoleSample ساخته شد و working directory را تغییر دادیم به همین فولدر.تا اینجا هنوز با دستورهای dotnet کار نکرده ایم، اولین دستور:dotnet new consoleدستور dotnet new console یک کنسول اپلیکیشن Hello World در فولدر جاری می سازد. اگر محتویات فولدر را نگاه کنیم این دو فایل را می بینیم:Program.csDotNetConsoleSample.csprojمحتویات فایل Program.cs:using System;&#xA;namespace DotNetConsoleSample&#xA;{&#xA;       class Program&#xA;        {&#xA;              static void Main(string[] args)&#xA;              {&#xA;                     Console.WriteLine(&#34;Hello World!&#34;);&#xA;              }&#xA;       }&#xA;}همان طور که مشخص است، این برنامه فقط یک عبارت Hello World نمایش می دهد.و فایل DotNetConsoleSample.csproj: (دقت کنید که فایل csproj همنامِ فولدرِ پروژه است)&lt;Project Sdk=&#34;Microsoft.NET.Sdk&#34;&gt;&#xA;         &lt;PropertyGroup&gt;&#xA;                   &lt;OutputType&gt;Exe&lt;/OutputType&gt;&#xA;                   &lt;TargetFramework&gt;net5.0&lt;/TargetFramework&gt;&#xA;         &lt;/PropertyGroup&gt;&#xA;&lt;/Project&gt;فایل csproj در واقع توصیف کننده ی پروژه است. به صورت پیش فرض، همه ی فایل ها و subfolder هایی که در فولدر پروژه قرار دارند، در این فایل include هستند.اگر قبلا با NET Framework. کار کرده باشید، احتمالا متوجه یک تفاوت مهم شده اید. در NET Framework. لیست همه ی فایل های پروژه، در فایل csproj وجود داشت، در نتیجه اگر پروژه بزرگ بود و تعداد فایل های زیادی داشت، فایل csproj خیلی شلوغ می شد.ولی در NET (Core). تنها در صورتی که فایلی وجود دارد که در بیرون از فولدر پروژه است، باید آن را به فایل csproj. معرفی کنیم.با این حال، هنوز هم یک سری اطلاعات در فایل csproj. قرار می گیرد. برای مثال:1. مشخص کردن نسخه ی framework این برنامه:&lt;PropertyGroup&gt;&#xA;       &lt;TargetFramework&gt;net5.0&lt;/TargetFramework&gt;&#xA;&lt;/PropertyGroup&gt;یا اگر می خواهید چندین نسخه مشخص کنید:&lt;PropertyGroup&gt;&#xA;      &lt;TargetFrameworks&gt;netcoreapp3.1;net462&lt;/TargetFrameworks&gt;&#xA;&lt;/PropertyGroup&gt;2. اگر می خواهید برنامه ای که نوشته اید را به صورت یک package منتشر کنید:&lt;PropertyGroup&gt;&#xA;      &lt;PackageId&gt;ClassLibDotNetStandard&lt;/PackageId&gt;&#xA;      &lt;Version&gt;1.0.0&lt;/Version&gt;&#xA;      &lt;Authors&gt;John Doe&lt;/Authors&gt;&#xA;      &lt;Company&gt;Contoso&lt;/Company&gt;&#xA;&lt;/PropertyGroup&gt;3. برای مشخص کردن فایلی که خارج از فولدر پروژه است:&lt;ItemGroup&gt;&#xA;      &lt;Content Include=&#34;..\Extras\**\*.cs&#34; LinkBase=&#34;Shared&#34;/&gt;&#xA;&lt;/ItemGroup&gt;4. مشخص کردن یک نسخه ی خاص از زبان برنامه نویسی (برای مثال C# preview):&lt;PropertyGroup&gt;&#xA;       &lt;LangVersion&gt;preview&lt;/LangVersion&gt;&#xA;&lt;/PropertyGroup&gt;5. فعال کردن، یا تعیین نوعِ قوانینِ کیفیتِ کد (Code quality rules):&lt;PropertyGroup&gt;&#xA;      &lt;AnalysisMode&gt;AllEnabledByDefault&lt;/AnalysisMode&gt;&#xA;&lt;/PropertyGroup&gt;اگر نمی دانید که analyzer چیست:از NET 5.، مایکروسافت، analyzer های کد را به NET SDK. اضافه کرد. این analyzer ها قبلا هم وجود داشتند ولی در صورت نیاز، باید جداگانه نصب می شدند. در SDK جدید، نیاز به نصب جداگانه نیست و به صورت پیش فرض، فعال هستند.این analyzer ها می توانند هنگام کامپایل یا هنگام ویرایش کد، از دو جهت کد را بررسی کنند و در صورت نیاز خطا یا هشدار نشان دهند:1. سبک نوشتن کد2. کیفیت کداین ها چند نمونه از آیتم ها و ویژگی های فایل csproj. بود. لیست کامل را اینجا ببینید:https://docs.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-propsدستورهای dotnetما تا اینجا فقط از دستور dotnet new console برای ساخت یک کنسول اپلیکیشن استفاده کردیم، ولی دستور dotnet new امکانات دیگری هم دارد. برای نمونه:dotnet new classlib            Creates a Class library.dotnet new wpf                  Creates a WPF Application.dotnet new worker             Creates a Worker Service.dotnet new mstest             Creates an Unit Test Project.dotnet new xunit                Creates a xUnit Test Project.dotnet new web                 Creates a ASP.NET Core Empty.dotnet new mvc                 Creates a ASP.NET Core Web App.dotnet new angular           Creates a ASP.NET Core with Angular.dotnet new grpc                Creates a ASP.NET Core gRPC Service.dotnet new webconfig      Creates a Web Config.dotnet new sln                  Creates a Solution File.لیست کامل را اینجا ببینید:https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-newدستور restoreبرای restore کردن package ها از فرمان زیر استفاده می کنیم:dotnet restoreهمچنین، CLI با اجرای دستورهای زیر، به صورت اتوماتیک، restore هم اجرا می کند:dotnet newdotnet builddotnet build-serverdotnet rundotnet testdotnet publishdotnet packاگر نمی دانید منظور از restore کردن پکیج ها چیست:یک ابزار مهم که هر platform برنامه نویسی باید داشته باشد، ابزاری است که به برنامه نویس ها اجازه دهد کدهایی را که می نویسند به اشتراک بگذارند. یعنی اگر یک برنامه نویس، یک کد reusable و مفید می نویسد، که احتمالا برای بقیه قابل استفاده است، آن را pack کرده و به صورت یک package، در اختیار بقیه قرار دهد. این package ها معمولا شامل کد کامپایل شده (dll) و یا سایر فایل های مرتبط با اون کد هستند.برای NET.، مایکروسافت یک مکانیزم اشتراک گذاری کد فراهم کرده، به اسم NuGet، که همه ی کارهای مربوط به ساخت پکیج ها و استفاده از آن توسط دیگران را انجام می دهد. NuGet یک repository مرکزی دارد که همه ی package ها آنجا قرار دارند و می توانند توسط برنامه نویس های NET. استفاده شوند.مثلا اگر نیاز داشته باشیم که برای Serialize کردن object ها، از Newtonsoft.Json استفاده کنیم این دستور را اجرا می کنیم:dotnet add package Newtonsoft.Jsonبا اجرای این دستور، پکیج مربوط به Newtonsoft.Json به پروژه اضافه شده و می توانیم از امکانات آن استفاده کنیم.بعد، هربار که دستور dotnet restore را اجرا می کنیم، NuGet دو فایل را برای پیدا کردن dependency ها چک می کند:فایل csproj.فایل packages.configبعد از پیدا کردن dependency ها، آن ها را، به همراه وابستگی های آن ها، دانلود کرده و داخل یک فولدر قرار می دهد. این که از کجا دانلود کند و در چه فولدری ذخیره کند، همه قابل configure کردن هستند.در نتیجه دیگر نیازی به دانلود dll مربوط به کامپوننت هایی که نیاز داریم اضافه کردن آنها به reference های پروژه نیست. فقط برای اضافه کردن dependency ها به پروژه از یک دستور استفاده می کنیم و با هر بار build یا run ، نسخه ی مناسبِ این پکیج -اگر لازم باشد- restore شده و امکانات آن قابل استفاده است. از این جا به بعد، همه ی کارهای مربوط به package ها، مثل مدیریت آپدیت شدن نسخه ها و ... را NuGet انجام می دهد.اجرای برنامه:برای اجرای برنامه، کافیست در cmd این دستور را اجرا کنیم:dotnet runنگران بیلد شدن برنامه نباشید، dotnet ، هر زمان لازم باشد بیلد را انجام خواهد داد.با این حال، دستور build هم وجود دارد:dotnet buildبعد از اجرای dotnet run، برنامه باید عبارت Hello World را نمایش دهد: ساخت یک وب اپلیکیشنبرای برنامه های وب هم، مثل کنسول اپلیکیشن، یک نمونه برنامه ی Hello World وجود دارد. این برنامه وقتی اجرا شود، یک سرویس HTTP می سازد، که در پاسخ به یک GET Request، عبارت Hello World را برمی گرداند.دستور ساختن یک وب اپلیکیشن:dotnet new webبعد از اجرای این دستور، علاوه بر فایل های Program.cs و csproj.، یک فایل نام Startup.cs هم ساخته می شود. ASP.NET برای مدیریتِ نحوه ی پاسخگویی به request ها، از این کلاس استفاده می کند.در این برنامه، Program.cs دو وظیفه بر عهده دارد:وظیفه ی start کردن وب سرور وظیفه ی start کردن وب اپلیکیشنفایل Program.cs:public class Program&#xA;{&#xA;      public static void Main(string[] args)&#xA;      {&#xA;            CreateHostBuilder(args).Build().Run();&#xA;      }&#xA;      public static IHostBuilder CreateHostBuilder(string[] args) =&gt;&#xA;            Host.CreateDefaultBuilder(args)&#xA;            .ConfigureWebHostDefaults(webBuilder =&gt;&#xA;            {&#xA;                  webBuilder.UseStartup&lt;Startup&gt;();&#xA;            });&#xA;}همانطور که مشخص است، وقتی سرور شروع به کار می کند، از کلاس برای start کردن اپلیکیشن، از کلاس Startup استفاده می کند.اگر برای این برنامه، دستور زیر را اجرا کنیم:dotnet runوب اپلیکیشن روی port مشخص، آماده ی جواب دادن به request هاست. کافی است browser را باز کرده و به آدرس مشخص شده در تصویر بالا مراجعه کنیم:پابلیش اپلیکیشنطراحی NET (Core). به گونه ای است که، deploy کردن اپلیکیشن، به سادگیِ کپی کردن چند فایل باشد.در NET CLI. این امکان وجود دارد، که همه ی binary های مورد نیاز برای پابلیش اپلیکیشن، در یک فولدر جمع آوری شود. کافی است دستور زیر را اجرا کنیم:dotnet publish -c Releaseدقت کنید که در دستور بالا، Release نام فولدری است که می خواهید فایل های برنامه در آن قرار بگیرند. این فایل ها را روی یک سیستم دیگر که NET SDK. روی آن نصب است کپی کنید و سپس دستور زیر را (از همان فولدر) اجرا کنید:dotnet DotNetConsoleSample.dllبه این روشِ پابلیش، framework-dependent deployment گفته می شود. یعنی وقتی فایل ها را روی سیستمِ مقصد کپی می کنیم، برای اجرای برنامه، باید NET SDK. روی آن سیستم موجود باشد.اگر می خواهیم بدون نیاز به NET SDK. برنامه را اجرا کنیم باید از روش self-contained application استفاده کنیم. ابتدا باید در فایل csproj. مشخص کنیم که نرم افزار روی چه runtime ای قابلیت اجرا دارد:&lt;Project Sdk=&#34;Microsoft.NET.Sdk&#34;&gt;&#xA;  &lt;PropertyGroup&gt;&#xA;    &lt;OutputType&gt;Exe&lt;/OutputType&gt;&#xA;    &lt;TargetFramework&gt;net5.0&lt;/TargetFramework&gt;&#xA;    &lt;RuntimeIdentifiers&gt;win10-x64;osx.10.11-x64;linuxmint.17.1x64&lt;/RuntimeIdentifiers&gt;&#xA;  &lt;/PropertyGroup&gt;&#xA;&lt;/Project&gt;و دستور زیر را اجرا می کنیم:dotnet publish -c Release -r linuxmint.17.1-x64با این روش، تمام فایل های مورد نیاز برای اجرای برنامه، شامل dll های dotnet، در فولدر مشخص شده قرار می گیرند. همچنین اگر پروژه شامل package خاصی باشد، فایل های آن هم در این فولدر کپی خواهد شد.بنابراین این فولدر، می تواند روی هر ماشینی کپی شده و بدون نیاز به SDK اجرا شود.مشکل محیط توسعه نرم افزارگاهی نرم افزارهایی می نویسیم که وابسته به محیط اجرا هستند. یعنی برنامه نویس روی سیستم خودش، یک سری فایل، تنظیمات و ابزار دارد، توسعه ی نرم افزار و تست آن را روی همین محیط انجام می دهد و همه چیز به درستی کار می کند. بعد از پابلیش، همان نرم افزار، هر جا که مستقر می شود باگ دارد.وقتی مسئول تست، سیستم را تست می کند و باگ پیدا می کند، و یا وقتی مدیر، در مورد یک باگ از برنامه نویس سوال می پرسد، یک جمله ی معروف هست که هیچ کس مایل نیست بشنود: &#34;روی سیستم من درست کار می کند!&#34;این جمله هیچ ارزشی ایجاد نمی کند، چون هیچ کاربری قرار نیست به سیستم برنامه نویس متصل شود و از برنامه استفاده کند. نرم افزار باید ثبات داشته باشد و روی محیط های مختلف، از مرحله ی تست تا عملیاتی شدن به درستی کار کند. برای تحقق این امر، باید محیط توسعه ی مناسب فراهم باشد.طراحی NET (Core). به گونه ای است که به هیچ چیزی در بیرون از خودش وابسته نیست و هر آنچه که نیاز دارد، درون خودش هست. به همین دلیل با containerها کاملا سازگار است، پس روی همان container که کار توسعه را انجام می دهیم، کار تست و عملیات هم قابل انجام است. پس قرار نیست یک خطا را روی محیط عملیاتی ببینیم ولی روی محیط توسعه اثری از آن نباشد، چون همه ی تنظیمات و بستر اجرایی، روی محیط توسعه و عملیات، یکی است.اگر نمی دانید container چیست:container یک واحد است، که همه اجزای یک اپلیکیشن، شامل کد و dependency ها را در خودش بسته بندی می کند. این &#34;بسته بندی&#34;، هر جا که اجرا شود، رفتار یکسانی خواهد داشت. از لپ تاپِ برنامه نویس، تا یک دیتاسنتر اختصاصی، یا cloud.در نتیجه برنامه نویس، فقط به پیاده سازیِ logic نرم افزار و وابستگی هاش فکر خواهد کرد، تیم استقرار هم فقط به deployment می پردازد و دغدغه ای در خصوص جزئیات پیاده سازی، مثل نسخه های مختلف و config ها نخواهد داشت. پابلیش روی یک container داکر:همانطور که در قسمت قبل دیدیم، پابلیش روی Linux، macOS و Windows، به سادگی امکان پذیر است. کافی است محتویات فولدرِ publish را روی سیستم مقصد کپی کنیم.برای داکر هم همان روش به درستی کار می کند، تنها تفاوت اینجاست که باید یک container بسازیم.برای نوشتن برنامه با NET. اولین کاری که کردیم نصب SDK بود.هنگام کار با Docker، کافیست دستور زیر را اجرا کنیم تا image مربوط به dotnet را دریافت کنیم. این image شامل NET SDK. است:docker run -it microsoft/dotnet:latestحالا برای ساختن container:یک فایل text به نام Dockerfile در فولدری که اپلیکیشن آنجا قرار دارد (همانجایی که فایل .csproj هست) بسازید. محتویات این فایل باید به این صورت باشد:FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base&#xA;COPY bin/Release/net5.0/publish/ /root/&#xA;ENTRYPOINT dotnet /root/DotNetConsoleSample.dllدر باکس بالا، دقت کنید که bin/Release/net5.0/publish/ آدرس جایی است که محتویات اپلیکیشن بعد از پابلیش آن جا قرار دارد و DotNetConsoleSample.dll نام dll مربوط به برنامه است.بعد از ساخت و ذخیره ی فایل، دستور زیر را اجرا می کنیم تا container ساخته شود:docker build -t dotnetconsolesample .این دستور از Dockerfile ای که ساختیم استفاده می کند تا container را بسازد. حالا باید در لیست image های Docker، این image را ببینید:dotnetconsolesampleبرای اجرای برنامه در Docker، دستور زیر را اجرا می کنیم:docker run -it dotnetconsolesampleاجرای برنامه با استفاده از docker runپابلیش وب اپلیکیشن روی container:برای پابلیش وب اپلیکیشن روی یک container، باید مقداری تنظیمات انجام دهیم:در فایل Program.cs webBuilder&#xA;.UseUrls(&#34;http://*:5000/&#34;)&#xA;.UseStartup&lt;Startup&gt;();با استفاده از متد UseUrls، مشخص می کنیم که سرور باید روی چه آدرس و port مشخصی، به request ها پاسخ دهد.فایل Dockerfile به صورت زیر خواهد بود:FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base&#xA;COPY bin/release/net5.0/publish/ /root/&#xA;EXPOSE 5000/tcp&#xA;ENTRYPOINT dotnet /root/DotNetWebSample.dllدستور Expose، به Docker می گوید که container در هنگام اجرا، باید به کدام port شبکه گوش بدهد.بعد از ذخیره کردن Dockerfile، دستورهای زیر را به ترتیب اجرا می کنیم:dotnet publish -c releasedocker build -t dotnetwebsample .docker run -it -p 5000:5000 dotnetwebsample و روی آدرس http://localhost:5000 می تونیم Hello World را مشاهده کنیم.پکیج کردن پروژهاگر می خواهید اپلیکیشن خود را به صورت یک پکیج در اختیار دیگران قرار دهید یک دستور ساده برای این کار وجود دارد:dotnet pack -c Releaseبعد از اجرای این دستور، یک فایل با نام DotNetConsoleSample.1.0.0.nupkg ساخته می شود. این فایل یک NuGet Packge است و امکان به اشتراک گذاشته شدن کد شما را فراهم می کند.تا اینجا ما با دستورهای زیر کار کردیم:dotnet new consoledotnet new web dotnet restoredotnet builddotnet rundotnet publishdotnet packبه جز این ها، دستورهای دیگری هم توسط CLI قابل اجراست:dotnet clean:  Clean build outputs.dotnet msbuild:  Provides access to the MSBuild command line.dotnet sln: Options to add, remove, and list projects in a solution file.dotnet test: Runs tests using a test runner.dotnet add reference: Adds a project reference.dotnet add package: Adds a NuGet package.لیست کامل را اینجا ببینید:https://docs.microsoft.com/en-us/dotnet/core/tools/dotnetخلاصه:در این نوشته، یک شروع ساده با NET. را طی مراحل زیر توضیح دادیم:نصب NET SDK.کار با دستورهای ابتدایی NET CLI.ساخت یک console application و یک web applicationپابلیش اپلیکیشن ها و اجرای آن هامنابع:.Net Core in Action | Dustin Metzgardocs.microsoft.com</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Fri, 26 Mar 2021 21:03:32 +0430</pubDate></item><item><title>در باب NET Core. - قسمت اول</title><link>https://virgool.ir/@sara.rezaei/بسوی-net-core-قسمت-اول-hwe6o5vkp8yp</link><description> از ماه نوامبر سال 2020، مایکروسافت پسوند Core را حذف کرد و NET 5. را عرضه کرد. در این مقاله، برای تاکید روی تفاوت NET Framework. و NET. (نسخه ی آخر NET Core. سابق)، از عبارت NET Core. استفاده خواهد شد.یاد گرفتن یک framework جدید برای توسعه ی نرم افزار، هزینه های زیادی دارد. باید یاد بگیریم که چطور بنویسیم، build کنیم، تست کنیم، پابلیش بدهیم و نگهداری کنیم.بنابراین تصمیم گیری در زمینه ی مهاجرت به یک platform جدید، نیازمند بررسی مزایای آن است. در این نوشته از دو جهت این مساله بررسی خواهد شد:1. اگر تا الان با NET Framework. کار می کردید:قطعا به نفع شماست که به NET Core. مهاجرت کنید. البته احتمالا بعضی از قسمت های کد که وابسته به component های ویندوز هستند، به درستی کار نمی کنند و باید بازنویسی شوند.اما موارد زیر باعث می شود مهاجرت به صرفه باشد:نرم افزار، قابلیت cross-platform شدن خواهد داشت. یعنی روی سیستم عامل های مختلف امکان اجرا دارد.امکان استفاده از ASP.NET Core وجود دارد، که از نظر پرفورمنس بسیار بهتر از ASP.NET Framework عمل می کند.احتمالا تمام نوآوری های مایکروسافت، از این به بعد، روی NET Core. خواهد بود. از آنجا که مایکروسافت به ازای هر تغییری که روی NET Framework. می دهد باید نیم نگاهی به نسخه های قبلی داشته باشد، پس هر تغییر و بهبود روی NET Framework. هزینه دارد. بنابراین تغییر روی آن کمتر خواهد بود و بیشتر به سمت BugFix خواهد رفت. پس ویژگی های جدید، اکثرا روی NET Core. پیاده سازی خواهد شد.فاصله ی بین release ها کوتاه تر خواهد بود. قبلا، اگر خطایی روی NET Framework. پیدا می شد، برنامه نویس ها باید حداقل یک سال برای fix شدن bug صبر می کردند. ولی روی NET Core. به خاطر open source بودن و حذف وابستگی ها (تمام library هایی که روی framework بود جدا شده و برای دسترسی به آن ها باید از NuGet استفاده کرد) عملیات release، ساده تر هست.2. اگر هیچ وقت با NET. کار نکرده اید:باز هم به نفع شماست که NET Core. را انتخاب کنید. با این که که NET.، بعد از cross-platform شدن، با رقبای زیادی دست و پنجه نرم می کند، ولی هنوز هم برگ های برنده ای دارد:زبان برنامه نویسی C# قابلیت های بسیاری دارد.تمام تجربیات مایکروسافت در مورد NET Framework. ، روی طراحی و توسعه ی NET Core. تاثیر گذاشته، پس این فقط یک framework جدید و خام نیست. بلکه یک نسخه ی بهبود یافته از یک framework است که سال ها کار کرده و امتحان خود را پس داده است.پرفورمنس NET Core. در مقایسه با NET Framework. بسیار بهتر است. تیم CLR مایکروسافت، چندین سال روی GC و JIT کار کرده، و NET Core. هم روی همین بستر بنا شده است. همچنین طبق برنامه های اعلام شده، این تیم روی پرفورمنس NET Core. باز هم کار خواهد کرد و باید منتظر بهبود بیشتری در این زمینه باشیم.تعریف NET Core. :بهتر است ابتدا با NET Framework. شروع کنیم:مایکروسافت، اوایل سال 2002، اولین نسخه ی NET Framework. را منتشر کرد. یک framework توسعه ی نرم افزار، که فقط با windows کار می کند و امکان مدیریت حافظه، امنیت، مدیریت خطا (Exception handling) و بسیاری امکانات دیگر را فراهم می کند. همچنین تعداد زیادی library دارد که انجام کارهای بسیاری، از  parse کردن xml تا http request را برای برنامه نویس ها راحت تر می کند. علاوه بر این ها، وجود زبان میانی یا IL، باعث می شود زبان های مختلف تحت NET. بتوانند از library های یکدیگر استفاده کنند.همه ی این موارد در NET Core. هم وجود دارد.در سال 2016، مایکروسافت همزمان با تصاحب Xamarin، از NET Core. رونمایی کرد. همان موقع به این فکر افتاد که شاید بتوان کاری کرد، که همه ی نرم افزارهایی که با Net. پیاده سازی می شوند، بتوانند library ها را به اشتراک بگذارند. بنابراین Net Standard Library. و Common infrastructure معرفی شدند.هر سه DotNET Framework و DotNET Core و Xamarin، یک استاندارد به اسم DotNET Standard Library را پیاده سازی می کنندویژگی های مهم NET Core.دسترسی وسیع تر به library ها:با استفاده از NET Core. می توان با NET Standard Library.، اپلیکیشن یا library هایی توسعه داد که روی همه ی پلتفرم ها قابلیت اجرا داشته باشد. یعنی همان library که روی تبلت یا موبایل اجرا می شود، روی دسکتاپ، web server یا به عنوان یک backend service هم قابل استفاده باشد.در واقع به جای این که برای IOs و Android و Windows، اپلیکیشن های جداگانه توسعه بدهیم، می توانیم یک اپلیکیشنِ قابل استفاده روی تمام این platform ها بنویسیم.استقرار (Deployment) ساده روی پلتفرم های مختلف:قبلا، نصب محصولات مایکروسافت روال پیچیده ای داشت. کامپوننت های COM، رجیستری، فولدرهای خاص، GAC، که همگی مخصوص ویندوز بودند. NET Framework. هم به این ساختار وابسته است. به همین دلیل در زمانِ نصبِ یک نرم افزار که با NET Framework. نوشته شده، Installer باید از وجودِ نسخه ی مناسب NET Framework. روی سیستم عامل اطمینان حاصل کند. و اگر نسخه ی مناسب نصب باشد هم، باز ممکن است بعضی ویژگی های مورد نیاز نرم افزار، مثل IIS یا کامپوننت هایWCF موجود نباشند. همچنین ممکن است برنامه نویس، زمان تست نرم افزار با یک Patch از NET Framework. کار کرده باشد، ولی در محیط عملیاتی، Patch دیگری موجود باشد و عملکرد نرم افزار دچار مشکل شود.ولی در صورتی که از NET Core. استفاده کنیم، طراحی ماژولار به کمک ما خواهد آمد. یعنی فقط وابستگی هایی را که مورد نیاز نرم افزار هستند، در همان folder که فایل های نرم افزار قرار می گیرد قرار می دهیم. پس عملیاتِ پابلیش، فقط شامل کپی کردن یک folder در سیستم مقصد است! این همان مفهومی است که مایکروسافت، xcopy-deployable می نامد.یک مزیت دیگه آن هم این است که امکان اجرای چند نسخه از نرم افزار، در کنار هم، روی یک سرور به راحتی مهیا است.کانتینرها و cloudسیستم های could، سعی می کنند با استفاده از سخت افزار کمتر، به مشتری های بیشتری سرویس دهند. قبلا، برای استقرار یک نرم افزار در سیستم cloud، از virtual machine استفاده می شد. این روش مشکلاتی داشت: سایز بالای فایل های virtual machineزمانِ طولانیِ startupاستفاده ی زیاد از memory (کل سیستم عامل باید در memory بارگزاری شود)ناسازگاری (با این که امکان کپی کردن virtual machine روی یک host دیگر وجود دارد، ولی هیچ تضمینی نیست که درست مثل قبل رفتار کند)کانتینرها این مشکل را با virtualize کردن سیستم عامل حل کرده اند. در نتیجه کانتینر، فقط شامل اپلیکیشن و وابستگی های آن می باشد. بنابراین سایز فایل ها خیلی کمتر و startup خیلی سریع تر است، فقط اپلیکیشن در memory بارگزاری می شود و کانتینر روی هر host که قرار بگیرد، دقیقا مثل قبل عمل می کند.طراحی NET Framework. به گونه ای بوده که با کانتینتر ها خوب کار نمی کند ولی NET Core. به دلیل طراحی ماژولار، مناسب قرار گرفتن در یک کانتینر و پابلیش شدن هست.پرفورمنس بالای ASP.NET Coreدر سال 2002، اولین نسخه ی ASP.NET برای ساختن اپلیکیشن های وب منتشر شد. با وجود موفقیت بالا (که در پروژه های بزرگ مثل Stack Overflow استفاده شد) ولی به علت پرفورمنس پایین، مورد استفاده ی برنامه نویس های محدودی قرار گرفت.---از طرفی، یک کمپانی به نام TechEmpower وجود دارد که چند ماه یک بار، یک benchmark از framework های تحت وب منتشر می کند. در خروجی های این کمپانی، ASP.NET جایی نداشت، چون این benchmark در لینوکس اجرا می شود ولی NET Framework. با ویندوز سازگار است. البته چند بار ASP.NET MVC را وارد این benchmark می کنند، ولی آنقدر پرفورمنس پایینی داشته (50 هزار RPS در مقابل 320 هزار RPS که مربوط به Node.js بوده است) که در بین رقبا حرفی برای گفتن نداشته است.برای ASP.NET Core، این عدد به 1 میلیون RPS رسید.کدباز بودن (Open source)قبلا، مایکروسافت، درباره ی اطلاعات مربوط به محصولات و فیچر های در حال توسعه ی خود خیلی بسته عمل می کرد. دلایل خوبی هم برای این رویکرد وجود دارد:  رقبا نمی توانستند همزمان با مایکروسافت روی این ویژگی ها و محصولات کار کنند. چون تا زمانی که مایکروسافت آن فیچر را عرضه نکرده بود، هیچ کس اطلاعی از آن نداشت.اگر پیاده سازی یک فیچر طول می کشید و به زمان بندی نمی رسید، هیچ کس متوجه این تاخیر نمی شد.مایکروسافت همیشه در کنفرانس ها اخبار جدید برای انتشار داشت.ولی برنامه نویس های مدرن آنقدر صبور نیستند که برای یک فیچر، تا release بعدی (که شاید یک سال طول بکشد) صبر کنند. مخصوصا با وجود پروژه های open source بسیاری که پاسخگوی نیازشان است. بنابراین، مایکروسافت تصمیم به تغییر رویکرد گرفت.در اولین قدم، source مربوط به NET Framework. منتشر شد. چند سال است که این source روی این لینک و  GitHub در دسترس است.ولی برای برنامه نویس ها، فقط دیدن کد کافی نیست، آن ها تمایل دارند در طراحی و توسعه مشارکت کنند.به همین دلیل، کد NET Core. روی GitHub در دسترس عموم قرار گرفت و هر کسی امکان ثبت درخواست pull روی آن دارد. همچنین تیم ASP.NET Core هر هفته اینجا جلسه های standup برگزار می کند. بنابراین مایکروسافت یک رویکرد کاملا جدید در برابر open source در پیش گرفت.امکان استفاده از ابزارهای گوناگوناز آنجا که NET Core. روی پلتفرم های بسیاری کار می کند، امکان کار کردن با command-line اهمیت پیدا کرد، برای مثال، برای کار کردن با لینوکس و یا کانتینرهای Docker تنها ابزاری که در اختیار داریم یک terminal است. بنابراین NET Command-Line Interface. یا CLI به وجود آمد. در نتیجه، علاوه بر این که می توانیم از IDE قدرتمندی مثل Visual Studio برای توسعه نرم افزار استفاده کنیم، می توانیم با یک text editor ساده مثل emacs توسعه دهیم و با CLI کد را build و run کنیم.خلاصهصنعت نرم افزار پیوسته در حال تکامل است، از متدولوژی تا framework تا ابزار، همه در حال بهتر شدن هستند. برای این محیط رقابتی، NET Framework. خیلی صلب و انعطاف ناپذیر است ولی NET Core. به همه ثابت کرد که مایکروسافت، هنوز هم، در توسعه ی نرم افزار مدرن، قابل رقابت با دیگران است.خلاصه ی مزایایی که درباره ی NET Core. گفته شد:قابلیت نوشتن library هایی که می توانند روی framework ها و سیستم عامل های گوناگون کار کنندامکان deployment ساده روی کانتینرهاقابلیت نوشتن وب اپلیکیشن با performance بالا با استفاده از ASP.NET Coreپشتیبانی قوی CLI، در نتیجه برنامه نویس ها محدود به ابزار خاصی نشده و می توانند از ابزار مورد علاقه ی خود برای توسعه ی نرم افزار، استفاده کنند.منبع:.Net Core in Action | Dustin Metzgar</description><category>سارا رضائی</category><author>سارا رضائی</author><pubDate>Fri, 12 Mar 2021 19:01:38 +0330</pubDate></item></channel></rss>