کدنویسی تمیز: معکوس کردن منطق شرطی

Clean Coding: Inverting Conditional Logic

25 خرداد 1400
درسنامه درس 10 از سری کدنویسی تمیز
کدنویسی تمیز: معکوس کردن منطق شرطی (قسمت 10)

در جلسه قبل کدهای خودمان را تا حد زیادی تمیز کردیم اما هنوز جای بهینه سازی وجود دارد. اگر به دو متد processPayment و processRefund نگاه کنید متوجه حضور شرط های تکراری if می شوید که خودش نشانی از code duplication است:

function processPayment(paymentTransaction) {

  if (paymentTransaction.method === "CREDIT_CARD") {

    processCreditCardPayment(paymentTransaction);

  } else if (paymentTransaction.method === "PAYPAL") {

    processPayPalPayment(paymentTransaction);

  } else if (paymentTransaction.method === "PLAN") {

    processPlanPayment(paymentTransaction);

  }

}




function processRefund(refundTransaction) {

  if (refundTransaction.method === "CREDIT_CARD") {

    processCreditCardRefund(refundTransaction);

  } else if (refundTransaction.method === "PAYPAL") {

    processPayPalRefund(refundTransaction);

  } else if (refundTransaction.method === "PLAN") {

    processPlanRefund(refundTransaction);

  }

}

همانطور که می بینید ما مرتبا در حال چک کردن متد تراکنش بین سه مقدار CREDIT_CARD و PAYPAL و PLAN هستیم. مشکل اصلی در این کد این است که شرط های ما شبیه به هم هستند اما برای هر کدام از این شرط ها عملیات های مختلفی را انجام می دهیم و این عملیات ها به هیچ وجه شباهتی با هم ندارند. توجه داشته باشید که من برای ساده تر شدن کدها، متدهای بالا (مانند processPlanRefund یا processCreditCardRefund یا processCreditCardPayment و غیره) را طوری نوشته ام که فقط console.log کنند:

function processCreditCardPayment(transaction) {

  console.log(

    "Processing credit card payment for amount: " + transaction.amount

  );

}




function processCreditCardRefund(transaction) {

  console.log(

    "Processing credit card refund for amount: " + transaction.amount

  );

}




function processPayPalPayment(transaction) {

  console.log("Processing PayPal payment for amount: " + transaction.amount);

}




function processPayPalRefund(transaction) {

  console.log("Processing PayPal refund for amount: " + transaction.amount);

}




function processPlanPayment(transaction) {

  console.log("Processing plan payment for amount: " + transaction.amount);

}




function processPlanRefund(transaction) {

  console.log("Processing plan refund for amount: " + transaction.amount);

}

اما در برنامه های واقعی این توابع مسئول پردازش تراکنش ها خواهند بود و عملیات های بانکی را انجام می دهند. به همین خاطر ادغام این کدها در هم غیر ممکن است اما می توانیم به روش خاصی آن را تمیزتر کنیم. چه روشی؟ ما می توانیم منطق این شرط را برعکس کنیم! یعنی چه؟

در حال حاضر برنامه ما ابتدا نوع تراکنش را در متد processTransaction بررسی می کند تا متوجه شویم تراکنش از نوع Payment (پرداخت) یا Refund (بازگرداندن پول مشتری) است:

function processTransaction(transaction) {

  if (transactions.status !== "OPEN") {

    showErrorMessage("Invalid transaction type!");

    return;

  }




  if (transaction.type === "PAYMENT") {

    processPayment(transaction);

  } else if (transaction.type === "REFUND") {

    processRefund(transaction);

  } else {

    showErrorMessage("Invalid transaction type!", transaction);

  }

}

همانطور که در کد بالا مشخص است، بر اساس نوع تراکنش یکی از متدهای processPayment یا processRefund را فراخوانی می کنیم. با برعکس کردن این شرط می توانیم بدین شکل عمل کنیم: ابتدا به جای بررسی نوع تراکنش، متد تراکنش را بررسی می کنیم (CREDIT_CARD و PAYPAL و PLAN) و سپس در مرحله دوم نوع تراکنش را بررسی می کنیم. با انجام این کار نیازی نیست که هر بار متد تراکنش را جداگانه بررسی کرده و باعث ایجاد code duplication (تکرار کد) شویم. با این حساب اولین قدم ما برای ویرایش این کد این است که توابع جداگانه ای برای تعیین متد تراکنش بنویسیم:

