Skip to content

فصل ۶ — افزایش تدریجی

افزایش تدریجی (Incrementalism) به‌صورت زیر تعریف می‌شود: «طراحی افزایشی مستقیماً به هر کاربرد طراحی ماژولار مرتبط است، که در آن اجزا در صورت بهبود برای اطمینان از عملکرد بهتر، آزادانه جایگزین می‌شوند.»[^1]

کار به‌صورت افزایشی درباره ساخت تدریجی ارزش است. به‌طور ساده، این درباره بهره‌برداری از ماژولار بودن یا componentization سیستم‌هایمان است.

اگر کار تکراری (iterative) درباره پالایش و بهبود چیزی در طول مجموعه‌ای از تکرارهاست، کار افزایشی (incremental) درباره ساخت سیستم — و ایده‌آل انتشار آن — تکه‌تکه است. این به‌خوبی در شکل ۶.۱، گرفته‌شده از User Story Mapping [Patton]،[^2] نشان داده شده است.

شکل ۶.۱ — تکراری در مقابل افزایشی

در رویکرد تکراری، همان محصول در هر تکرار کامل‌تر می‌شود (مانند تکمیل یک دایره در هر دور). در رویکرد افزایشی، بخش‌های جدیدی به سیستم اضافه می‌شوند (مانند افزودن تدریجی بخش‌ها به یک خط).

[^1]: منبع: Wikipedia، https://en.wikipedia.org/wiki/Continuous_design [^2]: این مقایسه بین رویکردهای «تکراری» و «افزایشی» را اولین بار در کتاب User Story Mapping اثر Jeff Patton دیدم. ببینید https://bit.ly/3s9jvY6.

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

اهمیت ماژولار بودن

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

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

وقتی برنامه Apollo در دهه ۱۹۶۰ با هدف فرستادن انسان به ماه شروع شد، یکی از جهش‌های اولیه ایجاد پروفایل مأموریتی به نام lunar orbit rendezvous (LOR) بود. LOR یعنی فضاپیما به مجموعه‌ای از ماژول‌ها تقسیم می‌شد، هر کدام متمرکز بر بخش خاصی از چالش. Saturn V وظیفه‌اش رساندن همه چیز به مدار زمین بود و مرحله نهایی این بود که ماژول وظیفه‌خاص دیگری بقیه اجزای فضاپیما را از زمین به ماه پرتاب کند.

بقیه فضاپیمای Apollo از چهار ماژول اصلی تشکیل شده بود:

  • Service Module وظیفه‌اش رساندن همه چیز از زمین به ماه و برگشت بود.
  • Command Module زیستگاه اصلی فضانوردان بود؛ وظیفه اصلی‌اش بازگرداندن فضانوردان از مدار زمین به سطح بود.
  • lunar excursion module (LEM) از دو ماژول دیگر تشکیل شده بود: Descent و Ascent. Descent Module فضانوردان را از مدار ماه به سطح ماه می‌رساند.
  • Ascent Module فضانوردان را به مدار ماه برمی‌گرداند جایی که با Command و Service Moduleها rendezvous و docking کرده و سپس به زمین برمی‌گشتند.

این ماژولار بودن مزایای زیادی داشت. یعنی هر جزء می‌توانست برای تمرکز بر یک بخش از مشکل ساخته شود و کمتر در طراحی‌اش مصالحه کند. به گروه‌های مختلف — در این مورد شرکت‌های کاملاً متفاوت — اجازه می‌داد روی هر ماژول تا حد زیادی مستقل از دیگران کار کنند. تا زمانی که گروه‌های مختلف درباره نحوه رابط ماژول‌ها با یکدیگر توافق می‌کردند، می‌توانستند بدون محدودیت روی مشکلات ماژول خود کار کنند. هر ماژول می‌توانست سبک‌تر باشد چون، مثلاً، Lunar Module نیازی نداشت وسیله بازگشت به زمین را تا سطح ماه حمل کند.

اگرچه کشیدن خط و نامیدن هر فضاپیمای Apollo ساده دشوار است، هر ماژول می‌توانست ساده‌تر از حالتی باشد که برای بخش بزرگ‌تری از کل مشکل طراحی شده بود.

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

