Skip to content

فصل ۷ — تجربه‌گرایی

تجربه‌گرایی (Empiricism)، در فلسفه علم، به‌صورت «تأکید بر شواهد، به‌ویژه آنچه در آزمایش‌ها کشف می‌شود» تعریف می‌شود. «بخش بنیادی روش علمی است که همه فرضیه‌ها و نظریه‌ها باید در برابر مشاهدات دنیای طبیعی آزمایش شوند، نه اینکه تنها بر استدلال a priori، شهود یا وحی استوار باشند.»[^1]

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

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

[^1]: منبع: Wikipedia، https://en.wikipedia.org/wiki/Empiricism

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

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

ریشه در واقعیت

بعد دیگر این است که سیستم‌های تولیدی‌مان همیشه ما را غافلگیر می‌کنند و باید هم بکنند!

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

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

شواهد از بررسی تأثیر انتخاب‌ها در محصولات نرم‌افزاری نشان می‌دهد برای بهترین شرکت‌ها، فقط بخشی از ایده‌هایشان اثراتی که پیش‌بینی کرده‌اند تولید می‌کند.

ویژگی‌ها ساخته می‌شوند چون تیم‌ها باور دارند مفیدند، با این حال در بسیاری از حوزه‌ها بیشتر ایده‌ها در بهبود معیارهای کلیدی شکست می‌خورند. فقط یک‌سوم ایده‌های آزمایش‌شده در Microsoft معیار(هایی) که برای بهبودشان طراحی شده بودند را بهبود دادند.[^2]

