<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های جعفر خاکپور</title>
        <link>https://virgool.io/feed/@jfr.khakpour</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-04-15 06:44:23</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/1016018/avatar/0KVVwX.png?height=120&amp;width=120</url>
            <title>جعفر خاکپور</title>
            <link>https://virgool.io/@jfr.khakpour</link>
        </image>

                    <item>
                <title>پایتون و مدیریت حافظه،‌  Stack و فریم در پایتون چطور کار میکند</title>
                <link>https://virgool.io/@jfr.khakpour/%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86-%D9%88-%D9%85%D8%AF%DB%8C%D8%B1%DB%8C%D8%AA-%D8%AD%D8%A7%D9%81%D8%B8%D9%87-stack-%D9%88-%D9%81%D8%B1%DB%8C%D9%85-%D8%AF%D8%B1-%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86-%DA%86%D8%B7%D9%88%D8%B1-%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C%DA%A9%D9%86%D8%AF-q7kj7ymbgkjn</link>
                <description>من یک سری متن در https://dev.to/j4kh در مورد مدیریت حافظه پایتون (اگر بخوایم دقیقتر حرف بزنیم CPython) نوشتم که گفتم شاید بد نباشه خلاصه‌ای از اون پست‌ها رو با جزئیات کمتر و بخش Hands-on اونها، اینجا هم بنویسم.این سری پستها وبلاگ در مورد این صحبت می‌کنن که:پایتون چطور آبجکت‌ها رو در حافظه می‌سازه و از بین می‌بره و Garbage collector توی پایتون چطور کار می‌کنه؛ چطوری حافظه Heap برای این کارها تخصیص داده می‌شه و Heap توی پایتون چه فرق‌هایی با زبان‌های دیگه داره؛ در نهایت اون چیزی که نقش Stack رو توی پایتون بر عهده داره چیه و این بخش چطوری کار می‌کنه، Frame چیه و چرا Generatorها یک ساختار خیلی قدرتمند برای پایتون به حساب می‌آن.نگاهی به درون ماشین پایتون: نحوه ساخت آبجکت‌ها و مدیریت حافظهوقتی ما توی پایتون یک متغیر رو تعریف میکنیم و مقدار میدیم، دقیقا پشت صحنه چه اتفاقی می‌افته؟ وقتی در یک متغیر از نوع عدد، یک String ذخیره می‌کنیم چی؟ پایتون چطور این اشیا رو مدیریت می‌کنه تا برنامه‌مون هنگ نکنه و حافظه رو درست آزاد کنه؟ بیایید با هم قدم به قدم بریم توی دل CPython (همون پیاده‌سازی اصلی پایتون) و این ماجرا رو بررسی کنیم.۱. آبجکت‌های پایتون: Everythin is an objectاولین و مهم‌ترین نکته اینه که توی پایتون، همه چیز یک شئ (Object) هست. حتی اعداد ساده (مثل عدد ۱ یا ۵) هم یک شئ هستن. این آبجکت‌ها که بوسیله کدهای زبان C مدیر حافظه پایتون مدیریت می‌شن، به صورت یک struct به نامPyObject تعریف می‌شه.این PyObject ها بسته به نوع‌شون می‌تونن متفاوت باشن، ولی حداقل این دو بخش خیلی مهم رو همیشه دارن:1. یک شمارنده مرجع (ob_refcnt): که تعداد ارجاع‌ها به این آبجکت رو نشون می‌ده (تعداد جاهایی که از یک متغیر به این آبجکت ها اشاره کرده). پایتون از این شمارنده برای فهمیدن اینکه کی می‌تونه یه آبجکت رو از بین ببره استفاده می‌کنه.2. یک اشاره‌گر به نوع آبجکت (ob_type): که مشخص می‌کنه این شئ از چه نوعیه (مثلاً int، list، dict). توی این ساختار نوع، اطلاعاتی مثل متدها و نحوه مدیریت حافظه اون نوع خاص ذخیره شده.پس هر وقت یه آبجکت جدید می‌سازیم، یه تکه حافظه برای این ساختار پایه‌ای و بعد هم برای داده‌های خاص اون آبجکت (مثلاً بایت های عدد۱ یا ۵) در نظر گرفته می‌شه. ob_type همون چیزیه که امکان Dynamic Type بودن پایتون رو بوجود میاره. وقتی شما مقدار یک متغیر رو از int تبدیل میکنید به str، مقدار جدید یه رفرنس داره که علاوه بر مقدار ذخیره شده توی رم، به ob_type این مقدار اشاره می‌کنه که اونجا هم اطلاعات ob_type ذخیره شده.با ob_refcnt هم جلوتر خیلی کار داریم،‌ ولی فعلا همین رو اشاره می‌کنم که تعداد جاهایی هست که این مقدار در اونجا مورد استفاده قرار گرفته. (تعداد متغیرهایی که این مقدار رو دارن، attribute های یک آبجکت، درایه های یک لیست و ...).۲. تخصیص حافظه: از malloc تا pymallocحالا این حافظه‌ای که آبجکت‌ها توش قرار می‌گیرن از کجا میاد؟ توی زبان C ( یا هر زبون سطح پایین که مستقیم با حافظه کار می‌کنه) برای گرفتن حافظه از تابعی به اسم malloc استفاده می‌شه. در واقع زبان C به سیستم عامل میگه که من یک بلوک حافظه به طول n بایت می‌خوام و سیستم عامل بعد از تخصیص حافظه، آدرس اول اون بلوک رو برمی‌گردونه به برنامه). اما malloc برای تعداد زیاد و سایزهای کوچیک خیلی هم بهینه نیست و می‌تونه باعث کندی و تکه‌تکه شدن حافظه بشه.برای همین، CPython یه تخصیص‌دهنده حافظه اختصاصی به اسم pymalloc داره. pymalloc لایه‌ای بین پایتون و سیستم‌عامل (و malloc) هست. کارش اینه که حافظه رو به صورت هوشمندانه‌ای برای آبجکت‌های کوچیک مدیریت کنه.۳. استراتژی pymalloc: نقش Arena, Pool &amp; Blockpymalloc حافظه رو به شکلی سازمان‌دهی می‌کنه که بتونه سریع و کارآمد آبجکت‌های کمتر از 512 بایتی رو توش جا بده. این سازمان‌دهی با سه مفهوم اصلی انجام می‌شه:Block: کوچک‌ترین واحد حافظه‌ست. هر بلوک سایز ثابتی داره (مثلاً ۸ بایت، ۱۶ بایت، ۳۲ بایت و ...). یه آبجکت کوچیک مثل یه عدد، دقیقاً توی یه بلوک با سایز مناسب خودش جا می‌گیره. وقتی برنامه شما میخواد یه مقدار رو در حافظه ذخیره کنه، مدیر حافظه پایتون دنبال یک بلوک بلااستفاده با اندازه مناسب می‌گرده و اون رو برای مقداری که برنامه می‌خواد اختصاص می‌ده.این بلوک‌ها همیشه به شکل یه دسته حافظه هم اندازه (پر یا خالی) کنار هم هستن که پایتون اونها رو از مقدارهایی که برنامه شما می‌سازه و در بلوک های بزرگتر چند کیلوبایتی مدیریت می‌کنه که بهشون Pool گفته میشه. این بلوک‌ها بعد از خالی شدن، به سیستم عامل برگردونده نمی‌شه، بلکه خود پایتون اونها رو به عنوان حافظه بلااستفاده برای استفاده‌های بعدی نگه می‌داره.Pool: مجموعه‌ای از بلوک‌های هم‌سایز. معمولاً یه پول چند کیلوبایت (مثلاً ۴ کیلوبایت) حافظه داره که به بلوک‌های یکسان تقسیم شده. همه بلوک‌های یه پول، سایز یکسانی دارن.Arena: بزرگ‌ترین واحد حافظه‌ست. یه Arena شامل چندین Pool می‌شه (مثلاً ۲۵۶ کیلوبایت). پایتون حافظه Arena رو از سیستم‌عامل به صورت یکجا می‌گیره و یکجا آزادش می‌کنه.نحوه مدیریت حافظه توی پایتون خیلی جذابه و من همیشه با جستجو توی اون چیزای جدید یاد گرفتم، به شما هم پیشنهاد می‌کنم اگر دوست داشتید حتما برید و در مورد بخونید (توی پست های انگلیسی چند تا لینک خوب هم در موردشون هست).خلاصه این بحث اینکه وقتی شما مقدار int رو توی یک متغیر ذخیره می‌کنید، پایتون می‌ره pool های با سایز مناسب توی Arena ها (که یک linked list هستن) رو نگاه می‌کنه تا یکی رو پیدا کنه که جای خالی داره و این مقدار رو توش بنویسه (اگر پیدا نکرد، یک pool خالی رو برای این سایز اختصاص میده و به انتهای linked list اضافه میکنه).معماری حافظه Arena۴. مدیریت خودکار حافظه: Garbage Collection چطور کار می‌کنه؟حالا که می‌دونیم آبجکت‌ها چطور ساخته می‌شن و در حافظه جا می‌گیرن، سوال بعدی این هست که پایتون چه زمانی و چطور این آبجکت‌ها را از بین می‌بره؟ این وظیفه بر عهده Garbage Collector هست. پایتون برای این کار از دو مکانیسم اصلی استفاده می‌کنه: Reference Counting و Generational Garbage Collection.شمارش مرجع (Reference Counting): مکانیسم اصلی و سریعساده‌ترین و اصلی‌ترین روش مدیریت حافظه در پایتون، شمارش مرجع هست. همان‌طور که قبلاً اشاره کردم، هر PyObject یک فیلد به نام ob_refcnt داره که تعداد ارجاع‌ها به اون آبجکت رو نشون می‌ده.این شمارنده چطور کار می‌کنه؟- وقتی یک ارجاع جدید به یک آبجکت ایجاد می‌شه (مثلاً با تخصیص متغیر a = ۵ یا اضافه کردن آن به یک لیست)، مقدار این شمارنده یک واحد افزایش پیدا میکنه.- وقتی یک ارجاع از بین میره (مثلاً با دستور del a یا خروج متغیر از محدوده تابع در حال اجرا)، مقدار شمارنده یک واحد کاهش پیدا میکنه.- وقتی شمارنده به صفر می‌رسه، یعنی در این لحظه هیچ‌کس به اون آبجکت اشاره نمی‌کنه. در این حالت، حافظه اختصاص داده شده ( که اگر حافظه کوچیک باشه، حافظه بلوکی اون متغیر و اگر بزرگ باشه حافظه‌ای که مستقیم از سیستم عامل براش اختصاص پیدا کرده) آزاد میشه.این مکانیسم بسیار سریع و کارآمد هست و بیشتر آبجکت‌ها در پایتون به همین سادگی از بین می‌رن. اما یک مشکل بزرگ دارد: ناتوانی در تشخیص ارجاع های پیچیده Reference Cycles.مشکل چرخه رفرنسچرخه رفرنس وقتی اتفاق می‌افته که دو یا چند آبجکت به هم ارجاع بدن. مثلاً یک لیست که به خودش اشاره می‌کند، یا دو آبجکت که هر کدام به دیگری ارجاع دارند. در این حالت، با پاک شدن متغیرها، شمارنده مرجع اونها هیچوقت صفر نمیشه (چون رفرنس همدیگر را نگه داشته‌اند و اگر یکی کامل پاک بشه اون یکی هم شمارنده اش صفر میشه، ولی هر دو منتظر هستن اون یکی اول پاک بشه تا تعداد رفرنس‌هاشون صفر بشه و اینطوری جفتشون قفل می‌شن)، اما در عمل این آبجکت‌ها دیگه قابل دسترسی نیستند و حافظه‌ اونها باید آزاد بشه. اینجاست که پای جمع‌آوری نسلی به میان می‌آید.جمع‌آوری نسلی (Generational Garbage Collection)پایتون برای حل مشکل مرجع های پیچیده، یک الگوریتم کمکی داره که به صورت دوره‌ای اجرا می‌شه و به دنبال این رفرنس‌های چرخشی میگرده. تا اینجا با هم همه چی خوبه، ولی این الگوریتم چیزی نیست که بشه بعد از هر خط کد پایتون تکرارش کرد، چون الگوریتم سنگینی هست. به همین خاطر یک اصل ساده در طراحی اون اعمال شده: بیشتر آبجکت‌ها خیلی زود می‌میرند!بر این اساس، آبجکت‌ها در سه نسل (Generation) دسته‌بندی میشن:- نسل ۰: آبجکت‌های تازه ساخته شده ابتدا به از این نسلحساب میشن.- نسل ۱: آبجکت‌هایی که از یک مرحله جمع‌آوری نسل ۰ زنده بیرون آمده‌ باشند توی اینجا قرار میگیرن.- نسل ۲: آبجکت‌هایی که حداقل یک چرخه پاک‌سازی نسل ۱ رو پشت سر گذاشتن و احتمالش خیلی زیاده که جزو آبجکت‌هایی باشن به این زودی ها قرار نیست پاک بشن.نحوه کار به این صورت هست:1. جمع‌آوری زباله بیشتر روی نسل‌های جوان‌تر (مخصوصاً نسل ۰) انجام می‌شه. این نسل به شکل دیفالت بعد از ۷۰۰ بار نوشتن مقادیر توی حافظه پایتون اجرا می‌شه و آبجکت‌های اضافی رو پاک می‌کنه. هر آبجکتی که جون به در ببره، میره جزو نسل ۱ ای‌ها.2. هر ۱۰ دفعه که پاک کردن حافظه برای نسل ۰ اتفاق بیافته، یک بار هم روی نسل ۱ اعمال میشه و آبجکت‌ها توی این نسل پاک‌سازی می‌شن و هر چی موند میره جزو نسل ۲.3. بعد از هر ۱۰ دفعه پاک کردن آبجکت‌های نسل ۱، یک بار هم این اتفاق برای نسل ۲ می‌افته و آبجکت‌های بدون رفرنس توی این نسل هم پاک می‌شن.آبجکت‌های Immortalیکی از استثناهای garbage collection، آبجکت‌های Immortal هستند. بعضی از آبجکت‌های خاص در پایتون، مثل None، True، False، و برخی آبجکت‌های داخلی، هرگز از بین نمی‌رن. این آبجکت‌ها موقع اجرای برنامه ساخته میشن و هیچوقت هم پاک نمی‌شن.در نسخه‌های جدیدتر پایتون (از ۳.۱۲ به بعد)، یه تغییر کوچیک هم هست: این آبجکت‌ها به‌عنوان Immortal فلگ زده شده هستند. مکانیسم شمارش مرجع برای اونها کار نمی‌کنه و هر چقدر به اون‌ها ارجاع داده بشه یا ارجاع ها از بین بره، شمارنده اون‌ها تغییر نمی‌کنه.۵. پشته (Stack) و فریم (Frame) در پایتونتا اینجا هر چی گفتیم، یه جورایی در مورد حافظه ای بود که زبان های دیگه بهش می‌گن هیپ (Heap)، جایی که آبجکت‌ها و داده‌ها زندگی می‌کنن. حالا نوبت به پشته (Stack) می‌رسه. پشته جاییه که اطلاعات مربوط به اجرای توابع (فراخوانی‌ها) ذخیره می‌شه.وقتی تابعی رو صدا می‌زنیم، پایتون یه شئ مخصوص به اسم فریم (Frame) می‌سازه. این فریم رو می‌تونیم مثل یه حافظه مخصوص برای اجرای تابع در نظر بگیریم که توش اینا هست:- متغیرهای محلی (Local Variables) تابع.- شئ Code Object که دستورالعمل‌های خود تابع رو داره (که از روی کدهای اون تابع ساخته شدن).- اشاره‌گر f_back به فریم تابع قبلی که این تابع رو صدا زده.- اشاره‌گر به شئ globalها و built-inها.- مقدار بازگشتی.نکته جالب اینه که فریم‌ها توی پایتون واقعا یک آبجکت هستن و توی همون حافظه ای ذخیره میشن که آبجکت‌های دیگه ذخیره می‌شن و شما می‌تونید توی کد خودتون به اونها مثل یک متغیر عادی دسترسی داشته باشید!این فریم‌ها روی یه پشته (Call Stack) قرار می‌گیرن. هر بار تابعی صدا زده می‌شه، فریم جدیدش می‌ره بالای پشته، و وقتی تابع تموم شد، فریمش از پشته حذف می‌شه. فقط یه فرق کوچیک بین پایتون و زبانهایی مثل C هست: توی پایتون Call Stack یک آرایه LIFO نیست، بلکه یک Linked List هست که هر فریم با رفرنس f_back به فریم قبلی توی استک وصل می‌شه. این همون چیزی هست که به پایتون اجازه می‌ده ساختار انعطاف پذیری مثل generator و coroutine رو داشته باشه.۶. از فریم تا Generatorاینجاست که به قدرت مفهوم Generator می‌رسیم. Generator ها توابعی هستن که به جای return از yield استفاده می‌کنن. تفاوتشون چیه؟وقتی یه تابع معمولی با return کارش تموم می‌شه، کل فریمش از روی پشته حذف می‌شه و اطلاعاتش (مثل مقدار متغیرهای محلی) برای همیشه از بین می‌ره. اما generator وقتی به yield می‌رسه، یه مقدار برمی‌گردونه و بعد... متوقف می‌شه. اما نه به قیمت از دست رفتن اطلاعات!Generator چیزی هست که یه اشاره‌گر به فریم خودش رو نگه می‌داره. وقتی جنریتور متوقف می‌شه، فریمش از پشته اصلی حذف می‌شه (چون دیگه اون تابع در حال اجرا نیست)، اما خود فریم به عنوان یه شئ توی حافظه زنده می‌مونه. تمام متغیرهای محلی و وضعیت اجرا توی این فریم ذخیره شدن.وقتی دوباره با متدnextسراغ جنریتور می‌ریم، پایتون این فریم رو از توی حافظه برمی‌داره و دوباره می‌چسبونه به پشته تا اجراش از همون جایی که متوقف شده بود ادامه پیدا کنه.این قابلیت که یه تابع بتونه وضعیت خودش رو بین فراخوانی‌ها حفظ کنه به خاطر جداسازی مفهوم فریم از پشته‌ هست.با اینکه پایتون در کل یک زبون Stacked به حساب میاد (که یعنی توابع‌اش فقط اجازه اجرای توابع نرمال با return رو بدن)، ولی این مفهوم اجازه می‌ده بتونه گاهی رفتار یک زبان Stackless رو هم داشته باشه(همین توابعی که yield می‌کنن). این ساختار به ما اجازه می‌ده کارهای زیادی باهاش بکنیم، مثلا:- پردازش جریان داده (Streams) رو خیلی راحت‌تر و مدیریت حافظه رو به کمک Generator ها خیلی بهینه‌تر می‌کنه.- توابع هم‌روال (Coroutines) داشته باشیم که می‌تونن چندبار ورودی و خروجی داشته باشن و با هم همکاری کنن.- صرفه‌جویی در حافظه از این طریق به جای ساختن لیست‌های عظیم، مقادیر رو به کمک Generator ها یکی یکی تولید کنیم.جمع‌بندیمدیریت حافظه در پایتون حاصل یک لایه‌بندی هوشمندانه‌ است. در پایین‌ترین سطح، آبجکت‌ها با ساختار PyObject تعریف می‌شن. برای تخصیص حافظه، pymalloc با استفاده از ساختار Arena، Pool و Block، کار malloc رو برای آبجکت‌های کوچیک بهینه می‌کنه. برای اجرای کد، فریم‌ها روی پشته قرار می‌گیرن. و در نهایت، Genrator ها با نگه داشتن فریم‌ها در Heap، قابلیت نگهداری وضعیت و اجرای ناهمزمان رو ممکن می‌کنن. این طراحی زیبا و لایه‌لایه، پایتون رو همزمان هم ساده و هم قدرتمند کرده.</description>
                <category>جعفر خاکپور</category>
                <author>جعفر خاکپور</author>
                <pubDate>Thu, 26 Feb 2026 19:33:17 +0330</pubDate>
            </item>
                    <item>
                <title>ابزارهای Job Scheduling: انواع معماری مدیریت و توزیع تسک</title>
                <link>https://virgool.io/@jfr.khakpour/job-scheduling-a8yzgrpkzc5e</link>
                <description>یکی از ابزارهایی که همیشه در پروژه های متوسط و بزرگ نقش ایفا میکنه، ابزار مدیریت task های (یا Job های) سیستم هست که کمک می‌کنه حجم زیادی از کارهای پیچیده و طولانی رو بشه به صورت غیرهمزمان و مطمئن توی یک سرویس دیگه اجرا کرد. این سرویس ها که معمولا به شکل یک صف (Queue) از تسک ها پیاده میشن، میتونن تسکی رو که بهشون داده شده رو با توجه به اولویت‌بندی‌هایی که دارن تحویل ورکرهای مناسب بدن که تسک رو ببره و اجرا کنه.با اینکه این معماری ها خیلی کارآمد هستن و بخش بزرگی از پیچیدگی فرآیند توزیع تسک رو از سر راه ما برمی‌دارن، گاهی با افزایش پیچیدگی پروژه دیگه نمی تونن همه انواع تسک‌های بک‌گراند رو برای ما مدیریت کنن. خیلی از ما تو پروژه‌هامون نیاز داشتیم و به سراغ بعضی از این معماری‌های جایگزین رفتیم (که در ادامه بهشون اشاره خواهم کرد).این پست سعی داره بگه چطوری میشه یک چارچوب کلی برای دسته بندی همه معماری‌های اجرای تسک ارائه کرد که تا جای ممکن بتونه همه ابزارهای موجود رو دسته بندی بده. البته این به معنی کامل بودن دسته بندی معرفی شده در اینجا نیست. به نظر من یک دسته بندی خیلی کامل برای این ابزارها وجود نداره و این ابزارها و نحوه رفتارشون همیشه بنا به نیازهای موجود در زمان طراحی اون نرم‌افزار، ابزارها، امکانات و محدودیت‌های موجود کاملا متفاوت هست. ولی آشنایی با این دسته بندی میتونه بهمون کمک کنه که چطور باید به این دسته از مسائل نگاه کنیم.بیاید بحث رو با آشناترین معماری شروع کنیم:معماری آشنا: صف تسک (Task Queue)ساده‌ترین و پراستفاده‌ترین روش برای توزیع کار، صف‌ها هستند. ابزارهایی مثل BullMQ, RQ, Celery و ... جزو این دسته هستند. این سیستم‌ها حداقل دو تا بازیگر اصلی دارن:تولید کننده (Producer) که اجرا شدن تسک رو درخواست میکنه (می‌نویسه توی صف)مصرف کننده ها (Consumers) که دارن به صف گوش میدن، تسک های جدیدی که توی صف گذاشته می‌شه رو نفر اولی که می‌تونه برمیداره، پردازش می‌کنه و تکمیل شدن تسک رو تأیید (ACK) می‌کنه. بعد از تایید، تسک برای همیشه از صف حذف میشه.داده مرتبط با صف و مکانیزم‌‌های اون میتونن توی ساختارهای مختلفی ذخیره بشن، از Redis و RabbitMQ گرفته تا Kafka و PostgresQL و باقی ابزارهای ذخیره‌سازی.یک نکته همیشه در معماری‌های صف مطرحه: همیشه احتمال fail شدن تسک و در نتیجه نیاز به اجرای دوباره اون هست. اکثر فریم‌ورک‌ها، مدیریت صف رو به شکل At Least Once اجرا می‌کنن (تعدادی هم At Most Once هستن). در روش At Least Once تضمین میشه که تسک شما (حداقل) یکبار تا آخر (یا شرط توقف) اجرا بشه، ولی برای مطمئن بودن از نتیجه ممکنه فریم‌ورک تصمیم بگیره کد شما رو دو بار اجرا کنه. در مدیریت شکست به روش At Least Once یک مفهوم خیلی مهم در تسک‌ها مطرح می‌شه که در این مطلب چندین بار تکرار خواهد شد: کد اجرا شده در یک تسک باید Idempotent باشه، یعنی خروجی یک بار اجرای تسک و چندین بار تکرار شدن همون تسک باید یکسان باشه.یک مفهوم دیگه در صف تسک‌ها که ما اینجا باهاش کار داریم، نحوه توزیع تسک هست. وقتی Producer یک تسک رو به صف می‌سپره تا اجرا بشه، لزوما نمی‌دونه که تسک چه زمانی قراره اجرا بشه (ممکنه مدتی در صف بمونه)، و نمیدونه این تسک لزوما قراره از کدوم ورکر (یا همون Consumer) سر در بیاره و اجرا بشه ( این تسک ممکنه نصیب هر Consumer ای بشه که داره تسک ها رو از صف برمی‌داره و انجام میده). Consumer ها هم طبق اصول این معماری، بی‌حافظه هستند و حین اجرای یک تسک، نمی‌دونن تسک‌های قبلی که اجرا کردن چه تسکی و چه زمانی بوده.ابزارهایی که پوشش داده نمی‌شن: توزیع تسک یا توزیع داده؟قبل از ادامه، یک توضیح کوتاه لازمه. ما ابزارهایی رو اینجا بررسی می‌کنیم که کارشون توزیع تسک‌ها بین ماشین‌ها هست. به عبارتی، وظیفه شون هماهنگی کارها و جریان رویدادها بین سرویس‌هاست. در واقع وظیفه اینها اینه که به این سؤال جواب بدن: چطور یک تسک رو به جای درستی برسونیم و مطمئن باشیم تسک به درستی اجرا میشه.یک دسته دیگه از چارچوب های توزیع شده هستن که کارشون بر پایه توزیع داده هست. یعنی یک تسک خیلی بزرگ به سیستم سپرده میشه که باید چند ماشین در کنار هم روی اون کار کنند. ابزارهای زیادی از جمله Spark و Flink و Iceberg توی این دسته قرار میگرن. هدف این ابزارها اینه که به این سوال جواب بدن: یک تسک محاسباتی بزرگ که توی یک ماشین جا نمیشه رو چطوری به صورت سریع و بهینه روی چندین ماشین انجام بدیم.توی این پست من فقط دسته اول رو برررسی می‌کنم و سعی میکنم وارد دسته دوم نشم، چون این روزها تنوع دنیای دسته دوم خیلی بیشتر از دسته اوله. با این خلاصه، بیایید معماری‌هایی را که در سناریوهای مختلف می‌تونن استفاده بشن رو بررسی کنیم.نزدیک ترین آلترناتیو: Pub/Sub هاسیستم‌های Publish/Subscribe یا پاب‌ساب‌ها یک مدل موفق توزیع message یا event هستند که در اون پابلیشر بدون اینکه بدونه مخاطب های یک مسیج چه کسانی باید باشند، ایونت خودش رو منتشر میکنه و این وظیفه سابسکرایبرها هست که به ابزار پاب‌ساب بگن چه پیام‌هایی رو می‌خوان دریافت کنن و این سیستم اطلاعات رو به درستی به دست سابسکرایبرها می‌رسونه.یک تفاوت عمده این دسته با صف ها اینه که توی صف، هر تسک فقط به یک ورکر (یا consumer) داده میشه، ولی در پاب‌ساب، این اطلاعات به همه سابسکرایبرهایی که دارن به اون نوع پیام‌ها گوش میدن ارسال میشه (اصطلاحا Fan-out میشه).مزیت بزرگ این ابزارها چیه؟ فرض کنید یک event خیلی ساده در یک سیستم بزرگ اتفاق می‌افته: یوزر عکس پروفایل جدیدی برای خودش آپلود می‌کنه. عکس ذخیره می‌شه و:سرویس بهینه‌سازی حجم عکس توی بک گراند تصویر رو بهینه می‌کنهسرویس Audit لاگ مربوط به این ایونت رو ثبت میکنهسرویس CDN خودش رو sync میکنهسرویس جستجو اطلاعات خودش رو آپدیت میکنهسرویس اطلاع رسانی ایمیلی برای یوزر می‌فرسته که به وزر می‌گه عکس پروفایل شما با موفقیت آپدیت شدهبه نظرتون سرویسی که پروفایل کاربر رو مدیریت می‌کنه باید عوض شدن تصویر پروفایل رو به همه این سرویس‌ها اطلاع بده و برای هر کدوم یک تسک مجزا بسازه؟ اصلا لزومی داره که این سرویس از وجود این همه بخش مختلف در سیستم آگاه باشه؟ پس شاید راحت‌تر باشه هر کسی به این ایونت‌ها نیاز داره، خودش اعلام کنه. سرویس پروفایل کاربر به سک سیستم مرکزی اطلاع میده آپدیت اتفاق افتاده و این سیستم مرکزی به هر بخشی که برای این نوع ایونت رجیستر کرده، آپدیت رو بفرسته.برای این جنس تسک ها ابزارهای مختلفی استفاده می‌شه که در مرکز اونها ابزارهای مثل RabbitMQ، Apache Kafka و Apache Pulsar پر طرفدارترین گزینه‌ها هستند.یک گام جلوتر: پلتفرم‌‌های Event Streamingاین پلتفرم‌ها معمولا یک تفاوت عمده با پاب‌ساب دارن: اونها اجازه برگشتن به عقب در تاریخچه اتفاقات رو هم به شما می‌دن. در پاب‌ساب، سابسکرایبر فقط ایوینت‌هایی که تو اون زمان تولید می‌شن رو می‌بینه، ولی این پلتفرم‌ها یه کم کنترل بیشتری روی جریان داده ایونت‌ها به شما می‌دن: شما می‌تونید رو با تغییر مقدار offset به عقب و جلو ببرید. ساختار داده‌ای که اینجا استفاده می‌شه شبیه به یک لیست هست که فقط اجازه دارید به تهش چیزی اضافه کنید، نه می‌تونید چیزی رو از وسطش حذف کنید و نه می‌تونید چیزی که اضافه شده رو تغییر بدید. یک چیز خیلی شبیه به log file که فقط می‌تونید بهش append کنید.اینجا دیگه ابزاری نیاز دارید که سریع باشه، هزینه ذخیره‌سازی اطلاعات‌اش پایین باشه و بتونه رو چندین ماشین توزیع بشه. مثل کدوم ابزار؟ بله Kafkaی فقید. ابزاری که ما تو این پست خیلی باهاش کار داریم. به قول یک میم اینترنتی:معماری Actor Model: غریب آشنابیاید از یک جنبه دیگه به Task Queue نگاه کنیم: ورکرها اصولا باید stateless باشن، یعنی وقتی ورکر داره روی یک تسک کار میکنه، نباید حافظه‌ای داشته باشه که به کمک این حافظه به بخشی از اطلاعات تسک‌های قبلی که روی اون‌ها کار کرده چی هستن (مگر اینکه state رو توی جایی مثل دیتابیس نگه دارید!)فرض کنید می‌خواید ورکری داشته باشید که چکیده‌ای از اطلاعات تجمیعی تسک های قبلی (مثلا آپدیت نگه داشتن آماری از ایونت‌های دریافت شده) رو توی حافظه خودش نگه داره و شما بتونید با ارسال تسک‌های مشابه به همون ورکر، این اطلاعات رو تکمیل کنید. حالا چطور این کار رو انجام می‌دید؟ هر بار به ازای هر ایونت جدید، اون اطلاعات از دیتابیس بازخوانی می‌کنید و بعد از اتمام دوباره توی دیتابیس می‌نویسید؟بیاید یک چیز دیگه رو تصور کنیم، شما تعدادی مدل هوش مصنوعی دارید. برای هر نوع تسک و مسیجی که بهتون میرسه هم باید یکی از این مدل‌ها رو استفاده کنید. در نتیجه مجبورید این مدل رو هر بار از دیسک بخونید، روی تسک دریافت شده اعمال کنید و نتیجه رو برگردونید. خوندن‌های متوالی حجم زیادی از اطلاعات از دیسک اصلا به صرفه نیست(مدل‌های هوش مصنوعی معمولا حجم زیادی دارن). اون‌ وقت چیکار می‌کنید؟ بهتر نبود بتونید هر بار تا وقتی تسک‌های مشابه وجود دارن، یک ورکر رو به اون نوع تسک‌ها اختصاص بدید؟ ولی Producer و Consumer انتخاب نمیکنن هر تسکی کجا اجرا بشه.یک گزینه خیلی کارآمد توی این جنس تسک‌ها، فریم‌ورک‌هایی هستند که به صورت اکتور-مدل کار می‌کنند. اکتورها یک مدل پایه از پردازش همزمان هستند که هر اکتور یک state یا وضعیت داخلی داره و با بقیه اکتورها از طریق ارسال message ارتباط برقرار میکنه. پلتفرم های زیادی هستند که امکان توزیع تسک با این معماری رو فراهم می‌کنند و معمولا معماری‌های متنوعی هم دارن. از Akka و Orleans و Ray تا ابزارهای دیگه مثل Faust و Dask.البته لازم به ذکره که اکتور-مدل یک مدل بسیار قدیمی از پردازش همزمانه و یکی از بنیان‌های ایده‌های اولیه Object Oriented Programming (تقریبا شبیه به چیزی که Ruby داره) هست. زبان‌هایی مثل Erlang، Elixir و Scala هم به صورت خیلی جدی از قدرت این معماری در پردازش همزمان خودشون استفاده می‌کنن که قدرت بالایی بهشون میده.دنیای بعدی: Workflow ها و تسک‌های بزرگ و پیچیدهبعضی وقتها ما تسک‌هایی داریم که خیلی پیچیده هستند، زمان زیادی برای اجرا نیاز دارند و مدام باید با سرویس‌های دیگه ارتباط برقرار کنن. یک نمونه از این تسک‌ها، پایپ لاین‌های ETL هست که کارشون استخراج (Extract) کردن دیتا از یک یا چند سورس مختلف، انجام یک دسته محاسبات (Transform) روی اونها و بارگزاری (Load) اونها توی یک دیتابیس دیگه هست. معمولا ما باید همه این بخش‌ها رو به صورت یک تسک یکجا پیاده‌سازی کنیم. تسکی که خودش شامل تسک های کوچکتر هست که مستقیما با هم ارتباط دارن. یک راه خوب برای نشون دادن این Data flow به صورت DAG (Direct Acyclic Graph) هست که توی ابزارهایی مثل Apache Airflow استفاده می‌شه.یک DAG ساده که از داکیومنت‌های خود ایرفلو برداشته شدهابزارهایی از این دست که میشه از اسم Workflow Orchestration براشون استفاده کرد، بخش بزرگی از مدیریت تسک‌های مهندسی داده رو بر عهده دارن. همه ابزارهای این دسته لزوما ورک‌فلو رو به صورت گراف DAG نشون نمی‌دن و ابزارهایی مثل Prefect هم پیدا می‌شن که سعی می‌کنن مفهوم یک ورک‌فلو بزرگ‌تر از اتصال بین گره‌های گراف جهت‌دار ببینن، ولی در نهایت یک ورک‌فلو دارن که از یک نقطه شروع میشه و جریان داده رو به سمت مشخص هدایت می‌کنه.نیمه دیگر مدیرهای جریان‌کاریبخش دیگه‌ای از کاربردهای این مدیران جریان کار، اونهایی هستند که شاید روی استریم بزرگی از داده کار نکنند، ولی ورک‌فلو پیچیده‌ای دارن که باید با جاهای مختلف ارتباط برقرار کنه و گاهی باید منتظر وقوع Event های بخش‌های دیگه بمونه تا اجراش بتونه ادامه پیدا کن. اجرای کل ورک‌فلوی یک تسک ممکنه روزها یا ماه‌ها طول بکشه. برای این مواقع هم معمولا ابزارهایی استفاده می‌شه که بتونن تعداد زیادی تسک طولانی مدت رو همزمان اجرا کنند. بیاید این ابزارها رو هم Workflow Engine ها صدا بزنیم.فرض کنید یک ورک‌فلو مدیریت سفارش، این قدم‌ها اتفاق می‌افته:۱- وقتی پرداخت انجام شد، کالای داخل انبار برای سفارش رزرو میشه و ارسال اونها به مرکز توزیع شروع میشه.۲- اگر کالاها به موقع از انبار ارسال شدند و به مرکز توزیع رسیدند، فرآیند بسته بندی اونها انجام میشه.۳- اگر تا یک روز قبل از زمان تحویل کالاها آماده نشدند، تیکت پشتیبانی ساخته می‌شه که پشتیبان با مشتری تماس بگیره و تصمیم مشتری برای ادامه روند تحویل سبد ناقص، تغییر زمان تحویل برای تامین کالا یا کنسل کردن سفارش رو در سیستم ثبت کنه.۴- اگر نظر مشتری ثبت شد و نظر بر این بود که تحویل ادامه پیدا کنه، سبد آپدیت بشه، پول اضافه به کیف پول مشتری برگرده و سبد آپدیت شده آماده تحویل به لجستیک بشه.۵- اگر تصمیم مشتری کنسل کردن سفارش بود، سفارش آپدیت بشه، کالاها به انبار برگشت بخورند و کیف پول هم آپدیت بشه.و ....پیاده کردن این ورک‌فلو در قالب یک تسک یکپارچه توی هیچ کدوم از ابزارهای قبلی ممکن نیست و ما نیاز به یک ابزار جدید داریم که بتونه یک تسک رو برای زمان طولانی در حال انجام نگه داره. حواسش باشه کدوم بخش ها ورک‌فلو قبلا اجرا شدن و کدوم‌ها باید الان اجرا بشن.اینجاست که ابزارهایی مثل Temporal و Cadence وارد می‌شن. این معماری توی ابزاری مثل Temporal ورک‌فلو رو که نمایش دهنده Business Logic های سیستم هست رو به دو قسمت workflow (فلو کلی و سطح بالا) و activity (کارهایی که باید در هر قدم از ورک‌فلو انجام بشن) تقسیم می‌کنه و تمام بخش‌های دیگه رو خود فریم‌ورک بر عهده می‌گیره، چیزهایی مثل:نگهداری state ورک‌فلونگهداری نتیجه هر کدوم از step های ورک‌فلو (activity ها)اجرای مجدد تسک fail شدهبه خاطر سپردن اینکه در هر قدم از ورک‌فلو (همون activity)، نتیجه چی بوده (اکتیویتی دوباره اجرا نمی‌شه)قطع کردن و ادامه دادن هزاران ورک‌فلو که دارن هم‌زمان اجرا میشن (تموم شدن هر کدوم از تسک‌ها (ورک‌فلوها) ممکنه ماه‌ها طول بکشه)با اینکه activity تضمین At Least Once داره، ولی اگر ورک‌فلو رو به درستی طراحی کنید، در سایه مدیریت state، ذخیره تاریخچه ایونت‌ها و از همه مهم‌تر deterministic بودن اجرای ورک‌فلو، می‌تونید تضمین Exactly Once رو در اجرای ورک‌فلو که Business Logic مرتبه بالا رو اجرا می‌کرد داشته باشید.خلاصه مقایسه‌ای و انتخاب ابزاراین‌ دسته بندی چیزی بود که مدتی بود تو ذهن من شکل گرفته بود و دوست داشتم با شما به اشتراک بزارم. نمی‌خواستم این مطلب رو به پست چند قسمتی تبدیل کنم و این باعث شد کمی طولانی‌ بشه. امیدوارم براتون مفید بوده باشه.اگر بخوام این پست رو براتون خلاصه کنم، اینجوری میشه:نیاز شما &gt;&gt;&gt; این کار پس‌زمینه را یک بار و مطمئن انجام بده.بهترین معماری: Task Queue (سادگی و قابلیت اطمینان)نیاز شما &gt;&gt;&gt; : این رویداد رخ داد؛ همه سرویس‌های مربوطه را مطلع کن.بهترین معماری: Pub/Sub (جداسازی کامل تولیدکننده و مصرف‌کننده، قابلیت Fan-Out)نیاز شما &gt;&gt;&gt; یک جریان پیوسته از داده دارم که باید توسط سرویس‌های مختلف خوانده و بازخوانی شود.بهترین معماری: ٍEvent Streaming (قابلیت replay، تحمل خطا، حافظه طولانی با امکان برگشت)نیاز شما &gt;&gt;&gt; یک سرویس با ترافیک همزمان بالا و state های پیچیده می‌خوام.بهترین معماری: Actor Model (حذف همزمانی، مدل‌سازی نزدیک‌تر به دنیای ذهنی OOP)نیاز شما &gt;&gt;&gt; باید یک پایپلاین داده برنامه‌ریزی‌شده و قابل مانیتورینگ اجرا کنمبهترین معماری: Workflow Orchestration (مدیریت وابستگی‌های پیچیده مراحل داده‌ای)نیاز شما &gt;&gt;&gt; باید یک فرآیند کسب‌وکار بلندمدت و مقاوم در برابر خطا را پیاده‌سازی کنمبهترین معماری: Workflow Engine (اجرای پایدار و مدیریت خودکار state)</description>
                <category>جعفر خاکپور</category>
                <author>جعفر خاکپور</author>
                <pubDate>Thu, 04 Dec 2025 19:53:14 +0330</pubDate>
            </item>
                    <item>
                <title>آمار بیزی و فراوانی‌گرا</title>
                <link>https://virgool.io/@jfr.khakpour/bayesian-frequentist-shben99uwiie</link>
                <description>توی این پست در مورد آمار و تفاوت آمار Frequentist و Bayesian خواهم نوشت تا این دو نحوه استدلال و استنتاج در آمار رو تا جایی که خودم می‌فهمم شرح بدم و بگم که چی هستن، چرا مهم هستن و در کدوم مسائل مرتبط با آمار، کدوم روش‌ها میتونن انتخاب بشن.تا حدی مقدمه، ریاضیات و سایر علوم، دنیای تئوری و تجربه‌گراییدر دنیای ریاضیات (و حوزه خیلی مرتبط با اون یعنی آمار و احتمال) نحوه تفسیر ما از مفاهیم خیلی مهمه. دنیای ریاضیات، مثل بقیه علوم پایه، تجربه پذیر نیست. ریاضیات یک ساختمان بزرگ هست که بر پایه تعدادی اصول ساده اولیه ساخته شده و این اصول باید به قدری ساده و دقیق باشند که قابل پذیرش توسط همه بوده و هیچ استدلال منطقی‌ای نتونه یک گزاره یا ادعای غلط رو از این اصول بیرون بکشه. برای درک تفاوت روش ریاضیات با علم و روش علمی، میشه از تفاوتش با فیزیک مثال زد، وقتی میگیم گرانش وجود داره و وقتی یک سیب رو تو آسمون رها کنید، به زمین می‌افته، این یک پدیده تجربه پذیره. در واقع بحث بر سر اینه که هیچ کسی نمی‌تونه ثابت کنه همیشه همه سیب‌ها به زمین می‌افتن، ولی چون این مساله تجربه پذیره و مسیر اثبات غلط بودن اون از طریق طراحی آزمایش و تجربه کار نکردن این قانون باز هست، ما میگیم این یک فرضیه علمی هست.به همین دلیل فیزیک تجربی همیشه جایگاه خیلی مهمی داره و هر چیزی که در عالم فرمول‌های فیزیک ثابت میشه، باید یه جوری مورد آزمایش قرار بگیره و بشه دید این نظریه‌ها رد می‌شن یا با انجام این آزمایش تقویت می‌شن (و همچنان درستی‌شون اثبات نمی‌شه).ولی ریاضیات به این شکل کار نمی‌کنه. توی ریاضیات، ما فرض می‌کنیم چیزی به نام مجموعه‌ها و اعداد ۱ و ۲ و ۳ و .. هست و بوسیله اینا جمع و ضرب و توابع و نقطه و خط و هندسه و ... رو می‌سازیم. کل دنیای ریاضیات بر پایه استدلال‌های منطقی بر پایه همین مفاهیم ساده و فرض‌های اولیه ساخته می‌شه. همون طور که می‌بینید، تو دنیای نظری (از جمله ریاضیات)، مفاهیم علمی پیچیده بر اساس مفاهیم ساده‌تر ساخته میشن، ولی تو دنیای تجربی نظریه‌های مختلف ارايه میشن و سعی میشه نظریه‌های نادرست رد بشه و فقط نظریه های درست‌تر باقی بمونن.دنیای آمارتوی دنیای ریاضی و همینطور دنیای آمار، تعریف‌های اولیه خیلی مهم هستن و دانشمندا همیشه با فرضها و مفاهیم اولیه کلنجار می‌رن. یکی از بنیادی‌ترین مفاهیم در دنیای آمار، مفهوم احتمال هست. وقتی می‌گیم احتمال شیر یا خط اومدن یک سکه ۱/۲ هست، این به چه معناست؟ احتمالا بگید به این معنی هست که اگر یک سکه رو به تعداد خیلی زیاد پرتاب کنیم، تعداد دفعاتی که سکه شیر و خط میاد یک عدد نزدیک به نصف کل پرتاب‌ها هست. این تفسیری هست که معمولا در کتابهای آمار و احتمال هم بهمون یاد می‌دن. آزمایش پرتاب سکه به دو ویژگی تکیه داره:ما می‌تونیم آزمایش پرتاب سکه رو بارها تکرار کنیماتفاق‌هایی که قبلا افتاده (نتیجه آزمایش‌های قبلی)، روی آزمایش بعدی تاثیری نداره (آزمایش‌ها مستقل از هم هستن)ولی این تفسیر همه جا قابل استفاده نیست. در گزاره «احتمال سقوط هواپیمای مدل X که یک موتور آن از کار افتاده و ۳۰۰ کیلومتر با نزدیک‌ترین فرودگاه فاصله دارد» امکان تکرار این آزمایش به تعداد کافی وجود داره؟ برای «احتمال برنده شدن تیم آرژانتین در بازی با فرانسه در جام جهانی» چطور؟ امکان تکرارش هست؟ تکرار بازی بدون تاثیر گرفتن از نتایج تکرارهای قبلی چطور؟یعنی به کار بردن کلمه احتمال در این جملات غلط هست؟ نه غلط نیست، فقط به کمک مفهوم تکرار آزمایش‌های مستقل قابل تفسیر نیست. در واقع این تفسیرها از احتمال یک چیز ذهنی هستند و ما نمی‌تونیم اونطوری که از تکرار آزمایش و رسیدن به مقدار احتمال در مثال سکه گفتیم، بیرون از ذهنمون و به شکل آبجکتیو (Objective) و عینی اون‌ها رو هم اثبات کنیم (به ارتباطش با تفاوتی که ریاضیات و فیزیک توضیح دادم دقت کنید).معمولا اینجا می‌ریم سراغ استنتاج بیز و از اون کمک می‌گیریم. اول یک مقدمه‌ کوتاه از بیز بگم. احتمالا قضیه بیز رو بدونید:عکسها از ویکی پدیااین قضیه ساده و اساسی که در قرن ۱۸ توسط توماس بیز بیان شده بود، بعدها باعث اتفاقات بسیار زیادی در دنیای آمار شد. پیر سیمون لاپلاس در قرن ۱۹ از این قضیه کمک گرفت تا یک تفسیر برای مفهوم احتمال رو ارئه بده. لاپلاس گفت که میشه احتمال رو به شکل یک مفهوم از عدم اطمینان و چیزی که نمی‌دونیم درست یا غلط هست در نظر گرفت. در این تفسیر ما سعی می‌کنیم از اطلاعات ناقص و ناکافی که به دست آوردیم استفاده کنیم تا برآورد خودمون از احتمال واقعی رو به کمک اطلاعات جدید بهبود ببخشیم. احتمال p یک عدد بین ۰ و ۱ هست که در ذهن ماست و ما با توجه به اطلاعاتی که به دست میاریم می‌تونیم برآورد بهتری از این احتمال قبلی (prior) که داشتیم رو به عنوان احتمال جدید (posterior) برای خودمون بسازیم:عکس از این مقاله در مورد Likelihood و Bayesاواخر قرن ۱۹ یک تفسیر دیگه از مفهوم احتمال هم بوجود اومد. همونطور که دیدیم، تفسیر بیز کاملا بر پایه ذهنیت‌های ما هست و در واقع چیزی بیرون از ذهن ما نیست و کسی نمی‌تونه لمس‌اش کنه. این روند باعث شد که یک تفسیر دیگه از احتمال هم خیلی مطرح بشه. تفسیری که قابل اندازه گیری عینی بود و با مفهوم آزمایش علمی و تجربه‌پذیری سازگارتر بود: آمار Frequentist یا اونطور  که ویکی پدیا میگه فراوانی‌گرا. این تفسیر همونی هست که اول این پست گفته شد: اگه آزمایش‌های مستقل رو به تعداد کافی که تکرار کنید نسبت‌ اتفاق افتادن یک پدیده به کل دفعات میشه احتمال اتفاق افتادن اون پدیده.همونطور که دیدیم، آمار فراوانی‌گرا با اینکه خیلی به مفهوم تجربه گرایی در علم نزدیکه و این باعث میشه که علوم دیگه بتونن بهتر ازش استفاده کنن.کجاها از کدوم یکی میشه استفاده کرد؟مهم‌ترین تفاوت این دو تفسیر اینجاست که یکی احتمال پدیده‌ها رو یک چیز مرتبط با جهان فیزیکی و بدون تغییر در طول آزمایش می‌دونه و اون یکی احتمال رو یک مفهوم ذهنی و قابل تغییر در ذهن ما. برای مثال میگن که شما یک سکه دارید و از دو آماردان فراوانی‌گرا و بیزی می‌پرسید که احتمال شیر اومدن چقدره؟ و هر دو میگن احتمالش ۰.۵ است. حالا سکه رو پرتاب می‌کنید بدون اینکه این دو نفر ببیننن شیر اومده یا خط از آماردان فراوانی‌گرا می‌پرسید که الان احتمال شیر بودن چقدره؟ و اون خواهد گفت که احتمال در اینجا مفهومی نداره و سکه یا شیر هست یا خط. ولی آماردان بیزی همچنان میگه که احتمالش ۰.۵ هست، چون اولی می‌دونه پدیده اتفاق افتاده و نتیجه‌اش به شکل قطعی مشخص هست (ولی اون هنوز نمی‌دونه)، ولی در دیدگاه دومی جواب سوال همچنان در ذهنش غیر قطعی باقی مونده.تفاوت در کارکردهاروش‌های کار دو آماردان هم با هم فرق داره. فرض کنید می‌خوان بررسی کنن که داروی X باعث التهاب پوستی میشه؟حالا آماردان بیزی چجوری به این سوال جواب می‌ده؟به تصویر فرمول بالا نگاه کنید. توی این تصویر:احتمال P(A) اینه که به چه احتمالی یک فرد این دارو رو مصرف می‌کنه (مثلا ۶٪)احتمال P(B) برای گزارش کردن التهاب پوستی هست (مثلا ۳٪)احتمال P(B|A) احتمال این هست که فرد دارای التهاب پوستی بگه که داروی X رو در این دوره مصرف کرده (مثلا ۱۰٪)پس طبق فرمول بیز، احتمال التهاب پوستی بعد از مصرف دارو میشه P(A|B) که میشه ۲۰٪عکس تزئینی بوده و از اینجا  برداشته شدهولی اساس کار فراوانی‌گرا معمولا به این شکله که سعی میکنن فرضیه‌ای مطرح کنن و ببینن می‌تونن اون رو رد کنن؟ (تو این روش، اثبات یک نظریه بی معنی هست و شما فقط می‌تونید نظریه‌ها رو رد کنید). پس اول از همه یه فرض اولیه (که بهش میگن فرض صفر) می‌سازیم که بهش حمله کنیم:«ارتباطی بین مصرف دارو و التهاب پوستی وجود نداره»به عبارتی، این فرضیه داره میگه که تعداد گزارش‌های التهاب پوستی در افرادی که دارو رو مصرف کردن و افرادی که مصرف نکردن یکی هست. یا با جمله سازی آماری‌تر:«اگر یک توزیع احتمالی وجود داشته باشه که بگه احتمال اینکه n درصد از کل جامعه که التهاب پوستی گزارش می‌کنن رو بگه (توزیع نرمال پایین)، درصد n برای افرادی که  دارو مصرف کردن و التهاب پوستی هم داشتن یک عدد قابل انتظار در این توزیع هست(باز هم به توزیع نرمال نگاه کنید)»حالا با این تفسیر، اگه عددی که به دست آوردیم نسبت به این توزیع، یک عدد خیلی پرت باشه، می‌تونیم بگیم خیلی بعیده فرض صفر درست باشه:خب تا اینجا اوکیه. ولی چطوری این رو فرموله می‌کنن؟ مثلا تو نمودار بالا آماردان فراوانی‌گرا میگه که بدست آوردن نتیجه موجود در صورت درست بودن فرض صفر کمتر از ۱٪ هست و در نتیجه آماردان فراوانی‌گرا می‌تونه با اطمینان ۹۹٪ فرضیه ما رو رد کنه و بگه خیلی بعیده که این نتایج توسط توزیع بالا تولید شده باشن. پس فرض صفر رد می‌شه و رابطه بین مصرف دارو و التهاب پوستی با اطمینان ۹۹٪ تایید می‌شه.تو این مثال تفاوت دو نگاه فلسفی به مساله مشخصه. یکی همیشه تاکید بر تجربه‌پذیر و قابل تکرار بودن آزمایش داره. اون یکی ولی تاکید بر استنتاج بیشترین نتایج از داده جمع‌آوری شده، فارغ از قابل تکرار بودن نتایج داره.به همین خاطر روش فراوانی‌گرایی تو تحقیقات حوزه هایی مثل فیزیک، شیمی، علوم اجتماعی و پزشکی که اغلب روی تکرار کردن آزمایش‌ها حساس هستن خیلی پرکاربرده. در عوض ابزارهای استنتاج بیزی معمولا انعطاف‌پذیرتر هستن، با داده‌های ناقص راحت‌تر کنار میان و به لطف فرض‌های اولیه‌ای که می‌شه بدون انجام مشاهدات کافی به مدلسازی اضافه کرد خیلی وقت‌ها می‌تونن چند قدم جلوتر از آمار فراوانی‌گرا برن. همونطور که میشه دید، استنتاج بیزی خیلی شبیه تر به بخش بزرگی از استنتاج ذهنی ما در فضای عدم اطمینان هست و در نتیجه توی هوش مصنوعی و یادگیری ماشین خیلی پرطرفدار هستن. حساسیت کمتر آمار بیزی روی طراحی آزمایش و نحوه جمع‌آوری داده، باعث می‌شه تو حوزه داده‌کاوی (و هر حوزه که از داده کاوی استفاده می‌کنه) هم خیلی علاقمند به این روش‌ها باشن.آمار فقط فراوانی‌گرا  و بیزی هست؟نه نیست. قبل از فراوانی گرایی و بیزی. تعریف احتمال در طول تاریخ یک مسیری رو پیموده و اینا اولین تفسیرها از احتمال نیستن و این تفسیر هنوز هم داره تغییر میکنه. ما هنوز هم تفاسیر جدیدی از مفهوم احتمال خواهیم ساخت تا بتونیم احتمال رو بهتر توضیح بدیم. یکی از این مفاهیم Propensity Probability (احتمال تمایلی؟ احتمال متمایل بودن؟ ) هست که تو قرن بیستم ساخته شد. ولی هنوز به اندازه تفسیرهای قبلی جا نیافتاده (من خودم هم به اندازه دو تا بالایی ازش درک شهودی ندارم و بهش اشاره نکردم)، ولی انتظار میره که بتونه خیلی از نقاط قوت دو دنیای آمار بیز و فراوانی‌گرا رو یکجا جمع کنه.فقط یه نکته دیگه. من اینجا از اصطلاح آماردان فراوانی‌گرا و بیزی استفاده کردم و این اصطلاح معمولا جاهای دیگه هم استفاده میشه. ولی در واقع اینطوری نیست که یه آماردان لزوما معتقد به یکی از این دو دسته روش باشه و اون یکی رو رد کنه. البته ممکنه یه آماردان تو یکی از این حوزه‌ها تخصص داشته باشه ولی به این معنی نیست که حوزه دیگه رو جزو علم نمی‌دونه و اون رو رد می‌کنه.</description>
                <category>جعفر خاکپور</category>
                <author>جعفر خاکپور</author>
                <pubDate>Wed, 22 Feb 2023 22:42:50 +0330</pubDate>
            </item>
                    <item>
                <title>ابزار man in the middle، ماک، هک و تست نرم افزار</title>
                <link>https://virgool.io/@jfr.khakpour/httpsvirgooliojfrkhakpourmitm-hbnwxfipw62b-hbnwxfipw62b</link>
                <description>بعد از یک مدت طولانی دوباره برگشتم تا پست جدید بزارم و تکونی به اینجا بدم.  اول از همه این رو بگم که هدف من نوشتن وبلاگ فقط در مورد تست نرم افزار نیست، ولی این مطلب جدید هم که در پیش‌نویس‌های وبلاگ مونده بود، مرتبط با تست خودکار نرم‌افزار هست که داره منتشر میشه.احتمالا در مورد حمله مرد میانی یا Man In The Middle (MITM) Attack می‌دونید. اینکه یکی بتونه اطلاعات رد و بدل شده بین دو نقطه مختلف رو بشنوه یا حتی در اونها تغییر ایجاد کنه. فکر می‌کنم نیازی به تاکید بر میزان خطرناک بودن و پیچیدگی حفاظت در مقابل این نوع حملات نیست:عکس از اینجا روشهای زیادی برای جلوگیری از این حمله‌ها وجود دارن. احتمالا مهم‌ترین اونها رمزنگاری SSL/TLS هست که ما برای غیرقابل فهم کردن اطلاعات‌مون در اینترنت و بر روی پروتوکل HTTP استفاده می‌کنیم (همون HTTPS). خود SSL خیلی روش ساده و جالبی هست و این سادگی در روشی که امنیت بخش بزرگی از دنیای دیجیتال رو بر عهده داره نشان از نبوغ به کار رفته در طراحی اون داره. نوشتن در مورد استک HTTPS خیلی طولانیه و فعلا بهتره ازش بگذریم. امیدوارم یه روز فرصت بشه در موردش یک مطلب مناسب تولید کنم. توی این پست به نوعی حمله MITM اشاره میشه که توسط یک پروکسی‌ قابل انجام هست و این نرم افزار مخصوص این کار (ولی نه لزوما برای خرابکاری) نوشته شده  و به این مساله هم جلوتر اشاره میشه که چطوری حتی HTTPS رو دور می‌زنیم و اطلاعاتش رو می‌خونیم.این پست در مورد یه نرم افزار خیلی کاربردیه که من خیلی دوستش دارم. یک ایده هکری کوچیک که به شما توانایی زیادی در کنترل ارتباط سطح شبکه نرم‌افزارها رو میده.بزارید از اینجا شروع کنم: توی تست نرم‌افزار، یک مفهوم مهمی وجود داره به اسم Mocking. نرم افزار شما از کلی اجزای کوچیک (مثل تابع ها و API  ها) تشکیل شده که با هم در ارتباط هستن و در این شرایط کافیه یکی از این توابع پایه درست کار نکنه تا همه اجزایی که کارایی شون به اون وابسته هست با مشکل مواجه بشن و نتایج کل مجموعه تست‌هاتون به یک چیز غیر قابل استفاده تبدیل بشه (چون فقط میگه که هیچ چیزی توی نرم‌افزار کار نمی‌کنه ولی نمیگه مشکل از کجاست). فرض کنید شما دو تا تابع A و B دارید که B با فراخوانی A میتونه وظیفه اش رو انجام بده، یعنی اگر A درست کار نکنه، B هم درست کار نخواهد کرد. خب الان شما یک تست برای تابع B نوشتید و تست درست اجرا نمیشه. این خطا نشون میده B درست کار نمیکنه؟ یا شاید مشکل از A هست؟ماک کردن در این مواقع خیلی به درد میخوره. شما موقع نوشتن تست برای تابع B یک تابع ماک‌ می‌نویسید که رفتار A رو تقلید می‌کنه، ولی این تابع فقط یک تقلیدگر هست و واقعا اون کار رو انجام نمیده. مثلا تابع A قراره به یک جایی تو شبکه وصل بشه و یک تغییراتی روی سرور دوم ایجاد کنه. حالا اگر اون سرور مشکل داشته باشه، یا شبکه ارتباطی به مشکل بخوره، یا هر چیز دیگه، نه تنها تست‌های تابع A، که تست‌های تابع B هم مشکل پیدا می‌کنن.یه راه حل اینه که وقتی می‌خوایم کارکرد تابع B رو چک کنیم، تابع A رو ماک کنیم و هر وقت B به تابع A تقلبی گفت به فلان دیتابیس وصل شو و این کار رو بکن، تابع A تقلبی هم بلافاصله بگه باشه انجامش دادم و تموم شد، این هم خروجی کار (یک خروجی تقلبی نشون بده). اینطوری حتی اگه تابع A واقعی هم درست کار نکنه، تابع B  تست‌ها رو به سلامت می‌گذرونه.ماک کردن در unit test ها خیلی معمول هست و اکثر فریم‌ورک‌هاش ابزارهایی برای ماک کردن توابع و بخش‌های مختلف نرم‌افزار دارن. در تست‌های End-to-End ما معمولا نمی‌خوایم سیستم رو ماک کنیم (به همین خاطر بهش میگن End-to-End) ولی گاهی نیاز میشه که یه چیزهایی رو از سناریو تست‌مون کنار بزاریم (اگه این اصطلاح ها براتون جدیدن می‌تونید به پست‌های اول من نگاهی بندازید، اینها رو اونجا هم توضیح دادم). مثلا شما در نرم افزارتون تابعی دارید که زمان طولانی برای اجراش لازمه، ولی می‌خواید کنترل‌های مربوط به عملکرد اون در رابط گرافیکی رو (فقط رابط گرافیکی) چک کنید که درست کار می‌کنن و فرامین رو به درستی فعال می‌کنن یا نه.یه مثال دیگه: توی تست فرانت‌اند شما نمی‌خواید کل سیستم برای هر کلیک که در رابط گرافیکی انجام می‌دید، بره و همه کارهای مرتبط با اون کلیک رو روی سرور انجام بده. اگر از ابزارهایی مثل Cypress استفاده می‌کنید که برای تست فرانت‌اند به کار میرن و می‌تونن درخواستهای HTTP رو توی مرورگر ماک کنن، این مشکل براتون قابل حل هست، ولی اگر از چیزهایی مثل Selenium استفاده می‌کنید، راه حل های موجود برای این کار یه کم پیچیده‌تر خواهند بود.یک راه حل برای این کار استفاده از ابزار‌های حمله مرد میانی هست. من از mitm-proxy برای این کار استفاده می‌کنم. توی این روش mitmproxy یه تونل برای شما باز می‌کنه که با تنظیم مرورگر برای استفاده از اون تونل، همه اطلاعات بین سرور و مرورگر از زیر دست این نرم افزار رد میشن. این نرم افزار چند محیط برای کار داره، شما می‌تونید توی محیط گرافیکی به شکل دستی به request ها نگاه کنید و اگر نیاز بود تغییرشون بدید، از طریق خط فرمان نحوه دستکاری رو کنترل کنید و از افزونه‌هایی که نرم‌افزار داره استفاده کنید، یا اینکه یک کد ساده پایتون بنویسید که اتوماتیک اجرا بشه.رابط وب نرم افزار  (mimtweb)کدهای mitm-proxy دو تابع مهم داره: یکی که وقتی request فرستاده شده به پروکسی رسید فعال میشه و چک میکنه ببینه آیا می‌خواد دخالت کنه و جواب تقلبی رو خودش رو برای این درخواست برگردونه؟ (دقت کنید که سرور اصلا از ارسال این درخواست باخبر نمیشه)، و یکی هم وقتی که درخواست به سرور رسیده، سرور response رو تولید کرده و حالا نرم‌افزار می‌تونه جواب در حال برگشتن رو بخونه و قبل از رسیدن به دست کلاینت در اون تغییر ایجاد کنه. به همین سادگی :)from mitmproxy import ctx, httpdef request(flow: http.HTTPFlow) -&gt; None:    if flow.request.pretty_url == &amp;quothttp://example.com/path/to/api&amp;quot:        flow.response = http.Response.make(            200,  # status code            b&amp;quotOK, sure! ψ(｀∇´)ψ&amp;quot,  # content            {&amp;quotContent-Type&amp;quot: &amp;quottext/html&amp;quot},  # headers        )def response(flow: http.HTTPFlow) -&gt; None:
    if flow.request.pretty_url == &amp;quothttp://example.com/path/to/the/api&amp;quot:        response_body = flow.response.get_text() # get server response        flow.response.text =  b&amp;quot? ΔΜØŇǤ ỮŞ&amp;quot # modify response contentحالا شما می‌تونید با کمی خلاقیت و با استفاده از انعطاف پذیری بالایی که این فریم‌ورک در اختیارتون قرار داده، کدی بنویسید که به mitm-proxy بگید که در حین انجام هر سناریو، در مقابل چه دسته از درخواستهایی که ارسال میشن چه رفتاری رو نشون بده. مثلا اگه یه REST API دارید که میگه برو یک کار محاسباتی سنگین رو انجام بده و برگرد، و شما در چندین سناریو تست مختلف قراره به این API درخواست‌های مشابه بفرستید، حالا لزوما نیاز نیست تو همه این دفعات، API واقعی رو فعال کنید و کلی وقت برای هر بار تست صرف کنید.برای اینکه این پست طولانی‌ نشه، چند تا نکته رو هم به طور خلاصه اشاره می‌کنم و می‌رم به بخش بعد:تنها راه فرستادن درخواستها به mitm-proxy فقط تنظیم پروکسی روی نرم افزار نیست، شما میتونید از این فریم‌ورک به عنوان پروکسی transparent هم استفاده کنید (اگر نرم افزارتون می‌تونه به جای فرستادن درخواست‌ها به آدرس سرور پیش‌فرض‌اش، از یک آدرس ثانویه استفاده کنه و درخواست‌ها رو خودش به آدرس پروکسی بفرسته).پروکسی transparent توی یک حالت دیگه هم کاربرد داره: نرم افزار شما قابلیتی که بالا اشاره شد رو ساپورت نمی‌کنه و شما می‌تونید این کار رو تو سطح سیستم عامل انجام بدید و مسیردهی iptable رو تغییر بدید و یک port forward روی سیستم‌تون تعریف کنید (این کار توی ویندوز و یونیکس ممکن هست ولی من تابحال روی ویندوز امتحانش نکردم).این نرم‌افزار mode های دیگه هم داره ولی برای موارد خیلی خاص‌تر استفاده میشن. دو مورد بالا هم یه کم استفاده‌های پیچیده‌ای هستن و شاید خیلی برای شروع مناسب نباشن. توصیه می‌کنم همون روش پروکسی عادی توضیح داده شده رو همیشه امتحان کنید، چون اون روش به کمترین تغییرات در خارج از مجموعه کدهای تست شما نیاز داره و از این لحاظ خیلی نسبت به روش‌های دیگه اولویت داره و اگر با این روش تونستید کار رو  پیش ببرید، بهترین انتخاب برای انجام تست خواهد بود.با نصب این نرم‌افزار، یک دستور دیگه برای شما در دسترس خواهد بود به اسم mitmdump که میتونید برای ذخیره کردن اطلاعات همه درخواست‌های HTTP رد و بدل شده استفاده کنید که امکانات خوبی هم داره (ولی من به شخصه روش‌های قدیمی مثل tcpdump و wireshark رو ترجیح میدم، هر چند که این دستور امکانات جالب زیادی در اختیار قرار میده).یک مورد هم اینکه من چند جا دیدم در مورد کند بودن این نرم‌افزار صحبت شده و وقتی حجم ترافیک دیتایی که ازش میگذره زیاد باشه، راه حل خیلی مناسبی برای استفاده نیست. اما این کند بودن در انجام تست‌ها خودش رو نشون نمیده چون حجم دیتا معمولا کم هست. ولی اگر خواستید از mitm-proxy برای جاهای دیگه استفاده کنید، این نکته رو هم به یاد داشته باشید.حالا می‌رسیم به بخش HTTPS و امنیتفرض میشه که HTTPS میتونه امنیت اطلاعات ما رو برامون برقرار کنه و جلوی این رو که کسی اطلاعات ما رو تو میانه راه بخونه یا حتی عوض‌شون کنه رو میگیره. SSL\TLS از الگوریتم RSA استفاده میکنه که یک الگوریتم رمزنگاری نامتقارن بوده و  یک جفت کلید (کلید عمومی و خصوصی) داره و برای رمزگشایی از اطلاعات کلید عمومی به کلید خصوصی نیاز هست و برعکس.  مثلا وقتی داریم با google.com ارتباط برقرار می‌کنیم، الگوریتم   RSA به ما اجازه میده با داشتن یک کلید عمومی که همه بهش دسترسی دارن با گوگل ارتباطی برقرار کنیم و هیچ کسی به جز گوگل نتونه اطلاعات رمزنگاری شده ما رو بخونه.ولی این روش هم همچنان با حمله مرد میانی آسیب پذیره. مرد میانی‌ای که خودش رو گوگل معرفی می‌کنه، وسط راه میشینه و کلید عمومی خودش رو برای من می‌فرسته و با داشتن کلید خصوصی‌ پیش خودش، می‌تونه اطلاعات من رو رمزگشایی کنه و تغییر بده و بفرسته برای گوگل. راه حلی که برای این مشکل استفاده میشه اینه که ما بتونیم یه جوری  تشخیص بدیم این کلید متعلق به google.com هست و آیا میشه به کلید برای رمزنگاری اعتماد کرد؟ یا کلید خصوصی اش ممکنه دست یکی دیگه به غیر از گوگل باشه؟طبیعتا یه راه حل اینه که یه نشانه‌ای توی کلید باشه که بتونیم از روش بفهمیم کلید متعلق به گوگل هست. ولی این کار رو که نمیشه برای تک‌ تک سایت‌های روی اینترنت انجام داد. پس چیکار کنیم؟ یه دسته کلید از مرجع صلاحیت‌های اصلی قابل اعتماد توسط همه توی اینترنت رو توی نرم‌افزارمون میزاریم و هر کلیدی که نشانه اون مرجع صلاحیت یا (CA)Certificate Authority رو با خودش داشته باشه برای ما قابل اعتماد خواهد بود. این روش هم مختص رابطه مستقیم یک  CA اصلی با اون سایت نیست و این کار میتونه شامل یک زنجیره اعتماد از CA های مختلف بشه که نهایتا CA آخری، کلید گوگل رو تایید میکنه (مثلا اینجا رو نگاه کنید) و فقط کافیه ما به سر اون زنجیره اعتماد داشته باشیم تا به کل زنجیره اعتماد کنیم.تا اینجای کار به چی رسیدیم؟ HTTPS می‌تونه به ما اطمینان بده که اگه به سرور گوگل وصل شدیم و داریم باهاش حرف میزنیم، محتوای صفحات رو کسی نمیتونه وسط راه ببینه یا دستکاری کنه. ولی به شرط اینکه مطمئن باشیم از اول با گوگل در ارتباط بودیم و کلید رمزنگاری رو از اون گرفتیم. برای اطمینان از این قضیه هم CA ها بهمون کمک می‌کنن تا مطمئن باشیم کلید امضا شده توسط اونا متعلق به سایت گوگل هست.خب تا اینجا همه چی امنه و ما نمیتونیم اطلاعات بین سرور و کلاینت رو بخونیم. پس توی این حالت چطوری می‌تونیم حمله مرد میانی رو با پروکسی‌مون روی HTTPS انجام بدیم؟  تقریبا تمام سیستم‌عامل‌ها و مرورگرها این امکان رو دارن که کلید CA جدیدی بهشون اضافه کنید و بگید که من به این CA اعتماد دارم (کنترل در سطح Secure Socket یا SSL معمولا با سیستم عامل هست، ولی بعضی نرم‌افزارها مثل مرورگرها این کا رو خودشون انجام میدن تا از ارتباط امن‌شون مطمئن باشن). این CA جدید کدوم هست؟ تو مثال ما، این CA چیزی نیست به جز mitm-proxy که:نقش مرد میانی رو برای خوندن اطلاعات رو بازی می‌کنهکلید تقلبی خودش رو استفاده می‌کنه تا برنامه‌ها و مرورگر‌ها رو به اشتباه بندازهما اون رو به عنوان CA قابل اعتماد به نرم افزار معرفی کردیم و چون خودش کلید خودش رو امضا می‌کنه، نرم‌‌افزارها هم به کلیدش اعتماد می‌کننبا این روش شما می‌تونید اطلاعات رد و بدل شده در زمان توسعه یا تست نرم افزار رو حتی اگر اون نرم‌افزار از HTTPS استفاده می‌کنه ببینید یا دستکاری کنید. فقط یک نکته باقی می‌مونه، ما که تا اینجا با حملات مرد میانی راه اومدیم، باید متوجه باشیم که چون اغلب نرم‌افزارها این ایده رو دارن که HTTPS امنه و بهش اعتماد می‌کنن، ما می‌تونیم با استفاده از نرم افزار و مانیتور کردن اطلاعات در شبکه و استفاده از CA تقلبی خودمون، کارهای زیادی روی نرم‌افزارها انجام بدیم و اونا هم واقعا فکر کنن با سرور خود نرم افزار در ارتباط هستن! مثلا هک کردن اپ‌های اندروید و دستکاری اطلاعاتی که بین سرور و اپلیکیشن رد و بدل میشه، می‌تونه اپلیکیشن و سرور رو به خطا بندازه، چون سرور مطمئنه اون چیزی که کلاینت میگه اطلاعات واقعیه و کلاینت هم همین ایده رو در مورد سرور داره. یک ایده ساده برای هک بعضی نرم‌افزارها  ;)</description>
                <category>جعفر خاکپور</category>
                <author>جعفر خاکپور</author>
                <pubDate>Tue, 22 Nov 2022 19:19:10 +0330</pubDate>
            </item>
                    <item>
                <title>تجربه‌ای از تست خودکار نرم افزار، قسمت سوم - اطمینان از درستی تست‌ها</title>
                <link>https://virgool.io/@jfr.khakpour/bdd-my-experience-3-yeyfnbpjmxia</link>
                <description>اگر پست‌های قبلی این سری رو خونده باشید می‌دونید که من در این تجربه‌ام می‌خواستم جوابی برای چندتا سوال در مورد نوشتن تست‌های نرم‌افزار پیدا کنم. اگv بخوام در مورد سوالهایی که من در قسمت اول پرسیدم یک جمع‌بندی داشته باشم. به نظرم میشه نگرانی‌های من رو در دو دسته خلاصه کرد:چطور تشخیص بدم یک تست E2E ناقص نیست و همه چی رو پوشش داده؟چطور تشخیص بدم تست E2E غلط نیست؟در قسمت دوم تونستیم جواب نسبی به سوال اول بدیم: زبان Gherkin به همراه test coverage بر پایه لیست فیچرهای مورد انتظار در سیستم باید بتونه نگرانی اینکه چیزی از قلم افتاده باشه و تستی براش نوشته نشده باشه رو تا حد زیادی رفع کنه.حالا می‌مونه دسته دوم نگرانی‌ من و اینکه چطور تشخیص بدیم تست‌مون درست نوشته شده؟ همونطور که دیدیم ما ابزارهای خوبی در تستهای دیگه مثل تست واحد داریم که متاسفانه اینجا به کار ما نمیان. با این حال از پیدا کردن یک جواب برای سوال دوم هم خیلی دور نیستیم.با توجه به هزینه‌بر بودن تستهای E2E (از لحاظ زمان و منابع مورد استفاده) قطعا نمیتونیم سراغ از کار انداختن سیستم به روشهای مختلف و اطمینان از قرمز شدن تست‌ها بریم (منظورم ایده فضایی تست جهش در قیمت قبل هست). حالا که صحبت  از تست‌های سبز و قرمز شد، ما دیدیم که وقتی شروع به کار روی یک باگ یا فیچر می‌کنیم، سیستم هنوز باگ رو داره (و یا فیچر جدید رو نداره)، پس اگر تست رو قبل از رفع باگ بنویسیم، تست قبل از تغییر کد نرم‌افزار قرمز هست، و بعد از تغییر کد سبز میشه؟ ما می‌تونیم با همین نکته راه حلی برای سوال بعدی هم پیدا کنیم. کافیه کار رو از نوشتن تست شروع کنیم و اینطوری یک تست برای تغییرات مورد انتظارمون داریم که  از سیستم مشکل‌دار (قبل از رفع مشکل) برای  معنی‌دار بودنش استفاده می‌کنیم. من این‌ روکجا دیدم؟ آهان، در مورد  BDD گفتیم که خیلی خوبه چون حتی یک آدم غیر فنی هم میتونه با نوشتن تست به زبان Gherkin بهمون دقیقا بگه که انتظاراتش از سیستم چی هست که برآورده نشده و در این صورت، تست‌مون قبل از اینکه شروع به کار کنیم آماده هست!به این روش که ما قبل از نوشتن کدهای برنامه، تست‌هامون رو بنویسیم، Test Driven Development  یا TDD گفته میشه و BDD حالت توسعه یافته‌ای از TDD هست. در واقع تفاوت‌شون اینطوریه که اگه تست ما در سطح پایین‌تر و به شکل تست واحد باشه، اصطلاح کلی‌تر TDD رو براش استفاده می‌کنیم ولی اگه در سطح فیچر و رفتار کلی سیستم باشه، بهش میگیم BDD (تست BDD فقط محدود به Gherkin نیست و میتونه به شکل‌های مختلف طراحی بشه). این نکته که تستهای BDD در مورد رفتار بیرونی سیستم صحبت می‌کنن و کاری به اجزای داخلی سیستم ندارن باعث میشه که  BDD گزینه خوبی برای تعریف نیازها توسط تیمهای دیگه (حتی تیم‌های فنی دیگه) باشه. البته این به معنی جدا بودن BDD و TDD از هم نیست و حتی در مشکل یا فیچری که توسط تست BDD به ما گزارش میشه، معمولا باید اون رو به چند کار کوچکتر بشکنیم و اینجاست که TDD و تست واحد دوباره وارد وارد مساله میشن.چرخه  توسعه در  BDDخب تفاوت TDD و تست رگرسیون چیه؟ اگر یادتون باشه، تست رگرسیون تستی بود که بعد از هر تغییر در سورس برنامه اجرا می‌شد و تغییر رفتار سیستم و تغییر نتیجه تست‌ها رو فوری شناسایی می‌کرد. خب اینا رو که TDD هم می‌تونه انجام بده؟ بله، در واقع اگر بخوایم روی تعاریف سختگیری کنیم، تفاوتهایی بینشون هست، ولی اگر خیلی رو اسم‌ها تاکید نداشته باشیم، میشه گفت اگر تستی رو قبل از شروع کار مینویسید، از TDD پیروی می‌کنید، و اگر اجرای این تست رو بعد از هر تغییر در سورس نرم‌افزار تکرار می‌کنید، ازش به عنوان تست رگرسیون استفاده می‌کنید (مثل تست واحد که میتونه جزو هر دو دسته به حساب بیاد).تست‌ TDD و BDD فقط یک ابزار  در متدلوژی‌های توسعه نرم‌افزار (مثل اسکرام) نیستن و بخش مهمی از این فرآیند و ابزار تضمین کیفیت نرم‌افزارها هستن، چیزی که برای متدهای agile و انتشار مداوم نرم‌افزار خیلی مهمه. تجربه‌ من هم در این مدت این بود که TDD انقدر خوب با متدلوژی‌ اسکرام سازگاره که نه تنها چندین مشکل رو همزمان برامون حل می‌کنه، که برای بعضی مشکل‌ها، تنها راه حل ممکنه!متد TDD در سه مرحله توصیف میشه:قرمز:وقتی فیچر درخواستی یا باگ سیستم بهتون گزارش می‌شه و شما شروع به نوشتن تست می‌کنید. در این مرحله همه تست‌هایی که اضافه می‌کنید باید قرمز باشن (fail بشن). یکی دیگه از مزیت‌های این مرحله (به جز چیزایی که بالاتر گفتم) اینه که شما مطمئنید اون چیزی که می‌خواید روش کار کنید یک scope کاملا مشخص داره و تمام چیزی که قراره بنویسید در تست‌ها مشخص شدن (مثلا وقتی برنامه‌نویس بخشی از چیزی که ازش خواستن رو متوجه نشده یا اشتباه متوجه شده). به این خاطر هم، خیلی‌ تیم‌ها ترجیح می‌دن تست‌ها رو یه برنامه‌نویس بنویسه و تسک رو یکی دیگه انجام بده.سبز:در این مرحله شما دنبال مشکل می‌گردید و با ایجاد هر تغییر، تست‌ها رو اجرا می‌کنید تا ببینید سبز میشن یا نه؟ این قسمت از کار، برای حل مشکل توسط برنامه‌نویس‌ها (به خصوص کسی که تازه‌ وارد تیم شده و با سورس‌های نرم‌افزار خیلی آشنا نیست) ارزش زیادی می‌تونه داشته باشه، برای مثال وقتی برنامه‌نویس حدس میزنه که چجوری مشکل رو میشه برطرف کرد، اون رو انجام می‌ده و با اجرای تست‌ها امتحان می‌کنه که حدسش برای تمام حالات درست بوده یا نه و دیگه نیازی نیست موقع اضافه مردن هر خط به هزارتا چیز مختلف فکر کنه.در نهایت بعد از سبز شدن تست‌های مربوط به تسکی که روش کار می‌کردید، می‌تونید با اجرای همه تست‌ها، مطمئن بشید که با این تغییر جای دیگه‌ای از سیستم ایراد پیدا نکرده (یا این کار رو به تست رگرسیون و سیستم CI بسپرید).ریفکتور:حالا که اون نقص رو در نرم‌فزار برطرف کردید، این نرم‌افزار داره بدون ایراد کار می‌کنه و الان وقتشه که کدهایی که نوشته شدن تمیز بشن (تمیز کردن در کدنویسی، یا بهتر نوشتن منطق پیاده شده، اطمینان از اینکه کد شما convention های نرم‌افزار رو رعایت می‌کنه و .. ). با اجرای مداوم تست‌ها هم می‌تونید مطمئن باشید که تست‌ها هنوز سبز موندن و قسمتی از کدهاتون حین ریفکتور مشکل پیدا نکرده.متد TDD چند تا مزیت دیگه هم داره، از جمله اینکه وقتی شما با این متد کار می‌کنید تعداد باگ کمتری در سیستم شما ممکنه بوجود بیاد (چون خیلی چیزها دارن به شکل مداوم و حین توسعه تست میشن). یک مزیت دیگه اینکه تعداد تست‌‌های شما با تعداد فیچرها و باگ‌فیکس‌های سیستم در حال رشد هست (coverage همیشه رقم بالایی می‌مونه).در آخربعد از این جستجوی روشهای مختلف و مطالعه در مورد تست نرم‌افزار، که برای خودم تجربه جالبی بود، در نهایت به این نتیجه رسیدم که اگر TDD در اسکرام جدی‌تر گرفته می شد، ما از روز اول و بدون اینکه خودمون بدونیم بیشتر راه رو رفته بودیم. و البته با توسعه TDD و استفاده از BDD می‌تونستیم ارزش خیلی بیشتری به تست‌هامون بدیم.امیدوارم این رشته پست‌ها به کارتون اومده باشه. سعی کردم اشاره‌ای در داخل این متن‌ها به خیلی از چیزایی که به نظرم در تست نرم‌افزار برام جال بودن داشته باشم. اگر فرصت بشه، سعی می‌کنم در آینده هم بیشتر در مورد تست نرم‌افزار بنویسم، چون به نظرم موضوع جالب و بسیار پرچالشی هست که در اکوسیستم ایرانی خیلی جای نوشتن و کار کردن داره.</description>
                <category>جعفر خاکپور</category>
                <author>جعفر خاکپور</author>
                <pubDate>Sun, 28 Nov 2021 15:32:41 +0330</pubDate>
            </item>
                    <item>
                <title>تجربه‌ای از تست خودکار نرم افزار، قسمت دوم - پوشش تست‌ها</title>
                <link>https://virgool.io/@jfr.khakpour/bdd-my-experience-2-aemqq0ctejme</link>
                <description>این سری پست‌ها در مورد تجربه یک گشت و گذار دنبال جواب این سوال هست که من چجوری مطمئن دشم تست‌های درستی مینویسم. به من وظیفه‌ای محول شده بود که تعدادی تست E2E برای شرکت بنویسم و این سوالها در ادامه اون تجربه برام بوجود اومدن.همونطور که در قسمت اول این پست گفتم ما تست واحد داشتیم ولی تست‌هایی که بتونن کارکرد سیستمرو  در لایه‌های دیگه ارزیابی کنن وجود نداشت. از جمله تست‌های I&amp;T که می‌تونست کمک بزرگی برای تضمین کیفیت نرم‌افزار باشه ولی ما سراغش نرفته بودیم. تست‌هایی که من قرار بود روشون کار کنم، تستهای E2E بودن و در نتیجه من باید تستهایی با متد blackbox testing می‌نوشتم که نرم‌افزارها رو توی محیط تستی نصب کنم و به همون شکلی که  یک مشتری با این نرم‌افزارها کار میکنه، من هم کار کنم. یعنی نمی‌تونم به داخل سیستم دست بزنم و فقط باید رفتار بیرونی سیستم رو بررسی کنم.قطعا در متدهای whitebox testing مثل تست واحد، انعطاف خیلی بیشتری در تست کارکردها وجود داره. این تست‌ها یک بخش کوچک از سیستم رو تست می‌کنن و معمولا اون بخش رو تا حد ممکن از بقیه سیستم ایزوله میکنن که باعث می‌شه کنترل بیشتری روی جزيیات رفتار سیستم داشته باشن.خود تست‌های واحد همیشه جزو مهم‌ترین تستهای نرم‌افزار در هر پروژه‌ای هستن. این تستها نسبتا راحت‌ نوشته میشن، کد رو در سطحی از جزئیات تست میکنن که اغلب برای باقی تستها ناممکن هست و همینطور خیلی سریع اجرا میشن. همون چیزی که test automation pyramid  میگه:بین تستهایی که من در موردشون گفتم تست واحد لایه پایین هرم، تست I&amp;T لایه دوم  (که پروژه ما در حال حاضر نداره) و E2E لایه بالای هرم هست. منبع عکساگر شما تست واحد می‌نویسید، قطعا ابزارهای بیشتری برای ارزیابی کیفیت تستهای نوشته شده شما وجود داره و راحت‌تر میتونید به سوالاتی مثل سوالات بالا جواب بدید. مثلا یکی از ابزارهای محبوب در این زمینه code coverage هست. ایده پشت این معیار اینه که تستهای شما باید باعث بشن خط به خط کدهای نرم افزار شما یا درصد خیلی بالایی از اونها حین تستها اجرا بشن (مثل کدهای داخل تمام توابع، تمام ifها، تمام switch caseها). میزان پوشش تست‌ها هرچقدر به ۱۰۰٪ نزدیکتر باشه، میشه اطمینان بیشتری داشت که ما داریم همه جای کد رو تست میکنیم و کم شدن درصد پوشش به مرور زمان، نشان‌دهنده این هست که سرعت نوشتن تست‌ها از سرعت توسعه نرم‌افزار کمتره و خیلی چیزهای جدید نوشته شدن و به کد اضافه شدن که هیچ تستی براشون نوشته نشده.ولی یکی از بامزه‌ترین‌ ابزارهایی که من باهاش برخورد کردم، Mutation testing هست که یک قدم هم از code coverage جلوتر رفته. ایده تست جهش اینه که اگر ادعا میشه تستهایی که code coverage صددرصد دارن و همه چی رو تست میکنن، پس اگر وقتی همه سبز هستن، قسمتی از کد نرم‌افزار رو تغییر بدیم، مثل تغییر مقدار یک متغیر داخل کد، یا تغییر یک شرط (if(x &lt; n) به if(x &gt; n)) انتظار میره که حداقل یکی از تستها قرمز بشه و این جهش رو reject کنه. دقت کردید چی شد؟ یعنی همونطور که تستها کیفیت نرم‌افزار رو ارزیابی میکنن،  خود نرم‌افزار هم برای ارزیابی کیفیت تست‌ها به کار میره!البته باید گفت که نه این معیار و نه حتی معیار code coverage همیشه قابل استفاده نیستن و معمولا به کار نرم‌افزارهای بزرگ نمیان. ولی به نظرم اومد چون باحال هستن ارزش اشاره کردن رو دارن :) (البته جلوتر باز هم به ایده کلی پشت هر دو اینا بر می‌گردیم.)خب حالا برگردیم به تستهای E2E. بعد از خوندن در مورد تستهای مختلف، من به چند تا نتیجه رسیدم. اول اینکه ما معیاری به خوش‌دستی چیزهایی مثل دو تا معیار بالا برای تستهای E2E نداریم. ولی شاید بشه با تغییر صورت مساله راه‌حل‌هایی براش طراحی کرد.اول اینکه شاید نشه معیاری مثل code coverage رو برای نرم‌افزارهای شرکت ما به کار برد، ولی شاید بشه معیار coverage رو تغییر داد. تنها چیزی که باید در تستها cover بشه فقط اجرای خط‌های سورس‌کد نیست.اول بزارید این سوال رو بپرسیم که اصلا چرا test coverage در هر پروژه‌ای مهم هست؟ بزارید اول به این اشاره کنم که  چیزی وجود داره به نام regression testing. این تست‌ها تضمین میکنن که تغییر جدید در سورس‌کد نرم‌افزار هیچ تغییری در رفتار نرم‌افزار، در هیچ سطحی از کارکردها (از رفتار کلی سیستم تا رفتار یک متد یا api) ایجاد نمیکنه (به زبان خودمونی، در ازای هر رفع باگ توسط یک برنامه‌نویس خلاق و تجربه‌گرا، ۳ تا باگ جدید در سیستم تولید نمیشن!). تستهای رگرسیون میتونن شامل همه تستهایی باشن که تا اینجا در موردشون صحبت کردیم، فقط تنها شرطشون اینه که باید بعد از هر تغییر در کدهای نرم‌افزار اجرا بشن تا فوری تشخیص بدیم کدوم تغییر در سورس نرم‌افزار باعث تغییر در رفتار سیستم شده (همون کاری که در CI/CD هم انجام میشه). در نتیجه میتونیم هر خطایی در بیلد شدن نرم‌افزار رو فوری تشخیص بدیم و بدونیم برای اون خطا باید سراغ کدون تغییرات در سورس برنامه بریم. فایده بالا نگه داشتن coverage تو اینجا چیه؟ هر چقدر پوشش بالاتر باشه، باعث میشه مطمئن باشیم که تعداد این مدل خطاها که از دستمون در میرن و نمیتونیم در لحظه اول شناسایی‌شون کنیم کمتر هستن.خب، من گفتم coverage میتونه برای چیزهای دیگه‌ای هم مطرح بشه. مثل چی؟ مثل درصد پوشش از لیست رفتارهای مورد انتظار در سیستم. برای مثال از یک اپلیکیشن فروشگاهی انتظار میره فیچر نمایش لیست خرید رو داشته باشه. فیچری مثل نشون دادن لیست کالای انتخاب شده توسط کاربر در سبد خرید به همراه قیمت کلی اونها. برای نمونه این سناریو رو در نظر بگیرید:سه کالای مداد به قیمت ۱ دلار و خودکار به قیمت ۲ دلار و دفتر به قیمت ۳ دلار در فروشگاه برای خرید موجود هستن. اکانت کاربر تست هیچ چیزی در سبد خرید خودش نداره. کاربر یک مداد و  دو دفتر  به لیست خرید اضافه میکنه. وقتی کاربر به صفحه سبد خرید میره، یک مداد به قیمت ۱ دلار و دو دفتر هر کدوم به قیمت ۳ دلار در سبد هستن.یک فیچر نمایش سبد خرید میتونه چندین سناریو برای تست داشته باشه. مثلا هزینه کل سبد، محاسبه و نمایش تخفیفها و امکان حذف از سبد هم چیزیه که یک تست جداگانه باید براشون طراحی بشه.ما می‌تونیم برای سیستم خودمون یک لیست درست کنیم از فیچرهایی که ادعا می‌کنیم سیستم‌مون قراره ساپورت کنه (یا لیستی از فیچرها که مشتری انتظار داره سیستم ما اونها رو ساپورت کنه). هر کدوم از فیچرهای این لیست هم چند سناریو داشته باشن که رفتار مورد انتظار فیچر در حالتهای مختلف رو تشریح می‌کنه. و ما میتونیم معیار coverage رو اینطوری تعریف کنیم که تست‌های ما چند درصد از فیچرهای این لیست و سناریوهای مختلف هر فیچر رو پوشش میدن؟ اگر ما بتونیم درصد بالایی از این لیست رو پوشش بدیم، در واقع تونستیم یک معیار coverage با کارایی توجیه‌پذیر بسازیم.ولی این تمام چیزی که می‌خوام بگم نیست. در دنیای تست نرم‌افزار چیزی وجود داره به نام Behavior Driven Development یا BDD. یکی از مهم‌ترین ویژگی‌های BDD این هست که سناریوهای تست رو به زبانی غیر فنی ولی قابل فهم برای کامپیوتر بیان می‌کنه. این تستها به زبان Gherkin نوشته میشن که مثلا برای فیچر و سناریو بالا به این شکل هست:Feature: Application has cart to store and show order info