این واقعاً فلسفه رویکردهای مبتنی بر component به طراحی، مثل microservices، یا در واقع هر طراحی service-oriented است.

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

با این حال، microservices تنها رویکردی نیست که می‌توانیم از ماژولار بودن در هر سیستم نرم‌افزاری بهره ببریم. واقعاً مسئله جدی گرفتن طراحی است.

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

افزایش تدریجی سازمانی

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

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

فراتر از ارزش توانایی ایجاد تغییرات فنی به‌صورت افزایشی، این رویکرد سازمان‌ها را آزاد می‌کند رویکرد افزایشی به تغییر فرهنگی و سازمانی اتخاذ کنند.

بسیاری از سازمان‌ها در دستیابی به تغییرات مؤثر در شیوه‌های کاری‌شان مشکل دارند. چنین «تحولات» (transformations) بدنام سخت بودن دارند. مانع اصلی ایجاد چنین تغییری همیشه نحوه گسترش راه‌حل در سراسر سازمان است. دو مانع این گسترش تغییرات را دشوار می‌کند. اولی توضیح و انگیزه دادن به مردم برای ایجاد تغییر است و دومی غلبه بر موانع سازمانی یا رویه‌ای که پذیرش آن را محدود می‌کند.

رایج‌ترین رویکرد برای پیاده‌سازی تغییر به نظر می‌رسد تلاش برای استانداردسازی فرایندها در سراسر سازمان است. «نقشه‌برداری فرایند» (process mapping) و «تحول کسب‌وکار» (business transformation) کسب‌وکار بزرگی برای مشاوران مدیریت است. مشکل این است که همه سازمان‌ها — قطعاً آن‌هایی که در کار خلاقانه‌اند — به خلاقیت انسانی وابسته‌اند. اگر می‌توانستیم فرایند را به مجموعه‌ای از گام‌ها «استاندارد» کنیم، می‌توانستیم آن را خودکار کنیم و افراد پرهزینه و خطاپذیر را حذف کنیم. چند بار از سیستم فیلتر تلفن خودکار استفاده کرده‌اید و به منویی رسیده‌اید که گزینه‌ای مطابق پرسش شما ندارد یا تماس را قطع می‌کند؟ این به‌خاطر این است که برخی چیزها ساده نیستند که به گام‌های ساده تقسیم شوند، همان‌طور که هر کسی که برنامه کامپیوتری نوشته گواهی می‌دهد.

وقتی درباره توسعه نرم‌افزار بحث می‌کنیم، هیچ‌جا نزدیک نیستیم که بتوانیم خلاقیت انسانی را از این تلاش حذف کنیم. پس برای فعال کردن خلاقیت انسانی، باید در فرایند و سیاست‌هایی که کارمان را ساختار می‌دهند فضایی برای آزادی خلاقانه بگذاریم. یکی از ویژگی‌های تعریف‌کننده تیم‌های با عملکرد بالا در توسعه نرم‌افزار، توانایی پیشرفت و تغییر نظر بدون درخواست اجازه از هیچ فرد یا گروهی خارج از تیم کوچکشان است.[^3]

[^3]: کتاب Accelerate توصیف می‌کند تیم‌هایی که رویکرد منضبط‌تری به توسعه دارند «۴۴٪ زمان بیشتری روی کار جدید» نسبت به تیم‌هایی که ندارند صرف می‌کنند. ببینید https://amzn.to/2YYf5Z8.

بیایید کمی این را تحلیل کنیم. با «تیم‌های کوچک» شروع کنیم. اگرچه اکنون داده بیشتری برای پشتیبانی از این ادعا داریم،[^4] مدت‌هاست که شناخته شده تیم‌های کوچک از تیم‌های بزرگ بهتر عمل می‌کنند. Fred Brooks در کتاب The Mythical Man Month نوشت:

نتیجه ساده است: اگر پروژه ۲۰۰ نفره ۲۵ مدیر دارد که ماهرترین و باتجربه‌ترین برنامه‌نویسان‌اند، ۱۷۵ نفر را اخراج کنید و مدیران را به برنامه‌نویسی برگردانید.

[^4]: در کتاب Accelerate: The Science of Lean Software & DevOps، Nicole Forsgren، Jez Humble و Gene Kim ویژگی‌های تیم‌های با عملکرد بالا را توصیف می‌کنند. ببینید https://amzn.to/3g0Lvup.

