<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های Hossein Mobasher</title>
        <link>https://virgool.io/feed/@hoseinmobasher</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-07-07 14:16:01</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/189466/avatar/avatar.png?height=120&amp;width=120</url>
            <title>Hossein Mobasher</title>
            <link>https://virgool.io/@hoseinmobasher</link>
        </image>

                    <item>
                <title>الگوی LMAX Disruptor</title>
                <link>https://virgool.io/@hoseinmobasher/disruptor-pattern-ztd3ly7sqmed</link>
                <description>مقدمهیکی از دغدغه‌هایی که سیستم‌های مختلف همواره با آن دست و پنجه نرم می‌کنند، ارتباط بین تولید کننده و مصرف کننده پیغام‌ها و رویدادهاست. رعایت نکردن اصول همروندی در این سیستم‌ها منجر به کاهش کارآیی، از دست رفتن داده و افزایش زمان عملکرد آن‌ها می‌شود. نرم‌افزارهایی که به مدیریت ارتباطات شبکه‌ای بین نقاط مختلف شبکه می‌پردازند، مثالی از این نوع نرم‌افزارهاست که بایستی با کمترین هزینه‌ی زمانی، دریافت و ارسال داده را انجام دهند. این نوع از نرم‌افزارها در سامانه‌های مختلف بانکی استفاده می‌شوند.با توجه به این‌که ارتباط داده‌ای بین سامانه‌های مختلف بانکی در لایه‌ی چهارم شبکه (برای مثال پروتکل TCP) انجام می‌شود، توجه نکردن به زیرساخت درست شبکه‌ای و کیفیت ابزارهای مورد استفاده ممکن است منجر به قطعی‌های گاه و بیگاه شبکه بانک و تحمیل هزینه‌های گزاف و جرایم مختلف به بانک (مانند جرایم شتابی) و بالطبع شرکت‌های ارائه دهنده‌ی زیرساخت بانکی شود.در بخش بعد، مساله‌ی مدیریت ارتباط و مسیریابی بین نقاط مختلف شبکه‌ای مطرح و پاسخی عمومی به این مساله ارائه می‌شود. در بخش‌های دیگر، مشکلات مختلف این پاسخ عمومی بررسی و در نهایت به شرح الگوی Disruptor پرداخته می‌شود.طراحی اولیهدر نگاه کلی، عمل‌کرد این نرم‌افزار بایستی به گونه‌ای باشد که از یک نقطه‌ی شبکه، بسته‌ای را دریافت و با توجه به منطق مسیریابی به نقطه‌ی بعدی ارسال کند. از این رو، می‌توان این ساختار را به شکل زیر تصور کرد:تولید بسته‌های ۱ و ۲ از نقاط ۳ و ۴ و مصرف آن در نقاط ۲ و ۱طبق آن‌چه که در این شکل دیده می‌شود، تعدادی بسته به ترتیب وارد صف شده تا آماده‌ی مسیریابی شوند و به مقصدهای مطلوب ارسال شوند. پس استفاده از ساختار صف در تعامل مشترک با چند تولید کننده‌ی ورودی (نقطه ۳ و ۴) و مصرف کننده‌ی خروجی (نقطه ۱ و ۲) می‌تواند یک پاسخ به این مساله باشد. در اینجا نکته‌ای که باید به آن توجه کرد، هم‌زمانی در تولید و مصرف است.در این صورت باید مکانیزمی برای کنترل هم‌زمانی در نظر گرفت که در ساده‌ترین حالت استفاده از ساختارهای مختلف هم‌روندی (بلوک‌های هم‌زمانی، قفل کردن متغیرها و انحصار متقابل) است، در حالی‌که خود یکی از عوامل کاهش کارآیی یک نرم‌افزار است و در نرم‌افزارهای با زمان پاسخ پایین بایستی تا حد ممکن از آن‌ها اجنتاب کرد.در ادامه‌ی این بحث به بیان الگوی Disruptor پرداخته می‌شود که مشکل کارآیی یاد شده، یکی از چند فاکتوری است که در طراحی این الگو مورد توجه قرار گرفته است. این الگو (کتابخانه) که در گروه LMAX - شرکتی که با هدف مبادله‌ی مالی کارآ تاسیس شده است - توسعه داده شده است، یک ساختار داده‌ی صف حلقوی ارائه می‌دهد که در محیط‌های هم‌روند می‌تواند مورد استفاده قرار گیرد.مسأله‌ی هم‌زمانیمسأله‌ی هم‌زمانی وقتی پیش می‌آید که چند کار به صورت هم‌زمان در حال اجرا باشند و بخواهند تغییراتی را روی یک منبع مشترک (پایگاه داده، فایل، متغیری در حافظه و سوکت ارتباطی) ایجاد کنند یا بخواهند از تغییرات داده شده مطلع شوند.در حال حاضر دو روش برای این کار وجود دارد، اولی به کارگیری روش قفل کردن منابع در زمان تغییر است که منجر به ایجاد یک وقفه روی نخ در حال اجرا در سطح هسته‌ی سیستم عامل می‌شود که هزینه‌ی زمانی بسیار بالایی دارد. روش دوم استفاده از دستورات Compare And Swap است. در پردازنده‌های مدرن، یک روش جایگزین برای کار با حافظه وجود دارد که در آن مقدار جدید و مقدار قدیمی که روی آن عملیات انجام شده است، به پردازنده داده می‌شود. در صورت تطابق مقدار قدیمی با مقدار فعلی حافظه، مقدار جدید در آن حافظه اعمال می‌شود و در غیر این‌صورت عملیات لغو و به برنامه اطلاع داده می‌شود تا مجدد مقدار حافظه خوانده شده و عملیات اجرا شود.برخلاف قفل کردن در سطح هسته‌ی سیستم عامل، در روش CAS، عملیات به گونه‌ای محدود می‌شوند که در لحظه یک عملیات انجام شود. بالطبع اگر عملیاتی که در ناحیه‌ی بحرانی از کد شما اتفاق می‌افتد، پیچیده باشد، انجام آن به صورت CAS بسیار پیچیده و در عمل نشدنی است. این روش برای عملیات ساده‌ای مانند افزایش و کاهش تک مقدار مورد استفاده قرار می‌گیرد. پیاده‌سازی این روش در قالب کلاس‌های Atomic در جاوا دیده می‌شود.طبق بررسی‌های انجام شده روی پردازنده‌ی Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz و اجرای ۵۰۰ میلیون عملیات جمع، تاخیرهای زیر به دست آمد:تاخیرهای ایجاد شده در محیط‌های یک یا چند نخی با تصادم و بدون تصادمهمان‌طوری که از خروجی جدول بالا مشخص است، به کارگیری مکانیزم قفل منجر به ایجاد بیش‌ترین تأخیر در عملیات می‌شود. به صورت کلی، در مواجهه با مسأله هم‌زمانی، بهره‌گیری از روشی ایده‌آل است که در لحظه فقط یک نخ در حال نوشتن منبع مشترک باشد و باقی نخ‌ها در کمترین زمان ممکن از تغییرات منبع مشترک مطلع شوند. در پردازنده‌های مدرن، مطلع شدن نخ‌ها از تغییرات منبع مشترک با استفاده از دستورالعمل‌های Memory Barrier قابل انجام است که در سطوح بالاتر و در پیاده‌سازی Mutex و Semaphore از آن استفاده می‌شود.خواندن از حافظه و نوشتن در آننکته‌ی پنهانی که در مسأله‌ی مطرح شده به ندرت مورد توجه قرار می‌گیرد، مسأله‌ی خواندن از حافظه و نوشتن در آن است. برای روشن‌تر شدن این مسأله، گریزی به مفهوم Cache Line در پردازنده می‌زنیم. پردازنده در تعامل با حافظه، هیچ‌گاه بلوکی به اندازه یک byte یا word را استفاده نمی‌کند و معمولاً از یک خط داده‌ای که بین ۳۲ تا ۲۵۶ بایت (معمولاً ۶۴ بایت) است، استفاده می‌کند.در تعامل پردازنده و حافظه، زمانی اختلال در کارآیی پیش می‌آید که دو نخ بخواهند روی دو متغیری که در یک Cache Line قرار می‌گیرند، بنویسند. در این صورت تصادم در نوشتن پیش می‌آید. به این پدیده False Sharing می‌گویند. برای روشن‌تر شدن این مسأله، به آزمون زیر توجه کنید.فرض کنید که در پردازنده‌ای با مشخصات Core i5 (I5-5257U) 2.7GHz،  تعداد ۵۰۰ میلیون عملیات نوشتن را در فضای چند نخی (بین ۱ تا ۴ نخ) روی چند متغیر (بین ۱ تا ۴ متغیر) و در حالی‌که همه‌ی متغیرها در Cache Line های یکسان یا مختلف باشند، انجام دهیم. در این صورت خروجی زیر را خواهیم داشت.بررسی اثر False Sharingبرای حصول اطمینان از این که دو متغیر در یک Cache Line نیستند، یک کلاس تعریف کنید و متغیرهای خود را در آن جای دهید و کلاس را به نحوی پر کنید که مضرب صحیحی از اندازه Cache Line باشد. برای مثال، اگر یک داده‌ی ۴ بایتی (long) در جاوا داشته باشیم، در این صورت ساختاری مشابه زیر خواهیم داشت.public final static class ContentedLong {
    public volatile long value = 0L;
    private volatile long p1, p2, p3, p4, p5, p6, p7; // not-used (padding)
}همچین شما می‌توانید از نشانه‌ی Contended در جاوا استفاده کنید که کدتان به شکل زیر در خواهد آمد:public final static class ContentedLong  {
    @sun.misc.Contended
    public volatile long value = 0L;
}با رسم نمودار برای خروجی آزمون انجام شده، دیده می‌شود که در حالتی که False Sharing اتفاق می‌افتد، هر چه تعداد نخ‌ها زیاد می‌شود، زمان انجام عملیات نیز به صورت خطی بالا می‌رود. به عبارتی به ازای هر نخ بیش‌تر، تعداد تصادم‌ها افزایش می‌یابد. حال اگر در انجام عملیات، از رخ دادن False Sharing جلوگیری کنیم، افزایش تعداد نخ‌ها تأثیری در زمان انجام عملیات نخواهد داشت.تاثیر اثر False Sharing در افزایش تصادم در محیط‌های چند نخیمشکلات استفاده از صفمجدد به پاسخ مطرح شده برای مسأله‌ی ابتدایی برمی‌گردیم. استفاده از صف برای کنترل ترافیک ورودی و خروجی دارای چند مشکل جدی است که در ادامه به شرح آن‌ها می‌پردازیم.- عموما برای ایجاد صف از ساختار داده‌ی لیست پیوندی یا آرایه استفاده می‌شود. اگر بیشینه طول صف نامحدود باشد، در این صورت به ازای هر ورودی، یک نمونه از داده‌ی صف ساخته و به آن اضافه می‌شود. حال اگر نرخ ورودی نسبت به نرخ خروجی بیش‌تر باشد، در این صورت برنامه دچار کمبود حافظه و شکست می‌شود. از این رو، بایستی اندازه‌ی صف محدود شود. (استفاده از آرایه یا حافظه با تضمین اندازه‌ی مشخص)- پیاده‌سازی صف نیازمند رفع تصادم نوشتن در ابتدا، انتها و طول صف است. به صورت کلی، با توجه به نرخ تولید یا مصرف، در اغلب اوقات صف‌ها در وضعیت نسبتاً پر یا نسبتاً خالی هستند که این موضوع منجر به افزایش میزان تصادم می‌شود.- در زبان‌های مبتنی بر بازیافت حافظه (مانند جاوا)، ساختار داده‌ی صف جزو منابع اصلی تولید اشیای بازیافتی در نرم‌افزار می‌باشند.الگوی LMAX Disruptorالگوی Disruptor یک صف حلقوی هم‌روند است که سعی کرده تا نقاط ضعف بیان شده در این مقاله را برطرف کند تا با ارائه‌ی یک ساختار داده‌ی مؤثر برای تخصیص حافظه و توجه به حافظه‌ی نهان پردازنده، آماده‌ی اجرا در سخت‌افزارهای مدرن باشد.تخصیص حافظههمان‌طوری که اشاره شد، ناهماهنگی نرخ ورودی به صف و خروجی از آن، منجر به استفاده‌ی بیش از حد از حافظه می‌شود که خود همین موضوع باعث طولانی‌تر شدن فرآیند بازیابی حافظه (چه به صورت دستی و چه با فرآیند بازیافت حافظه) می‌شود. از این رو، در نرخ بالای ورودی و خروجی روش مناسبی به نظر نمی‌رسد. همچنین در الگوریتم‌های بازیافت حافظه مانند CMS / G1، چون یک شئ چند نسل باید از ساخته شدن آن بگذرد تا بازیافت شود، از این رو هزینه‌ی جابه‌جایی شئ بین نسل‌های جدید و قدیم طولانی می‌شود و منجر به تأخیر در عمل‌کرد نرم‌افزار و افزایش زمان Stop the World بازیافت حافظه می‌شود.در Disruptor، یک بافر حلقوی (با پیاده‌سازی آرایه محدود) در نظر گرفته شده است. قبل از استفاده از این ساختار داده، تمام حافظه‌ی مورد نیاز این بافر حلقوی تخصیص می‌یابد، بنابراین قابلیت استفاده مجدد دارد. به کارگیری آرایه‌ی محدود و تخصیص حافظه در همان ابتدا، منجر به نزدیک به صفر شدن زمان GC در زبان‌های با قابلیت بازیافت حافظه می‌شود.نکته‌ی بعدی در استفاده از آرایه (حافظه ترتیبی) به جای لیست پیوندی، در نحوه‌ی تعامل پردازنده در زمان خواندن این حافظه است. در دسترسی پردازنده به اولین خانه‌ی آرایه، خانه‌های بعدی آرایه نیز به حافظه‌ی نهان پردازنده انتقال می‌یابند، از این رو سرعت دسترسی به باقی خانه‌ها به مراتب سریع‌تر و کارآمدتر خواهد بود. در حالی‌که این موضوع به دلیل فاصله دار بودن خانه‌های لیست پیوندی امکان‌پذیر نیست.تصادمدر صف‌های محدود همواره تصادم در زمان نوشتن در صف (ابتدای صف) و خواندن از صف (انتهای صف) وجود دارد. اگر فضای مسأله به شکلی است که نیاز به یک تولید کننده دارید، در این حالت تصادمی در ابتدای صف ایجاد نمی‌شود ولی برای فضای چند تولید کننده، تولید‌کننده‌ها بایستی بر سر گرفتن خانه‌ی بعدی که باید در آن بنویسند با هم بجنگند که برای حل این مشکل می‌توان از دستورالعمل CAS استفاده کرد.زمانی‌که داده وارد صف شد، این بار نوبت به مصرف‌کننده‌ها می‌رسد تا ورودی جدید را تصاحب کنند. برای کنترل این تصادم، دو راه پیش رو داریم. راه اول، استفاده از دستورات صلب قفل کردن منابع و راه دوم، به کارگیری حلقه‌های شرطی است که تا زمانی که داده‌ی جدید از راه برسد، مصرف کننده در حلقه می‌ماند. اگر درگیر نگه‌داشتن پردازنده برای شما هزینه‌ی بالایی دارد، از این رو استفاده از روش اول توصیه می‌شود و در صورتی‌که تأخیر پایین برای شما اهمیت بیش‌تری دارد، استفاده از روش دوم مناسب‌تر خواهد بود. تمام این استراتژی‌ها در الگوی Disruptor پیاده‌سازی شده است.ترتیب‌گذاریساختار ترتیبی روشی است که در Disruptor به کار گرفته شده است تا هم‌روندی مدیریت شود. در این ساختار دو ترتیب پایه‌ای برای خواندن و نوشتن وجود دارد. به شکل زیر توجه کنید:وضعیت ترتیب‌های نوشتن (توسط تولید کننده) و خواندن (توسط مصرف کننده) زمانی‌که یک تولید‌کننده بخواهد در صف بنویسد، بایستی ابتدا مقدار بعدی ترتیب نوشتن را دریافت (claimedSequence) و متناظر با آن در یک خانه‌ی صف حلقوی بنویسد. بعد از آن‌که عملیات نوشتن تمام شد، بایستی مقدار Cursor به آخرین خانه‌ی قابل خواندن تغییر یابد. در صورتی که یک تولید‌کننده داشته باشیم، ترتیب نوشتن را می‌توان با یک متغیر ساده و به روزرسانی ترتیب خواندن را با یک حلقه‌ی درگیر (Busy Spin) مانند زیر مدیریت کرد.long expectedSequence = claimedSequence – 1;
while (cursor != expectedSequence);
cursor = claimedSequence;با توجه به موضوعیت ترتیب در نوشتن و خواندن، زمانی مقدار cursor به مقدار claimedSequence تغییر می‌‌کند که cursor در خانه‌ی ماقبل آن باشد. برای مثال، فرض کنید که دو تولید کننده داشته باشیم و تولید‌کننده‌ی اول ترتیب ۱ و تولید‌کننده‌ی دوم ترتیب ۲ را گرفته باشند. در این صورت ابتدا بایستی تولید‌کننده‌ی ۱ مقدار cursor را به روز کند و سپس تولید‌کننده‌ی ۲ (حتی اگر تولید‌کننده‌ی دوم زودتر کارش را به اتمام برساند.)اثر دسته‌ایزمانی که مصرف‌کننده منتظر تغییر cursor به مقدار جدید باشد، ممکن است که در فاصله‌ی بین دو بررسی مقدار cursor، مقدار آن چند گام جلوتر رفته باشد، در این صورت مصرف‌کننده می‌تواند بدون درگیر شدن با مشکلات هم‌روندی تمام این خانه‌ها را پردازش کند. این پردازش دسته‌ای که با تاخیر بسیار پایین همراه است، منجر به ثابت ماندن رفتار سیستم در مقابل هجمه‌ی ورودی از تولید کننده می‌شود و بدون توجه به میزان بار ورودی، انتظار در صفِ نسبتا ثابتی در وضعیت‌های مختلف باری خواهیم داشت.گراف وابستگیبه صورت کلی، به کار گرفتن صف برای ارتباط بین تولید کننده و مصرف کننده یکی از راه‌حل‌های موثر است اما زمانی‌که این ارتباط از حالت خطی به یک وابستگی گرافی تبدیل می‌شود و پاس‌کاری داده‌ای بین بخش‌های مختلف سیستم زیاد شود، در این صورت ممکن است منجر به ایجاد تاخیر و تبدیل شدن صف به گلوگاه ارتباطی شود. طبق بررسی‌های انجام شده روی الگوی LMAX Disruptor، از آن‌جایی که این الگو ارتباط بین مصرف‌کننده و تولید‌کننده را از هم جدا کرده و راهکاری بدون وقفه ارائه داده است، تبدیل شدن ارتباط خطی به گرافی، تاثیر چندانی در عملکرد سیستم نخواهد داشت و منجر به ایجاد گلوگاه نخواهد شد.بررسی کارآییبرای بررسی کارآیی این روش، آزمایشی ترتیب دیده شد که در آن ۵۰ میلیون پیام در فاصله‌های زمانی ثابت (۱ میلی‌ثانیه) به صف حلقوی و به صورت مشابه در ArrayBlockingQueue تزریق شد. برای این آزمایش محیطی با پردازنده Core-i7 2720QM 2.2GHz، سیستم عامل Ubuntu 11.04 و نسخه‌ی ۱.۶.۰.۲۵ جاوا در نظر گرفته شد. طبق خروجی به دست آمده، میزان تاخیر ایجاد شده در این صف حلقوی حدود ۵۲ نانوثانیه و برای ArrayBlockingQueue به ۳۲۷۵۷ نانوثانیه رسید و دلیل این اختلاف، استفاده از روش‌های معمول مدیریت هم‌روندی (مانند قفل کردن منابع) بود.جمع‌بندیدر این مقاله به معرفی ساختار داده‌ای برای مدیریت مؤثر تعامل بین تولید کننده و مصرف کننده‌ی پیغام‌ها و رویدادها پرداخته شد. این ساختار داده که به الگوی Disruptor معروف است، برای رفع مشکلات ناشی از استفاده از صف‌های معمول برای ذخیره ورودی یا خروجی داده طراحی شده است. از نکات طراحی دیده شده در این الگو می‌توان به استفاده از صف حلقوی به جای صف خطی نامحدود، تخصیص حافظه در زمان ساخت صف، جلوگیری از ایجاد پدیده False Sharing، استفاده از حلقه‌ی درگیر به جای روال‌های رایج هم‌روندی و ترتیب‌گذاری در نوشتن و خواندن هم‌روند در صف، بدون استفاده از روال‌های صلب هم‌روندی اشاره کرد.طبق آزمون طراحی شده، تاخیر ایجاد شده بین تولید و مصرف داده در این الگو در مقایسه با ساختارهای مرسوم (پیاده‌سازی ArrayBlockingQueue) بسیار پایین و حدودا ۱/۱۰۰۰ شد. پیکربندی درست این الگو در محیط‌هایی که دغدغه‌ی جابه‌جایی داده بین ماژول‌ های مختلف داخلی وجود دارد، می‌تواند منجر به کاهش هزینه‌ی زمانی شود.منابعhttps://lmax-exchange.github.io/disruptor/disruptor.htmlhttp://robsjava.blogspot.ru/2014/03/what-is-false-sharing.htmlhttps://en.wikipedia.org/wiki/Compare-and-swaphttps://shipilev.net/talks/jvmls-July2013-contended.pdfhttps://martinfowler.com/articles/lmax.htmlhttps://www.f5.comhttps://stackoverflow.com/a/12065801/860721</description>
                <category>Hossein Mobasher</category>
                <author>Hossein Mobasher</author>
                <pubDate>Mon, 22 Feb 2021 23:48:05 +0330</pubDate>
            </item>
                    <item>
                <title>چارچوب‌های وارونگی کنترل و الگوهای آن</title>
                <link>https://virgool.io/@hoseinmobasher/%DA%86%D8%A7%D8%B1%DA%86%D9%88%D8%A8%E2%80%8C%D9%87%D8%A7%DB%8C-%D9%88%D8%A7%D8%B1%D9%88%D9%86%DA%AF%DB%8C-%DA%A9%D9%86%D8%AA%D8%B1%D9%84-%D9%88-%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7%DB%8C-%D8%A2%D9%86-ypqomih3zbie</link>
                <description>اگر در فضای کسب‌وکار تجاری قرار گرفته باشید، یکی از مشکلاتی که احتمالا به آن برخورده‌اید، مساله‌ی نگه‌داری و پشتیبانی از پروژه‌های جاری است. در این پروژه‌ها، وجود کدهای وصله پینه‌ای و بدون ساختار، توسعه‌ی سلیقه‌ای بخش‌های مختلف، کدهای مبتنی بر ساده‌ترین راه‌حل‌ها و کدهای حاصل از تغییرات در زمان کوتاه بسیار شایع هستند. این گونه پروژه‌ها بسیاری از نشانه‌های کد بد را دارند و به مرور زمان تبدیل به کد مرده می‌شوند. یکی از مسائلی که در این کسب‌وکارها نمود زیادی دارد، مسأله‌ی توسعه‌ی سریع و چابک است.از آن‌جایی که در این پروژه‌ها، کد، بدساختار یا در خوش‌بینانه‌ترین حالت بی‌ساختار است، از این رو اضافه کردن یک قابلیت جدید یا تغییر در روش اجرای یک کسب و کار خرد در آن‌ها، زحمت بسیاری را به تیم توسعه تحمیل می‌کند. همان‌طوری که اشاره شد، یکی از ناملایماتی که در این پروژه‌ها با آن روبه‌رو هستیم، مسأله‌ی انتشار تغییرات حاصل از اضافه کردن یک قابلیت جدید به سیستم است و ساختار صلب طراحی‌های اشتباه در پروژه‌های بزرگ، معضلی است که منجر به این مسأله می‌شود.مسأله: سامانه پایش - Monitoring Systemفرض کنید برای یک مجموعه‌ی کسب و کاری، نیاز به طراحی یک سامانه پایش هستید. در این صورت، بایستی جوانب مختلف نرم‌افزاری و سخت‌افزاری آن کسب و کار پایش شود و در صورتی‌که رفتاری نامتعارف در جایی از مجموعه دیده می‌شود، اعلان‌های مختلفی به مدیران و افراد مرتبط داده شود. سرویس اعلان را در این‌جا NotificationService می‌نامیم. این سرویس باید بتواند پیام‌های مناسب را در زمان‌های مناسب ایجاد و به کاربران مرتبط ارسال کند. روش‌های مختلفی برای ارسال پیام وجود دارد که در این بخش به دو روش ارسال پیام از طریق پیامک و ایمیل اشاره خواهیم کرد. مصرف‌کننده‌های مختلفی که نیاز به ارسال پیام دارند، بسته به نوع نیاز، می‌توانند یکی از این روش‌ها را انتخاب و استفاده کنند. انتخاب یک روش در دنیای شئ‌گرا به معنای نمونه‌سازی از آن کلاس و استفاده از قابلیت ارسال پیام آن است.فرض کنید که سرویس اعلان یک نمونه از ارسال پیام کوتاه ایجاد و استفاده کند. در این حین، ممکن است بسته به کسب و کار جاری پروژه، نیاز به تغییر استراتژی ارسال پیام به وجود آید. در این صورت، چه باید کرد؟ در صورتی‌که ما تنها دو سرویس ارسال با پیام و ارسال با ایمیل را داشته باشیم، در این صورت بایستی جاهایی از کد که ارجاع به سرویس ارسال با پیام دارند با سرویس ارسال با ایمیل تعویض شوند که بسیار هزینه‌بر و نیاز به استقرار مجدد پروژه دارد!وارونگی کنترل - Inversion of Controlبزرگترین عیب در تفکر بالا، انتشار تغییراتی است که منجر به تحمیل فشار و هزینه‌ی زیاد به پروژه و کسب و کار می‌شود. از این رو به بیان اصل DIP از SOLID می‌پردازیم. با توجه به اصل DIP، بایستی وابستگی بین کلاس‌ها به سطح انتزاع – کلاس‌های abstract یا interface ها - برسد و کلاس‌ها نباید به هم وابستگی سطح پایین –کلاس‌های concrete - داشته باشند. از این رو استفاده از الگوهایی مانند Strategy بسیار مهم و حیاتی به نظر می‌رسد. همان‌طوری که در الگوی استراتژی اشاره شده است، بایستی الگوریتم‌های با امضای مشترک - در این‌جا ارسال پیام - زیر یک انتزاع مشترک – در این‌جا interface - تعریف شوند. از این رو ساختار اولیه‌ی زیر شکل می‌گیرد.public interface MessageSender {
    void sendMessage(String message, String destination);
}حال دو روش بیان شده به شکل زیر قابل پیاده‌سازی هستند.ارسال پیام کوتاهpublic class SMSMessageSender implements MessageSender {
    private SMSProvider provider; // Service of SMS provider company
    public void sendMessage(String message, String destination) {
        // Validation, Log, ...
        provider.sendSMS(message, destination);
    }
}ارسال ایمیلpublic class EmailMessageSender implements MessageSender {
    private EmailProvider provider; // OS service to send email
    public void sendMessage(String message, String destination) {
        // Validation, Log, ...
        provider.sendEmail(&amp;quotEmail Title&amp;quot, message, destination);
    }
}با توجه به تغییرات انجام شده، کافیست که مصرف‌کننده‌ها وابستگی خود را به سطح MessageSender برسانند تا اصل DIP رعایت شود. استفاده از این وابستگی نیازمند تخصیص یکی از دو روش بالاست. دو راه حل برای انتخاب روش پیاده‌سازی وجود دارد. اولی ساخت شئ پیاده‌ساز توسط هر کلاس مصرف‌کننده و دومی برون سپاری تولید شئ پیاده‌ساز است.قبل از آن‌که به ادامه‌ی بحث قبل بپردازیم، ابتدا اصل برنامه‌نویسی Inversion of Control (وارونگی کنترل) را بیان می‌کنیم. در برنامه‌نویسی سنتی، برنامه‌نویس با استفاده از دستورات شرطی، حلقه‌ها، فراخوانی توابع مختلف و غیره، مسیر کامل اجرای برنامه‌ را مشخص می‌کرد و نیازهای مختلف برنامه را با تولید کتابخانه‌های مختلف و استفاده از آن‌ها پاسخ می‌گفت. نمونه‌ای از این برنامه‌ها، برنامه‌ی مبتنی بر خط فرمان زیر است که کاربر مشخص می‌کند که چه زمانی، چه اطلاعاتی دریافت و نمایش داده شود. از این رو کنترل کامل جریان توسط کدی که برنامه‌نویس نوشته است، مدیریت می‌شود.Scanner scanner = new Scanner(System.in);
System.out.println(&amp;quotEnter firstName:&amp;quot);
String firstName = scanner.nextLine();
System.out.println(&amp;quotEnter lastName:&amp;quot);
String lastName = scanner.nextLine();
System.out.printf(&amp;quotYour name is %s   %s&amp;quot, firstName, lastName);
scanner.close();بعد از ظهور چارچوب‌های رابط کاربری گرافیکی، کنترل اجرایی برنامه به آن‌ها سپرده شد و تغییرات داده حاصل از گرفتن اطلاعات از کاربر از طریق یک سری رخداد به برنامه اطلاع داده می‌شد. به عبارتی کنترل جریان اصلی از کد برنامه‌نویس به چارچوب گرافیکی مورد استفاده، معکوس شده بود و این‌که چه زمانی، چه رخدادی اتفاق خواهد افتاد، حاصل تعامل بین کاربر نهایی و برنامه بود.حال اگر به مثال ارسال پیام برگردیم، در روش اول، در صورتی‌که روش دیگری – مانند ارسال پیام از طریق واتزاپ – به سیستم اضافه شود، در این صورت تغییرات در تمام مصرف‌کننده‌هایی که می‌خواهند از این روش استفاده کنند منتشر می‌شود. همچنین یک آگاهی اضافی به مصرف‌کننده‌ها در مورد پیاده‌سازی‌ها داده می‌شود که به آن نیازی ندارند. از این رو روش مناسبی به نظر نمی‌رسد. طبق آن‌چه که در مورد جریان کنترلی گفته شد، در این روش، کنترل جریان برنامه به خود کلاس مصرف کننده سپرده شده است که حاصل آن انتشار تغییرات و آگاهی اضافی - وابستگی بین کلاس‌های مصرف کننده و کلاس‌های پیاده‌ساز - است.در روش دوم، کلاس‌های مصرف کننده فرض می‌کنند که شئ پیاده‌ساز به آن‌ها داده می‌شود و تمرکز آن‌ها فقط پیاده‌سازی کسب و کار خود خواهد بود. از طرفی، مسئولیت تولید شئ پیاده‌ساز و تزریق آن به کلاس‌های وابسته، به یک ساختار دیگر سپرده می‌شود. در این روش، کنترل ساخت و ارائه‌ی اشیاء معکوس شده و به ساختار جانبی – IoC Container - واگذار شده است.تزریق وابستگی - Dependency Injectionهمان‌طوری که گفته شد، وظیفه‌ی کنترل اشیاء به IoC Container ها سپرده می‌شود. اما چگونه؟ این Container ها یک سری قرارداد وضع می‌کنند تا با رعایت این قراردادها در سرویس‌های مختلف، ماژول Assembler (ماژول تزریق) بتواند پیاده‌سازی مناسبی از یک انتزاع را در جای مناسب تزریق کند. در شکل زیر ارتباط بین ماژول تزریق با سرویس اعلان و ارسال پیام دیده می‌شود. نمودار وابستگی سامانه پایش با Assemblerطبق این نمودار، سرویس اعلان به انتزاع ارسال پیام وابستگی دارد و ماژول تزریق با علم بر پیاده‌سازی‌های ارسال پیام، به ساخت و تزریق پیاده‌ساز مناسب در سرویس اعلان می‌پردازد. از آن‌جایی که این چارچوب‌ها فقط مناسب‌ترین پیاده‌سازی یک انتزاع را پیدا می‌کنند، نام Inversion of Control برای آن‌ها بسیار کلی و مبهم به نظر می‌رسد. از این رو ارائه‌دهندگان این چارچوب‌ها تصمیم گرفتند تا نام Dependency Injection را برای این چارچوب‌ها استفاده کنند. در ادامه به معرفی انواع مختلف تزریق وابستگی خواهیم پرداخت.تزریق از طریق سازنده کلاساولین روش، استفاده از سازنده‌ی کلاس (Constructor Injection) است که در آن، تمام وابستگی‌های یک کلاس به عنوان آرگومان‌های ورودی به آن داده می‌شود.private MessageSender sender;
public MonitoringService(MessageSender sender) {
    this.sender = sender;
}در این روش، ماژول تزریق برای ساخت شئ، از کلاس‌های پیاده‌ساز موجود استفاده می‌کند و آن‌ها را به سازنده‌ی کلاس‌ها ارسال می‌کند. معرفی پیاده‌سازها به تزریق‌کننده می‌تواند با استفاده از یک فایل  پیکربندی (مانند spring-bean.xml)  یا قرارداد داخل کد (مانند Spring Annotation) اتفاق بیفتد. در چارچوب Spring، از هر دوی این روش‌ها برای معرفی اشیاء و تزریق آن‌ها استفاده می‌شود.تزریق از طریق توابع Setterروش Setter Injection مشابه روش اول است ولی برای تزریق وابستگی‌ها در محل وابستگی توابع set برایشان تعریف می‌شود. در زیر یک نمونه دیده می‌شود.private MessageSender sender;
public void setSender(MessageSender sender) {
    this.sender = sender;
}ماژول تزریق با اسکن کردن کلاس‌ها، در صورتی‌که وابستگی را تشخیص دهد، از تابع  set آن استفاده می‌کند و وابستگی را تزریق می‌کند. چارچوب Spring این حالت را نیز به خوبی پشتیبانی می‌کند.تزریق از طریق Interface Injectionدر این روش انتزاع‌هایی برای تزریق وابستگی‌ها ساخته می‌شود و هر جایی که نیاز به وابستگی‌ها باشد، این انتزاع‌ها مورد استفاده قرار می‌گیرند. برای مثال، interface زیر برای تزریق شئ از جنس T مورد استفاده قرار می‌گیرد.public interface Injectable&lt;T&gt; {
void inject(T t);
}حال کافیست تا این  interface در کلاس‌های وابسته اضافه و از شئ ارسال شده به آن استفاده شود. این بخش از کار،‌ مشابه روش دوم است.public class MonitoringService implements Injectable&lt;MessageSender&gt;
    private MessageSender sender;
    @Override
    public void inject(MessageSender sender) {
        this.sender = sender;
    }تنها بخش از حلقه‌ی گمشده در این روش، نحوه‌ی ایجاد اشیاء تزریق شونده است. تزریق وابستگی در این روش با کمک گرفتن از الگوی طراحی Visitor انجام می‌شود و کلاس‌های پیاده‌ساز بایستی این انتزاع عمومی را پیاده‌سازی کنند. نمونه‌ی این انتزاع در interface زیر دیده می‌شود.public interface Injector {
    void inject(Object target);
}در زیر نحوه‌ی اضافه شدن این interface به پیاده‌ساز دیده می‌شود.public class SMSMessageSender implements MessageSender, Injector
    @Override
    public void inject(Object target) {
        ((Injectable&lt;MessageSender&gt;) target).inject(this);
    }ماژول تزریق از این پیاده‌سازی‌ها برای تزریق وابستگی‌ها استفاده می‌کند. در زیر یک استفاده از این روش دیده می‌شود:Container container = new Container();