[^2]: در مقاله‌ای با عنوان «Online Experiments at Large Scale» (https://stanford.io/2LdjvmC)، نویسندگان توصیف می‌کنند دو تا سه‌سوم ایده‌های تغییر نرم‌افزار ارزش صفر یا منفی برای سازمانی که آن‌ها را پیاده‌سازی کرده تولید کردند.

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

تفکیک تجربه‌گرایی از آزمایشی بودن

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

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

«این باگ را می‌شناسم!»

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

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

کاندید انتشار در حال بررسی‌های نهایی قبل از انتشار بود. اوایل همان روز، یکی از همکارانمان Darren در stand-up گفت که شکست عجیب messaging روی ایستگاه توسعه‌اش هنگام اجرای مجموعه acceptance testهای API دیده است. ظاهراً threadی در کد pub-sub messaging شخص ثالث زیربنایی مسدود شده بود. سعی کرد بازتولید کند و توانست، اما فقط روی یک pairing station خاص. عجیب بود، چون پیکربندی محیط‌هایمان کاملاً خودکار و version-controlled با رویکرد نسبتاً پیچیده infrastructure-as-code بود.

بعدازظهر همان روز، کار روی مجموعه بعدی تغییرات را شروع کردیم. تقریباً فوراً build grid تغییر چشمگیری با شکست زیاد acceptance testها نشان داد. شروع به بررسی کردیم و متوجه شدیم یکی از سرویس‌ها بار CPU بسیار بالایی داشت. غیرمعمول بود چون نرم‌افزارمان معمولاً فوق‌العاده کارآمد بود. با بررسی بیشتر، متوجه شدیم کد messaging جدیدمان ظاهراً گیر کرده بود. حتماً همان چیزی است که Darren دیده بود. واضح بود مشکلی با کد messaging جدیدمان داریم!

فوراً واکنش نشان دادیم. به همه گفتیم کاندید انتشار شاید برای انتشار آماده نباشد. شروع به فکر کردیم شاید مجبور شویم branch بگیریم — کاری که معمولاً سعی می‌کردیم اجتناب کنیم — و تغییرات messaging را برگردانیم.

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

توقف کردیم و آنچه می‌دانستیم را مرور کردیم؛ حقایق را جمع کردیم. messaging را در ابتدای iteration ارتقا داده بودیم و thread dump داشتیم که messaging متوقف شده بود؛ Darren هم داشت، اما dump او در جای متفاوتی متوقف به نظر می‌رسید. بیش از یک هفته همه این تست‌ها را در deployment pipeline مکرراً و با موفقیت اجرا کرده بودیم، با تغییرات messaging.

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

خلاصه داستان: messaging سالم بود. «مشکل messaging» ظاهری، نه علت بود. در واقع thread dumpی را می‌دیدیم که در حالت انتظار عادی و طبق انتظار کار می‌کرد. آنچه اتفاق افتاده بود این بود که باگ threading در کد جدیدی نامرتبط با messaging برخورد کرده بودیم. اصلاح واضح و ساده بود و اگر به نتیجه‌گیری که مشکل messaging است نمی‌پریدیم، در پنج دقیقه بدون دردسر پیدا می‌کردیم؛ در واقع پس از توقف برای فکر کردن و ساخت فرضیه بر اساس حقایقی که داشتیم، نه پریدن به نتیجه‌ای اشتباه اما ظاهراً «واضح»، در پنج دقیقه اصلاح کردیم.

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

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

علم کار می‌کند! فرضیه بسازید. بفهمید چگونه اثبات یا رد کنید. آزمایش را انجام دهید. نتایج را مشاهده کنید و ببینید با فرضیه‌تان جور می‌آید. تکرار!

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

اجتناب از فریب خود

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

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

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

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

اصل اول این است که نباید خودتان را فریب دهید — و شما آسان‌ترین کسی هستید که می‌توانید فریب دهید.[^3]

[^3]: فیزیکدان برنده جایزه نوبل Richard Feynman (۱۹۱۸–۱۹۸۸)، https://bit.ly/2PLfEU3

علم آن چیزی نیست که بیشتر مردم فکر می‌کنند. درباره برخوردکننده هادرون بزرگ یا پزشکی مدرن یا حتی فیزیک نیست. علم تکنیک حل مسئله است. مدلی از مشکل پیش رو می‌سازیم و بررسی می‌کنیم آیا همه چیزهایی که فعلاً می‌دانیم با مدل جور می‌آید. سپس سعی می‌کنیم راه‌هایی فکر کنیم که بتوانیم مدل را اشتباه ثابت کنیم. David Deutsch می‌گوید مدل از «توضیحات خوب» تشکیل شده است.[^4]

[^4]: The Beginning of Infinity اثر David Deutsch، https://amzn.to/2IyY553

ساختن واقعیتی متناسب با استدلالمان

بیایید مثال دیگری از اینکه چقدر آسان می‌توانیم خودمان را فریب دهیم ببینیم.

وقتی بورس فوق‌سریعمان را می‌ساختیم،[^5] آزمایش زیادی در ساخت نرم‌افزار بسیار سریع انجام دادیم. از طریق آزمایش، چیزهای جالب زیادی کشف کردیم. قابل‌توجه‌ترین، رویکرد طراحی نرم‌افزاری بود که mechanical sympathy نامیدیم.

[^5]: بیشتر درباره معماری نوآورانه بورس ما بخوانید: https://bit.ly/3a48mS3.

در این رویکرد، کدمان را بر اساس درک نسبتاً عمیق از نحوه کار سخت‌افزار زیربنایی طراحی می‌کردیم تا بتوانیم از آن بهره ببریم. یکی از چند درس مهم که از طریق آزمایش یاد گرفتیم این بود که وقتی اشتباهات احمقانه را حذف کردید،[^6] مهم‌ترین تأثیر بر عملکرد خام قطعه کد در کامپیوتر مدرن cache-miss بود. اجتناب از cache-miss بر رویکرد طراحی برای بخش‌های با عملکرد بسیار بالای کدمان مسلط شد.

[^6]: رایج‌ترین اشتباه عملکردی استفاده از نوع نادرست ساختار داده برای ذخیره چیزی است. بسیاری از توسعه‌دهندگان زمان بازیابی انواع مختلف collectionها را در نظر نمی‌گیرند. برای اندازه‌های کوچک collection، یک آرایه ساده (O(n) در بازیابی) ممکن است سریع‌تر از چیزی مثل hash table (با معنای O(1)) باشد. برای collectionهای بزرگ‌تر، راه‌حل O(1) برای دسترسی تصادفی بهترین است. بعد از آن، پیاده‌سازی collectionها می‌تواند هزینه داشته باشد.

یکی از رایج‌ترین علل cache-miss برای بیشتر سیستم‌هایی که با اندازه‌گیری پیدا کردیم، concurrency بود.

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

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

[^7]: ارائه‌ای درباره موازی‌سازی خودکار: https://bit.ly/35JPqVs

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

به زبان دانشگاهی دسترسی نداشتیم، اما یکی از همکارانم Mike Barker یک آزمایش ساده انجام داد. همان الگوریتمی که زبان‌شناسان دانشگاهی توصیف می‌کردند را در Scala و رویکرد ساده brute-force در Java پیاده‌سازی کرد و سپس نتایج را با پردازش متن Alice in Wonderland Lewis Carroll در مجموعه‌ای از اجراها اندازه‌گیری کرد.

الگوریتم همزمان Scala در ۶۱ خط کد پیاده‌سازی شد؛ نسخه Java ۳۳ خط گرفت.

نسخه Scala می‌توانست ۴۰۰ نسخه کتاب در ثانیه پردازش کند. تا وقتی با کد تک‌رشته‌ای ساده‌تر و خواناتر Java که ۱٬۶۰۰ نسخه در ثانیه پردازش می‌کرد مقایسه نکنید، چشمگیر به نظر می‌رسد.

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

تفکیک افسانه از واقعیت: مثال

درک شده که توسعه CPU به حدی رسیده و افزایش‌های سرعت clock-cycle متوقف شده‌اند. clock cycleها از حدود ۲۰۰۵ افزایش نیافته‌اند! دلایل خوبی بر اساس فیزیک ساخت ترانزیستور از سیلیکون وجود دارد. رابطه‌ای بین تراکم ترانزیستورها و گرمای تولیدشان در عمل وجود دارد. ساخت تراشه‌ای که خیلی سریع‌تر از ۳GHz باشد یعنی overheating مشکل جدی می‌شود.

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

این برای اجرای فرایندهای مستقل و غیرمرتبط خوب است، اما اگر بخواهید الگوریتم سریعی بسازید؟ نتیجه‌گیری واضح (حدس) این است که راه‌حل این مشکل ناگزیر موازی‌سازی الگوریتم‌هایمان است. اساساً، ایده این است که می‌توانیم با پرتاب threadهای اجرای بیشتر به مشکلاتی که حل می‌کنیم سرعت ببخشیم.

چند زبان برنامه‌نویسی عمومی بر این فرض بنا شده‌اند تا به ما کمک کنند راه‌حل‌های موازی مؤثرتری بنویسیم.

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

بیایید بازخورد جمع کنیم. به‌جای پریدن به نتیجه که موازی‌سازی پاسخ است، داده جمع کنیم.

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

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

MethodTime (ms)
Single thread300
Single thread with lock10,000
Two threads with lock224,000
Single thread with CAS5,700
Two threads with CAS30,000

جدول نتیجه این آزمایش با رویکردهای مختلف را نشان می‌دهد. ابتدا تست پایه. کد را روی یک thread بنویسید و مقدار long را افزایش دهید. ۳۰۰ میلی‌ثانیه طول می‌کشد تا به ۵۰۰ میلیون برسد.

به‌محض معرفی کد برای همگام‌سازی، هزینه‌هایی می‌بینیم که پیش‌بینی نکرده بودیم (مگر متخصص concurrency سطح پایین باشیم). اگر همه کار را روی یک thread انجام دهیم اما lock اضافه کنیم تا نتایج از thread دیگر قابل استفاده باشد، ۹٬۷۰۰ میلی‌ثانیه به هزینه اضافه می‌شود. lockها فوق‌العاده گران‌اند!

اگر تصمیم بگیریم کار را بین فقط دو thread تقسیم کنیم و نتایجشان را همگام کنیم، ۷۴۶ برابر کندتر از انجام کار روی یک thread است!

پس lockها فوق‌العاده گران‌اند. راه‌های کارآمدتر اما سخت‌تر برای استفاده برای هماهنگی کار بین threadها وجود دارد. کارآمدترین راه رویکرد concurrency سطح پایین به نام compare-and-swap (CAS) است. متأسفانه، حتی این رویکرد ۱۰۰ برابر کندتر از کار روی یک thread است.

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

(این آزمایش را اولین بار Mike Barker وقتی چند سال پیش با هم کار می‌کردیم انجام داد.)

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

هدایت‌شده توسط واقعیت

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

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

بهترین راه شروع این است که فرض کنیم آنچه می‌دانیم و فکر می‌کنیم احتمالاً اشتباه است و سپس بفهمیم چگونه می‌توانید بفهمید چگونه اشتباه است.

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

متأسفانه، هزینه‌های موازی‌سازی را در نظر نگرفتند؛ واقعیت سخت‌افزار کامپیوتر مدرن و علوم کامپیوتر را نادیده گرفتند. مدت‌هاست فهمیده شده موازی‌سازی وقتی باید «نتایج را دوباره به هم بچسبانید» هزینه دارد. قانون Amdahl نشان می‌دهد محدودیت سختی بر تعداد عملیات همزمان معقول وجود دارد، مگر کاملاً مستقل از یکدیگر باشند.

دانشگاهیان فرض کردند «موازی‌سازی بیشتر خوب است»، اما این ایده‌ای است بر اساس نوعی ماشین خیالی و نظری که هزینه‌های concurrency پایین بود؛ چنین ماشین‌هایی وجود ندارند.

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

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

خلاصه

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