راهنمای یادگیری برنامه‌نویسی

چطور کد بخوانیم و واقعاً بفهمیم چه اتفاقی می‌افتد؟

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

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

فهرست مطالب

  1. چرا خواندن کد با خواندن کتاب فرق دارد؟
  2. چرا فهمیدن پروژه‌های بزرگ سخت است؟
  3. اشتباه‌های رایج هنگام خواندن کد
  4. روش چندمرحله‌ای برای خواندن کد
  5. ردیابی جریان اجرای برنامه
  6. پیداکردن محل استفاده از توابع
  7. دنبال‌کردن مسیر داده‌ها
  8. چطور تغییرات کد را بررسی کنیم؟
  9. استفاده از روش جعبه‌سیاه
  10. آیا هوش مصنوعی جای خواندن کد را می‌گیرد؟
  11. روال عملی بررسی یک پروژه
  12. تمرین تعاملی و آزمون

۱. چرا خواندن کد با خواندن کتاب فرق دارد؟

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

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

مثال اول: کدی که باید اجرا شود

function calculatePrice(price, tax) {
    return price + tax;
}

const finalPrice = calculatePrice(100, 20);

console.log(finalPrice);

اگر این کد را مانند یک متن معمولی بخوانیم، ممکن است فقط معنی هر خط را بفهمیم. اما برای درک رفتار آن باید مسیر اجرا را دنبال کنیم:

تعریف تابع
←
فراخوانی با ۱۰۰ و ۲۰
←
محاسبه ۱۲۰
←
نمایش خروجی

نکته مهم این است که تعریف تابع به‌تنهایی باعث اجرای بدنه آن نمی‌شود. بدنه تابع زمانی اجرا می‌شود که تابع فراخوانی شود.

مثال دوم: ترتیب نوشته‌شدن همیشه ترتیب اجرا نیست

function greet() {
    console.log("سلام");
}

console.log("شروع");

greet();

console.log("پایان");

خروجی برنامه به این ترتیب است:

شروع
سلام
پایان

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

اصل اول: کد را فقط از بالا به پایین نخوان. مسیر واقعی اجرای برنامه را پیدا کن.

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

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

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

۲. چرا فهمیدن پروژه‌های بزرگ سخت است؟

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

درخواست کاربر
    ↓
Route
    ↓
Controller
    ↓
Service
    ↓
Database
    ↓
نتیجه به کاربر

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

مثال: ورود کاربر

فرض کن کاربر ایمیل و رمز عبور خود را ارسال می‌کند. برای درک فرایند ورود شاید لازم باشد این قسمت‌ها را پیدا کنیم:

  1. کدام مسیر درخواست ورود را دریافت می‌کند؟
  2. کدام کنترلر ورودی‌ها را می‌خواند؟
  3. کدام سرویس کاربر را در پایگاه داده پیدا می‌کند؟
  4. رمز واردشده چگونه با رمز هش‌شده مقایسه می‌شود؟
  5. توکن چگونه ساخته می‌شود و پاسخ چگونه برمی‌گردد؟

اگر فقط فایل کنترلر را بخوانیم، احتمال دارد بخش مهمی از رفتار برنامه را نبینیم.

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

یک نکته درباره اندازه پروژه‌ها

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

۳. اشتباه‌های رایج هنگام خواندن کد

اشتباه اول: خواندن تمام فایل‌ها از ابتدا تا انتها

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

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

اشتباه دوم: نگاه‌کردن فقط به خطوط تغییرکرده

فرض کن یک خط تغییر کرده است:

const total = price;

و حالا تبدیل شده است به:

const total = calculateDiscount(price);

ممکن است این تغییر فقط یک خط باشد؛ اما برای فهم آن باید تابع calculateDiscount را نیز بررسی کنیم. شاید این تابع مبلغ نهایی را تغییر دهد، ورودی خاصی بخواهد یا در چند بخش دیگر استفاده شده باشد.

بنابراین کوچک‌بودن تغییر به معنای کوچک‌بودن اثر آن نیست.

اشتباه سوم: حدس‌زدن به‌جای بررسی

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

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

۴. روش چندمرحله‌ای برای خواندن کد

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

مرحله ۱
دید کلی
←
مرحله ۲
مسیر اجرا
←
مرحله ۳
بررسی جزئیات
←
مرحله ۴
بازبینی کامل

مرحله اول: دید کلی به دست بیاور