function usesCreditCard(transaction) {

  return transaction.method === "CREDIT_CARD";

}




function usesPayPal(transaction) {

  return transaction.method === "PAYPAL";

}




function usesPlan(transaction) {

  return transaction.method === "PLAN";

}

همانطور که می بینید این سه تابع مشخص کننده این هستند که متد تراکنش چیست. با return کردن یک مقایسه مانند کدهای بالا یا true و یا false برای ما برگردانده می شود بنابراین متوجه می شویم که نوع تراکنش چیست. کدهای بالا از نظر فنی مشکلی ندارند اما زیاده نویسی هستند! چرا؟ به دلیل اینکه می توانیم هر سه را در یک تابع واحد ادغام کنیم:

function usesTransactionMethod(transaction, method) {

  return transaction.method === method;

}

تابع ما دو پارامتر دارد: پارامتر اول تراکنش مورد نظر است و پارامتر دوم متد مورد نظر ما برای انجام مقایسه! با این حساب می توانیم متد تراکنش را در هر حالتی مقایسه کنیم و نیازی به سه تابع جداگانه نداریم. حالا که این تابع را داریم می توانیم به تابع processTransaction برگشته و آن را بدین شکل ویرایش کنیم:

function processTransaction(transaction) {

  if (!isOpen(transaction)) {

    showErrorMessage("Invalid transaction type!");

    return;

  }




  if (usesTransactionMethod(transaction, "CREDIT_CARD")) {

    // ...

  } else if (usesTransactionMethod(transaction, "PAYPAL")) {

    // ...

  } else if (usesTransactionMethod(transaction, "PLAN")) {

    // ...

  }

}

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

function processCreditCardTransaction(transaction) {}




function processPayPalTransaction(transaction) {}




function processPlanTransaction(transaction) {}

از شما می خواهم که به عنوان یک چالش خودتان بر اساس کدهایی که از قبل داریم محتوای درون این توابع را بنویسید و سپس به پاسخ من نگاه کنید. طبیعتا قدم اول این است که این متدها را در processTransaction صدا بزنیم:

function processTransaction(transaction) {

  if (!isOpen(transaction)) {

    showErrorMessage("Invalid transaction type!");

    return;

  }




  if (usesTransactionMethod(transaction, "CREDIT_CARD")) {

    processCreditCardTransaction(transaction);

  } else if (usesTransactionMethod(transaction, "PAYPAL")) {

    processPayPalTransaction(transaction);

  } else if (usesTransactionMethod(transaction, "PLAN")) {

    processPlanTransaction(transaction);

  }

}

در مرحله بعدی وارد نوشتن محتوای این توابع می شویم:

function processCreditCardTransaction(transaction) {

  if (isPayment(transaction)) {

    processCreditCardPayment();

  } else if (isRefund(transaction)) {

    processCreditCardRefund();

  } else {

    showErrorMessage("Invalid transaction type!", transaction);

  }

}




function processPayPalTransaction(transaction) {

  if (isPayment(transaction)) {

    processPayPalPayment();

  } else if (isRefund(transaction)) {

    processPayPalRefund();

  } else {

    showErrorMessage("Invalid transaction type!", transaction);

  }

}




function processPlanTransaction(transaction) {

  if (isPayment(transaction)) {

    processPlanPayment();

  } else if (isRefund(transaction)) {

    processPlanRefund();

  } else {

    showErrorMessage("Invalid transaction type!", transaction);

  }

}

واضح است که برای برعکس کردن کامل منطق برنامه، شرط های if برای PAYMENT و REFUND را به درون تک تک این متدها منتقل کرده ایم. با اینکه هنوز هم code duplication داریم و به دلیل منطق کلی برنامه راهی برای حذف کامل آن نیست اما حالا خواندن کد آسان تر شده است چرا که شرط های if را در بین برنامه پخش کرده ایم. در حال حاضر تمام کدهای فایل من بدین شکل هستند:

main();




function main() {

  const transactions = [

    {

      id: "t1",

      type: "PAYMENT",

      status: "OPEN",

      method: "CREDIT_CARD",

      amount: "23.99",

    },

    {

      id: "t2",

      type: "PAYMENT",

      status: "OPEN",

      method: "PAYPAL",

      amount: "100.43",

    },

    {

      id: "t3",

      type: "REFUND",

      status: "OPEN",

      method: "CREDIT_CARD",

      amount: "10.99",

    },

    {

      id: "t4",

      type: "PAYMENT",

      status: "CLOSED",

      method: "PLAN",

      amount: "15.99",

    },

  ];




  processTransactions(transactions);

}




function processTransactions(transactions) {

  if (isEmpty(transactions)) {

    showErrorMessage("No transactions provided!");

    return;

  }




  for (const transaction of transactions) {

    processTransaction(transaction);

  }

}




function isEmpty(transactions) {

  return !transactions || transactions.length === 0;

}




function showErrorMessage(message, item) {

  console.log(message);

  if (item) {

    console.log(item);

  }

}




function processTransaction(transaction) {

  if (!isOpen(transaction)) {

    showErrorMessage("Invalid transaction type!");

    return;

  }




  if (usesTransactionMethod(transaction, "CREDIT_CARD")) {

    processCreditCardTransaction(transaction);

  } else if (usesTransactionMethod(transaction, "PAYPAL")) {

    processPayPalTransaction(transaction);

  } else if (usesTransactionMethod(transaction, "PLAN")) {

    processPlanTransaction(transaction);

  }

}




function isOpen(transaction) {

  return transaction.status === "OPEN";

}




function usesTransactionMethod(transaction, method) {

  return transaction.method === method;

}




function isPayment(transaction) {

  return transaction.type === "PAYMENT";

}




function isRefund(transaction) {

  return transaction.type === "REFUND";

}




function processCreditCardTransaction(transaction) {

  if (isPayment(transaction)) {

    processCreditCardPayment();

  } else if (isRefund(transaction)) {

    processCreditCardRefund();

  } else {

    showErrorMessage("Invalid transaction type!", transaction);

  }

}




function processPayPalTransaction(transaction) {

  if (isPayment(transaction)) {

    processPayPalPayment();

  } else if (isRefund(transaction)) {

    processPayPalRefund();

  } else {

    showErrorMessage("Invalid transaction type!", transaction);

  }

}




function processPlanTransaction(transaction) {

  if (isPayment(transaction)) {

    processPlanPayment();

  } else if (isRefund(transaction)) {

    processPlanRefund();

  } else {

    showErrorMessage("Invalid transaction type!", transaction);

  }

}




function processCreditCardPayment(transaction) {

  console.log(

    "Processing credit card payment for amount: " + transaction.amount

  );

}




function processCreditCardRefund(transaction) {

  console.log(

    "Processing credit card refund for amount: " + transaction.amount

  );

}




function processPayPalPayment(transaction) {

  console.log("Processing PayPal payment for amount: " + transaction.amount);

}




function processPayPalRefund(transaction) {

  console.log("Processing PayPal refund for amount: " + transaction.amount);

}




function processPlanPayment(transaction) {

  console.log("Processing plan payment for amount: " + transaction.amount);

}




function processPlanRefund(transaction) {

  console.log("Processing plan refund for amount: " + transaction.amount);

}

به نظر من کدهایمان بسیار خواناتر شده اند.

خطاها را دست کم نگیرید!

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

if (!isEmail) {

  return {code: 422, message: 'Invalid Input'};

}

همانطور که می بینید در اینجا ایمیل بودن یک داده را بررسی می کنیم و اگر این داده واقعا ایمیل نبود یک شیء را برمی گردانیم که خصوصیات code و message را دارد. پس از انجام این کار احتمالا باید در بخش دیگری از برنامه این شیء را دریافت کنیم و سپس قسمت code آن را بررسی کنیم تا ببینیم ورودی کاربر باید قبول شود یا خیر! به زبان ساده ما در حال ساخت یک error شخصی با استفاده از شرط های if هستیم در حالی که می توانستیم به سادگی از یک Error در زبان برنامه نویسی خودمان استفاده کنیم:

if (!isEmail) {

  const error = new Error('Invalid input');

  error.code = 422;

  throw error;

}

این بار به جای پاس دادن اشیاء بین بخش های مختلف کد، به سادگی یک خطا را پرتاب کرده ایم. با اینکه این کد به زبان جاوا اسکریپت نوشته شده است اما تمام زبان های برنامه نویسی مکانیسمی شبیه به آن را دارند که نهایتا در جزئیات متفاوت خواهد بود.

ما در کدی که تا این لحظه روی آن کار کرده ایم چند مثال به همین شکل داریم که موقعیت نیاز به استفاده از خطاها دارد اما ما از آنها استفاده نکرده ایم. یک نمونه از آن متد processTransactions است:

function processTransactions(transactions) {

  if (isEmpty(transactions)) {

    showErrorMessage("No transactions provided!");

    return;

  }




  for (const transaction of transactions) {

    processTransaction(transaction);

  }

}

ما در این مثال خالی بودن تراکنش را بررسی کرده ایم (isEmpty) و در صورتی که تراکنش واقعا خالی باشد یک متن خطا را با استفاده از متد showErrorMessage به کاربر نمایش داده ایم. از نظر خوانایی کد، چاپ کردن خطا نباید بخشی از پردازش یک تراکنش باشد، پردازش تراکنش واقعا باید پردازش را بر عهده داشته باشد نه اینکه خطاهای برنامه را چاپ کند. روش بهتر این است که این کار را در متد main (متد اصلی برنامه) انجام بدهیم.

یکی از راه حل های ممکن این است که مانند مثالی که توضیح دادم، کاری کنیم تا متد processTransactions به جای چاپ خطا، یک شیء را برگرداند که خصوصیات code و message را داشته باشد:

function processTransactions(transactions) {

  if (isEmpty(transactions)) {

    return { code: 1, message: "No transaction provided!" }

  }




  for (const transaction of transactions) {

    processTransaction(transaction);

  }

}

حالا در متد main می توانیم متد processTransactions را به شکل زیر صدا بزنیم:

function main() {

  const transactions = [

    {

      id: "t1",

      type: "PAYMENT",

      status: "OPEN",

      method: "CREDIT_CARD",

      amount: "23.99",

    },

    {

      id: "t2",

      type: "PAYMENT",

      status: "OPEN",

      method: "PAYPAL",

      amount: "100.43",

    },

    {

      id: "t3",

      type: "REFUND",

      status: "OPEN",

      method: "CREDIT_CARD",

      amount: "10.99",

    },

    {

      id: "t4",

      type: "PAYMENT",

      status: "CLOSED",

      method: "PLAN",

      amount: "15.99",

    },

  ];




  const result = processTransactions(transactions);

  if (result.code === 1) {

    showErrorMessage(result.message);

  }

}

این روش کمی بهتر از روش قبلی ما است چرا که چاپ پیام خطا را از متد مربوط به پردازش تراکنش ها خارج می کند اما هنوز هم بد است چرا که ما یک شرط اضافی if را به کدهایمان اضافه کرده ایم و به جای اینکه از مکانیسم ساده خطا استفاده کنیم، سعی در ساخت مکانیسم خطای خودمان داشته ایم! روش بهتر، همانطور که توضیح داده شد، استفاده از خطاها است:

function processTransactions(transactions) {

  if (isEmpty(transactions)) {

    const error = new Error('No transactions provided!');

    error.code = 1;

    throw error;

  }




  for (const transaction of transactions) {

    processTransaction(transaction);

  }

}

به دلیل ماهیت دستور throw و پرتاب کردن خطاها در جاوا اسکریپت، زمانی که به دستور throw error برسیم اجرای کد متوقف شده و دیگر حلقه for و کدهای بعد از آن اجرا نمی شوند. حالا باید در قسمتی از برنامه این خطا را مدیریت کنیم. من با هم به همان تابع main می روم و می گویم:

function main() {

  const transactions = [

    {

      id: "t1",

      type: "PAYMENT",

      status: "OPEN",

      method: "CREDIT_CARD",

      amount: "23.99",

    },

    {

      id: "t2",

      type: "PAYMENT",

      status: "OPEN",

      method: "PAYPAL",

      amount: "100.43",

    },

    {

      id: "t3",

      type: "REFUND",

      status: "OPEN",

      method: "CREDIT_CARD",

      amount: "10.99",

    },

    {

      id: "t4",

      type: "PAYMENT",

      status: "CLOSED",

      method: "PLAN",

      amount: "15.99",

    },

  ];




  try {

    processTransactions(transactions);

  } catch (error) {

    showErrorMessage(error.message);

  }

}

همانطور که می بینید من از یک بلوک try & catch استفاده کرده ام. این بلوک ها بدین شکل کار می کنند که ابتدا بخش try اجرا می شود و اگر خطایی داشته باشیم همه چیز متوقف شده و وارد بخش catch می شویم که به صورت خودکار آن خطا را دریافت می کند. ما در catch به شیء error دسترسی داریم بنابراین می توانیم خصوصیت message آن را استخراج کرده و نمایش بدهیم. این روش، روش صحیح و استاندارد انجام این کار است.

ما در کدهای خودمان دو بخش دیگر را نیز داریم که با این مشکل دست و پنجه نرم می کنند. بخش اول همان متد processTransaction است:

function processTransaction(transaction) {

  if (!isOpen(transaction)) {

    showErrorMessage("Invalid transaction type!");

    return;

  }




  if (usesTransactionMethod(transaction, "CREDIT_CARD")) {

    processCreditCardTransaction(transaction);

  } else if (usesTransactionMethod(transaction, "PAYPAL")) {

    processPayPalTransaction(transaction);

  } else if (usesTransactionMethod(transaction, "PLAN")) {

    processPlanTransaction(transaction);

  }

}

همانطور که می بینید ما در این بخش نیز از showErrorMessage استفاده کرده ایم و خطای مناسبی نداریم. بخش دوم نیز شامل تمام متدهای processCreditCardTransaction و processPayPalTransaction و processPlanTransaction است. تمام این متدها ساختاری شبیه به هم را دارند و در انتهای خود به جای پرتاب خطا، یک رشته ساده را چاپ می کنند. بیایید برای مثال متد processCreditCardTransaction را بازنگری کنیم:

 function processCreditCardTransaction(transaction) {

  if (isPayment(transaction)) {

    processCreditCardPayment();

  } else if (isRefund(transaction)) {

    processCreditCardRefund();

  } else {

    showErrorMessage("Invalid transaction type!", transaction);

  }

}

در صورتی که تراکنش نه پرداخت و نه بازپس گیری پول مشتری باشد یعنی تراکنش غیر معتبر است و ما این کار را با چاپ کردن یک رشته ساده انجام داده ایم. البته در نظر داشته باشید که خود تراکنش را نیز به آن ضمیمه کرده و چاپ می کنیم. متدهای  processPayPalTransaction و processPlanTransaction نیز دقیقا به همین شکل هستند.

برای تصحیح این موارد ابتدا از متد processTransaction شروع می کنیم:

function processTransaction(transaction) {

  if (!isOpen(transaction)) {

    const error = new Error('Invalid transaction type.');

    throw error;

  }




  if (usesTransactionMethod(transaction, "CREDIT_CARD")) {

    processCreditCardTransaction(transaction);

  } else if (usesTransactionMethod(transaction, "PAYPAL")) {

    processPayPalTransaction(transaction);

  } else if (usesTransactionMethod(transaction, "PLAN")) {

    processPlanTransaction(transaction);

  }

}

اضافه کردن خصوصیت code به شیء Error فقط جهت مثال بود و من واقعا به آن نیازی ندارم بنابراین این کار را در این بخش انجام نداده ام. تنها کاری که کرده ایم ساخت یک خطای جدید و سپس پرتاب کردن آن است. در مرحله بعدی به سراغ متد processCreditCardTransaction می رویم تا آن را نیز به همین شکل تصحیح کنیم:

function processCreditCardTransaction(transaction) {

  if (isPayment(transaction)) {

    processCreditCardPayment();

  } else if (isRefund(transaction)) {

    processCreditCardRefund();

  } else {

    const error = new Error('Invalid transaction type!');

    error.item = transaction;

    throw error;

  }

}

