سیستم های
موارد زیر به طور کلی ویژگی های مطلوب یک سیستم هستند:- حداقل پیچیدگی باید از پروژه های بیش از حد پیچیده اجتناب شود. مهمترین چیز سادگی و وضوح (ساده تر = بهتر) است.
- سهولت نگهداری. هنگام ایجاد یک برنامه، باید به خاطر داشته باشید که باید آن را نگهداری کنید (حتی اگر شخصاً مسئولیت نگهداری آن را بر عهده نگیرید). این بدان معنی است که کد باید واضح و واضح باشد.
- اتصال سست. این بدان معناست که ما تعداد وابستگیها را بین بخشهای مختلف برنامه به حداقل میرسانیم (به حداکثر رساندن انطباق ما با اصول OOP).
- قابلیت استفاده مجدد ما سیستم خود را با قابلیت استفاده مجدد از اجزا در برنامه های دیگر طراحی می کنیم.
- قابل حمل بودن تطبیق یک سیستم با محیط دیگر باید آسان باشد.
- سبک یکنواخت. ما سیستم خود را با استفاده از یک سبک یکنواخت در اجزای مختلف آن طراحی می کنیم.
- توسعه پذیری (مقیاس پذیری). ما میتوانیم سیستم را بدون نقض ساختار اصلی آن ارتقا دهیم (افزودن یا تغییر یک مؤلفه نباید بر سایر اجزا تأثیر بگذارد).
مراحل طراحی یک سیستم
- سیستم نرم افزاری. برنامه را به طور کلی طراحی کنید.
- تقسیم به زیرسیستم ها/بسته ها اجزای منطقی متمایز را تعریف کنید و قوانین تعامل بین آنها را تعریف کنید.
- تقسیم زیرسیستم ها به کلاس ها بخش هایی از سیستم را به کلاس ها و رابط های خاص تقسیم کنید و تعامل بین آنها را تعریف کنید.
- تقسیم کلاس ها به متدها تعریف کاملی از متدهای لازم برای یک کلاس بر اساس مسئولیت تعیین شده ایجاد کنید.
- طراحی روش. تعریف دقیقی از عملکرد روش های فردی ایجاد کنید.
اصول و مفاهیم کلی طراحی سیستم
مقداردهی اولیه تنبل در این اصطلاح برنامه نویسی، برنامه زمانی را برای ایجاد یک شی تلف نمی کند تا زمانی که واقعاً از آن استفاده شود. این امر فرآیند اولیه سازی را سرعت می بخشد و بار روی زباله جمع کن را کاهش می دهد. با این اوصاف، شما نباید این موضوع را زیاد دور کنید، زیرا این امر می تواند اصل مدولار بودن را نقض کند. شاید ارزش آن را داشته باشد که تمام نمونه های ساخت و ساز را به بخشی خاص، به عنوان مثال، روش اصلی یا به یک کلاس کارخانه منتقل کنیم. یکی از ویژگی های کد خوب، عدم وجود کد تکراری و دیگ بخاری است. به عنوان یک قاعده، چنین کدی در یک کلاس جداگانه قرار می گیرد تا در صورت نیاز بتوان آن را فراخوانی کرد.AOP
من همچنین می خواهم به برنامه نویسی جنبه گرا اشاره کنم. این پارادایم برنامه نویسی همه چیز در مورد معرفی منطق شفاف است. یعنی کدهای تکراری در کلاس ها (جنبه ها) قرار می گیرند و زمانی فراخوانی می شوند که شرایط خاصی برآورده شود. به عنوان مثال، هنگام فراخوانی یک متد با یک نام خاص یا دسترسی به یک متغیر از یک نوع خاص. گاهی اوقات جنبه ها ممکن است گیج کننده باشند، زیرا بلافاصله مشخص نیست که کد از کجا فراخوانی می شود، اما این هنوز هم عملکرد بسیار مفیدی است. مخصوصاً هنگام ذخیره سازی یا ورود به سیستم. ما این قابلیت را بدون اضافه کردن منطق اضافی به کلاس های معمولی اضافه می کنیم. چهار قانون کنت بک برای یک معماری ساده:- بیانی - هدف یک کلاس باید به وضوح بیان شود. این امر از طریق نامگذاری مناسب، اندازه کوچک و رعایت اصل مسئولیت واحد (که در ادامه با جزئیات بیشتر بررسی خواهیم کرد) به دست می آید.
- حداقل تعداد کلاسها و روشها - برای اینکه میخواهید کلاسها را تا حد امکان کوچک و با تمرکز محدود کنید، میتوانید بیش از حد پیش بروید (که منجر به ضد الگوی جراحی تفنگ ساچمهای میشود). این اصل مستلزم فشرده نگه داشتن سیستم و عدم پیشروی زیاد، ایجاد یک کلاس جداگانه برای هر اقدام ممکن است.
- بدون تکرار - کد تکراری که سردرگمی ایجاد می کند و نشانه ای از طراحی سیستم بهینه نیست، استخراج شده و به مکان جداگانه منتقل می شود.
- همه تست ها را اجرا می کند - سیستمی که تمام تست ها را پشت سر بگذارد قابل مدیریت است. هر تغییری میتواند باعث شکست تست شود و به ما نشان دهد که تغییر ما در منطق درونی یک روش نیز رفتار سیستم را به روشهای غیرمنتظرهای تغییر داده است.
جامد
هنگام طراحی یک سیستم، اصول شناخته شده SOLID ارزش در نظر گرفتن دارد:S (مسئولیت تک)، O (باز-بسته)، L (جایگزینی لیسکوف)، I (تفکیک رابط)، D (وارونگی وابستگی).
ما در مورد هر یک از اصول فردی صحبت نمی کنیم. این کمی فراتر از محدوده این مقاله خواهد بود، اما می توانید اطلاعات بیشتری را در اینجا بخوانید .رابط
شاید یکی از مهمترین مراحل در ایجاد یک کلاس با طراحی خوب، ایجاد یک رابط طراحی شده خوب باشد که نمایانگر یک انتزاع خوب، پنهان کردن جزئیات پیاده سازی کلاس و ارائه همزمان گروهی از متدها است که به وضوح با یکدیگر سازگار هستند. بیایید نگاهی دقیقتر به یکی از اصول SOLID بیندازیم - تفکیک رابط: کلاینتها (کلاسها) نباید روشهای غیرضروری را پیادهسازی کنند که از آنها استفاده نکنند. به عبارت دیگر، اگر ما در مورد ایجاد یک رابط با کمترین تعداد روش ها با هدف انجام تنها کار رابط صحبت می کنیم (که فکر می کنم بسیار شبیه به اصل مسئولیت تک است)، بهتر است به جای آن یک دو نوع کوچکتر ایجاد کنید. از یک رابط پف کرده. خوشبختانه یک کلاس می تواند بیش از یک رابط را پیاده سازی کند. به یاد داشته باشید که رابط های خود را به درستی نام گذاری کنید: نام باید وظیفه تعیین شده را تا حد امکان دقیق نشان دهد. و البته هر چه کوتاهتر باشد، سردرگمی کمتری ایجاد خواهد کرد. نظرات اسناد معمولاً در سطح رابط نوشته می شوند. این نظرات جزئیاتی را در مورد اینکه هر روش باید چه کاری انجام دهد، چه آرگومان هایی را می گیرد، و چه چیزی را برمی گرداند، ارائه می دهد.کلاس
- ثابت های استاتیک عمومی؛
- ثابت های استاتیک خصوصی؛
- متغیرهای نمونه خصوصی
اندازه کلاس
حالا می خواهم در مورد حجم کلاس ها صحبت کنم. بیایید یکی از اصول SOLID - اصل مسئولیت واحد را به یاد بیاوریم. بیان میکند که هر شی فقط یک هدف (مسئولیت) دارد و منطق تمام روشهای آن به انجام رساندن آن است. این به ما میگوید از کلاسهای بزرگ و متورم (که در واقع ضد الگوی شی خدا هستند) اجتناب کنیم، و اگر روشهای زیادی با انواع منطق مختلف در یک کلاس داریم، باید به تجزیه آن به یک کلاس فکر کنیم. چند بخش منطقی (کلاس). این به نوبه خود خوانایی کد را افزایش می دهد، زیرا اگر هدف تقریبی هر کلاس را بدانیم، درک هدف هر روش زمان زیادی طول نخواهد کشید. همچنین، به نام کلاس توجه داشته باشید، که باید منطقی را که در آن وجود دارد منعکس کند. به عنوان مثال، اگر کلاسی داریم که بیش از 20 کلمه در نام آن وجود دارد، باید به فکر refactoring باشیم. هر کلاسی که به خود احترام می گذارد نباید این همه متغیر داخلی داشته باشد. در واقع، هر متد با یک یا چند مورد از آنها کار می کند و باعث ایجاد انسجام زیادی در کلاس می شود (که دقیقاً همانطور که باید باشد، زیرا کلاس باید یک کل واحد باشد). در نتیجه افزایش انسجام یک کلاس منجر به کاهش حجم کلاس و البته افزایش تعداد کلاس ها می شود. این برای برخی افراد آزاردهنده است، زیرا برای اینکه ببینید یک کار بزرگ خاص چگونه کار میکند، باید فایلهای کلاس را بیشتر بررسی کنید. علاوه بر همه اینها، هر کلاس یک ماژول کوچک است که باید حداقل با سایرین مرتبط باشد. این جداسازی تعداد تغییراتی را که باید هنگام اضافه کردن منطق اضافی به یک کلاس انجام دهیم، کاهش می دهد.اشیاء
کپسوله سازی
در اینجا ابتدا در مورد یک اصل OOP صحبت خواهیم کرد: کپسولاسیون. پنهان کردن پیادهسازی به معنای ایجاد روشی برای عایقسازی متغیرها نیست (محدود کردن بدون فکر دسترسی از طریق روشها، دریافتکنندهها و تنظیمکنندهها، که خوب نیست، زیرا کل نقطه کپسولسازی از بین رفته است). هدف پنهان کردن دسترسی، شکل دادن به انتزاع است، یعنی کلاس روشهای عینی مشترکی را ارائه میکند که ما از آنها برای کار با دادههای خود استفاده میکنیم. و کاربر نیازی ندارد دقیقا بداند که ما چگونه با این داده ها کار می کنیم - کار می کند و کافی است.قانون دمتر
ما همچنین میتوانیم قانون دمتر را در نظر بگیریم: این مجموعه کوچکی از قوانین است که به مدیریت پیچیدگی در سطح کلاس و روش کمک میکند. فرض کنید یک آبجکت Car داریم و متد حرکت (Object arg1, Object arg2) دارد . طبق قانون دمتر، این روش محدود به فراخوانی است:- روش های خود شیء Car (به عبارت دیگر، شیء "این")؛
- روش های اشیاء ایجاد شده در روش حرکت ؛
- متدهای اشیاء ارسال شده به عنوان آرگومان ( arg1 , arg2 );
- روش های داخلی اشیاء اتومبیل (دوباره "این").
ساختار داده ها
ساختار داده مجموعه ای از عناصر مرتبط است. هنگام در نظر گرفتن یک شی به عنوان یک ساختار داده، مجموعه ای از عناصر داده وجود دارد که روش ها بر روی آنها عمل می کنند. وجود این روش ها به طور ضمنی فرض می شود. به این معنا که یک ساختار داده، یک شی است که هدف آن ذخیره و کار با (پردازش) داده های ذخیره شده است. تفاوت اصلی آن با یک شی معمولی این است که یک شی معمولی مجموعه ای از روش هایی است که بر روی عناصر داده ای که به طور ضمنی فرض می شود وجود دارند عمل می کنند. آیا می فهمی؟ جنبه اصلی یک شی معمولی روش ها است. متغیرهای داخلی عملکرد صحیح آنها را تسهیل می کنند. اما در یک ساختار داده، روشهایی برای پشتیبانی از کار شما با عناصر داده ذخیرهشده وجود دارد که در اینجا بسیار مهم هستند. یکی از انواع ساختار داده، شی انتقال داده (DTO) است. این یک کلاس با متغیرهای عمومی و بدون روش (یا فقط روشهایی برای خواندن/نوشتن) است که برای انتقال دادهها هنگام کار با پایگاههای داده، تجزیه پیامها از سوکتها و غیره استفاده میشود. معمولاً دادهها برای مدت طولانی در چنین اشیایی ذخیره نمیشوند. تقریباً بلافاصله به نوع موجودی که برنامه ما کار می کند تبدیل می شود. یک موجودیت نیز به نوبه خود یک ساختار داده است، اما هدف آن مشارکت در منطق تجاری در سطوح مختلف برنامه است. هدف از DTO انتقال داده ها به / از برنامه است. نمونه ای از DTO:@Setter
@Getter
@NoArgsConstructor
public class UserDto {
private long id;
private String firstName;
private String lastName;
private String email;
private String password;
}
همه چیز به اندازه کافی واضح به نظر می رسد، اما در اینجا با وجود هیبریدها آشنا می شویم. هیبریدها اشیایی هستند که روش هایی برای مدیریت منطق مهم دارند، عناصر داخلی را ذخیره می کنند و همچنین شامل متدهای دسترسی (به دست آوردن/تنظیم) می شوند. چنین اشیایی کثیف هستند و اضافه کردن روش های جدید را دشوار می کنند. شما باید از آنها اجتناب کنید، زیرا مشخص نیست که آنها برای چه چیزی هستند - ذخیره عناصر یا اجرای منطق؟
اصول ایجاد متغیرها
بیایید کمی در مورد متغیرها تامل کنیم. به طور خاص، بیایید به این فکر کنیم که چه اصولی هنگام ایجاد آنها اعمال می شود:- در حالت ایده آل، شما باید یک متغیر را درست قبل از استفاده از آن اعلام و مقداردهی اولیه کنید (یکی را ایجاد نکنید و آن را فراموش کنید).
- در صورت امکان، متغیرها را به عنوان نهایی اعلام کنید تا از تغییر مقدار آنها پس از مقداردهی اولیه جلوگیری شود.
- متغیرهای شمارنده را فراموش نکنید که معمولاً از آنها در نوعی حلقه for استفاده می کنیم . یعنی فراموش نکنید که آنها را صفر کنید. در غیر این صورت ممکن است تمام منطق ما به هم بریزد.
- شما باید سعی کنید متغیرها را در سازنده مقداردهی اولیه کنید.
- اگر انتخابی بین استفاده از یک شی با مرجع یا بدون ( SomeObject() جدید باشد، بدون را انتخاب کنید، زیرا پس از استفاده از شیء در چرخه جمع آوری زباله بعدی حذف می شود و منابع آن هدر نمی رود.
- طول عمر متغیر (فاصله بین ایجاد متغیر تا آخرین باری که به آن ارجاع داده شده است) تا حد امکان کوتاه باشد.
- متغیرهای مورد استفاده در یک حلقه درست قبل از حلقه، نه در ابتدای روشی که حلقه را در بر می گیرد، مقداردهی اولیه کنید.
- همیشه با محدودترین محدوده شروع کنید و فقط در صورت لزوم گسترش دهید (شما باید سعی کنید یک متغیر را تا حد امکان محلی بسازید).
- از هر متغیر فقط برای یک هدف استفاده کنید.
- از متغیرهایی با هدف پنهان اجتناب کنید، به عنوان مثال، یک متغیر بین دو کار تقسیم می شود - این بدان معنی است که نوع آن برای حل یکی از آنها مناسب نیست.
مواد و روش ها
از فیلم "جنگ ستارگان: اپیزود سوم - انتقام سیث" (2005)
-
قانون شماره 1 - فشردگی در حالت ایده آل، یک روش نباید بیش از 20 خط باشد. این بدان معناست که اگر یک روش عمومی به طور قابل توجهی متورم شود، باید به فکر شکستن منطق و انتقال آن به روشهای خصوصی جداگانه باشید.
-
قانون شماره 2 - if , else , while و سایر دستورات نباید بلوک های تو در تو داشته باشند: تعداد زیادی تودرتو به طور قابل توجهی خوانایی کد را کاهش می دهد. در حالت ایده آل، شما نباید بیش از دو بلوک تودرتو {} داشته باشید .
و همچنین مطلوب است که کد در این بلوک ها فشرده و ساده باشد.
-
قانون شماره 3 - یک روش باید فقط یک عملیات را انجام دهد. یعنی اگر روشی انواع منطق پیچیده را انجام دهد، آن را به روشهای فرعی تقسیم میکنیم. در نتیجه، روش خود یک نما خواهد بود که هدف آن فراخوانی تمام عملیات های دیگر به ترتیب صحیح است.
اما اگر این عملیات برای قرار دادن در یک روش جداگانه بسیار ساده به نظر برسد، چه؟ درست است، گاهی اوقات ممکن است شبیه شلیک توپ به سمت گنجشک ها باشد، اما روش های کوچک تعدادی مزیت را به همراه دارد:
- درک بهتر کد؛
- با پیشرفت توسعه، روشها پیچیدهتر میشوند. اگر یک روش برای شروع ساده باشد، پیچیده کردن عملکرد آن کمی ساده تر خواهد بود.
- جزئیات پیاده سازی پنهان هستند.
- استفاده مجدد از کد آسان تر؛
- کد قابل اعتماد تر
-
قانون گام به گام - کد باید از بالا به پایین خوانده شود: هرچه پایین تر بخوانید، عمیق تر به منطق می پردازید. و بالعکس، هر چه بالاتر بروید، روش ها انتزاعی تر می شوند. برای مثال، عبارات سوئیچ نسبتاً فشرده و نامطلوب هستند، اما اگر نمیتوانید از استفاده از سوئیچ اجتناب کنید، باید سعی کنید آن را تا حد امکان به پایینترین سطح ممکن منتقل کنید.
-
آرگومان های روش - عدد ایده آل چیست؟ در حالت ایده آل، اصلاً هیچ :) اما آیا واقعاً این اتفاق می افتد؟ همانطور که گفته شد، شما باید سعی کنید تا حد امکان استدلال های کمتری داشته باشید، زیرا هر چه تعداد آرگومان ها کمتر باشد، استفاده از یک روش آسان تر و آزمایش آن آسان تر است. در صورت شک، سعی کنید تمام سناریوهای استفاده از روش با تعداد زیادی پارامتر ورودی را پیش بینی کنید.
-
علاوه بر این، خوب است متدهایی را که دارای یک پرچم بولین به عنوان پارامتر ورودی هستند، جدا کنیم، زیرا این به خودی خود نشان میدهد که متد بیش از یک عملیات را انجام میدهد (اگر درست است، یک کار را انجام دهید، اگر نادرست است، یکی دیگر را انجام دهید). همانطور که در بالا نوشتم، این خوب نیست و در صورت امکان باید از آن اجتناب کرد.
-
اگر یک روش دارای تعداد زیادی پارامتر ورودی است (افراطی 7 است، اما واقعاً باید بعد از 2-3 شروع به فکر کردن کنید)، برخی از آرگومان ها باید در یک شی جداگانه گروه بندی شوند.
-
اگر چندین روش مشابه (بارگذاری بیش از حد) وجود داشته باشد، پارامترهای مشابه باید به همان ترتیب ارسال شوند: این خوانایی و قابلیت استفاده را بهبود می بخشد.
-
وقتی پارامترها را به یک متد ارسال می کنید، باید مطمئن باشید که همه آنها استفاده می شوند، در غیر این صورت چرا به آنها نیاز دارید؟ تمام پارامترهای استفاده نشده را از رابط حذف کنید و با آن کار کنید.
- try/catch در طبیعت خیلی خوب به نظر نمی رسد، بنابراین ایده خوبی است که آن را به یک روش میانی جداگانه منتقل کنید (روشی برای رسیدگی به استثناها):
public void exceptionHandling(SomeObject obj) { try { someMethod(obj); } catch (IOException e) { e.printStackTrace(); } }
GO TO FULL VERSION