هنوز لازم نیست تمام خطوط را بفهمی. ابتدا دنبال پاسخ این پرسش‌ها باش:

مثلاً در یک پروژه Express، ممکن است ابتدا مسیرهای routes، کنترلرها و سرویس‌های مرتبط را پیدا کنی؛ نه اینکه تمام فایل‌های پروژه را با جزئیات بخوانی.

مرحله دوم: یک مسیر مهم را انتخاب کن

حالا یک سناریوی اصلی را دنبال کن؛ مثلاً مسیر موفق ثبت سفارش. به این مسیر معمولاً مسیر موفق یا happy path گفته می‌شود. یعنی حالتی که ورودی معتبر است و اتفاق غیرمنتظره‌ای رخ نمی‌دهد.

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

مرحله سوم: جزئیات مهم را بررسی کن

وقتی مسیر کلی را شناختی، به داخل توابع برو و بررسی کن:

مرحله چهارم: کد را یک‌بار کامل بخوان

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

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

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

۵. چطور جریان اجرای برنامه را دنبال کنیم؟

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

مثال: ثبت سفارش

function createOrder(product, quantity) {
    const total = calculateTotal(product.price, quantity);
    return saveOrder(product.id, quantity, total);
}

function calculateTotal(price, quantity) {
    return price * quantity;
}

function saveOrder(productId, quantity, total) {
    return {
        productId,
        quantity,
        total
    };
}

const order = createOrder(
    { id: 7, price: 200 },
    3
);

console.log(order);

برای درک این کد، ابتدا لازم نیست تمام توابع را با جزئیات بخوانی. مسیر را به این شکل دنبال کن:

createOrder
←
calculateTotal
←
saveOrder
←
ساخت شیء سفارش

خروجی نهایی چنین است:

{
    productId: 7,
    quantity: 3,
    total: 600
}

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

چه زمانی باید مسیر دیگری را بررسی کنیم؟

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

function calculateTotal(price, quantity) {
    if (quantity <= 0) {
        throw new Error("تعداد باید بیشتر از صفر باشد");
    }

    return price * quantity;
}

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

۶. چطور محل استفاده از توابع را پیدا کنیم؟

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

به محل‌هایی که یک تابع در آن‌ها استفاده یا فراخوانی می‌شود، call site می‌گویند.

مثال

function formatPrice(price) {
    return price.toLocaleString("en-US");
}

function showProduct(product) {
    return formatPrice(product.price);
}

function showCart(cart) {
    return formatPrice(cart.total);
}

تابع formatPrice در دو جای متفاوت استفاده شده است: نمایش قیمت یک محصول و نمایش مجموع سبد خرید.

اگر نحوه قالب‌بندی قیمت را تغییر دهیم، ممکن است هر دو قسمت تحت تأثیر قرار بگیرند. به همین دلیل باید محل استفاده‌های تابع را پیدا کنیم.

روش‌های عملی جست‌وجو در ویرایشگر کد

جست‌وجوی متنی همیشه تمام استفاده‌ها را پیدا نمی‌کند. در JavaScript و TypeScript، فراخوانی از طریق یک متغیر، نام مستعار، ویژگی یک شیء یا کد پویا ممکن است نیازمند بررسی بیشتری باشد.

۷. مسیر داده‌ها را دنبال کن

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

مثال: تبدیل نام کاربر

function normalizeName(name) {
    return name.trim().toLowerCase();
}

function createUser(name) {
    const normalizedName = normalizeName(name);

    return {
        name: normalizedName
    };
}

const user = createUser("  ALI  ");

console.log(user.name);

برای خواندن این کد، مقدار ورودی را دنبال کن:

" ALI "
←
trim
←
"ALI"
←
lowercase
←
"ali"

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

هنگام دنبال‌کردن داده‌ها چه بپرسیم؟

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

۸. چطور تغییرات کد را بررسی کنیم؟

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

مثال: اضافه‌شدن تخفیف

نسخه قبلی:

function getFinalPrice(price) {
    return price;
}

نسخه جدید:

function getFinalPrice(price, discount) {
    return price - discount;
}

در نگاه اول، تغییر ساده است. اما باید چند پرسش را بررسی کنیم:

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

برای مثال، فراخوانی قدیمی زیر اکنون ممکن است نتیجه غیرمنتظره بدهد:

getFinalPrice(100);