در این متد باید خود تراکنش را نیز داشته باشیم بنابراین آن را به شکل خصوصیتی به نام item به شیء error اضافه کرده ایم. در جاوا اسکریپت می توانیم هر خصوصیتی با هر نامی را به یک شیء اضافه کنیم حتی اگر از قبل وجود نداشته باشد، به همین خاطر است که می توانیم item را بدین شکل اضافه کنیم. در نهایت نیز خطا را پرتاب می کنیم. به نظر شما اگر این کار را برای متدهای دیگر انجام بدهیم چه می شود؟

function processCreditCardTransaction(transaction) {

  if (isPayment(transaction)) {

    processCreditCardPayment();

  } else if (isRefund(transaction)) {

    processCreditCardRefund();

  } else {

    const error = new Error('Invalid transaction type!');

    error.item = transaction;

    throw error;

  }

}




function processPayPalTransaction(transaction) {

  if (isPayment(transaction)) {

    processPayPalPayment();

  } else if (isRefund(transaction)) {

    processPayPalRefund();

  } else {

    const error = new Error('Invalid transaction type!');

    error.item = transaction;

    throw error;

  }

}




function processPlanTransaction(transaction) {

  if (isPayment(transaction)) {

    processPlanPayment();

  } else if (isRefund(transaction)) {

    processPlanRefund();

  } else {

    const error = new Error('Invalid transaction type!');

    error.item = transaction;

    throw error;

  }

}

همانطور که می بینید ما کدهایمان را تکرار کرده ایم و این تکرار کد از نظر نوشتن کد تمیز یک عیب محسوب می شود (قبلا در مورد code duplication صحبت کرده بودیم). به نظر شما راه حل چیست؟ بهترین راه حل استفاده از یک guard است که قبلا با آن ها آشنا شده ایم! چطور؟ یعنی در مرحله اول، تمام قسمت else از سه متد بالا را حذف می کنیم:

ط

function processCreditCardTransaction(transaction) {

  if (isPayment(transaction)) {

    processCreditCardPayment();

  } else if (isRefund(transaction)) {

    processCreditCardRefund();

  } else {

    const error = new Error('Invalid transaction type!');

    error.item = transaction;

    throw error;

  }

}




function processPayPalTransaction(transaction) {

  if (isPayment(transaction)) {

    processPayPalPayment();

  } else if (isRefund(transaction)) {

    processPayPalRefund();

  } else {

    const error = new Error('Invalid transaction type!');

    error.item = transaction;

    throw error;

  }

}




function processPlanTransaction(transaction) {

  if (isPayment(transaction)) {

    processPlanPayment();

  } else if (isRefund(transaction)) {

    processPlanRefund();

  } else {

    const error = new Error('Invalid transaction type!');

    error.item = transaction;

    throw error;

  }

}

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

function processTransaction(transaction) {

  if (!isOpen(transaction)) {

    const error = new Error('Invalid transaction type.');

    throw error;

  }




  if (!isPayment(transaction) && !isRefund(transaction)) {

    const error = new Error('Invalid transaction type!');

    error.item = transaction;

    throw error;

  }




  if (usesTransactionMethod(transaction, 'CREDIT_CARD')) {

    processCreditCardTransaction(transaction);

  } else if (usesTransactionMethod(transaction, 'PAYPAL')) {

    processPayPalTransaction(transaction);

  } else if (usesTransactionMethod(transaction, 'PLAN')) {

    processPlanTransaction(transaction);

  }

}

من در این متد یک شرط ساده تعریف کرده ام که می گوید اگر تراکنش payment (پرداخت) یا refund (برگرداندن پول مشتری) نباشد بنابراین غیر معتبر است و درون آن یک خطای ساده را پرتاب می کنیم. نحوه ساخت این خطا نیز ساده است و چندین بار توضیح داده شده است بنابراین دوباره آن را تکرار نمی کنم.

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

function processTransactions(transactions) {

  if (isEmpty(transactions)) {

    const error = new Error('No transactions provided!');

    error.code = 1;

    throw error;

  }




  for (const transaction of transactions) {

    try {

      processTransaction(transaction);

    } catch (error) {

      showErrorMessage(error.message, error.item);

    }

  }

}

