تفاوت پارادایم های برنامه نویسی شیءگرا، تابع‌گرا و رویه ای (قسمت سوم: تابع گرا)

تفاوت پارادایم های برنامه نویسی شیءگرا، تابع‌گرا و رویه ای (قسمت اول)

تفاوت پارادایم های برنامه نویسی شیءگرا، تابع‌گرا و رویه ای (قسمت سوم: تابع گرا)

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

زمانی که از برنامه نویسی شیء گرا استفاده می کنیم می توانیم از یک کلاس چندین بار استفاده کنیم، مثل کلاس validator که در هر قسمتی از برنامه قابل استفاده بود، اما در پارادایم رویه ای باید منطق خود را کپی و paste می کردیم که کار جالبی نیست.

در این قسمت به پارادایم تابع گرا می رسیم. قبلا توضیح داده بودم که در برنامه نویسی تابع‌گرا، تابعی یا functional تا حد ممکن سعی می کنیم که کدهای خودمان را در قالب توابع بنویسیم. یعنی داده های مورد نظر ما به صورت پارامتر به این توابع پاس داده می شوند و در نهایت توابع هستند که عملیات خاصی را روی آن ها انجام داده و نتیجه را به ما برمی گردانند.

برای شروع یک فایل جدید به نام functional.js ایجاد کرده و دستور script درون فایل HTML را روی آن تنظیم کنید:

    <script src="functional.js" defer></script>

بهترین روش برای یادگیری این پارادایم (که شاید برای مبتدی ها نسبتا سخت تر از دو پارادایم قبلی باشد) کدنویسی است بنابراین بدون مقدمه شروع می کنم. در ابتدا مثل همیشه باید به input ها و فرم خودمان دسترسی داشته باشیم بنابراین یک تابع جدید تعریف می کنیم:

function connectForm() {
  const form = document.getElementById('user-input');
  form.addEventListener('submit', signupHandler);
}

ساختار این تابع بسیار ساده است: ابتدا فرم را دریافت می کنیم و سپس یک eventListener را روی آن صدا می زنیم. هنوز تابعی به نام formSubmitHandler را تعریف نکرده ایم بنابراین قدم بعدی تعریف این تابع است:

function signupHandler(event) {
    event.preventDefault();

}

function connectForm() {
    const form = document.getElementById('user-input');
    form.addEventListener('submit', signupHandler);
}

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

function connectForm(formId, formSubmitHandler) {
    const form = document.getElementById(formId);
    form.addEventListener('submit', formSubmitHandler);
}

یعنی هر چه که مورد نیاز باشد از طریق پارامترهای ورودی به ما پاس داده می شود. تفاوت اینجاست که کد قبلی برای تابع  connectForm پویا نبود و فقط برای یک فرم خاص (فرم خودمان) کار می کرد در صورتی که حالا با استفاده از پارامترها می توانیم از این تابع برای هر پروژه ای استفاده کنیم. حالا باید در انتهای همین فایل این تابع را صدا بزنیم:

function signupHandler(event) {
    event.preventDefault();

}

function connectForm(formId, formSubmitHandler) {
    const form = document.getElementById(formId);
    form.addEventListener('submit', formSubmitHandler);
}

connectForm('user-input', signupHandler);

توابع بعدی ما وابسته به این تابع هستند بنابراین اگر این تابع را صدا نزنیم، کدهای درون آن اجرا نمی شوند و تمام برنامه به خطا برمی خورد. حالا باید signupHandler را کامل کنیم. قرار است من در تابع به مقادیر وارد شده توسط کاربر دسترسی داشته باشم بنابراین ممکن است بگویید باید این مقادیر را به صورت پارامتر دریافت کنم. این نوع تابع خاص با دیگر توابع متفاوت است! من گفتم که در برنامه نویسی تابع گرا باید تا حد ممکن داده های مورد نیاز را از پارامترها دریافت کنیم اما تابع signupHandler بسیار خاص است و بر اساس id های تعیین شده در هر پروژه تفاوت زیادی خواهد کرد بنابراین من آن را با یک تابع کمکی دیگر انجام می دهم:

function getUserInput(inputElementId) {
  return document.getElementById(inputElementId).value;
}

این تابع مسئول دریافت مقدار هر input است که ما مشخص کنیم. با استفاده از این تابع دیگر نیازی به دوباره نویسی کدها نیست و به راحتی در تابع signupHandler می گوییم:

function signupHandler(event) {
    event.preventDefault();

    const enteredUsername = getUserInput('username');
    const enteredPassword = getUserInput('password');
}

این روش، روش صحیح برنامه نویسی تابع گرا است. البته شما می توانید به صورت عادی نیز این مقادیر را دریافت می کردید و حتما الزامی به این کار نبود. در مرحله ی بعد باید تابعی برای اعتبار سنجی داده ها داشته باشیم. مثل جلسه ی قبل و برنامه نویسی شیء گرا، دو flag (در واقع دو ثابت) برای اعتبار سنجی ایجاد می کنم:

const REQUIRED = 'REQUIRED';
const MIN_LENGTH = 'MIN_LENGTH';

سپس تابعی به نام validate را برای این کار تعریف می کنیم:

function validate(value, flag, validatorValue) {
  if (flag === REQUIRED) {
    return value.trim().length > 0;
  }
  if (flag === MIN_LENGTH) {
    return value.trim().length > validatorValue;
  }
}