در JavaScript، اگر discount مقدار نداشته باشد، عبارت 100 - undefined به NaN می‌رسد. پس باید تصمیم بگیریم که رفتار درست چیست.

function getFinalPrice(price, discount = 0) {
    if (discount < 0 || discount > price) {
        throw new Error("تخفیف نامعتبر است");
    }

    return price - discount;
}

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

یک روش ساده برای بررسی تغییرات

  1. مشخص کن تغییر برای حل چه مسئله‌ای انجام شده است.
  2. خطوط اضافه‌شده، حذف‌شده و اصلاح‌شده را پیدا کن.
  3. توابع و داده‌های مرتبط با تغییر را دنبال کن.
  4. محل استفاده از تابع‌های تغییرکرده را بررسی کن.
  5. رفتار موفق و حالت‌های خطا را ارزیابی کن.
  6. در پایان، تغییر را یک‌بار کامل بخوان.

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

۹. روش جعبه‌سیاه: لازم نیست همه‌چیز را بفهمی

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

برای مثال، اگر تابعی مسئول ارسال ایمیل است و اکنون می‌خواهیم بفهمیم پس از ثبت‌نام چه اتفاقی می‌افتد، شاید فعلاً فقط لازم باشد بدانیم: این تابع ایمیل می‌گیرد، پیام را ارسال می‌کند و ممکن است خطا برگرداند.

async function registerUser(input) {
    const user = await saveUser(input);

    await sendWelcomeEmail(user.email);

    return user;
}

برای درک مسیر اصلی ثبت‌نام، می‌توانیم فعلاً تابع sendWelcomeEmail را جعبه‌سیاه در نظر بگیریم. لازم نیست همین لحظه تمام جزئیات اتصال به سرویس ایمیل را بررسی کنیم.

اما اگر مسئله این باشد که ایمیل ارسال نمی‌شود، دیگر نمی‌توانیم آن را نادیده بگیریم. باید وارد تابع شویم و رفتار واقعی آن را بررسی کنیم.

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

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

۱۰. آیا هوش مصنوعی جای خواندن کد را می‌گیرد؟

یکی از بحث‌های مهم مقاله این است که با وجود توانایی مدل‌های زبانی در تولید و بررسی کد، هنوز نمی‌توان مسئولیت درک و بازبینی برنامه را به‌طور کامل به آن‌ها واگذار کرد.

چرا تولیدشدن کد توسط هوش مصنوعی تضمین‌کننده کیفیت نیست؟

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

مثال: راه‌حل بیش از اندازه پیچیده

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

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

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

مثال دیگر: درخواست ساده و تغییر گسترده

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

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

پس چطور از هوش مصنوعی کمک بگیریم؟

نمونه یک درخواست مناسب از هوش مصنوعی

این قابلیت را مرحله‌به‌مرحله توضیح بده.

1. نقطه شروع اجرا را پیدا کن.
2. توابعی را که مستقیماً یا غیرمستقیم فراخوانی می‌شوند مشخص کن.
3. مسیر داده‌ها را توضیح بده.
4. فعلاً وارد جزئیات نامرتبط نشو.
5. فرضیات خودت را از چیزهایی که در کد ثابت شده‌اند جدا کن.
6. برای هر ادعا، فایل و بخش مرتبط را نشان بده.
7. اگر اطلاعات کافی نداری، حدس را به‌عنوان واقعیت بیان نکن.

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

آیا همیشه لازم است تمام کد تولیدشده توسط هوش مصنوعی را بخوانیم؟

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

نکته اصلی این است که مسئولیت درک اثر تغییرات و تصمیم نهایی همچنان بر عهده کسی است که آن تغییر را می‌پذیرد.

۱۱. یک روال عملی برای بررسی پروژه

فرض کنیم در یک پروژه TypeScript و Express، قرار است قابلیت فیلتر مقاله‌ها بر اساس عنوان اضافه شود. می‌توانیم از روال زیر استفاده کنیم.

گام اول: مسئله را دقیق مشخص کن

سؤال ما این نیست که «این پروژه چطور کار می‌کند؟» سؤال دقیق‌تر این است: «درخواست فیلتر عنوان از کجا وارد می‌شود، چگونه پردازش می‌شود و نتیجه چگونه برمی‌گردد؟»

گام دوم: نقطه ورود را پیدا کن

router.get("/articles", getAllArticlesController);

