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

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

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

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

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

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

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

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

1.1 چگونه از این کتاب استفاده کنیم

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

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

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

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