Scenario: Add some products and check them in cart
  Given Website has given products
   | prod_name | price |
   | pencil          | 1        |
   | pen              | 2        |
   | notebook    | 3        |
   And test_user has no product in cart
When user adds 1 pencil to cart
   And user adds 2 notebook to cart
   And user opens cart
   Then cart has 1 pen with price 1
   And cart has 2 notebooks with price 2

Scenario: Remove some of selected products from cart
....این زبان با سینتکس ساده‌اش چجوری کار میکنه؟ اول توی کد چند تا عبارت اصلی داریم:عبارت Feature که اول هر فایل میاد و هر فایل تست فقط شامل توضیحات یک فیچر خواهد بود.عبارت Scenario (یا Example )که برای هر سناریوی تست مرتبط با این فیچر به کار میرهعبارت Given پیش‌شرط‌هایی که قبل از شروع تست آماده میشنعبارت When گام‌های سناریو تست که باید دونه دونه انجام بشنعبارت Then شامل چیزهایی که باید توی این سناریو چک بشنو بعد توسعه دهنده باید هر کدوم  از این گام‌ها (جملات محاوره) رو به یک تابع وصل کنه تا فریم‌ورک تست شما بدونه با هر جمله چه کارهایی باید انجام بشه. مثل این کد در پایتون:@when(&#039;user adds {amount} {product_name} to cart&#039;)
def step_add_cart(context, amount, product_name):
    .....تست BDD مزایای زیادی داره. اینکه شما میتونید یک تست رو به شکل غیرفنی بنویسید، باعث میشه آدمهای غیرفنی شرکت هم بتونن در پروسه تست سهیم باشن، و علاوه بر این، در اصافه کردن هر فیچر جدید، این زبان در عین راحتی و قابل فهم بودن برای همه، جزئیات فنی و دقیقی از اونچه توسعه‌دهنده نیاز داره رو هم در اختیارش میزاره(البته کاملا سطح بالا هست و توی جزئيات نمیره). این تستها برای من یک مزیت خیلی مهم دیگه هم داشتن، من مطمئن بودم که هیچ کسی هیچوقت سراغ تست‌های فعلی من نمیره که ببینه من واقعا تو اون تست چی نوشتم، مگر موقعی که سیستم دچار مشکل بشه و همه برن سراغ اینکه چرا تستها مشکل رو تشخیص نمی‌دادن. در حالی که نوشتن تست‌ها به زبان Gherkin در واقع میتونه تستها رو همزمان داکیومنت کنه و اینطوری انتظار میره اون تست مشکل‌دار راحت‌تر توسط یکی از آدمهای فنی یا غیر فنی شناسایی بشن.تا اینجا، بعد از معرفی چند نوع تست، معرفی خیلی مختصری از تستهای BDD داشتم. در بخش سوم و پایانی، بعد از پاسخ به سوالات قسمت اول، جمع بندی درباره چیزهایی که گفتم رو خواهم داشت.</description>
                <category>جعفر خاکپور</category>
                <author>جعفر خاکپور</author>
                <pubDate>Sat, 20 Nov 2021 18:49:11 +0330</pubDate>
            </item>
                    <item>
                <title>تجربه‌ای از تست خودکار نرم افزار و توسعه تست محور</title>
                <link>https://virgool.io/@jfr.khakpour/bdd-my-experience-1-yljis1rxsqt9</link>
                <description>این متن یک تجربه شخصی از نوشتن تست های خودکار برای یک محصول نرم‌افزاری هست  و نتایجی که حین این تجربه به دست آوردم و به نظرم اومده ارزش نوشتن و ثبت کردن رو داشتن (به خصوص برای خودم). در شرکتی که من براش کار میکنم تعدادی محصول نرم افزاری تولید شده که چندتا از این نرم‌افزارها بستری هستن که به عنوان هسته اصلی تمام خدمات شرکت کار میکنن و نرم‌افزارهای دیگه معمولا از طریق همین سیستم‌های اصلی میتونن خدمات ارايه کنن. اخیرا شرکت تصمیم گرفت هسته‌ای‌ترین سیستم (و همچنین بزرگترین از لحاظ تعداد فانکشنالیتی‌ها) که برای بیش از یک دهه مشغول توسعه و نگهداریش بود رو بازنویسی کنه و معماریش رو بهبود بده (البته با حفظ   حداکثر backward compatibility ممکن برای این محصول). تا همینجا میشه تصور کرد که این تصمیم چقدر برای همه ترسناک بود. میشه گفت همه نرم‌افزارهای دیگه به نحوی با این سیستم بزرگ سر و کار دارن و نسخه جدید باید میتونست همه کارکردهای نسخه قبلی رو حفظ کنه یا بهبود بده. خوشبختانه من از روز اول توی این پروژه بازنویسی نبودم (ورود به تیم در زمان استارت پروژه بزرگ خیلی سخت‌تر از وقتی می‌بود که پروژه تا حد خوبی رو روالش افتاده بود و من فقط در ادامه همون روند کمک میکردم). من وقتی به تیم اضافه شدم که سیستم نسخه‌ای داشت که آماده برای نصب دولوپرهای دیگه بود. ولی هنوز تعدادی از بخشهای سیستم کار نمیکردن (بعضی وقتها این بخش‌ها نوشته شده بودن اما درست کار نمی‌کردن، یا رفتارشون تغییر کرده بود و این تغییرات هنوز هیچ جایی داکیومنت نشده بودن ).طبق روال پروژه ما همزمان با توسعه، تعداد زیادی تست واحد (unit test) برای بخشهای مختلف می‌نوشتیم که تا حد ممکن جلوی خطای برنامه‌نویس‌ها رو بگیرن ولی مثلا چیزی که در مورد تغییر رفتار گفتم که باعث عدم تطابق کامپوننت‌ها می‌شد مشکلی بود که با تست واحد قابل شناسایی نبود. شناسایی این مشکل‌ها معمولا توسط تستهای دیگه انجام میشه که بعد از تست واحد اجرا میشن. به این تستها معمولا I&amp;T یا integration testing گفته میشه، ولی ما این تستها رو تو شرکت استفاده نمی‌کردیم. من دلیل‌اش رو نمی‌دونم، ولی به هر حال نوشتن این تستها برای سیستمی که ورودی و خروجی خیلی از کامپوننت‌ها در حال تغییره همزمان خوب و همزمان بد هست. این تست‌ها میتونن جلوی مشکل عدم تطابق در کامپوننت‌ها رو بگیرن، ولی این برای زمانیه که سیستم تا یه حدی پایدار شده باشه. تو مرحله‌ای که ما بودیم، رفتار هر بخشی از سیستم ممکن بود هر روز عوض بشه. در این صورت به ازای هر تغییر، کسی که کد رو تغییر داده بود (یا یک فرد دیگه) باید زمانی بیشتر از نوشتن اون تغییرات رو مشغول عوض کردن تستها می‌بود تا بتونه سیستم   (Continuous Integration) CI رو دوباره راه بندازه ( پلن CI معمولا بعد از هر تغییر در کدهای نرم افزار اون رو بیلد میکنه و تستهای نوشته شده رو روی این سیستم اجرا  میکنه و اگه تستی خطا بده، اون فرآیند بیلد fail میشه).یک نکته دیگه این بود که حداقل در جلساتی که من توش بودم، چندین بار در مورد اینکه تست‌های واحد چجوری و در چه فرآیندی نوشته بشن بحث می‌شد (مثلا اینکه برای کدوم دسته تیکت‌ها باید چه تست‌هایی حتما نوشته بشن؟ تست‌ها باید قبل از یک رفع باگ نوشته بشن یا بعدش؟ تستها رو کسی که باگ رو حل کرده باید بنویسه یا یک برنامه نویس دیگه؟ بهتر نیست تستها رو به یک فرد مشخص بسپریم؟ لازمه یکی دیگه این تستها رو بعد از تموم شدن کار بخونه و چک کنه؟ و...). قطعا طراحی فرآیند برای تستهای I&amp;T وقت و انرژی خیلی بیشتری نسبت به تست واحد لازم داره و از این لحاظ هم مدیریت فرآیندهای مربوط به اونها میتونست سخت باشه و از بیشتر از زمانی که برای تستهای واحد صرف شده رو لازم داشته باشه.برگردیم به داستان، روند کار روی پروژه به شکلی که اشاره کردم ادامه داشت و طبیعتا همه اعضای تیم استرس این رو داشتن که اون بخشی از نرم‌افزار که روش کار میکنن در روز نهایی، رفتار مورد انتظار رو خواهد داشت یا نه؟ تو این شرایط بودیم که به من تسک جدیدی دادن و گفتن که ما یه دسته تست E2E (End-to-End) قدیمی نوشته بودیم که سالی یه بار رو نسخه قدیمی اجراشون می‌کردیم، حالا تو اونا رو انتقال بده به سیستم جدید و یک لیست هم دادن که  برای این لیست از فیچرهای جدید و قدیمی که تست ندارن هم تست‌های جدید اضافه کن. این تست‌ها قراره به پروسه CI(Continuous Integration) نرم‌افزار جدید اضافه بشن.عکس از  اینجامن هم با هر ضرب و زوری بود این تسک رو براشون انجام دادم، ولی چون سیستم رو کامل نمیشناختم، خیلی چیزها رو حین نوشتن تستها یاد میگرفتم! (واسه اینکه عمق فاجعه رو نشون بدم این رو هم بگم که با بعضی چیزها هم با آزمون و خطا آشنا می‌شدم!! چون هنوز هیچ داکیومنتی نداشتن). این سیستم طوری هست که تعداد آدماهایی که کل سیستم رو میشناختن، یک یا دو نفر بیشتر نبودن و طبیعتا وقتشون برای شرکت خیی ارزشمندتر از این بود که من هر نیم ساعت برم و ازشون در مورد فیچری که دارم تست میکنم توضیح بخوام.بعد از اینکه تست‌ها رو نوشتم خیلی نگران این بودم که شاید من تستها رو به درستی ننوشته باشم. چند تا نگرانی بودند که رها نمیکردن:اگه من تست رو اشتباه بازنویسی کرده باشم چی؟اگه فراموش کردم بخشی از تست رو بازنویسی کنم چی؟همه جوانب فیچری که باید تست بشن رو در نظر گرفتم؟ یا فقط یه حالت روتین رو تست کردم که شاید ارزش کافی برای تست کردن رو نداشت.مطمئنم که رفتار مورد انتظار یک فیچر از سیستم رو اونطوری که انتظار میره تست میکنم، نه اونطوری که خودم با آزمون و خطا بهش رسیدم؟ (این یعنی من به اشتباه رفتار غلط رو به عنوان رفتار صحیح در نظر گرفتم و تستی نوشتم که وقتی رنگش سبزه، یعنی سیستم داره غلط کار میکنه!) و این سوال که اون تستی که نوشتم اصلا درست ارزیابی میکنه؟ مثلا نکنه من با یک API تعامل داشتم و با اینکه اون نتیجه اشتباه یا حتی پیغام خطا برمگیردونه ولی من بخش اشتباهی از پاسخ برگردونده شده رو چک میکنم؟این سوالات و استرس کلی پروژه ذهنم رو مشغول کرده بود که اگه کد یکی خطا داشته باشه و تست من که انتظار میرفته اون خطا رو پیدا کنه همچنان سبز بمونه خیلی برای من بد میشه.به همین خاطر رفتم دنبال مطالعه در مورد تست‌های خودکار تا ببینم دیگران با این مشکل چیکار میکنن.برای اینکه متن طولانی نشه متن رو به به چند قسمت شکستم که در ادامه همین پست منتشر خواهند شد. قسمت بعدی جستجوی من به دنبال راه حل‌های قابل استفاده برای سوالات بالا خواهد بود.</description>
                <category>جعفر خاکپور</category>
                <author>جعفر خاکپور</author>
                <pubDate>Sat, 20 Nov 2021 18:41:54 +0330</pubDate>
            </item>
            </channel>
</rss>