منطق کدهای این قسمت دقیقا مانند منطق اعتبار سنجی در جلسه ی قبل است. در کد بالا اگر طول value.trim بیشتر از صفر باشد (کاربر فیلد را خالی نگذاشته باشد) مقدار True را برمی گردانیم اما اگر این شرط صحیح نباشد مقدار false برگردانده خواهد شد. همانطور که می بینید برنامه نویسی تابع گرا و شیء گرا آنقدر از هم متفاوت نیستند و منطق کاری آن ها به هم نزدیک است. همانطور که می بینید ما از ثابت های سراسری استفاده کرده ایم و الزامی نیست که در برنامه تابع گرا همه چیز را درون توابع قرار دهید، بلکه باید تا حد ممکن و تا جایی که عقلانی است از توابع استفاده کنید. استفاده از یک تابع جداگانه برای تعریف دو ثابت واقعا عاقلانه نیست بنابراین آن را به صورت دو ثابت سراسری و خارج از توابع تعریف کرده ایم.

حالا می توانیم به تابع signupHandler برگردیم و از تابع validate استفاده کنیم اما من می خواهم قبل از این کار یک تابع دیگر تعریف کنم که مسئول ایجاد کردن کاربر است. همان شیء user که همیشه می ساختیم:

function createUser(userName, userPassword) {
  if (!validate(userName, REQUIRED) || !validate(userPassword, MIN_LENGTH, 5)) {
    throw new Error(
      'Invalid input - username or password is wrong (password should be at least six characters).'
    );
  }
  return {
    userName: userName,
    password: userPassword
  };
}

این تابع دو پارامتر userName و userPassword را می گیرد و بر اساس نتیجه ی برگشتی از تابع validate یک خطا برمی گرداند و یا اینکه شیء مورد نظر ما را به ما می دهد. شما می توانید به جای throw کردن خطای جدید از همان روش جلسه ی قبل (پنجره ی alert) استفاده کنید اما من برای استفاده نکردن از alert دلیل خاصی دارم. در دنیای برنامه نویسی دو نوع تابع داریم:

  • توابع خاص (pure) که پارامتری را دریافت می کنند و کار خاصی را انجام داده و مقداری را return می کنند. در واقع کاری خارج از محدوده ی خودش نمی کند.
  • توابع ناخالص (impure) که دارای side effect (عارضه ی جانبی) هستند. Side effect یعنی منطقی که درون یک تابع اجرا می شود اما کاری را خارج از تابع انجام می دهد. مثلا تابعی که یک درخواست HTTP را ارسال می کند یا DOM را تغییر می دهد و الی آخر.

در برنامه نویسی تابع گرا بهتر است تا حد ممکن از توابع خالص استفاده کنید بنابراین من از alert استفاده نمی کنم. چرا؟ چون alert یک side effect است و کار را خارج از تابع createUser انجام می دهد. یادتان باشد که این کار خللی در اجرای برنامه ی شما ندارد بلکه یک توصیه ی کلی برای دوری از مشکلات می باشد. غیر از این مورد خاص، تمام این منطق را در جلسه ی قبل توضیح داده بودیم بنابراین از تکرار مکررات پرهیز می کنم. توجه کنید که اگر کد throw اجرا شود، از ادامه ی اجرای اسکریپت جلوگیری می کند بنابراین برخلاف جلسه ی قبل نیازی به return خالی نداریم، خود throw برنامه را متوقف خواهد کرد. اگر throw اجرا نشود هم شیء مورد نظرمان را return می کنیم.

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

function greetUser(user) {
  console.log('Hi, I am ' + user.userName);
}

تابع بالا حاوی console.log است که خودش یک side effect است اما همانطور که گفتم جلوگیری از side effect ها فقط تا حدی است که ممکن باشد، نه اینکه به هر قیمتی از آن ها جلوگیری کنیم. حالا به signupHandler می رویم و کدهای آن را تکمیل می کنیم:

function signupHandler(event) {
  event.preventDefault();

  const enteredUsername = getUserInput('username');
  const enteredPassword = getUserInput('password');

  try {
    const newUser = createUser(enteredUsername, enteredPassword);
    console.log(newUser);
    greetUser(newUser);
  } catch (err) {
    alert(err.message);
  }
}

از آنجایی که در تابع createUser ممکن است خطایی را throw کنیم بهتر است از ساختار try and catch استفاده کنیم تا بتوانیم خطا ها را تشخیص داده و به کاربر اعلام کنیم. در کد بالا اگر خطایی وجود داشته باشد (از طرف createUser خطایی که نوشتیم throw شود) آن را در err دریافت کرده و به صورت err.message به کاربر نمایش می دهیم که همان رشته ای بود که خودمان به عنوان پیام خطا نوشته بودیم. همچنین توجه داشته باشید که تابع alert درون قسمت catch یک side effect است اما همانطور که گفتم برخی اوقات نمی توانیم از آن دوری کنیم و در چنین مواقعی مشکلی وجود ندارد. تعریف کردن یک تابع دیگر فقط برای صدا زدن alert عقلانی نیست.

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

سوال نهایی: کدام پارادایم از بقیه بهتر است؟

پاسخ: بحث هایی که در اینترنت در مورد بهتر بودن یکی از این روش ها می بینید، بحث های دقیق و علمی نیستند. هر کدام از این روش ها در جای خود مهم و کارآمد است و به سلیقه و شرایط شما بستگی دارد که از چه روشی استفاده کنید. در 99 درصد مواقع می توانید از هر کدام از این روش ها استفاده کنید و هیچ مشکلی در اجرای برنامه ی شما به وجود نخواهد آمد بنابراین جواب نهایی این است که هیچ روش برتری وجود ندارد. تنها نکته ای که می توان گفت این است که استفاده از پارادایم رویه ای (procedural) در برنامه های بزرگ برای شما بسیار سخت خواهد بود.

(دانلود سورس کد این جلسه)

نویسنده شوید

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

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