پرش به مطلب اصلی

مسئول کیفیت نرم‌افزار چه کسی است؟

· ۱۱ دقیقه مطالعه
مهدی مالوردی
مهندس نرم‌افزار و نویسندهٔ این سایت

مسئول کیفیت نرم‌افزار چه کسی است؟

یک باگ به production می‌رود. کاربر ناراضی می‌شود، پشتیبانی درگیر می‌شود و تیم فنی دنبال علت می‌گردد. در چنین لحظه‌ای معمولاً اولین سؤال این است: «چرا QA این را نگرفت؟»

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

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

پس ایده‌ی اصلی این متن ساده است: کیفیت مسئولیت مشترک تیم است؛ اما مسئولیت مشترک نباید به بی‌مالکیتی تبدیل شود. هر نقش باید مالک کیفیت همان تصمیمی باشد که روی آن اثر می‌گذارد.

مستند بی‌صاحب از بی‌مستندی بدتر است

· ۱۳ دقیقه مطالعه
مهدی مالوردی
مهندس نرم‌افزار و نویسندهٔ این سایت

حافظه‌ی فنی تیم

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

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

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

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

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

نه به معماری نمایشی

· ۱۲۰ دقیقه مطالعه
مهدی مالوردی
مهندس نرم‌افزار و نویسندهٔ این سایت

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

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

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

رشد تدریجی یک سامانه‌ی نرم‌افزاری؛ از یک برنامه‌ی ساده تا ساختاری پیچیده‌تر و بالغ‌تر

اصل تک‌مسئولیتی؛ وقتی خودِ قاب‌بندی مسئله‌ساز است

· ۱۷ دقیقه مطالعه

کد تمیزی که داستانش گم شده

این نوشته درباره‌ی این نیست که کسی اصل تک‌مسئولیتی را «بد اجرا کرده» و اگر کمی دقیق‌تر اجرا می‌کرد، همه‌چیز درست می‌شد. نقد من کمی ریشه‌ای‌تر است: خودِ قاب‌بندی این اصل گاهی ما را با سؤال اشتباهی وارد طراحی می‌کند.

به‌جای اینکه از خودمان بپرسیم «این مرزبندی در طول زمان چه هزینه‌ای دارد؟»، خیلی زود می‌پرسیم: «این کلاس چند مسئولیت دارد؟» همین تغییر کوچک در سؤال، ما را آرام‌آرام به سمت شکستن کد بر اساس مسئولیت‌های ظاهری می‌برد: Validator، Policy، Factory، Writer، Service.

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

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

· ۱۱ دقیقه مطالعه
مهدی مالوردی
مهندس نرم‌افزار و نویسندهٔ این سایت

یک بخش ساده‌ی نمایش در کنار بخشی که داده‌های خام را مرتب، تصمیم‌ها را اعمال، و خروجی آماده‌ی نمایش تولید می‌کند.

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

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

function AccountSummary({account}: {account: Account}) {
const isBlocked = account.status === 'blocked'
const isLowBalance = account.balance < 100_000
const canWithdraw = account.status === 'active' && account.balance > 0

const statusText = isBlocked ? 'مسدود' : 'فعال'
const statusColor = isBlocked ? 'red' : 'green'
const balanceText = `${account.balance.toLocaleString('fa-IR')} تومان`
const warningText = isLowBalance
? 'موجودی حساب کم است'
: null

return (
<section>
<h2>{account.ownerName}</h2>
<span className={statusColor}>{statusText}</span>
<p>{balanceText}</p>
{warningText && <p>{warningText}</p>}
<button disabled={!canWithdraw}>برداشت</button>
</section>
)
}

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

مسئله این نیست که رابط کاربری بد است. مسئله این است که رابط کاربری جای خوبی برای پنهان‌کردن منطق تصمیم‌گیری نیست.

گره‌های پنهان در معماری نرم‌افزار

· ۱۱ دقیقه مطالعه

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

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

اما پرسش اصلی همین‌جاست:

آیا مشکل حل شد، یا فقط صدای هشدار را خاموش کردیم؟

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

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

نموداری مفهومی از وابستگی‌های چرخه‌ای در طراحی نرم‌افزار