container.registerComponent(&amp;quotMessageSender&amp;quot, SMSMessageSender.class);
container.registerComponent(&amp;quotMonitoringService &amp;quot, MonitoringService.class);
container.registerInjector(Injector.class, container.lookup(&amp;quotMessageSender&amp;quot));
container.start();این قطعه کد از دو بخش اصلی تشکیل شده است. بخش اول، معرفی کلاس‌های پیاده‌ساز و مصرف‌کننده و بخش دوم، معرفی سرویس Injector به Container است. پس از این که Container شروع به کار کند، با فراخوانی تابع inject کلاس SMSMessageSender سرویس MonitoringService را مقداردهی می‌کند.از بین روش‌های معرفی‌شده، روش تزریق از طریق سازنده، روش بسیار مطمئن‌تری است. زیرا زمانی یک شئ ساخته می‌شود که تمام وابستگی‌های آن فراهم باشد، در غیر این‌صورت شئ ساخته نخواهد شد و برنامه قابل اجرا نخواهد بود. در روش دوم و سوم، شئ همواره ساخته می‌شود ولی ممکن است یکی از وابستگی‌های آن ساخته نشود و در این صورت شئ ناقصی خواهیم داشت که رفتار قطعی نشان نخواهد داد. در چارچوب Spring می‌توان از @Required یا @Autowired برای فراخوانی حتمی توابع set استفاده کرد ولی در این‌صورت کدتان وابسته به چارچوب خواهد شد. همچنین در روش دوم و سوم، ایجاد ارجاع چرخشی - Circular Reference - به صورت ضمنی و غیر قابل تشخیص است و ممکن است منجر به تولید خطاهای غیرقابل پیش‌بینی شود در حالی‌که در روش اول این موضوع صریح است و منجر به خطای زمان تفسیر می‌شود.الگوی Service Locatorهمان‌طوری که در بخش‌های قبلی دیده شد، استفاده از تزریق وابستگی باعث شد تا وابستگی سرویس اعلان به پیاده‌سازهای ارسال پیام حذف شود. اما Dependency Injection تنها راه برای حذف وابستگی نیست و روش دیگری به نام Service Locator نیز قابل استفاده است که به صورت اجمالی مورد بررسی قرار می‌گیرد.ایده‌ی اصلی الگوی Service Locator، شئ‌ای است که در صورت تقاضای یک سرویس از آن، نمونه‌ای از سرویس درخواستی را بر می‌گرداند. ساده‌ترین حالت پیاده‌سازی یک Service Locator، استفاده از الگوی Singleton است. در این روش، یک شئ ایستا از یک سرویس در Service Locator ساخته می‌شود و بعد از مقداردهی، در بخش‌های دیگر قابل استفاده است. شرایطی ممکن است پیش بیاید که نیاز به چند نمونه از یک سرویس را داشته باشید، در این صورت می‌توان از الگوی Registry استفاده کرد.بعضی اوقات شنیده می‌شود که الگوی Service Locator قابلیت تست ندارد و نمی‌توان اشیاء محیط تست و عملیاتی را در آن تفکیک کرد. از آن‌جایی که Service Locator تنها یک محل ذخیره‌سازی برای یک سری نمونه است، این مورد چندان قابل قبول نیست. شما می‌توانید این تفکیک و تعویض اشیاء را به دو روش زیر در محیط‌های مختلف انجام دهید. برای هر دو روش کافی است که تابع پیکربندی configure به پیاده‌سازی این الگو اضافه شود. در روش اول، با اضافه کردن فایل‌های پیکربندی مجزا (برای محیط‌های تست و عملیاتی) و پیاده‌سازی مناسب تابع configure، می‌توان نمونه‌های منطبق بر آن، فایل پیکربندی را تولید کرد. در روش دوم، از کلاس Service Locator زیرکلاس گرفته می‌شود و به ازای محیط‌های مختلف، پیاده‌سازی‌های متفاوتی از configure و سایر متدهای مرتبط انجام می‌شود.پیاده‌سازی متداولاین الگو، از بخش‌های اصلی Service Locator، Cache و Initializer تشکیل می‌شود. جزء Initializer در واقع یک Factory Method است که نام سرویس را می‌گیرد و شئ‌ای متناظر برای آن سرویس می‌سازد. Cache بخشی است که اشیاء ساخته شده در Initializer را نگه می‌دارد تا از ساخت مجدد آن‌ها جلوگیری کند و مجدد مورد استفاده قرار دهد.بخش Service Locator، هسته‌ی اصلی این الگو است که شئ‌ای از Cache را در خود دارد و متد getService آن، ابتدا در Cache دنبال سرویس می‌گردد و در صورتی‌که سرویس تا به حال ساخته نشده باشد از Initializer Factory استفاده می‌کند تا شئ را ساخته و برگرداند. در شکل زیر نمودار وابستگی مساله سامانه پایش با استفاده از الگوی Service Locator دیده می‌شود.نمودار وابستگی مسأله سامانه پایش با ServiceLocatorکلام آخردر این مقاله سعی شد تا وارونگی کنترل و الگوهای کاربردی آن معرفی و با بیان مسأله‌ای ملموس مورد بررسی قرار بگیرد. از جمله فوایدی که با به کارگیری این الگو ایجاد می‌شود، می‌توان به نکات زیر اشاره کرد:جدا شدن اجرای یک سرویس از پیاده‌سازی آنسهولت در تغییر پیاده‌سازی سرویس مورد استفاده (چه به صورت آنلاین و چه به صورت آفلاین)افزایش تست‌پذیری کد نهایی و تزریق سرویس‌های Mock به جای سرویس‌های اصلیبا توجه به این‌که استفاده از این الگو منجر به فواید اشاره شده می‌شود ولی زیاده‌روی در آن و توجه نکردن به ریزه‌کاری‌ها ممکن است منجر به کاهش Readability و Maintainability پروژه شود. از این رو، توصیه می‌شود تا قبل از به کارگیری هر چارچوب تزریق وابستگی، نکات مثبت و منفی آن‌را مورد مطالعه قرار دهید.منابعMartin, Fowler. 2004. Inversion of Control Containers and the Dependency Injection pattern.Crusoveanu, Loredana. 2020. Intro to Inversion of Control and Dependency Injection with Spring.Martin, Robert C. 1996. The Dependency Inversion Principle.Erich Gamma, John Vlissides, Richard Helm, Ralph Johnson. 1994. Design Patterns: Elements of Reusable Object-Oriented Software.</description>
                <category>Hossein Mobasher</category>
                <author>Hossein Mobasher</author>
                <pubDate>Fri, 01 Jan 2021 12:41:35 +0330</pubDate>
            </item>
                    <item>
                <title>نشانه‌های کد بد (Symptoms of bad code)</title>
                <link>https://virgool.io/@hoseinmobasher/symptoms-of-bad-code-hwjnxa9dv5g4</link>
                <description>مفاهیم مربوط به طراحی خوب یا بد بسیار نسبی هستند. با رعایت برخی استانداردها در برنامه‌نویسی، می‌توان یک نرم‌افزار کارا، قابل نگهداری و قابل تست تولید کرد. برای مثال، در برنامه‌نویسی شی­گرا با رعایت کردن مفاهیم پایه‌ای (ارث بری، کپسوله کردن و چند ریختی)، الگوهای مختلف طراحی و اصول SOLID می‌توان یک طراحی خوب از کد ارائه داد. البته توجه به این نکته ضروری است که در برخی موارد ممکن است استفاده‌ی بیش از حد از این مفاهیم یا بی‌توجهی به آن‌ها، منجر به طراحی­های مشکل‌دار شود.در این مقاله به بیان نشانه‌های یک طراحی بد خواهیم پرداخت و موارد زیر را مورد بررسی قرار می­دهیم:- سختی (Rigidity): تغییر کد حتی برای یک تغییر بسیار ساده، دشوار است.- شکنندگی (Fragility): تغییرات بسیار کوچک منجر به تولید باگ‌های زیاد در قسمت­های دیگری از کد شود.- بی‌تحرکی (Immobility): جدا کردن کد به ماژول‌های مستقل با قابلیت استفاده مجدد بسیار سخت باشد.- چسبناکی (Viscosity): تغییر کد با حفظ ساختار فعلی آن بسیار سخت باشد.- پیچیدگی اضافی (Needless Complexity): کد بخش‌هایی دارد که استفاده نمی‌شوند یا برای استفاده‌ در آینده نوشته شده است.- خوانایی کم (Poor Readability): استفاده از کدهای نامتعارف از لحاظ طراحی ظاهری یا کسب‌وکاری.سختی (Rigidity)وقتی که چندین ماژول در یک پروژه به همدیگر وابسته باشند و تغییر در یک بخش از ماژول، منجر به انتشار تغییرات در ماژول‌های دیگر شود، در این صورت کد دچار Rigidity شده است. در صورتی‌که کد دچار این وضعیت شود، حتی زمان انجام کوچک‌ترین تغییر نیز به سختی قابل تخمین است و کد تبدیل به کدِ مرده می‌شود.برای مثال، در صورتی‌که ارتباط بین دو کلاس در لایه‌ی کلاس‌های سطح پایین (Concrete class) باشد، در این‌صورت هر تغییر در کلاس سطح پایین منجر به انتشار تغییرات در کلاس‌های وابسته می‌شود. برای این‌کار وابستگی‌ها بایستی در لایه‌ی کلاس‌های انتراعی (Abstract class) انجام شود تا تغییر پیاده‌سازی، تاثیری در ساختار وابستگی‌ها ایجاد نکند.در زیر مثالی از یک وابستگی کلاس‌های سطح پایین دیده می‌شود. کلاس LoggerClient وابستگی به کلاس سطح پایین FileLogger دارد. در صورتی‌که بخواهیم تعیین نوع Logger را بر عهده‌ی کاربر بگذاریم، در این صورت کد نوشته شده به راحتی قابل استفاده نیست و بایستی تغییراتی در ساختار LoggerClient نیز ایجاد شود.interface Logger {
    void log(String message, Level level);
}