این خط نشان می‌دهد درخواست GET /articles به کنترلر مشخص‌شده هدایت می‌شود؛ البته باید تنظیمات اصلی روتر و مسیر پایه API را نیز بررسی کنیم تا آدرس نهایی را دقیق بدانیم.

گام سوم: کنترلر را بررسی کن

async function getAllArticlesController(req, res, next) {
    try {
        const title = String(req.query.title ?? "");

        const articles = await articleService.searchByTitle(title);

        res.json(articles);
    } catch (error) {
        next(error);
    }
}

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

گام چهارم: سرویس را دنبال کن

async function searchByTitle(title) {
    return articleRepository.findByTitle(title);
}

سرویس در این مثال فقط مسئولیت را به مخزن داده‌ها واگذار می‌کند. هنوز نمی‌دانیم جست‌وجو دقیقاً چگونه انجام می‌شود؛ پس مسیر را ادامه می‌دهیم.

گام پنجم: رفتار پایگاه داده را بررسی کن

async function findByTitle(title) {
    return articleRepository.find({
        where: {
            title: Like(`%${title}%`)
        }
    });
}

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

گام ششم: مسیر را خلاصه کن

GET /articles
←
Controller
←
Service
←
Repository
←
Database

اکنون یک نقشه ذهنی از قابلیت داریم. در صورت بروز مشکل می‌دانیم کدام بخش‌ها را بررسی کنیم و برای هر بخش چه سؤالی بپرسیم.

گام هفتم: موارد مرزی را بررسی کن

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

۱۲. ابزارهای کمکی برای خواندن کد

ابزار یا قابلیت کاربرد مثال استفاده
جست‌وجوی متن پیداکردن نام تابع، متغیر یا پیام خطا جست‌وجوی searchByTitle
Go to Definition رفتن به محل تعریف تابع یا متغیر رفتن از محل فراخوانی به بدنه تابع
Find All References یافتن محل‌های استفاده از یک نماد بررسی اثر تغییر یک تابع مشترک
Git diff مشاهده تفاوت دو نسخه کد بررسی تغییرات پیش از ثبت یا ادغام
Debugger مشاهده اجرای واقعی و مقادیر متغیرها بررسی علت مقدار اشتباه در یک شرط
آزمون‌ها بررسی رفتار مورد انتظار اطمینان از درست‌بودن محاسبه تخفیف

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

۱۳. چک‌لیست خواندن کد

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

۰ از ۸ مرحله انجام شده است.

۱۴. تمرین تعاملی: مسیر اجرای کد را پیدا کن

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

function addTax(price) {
    return price * 1.1;
}

function createReceipt(price) {
    const finalPrice = addTax(price);
    return {
        original: price,
        final: finalPrice
    };
}

const receipt = createReceipt(200);

console.log(receipt.final);

سؤال اول: خروجی نهایی چیست؟

پاسخ خودت را انتخاب کن.

سؤال دوم: کدام روش برای شروع بررسی یک پروژه بزرگ بهتر است؟

پاسخ خودت را انتخاب کن.

سؤال سوم: اگر یک تابع تغییر کند، چه چیزی را باید بررسی کنیم؟

پاسخ خودت را انتخاب کن.

۱۵. جمع‌بندی نهایی

مهم‌ترین مهارت در خواندن کد، حفظ‌کردن تمام خطوط نیست؛ بلکه پیدا کردن مسیر درست برای فهم یک مسئله مشخص است.

  1. ابتدا مشخص کن دقیقاً چه چیزی را می‌خواهی بفهمی.
  2. ساختار کلی و نقطه شروع را پیدا کن.
  3. یک مسیر مهم از فراخوانی توابع را دنبال کن.
  4. بعد از شناخت مسیر، جزئیات لازم را بررسی کن.
  5. محل استفاده از توابع و مسیر داده‌ها را دنبال کن.
  6. در بررسی تغییرات، فقط به خطوط جدید اکتفا نکن.
  7. برای بخش‌های نامرتبط از روش جعبه‌سیاه استفاده کن.
  8. از هوش مصنوعی کمک بگیر، اما توضیحاتش را با کد واقعی تطبیق بده.
  9. در پایان، کد را کامل بازخوانی کن و موارد غیرمنتظره را بررسی کن.
به خاطر بسپار: برای خواندن حرفه‌ای کد لازم نیست همه‌چیز را هم‌زمان بفهمی. ابتدا ساختار را پیدا کن، سپس مسیر را دنبال کن و در نهایت جزئیات مهم را بررسی کن.