این روزها، بیشتر پیروان چابک تیم ۲۵ نفره را تیم بزرگ می‌دانند. تفکر فعلی این است که اندازه بهینه تیم هشت نفر یا کمتر است.

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

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

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

سازمان‌های ماژولار انعطاف‌پذیرتر، مقیاس‌پذیرتر و کارآمدتر از ساختارهای سازمانی سنتی‌تر برای توسعه نرم‌افزار هستند.

ابزارهای افزایش تدریجی

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

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

فراتر از آن اصول عمیق‌تر، ایده‌های کمتر انتزاعی چه هستند که می‌توانند به رویکرد افزایشی‌تر به تغییر کمک کنند؟ چه کاری باید انجام دهیم که بتوانیم به‌صورت افزایشی کار کنیم؟

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

اگر کدم توپ کثیف اسپاگتی بزرگی باشد و تغییری در یک جا بدهم، ممکن است ناخواسته بخش دیگری از کد را تحت تأثیر قرار دهم. سه تکنیک مهم به من اجازه می‌دهند چنین تغییری را ایمن‌تر انجام دهم.

می‌توانم سیستم را معماری کنم تا دامنه تغییر را محدود کنم. با طراحی سیستم‌هایی که ماژولارند و جداسازی نگرانی‌های خوبی دارند، می‌توانم تأثیر تغییراتم را فراتر از ناحیه کدی که روی آن تمرکز دارم محدود کنم.

می‌توانم شیوه‌ها و تکنیک‌هایی اتخاذ کنم که با ریسک کمتر کد را تغییر دهم. مهم‌ترین این شیوه‌های ایمن‌تر، refactoring است. یعنی توانایی ایجاد تغییرات در گام‌های کوچک، ساده و کنترل‌شده که به من اجازه می‌دهد کد را ایمن بهبود دهم یا حداقل تغییر دهم.

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

اگر از ابزارهای refactoring در محیط توسعه برای، مثلاً، «استخراج متد» (extract a method) یا «معرفی پارامتر» (introduce a parameter) استفاده کنم، می‌توانم مطمئن باشم تغییر ایمن انجام می‌شود، یا می‌توانم ابزارهای توسعه بهتری بخرم.

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

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

ظرافت‌هایی در کار مؤثر با سطوح بالای آزمایش خودکار وجود دارد که در فصل‌های بعد بررسی می‌کنیم، اما آزمایش خودکار جزء مهمی از توانایی ما برای ایجاد تغییر سریع با اطمینان است.

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

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

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

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

محدود کردن تأثیر تغییر

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

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

فقط دو استراتژی منطقی است و هر دو ماهیت افزایشی دارند.

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

برای مستقل‌تر کردن قطعات سیستم، می‌توانیم از تکنیک قدرتمند الگوی Ports & Adapters[^5] استفاده کنیم.

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

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

همیشه باید این نقاط یکپارچه‌سازی، این portها، را کمی بیشتر از سایر بخش‌های سیستم با دقت رفتار کنیم چون وقتی چیزها اینجا باید تغییر کنند درد بیشتری ایجاد می‌کنند. رویکرد Ports & Adapters استراتژی‌ای به ما می‌دهد که آن «دقت بیشتر» را در کدمان تجسم کنیم.

[^5]: Ports & Adapters الگوی معماری است که هدفش تولید اجزای کاربرد جفت‌شدگی سست‌تر است؛ Hexagonal Architecture هم نامیده می‌شود. ببینید https://bit.ly/3cwH3Sd.

توجه کنید، این ربطی به فناوری در حال استفاده ندارد. Ports & Adapters برای اطلاعات باینری ارسال‌شده از طریق socket — احتمالاً مفیدتر — به‌اندازه متن ساختاریافته ارسال‌شده از طریق فراخوانی REST API مفید است.

ابزار مهم و اغلب نادیده‌گرفته‌شده دیگر در مدیریت تأثیر تغییر، سرعت بازخورد است. اگر کدی بنویسم که کد شما را بشکند، اهمیت آن بسته به زمان کشف این موضوع بسیار متفاوت است.

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

