نکات کلیدی
- وقتی پای معماری نرمافزار در میان است، اشتباه بودن اجتنابناپذیر است. هنر معماری این است که فقط مقدار کمی زمان را صرف رفتن در مسیر اشتباه کنید. تنها راه تصمیمگیری این است که آزمایشها را اجرا کنید و دادههایی را جمعآوری کنید که بتوانند این تصمیمها را آگاه کنند.
- معماریهای حداقلِ قابلاتکا (Minimum Viable Architectures یا MVAs) از آزمایشهایی تشکیل میشوند که قابلیتپذیری تصمیمهای معماری را میآزمایند. این آزمایشها بازخوردی جمعآوری میکنند که به تیم توسعه امکان میدهد تصمیمهایش را بازبینی کند.
- MVAها همچنین آزمایشهایی دربارهٔ MVPهای خودشان هستند؛ آنها قابلیتپذیری MVP را از منظر فنی میآزمایند. اگر MVP از نظر فنی قابلاتکا نباشد، پس هیچ ارزش کسبوکاریای در MVP وجود ندارد.
- یک آزمایش چیزی بیشتر از فقط امتحان کردن یک چیز برای دیدن اینکه کار میکند یا نه است. هر انتشار محصول مجموعهای از آزمایشها دربارهٔ ارزش و قابلیت پشتیبانی است. بازخورد این آزمایشها به تیمهای توسعه کمک میکند هم ارزش محصول و هم قابلیت پشتیبانی آن را بهتر کنند.
- آزمایشهای معماری همچنین باید کارِ «پشتیبانی و تغییر» را پیشبینی کنند.
اشتباه بودن آزاردهنده است، گاهی خجالتآور است، و با این حال… اجتنابناپذیر. مخصوصاً در رابطه با معماری نرمافزار، اگر شما هیچوقت اشتباه نمیکنید یعنی به اندازهٔ کافی خودتان را به چالش نمیکشید، و یاد نمیگیرید. اما اشتباه بودن از نظر روانشناختی به اندازهای دردناک است که بیشتر مردم از آن اجتناب میکنند، عمدتاً با این کار که هرگز کارشان را چک نمیکنند.
بعضیها فکر میکنند نمیتوانند معماریِ یک محصول نرمافزاری را بدون ساختن کل آن تست کنند. اما معماری نرمافزار یک چیزِ واحد نیست، نتیجهٔ تعداد بسیار بسیار زیادی تصمیم است، که هر کدام از آنها میتواند جدا شود و از طریق آزمایش ارزیابی شود.
و در حالی که نمیتوانیم همیشه از اشتباه بودن اجتناب کنیم، میتوانیم هزینهٔ اشتباه بودن را با اجرای آزمایشهای کوچک برای تست فرضیاتمان کاهش دهیم و تصمیمهای اشتباه را قبل از اینکه هزینههایشان انباشته شود برگردانیم. اما اینجا زمان دشمن است: هیچوقت زمان کافی برای تست هر فرضیهای وجود ندارد و پس دانستن اینکه با کدامها روبهرو شویم هنرِ معماری کردن است.
معماری موفق یعنی آزمایش کردن برای تست تصمیمهایی که بر معماری سیستم اثر میگذارند، یعنی آن تصمیمهایی که اگر در آنها اشتباه کنید برای موفقیت چیزی که میسازید «مرگبار» هستند.
دانستن اینکه چه چیزی را تست کنیم نصف مسئله است؛ نصف دیگر طراحی آزمایشهای مؤثر اما کمهزینه است که نقصها را در فرضیات فرد آشکار میکند.
ایدهٔ کلیدی: Minimum Viable Architecture (MVA)
این ایدهٔ کلیدی پشتِ مفهومی است که آن را Minimum Viable Architecture (MVA) مینامیم، که یک مجموعه از تصمیمها است که شما باور دارید باعث میشود افزونهٔ سیستم یا محصول، یعنی Minimum Viable Product (MVP)، که روی آن کار میکنید بتواند در طول زمان بهصورت پایدار ارزش ارائه کند.
در این مقاله، ویژگیهای یک آزمایش خوب را بررسی میکنیم.
در مقالهٔ ذکرشده، مشاهده کردیم:
تنها راه برای دانستن اینکه این تصمیمها معقول هستند یا نه این است که آزمایشها را اجرا کنیم و داده جمعآوری کنیم. این آزمایشها مقرونبهصرفه بودن، قابلیتپذیری، پایداری، و قابلیت پشتیبانی MVP را میآزمایند. MVA بازتاب مصالحههایی است که تیم توسعه برای دستیابی به اهداف معماری MVP انجام میدهد. چون هر انتشار یک MVP با یک MVA مرتبط است، هر انتشار یک مجموعه از آزمایشها دربارهٔ ارزش و قابلیت پشتیبانی است. هدف انتشار این است که به مشتریان ارزش ارائه کند و بازخوردی جمعآوری کند دربارهٔ اینکه انتشار تا چه حد نیازهای مشتریانش را برآورده میکند، هم امروز و هم در طول عمر سیستم.
اگر تیم آزمایشهای معماری اجرا نکند، تصمیمهایشان فقط حدس هستند، مبتنی بر فرضیات، دربارهٔ اینکه راهحل باید چه باشد. اگر این حدسها اشتباه از آب دربیاید، آنوقت به خاطر اثرشان، برگرداندنشان بسیار پرهزینه خواهد بود و حتی ممکن است محصول/پروژه را بکشد.
برای مثال، یک تیم ممکن است تصمیم بگیرد از یک پایگاهدادهٔ برداری برای توسعهٔ یک سرویس اختصاصیِ تشخیص تقلب در یک مؤسسهٔ مالی با استفاده از Machine Learning استفاده کند. بر اساس تحقیقشان، استفاده از محصول پایگاهدادهٔ برداری ممکن است توسعهٔ MVP آنها را سریعتر کند، و یکی از اعضای تیم تجربهٔ محدودی با آن محصول دارد. بهعنوان یک آزمایش، آنها تصمیم گرفتند یک use case کوچکِ تشخیص تقلب را با استفاده از آن محصول پیادهسازی کنند و بهبود بهرهوری را اندازهگیری کنند.
اما مشخص میشود که دستاوردهای بهرهوریِ مورد انتظار محقق نشد، چون محصول خیلی سختتر از چیزی بود که انتظار میرفت. رابط برنامهنویسی پایگاهدادهٔ برداری با پارادایمهای برنامهنویسیای که تیم انتخاب کرده بود همخوانی نداشت، و رسیدن به اهداف کارایی با استفاده از آن محصول هم چالشبرانگیز بود. بر اساس آزمایششان، تیم فهمید که استفاده از یک پایگاهدادهٔ برداری تحویل MVP آنها را به تأخیر میاندازد و احتمالاً تهدید میکند، و تصمیم گرفتند از آن محصول استفاده نکنند.
MVAها همچنین آزمایشهایی هستند که قابلیتپذیری فنیِ MVP را میآزمایند
یک راه برای فکر کردن به یک MVA این است که از یک یا چند آزمایش تشکیل شده است که پایداری بلندمدتِ ارزش ارائهشده توسط افزونهٔ محصول، یا MVP، را میآزمایند. همانطور که در یک مقالهٔ قبلی مشاهده کردیم، مفهوم (MVP) میتواند به تیمها کمک کند روی ارائهٔ چیزی که فکر میکنند برای مشتریان بیشترین ارزش را دارد، زودتر، تمرکز کنند، تا بتوانند سریع و کمهزینه اندازهٔ بازار محصولشان را قبل از سرمایهگذاری زمان و منابع قابلتوجه برآورد کنند. بنابراین هر MVP یک مجموعه از آزمایشها است که ارزشی را که افزونهٔ محصول به مشتریان ارائه میکند میآزماید.
MVAها مهم هستند چون یک MVP فقط smoke and mirrors (یا wishful thinking) است تا وقتی که شما یک MVA داشته باشید که بتواند آن را پشتیبانی کند. ما نمونههای زیادی دیدهایم که یک ذینفع کسبوکاری با یک نوآوری کسبوکاری جدید و جسورانه میآید که هیچ توجهی به اینکه ایده چگونه یا آیا اصلاً قابل تحقق است ندارد.
MVPها محدود به start-upها نیستند، چون هر برنامهای یک انتشار اولیه دارد که میتوان آن را بهعنوان یک MVP در نظر گرفت. MVPها یک جزء مفید از استراتژیهای توسعهٔ محصول هستند. برخلاف پروتوتایپهای صرف، MVPها قرار نیست «دور انداخته شوند».
نحوهای که گاهی دربارهٔ MVAها حرف میزنیم طوری به نظر میرسد که انگار آنها آزمایشهایی جدا از MVP هستند اما اینطور نیست، همانطور که در یک مقالهٔ قبلی بحث کردیم. چیزی که وقتی یک تیم توسعه روی MVP خودش کار میکند اتفاق میافتد این است که آنها دائماً تصمیمهای معماری میگیرند دربارهٔ اینکه محصول چگونه به اهداف معماریاش خواهد رسید. این تصمیمها همان MVA هستند.
ویژگیهای آزمایشهای معماریِ مؤثر
یک آزمایش چیزی بیشتر از فقط امتحان کردن یک چیز برای دیدن اینکه کار میکند یا نه است؛ این یک تست است که بهطور مشخص طراحی شده تا یک فرضیهٔ مشخص را تأیید یا رد کند. یک فرضیه یک پاسخ بالقوه به یک سؤال است که یک تیم دربارهٔ مناسب بودن راهحلش دارد. آزمایشها نمیتوانند ثابت کنند چیزی درست است، فقط میتوانند ثابت کنند چیزی غلط است، اما این هنوز مفید است چون یعنی اگر آزمایشهایتان را درست طراحی کنید، میتوانید بفهمید کدام فرضیاتتان نادرست هستند، قبل از اینکه غافلگیریهای ناخوشایند ایجاد کنند.
اگر یک آزمایش اجرا نکنید دارید فرض میکنید از قبل پاسخ یک سؤال را میدانید. تا وقتی اینطور باشد، یا تا وقتی ریسک و هزینهٔ اشتباه بودن کوچک باشد، ممکن است نیاز نباشد آزمایش کنید. اما بعضی سؤالهای بزرگ، فقط با آزمایش کردن قابل پاسخ دادن هستند. چون احتمالاً نمیتوانید برای همهٔ سؤالهایی که باید جواب دهید آزمایش اجرا کنید، شما بهطور ضمنی ریسک مرتبط را میپذیرید، پس باید بین تعداد آزمایشهایی که میتوانید اجرا کنید و ریسکهایی که نمیتوانید با آزمایش کردن کاهش دهید یک مصالحه انجام دهید.
چالش در ساختن آزمایشهایی که هم MVP و هم MVA را تست میکنند این است که سؤالهایی بپرسید که فرضیات کسبوکاری و فنیِ هم ذینفعان و هم توسعهدهندگان را به چالش بکشد. این آزمایشها باید به اندازهٔ کافی کوچک باشند که سریع بازخورد جمع کنند اما به اندازهٔ کافی مهم باشند که با ریسکهایی که تیم با آنها مواجه است روبهرو شوند.
در متن MVA، این یعنی روبهرو شدن با ریسک اینکه تصمیمهای معماریای که تیم میگیرد ممکن است اشتباه باشند. ترتیبی که تیم این کار را انجام میدهد با این سؤالها هدایت میشود: «کدامیک از تصمیمهایی که گرفتهایم اگر اشتباه از آب دربیاید بیشترین آسیب را میزند» و «کدامیک از این رخدادها احتمال بیشتری دارد رخ دهد». ما دیدهایم این بحث مفید است، اما لازم نیست طولانی باشد؛ بیشتر تیمها ایدهٔ نسبتاً خوبی دارند از اینکه کدام تصمیمها شبها خواب را از چشمشان میگیرد.
همانطور که در مقالهای دیگر اشاره کردیم، هر نیازمندی، از جمله Quality Attribute Requirements (QARs) که طراحی معماری را هدایت میکنند، نمایانگر یک فرضیه دربارهٔ ارزش است. صریح کردن این فرضیهها و آگاهانه طراحی کردن آزمایشها به تیم کمک میکند از فرضکردن دربارهٔ راهحلش اجتناب کند.
آزمایشهای معماریِ مؤثر اینها هستند:
Atomic: آنها هر بار با یک سؤال سروکار دارند. اجرای بیش از یک آزمایش همزمان نتایج را مبهم میکند و معمولاً گرفتن بازخورد مهم را به تأخیر میاندازد.
Timely: آنها ریسک را به تکههای کوچک و قابل مدیریت میشکنند تا سریعتر بازخورد بگیرند و تفسیر نتایج را سادهتر کنند.
Unambiguous: آنها معیارهای موفقیت روشن و خروجیهای قابل اندازهگیری دارند. یک آزمایش صرفاً امتحان کردن یک چیز برای دیدن اینکه کار میکند یا نه نیست.
برای رسیدن به این، هر آزمایش نیاز دارد:
یک فرضیهٔ روشن
یک تیم که برای یک شرکت بیمه کار میکند در حال بررسی استفاده از نرمافزار تشخیص تصویر برای تشخیص این است که آیا یک خانه در یک منطقهٔ مستعد آتشسوزی در فاصلهٔ مشخصی پوشش گیاهی دارد یا نه. آنها فرض میکنند نرمافزار میتواند پوشش گیاهی را تشخیص دهد و فاصلهٔ آن را از یک سازه اندازهگیری کند تا ریسک آتشسوزی را با استفاده از یک تصویر گرفتهشده از یک ماهواره ارزیابی کند.
یک هدف یا تارگت صریح و قابل اندازهگیری
هدف آزمایش این است که تعیین کند آیا نرمافزار تشخیص تصویر میتواند دو بوته و یک درخت را در فاصلهٔ ۳۰ feet از یک خانهٔ مشخص تشخیص دهد و شناسایی کند، با استفاده از یک عکس ماهوارهای واضحِ با وضوح بالا.
یک روش برای اجرای آزمایش و سازوکارهایی برای اندازهگیری موفقیت یا شکست آن
چون آزمایش دامنهٔ محدودی دارد و تلاش خواهد کرد شکلهای خوب تعریفشده را شناسایی کند، از یک مدل تشخیص تصویرِ ازپیشآموزشدیده (pre-trained) استفاده خواهد کرد که فقط آموزش محدودی نیاز دارد. نتایج آزمایش با عکسهای سطح زمین از خانه و اطراف آن مقایسه خواهد شد تا یافتههای مدل اعتبارسنجی شود.
یک برنامه برای rollback اگر آزمایش شکست بخورد
برای مثال بالا، چون آزمایش غیرمخرب است و وضعیت یا محتوای دادههای موجود را تغییر نمیدهد، یک برنامهٔ roll-back لازم نیست. در مورد بعضی تغییرات کد، برگشتن به یک نسخهٔ قبلی کد ممکن است لازم باشد اگر آزمایش شکست بخورد. در موارد دیگر، جایی که آزمایش باید در یک انتشار که به مشتریان deploy شده انجام شود، همانطور که در حالتی که بازخورد استفادهٔ دنیای واقعی برای جمعآوری دادهٔ موردنیاز جهت تصمیمگیری ضروری است، تیم نیاز خواهد داشت راهی برای rollback کردن تغییر با استفاده از تکنیکهایی مثل A/B testing یا یک redeployment سریعِ سیستم داشته باشد اگر آزمایش شکست بخورد.
یک خط زمانی صریح برای آزمایش
چون ما در چرخههای کوتاه کار میکنیم، آزمایش باید داخل timebox انتشار جا شود. در متن یک روش چابک مثل Scrum، آزمایش باید داخل یک Sprint جا شود. اگر کارِ لازم برای جا دادن آزمایش در timebox توسعه و تست انتشار زیاد باشد، باید آن را به آزمایشهای کوچکتر بشکنید.
گاهی آزمایشها به نتایج مطلوب نمیرسند و تیم ممکن است وسوسه شود آزمایش را تمدید کند تا زمان داشته باشد راهحلش را refine کند. این باید یک آزمایش جدید باشد، نه یک تمدیدِ آزمایش قبلی. مورد آزمایش تشخیص تصویر بالا را در نظر بگیرید. تیم ممکن است وسوسه شود باور کند آموزش بیشتر مدل ممکن است نتایج را بهتر کند و آزمایش را موفق کند. اما این باید بهعنوان یک آزمایش جداگانه در نظر گرفته شود.
بعضی آزمایشها ممکن است نیاز داشته باشند تیم سختافزار یا نرمافزار تهیه کند، کسی را (حتی اگر فقط موقت) با تخصص لازم استخدام کند، یا منابع محاسباتی (مثل cloud یا محیطهای تست) که ندارد تهیه کند. در این موارد، ممکن است به بودجه و تأیید تأمین مالی نیاز داشته باشند.
بازنگریهای معماری (Architectural retrospectives) میتوانند به تیم کمک کنند بررسی کند آیا به اندازهٔ کافی آزمایش میکند، یا شاید بیش از حد. مشکلهای آزمایش نکردن واضحاند، اما آزمایش کردن بیش از حد هم میتواند به همان اندازه بد باشد؛ اگر مهم نیست که یک تصمیم در نهایت اشتباه از آب دربیاید، آن تصمیم واقعاً معماری نیست، صرفاً یک انتخاب طراحی متفاوت است.
آزمایشهای معماری همچنین باید کارِ «پشتیبانی و تغییر» را پیشبینی کنند
ما در سیستمهای نرمافزاری modularity را ارزش میدهیم چون سیستم را آسانتر برای گسترش و تکامل میکند. اما درست مثل یک ساختمان، تغییرات پیاپی در یک سیستم نرمافزاری، اگر بد طراحی یا اجرا شوند، میتوانند در نهایت سیستم را زیر فشار ببرند و قابلیتپذیری بلندمدت آن را کاهش دهند. یک معماری نرمافزاری خوب تغییر را پیشبینی میکند و بعضی نوعهای تغییر را آسانتر و کمتر مخرب میکند. اما یک تیم چطور میداند چقدر باید برای پیشبینی و آسانسازی تغییر آینده سرمایهگذاری کند؟ و چطور میداند آیا کارش در این زمینه موفق بوده است؟
مثل دیگر انواع تصمیمهای معماری، تنها راه دانستن این است که چند آزمایش اجرا کنید که روی ارزیابی هزینه و اثر بعضی نوعهای تغییر تمرکز دارند. برای مثال، یک سیستم بیمهنویسی (underwriting) را در نظر بگیرید که با نوعهای مشخصی از داراییهای تحت پوشش (برای مثال، household furniture) و نوعهای مشخصی از رخدادهای خسارت (برای مثال، fire) سروکار دارد. برای تیمی که این سیستم را توسعه میدهد مفید خواهد بود که در نظر بگیرد اضافه کردن یک نوع جدید از دارایی تحت پوشش (برای مثال، fine art) و یک نوع جدید از رخداد خسارت (برای مثال، a theft) چقدر آسان است. ممکن است بفهمند بعضی نوعهای تغییر آساناند در حالی که بعضی دیگر به یک سیستم کاملاً جدید نیاز دارند.
این یکی از دلیلهاست که چرا یک بیمهنامهٔ صاحب خانه، خودروها را پوشش نمیدهد، چون ریسکها و معیارهای تصمیمگیری برای تسویهٔ خسارتها آنقدر متفاوتاند که یک سیستم نمیتواند برای هر نوع دارایی و هر نوع ریسک کار کند. دانستن مرزهای تغییر یک تصمیم معماری مهم است.
کارِ معماری همچنین باید در نظر بگیرد آن سیستم در طول زمان چگونه پشتیبانی خواهد شد. وقتی شکست میخورد آیا اطلاعات کافی برای تشخیص مشکل ارائه میدهد؟
پاسخ دادن به این سؤال نیازمند فهم دانش و تخصص نیروهای پشتیبانی و همچنین نوع رخدادهایی است که میتوانند باعث شکست شوند. گاهی تنها راه دانستن این است که آزمایشهایی اجرا کنید که هدفشان این است سیستم را به شکست بکشانند تا ببینید چگونه واکنش نشان میدهد و بعد از شکست چه اطلاعاتی برای رفع مشکل لازم است.
برای مثال، یک سیستم بیمهٔ خودرو در یک شرکت بیمه معمولاً از یک بانک قوانین برای شخصیسازی و قیمتگذاری پوششها برای مشتریان شرکت استفاده میکند. پیکربندی و تست قواعد اغلب کاری چالشبرانگیز و زمانبر است. همانطور که Thomas Betts در یک مقالهٔ اخیر پیشنهاد کرده است، استفاده از یک LLM برای وارد کردن و اعتبارسنجی این قواعد راه خوبی خواهد بود تا آن کار بهطور قابلتوجهی آسانتر و سریعتر شود، به شرط اینکه شرکت بیمه نمونههای کافی از پیکربندی قواعد داشته باشد تا LLM را آموزش دهد.
با این حال، خروجی یک LLM گاهی سخت است که توضیح داده شود، و تیم باید آزمایشهایی اجرا کند که طراحی شدهاند LLM را وادار کنند اطلاعات «غلط» تولید کند، تا مطمئن شوند اگر این اتفاق در تولید افتاد میتوانند مشکل را تشخیص دهند. انجام چنین آزمایشی ممکن است از چند غافلگیری بد وقتی سیستم پیادهسازی شد جلوگیری کند. ممکن است تیم را قانع کند رویکرد مبتنی بر LLM خود را دوباره بررسی کند اگر نتواند مشکل را تشخیص دهد.
نتیجهگیری
کارِ معماری نرمافزار همیشه قابل پیشبینی نیست؛ سیستمها موجودیتهای پیچیدهای هستند که گاهی به شکلهای غیرمنتظره رفتار میکنند. گاهی تنها راه برای فهم مرزهای بین رفتارِ مورد انتظار و رفتارِ غیرمنتظره این است که آزمایشها را اجرا کنیم. یکی از هدفهای یک MVA این است که یک سازوکار برای اجرای آزمایشهای معماری فراهم کند تا تیم توسعه بتواند بفهمد تصمیمهای معماریاش چه زمانی و چگونه ممکن است شکست بخورند. با اطلاعات بهتر، تیم توسعه ممکن است انتخابهای متفاوتی انجام دهد، یا دستکم بداند چه زمانی فرضیاتش ممکن است شکست بخورند.
وقتی ریسکِ عبور از محدودیتهای سیستم پایین است، تیم توسعه ممکن است تصمیم بگیرد ریسک را بپذیرد، اما حتی در این حالتها هم باید سیستم را طوری طراحی کند که به شکل برازنده شکست بخورد و به نیروهای پشتیبانی یا اعضای آیندهٔ تیم توسعه اطلاعات کافی بدهد تا مشکل را رفع کنند بدون اینکه بخشهای اصلی سیستم را دور بریزند.
در معماری نرمافزار، بعضی وقتها اشتباه بودن اجتنابناپذیر است؛ اگر شما هیچوقت اشتباه نمیکنید یعنی به اندازهٔ کافی خودتان را به چالش نمیکشید و یاد نمیگیرید. چیزِ ضروری این است که تا حد ممکن تصمیمهایمان را با آزمایشهایی تست کنیم که فرضیاتمان را به چالش میکشند و سیستم را طوری بسازیم که وقتی تصمیمهایمان نادرست هستند سیستم به شکل فاجعهآمیز شکست نخورد.
