بدهی فنی رو اولین بار وارد کانینگهام (Ward Cunningham) برای قانع کردن کارفرمایی که تو سیستم مالی داشت به عنوان استعارهای برای دلیل نوسازی کردن نرمافزار مطرح کرد. «با قرض کردن میشه کارها رو سریعتر جلو برد اما تا زمانی که بدهیت رو صاف نکنی داری نرخ بهرهاش رو هم میپردازی» با نوسازی کردن نرمافزار در مسیر توسعه ما این بدهی رو پرداخت میکنیم. بدهیای که بخاطر سرعت بالا و دانش کم اولیهمون ایجاد شده.
حل بدهی فنی میتونه بهونهای برای نوشتن نرمافزار بد باشه. نرمافزار باید درک فعلی ما از مساله رو نشون بده. این درک میتونه با گذر زمان، اضافه شدن فیچرهای جدید و … تغییر بکنه. پرداخت بدهی فنی به معنی نوسازی (Refactor) نرمافزار موجود برای بازتاب درک فعلیمون از مسالهاست.
در دنیای نرمافزار «بهترین» دشمن «به اندازه کافی خوبه». اگه ما برای درک کامل مساله و نیازمندیها و یه طراحی درست صبر کنیم. احتمالا پنجره ورود به بازار رو از دست میدیم. یه راه خوب برای توسعه نرمافزار، توسعه، ارائه به کاربر، گرفتن بازخورد و گردش تو همین حلقه است. به این شکل میشه به شکل صعودی محصول و کیفیتش رو بهبود داد و همزمان درکمون از مسالهای که داریم حل میکنیم هم بیشتر میشه. بنابراین بدهی فنی به خودی خود، مساله نیست. مساله، نشناختن بدهی فنی و زیاد شدنشه.
پروداکشن کردن یه کد مثل رفتن زیر بار بدهیه. یه کمی بدهی سرعتمون رو بالا میبره اما باید بدونیم که به موقع بدهیمون رو پس بدیم. نقطه خطرناک اونجاست که بدهی باقی میمونه و بیشتر میشه. هر زمانی که به کد دارای بدهی اختصاص میدیم، نرخ بهره بدهیمون رو بیشتر میکنه. بزرگترین سازمانها هم میتونن زیر بار بدهی فنی زیاد کاملا فلج بشن.
بدهی فنی بحث کیفیت رو در مورد کد که برای استفاده کننده نامرئیه مطرح میکنه. معمولا تیمها برای به وجود آوردن بدهی فنی بهانههای مختلفی دارن
بعضیها فکر میکنن میشه بدهی فنی رو با ازنو نویسی نرمافزار از بین برد اما در زمان بازنویسی نرمافزار اگه اختلاف درکمون از مساله رو پوشش ندیم، ما رو به بدهیهای فنی فعلی میرسونه. (در مورد بازنویسی و نوسازی اینجا توضیح دادم) برای رفع بدهی فنی باید نوسازی کردن با افزایش درک مساله و مدلسازی مجدد همراه باشه. بهروشها (Best Practices) به تنهایی کافی نیستند. کد تمیز نمیتونه یه مدل خراب رو درست کنه. اگه مساله درست فهمیده نشه استفاده از بهروشها و کد تمیز فقط هدر دادن پول و منابعه.
قانون کانوی (Conway's Law) میگه که ساختار و فرهنگ سازمان روی نرمافزاری که تولید میشه اثرگذاره. «با نحوه چینش تیم بعضی از تصمیمهای نرمافزاری خودبهخود گرفته میشن که شبیه نحوه چینش تیم باشن». اگه یه سیستم رو طراحی کنی اما ساختار سازمان رو طراحی نکنی در واقع طراحی سیستم رو درست انجام ندادی.
هر نرمافزاری شامل پیچیدگی ضروری و پیچیدگی غیرضروریه. پیچیدگی غیرضروری درک نرمافزار رو سخت میکنه. بیاین به این پیچیدگی غیرضروری بدهی فنی بگیم. در زمان توسعه باید با این پیچیدگی غیرضروری دست و پنجه نرم کنیم، بنابراین توسعهمون با پرداخت نرخ بهره برای بدهی فنیمون همراهه.
فرض کنید یه سابسیستم پیچیده و با روش پیادهسازی گمراهکننده تو نرمافزار وجود داره که باید یه فیچر جدید بهش اضافه بشه. اگه روش پیادهسازی تمیز و واضح بود، حدود سه روز برای فیچر جدید زمان نیاز داشتیم اما با وجود پیچیدگیهای غیرضروری این زمان به شش روز افزایش پیدا میکنه. این سه روز بیشتر برای پیادهسازی فیچر جدید در واقع بهرهایه که برای بدهی فنی موجود تو نرمافزار میپردازیم. اینها اعداد فرضی با پیشبینیهای فرضی هستن. تو دنیای واقعی بدهی فنی به این شکل با اعداد قابل اندازهگیری نیست و بهبودی که تو زمان و روش توسعه میده با این دقت قابل اندازهگیری نیست.
پس باز هم سراغ استعاره مالی میریم. بدهی با بهره رو چطور پس میدن؟ به مرور!
یکی از روشهای تشخیص بدهی فنی، نرخ تغییرات و تعداد افرادی هست که روی یه بخش مشخص از کد کار کردن. نرخ تغییرات زیاد معمولا نشون دهنده اینه که یه بخشی از کد مدام باید عوض میشده (احتمالا درکمون از مساله کامل نبوده). با پیدا کردن این بخشهای نرمافزار در زمان هر تغییر میشه بخشی از بدهی فنی اون بخش رو کاهش داد. پرداخت بدهی فنی زمانبره اما به استعاره دقت کنیم اگه بهره رو به مرور پرداخت نکنیم در نهایت ورشکست میشیم. ورشکستگی تو یه نرمافزار با بدهی فنی به این معنیه که دیگه نمیشه به جایی از نرمافزار دست زد. پرداخت مداوم بدهی فنی یکی از نشونههای Code Stewardship ه که میتونه به عنوان فرهنگ جاری تیم بین اعضا رایج بشه.
تفاوتی که استعاره بدهی فنی با دنیای واقعی مالی داره اینه که تو دنیای مالی با گذشت زمان باید بدهی رو پس بدهی اما بدهی فنی نرمافزار زمانی فعال میشه که با اون بخش کد سر و کار داشته باشیم، یعنی لزوما نیاز نیست سراغ بدهی فنیهایی بریم که باهاشون سر و کار نداریم. کدهایی که پیچیدگی غیرضروری دارن اما کار میکنن رو میشه تا زمانی که بهشون برنخوردیم، رها کرد. در مقابل بخشهایی از کد که مدام در حال تغییرن باید با سیاست عدم تحمل (صفر کردن بدهی) همراه باشن چون نرخ بهرهشون مدام بیشتر میشه.
نادیده گرفتن بدهی فنی رو نباید با رها کردن کیفیت اشتباه بگیریم. بدهی فنی وقتی بزرگ بشه روی بهرهوری تیم و انگیزه اعضاش تاثیر منفی میذاره و منجر به یه حلقه خطرناک میشه: «بدهی فنی زیاد بهرهوری و روحیه تیم رو کاهش میده و همزمان بهرهوری پایین باعث میشه مدیریت فیچرهای بیشتری رو وارد کار کنه و حل بدهیهای فنی رو به بعد موکول کنه، این کار باعث بیشتر شدن بدهی فنی میشه»
بدهی فنی اجتنابناپذیره. دنیا سریع در حرکته و تیمهای نرمافزاری هم باید به سرعت نرمافزار رو برای تولید ارزش به مشتری برسونن. تو این تعقیب و گریز تیمها گاهی مجبور میشن راهحلهای سریع و کثیف رو استفاده کنن. بنابراین تیمهای نرمافزاری باید آماده مدیریت بدهیهاشون باشن.
اولین روش مدیریت بدهی فنی جلوگیری از زیاد شدنشه که با افزایش آگاهی تیم نسبت به بدهی و ایجاد روتینهای سازمانی براش همراهه. روش دیگه مدیریت بازپرداخت بدهیهاست. باید تلاشهای آگاهانهای برای شناسایی، اولویتبندی و نوسازی بدهیهای فنی، انجام بشه.
یک روش جلوگیری از بدهی فنی افزایش آگاهی به وجودش تو تیمهاست. تیمهای توسعه باید بدهیهای فنی نرمافزارشون رو بشناسن، جنبههای مختلفش رو بررسی کنن و با اثرش روی نرمافزار آشنا باشن. بررسی خودکار کیفیت کد، رعایت اصول کد تمیز، Design Smell ها و روشهای نوسازی باید براشون روتین باشه. برای افزایش آگاهی میشه از دورهها، کنفرانسها و تمرینهای متمرکز کمک گرفت.
نمونههای عملیاتی جلوگیری از زیاد شدن بدهی فنی:
معمولا تیمها روی نرمافزارهایی کار میکنن که میزان زیادی بدهی فنی داره. توسعه بیشتر روی این نرمافزارها میتونه منجر به ورشکستگی فنی بشه. از طرفی توقف کامل توسعه برای تمرکز کامل روی رفع بدهیهای فنی منطقی و منطبق با دنیای واقعی نیست. باید روشی میانه این دو رو انتخاب کنیم که بتونیم بدهیهای فنی رو به صورت مداوم و با نرخ صعودی بازپرداخت کنیم.
۱. شناسایی، مستندسازی و دنبال کردن بدهی
اولین قدیم تو بازپرداخت بدهی فنی شناسایی و مستندسازی بدهیهای فنیه. ابزارهای زیادی برای شناسایی بدهی در سطح کد (نه معماری، طراحی و …) وجود داره. در ادامه توسعهدهندهها و معماران سیستم هم میتونن بدهیها رو شناسایی کنن. بعد از شناسایی، بدهیهای فنی باید تو فرمت مناسبی مستندسازی بشن. میشه برای داکیومنت کردن از شیتهای اکسل یا هر ابزار دیگهای استفاده کرد. بعد از اون وضعیت بدهی در تیم باید مشخص بشه که آیا قراره فیکس بشه، اگه جواب مثبته چه زمانی؟
۲. اولویتبندی بخشهای بد
نرمافزار بدون بدهی وجود نداره و بهتره که وجود نداشته باشه. بدهیها رو باید مرتب کرد و بر اساس نرخ تغییر و میزان اثرگذاریشون روی نرمافزار اولویت بندی کرد. طبیعیه که تو نرمافزار بدهیهای با اولویت پایین وجود داشته باشه که سراغ حلشون نریم.
۳. شکستن بدهیها به بخشهای کوچک قابل انجام در هر دوره
تو دنیای مالی وام بزرگ رو به مرور و با مقادیر کوچک بازپرداخت میکنن. تو دنیای نرمافزار هم امکان بازپرداخت بدهی فنی بزرگ به صورت یکجا وجود نداره و بهتره بدهیها رو به بخشهای قابل انجام تو دوره کنترلی بشکونیم و انجام بدیم.
۴. ایجاد انگیزه برای نگهداشت کیفیت
آدمها به چیزهایی که قابل اندازهگیری و پاداش دادن هستن اهمیت میدن. اگه مدیر یه تیم به تعداد فیچرهای منتشر شده یا تعداد باگهای فیکس شده اهمیت بده، تیم به سمت تعداد فیچر بیشتر یا تعداد باگهای فیکس شده بیشتر حرکت میکنه. حذف و بهبود کیفیت نرمافزار هم باید به معیارهای مهم مدیرها تبدیل بشن.
۵. پیروی از قانون پیشآهنگی
قانون پیشآهنگی میگه «همیشه زمینی که واردش میشی رو تمیزتر از وقتی که واردش شدی ترک کن». تو دنیای نرمافزار خوبه که از قانون «کدی که داری روش کار میکنی رو تو وضعیت بهتری که تحویل گرفتی، تحویل بده» پیروی بشه. پیروی از این قانون بازپرداخت بدهی فنی رو به روتین تیم تبدیل میکنه.
۶. به دنبال موقعیت برای بازپرداخت بدهی فنیهای بزرگ بودن
بدهی فنیهای بزرگ مثل تغییر معماری و طراحی میتونن بدهی فنی رو به شکل چشمگیری کاهش بدن. باید همیشه به دنبال موقعیتهای زمانی مناسب برای بازپرداخت بدهیهای بزرگ بود.
۷. رفع افقی بدهی به جای عمودی
بدهیهای بخشهای مختلف روی همدیگه اثرگذارن. برای مثال بدهی تست و طراحی میتونن روی هم اثر بذارن. میشه برای اینکه کد قابل تست کردن باشه بعضی از طراحیها رو تغییر داد یا برعکس. بنابراین بهتره که بدهی مرتبط به یه موضوع به شکل همزمان رفع بشه. یعنی سراغ یک موضوع تو همه جنبهها بریم نه سراغ همه بدهیهای یه جنبه.
۸. بعضی بدهیها رو رها کنین
بعضی بدهیها حتی اگه خیلی بزرگ باشن هم ارزش بازپرداخت ندارن. مثلا بدهی فنی تو PoC یا نرمافزاری که نزدیک پایان عمرشه، ارزش بازپرداخت نداره یا زمانی که تصمیم به مهاجرت به استک جدید گرفته شده بهتره بدهیهای فنی تو کد قبلی پرداخت نشه و در زمان مهاجرت بهشون پرداخته بشه.
بدهی فنی ابعاد مختلفی داره، بعضیهاشون رو میشه تو دستههای زیر قرار داد:
نیازمندی بیانیهایه که یکی از ذینفعهای سیستم درباره یه فیچر یا ویژگی درون سیستم آماده میکنه. نیازمندی میتونه یه تسک تو بکلاگ یا چند خط نوشته تو یه داکیومنت باشه. نیازمندیها در واقع پل بین کسبوکار یا ارزشهای سازمان با پیادهسازی فنی هستن. ارزشی که برای مشتری ایجاد میشه از طریق نیازمندیها مشخص میشن.
یکی از روشهای تشخیص بدهی نیازمندی اینه که نرمافزار در نهایت ارزشی رو به مشتری نمیرسونه. وقتی ما اولویتهای پیادهسازیمون رو به سمت نیازمندیهایی ببریم که ارزش کمتری برای مشتری ایجاد میکنن، داریم به سمت زیاد کردن بدهی نیازمندی حرکت میکنیم. برای مثال، شواهد نشون میدن که ۲۰ درصد فیچرهای یه نرمافزار نیازمندی ۸۰ درصد از کاربرا رو پوشش میده، یعنی ۸۰ درصد فیچرها عملا بدون استفاده هستن. بدهی نیازمندی معمولا زمانی اتفاق میافته که به همه درخواستهای مشتریها بله بگیم یا نیازمندیها رو بدون تحلیل و بررسی و تصمیمگیری وارد کارها کنیم.
نقش بدهی فنی تو فرایند بررسی نیازمندیها چیه؟ دو مدل بدهی نیازمندی داریم:
۱.۱ مهندسی ضعیف نیازمندیها: وقتی برای استخراج نیازمندیها میانبر بزنیم، بدهی نیازمندی اتفاق میافته. برای مثال، وقتی با همه ذینفعهای درگیر صحبت نکنیم، نتیجه کارمون بیکیفیت یا با اولویتبندی اشتباهه. این شکست تو فرایند استخراج نیازمندیهاست. اشتباه تو این فرایند میتونه بدهیهای فنی رو تا مراحل نهایی به شکل تصاعدی زیاد کنه. به عنوان مثال ساخت یه وبسایت که با کاربران در ارتباطه بدون درک درست از نیازمندیهای حریم خصوصی (مثلا GPDR) میتونه منجر به توسعه نرمافزاری بشه که تو بدهیهای فنی غرق شده.
۱.۲. نادیده گرفتن نیازمندیها: این مدل بدهی منجر به پیادهسازی فیچرهای اشتباه به خاطر درک نادرستمون از نیازمندیهای مشتری میشه. این شکست تو فرایند توسعه نرمافزاره.
پیدا کردن بدهی تو بخش مهندسی ضعیف نیازمندیها کار سختیه. ابزار خودکاری برای پیدا کردن این نیازمندیها وجود نداره. تو خیلی موارد بخصوص تو قسمت نیازمندیهای غیرکارکردی، حتی دیتای مناسب و آماده تحلیل هم پیدا نمیشه. مثلا تیمی که میخواد اپلیکیشنی برای IoT خونهها بسازه باید درک عمیقی از نیازمندیها و محدودیتها قانونی داشته باشه.
دونستن این نیازمندیها و عمل نکردن بهشون به خودی خود بد نیست. همونطور که میدونیم ما گاهی آگاهانه میپذیریم که بدهی فنی داشته باشیم. برای مثال اوبر با اینکه از مجوزهای لازم برای شروع کارش آگاه بود، اول تاکسی اینترنتیش رو راه انداخت و بعد به دنبال گرفتن مجوزهای قانونی رفت. اگه خیلی دیر بشه، بدهی نیازمندیها رو معمولا میشه از بازدید یه نهاد رگولاتور یا شکایت از طرف یه نهاد یا مشتری ناراضی پیدا کرد.
برای اینکه خوشحالتر باشیم، باید یه فرایند مشخص برای جمعآوری و استخراج نیازمندیها وجود داشته باشه که تو این فرایند نیازمندیها تجمیع و اولویتبندی بشن و بعد با استفاده از یه ابزار مدیریت کارهای مناسب تبدیل به درخواستهای قابل انجام بشن و در نهایت وضعیت انجامشون قابل ردیابی و مشاهده باشه.
برای مدیریت بدهی نیازمندیها باید به دنبال استخراج درست نیازمندیها و آگاهانه کار کردن روی چراییشون باشیم. به این معنی که مدام از خودمون بپرسیم چرا باید به این نیازمندی دقت کنیم و داریم روی چه چیزهایی کار میکنیم. سوال «چرا داریم روی این فیچر کار میکنیم؟» همیشه باید یه جواب سریع و قانعکننده داشته باشه، در غیر اینصورت احتمالا درگیر بدهی نیازمندی هستیم.
برای جلوگیری از زیاد شدن بدهی نیازمندیها باید تو مرحله استخراج نیازمندیها دقیق باشیم و از روش جمعسپاری برای جمعآوری اطلاعات استفاده کنیم. استخراج نیازمندیها تو روشهای اخیر توسعه نرمافزار معمولا به مدیر محصول یا مدیر پروژه (PM) اختصاص داده میشه و انجام درستش نیاز به تمرین و مهارت ارتباطی خوب داره. PM باید بتونه با مشتری و توسعهدهنده ارتباط بگیره و زبان مشترک بینشون باشه.
استخراج نیازمندیها روشهای متفاوتی داره. بررسی پروداکشن و اینکه مشتریها با کدوم بخش محصول بیشتر ارتباط دارن، چقدر طول میکشه که به جایی که نیاز دارن برسن، کدوم فیچرها اصلا استفاده نمیشن و …
مدیر محصول یا مدیر پروژه باید با بخش مشتریان ارتباط داشته باشه و به کمک جمعآوری دیتا از بخشهای مختلف (مشتریان جدید، مشتریان فعلی و …) نیازمندیها رو استخراج کنه. صرف زمان چند ساعته روی نیازمندیها میتونه جلوی چندین ساعت پیادهسازی دوباره یا تغییر معماری رو در آینده بگیره.
در نهایت PM باید بتونه نیازمندیها رو به زبان قابل درک و مدیریت برای توسعهدهنده تبدیل کنه:
در کنار اینها PM باید همیشه نیازمندیها رو بهروز نگه داره. برای مثال اگه PM از DOORS برای مدیریت نیازمندیها استفاده میکنه و توسعهدهنده از Jira، در این صورت وظیفه همگام کردن DOORS با Jira به عهده PM هست.
طراحی (Design) به معنی مدل ارتباطی و ساختاربندی در سطح کد و معماری (Architecture) ساختاربندی High-Level هستن. برخلاف بدهیهای پیادهسازی، بدهیهای طراحی و معماری سختتر قابل تشخیص هستن و معمولا با بررسی تاریخچه پروژه و روند تغییراتش قابل شناساییان. بدهی طراحی به شکل مستقیم روی موفقیت پروژه در بلند مدت تاثیرگذاره.
دلایل زیادی برای به وجود اومدن بدهی طراحی وجود داره. برای مثال:
طراحی خوب، طراحیای هست که انسجام (Cohesion) رو افزایش بده در حالی که درهمتنیدگی (Coupling) رو کم میکنه. هر کاری که این اصل رو نادیده بگیره، بدهی طراحی رو به محصول اضافه میکنه.
بیشتر توسعهدهندهها و معماران سیستم حسی کلی نسبت به بدهیهای تجمیع شده تو نرمافزار دارن. میتونن بدهی رو حس کنن، میتونن تاثیرش رو روی کارهای روزانهاشون ببینن اما نمیتونن دقیق بگن از کجاست و کجا تاثیر گذاشته. حالا چجوری باید بدهی رو اندازه بگیریم؟
برای عددی کردن بدهی بهتره اول تعریف دقیقی از بدهی طراحی داشته باشیم. بدهی طراحی ۱. گروهی از ماژولهای مرتبط به هم (معمولا فایلهای مرتبط به هم) و ۲. مدلی که هزینه نگهداری این ماژولها در طول زمان رو مشخص کنه، هست.
در درجه اول، چطور باید ماژولها (فایلها)ی مرتبط به هم رو پیدا کنیم؟ دو گام ساده برای این کار وجود داره. اولین گام بررسی وابستگیهای استاتیک بین فایلهاست. این کار با ابزارهای مختلف مثل Understand قابل انجامه. دومین گام بررسی رشد این وابستگیها در طول زمانه. برای این کار باید بررسی کنیم که آیا دو فایل با هم و همزمان تغییر میکنن یا نه. بهترین ابزار برای این کار، ورژن کنترلها هستن.
برای درک بهتر این وابستگیها میشه از ماتریس ساختار طراحی (DSM) استفاده کرد. اسپارس بودن این جدول نشونه خوبیه و به این معنیه که وابستگی شدیدی بین ماژولها وجود نداره.
به این ماتریس اطلاعات زیادی میشه اضافه کرد: تعداد باگهای فیکسشده که به این فایلها ارتباط دارن، تعداد کامیتهای مرتبط و … . در نهایت با بررسی این ماتریس و مدل وابستگی کدها میشه مشکلات مشابه رو تشخیص داد و برای رفعشون برنامهریزی کرد. بدهیهای طراحی معمولا به شش ضدالگو طراحی میرسن:
۱. اینترفیس ناپایدار (Unstable interface): وقتی یه اینترفیس همزمان با تغییر فایلهایی که ازش استفاده کردن، تغییر میکنه.
۲. اشکال در ماژولاریتی (Modularity violation): وقتی ماژولهای مجزا از هم، با هم تغییر میکنن.
۳. وراثت ناسالم (Unhealthy inheritance): وقتی یه کلاس پایه به یکی از سابکلاسهاش وابسته است یا یه کلاینت به کلاس پایه و سابکلاسش همزمان وابستهاست.
۴. وابستگی چرخهای (Cyclic dependency): وقتی چند فایل یا ماژول یه گراف به شدت متصل رو ایجاد میکنن.
۵. چرخه پکیج (Package cycle): وقتی دو یا بیشتر از دو پکیج به هم وابستهان.
۶. عبور (Crossing): وقتی یه فایل با وابستگی ورودی و خروجی بالا، همزمان با هر تغییر باعث تغییر در فایلهای دیگه میشه.
تحقیقات نشون میده که این ضدالگوها رابطه مستقیمی با باگها، تغییرات و زمان تغییر دارن.
بعد از شناسایی و اختصاص عدد (میزان کاری که برای بازپرداخت نیاز داریم) باید از خودمون بپرسیم که کدوم گروهها (مجموعه ماژولها و فایلها) هزینه بیشتری بهمون تحمیل میکنن؟ آیا رفع این بدهی فنی، یه تصمیم اقتصادی هست یا نه؟ به عنوان مثال گروه کوچکی که نرخ تغییراتش زیاده ارجحیت بیشتری نسبت به گروه بزرگی داره که نرخ تغییراتش خیلی پایینه.
مانیفست اجایل بهمون میگه که «بهترین طراحی و معماری از دل تیمهای خودمدیریت کننده به وجود میان». این جمله شاید برای تیمها و نرمافزارهای کوچک (وقتی که تصمیمات طراحی و معماری کم هستن) جواب بده اما برای سیستمهای بزرگتر این اصل هیچوقت جواب نداده. بررسیهای مختلف نشون میده که بسیاری از طراحیها -طراحیهای خوب- از دل تیم و خود به خود به وجود نمیان.
بهترین راه جلوگیری از بدهی طراحی اینه که از اول نداشته باشیمش. خیلی از سیستمها بدون هیچ نگاه کلانی به طراحی و معماری پیادهسازی میشن. برای از بین بردن بدهی طراحی، باید به صورت مداوم نتایج ضدالگوهای طراحی رو کم کنیم.
«کد مثل جوکه، اگه نیازه توضیحش بدی، خوب نیست» – Cory House
تو موارد پرشماری، کدهایی که توسعهدهنده چند سال پیش به عنوان Prototype پیاده کرده الان «محصول» ماست. نرمافزاری که پر از حفرههای امنیتیه، نمیشه Scale اش کرد و احتمالا نگهداری ازش خیلی هزینهبره. نوشتن کد خوب مثل سالم زندگی کردنه -ورزش کن و به اندازه، غذای خوب بخور- اما درست مثل سالم زندگی کردن، تداوم این کار خیلی سخته و خیلیها کلا رعایتش نمیکنن.
نوشتن کدهای خوب، با قابلیت نگهداری و تست شده باید تو رژیم کدنویسی روزانه همه باشه. کد غیر مهمی که امروز نوشته میشه میتونه تبدیل به نقطه بحرانی کسبوکار تو چند سال آینده بشه. توییتر و استفاده از Ruby تو شروع کارش نمونه خوبی برای این طرز فکره. اونا در نهایت مجبور به تغییرات سخت با هزینههای بسیار بالا شدن.
بدهی پیادهسازی به خاطر استفاده از میانبرها تو کد به وجود میاد. بعضی از این میانبرها اجتنابناپذیرن (کپی کردن یه بخش کد برای یه فیچر جدید) اما همیشه باید آماده تمیزکاری به موقع باشیم. مهمترین بدهیهای پیادهسازی شامل استایل کد، کد ناکافی، کد دپریکیت شده و کد کپی شده هستن.
برای بیشتر بدهیهای پیادهسازی میشه از ابزارهای بررسی استاتیک کد استفاده کرد. در کنار این ابزارها بهتره هر نرمافزار یه روش رو برای پیروی استایلها رو بپذیره و همه توسعهدهندهها از اون استفاده کنن.
زبان برنامهنویسی باید با توجه به دامنه مساله و نیازمندیهای نرمافزار انتخاب بشه. بعد از انتخاب زبان، برای جلوگیری از بدهیهای پیادهسازی بهتره روی کتابخونهها و پکیجهای ثانویهای که تو کد استفاده میشه حساس بود. ریویو کدها به صورت Peer Review باید وجود داشته باشه و بهتره که حداقل دو نفر جدا از توسعهدهنده اصلی این کار رو به عهده بگیرن. برای هر ریویو جدا از بررسیهای خودکار تغییرات (بررسی استایل و …) باید دامنه تغییر رو کمینه کرد. استفاده از پیامهای جامع و کامل برای کامیتها هم میتونه به جلوگیری از بدهیها کمک کنه. پیام خوب کامیت شامل تغییر و جزییاتش در صورت نیازه. استفاده از پیامهای کلی مثل Minor change/Minor improvement در نهایت کار رو پیچیده و بدهی رو زیاد میکنن. در نهایت، برای هر تغییر باید تستهای خودکار پیش از انتشار نسخه هم وجود داشته باشه.
تستها میتونن بدهیهای احتمالی فنی و باگها رو نمایان کنن. با این وجود خود تستها هم میتونن بدهی فنی داشته باشن. برنامهنویسها معمولا نرمافزارشون رو بدون تست مناسب به پروداکشن میبرن. دلایل زیادی برای این کار وجود داره اما یکی از مهمترینهاش ددلاینهای زمانیه.
در ابتدای عمر نرمافزار اصلاح یه باگ کاریه که میشه تو چند ساعت و نهایت یک روز انجامش داد اما با رشد نرمافزار، بزرگ شدن تیمها، رفتن و اومدن آدما و موج فیچرهای جدید، اصلاح باگها میتونن روزها زمان ببرن. امکان داره فیچرهای خرابی وجود داشته باشه که کسی از وجودشون خبر نداره. تمام چابکی اول کار میتونه به سادگی و به مرور از بین بره.
تست راهی برای اعتبارسنجی اینکه آیا کد و سیستم برای اساس نیازمندیها، درست کار میکنن یا نه رو فراهم میکنه. تست میتونه تو سطوح مختلف انجام بشه. از تست یه متد خاص گرفته تا تست کل محصول و ارتباط اجزاش با هم.
بدهی فنی تو تستها زمانی به وجود میاد که تو فرایند تست کردن میانبرهایی رو به وجود بیاریم. هدف تست باید پوشش همه جانبه برنامه باشه. با اینکه تو دنیای واقعی، هیچوقت نمیشه به این سطح از تست کردن رسید اما هدفمون باید همین باشه.
برای اندازهگیری پیچیدگی یه نرمافزار از متریکهای مختلفی استفاده میشه. یکی از معروفترینهاشون متریک McCabe یا پیچیدگی سایکلومتیک (Cyclomatic complexity) هست که تعداد مسیرهای مستقل یه کد رو اندازه میگیره. به عنوان مثال برای این شبه کد (که دو مسیر داره) باید حداقل دو تست وجود داشته باشه.
if (some condition) { do something return } do something else
بدهی تست رو میشه تو دستههای زیر قرار داد:
به طور کلی تستها باید این اطمینان رو بهمون بدن که کد فعلی درست کار میکنه و تغییرات آیندهمون، عملکرد فعلی رو خراب نکرده. بدهی تست رو میشه با نشانههایی مثل زیاد شدن تعداد باگهای گزارششده، زیاد شدن زمان رفع باگها و به وجود اومدن باگهای جدید بعد از رفع باگهای فعلی، تشخیص داد. استفاده از ابزارهای بررسی پوشش تست (Test Coverage) هم میتونه نیازمون به اضافه کردن تستها رو مشخص کنه.
نود و نه درصد کار، تحویل کاره. – Buddy Hackett
بدهی استقرار به میانبرها، خطاها و اشتباهاتمون در زمان استقرار و عملیاتی کردن یه سیستم، مرتبطه. همه توسعهدهندهها با مفاهیم استقرار نرمافزار تو اسکیل بزرگ آشنا نیستن. استقرار نرمافزار به معنی روشهاییه که نرمافزار رو در اختیار کاربر نهایی قرار میده. نرمافزاری که در اختیار کاربر نهایی قرار میگیره رو «محصول» یا «محیط پروداکشن» صدا میکنیم. هدف کاهش بدهی استقرار، بهینه کردن شیوه تحویل محصول به کاربر نهاییه.
بدهی استقرار زمانی زمانی اتفاق میافته که نرمافزار رو با کار فشرده و دستی مستقر کنیم. این روش امکان استقرار بهینه و سریع رو بدون دخالت اعضای تیم از بین میبره. جدا از این، بدهی استقرار به عنوان مرجع خطاهای مختلف میتونه منجر به هرجومرج بشه و یه کسبوکار موفق رو به ورشکستگی برسونه.
بدهی استقرار رو میشه با استفاده از نشانههای مختلف شناسایی کرد:
استراتژیهای متفاوتی برای مدیریت بدهی استقرار وجود داره:
برای جلوگیری از بدهی استقرار استراتژیهای مختلفی وجود داره:
مستند نامه عاشقانهایه که به خودمون در آینده مینویسیم. – Damain Conway
بدهی مستندسازی به خاطر میانبر زدن در زمان مستندسازی به وجود میاد. زمانی که مشخصات طراحی رو بروز نمیکنیم یا کامنتهای قدیمی کدها رو همونطور نگه میداریم. بدهی مستندسازی با مستندکردن بدهیهای فنی تفاوت داره. مستندسازی معمولا کاریه که کسی سراغش نمیره و یکباره به عضو جدید تیم سپرده میشه اما هر کسی مدتی با نرمافزارها کار کنه متوجه میشه که مستندات میتونه زمان درک نرمافزار رو به شکل چشمگیری کاهش بده. قانون اصلی مستندسازی اینه که تنها زمانی باید یه چیزی رو مستند کنیم که ارزشش از هزینهای که میپردازیم بیشتر باشه.
به دلیل اینکه توسعهدهندههای نرمافزار معمولا علاقهای به نوشتن ندارن، ایجاد انگیزه برای مستندسازی معمولا دشواره. بدهی مستندسازی زمانی مشخص میشه که به بخشی از معماری، طراحی یا کد نگاه کنیم و دنبال دلیلش باشیم اما دلیلش رو پیدا نکنیم. مستندسازی میتونه یه کامنت کوتاه روی کد باشه یا یه بحث مفصل روی درخواست مرج یا حتی یه سند مجزا روی سیستم مدیریت اسناد تیم. مستندهایی که مسیر سختی برای پیدا کردن جواب بهمون میدن هم مناسب نیستن. این مدل مستندات هزینه نگهداری بالایی دارن و امکان اشتباه رو هم زیاد میکنن.
اگه بعد از یه تصمیم بزرگ چرایی و چگونگی اجراش رو مستند نکنیم یا مستندات نامناسب بسازیم، شانس ایجاد بدهی مستندسازی رو زیاد کردیم. مقاله “A Rational Design Process: How and Why To Fake It” (Parnas and Clements, 1986) هفت ویژگی مستند مناسب رو مطرح کرده. رعایت نکردن هر کدوم از این هفت اصل میتونه به بدهی مستندسازی منجر بشه.
تو پروژههای کوچک و استارتآپها طبیعیه که کدها جلوتر از مستندات باشن. چرا که پروژه و استارتآپ ممکنه قبل از احساس نیازش به مستندات از هم بپاشه. نیاز به مستندسازی رو میشه تو جدول زیر خلاصه کرد.
زمانی که کانتکست مستندسازی مشخص شد، نشانههایی خودشون رو نشون میدن که مشخص میکنن آیا بدهی مستندسازی وجود داره یا نه.
چه چیزهایی باید مستند بشه
فرایند مدیریت بدهی مستندسازی شامل کارهایی هست که ساخت و نگهداری مستندات رو در محلی مشخص آسون میکنه.
جدا از هفت اصل مطرح شده برای تشخیص اینکه یه مستند مناسب هست یا نه دو روش دیگه وجود دارن که باهاشون میشه مناسب بودن مستند رو تشخیص داد.
اینکه پروژهها فقط به دلایل فنی شکست بخورن، خیلی نادره. بیشتر شکستها به دلیل مشکلات ارتباطی یا مدیریتی اتفاق میفتن. ساختار سازمان نقش مستقیمی روی پروژه و به طبع روی کدها و حتی فرایند تست و کنترل کیفیت داره. ارتباطات، مدیریت و ساختار تیم تاثیرات بزرگی روی بدهیهای فنی نرمافزار دارن.
مهندسی نرمافزار به شکل سنتی روی مصنوعات -کدها، فرایندها، ابزار و …- تمرکز میکنه، با این حال افراد و تیمهاشون، ساختار و ویژگیهای افراد و تمام چیزهایی که با آدمهایی که واقعا با این مصنوعات در اتباطن، اهمیت دارن. ملوین کانوی (Melvin Conway) تو سال ۱۹۶۸ قانونی رو معرفی میکنه که حالا به اسم قانون کانوی معروفه: «ساختار و معماری نرمافزار بازتابی از ساختار سازمانیه که تولیدش کرده.» برای مثال یه کامپایلر COBOL که توسط پنج نفر توسعه داده شده، تو پنج فاز اجرا میشه، یا یه کامپایلر ALGOL که سه توسعهدهنده داشته تو سه فاز اجرا میشه.
از اونجایی که راههای زیادی برای ایجاد ساختار سازمان و ساختار سیستمها وجود داره، میشه نتیجه گرفت که تعدادی از این ساختارها غیربهینه هستن. بدهی اجتماعی رو به عنوان اختلاف ساختار سیستم و سازمان تعریف میکینم. علاوه بر این، منطقیه که بعضی از نگاشتهای ساختار سازمانی روی ساختار سیستم هم غیربهینه باشن.
بدهی اجتماعی هزینه اضافهایه که به خاطر انواع ناسازگاریهای فنی به پروژه تحمیل میشه. برای مثال اثر سیلو رو در نظر بگیرین: ساختار سازمان از سیلوهایی تشکیل شده که ارتباط کمی با هم دارن. اگه نتیجه کار هر سیلو ورودی کار سیلوی دیگهای باشه، مشکلات شروع میشن. برای مثال یه سیلو مستندات لازم برای کار سیلوی دیگه رو با فرض اینکه به مستند نیازی نیست، آماده نکرده یا یه تیم از مشکل کارایی نرمافزار تیم دیگه مطلع نیست و راه خودش رو برای بهبود سیستم پیاده کرده که منجر به مشکل بیشتر برای تیم اول شده.
به طور کلی تطابق اجتماعیفنی (Sociotechnical Congruence) حالت ایدهآل کار روی یه پروژه است به این معنی که کامپوننتهایی که به شدت با هم در ارتباط هستن باید توسط تیمهایی که ارتباطات مداوم دارن (و یکجا هستن) توسعه داده بشن.
مشابه Code Smell ها که برای شناسایی اشکالات کد ازشون استفاده میکنیم. از مفهوم Community Smells برای شناسایی و اصلاح ساختارها و رفتارهای غیربهینه اجتماعی استفاده میکنیم.
همه ما بخشی از شبکههای مختلفیم: تو خونه، تو زندگی اجتماعی و تو زندگی کاریمون. اگه درباره چگونگی چینش شبکههای اجتماعی فکر کنیم و تاثیرش رو در محیط کسبوکار ببینیم متوجه میشیم که بعضی از حالات این شبکهها غیربهینه هستن.
غیربهینگی ساختار شبکه معمولا برای تیمهای کوچک (سه تا پنج نفره) مشکلساز نیست. وقتی تیم بزرگتر بشه و انتظار داشته باشیم جریان دانش و تصمیمات توی تیم وجود داشته باشه، مساله پیچیدهتر و زمانبر میشه. مثلا برای یه تیم دوازده نفره میتونه شصت و شش راه ارتباطی متفاوت تولید کنه. حالا اگه این تیم دوازده نفره رو به سه تیم چهار نفره تبدیل کنیم چه اتفاقی میافته؟ اگه این سابتیمها با هم ارتباط زیادی داشته باشن، چیزی رو عوض نکردیم اما اگه این ارتباط کم باشه احتمال اینکه تصمیم یه تیم روی تیم دیگه به شدت اثرگذار باشه وجود داره.
بیشتر سازمانها کار بهتری نسبت به این مدل ساده و پیشپا افتاده انجام میدن. هدف این مثالها این بود که ببینیم ساختار چطور روی بهینه کار کردن اثرگذاره. برای شناسایی بدهی اجتماعی باید ببینیم که کدوم الگوهای اجتماعی مخربن.
بیشتر سازمانها با Time Warp و Cognitive Distance و DevOps Clash دست و پنجه نرم میکنن. همچنین تحقیقات نشون میده که بدهیهای معماری ارتباطی مستقیمی با Smellهای ارتباطی دارن. (حدود ۸۰ درصد همسبتگی)
هدف نهایی ما در زمان توسعه نرمافزار مدیریت بدهیهاست و نه جلوگیری کامل ازشون. مدیر پروژه یا مدیر تیم باید به صورت مداوم وجود بدهیهای اجتماعی رو بررسی کنه.
مدیران باید گروههای بههمپیوسته بسازن، ترجیحا از نظر مکانی نزدیک به هم باشن و امکان ارتباط بینشون وجود داشته باشه. زمانی که امکان نزدیکی مکانی به صورت فیزیکی وجود نداره باید روشها و ابزارهای ارتباطی الکترونیکی مناسب (چت تصویری، پیامرسان، جلسات روزانه و …) وجود داشته باشه.
در کنار این روشها میشه با ابزارهایی مثل CodeFace4Smells یا CodeFace مدلهای ارتباطی سازمانی و فعالیت افراد رو بررسی و ضدالگوهای ارتباطی سازمان رو با کمکشون استخراج کرد اما به طور معمول میشه بدون این ابزارها هم ضدالگوهای ارتباطی رو پیدا کرد. روشهایی مثل گفتگوهای یکبهیک دورهای میتونن به پیدا کردن این ضدالگوها کمک کنن.
مثل بدهی مالی، اگه بدهی اجتماعی رو پرداخت نکنیم با گذر زمان بزرگ و بزرگتر میشه. به عنوان مثال، مطالعات اخیر نشون میده که تعداد Smellهای ارتباطی تو تیمهای چابک با گذشت هر ماه به شکل خطی افزایش پیدا میکنه. هیچ دلیلی وجود نداره که فکر کنیم تیمهای چابک از این قاعده مستثنی هستن. در ادامه بعضی از Smellها و روشهای احتمالی حلشون رو میبینیم.
تو دنیای واقعی اگه بخوایم نرمافزاری با عمر طولانی داشته باشیم، باید وجود بدهی درونش رو بپذیریم. با این حال باید به صورت مداوم حدود ۲۰ درصد یا بیشتر زمانمون رو صرف شناسایی و بازپرداخت این بدهیها کنیم.