از طرف دیگر، اگر ظرف چند دقیقه از ایجاد تغییرم بفهمیم، مهم نیست. می‌توانم مشکلی که ایجاد کردم را حل کنم، شاید قبل از اینکه متوجه شوید. این مشکلی است که continuous integration و continuous delivery حل می‌کنند.

این یعنی می‌توانیم از هر کدام یا هر دو این استراتژی‌ها برای محدود کردن تأثیر تغییر استفاده کنیم. می‌توانیم سیستم‌هایمان را برای تقویت توانایی ایجاد تغییر بدون وادار کردن نیاز به تغییر بر دیگران طراحی کنیم و شیوه‌های کاری‌مان را برای ایجاد تغییرات در گام‌های کوچک و افزایشی بهینه کنیم.

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

طراحی افزایشی

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

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

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

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

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

استدلال‌هایم در این کتاب درباره اینکه مهندسی واقعاً چیست و توسعه نرم‌افزار واقعاً چیست، برای کمک به زدن آن کلید ذهنی است، اگر هنوز نزده‌اید.

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

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

شکایات از اینکه «آن‌ها» همیشه نیازمندی‌ها را اشتباه می‌گیرند یکی از علائم این است. بله، کسی در ابتدا نمی‌داند چه بسازد. اگر بگوید می‌داند، واقعاً مشکل را درک نمی‌کند.

پذیرش اینکه نمی‌دانیم، تردید در آنچه می‌دانیم و کار برای یادگیری سریع، گامی از سگما به سمت مهندسی است.

از حقایقی درباره آنچه می‌دانیم و افزایشی کشف کرده‌ایم استفاده می‌کنیم و در هر مرحله سعی می‌کنیم گام بعدی‌مان را به ناشناخته‌ها بر اساس همه چیزهایی که فعلاً فکر می‌کنیم می‌دانیم استخراج کنیم. این دیدگاه علمی منطقی‌تر است. همان‌طور که فیزیکدان Richard Feynman گفت، علم «فلسفه رضایت‌بخشی از نادانی» است. او همچنین گفت:

دانشمند تجربه زیادی با نادانی، تردید و عدم قطعیت دارد و این تجربه فکر می‌کنم بسیار مهم است.

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

می‌توانیم کد را به‌گونه‌ای بنویسیم که صرفاً دنباله‌ای از گام‌های سازمان‌یافته — یا بهتر بگوییم سازمان‌نشده — به‌صورت توپ کثیف بزرگ، ضعیف تفکیک‌شده باشد. در عوض، می‌توانیم کد را به‌گونه‌ای بنویسیم که پیچیدگی‌اش را با تکاملش به‌طور مؤثر بپذیرد و مدیریت کند.

اگر اولی را انجام دهیم، هرچه کد جفت‌شدگی تنگ‌تر، کمتر ماژولار و کمتر منسجم‌تر باشد، تغییر دشوارتر است. به همین دلیل ویژگی‌هایی که به مدیریت پیچیدگی در کد کمک می‌کنند و مدام تکرار می‌کنم مهم‌اند. اگر این ایده‌ها را در هر سطحی از دانه‌بندی به‌طور فراگیر در کارمان اتخاذ کنیم، درهای کمتری به تغییر می‌بندیم و گزینه‌های بیشتری برای ایجاد تغییر — حتی تغییر غیرمنتظره — در آینده باز می‌گذاریم. این با over-engineering و نوشتن کدی که با هر احتمالی کنار می‌آید متفاوت است. این کدی است که برای آسان‌تر کردن تغییر سازمان‌دهی شده، نه کدی که همه چیزهایی که الان فکر می‌کنید انجام دهد.

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

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

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

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

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

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

خلاصه

کار افزایشی برای ساخت هر سیستم پیچیده‌ای بنیادی است. توهمی است که تصور کنیم چنین سیستم‌هایی «کاملاً شکل‌گرفته» از ذهن برخی متخصصان می‌جهند؛ نمی‌جهند. نتیجه کار و انباشت تدریجی دانش و درک با پیشرفت ما هستند. سازمان‌دهی کارمان برای تسهیل و اعتبارسنجی این یادگیری به ما اجازه می‌دهد مسیرهایی که هنوز دیده نشده‌اند در جهت پیشرفت برویم. این ایده‌ها در هسته آنچه به ما اجازه پیشرفت مؤثر می‌دهد قرار دارند.