همانطور که می بینید با نوشتن کد مدیریت خطا درون این حلقه for باعث رد شدن تراکنش نامعتبر می شویم اما به تراکنش های دیگر آسیبی وارد نمی کنیم. با این حساب کدهای ما تا این لحظه به شکل زیر خواهند بود:

main();




function main() {

  const transactions = [

    {

      id: 't1',

      type: 'PAYMENT',

      status: 'OPEN',

      method: 'CREDIT_CARD',

      amount: '23.99',

    },

    {

      id: 't2',

      type: 'PAYMENT',

      status: 'OPEN',

      method: 'PAYPAL',

      amount: '100.43',

    },

    {

      id: 't3',

      type: 'REFUND',

      status: 'OPEN',

      method: 'CREDIT_CARD',

      amount: '10.99',

    },

    {

      id: 't4',

      type: 'PAYMENT',

      status: 'CLOSED',

      method: 'PLAN',

      amount: '15.99',

    },

  ];




  try {

    processTransactions(transactions);

  } catch (error) {

    showErrorMessage(error.message);

  }

}




function processTransactions(transactions) {

  if (isEmpty(transactions)) {

    const error = new Error('No transactions provided!');

    error.code = 1;

    throw error;

  }




  for (const transaction of transactions) {

    try {

      processTransaction(transaction);

    } catch (error) {

      showErrorMessage(error.message, error.item);

    }

  }

}




function isEmpty(transactions) {

  return !transactions || transactions.length === 0;

}




function showErrorMessage(message, item) {

  console.log(message);

  if (item) {

    console.log(item);

  }

}




function processTransaction(transaction) {

  if (!isOpen(transaction)) {

    const error = new Error('Invalid transaction type.');

    throw error;

  }




  if (!isPayment(transaction) && !isRefund(transaction)) {

    const error = new Error('Invalid transaction type!');

    error.item = transaction;

    throw error;

  }




  if (usesTransactionMethod(transaction, 'CREDIT_CARD')) {

    processCreditCardTransaction(transaction);

  } else if (usesTransactionMethod(transaction, 'PAYPAL')) {

    processPayPalTransaction(transaction);

  } else if (usesTransactionMethod(transaction, 'PLAN')) {

    processPlanTransaction(transaction);

  }

}




function isOpen(transaction) {

  return transaction.status === 'OPEN';

}




function usesTransactionMethod(transaction, method) {

  return transaction.method === method;

}




function isPayment(transaction) {

  return transaction.type === 'PAYMENT';

}




function isRefund(transaction) {

  return transaction.type === 'REFUND';

}




function processCreditCardTransaction(transaction) {

  if (isPayment(transaction)) {

    processCreditCardPayment();

  } else if (isRefund(transaction)) {

    processCreditCardRefund();

  }

}




function processPayPalTransaction(transaction) {

  if (isPayment(transaction)) {

    processPayPalPayment();

  } else if (isRefund(transaction)) {

    processPayPalRefund();

  }

}




function processPlanTransaction(transaction) {

  if (isPayment(transaction)) {

    processPlanPayment();

  } else if (isRefund(transaction)) {

    processPlanRefund();

  }

}




function processCreditCardPayment(transaction) {

  console.log(

    'Processing credit card payment for amount: ' + transaction.amount

  );

}




function processCreditCardRefund(transaction) {

  console.log(

    'Processing credit card refund for amount: ' + transaction.amount

  );

}




function processPayPalPayment(transaction) {

  console.log('Processing PayPal payment for amount: ' + transaction.amount);

}




function processPayPalRefund(transaction) {

  console.log('Processing PayPal refund for amount: ' + transaction.amount);

}




function processPlanPayment(transaction) {

  console.log('Processing plan payment for amount: ' + transaction.amount);

}




function processPlanRefund(transaction) {

  console.log('Processing plan refund for amount: ' + transaction.amount);

}

در جلسه بعدی در رابطه با استخراج منطق اعتبارسنجی و موضوعات مربوط به آن صحبت می کنیم.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری کدنویسی تمیز توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

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