class FileLogger implements Logger {
    public void log(String message, Level level) {
        // write to file using FileWriter / Formatter / ...
    }
}

class ConsoleLogger implements Logger {
    public void log(String message, Level level) {
        // write to console using System.out.println(...)
    }
}

class LoggerClient {
    private FileLogger logger;
    public void doWork() {
        logger.log(&amp;quotStart&amp;quot, Level.INFO);
        try {
            // Something may produce exception.
            logger.log(&amp;quotTry started&amp;quot, Level.INFO);
        } catch (Throwable throwable) {
            logger.log(&amp;quotException started&amp;quot, Level.SEVERE);
        }
        logger.log(&amp;quotFinished&amp;quot, Level.INFO);
    }
}از این رو، وابستگی LoggerClient به FileLogger را به سطح انتزاع می‌بریم. در این صورت کد به شکل زیر در می‌آید. با این کار هر تغییری در سطوح پایین هیچ تاثیری بر استفاده کننده‌ها نخواهد داشت.class LoggerClient {
    private String loggerType;
    private Logger logger;

    public void init() {
        if (loggerType.equals(&amp;quotfile&amp;quot)) {
            this.logger = new FileLogger();
        } else if (loggerType.equals(&amp;quotconsole&amp;quot)) {
            this.logger = new ConsoleLogger();
        } else {
            throw new RuntimeException(&amp;quotNot implemented exception&amp;quot);
        }
    }

    public void doWork() {
        logger.log(&amp;quotStart&amp;quot, Level.INFO);
        try {
            // Something may produce exception.
            logger.log(&amp;quotTry started&amp;quot, Level.INFO);
        } catch (Throwable throwable) {
            logger.log(&amp;quotException started&amp;quot, Level.SEVERE);
        }
        logger.log(&amp;quotFinished&amp;quot, Level.INFO);
    }
}
با این تغییر، کلاس LoggerClient وابسته به کلاس انتزاعی Logger شده است و اضافه کردن Logger جدید مانند DbLogger برای نوشتن لاگ در دیتابیس، تاثیری در ساختار LoggerClient نخواهد داشت و به راحتی می‌توان پیاده‌ساز Logger را برای کلاس LoggerClient تغییر داد.پس راه حلی برای فرار از Ridigityبردن وابستگی‌ها به سطح انتزاع می‌باشد.شکنندگی (Fragility)این نشانه بسیار شبیه به Rigidity است. شکنندگی وقتی اتفاق می‌افتد که تغییر در یک بخش از نرم‌افزار منجر به ایجاد خرابی در بخش‌های دیگر نرم‌افزار شود و این خرابی‌ها از دید توسعه‌دهنده دور بمانند. اغلب این خرابی‌ها نیز در بخش‌هایی اتفاق می‌افتند که به صورت مفهومی به تغییر انجام شده مرتبط نیست.به صورت کلی، دو نوع Fragility وجود دارد. اولی در زمان کامپایل (back-side crash) اتفاق می‌افتد و در زمان تولید قابل شناسایی و رفع است و دومی در زمان اجرا(client-side crash). نتیجه‌ی خرابی در زمان اجرا، ترس کارفرما و مدیران نسبت به تمامی تغییرات (حتی کوچک) و بی‌اعتمادی نسبت به تیم توسعه است. زمانی‌که یک پروژه دچار شکنندگی شود، هر چه زمان بگذرد، این شکنندگی بیش‌تر نمود پیدا­ می­کند و نگهداری از آن ناممکن می‌شود. زیرا هر تغییر برای بهبود(!)، منجر به ایجاد مشکلات جدید در بخش‌های دیگری از کد می‌شود.برای مثال، اگر میزان استفاده از کلاس‌های Helper و توابع ایستا در یک نرم‌افزار زیاد باشد و عملیات حیاتی سیستم نیز به این توابع سپرده شود، در این صورت نرم‌افزار دچار Coupling زیاد و Cohesionکم شده است. در قطعه کد زیر، تابع authorize از کلاس Helper، به بررسی دسترسی یک نقش به منبع درخواستی آن می‌پردازد.static class Helper {
    public static boolean authorize(Long roleId, String resource) {
        if (resource.equals(&amp;quotadmin&amp;quot) &amp;&amp; (roleId == 1L || roleId == 2L)) {
            return true;
        }

        if (resource.equals(&amp;quotapi&amp;quot) &amp;&amp; roleId == 3L) {
            return true;
        }     

        return false;
    }
}در صورتی‌که منابع جدید و به موازات آن، نقش‌های جدید به نرم‌افزار اضافه شود، تابع authorizeبایستی خود را با تغییرات جدید وفق دهد. این مثال، نمونه‌ای از شکنندگی کد است. برای کم کردن شکنندگی در این بخش از کد، بایستی کلاس جدیدی به نام Resource به نرم‌افزار اضافه شود که وظیفه‌ی نگه‌داری اطلاعات مربوط به منبع و دسترسی‌های مجاز برای آن را داشته باشد.class Resource {
    private List&lt;Long&gt; roles = new ArrayList&lt;Long&gt;();
    public boolean isAuthorized(Long roleId) {
        return roles.contains(roleId);
    }

    public void addRole(Long roleId) {
        roles.add(roleId);
    }

    public void removeRole(Long roleId) {
        roles.remove(roleId);
    }
}برای اضافه کردن دسترسی جدید به یک منبع، کافیست که تابع addRole برای آن منبع فراخوانی شود.افزایش Cohesion ماژول‌ها و انتقال مفاهیم مرتبط (نقش و منبع) به ساختار جدید (Resource)، منجر به کاهش شکنندگی می‌شود.بی‌تحرکی (Immobility)شاید تا به حال برایتان پیش آمده باشد که به عنوان یک مهندس نرم‌افزار، نیاز به استفاده از آن‌چه که قبلا در یک پروژه‌ی دیگر از سازمان خود پیاده‌سازی کرده‌اید را داشته باشید. در این صورت تلاش می‌کنید بخشی که نیاز به استفاده‌ی مجدد دارد، از دل پروژه‌ی قبلی بیرون بکشید. ولی هزینه‌ی بیرون کشیدن این بخش به شدت سرسام‌آور و ریسکی است. از این رو ترجیح می‌دهید که آن بخش را مجدد نوشته و از کد قبلی استفاده نکنید. در این صورت زمانی‌که کسب و کار شما در آن بخش تکراری تغییری داشته باشد، مجبور خواهید بود تا در هر دو جا (قبلی و جدید) تغییرات را اعمال کنید.یکی از راه‌های جلوگیری از بی‌تحرک شدن یک نرم‌افزار، جلوگیری از طراحی یک تکه (Monolithic) و تفکر ماژولار است. این‌که سیستم به ماژول‌های مستقلی تقسیم شود و انتظار برود که این ماژول‌ها در سیستم‌های دیگر و به همراه ماژول‌های دیگر مورد استفاده قرار می­گیرند. برای نمونه، می‌توان به کدی اشاره کرد که تمام منطق کسب­و­کارهای مستقل در هم تنیده و در کنار هم باشند و هیچ تفکیک و مرزبندی بین آن‌ها وجود نداشته باشد. در این صورت کد دچار Immobility شده است. در مقابل، نرم‌افزاری که از معماری MVC برای جداسازی کسب­و­کار و داده استفاده می‌کند، احتمال دچار شدن کدِ آن به بی‌تحرکی، بسیار کم‌تر است.البته همواره به یاد داشته باشید که برای شروع معماری یک سیستم، همیشه شروع معماری خود را از درشت‌دانه‌ترین معماری شروع کنید و رفته رفته و بسته به محیطی که در آن فعالیت می‌کنید، به سمت معماری‌های ماژولار، سرویس محور و Microservice بروید.چسبناکی (Viscosity)چسبناکی در دو حالت اتفاق می‌افتد. اولی چسبناکی در طراحی و دومی چسبناکی در محیط.زمانی‌که در یک پروژه، دچار یک چالش می‌شوید، ممکن است چند راه حل برای حل چالش داشته باشید که برخی از راه‌حل‌ها طراحی فعلی پروژه را حفظ می­کنند و برخی دیگر اهداف طراحی پروژه را در هم می­شکنند. زمانی‌که استفاده از روش‌های حفظ کننده‌ی طراحی پروژه بیش‌از روش‌های دیگر هزینه‌بر باشند، می‌توان گفت که چسبناکی طراحی بالا است. از این رو، انجام کارهای درست هزینه‌ی بیش‌تری از انجام کارهای غلط در طی توسعه‌ی پروژه دارد.همان‌طور که می‌دانید یکی از مشکلاتی که در پروژه‌های بزرگ ایجاد می‌شود، ایجاد کلاس‌های بزرگی است که چند مسئولیتی هستند و در اصطلاح به آن‌ها God Class می‌گویند. کلاس‌های چند مسئولیتی از جمله طراحی‌هایی هستند که در آن‌ها Viscosityبالا دیده می‌شود. برای فائق آمدن بر این طراحی بد که منجر به چسبناکی در محیط شده است، کافیست از روش‌هایی همچون تقسیم و حل استفاده شود تا چندین کلاس تک مسئولیتی ایجاد شود.برای مثال، در برخی از پروژه‌ها دیده می‌شود که کلاسی نگه‌دارنده‌ی تمامی مقادیر ثابت و تنظیماتی است که در ماژول‌های مستقل استفاده می‌شوند. به همین دلیل، در طی توسعه و به دلایلی بی ربط این کلاس مورد تغییر - و حتی تغییر همزمان - قرار می‌گیرد. این مشکل منجر به کاهش سرعت توسعه می‌شود و این به دلیل Viscosity بالای طراحی است. جدا کردن مقادیر ثابت و تنظیمات هر ماژول، می‌تواند باعث کاهش چسبناکی طراحی شود.پیچیدگی اضافی (Needless Complexity)مواقعی پیش می‌آید که شما در زمان طراحی و پیاده‌سازی یک نرم‌افزار، با دید همه جانبه و وسواس زیاد، Feature هایی را طراحی و پیاده‌سازی می‌کنید که قرار نیست در نرم‌افزار شما استفاده شود. بودن Feature های بدون کاربرد و اضافی در نرم‌افزار شما، یعنی وجود پیچیدگی اضافی که منجر به افزایش زمان توسعه، تست و نگه‌داری از نرم‌افزار می‌شود.کدهای اضافه و بدون کاربرد، توابع مبهم و چند کاره در interface ها (تابعی مانند Process که می‌تواند پیاده‌سازی‌های مختلف داشته باشد) و استفاده زیاد از Design Pattern ها موجب ایجاد پیچیدگی اضافی در کد و همین‌طور منجر به افزایش Viscosity در طراحی می‌شود.خوانایی کم (Poor Readability)این نشانه زمانی مشهود است که خواندن کد و فهم آن سخت باشد. از دلایل ظهور این نشانه، عدم انطباق کد با نیازمندی‌های اجرایی (Syntax، Variable ها و Class ها) و منطق پیاده‌سازی شده است. برای مثال، فرض کنید که می‌خواهیم بر اساس یک ورودی منطقی (true / false) کسب و کار مشخصی را انجام دهیم. پیاده‌سازی به شکل زیر به شدت سخت فهم است.void Process_true_false(String input) throws Exception {
    if (input.length() == 4) { // true.length = 4
        //...
    } else if (input.length() == 5) { // false.length = 5
        //...
    } else {
        throw new Exception(&amp;quotNeither true nor false, not a nice input. return.&amp;quot);
    }
}این کد چندین مشکل دارد:1. نام‌گذاری تابع منافی Code Convention های عمومی است که در برنامه‌نویسی جاوا مورد استفاده قرار می‌گیرد.2. پیاده‌سازی متد، بهترین و معمول‌ترین راه نیست.3. کد حالت استثناء به خوبی نوشته نشده است. اول این‌که نوع استثناء خیلی کلی است (Exception) و پیام نوشته شده نیز به فرمت رسمی نوشته نشده است.از این رو، می‌توان کد را به شکل زیر بازنویسی کرد.void doLogic(boolean input) {
    if (input) {
        //...
    } else {
        //...
    }
}در صورتی‌که وجود کدهای ناخوانا یا کم خوانا منجر به تولید باگ در سیستم می‌شود، می‌توان به Refactor کد پرداخت.جمع‌بندییکی از بزرگ‌ترین مشکلات کدهای ضعیف، High coupling می‌باشد. اطمینان از پیاده‌سازی منطبق بر اصول SOLIDمی‌تواند شروع خوبی برای حل مشکلات کدها باشد. زمانی این مشکلات خود را بیش‌تر نشان می‌دهند که حجم زیادی از کد را کدهای ضعیف تشکیل دهند. برای حل این معضل، چند گزینه‌ وجود دارد.گزینه‌ی اول دوباره نویسی (Rewrite)، گزینه‌ی دوم بازآرایی (Refactor) و گزینه‌ی سوم دست نزدن به کد فعلی است (که البته در صورتی که نیاز به تغییر کد باشد، این روش کاربردی نیست). با توجه به فرمایشات Martin Fowler، در صورتی‌که انجام دادن کاری، شما را اذیت می‌کند، بیش‌تر انجامش دهید.[1] از این رو، در صورتی‌که اصلاح کد زحمت زیادی دارد، در زمان‌های نزدیک‌تری آن‌را انجام دهید.با توجه به نمودار بالا، در صورتی‌که فاصله‌ی بین دو کار پر زحمت زیاد باشد، در این صورت زحمت بیش‌تری به شما تحمیل خواهد شد ولی اگر این کار را در فواصل زمانی کمتری انجام دهید، در این صورت میزان زحمت به شدت کاهش می‌یابد. از این رو اکثر تیم‌های نرم‌افزاری میل به Continuous Integration حتی به صورت روزانه دارند.[1] If it hurts, do it more often.منابعFowler, Martin. 2011. Frequency   Reduces Difficulty. 07 28. Accessed November 8, 2020.   https://martinfowler.com/bliki/FrequencyReducesDifficulty.html.Martin, Robert C. 2000. Design Principles   and Design Patterns. Accessed October 28, 2020.   http://www.cvc.uab.es/shared/teach/a21291/temes/object_oriented_design/materials_adicionals/principles_and_patterns.pdf.Pearman, Brandon. 2019. Bad Design:   Symptoms of bad code. Accessed October 28, 2020.   http://www.devobsession.com/Post/design/symptoms-of-bad-code.Zolotaryov, Sergey. 2017. Indicators of   Problem Design. Accessed October 28, 2020.   https://codingsight.com/indicators-of-problem-design/.</description>
                <category>Hossein Mobasher</category>
                <author>Hossein Mobasher</author>
                <pubDate>Sun, 06 Dec 2020 11:55:59 +0330</pubDate>
            </item>
            </channel>
</rss>