🚀 قانون DRY؛ یکی از بدفهمیدهشدهترین قوانین در برنامهنویسی
هر برنامهنویسی خیلی زود با DRY آشنا میشود، و تقریباً همه آن را اشتباه یاد میگیرند.Don't Repeat Yourself.
دو قطعه کد مشابه دیدی؟ یک متد استخراج کن و کد تکراری را حذف کن.
من هم سالها همین کار را انجام میدادم و نتیجهاش بعضی از بدترین کدهایی بود که تاکنون مجبور به نگهداریشان شدهام:
🔹 یک Helper مشترک که هر اسپرینت یک پارامتر
bool جدید به آن اضافه میشد.🔹 یک Base Class که هیچکس جرئت تغییرش را نداشت، چون شش قابلیت کاملاً نامرتبط از آن ارثبری کرده بودند.
🔹 یک ماژول «مشترک» که دو بخش مستقل سیستم به آن وابسته بودند و در نتیجه هیچکدام نمیتوانستند بدون تأثیر روی دیگری تغییر کنند.
همه اینها با یک تلاش کاملاً بیضرر برای تکرار نکردن کد شروع شدند.
🎯 ءDRY واقعاً چه میگوید؟
این همان بخشی است که بیشتر افراد از آن عبور میکنند.
تعریف اصلی DRY که توسط Andy Hunt و Dave Thomas در کتاب The Pragmatic Programmer
ارائه شد، اصلاً درباره کد نیست:
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
«هر بخش از دانش باید در یک سیستم، تنها یک نمایش یکتا، شفاف و مرجع داشته باشد.»
موضوع اصلی دانش (Knowledge) است.
یک حقیقت درباره دامنه کسبوکار شما، مانند یک قانون مالیاتی یا فرمت شماره فاکتور، باید فقط در یک مکان وجود داشته باشد.
وقتی آن حقیقت تغییر کرد، باید فقط یک بار آن را تغییر دهید؛ نه اینکه به دنبال هفت نسخه مختلف آن در سیستم بگردید.
❌ اشتباه رایج: حذف تکرار کد، نه حذف تکرار دانش
دو قطعه کد میتوانند کاملاً مشابه باشند اما دانش کاملاً متفاوتی را نمایش دهند.
فرض کنید دو نوع آدرس را اعتبارسنجی میکنید:
🔹 آدرس ارسال مشتری
🔹 آدرس انبار
امروز قوانین هر دو یکسان هستند:
public bool IsValid(Address address) =>
!string.IsNullOrWhiteSpace(address.Street) &&
!string.IsNullOrWhiteSpace(address.City) &&
!string.IsNullOrWhiteSpace(address.PostalCode);
واکنش طبیعی DRY این است که یک Validator مشترک استخراج کنیم و از هر دو جا آن را فراخوانی کنیم.
اما اینها دو مفهوم متفاوت هستند که فقط همین هفته قوانین یکسانی دارند.
روزی که انبار به یک Loading Dock Code نیاز پیدا کند، دوباره به همان متد مشترک برمیگردید و یک Flag جدید اضافه میکنید تا مصرفکننده قبلی همچنان کار کند:
public bool IsValid(Address address, bool requireDockCode = false) =>
!string.IsNullOrWhiteSpace(address.Street) &&
!string.IsNullOrWhiteSpace(address.City) &&
!string.IsNullOrWhiteSpace(address.PostalCode) &&
(!requireDockCode || !string.IsNullOrWhiteSpace(address.DockCode));
همین پارامتر
bool نشانه خطر است.اولین باری که یک متد مشترک مجبور میشود Flag بگیرد تا برای یک مصرفکننده رفتار متفاوتی داشته باشد، یعنی شما با تکرار مواجه نبودید.
شما فقط دو چیز مشابه را به زور به هم چسباندهاید.
یک سال بعد، امضای متد سه Flag دیگر هم خواهد داشت؛ هر کدام نشانهای از اینکه این دو مفهوم هیچوقت واقعاً یکسان نبودهاند.
💸 هزینه Abstraction اشتباه از هزینه Duplication بیشتر است
تکرار کد معمولاً بسیار ارزانتر از یک Abstraction اشتباه است.
کپیپیست:
✅ قابل مشاهده است
✅ محلی است
✅ هر نسخه میتواند مستقل تکامل پیدا کند
اما Abstraction اشتباه:
❌ پنهان است
❌ سراسری است
❌ همه مصرفکنندهها را به یک شکل خاص وابسته میکند
ءFlagها اضافه میشوند، پیچیدگی افزایش پیدا میکند و در نهایت از تغییر متدی که دیگر درکش نمیکنید میترسید.
من زمان بسیار بیشتری را صرف حذف Abstractionهای بد کردهام تا زمانی که از ایجاد آنها صرفهجویی کرده باشم.
این همان هزینه پنهان Coupling است که قبلاً درباره آن صحبت کردهام و DRY افراطی یکی از رایجترین راههای ورود آن به سیستم است.
🏗 جایی که بیشترین آسیب را میزند: مرزهای سیستم
داخل یک کلاس، یک Helper بد فقط آزاردهنده است.
اما در مرز بین ماژولها، تبدیل به یک آسیب ساختاری میشود.
فرض کنید یک Modular Monolith داریم که شامل دو ماژول است:
🔹 Billing
🔹 Shipping
هر دو یک مفهوم Order دارند.
یک توسعهدهنده با نیت خیر متوجه میشود این دو کلاس فیلدهای مشابهی دارند و آنها را به یک Type مشترک منتقل میکند:
// Shared.Orders, referenced by both Billing and Shipping
public class Order
{
public Guid Id { get; set; }
public string CustomerName { get; set; }
public decimal Total { get; set; }
// ...whatever either module happens to need
}
حالا Billing و Shipping دیگر نمیتوانند مستقل تکامل پیدا کنند.
یک تغییر در Billing:
➡️ نیاز به Recompile
➡️ نیاز به Retest
➡️ نیاز به Redeploy
برای Shipping خواهد داشت.
شما دو Bounded Context را که قرار بود مستقل باشند، فقط برای صرفهجویی در چند Property به هم جوش دادهاید.
داشتن دو مدل Order مستقل دقیقاً همان دلیلی است که دادهها باید داخل مرزهای خودشان باقی بمانند.
شباهت داشتن آنها کاملاً طبیعی است.
آنها یک مفهوم واقعی را از دو زاویه متفاوت مدل میکنند؛ زاویههایی که به مرور زمان از هم فاصله خواهند گرفت.
من بار دوم چیزی را Deduplicate نمیکنم.
تا بار سوم صبر میکنم و فقط یک سؤال میپرسم:
اگر پاسخ بله باشد:
✅ با تکرار واقعی روبهرو هستید.
✅ همان حقیقت در چند نقطه نوشته شده است.
✅ آن را استخراج کنید.
اگر پاسخ خیر باشد:
✅ شباهت فقط تصادفی است.
✅ آن را به حال خودش رها کنید.
✅ ءCoupling آینده هزینه بیشتری خواهد داشت.
اجازه دهید کد تکرار شود تا زمانی که Abstraction مناسب خودش را نشان دهد.
ءAbstractionهای خوب از مثالهای واقعی کشف میشوند، نه اینکه از ابتدا حدس زده شوند.
بعضی افراد این رویکرد را AHA (Avoid Hasty Abstractions) مینامند.
وقتی بتوانید مفهوم را نامگذاری کنید، زمان استخراج فرا رسیده است.
نامهایی مثل:
✅
✅
✅
احتمالاً دانش دامنه هستند و ارزش تبدیل شدن به Value Object را دارند.
اما اگر بهترین نامی که پیدا میکنید اینها باشد:
❌
❌
❌
احتمالاً در حال Abstract کردن شکل کد هستید، نه دانش.
وقتی درست استفاده شود، DRY فوقالعاده ارزشمند است.
یک قانون کسبوکار باید فقط در یک مکان وجود داشته باشد.
فرض کنید قانون زیر را در سه بخش مختلف کپی کردهایم:
بالاخره روزی دو مورد را تغییر میدهید و سومی را فراموش میکنید.
همین نسخه سومِ منحرفشده منشأ باگ خواهد بود.
قانون را به Domain Model منتقل کنید تا تنها یک خانه داشته باشد:
این دقیقاً همان «نمایش یکتای مرجع» است که DRY درباره آن صحبت میکند.
🔹 ءDRY درباره دانش است، نه کدهایی که شبیه هم هستند.
🔹 هزینه یک Abstraction اشتباه از هزینه Duplication بیشتر است و حذف آن نیز سختتر خواهد بود.
🔹 تا بار سوم صبر کنید. فقط زمانی استخراج کنید که هر دو نسخه یک حقیقت مشترک را نمایش میدهند و باید با هم تغییر کنند.
🔹 دفعه بعد که خواستید یک کد تکراری را حذف کنید، از خودتان نپرسید:
«آیا این دو قطعه کد شبیه هم هستند؟»
بپرسید:
«آیا معنای یکسانی دارند؟»
همین یک سؤال، دردسرهای نگهداری بیشتری را از شما دور خواهد کرد تا هر مقدار تایپ نکردنی که DRY برایتان ذخیره میکند. 🚀
یک تغییر در Billing:
➡️ نیاز به Recompile
➡️ نیاز به Retest
➡️ نیاز به Redeploy
برای Shipping خواهد داشت.
شما دو Bounded Context را که قرار بود مستقل باشند، فقط برای صرفهجویی در چند Property به هم جوش دادهاید.
داشتن دو مدل Order مستقل دقیقاً همان دلیلی است که دادهها باید داخل مرزهای خودشان باقی بمانند.
شباهت داشتن آنها کاملاً طبیعی است.
آنها یک مفهوم واقعی را از دو زاویه متفاوت مدل میکنند؛ زاویههایی که به مرور زمان از هم فاصله خواهند گرفت.
📏 قانونی که من استفاده میکنم: تا بار سوم صبر کن
من بار دوم چیزی را Deduplicate نمیکنم.
تا بار سوم صبر میکنم و فقط یک سؤال میپرسم:
اگر این قانون تغییر کند، آیا هر دو نسخه باید با هم تغییر کنند؟
اگر پاسخ بله باشد:
✅ با تکرار واقعی روبهرو هستید.
✅ همان حقیقت در چند نقطه نوشته شده است.
✅ آن را استخراج کنید.
اگر پاسخ خیر باشد:
✅ شباهت فقط تصادفی است.
✅ آن را به حال خودش رها کنید.
✅ ءCoupling آینده هزینه بیشتری خواهد داشت.
اجازه دهید کد تکرار شود تا زمانی که Abstraction مناسب خودش را نشان دهد.
ءAbstractionهای خوب از مثالهای واقعی کشف میشوند، نه اینکه از ابتدا حدس زده شوند.
بعضی افراد این رویکرد را AHA (Avoid Hasty Abstractions) مینامند.
💡 یک نشانه عملی
وقتی بتوانید مفهوم را نامگذاری کنید، زمان استخراج فرا رسیده است.
نامهایی مثل:
✅
Money✅
TaxRate✅
InvoiceNumberاحتمالاً دانش دامنه هستند و ارزش تبدیل شدن به Value Object را دارند.
اما اگر بهترین نامی که پیدا میکنید اینها باشد:
❌
Helper❌
Utils❌
ProcessDataاحتمالاً در حال Abstract کردن شکل کد هستید، نه دانش.
✅ زمانی که DRY واقعاً درست است
وقتی درست استفاده شود، DRY فوقالعاده ارزشمند است.
یک قانون کسبوکار باید فقط در یک مکان وجود داشته باشد.
فرض کنید قانون زیر را در سه بخش مختلف کپی کردهایم:
«سفارشهای بالاتر از ۱۰۰۰ دلار نیاز به تأیید مدیر دارند.»
// OrderService
if (order.Total > 1000) { /* require approval */ }
// CheckoutService
if (order.Total > 1000m) { /* require approval */ }
// AdminController - someone bumped the limit here, and only here
if (order.Total > 5000) { /* require approval */ }
بالاخره روزی دو مورد را تغییر میدهید و سومی را فراموش میکنید.
همین نسخه سومِ منحرفشده منشأ باگ خواهد بود.
قانون را به Domain Model منتقل کنید تا تنها یک خانه داشته باشد:
public bool RequiresManagerApproval() => Total > 1000;
این دقیقاً همان «نمایش یکتای مرجع» است که DRY درباره آن صحبت میکند.
📝 جمعبندی
🔹 ءDRY درباره دانش است، نه کدهایی که شبیه هم هستند.
🔹 هزینه یک Abstraction اشتباه از هزینه Duplication بیشتر است و حذف آن نیز سختتر خواهد بود.
🔹 تا بار سوم صبر کنید. فقط زمانی استخراج کنید که هر دو نسخه یک حقیقت مشترک را نمایش میدهند و باید با هم تغییر کنند.
🔹 دفعه بعد که خواستید یک کد تکراری را حذف کنید، از خودتان نپرسید:
«آیا این دو قطعه کد شبیه هم هستند؟»
بپرسید:
«آیا معنای یکسانی دارند؟»
همین یک سؤال، دردسرهای نگهداری بیشتری را از شما دور خواهد کرد تا هر مقدار تایپ نکردنی که DRY برایتان ذخیره میکند. 🚀
🔖هشتگها:
#cleanarchitecture #dry #dotnetdeveloper
یکی از تغییرات جالبی که با افزایش تجربه اتفاق میافتد این است که دیگر از کدهای قدیمی تعجب نمیکنی.
اوایل مسیر، وقتی با یک متد ۵۰۰ خطی یا یک کلاس عجیب روبهرو میشوی، سریع قضاوت میکنی.
با خودت میگویی:«چه کسی چنین چیزی نوشته؟» اما بعد از چند سال کار روی سیستمهای واقعی، نگاهت تغییر میکند.
میفهمی بیشتر کدهای بد، توسط توسعهدهندگان بد نوشته نشدهاند.توسط توسعهدهندگانی نوشته شدهاند که تحت فشار بودهاند.
فشار زمان.
فشار تحویل.
فشار تغییر مداوم نیازمندیها.
و گاهی فشار زنده نگه داشتن سیستمی که هر روز باید کار کند.
بیشتر سیستمهای پیچیده، یک روز با نیت خوب شروع شدهاند.اما هر تصمیم کوچک،
هر میانبر موقت، و هر «فعلاً همین را Deploy کنیم» لایهای جدید به پیچیدگی آن اضافه کرده است.
به همین دلیل است که قضاوت کردن کدهای قدیمی آسان است.اما نگهداری و بهبود آنها مهارت واقعی را نشان میدهد.
هر توسعهدهندهای میتواند درباره ساختن یک سیستم ایدهآل صحبت کند.
اما مهندس واقعی کسی است که بتواند با واقعیتهای یک سیستم غیرایدهآل کار کند و آن را قدمبهقدم بهتر کند.
چون در دنیای واقعی، بیشتر ما نرمافزار را از صفر نمیسازیم.ما وارث تصمیمهای دیروز هستیم.
اوایل مسیر، وقتی با یک متد ۵۰۰ خطی یا یک کلاس عجیب روبهرو میشوی، سریع قضاوت میکنی.
با خودت میگویی:«چه کسی چنین چیزی نوشته؟» اما بعد از چند سال کار روی سیستمهای واقعی، نگاهت تغییر میکند.
میفهمی بیشتر کدهای بد، توسط توسعهدهندگان بد نوشته نشدهاند.توسط توسعهدهندگانی نوشته شدهاند که تحت فشار بودهاند.
فشار زمان.
فشار تحویل.
فشار تغییر مداوم نیازمندیها.
و گاهی فشار زنده نگه داشتن سیستمی که هر روز باید کار کند.
بیشتر سیستمهای پیچیده، یک روز با نیت خوب شروع شدهاند.اما هر تصمیم کوچک،
هر میانبر موقت، و هر «فعلاً همین را Deploy کنیم» لایهای جدید به پیچیدگی آن اضافه کرده است.
به همین دلیل است که قضاوت کردن کدهای قدیمی آسان است.اما نگهداری و بهبود آنها مهارت واقعی را نشان میدهد.
هر توسعهدهندهای میتواند درباره ساختن یک سیستم ایدهآل صحبت کند.
اما مهندس واقعی کسی است که بتواند با واقعیتهای یک سیستم غیرایدهآل کار کند و آن را قدمبهقدم بهتر کند.
چون در دنیای واقعی، بیشتر ما نرمافزار را از صفر نمیسازیم.ما وارث تصمیمهای دیروز هستیم.
یکی از خطرناکترین اتفاقاتی که میتواند برای یک تیم فنی بیفتد، این نیست که اشتباه کند.این است که دیگر اشتباهاتش را نبیند.
در روزهای اول یک پروژه، همه چیز زیر ذرهبین است.هر Bug بررسی میشود.هر تصمیم به چالش کشیده میشود.هر تغییر مورد بحث قرار میگیرد.
اما به مرور زمان، بعضی مشکلات آنقدر تکرار میشوند که به بخشی از واقعیت روزمره تبدیل میشوند.Buildها گاهی Fail میشوند.طبیعی است.
استقرارها کمی استرس دارند.طبیعی است.
برخی سرویسها هر از گاهی دچار مشکل میشوند.طبیعی است.
تعدادی Bug هم همیشه در Production وجود دارد.طبیعی است.
و دقیقاً از همین نقطه، خطر شروع میشود.
چون بسیاری از مشکلات بزرگ، زمانی ایجاد نمیشوند که یک اتفاق غیرعادی رخ دهد.زمانی ایجاد میشوند که یک اتفاق اشتباه، عادی شود.
تیمهای حرفهای فقط به دنبال حل مشکلات نیستند.آنها مراقب هستند که به مشکلات عادت نکنند.
چون هر بار که یک ضعف را به عنوان بخشی از طبیعت سیستم میپذیریم،استانداردهای خودمان را کمی پایینتر میآوریم.
و در مهندسی نرمافزار، سقوط معمولاً ناگهانی اتفاق نمیافتد.از مجموعهای از چیزهایی شروع میشود که کمکم برایمان عادی شدهاند.
در روزهای اول یک پروژه، همه چیز زیر ذرهبین است.هر Bug بررسی میشود.هر تصمیم به چالش کشیده میشود.هر تغییر مورد بحث قرار میگیرد.
اما به مرور زمان، بعضی مشکلات آنقدر تکرار میشوند که به بخشی از واقعیت روزمره تبدیل میشوند.Buildها گاهی Fail میشوند.طبیعی است.
استقرارها کمی استرس دارند.طبیعی است.
برخی سرویسها هر از گاهی دچار مشکل میشوند.طبیعی است.
تعدادی Bug هم همیشه در Production وجود دارد.طبیعی است.
و دقیقاً از همین نقطه، خطر شروع میشود.
چون بسیاری از مشکلات بزرگ، زمانی ایجاد نمیشوند که یک اتفاق غیرعادی رخ دهد.زمانی ایجاد میشوند که یک اتفاق اشتباه، عادی شود.
تیمهای حرفهای فقط به دنبال حل مشکلات نیستند.آنها مراقب هستند که به مشکلات عادت نکنند.
چون هر بار که یک ضعف را به عنوان بخشی از طبیعت سیستم میپذیریم،استانداردهای خودمان را کمی پایینتر میآوریم.
و در مهندسی نرمافزار، سقوط معمولاً ناگهانی اتفاق نمیافتد.از مجموعهای از چیزهایی شروع میشود که کمکم برایمان عادی شدهاند.
بعضی از تصمیمهای اشتباه در مهندسی نرمافزار، در لحظه کاملاً منطقی به نظر میرسند.همه اطلاعات موجود از آن تصمیم حمایت میکنند.
فشار زمان وجود دارد.
نیاز کسبوکار مشخص است.
راهحل هم جواب میدهد.
پس تصمیم گرفته میشود.
مشکل از جایی شروع میشود که چند ماه بعد، نتیجه آن تصمیم را میبینیم.اینجاست که معمولاً جملههای آشنایی شنیده میشود:
«از اول معلوم بود اشتباه است.»
«نباید این کار را انجام میدادیم.»
«همان موقع هم مشخص بود به مشکل میخوریم.»
اما واقعیت این است که بسیاری از تصمیمهای امروز، با دانشی که امروز داریم قضاوت میشوند.نه با دانشی که آن روز در اختیار داشتیم.
به همین دلیل است که تیمهای حرفهای کمتر به دنبال پیدا کردن مقصر هستند.و بیشتر تلاش میکنند فرآیند تصمیمگیری را بهبود دهند.
چون در پروژههای واقعی، همیشه نمیتوان بهترین تصمیم را گرفت.گاهی فقط میتوان بهترین تصمیم ممکن را با اطلاعات موجود گرفت.
بلوغ مهندسی زمانی اتفاق میافتد که به جای قضاوت گذشته،از آن برای ساختن تصمیمهای بهتر در آینده استفاده کنیم.
در نهایت، کیفیت یک تیم را تعداد اشتباهاتش مشخص نمیکند.توانایی یاد گرفتن از آنها مشخص میکند.
فشار زمان وجود دارد.
نیاز کسبوکار مشخص است.
راهحل هم جواب میدهد.
پس تصمیم گرفته میشود.
مشکل از جایی شروع میشود که چند ماه بعد، نتیجه آن تصمیم را میبینیم.اینجاست که معمولاً جملههای آشنایی شنیده میشود:
«از اول معلوم بود اشتباه است.»
«نباید این کار را انجام میدادیم.»
«همان موقع هم مشخص بود به مشکل میخوریم.»
اما واقعیت این است که بسیاری از تصمیمهای امروز، با دانشی که امروز داریم قضاوت میشوند.نه با دانشی که آن روز در اختیار داشتیم.
به همین دلیل است که تیمهای حرفهای کمتر به دنبال پیدا کردن مقصر هستند.و بیشتر تلاش میکنند فرآیند تصمیمگیری را بهبود دهند.
چون در پروژههای واقعی، همیشه نمیتوان بهترین تصمیم را گرفت.گاهی فقط میتوان بهترین تصمیم ممکن را با اطلاعات موجود گرفت.
بلوغ مهندسی زمانی اتفاق میافتد که به جای قضاوت گذشته،از آن برای ساختن تصمیمهای بهتر در آینده استفاده کنیم.
در نهایت، کیفیت یک تیم را تعداد اشتباهاتش مشخص نمیکند.توانایی یاد گرفتن از آنها مشخص میکند.
Forwarded from thisisnabi.dev [Farsi] (Nabi Karampour)
بچه ها بیاید با هم یک چالش بذاریم تا آخر سال بلکه یه نظمی در یادگیری پیدا کنیم.
من هر پنجشنبه ساعت 09:00 یک تاپیک رو معرفی میکنم با یکسری منبع، اگر منابع رو خوندید و موضوع رو یاد گرفتید پستش رو لایک کنید.
خواهشم اینه که اگر انجام ندادید لایک نکنید که آمارمون به هم نریزه.
@thisisnabi_dev
من هر پنجشنبه ساعت 09:00 یک تاپیک رو معرفی میکنم با یکسری منبع، اگر منابع رو خوندید و موضوع رو یاد گرفتید پستش رو لایک کنید.
خواهشم اینه که اگر انجام ندادید لایک نکنید که آمارمون به هم نریزه.
@thisisnabi_dev
چند سال پیش فکر میکردم بهترین برنامهنویس تیم کسی است که برای هر سؤال سریع جواب داشته باشد.
هر مشکلی پیش میآمد فوری راهحل میداد.
هر بحثی میشد نظر قطعی داشت.
هر تصمیمی مطرح میشد سریع انتخاب میکرد.
بعد از مدتی با آدمهای باتجربهتری آشنا شدم.
متوجه شدم اتفاقاً آنها کمتر جواب میدهند.
بیشتر سؤال میپرسند.
میپرسند:
اگر این فرض اشتباه باشد چه؟
اگر حجم کاربران ۱۰ برابر شود چه؟
اگر تیم دیگری بخواهد این کد را نگهداری کند چه؟
اگر نیازمندی تغییر کند چه؟
اول فکر میکردم مردد هستند.
بعد فهمیدم دارند هزینه تصمیم را میسنجند.
تازه آنجا بود که متوجه شدم.
خیلی وقتها اعتمادبهنفس نشانه دانستن نیست.
نشانه ندیدن پیچیدگیهای مسئله است.
هرچه تجربه بیشتر میشود، تعداد جوابهای قطعی کمتر میشود.
چون میفهمی پشت هر تصمیمی که امروز میگیری،
فهرستی از پیامدها ایستادهاند که هنوز خودشان را نشان ندادهاند.
شاید یکی از نشانههای رشد حرفهای این باشد که
به جای اینکه سریعتر جواب بدهی،
عمیقتر فکر کنی.
هر مشکلی پیش میآمد فوری راهحل میداد.
هر بحثی میشد نظر قطعی داشت.
هر تصمیمی مطرح میشد سریع انتخاب میکرد.
بعد از مدتی با آدمهای باتجربهتری آشنا شدم.
متوجه شدم اتفاقاً آنها کمتر جواب میدهند.
بیشتر سؤال میپرسند.
میپرسند:
اگر این فرض اشتباه باشد چه؟
اگر حجم کاربران ۱۰ برابر شود چه؟
اگر تیم دیگری بخواهد این کد را نگهداری کند چه؟
اگر نیازمندی تغییر کند چه؟
اول فکر میکردم مردد هستند.
بعد فهمیدم دارند هزینه تصمیم را میسنجند.
تازه آنجا بود که متوجه شدم.
خیلی وقتها اعتمادبهنفس نشانه دانستن نیست.
نشانه ندیدن پیچیدگیهای مسئله است.
هرچه تجربه بیشتر میشود، تعداد جوابهای قطعی کمتر میشود.
چون میفهمی پشت هر تصمیمی که امروز میگیری،
فهرستی از پیامدها ایستادهاند که هنوز خودشان را نشان ندادهاند.
شاید یکی از نشانههای رشد حرفهای این باشد که
به جای اینکه سریعتر جواب بدهی،
عمیقتر فکر کنی.
یه زمانی فکر میکردم مشکل پروژهها از کمبود آدمهای حرفهایه.
فکر میکردم اگه چند تا Senior Developer خوب بیاری، بیشتر مشکلات حل میشه.
بعد توی چند تا پروژه مختلف دیدم.
پروژههایی که آدمهای خیلی قوی داشتن.
و پروژههایی که تیمشون معمولیتر بود.
یه نکته جالب دیدم.
خیلی از پروژههای موفق، بهترین آدمها رو نداشتن.ولی بهترین تصمیمها رو داشتن.
و خیلی از پروژههای ناموفق، پر بودن از آدمهای باهوش.
اما هر کسی در حال اثبات خودش بود.
هر کسی میخواست ثابت کنه راهحلش بهتره.
هر کسی میخواست معماری خودش انتخاب بشه.
هر کسی میخواست حرف آخر رو بزنه.
کمکم فهمیدم که موفقیت یک تیم، فقط جمع توانایی اعضاش نیست.
حاصل جهتگیری اون تواناییهاست.
هشت نفر که در یک مسیر حرکت میکنن،
معمولاً نتیجه بهتری میگیرن از هشت نفر فوقالعاده که هر کدوم به یک سمت میکشن.
در دنیای نرمافزار، مشکل همیشه کمبود استعداد نیست.
خیلی وقتها، کمبود همراستاییه.
و ساختن همراستایی،معمولاً از نوشتن کد خیلی سختتره.
فکر میکردم اگه چند تا Senior Developer خوب بیاری، بیشتر مشکلات حل میشه.
بعد توی چند تا پروژه مختلف دیدم.
پروژههایی که آدمهای خیلی قوی داشتن.
و پروژههایی که تیمشون معمولیتر بود.
یه نکته جالب دیدم.
خیلی از پروژههای موفق، بهترین آدمها رو نداشتن.ولی بهترین تصمیمها رو داشتن.
و خیلی از پروژههای ناموفق، پر بودن از آدمهای باهوش.
اما هر کسی در حال اثبات خودش بود.
هر کسی میخواست ثابت کنه راهحلش بهتره.
هر کسی میخواست معماری خودش انتخاب بشه.
هر کسی میخواست حرف آخر رو بزنه.
کمکم فهمیدم که موفقیت یک تیم، فقط جمع توانایی اعضاش نیست.
حاصل جهتگیری اون تواناییهاست.
هشت نفر که در یک مسیر حرکت میکنن،
معمولاً نتیجه بهتری میگیرن از هشت نفر فوقالعاده که هر کدوم به یک سمت میکشن.
در دنیای نرمافزار، مشکل همیشه کمبود استعداد نیست.
خیلی وقتها، کمبود همراستاییه.
و ساختن همراستایی،معمولاً از نوشتن کد خیلی سختتره.
🎯 سوالات مصاحبه برای Microservices
اگر برای مصاحبههای معماری نرمافزار، Senior Backend یا Solution Architect آماده میشوید، اینها از مهمترین سوالاتی هستند که باید بتوانید به آنها پاسخ دهید. 🚀
1️⃣ چگونه مرزهای سرویسها را با استفاده از Domain-Driven Design (DDD) تعریف میکنید؟
2️⃣ چه معیارهایی تعیین میکنند که یک سرویس را جدا کنید یا آن را بهصورت مشترک نگه دارید؟
3️⃣ چگونه یک «Distributed Monolith» را شناسایی و از آن جلوگیری میکنید؟
4️⃣ مزایا و معایب Orchestration و Choreography چیست؟
5️⃣ چگونه از الگوی Strangler Fig برای مهاجرت سیستمهای Legacy استفاده میکنید؟
6️⃣ چه زمانی یک Shared Library بهتر از الگوی Sidecar است؟
7️⃣ چه زمانی برای ارتباطات داخلی gRPC را به REST ترجیح میدهید؟
8️⃣ چگونه الگوی Circuit Breaker را برای جلوگیری از Cascading Failure پیادهسازی میکنید؟
9️⃣ چگونه مشکل Thundering Herd را زمانی که یک سرویس دوباره آنلاین میشود مدیریت میکنید؟
🔟 استراتژی شما برای Retry با Exponential Backoff چیست؟
1️⃣1️⃣ چگونه هنگام تکامل Schema، سازگاری عقبرو (Backward Compatibility) API را حفظ میکنید؟
2️⃣1️⃣ چگونه Idempotency را در Message Consumerها طراحی میکنید؟
3️⃣1️⃣ چگونه بین Sagaهای مبتنی بر Choreography و Sagaهای مبتنی بر Orchestration انتخاب میکنید؟
4️⃣1️⃣ چگونه Joinهای بین سرویسها را برای گزارشگیریهای پیچیده مدیریت میکنید؟
5️⃣1️⃣ مزایا و معایب Database-per-Service در مقابل Database-Schema-per-Service چیست؟
6️⃣1️⃣ چگونه تراکنشهای توزیعشده را بدون Two-Phase Commit (2PC) مدیریت میکنید؟
7️⃣1️⃣ چگونه مشکل Dual Write را هنگام بهروزرسانی پایگاه داده و انتشار Eventها حل میکنید؟
8️⃣1️⃣ چه استراتژیهایی برای Eventual Consistency استفاده میکنید؟
9️⃣1️⃣ چگونه Distributed Tracing را بین چندین سرویس پیادهسازی میکنید؟
0️⃣2️⃣ چگونه Drift شدن Configuration را بین محیطهای مختلف مدیریت میکنید؟
1️⃣2️⃣ استراتژی شما برای Dead Letter Queue (DLQ) و بازپخش پیامها (Message Replay) چیست؟
2️⃣2️⃣ چگونه ارتباطات Service-to-Service را ایمن میکنید؟
3️⃣2️⃣ چگونه تفاوت بین تأخیر شبکه (Network Latency) و کندی خود برنامه (Application Slowness) را تشخیص میدهید؟
4️⃣2️⃣ چگونه یک Microservice را بهصورت ایزوله تست میکنید بدون اینکه کل مجموعه سرویسها را Mock کنید؟
💡 بسیاری از این سوالات مستقیماً به مفاهیمی مانند:
✅ Domain-Driven Design (DDD)
✅ Event-Driven Architecture
✅ Saga Pattern
✅ Outbox & Inbox Pattern
✅ CQRS
✅ Eventual Consistency
✅ Distributed Tracing
✅ Resiliency Patterns
✅ RabbitMQ / Kafka
✅ Kubernetes & Cloud-Native Architecture
مرتبط هستند و در مصاحبههای سطح Senior و Architect بسیار رایجاند. 🚀🔥
هشتگها:
#Microservices #SoftwareArchitecture #BackendDevelopment #EventDrivenArchitecture #TechInterview
یکی از جالبترین چیزهایی که در مصاحبههای فنی یاد گرفتم این بود که همیشه بهترین پاسخها، درستترین پاسخها نیستند.
بارها پیش آمده از یک نفر پرسیدهام:
«برای حل این مسئله چه راهکاری پیشنهاد میکنی؟»
و او شروع کرده به توضیح دادن پیشرفتهترین معماریها، Patternها و تکنولوژیهایی که میشناسد.
همه چیز از نظر تئوری درست بوده است.
اما یک سؤال همیشه در ذهنم شکل میگرفت:
«برای چه؟»
برای ۱۰۰ کاربر؟
برای ۱۰ میلیون کاربر؟
برای یک تیم ۳ نفره؟
یا یک تیم ۱۰۰ نفره؟
چون در مهندسی نرمافزار، هیچ راهکاری در خلأ خوب یا بد نیست.همه چیز به مسئله بستگی دارد.به محدودیتها.به هزینهها.و به Trade-offهایی که حاضر هستیم بپذیریم.
به مرور زمان متوجه شدم چیزی که در مصاحبهها بیشتر از دانش فنی توجه مرا جلب میکند،نوع فکر کردن افراد است.
اینکه قبل از ارائه راهحل، چند سؤال میپرسند.
چقدر برای فهمیدن مسئله زمان میگذارند.
و چقدر حاضرند فرضیات خودشان را به چالش بکشند.
چون در دنیای واقعی،بیشتر وقت ما صرف حل مسئله نمیشود.صرف فهمیدن مسئله میشود.
و گاهی تفاوت یک توسعهدهنده خوب و یک مهندس نرمافزار خوب،در کیفیت پاسخهایشان نیست.
در کیفیت سؤالهایشان است.
بارها پیش آمده از یک نفر پرسیدهام:
«برای حل این مسئله چه راهکاری پیشنهاد میکنی؟»
و او شروع کرده به توضیح دادن پیشرفتهترین معماریها، Patternها و تکنولوژیهایی که میشناسد.
همه چیز از نظر تئوری درست بوده است.
اما یک سؤال همیشه در ذهنم شکل میگرفت:
«برای چه؟»
برای ۱۰۰ کاربر؟
برای ۱۰ میلیون کاربر؟
برای یک تیم ۳ نفره؟
یا یک تیم ۱۰۰ نفره؟
چون در مهندسی نرمافزار، هیچ راهکاری در خلأ خوب یا بد نیست.همه چیز به مسئله بستگی دارد.به محدودیتها.به هزینهها.و به Trade-offهایی که حاضر هستیم بپذیریم.
به مرور زمان متوجه شدم چیزی که در مصاحبهها بیشتر از دانش فنی توجه مرا جلب میکند،نوع فکر کردن افراد است.
اینکه قبل از ارائه راهحل، چند سؤال میپرسند.
چقدر برای فهمیدن مسئله زمان میگذارند.
و چقدر حاضرند فرضیات خودشان را به چالش بکشند.
چون در دنیای واقعی،بیشتر وقت ما صرف حل مسئله نمیشود.صرف فهمیدن مسئله میشود.
و گاهی تفاوت یک توسعهدهنده خوب و یک مهندس نرمافزار خوب،در کیفیت پاسخهایشان نیست.
در کیفیت سؤالهایشان است.
Forwarded from thisisnabi.dev [Farsi]
2. Data Partitioning
منابع پیشنهادی این چالش که برای من اینها جذاب بوده و هست.
A. Pro SQL Server Internals [book]
- Chapters 16 - required
B. MySql Docs
- site
@thisisnabi_dev
منابع پیشنهادی این چالش که برای من اینها جذاب بوده و هست.
A. Pro SQL Server Internals [book]
- Chapters 16 - required
B. MySql Docs
- site
@thisisnabi_dev
چند وقت پیش داشتم یک مستند درباره پلهای قدیمی دنیا میدیدم.
یکی از مهندسها جمله جالبی گفت:
«بیشتر پلها به خاطر چیزی که نمیدانیم خراب نمیشوند. به خاطر چیزی خراب میشوند که فکر میکنیم میدانیم.»
این جمله عجیب در ذهنم ماند.
چون هرچه بیشتر در توسعه نرمافزار کار میکنم، بیشتر نمونههایش را میبینم.
خیلی از بحرانهای فنی از جایی شروع نمیشوند که دانش نداریم.
از جایی شروع میشوند که بیش از حد مطمئن هستیم.
مطمئنیم این Feature هیچوقت تغییر نمیکند.
مطمئنیم تعداد کاربران از این بیشتر نمیشود.
مطمئنیم این سرویس همیشه در دسترس خواهد بود.
مطمئنیم این Dependency مشکلی ایجاد نمیکند.
و دقیقاً همین اطمینانها هستند که بعدها ما را غافلگیر میکنند.
جالب است که در مهندسی نرمافزار، ندانستن معمولاً خطرناک نیست.
وقتی چیزی را نمیدانی، سؤال میپرسی.
تحقیق میکنی.
آزمایش میکنی.
اما وقتی فکر میکنی میدانی،اغلب هیچکدام از این کارها را انجام نمیدهی.
شاید به همین دلیل باشد که با افزایش تجربه، اعتمادبهنفس واقعی شبیه اطمینان مطلق نیست.
بیشتر شبیه احتیاط است.
شبیه این است که بدانی ممکن است چیزی را نبینی.
چون بسیاری از مشکلات بزرگ نرمافزار،از کمبود دانش به وجود نمیآیند.
از قطعیتهای اشتباه به وجود میآیند.
یکی از مهندسها جمله جالبی گفت:
«بیشتر پلها به خاطر چیزی که نمیدانیم خراب نمیشوند. به خاطر چیزی خراب میشوند که فکر میکنیم میدانیم.»
این جمله عجیب در ذهنم ماند.
چون هرچه بیشتر در توسعه نرمافزار کار میکنم، بیشتر نمونههایش را میبینم.
خیلی از بحرانهای فنی از جایی شروع نمیشوند که دانش نداریم.
از جایی شروع میشوند که بیش از حد مطمئن هستیم.
مطمئنیم این Feature هیچوقت تغییر نمیکند.
مطمئنیم تعداد کاربران از این بیشتر نمیشود.
مطمئنیم این سرویس همیشه در دسترس خواهد بود.
مطمئنیم این Dependency مشکلی ایجاد نمیکند.
و دقیقاً همین اطمینانها هستند که بعدها ما را غافلگیر میکنند.
جالب است که در مهندسی نرمافزار، ندانستن معمولاً خطرناک نیست.
وقتی چیزی را نمیدانی، سؤال میپرسی.
تحقیق میکنی.
آزمایش میکنی.
اما وقتی فکر میکنی میدانی،اغلب هیچکدام از این کارها را انجام نمیدهی.
شاید به همین دلیل باشد که با افزایش تجربه، اعتمادبهنفس واقعی شبیه اطمینان مطلق نیست.
بیشتر شبیه احتیاط است.
شبیه این است که بدانی ممکن است چیزی را نبینی.
چون بسیاری از مشکلات بزرگ نرمافزار،از کمبود دانش به وجود نمیآیند.
از قطعیتهای اشتباه به وجود میآیند.
Forwarded from tech-afternoon (Amin Mesbahi)
توی نظرسنجی آخر، گزینه Inbox/Outbox Pattern رأی دوم رو آورد، بالاخره امروز مطلب رو جمعجور کردم. با اینکه «مرور» است ولی از نظر حجم کمی بیشتر از مطالب رایج شد (چون اصل داستان سیستمهای توزیع شده، خیلی مبحث گستردهایه و مرور یک مفهوم رایج و دمدستیاش هم نیاز به توضیح بیشتری داشت.
پیشاپیش بابت اشتباهات نگارشی یا انشایی متن عذرخواهی میکنم، دلیلش هم اینه که طی دو هفته هر زمان فرصت کوتاهی پیدا میشد بخشیاش رو نوشتم و احتمالا اگر فرصت بازبینی و ویرایشش بود، متن روانتری به دست میومد؛ ولی خب، قرار نیست هر ورژن اولیهای بهترین باشه.
- مرور الگوی outbox/inbox
- مسئله اصلی: Dual Write Problem
- الگوی Outbox
- الگوی Inbox
- بررسی جزئیتر مفهوم Idempotency
- طراحی بهتر پیامها
- تفاوت Domain Event و Integration Event
- روشهای پیادهسازی Outbox Publisher
- ابزارها و فریمورکهای رایج برای پیادهسازی inbox/outbox
- مفاهیم مکمل: Poison Message، Retry و Dead Letter
- لزوم Observability و Monitoring
- کاربرد Ordering پیامها
- پاکسازی دادهها
- تفاوت Outbox با Event Sourcing
- نسبت Outbox و Inbox با Saga
- چه زمانی واقعا به Inbox و Outbox نیاز داریم؟
🔗 لینک مطلب
Please open Telegram to view this post
VIEW IN TELEGRAM
#مهندس_فکر_کن
قسمت:1️⃣
🎯 سوال مصاحبه مهندسی نرمافزار
فرض کنید یک سرویس ASP.NET Core دارید که پیامها را از Kafka دریافت میکند.
پیام دریافت میشود.Business Logic اجرا میشود.
داده در Database ذخیره میشود.
اما درست قبل از Commit Offset سرویس Crash میکند.
پس از Restart شدن سرویس، همان پیام دوباره پردازش میشود.
سؤال:
چگونه از ثبت دوباره اطلاعات جلوگیری میکنید؟
قسمت:1️⃣
🎯 سوال مصاحبه مهندسی نرمافزار
فرض کنید یک سرویس ASP.NET Core دارید که پیامها را از Kafka دریافت میکند.
پیام دریافت میشود.Business Logic اجرا میشود.
داده در Database ذخیره میشود.
اما درست قبل از Commit Offset سرویس Crash میکند.
پس از Restart شدن سرویس، همان پیام دوباره پردازش میشود.
سؤال:
چگونه از ثبت دوباره اطلاعات جلوگیری میکنید؟
Forwarded from مسعود بیگی (مسعود بیگی)
چرا هر تیم نرمافزاری باید Runbook داشته باشه؟ سلام دوستان، تو دنیای امروز که سیستمها ۲۴/۷ کار میکنن، Runbook دیگه لوکس نیست، یه ضرورت هست.Runbook چیه؟سند زندهای که دقیقاً میگه:وقتی مشکلی پیش اومد چیکار کنیم؟ (Step-by-step)
وابستگیهای سیستم چیا هستن؟
چکلیستهای روزانه، هفتگی و ماهانه
راهحلهای رایج خطاها و Incidentها
اطلاعات تماس افراد کلیدی و Escalation Path
حتی به تیم های مرتبط میگه endpoint هامون چین و ورودی خروجی های مورد انتظارش چیه
اهمیت Runbook برای تیمهای نرمافزاری:کاهش چشمگیر زمان Downtime
وقتی Incident اتفاق میافته، دیگه کسی سردرگم نیست که "اول کجا رو چک کنیم؟" تیم تو کمتر از ۵ دقیقه وارد Action میشه.
کاهش وابستگی به افراد خاص
یکی از اعضای کلیدی تیم مرخصی باشه یا شرکت رو ترک کنه، دانش از دست نمیره.
Onboarding خیلی سریعتر
developer یا SRE جدید تو همون هفته اول میتونه Incidentهای سطح متوسط رو هندل کنه.
Consistency در عملیات
همه اعضای تیم به یک روش استاندارد عمل میکنن، نه هر کسی به سلیقه خودش.
بهبود کیفیت و آرامش خاطر
وقتی بدونید همه چیز مستند شده، خواب راحتتری دارید!
تجربه واقعی:تیمهایی که Runbook خوب دارن، MTTR (Mean Time To Recovery) شون رو تا ۵۰-۷۰٪ کاهش دادن.پیشنهاد من:
همین امروز یه Runbook اولیه برای مهمترین سرویسهاتون بسازید. حتی اگه ناقص باشه، بهتر از هیچی هست. بعد به مرور کاملش کنید.از ابزارهایی مثل Notion، Confluence، Markdown تو GitHub یا GitLab Wiki میتونید استفاده کنید.شما Runbook دارید؟
تو کامنت بگید تیمتون چقدر به مستندات عملیاتی اهمیت میده؟ #DevOps #SRE #Runbook #SiteReliability #SoftwareEngineering #تیم_فنی
وابستگیهای سیستم چیا هستن؟
چکلیستهای روزانه، هفتگی و ماهانه
راهحلهای رایج خطاها و Incidentها
اطلاعات تماس افراد کلیدی و Escalation Path
حتی به تیم های مرتبط میگه endpoint هامون چین و ورودی خروجی های مورد انتظارش چیه
اهمیت Runbook برای تیمهای نرمافزاری:کاهش چشمگیر زمان Downtime
وقتی Incident اتفاق میافته، دیگه کسی سردرگم نیست که "اول کجا رو چک کنیم؟" تیم تو کمتر از ۵ دقیقه وارد Action میشه.
کاهش وابستگی به افراد خاص
یکی از اعضای کلیدی تیم مرخصی باشه یا شرکت رو ترک کنه، دانش از دست نمیره.
Onboarding خیلی سریعتر
developer یا SRE جدید تو همون هفته اول میتونه Incidentهای سطح متوسط رو هندل کنه.
Consistency در عملیات
همه اعضای تیم به یک روش استاندارد عمل میکنن، نه هر کسی به سلیقه خودش.
بهبود کیفیت و آرامش خاطر
وقتی بدونید همه چیز مستند شده، خواب راحتتری دارید!
تجربه واقعی:تیمهایی که Runbook خوب دارن، MTTR (Mean Time To Recovery) شون رو تا ۵۰-۷۰٪ کاهش دادن.پیشنهاد من:
همین امروز یه Runbook اولیه برای مهمترین سرویسهاتون بسازید. حتی اگه ناقص باشه، بهتر از هیچی هست. بعد به مرور کاملش کنید.از ابزارهایی مثل Notion، Confluence، Markdown تو GitHub یا GitLab Wiki میتونید استفاده کنید.شما Runbook دارید؟
تو کامنت بگید تیمتون چقدر به مستندات عملیاتی اهمیت میده؟ #DevOps #SRE #Runbook #SiteReliability #SoftwareEngineering #تیم_فنی
چند سال پیش از یکی از مدیران فنی سؤال کردم:
«چطور میفهمی یک تصمیم معماری خوب بوده یا نه؟»
فکر میکردم قرار است درباره Performance، Scalability یا Availability صحبت کند.
اما جوابش چیز دیگری بود.
گفت:
«ببین چند ماه بعد، تغییر دادن آن تصمیم چقدر درد دارد.»
آن زمان خیلی متوجه منظورش نشدم.
اما هرچه بیشتر روی سیستمهای واقعی کار کردم، بیشتر به عمق این جمله پی بردم.
بیشتر تصمیمهای معماری، روزی که گرفته میشوند عالی به نظر میرسند.
مشکل زمانی شروع میشود که نیازمندیها تغییر میکنند.
کسبوکار مسیر جدیدی میرود.
تیم بزرگتر میشود.
یا حجم کاربران چند برابر میشود.
اینجاست که کیفیت واقعی تصمیمها مشخص میشود.
چون بعضی تصمیمها فقط برای امروز بهینه هستند.
و بعضی دیگر، تغییرات آینده را هم در نظر گرفتهاند.
جالب است که انعطافپذیری یک سیستم را معمولاً از روی چیزهایی که میتواند انجام دهد نمیسنجند.
از روی چیزهایی میسنجند که میتواند تغییر دهد.
شاید به همین دلیل باشد که مهندسی نرمافزار کمتر درباره پیشبینی آینده است.
و بیشتر درباره آماده بودن برای آیندهای است که نمیتوانی پیشبینیاش کنی.
در نهایت، ارزش یک طراحی خوب فقط در این نیست که امروز جواب میدهد.
در این است که فردا هم بتوانی با آن زندگی کنی.
«چطور میفهمی یک تصمیم معماری خوب بوده یا نه؟»
فکر میکردم قرار است درباره Performance، Scalability یا Availability صحبت کند.
اما جوابش چیز دیگری بود.
گفت:
«ببین چند ماه بعد، تغییر دادن آن تصمیم چقدر درد دارد.»
آن زمان خیلی متوجه منظورش نشدم.
اما هرچه بیشتر روی سیستمهای واقعی کار کردم، بیشتر به عمق این جمله پی بردم.
بیشتر تصمیمهای معماری، روزی که گرفته میشوند عالی به نظر میرسند.
مشکل زمانی شروع میشود که نیازمندیها تغییر میکنند.
کسبوکار مسیر جدیدی میرود.
تیم بزرگتر میشود.
یا حجم کاربران چند برابر میشود.
اینجاست که کیفیت واقعی تصمیمها مشخص میشود.
چون بعضی تصمیمها فقط برای امروز بهینه هستند.
و بعضی دیگر، تغییرات آینده را هم در نظر گرفتهاند.
جالب است که انعطافپذیری یک سیستم را معمولاً از روی چیزهایی که میتواند انجام دهد نمیسنجند.
از روی چیزهایی میسنجند که میتواند تغییر دهد.
شاید به همین دلیل باشد که مهندسی نرمافزار کمتر درباره پیشبینی آینده است.
و بیشتر درباره آماده بودن برای آیندهای است که نمیتوانی پیشبینیاش کنی.
در نهایت، ارزش یک طراحی خوب فقط در این نیست که امروز جواب میدهد.
در این است که فردا هم بتوانی با آن زندگی کنی.