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

مزایا و معایب برنامه‌نویسی تابعی راکت

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

اینکه کجا باید از تابع استفاده کنید یا چه جایی نیاز به تعریف تابع در برنامه‌نویسی نیست، بسته به نیاز و خلاقیت شما دارد. تقریباً اکثر زبان‌های برنامه‌نویسی و همه زبان‌های پر کاربرد، مفهوم تابع را درون خود دارند. فقط ممکن است نحوه نوشتن آن (syntax آن) در زبان‌های مختلف متفاوت باشد. در برخی زبان‌ها به جای تابع (function) از کلماتی چون متد (method)، زیر روال (ساب‌روتین –sub-routine) یا رویه (پروسیژر – procedure) استفاده می‌شود. زمانی که ما در مورد یک پارادایم برنامه‌نویسی مانند برنامه‌نویسی تابعی یاد می‌گیریم اغلب مفید است که چگونگی عملکرد "رفتار و داده‌های" آن پارادایم را بدانیم.

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

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

هدف این دوره، آموزش مهارت‌های عملی لازم برای پیاده‌سازی شی‌گرایی در پایتون است. اگر هر تابع متغیری را در کل برنامه به اشتراک بگذارد، ممکن است ناخواسته و سهواً مقادیر متغیرهایی تغییر پیدا کنند که نباید تغییر کنند. علاوه بر این، به خاطر سپردن نام‌هایی که در جای دیگر نیز استفاده شده‌اند برای برنامه نویسان دشوار است و ایجاد نام‌های جدید برای نشان دادن ایده‌های مشابه قبلی، چالش برانگیز خواهد بود. با ارائه پارامترهای ثابت، تابع محض همواره نتیجه یکسانی بازمی‌گرداند و دیگر لازم نیست که به موقعیت‌هایی فکر کنیم که پارامترهای یکسان نتایج متفاوتی بازمی‌گردانند، زیرا چنین موقعیت‌هایی هرگز پیش نخواهند آمد. می‌بینید که تابع محض ما به نام increase-counter مقدار 2 را بازمی‌گرداند؛ اما مقدار counter همچنان همان است. این تابع مقدار افزایش یافته را بدون تغییر دادن مقدار متغیر بازگشت می‌دهد.

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

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

ماهنامه شبکه را از کجا تهیه کنیم؟ماهنامه شبکه را می‌توانید از کتابخانه‌های عمومی سراسر کشور و نیز از دکه‌های روزنامه‌فروشی تهیه نمائید. همانطور که گفته شد توابع قطعه های پازل منطق برنامه شما هستند و باید ساختار درستی داشته باشند. ترکیب تابع (Function composition) ترکیب 2 یا چند تابع برای ایجاد یک تابع جدید است. داده‌های تغییرناپذیر به این معنی است که شما به راحتی بتوانید ساختارهای داده‌ای ایجاد کنید به‌ جای اینکه ساختارهایی که از قبل وجود دارد را اصلاح کنید. پس از محاسبه معدل مورد نظر، اینکه این معدل با چه نامی شناخته شده و اینکه دقیقا معدل کدام لیست از نمرات است، برای تابع اهمیتی ندارد و تابع فقط وظیفه محاسبه را به دوش دارد. ایده مسئله آن است که یک آرایه مفروض از اعداد صحیح را فیلتر کنیم و تنها مقادیری را که کمتر از یک مقدار تعیین شده x هستند در خروجی ارائه کنیم.

این قضیه را مقایسه کنید با زمانی که باید یک عملیات خاص (مثل خواندن فایل) را در برنامه تعریف کنیم. برای خواندن فایل باید ابتدا فایل را باز کنیم و پس از خواندن، آن را ببندیم. اگر بارها و بارها بخواهیم این کار را انجام دهیم، بخش باز کردن و بستن فایل به دفعات تکرار خواهد شد. کنترل جریان (Flow Control) با استفاده از فراخوانی تابع و فراخوانی تابع با استفاده از بازگشت (recursion) انجام می شود. ترکیب تابع (Function Composition)  ترکیب تابع به معنی ترکیب کردن 2 یا چند تابع برای ساخت یک تابع جدید است.

سازمان‌هایی که به دنبال به حداقل رساندن اشکالات نرم‌افزاری هستند، باید بین تعداد عرضه‌ها و عقب‌گردهای نسخه‌های نرم‌افزاری انجام شده تعادل ایجاد کنند. با انجام این کار، آن‌ها اطمینان حاصل می‌کنند که فرایند دیباگ کردن مانع انتشار زمان‌بندی شده نرم‌افزار پایدار نخواهد شد. این شیوه را سازمان‌هایی به کار می‌گیرند که در محیط توسعه نرم افزار به روش چابُک (اجایل) فعالیت می‌کنند. علاوه بر این، Pure function خروجی مشابهی را برای پارامترهای که به آن داده شده ارائه می دهد. بخش بعدی این مقاله به بررسی فضای کاری (Workspace) مورد نیاز توابع در برنامه نویسی اختصاص داده شده است.

یکی دیگر از مسائلی که می‌توان از این ایده استفاده کرد، مسئله به‌روزرسانی لیست است که در آن می‌خواهیم صرفاً مقادیر یک مجموعه مفروض را با قدر مطلق‌شان به‌روزرسانی کنیم. ما کاری که تابع‌مان می‌خواهد انجام دهد را دقیقاً مشخص می‌کنیم، روی مجموعه تکرار می‌کنیم و آیتم کنونی مجموعه را با x مقایسه کرده و اگر شرایط مورد نظر را داشته باشد، آن عنصر را به resultArray ارسال می‌کنیم. اینک تابع f را داریم و می‌توانیم از آن برای پردازش a و b استفاده کنیم. بدین ترتیب تابع‌های + و – را برای ترکیب با تابع double-operator ارسال می‌کنیم و رفتار جدیدی را ایجاد می‌کنیم. کل ایده به صورت رفتار با تابع‌ها به عنوان مقدار و ارسال تابع‌ها مانند داده است.

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

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

داده های تغییر ناپذیر (Immutable Data)  داده‌های تغییرناپذیر به این معناست که شما باید به راحتی بتوانید به جای اصلاح ساختارهایی که از قبل وجود دارد، ساختارهای داده ای ایجاد کنید. شاید پذیرش اینکه کد شما در اولین بار آن‌طور که باید کامل نیست کمی سخت باشد اما دقیقاً همین‌طور است. برای اینکه کدهایی که می‌نویسید بهینه‌تر باشند، سعی کنید پس از پایان کدنویسی، یک بار دیگر و با دستور‌العمل‌های بهتری کد خود را ویرایش کنید تا به همان نتیجه کد قبلی برسید. در نکته قبلی در مورد وظایف اختصاصی هر کلاس صحبت کردیم اما اکنون می‌خواهیم همان تکفیک وظایف را در مورد ساختمان کلی برنامه‌ای که در حال نوشتن آن هستیم به‌کار ببریم. همان‌طور که مشاهده کردید سعی کنید به‌جای تکرار جملات تقریباً مشابه، الگوریتمی را پیدا کنید که تمام جملات تکراری فوق را در قالب یک حلقه خلاصه کند. مطمئناً رفع باگ یا اشکال‌زدایی حلقه‌ای که ۵۰ تکرار را مدیریت می‌کند بسیار آسان‌تر از ۵۰ بلوک کدی است که هرکدام دارای تکراری مشابه با دیگری است.

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

روی اینکه “چطور برنامه نویسی خودتان را انجام می دهید” تمرکز می کند. از مدل مبتنی بر برنامه نویسی اعلانی (Declarative Programming) پیروی می کند. فرض کنید که در حال کار روی بخشی از یک پروژه بزرگ در سرویس کنترل نسخه‌ای مانند گیت هستید و قرار است به‌زودی کد خود را در اختیار مدیر پروژه یا سایر برنامه‌نویسان قرار دهید. با توجه به پاراگراف قبلی اگر تابع بزرگی را نوشته اید با توجه به کارهای کوچکی که در تابع انجام می شود توابع دیگری از آن استخراج کنید  تا هم تعداد خطوط و هم تعداد تو رفتگی های تابع کم شود. توابع باید کوچک تعریف شوند تا بتوان منظور آنها را به راحتی متوجه شد. منظور از کوچک بودن توابع این است که هر تابع باید حد اکثر 5 یا 6 خط باشد ولی اگر کمتر از 4 خط باشد بهتر است.

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

ایده تابع Reduce دریافت یک تابع و یک مجموعه و بازگشت دادن یک مقدار ایجاد شده با ترکیب کردن آیتم‌ها است. تصور کنید در یک وب‌سایت فروشگاهی هستید و محصول‌های Product 1, Product 2, Product 3 و Product 4 را به سبد خرید خود اضافه می‌کنید (سفارش می‌دهید). تابع double-operator که در بخش فوق پیاده‌سازی کردیم، یک تابع درجه بالاتر است، چون یک تابع دیگر را به عنوان آرگومان دریافت کرد و مورد استفاده قرار می‌دهد. احتمالاً قبلاً در مورد filter, map و reduce چیزهایی شنیده‌اید. ساخت حالت‌هایی برای یک شیء نیز بسیار متداول است تصور کنید رشته‌ای دارید و می‌خواهیم این رشته را به صورت یک url slug درآورید. در برنامه‌نویسی شیءگرا در روبی (Ruby) به این منظور یک کلاس مانند UrlSlugify ایجاد می‌شود.

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

اینک هر تابعی مجزا عمل می‌کند و نمی‌تواند روی بخش‌های دیگر سیستم تأثیر بگذارد. دلیل غیر محض بودن این تابع آن است که از یک متغیر سراسری استفاده می‌کند که به صورت پارامتری به تابع ارسال نمی‌شود. اکنون تصور کنید برخی از ریاضیدان‌ها استدلال کنند که مقدار عدد PI در واقع برابر با 42 است و مقدار این متغیر سراسری تغییر یابد. برای مثال، در زبان جاوا باید سطح دسترسی توابع (متدها) را مشخص کنیم. منظور از سطح دسترسی همان وضعیت public و private و protected در برنامه‌نویسی شئ‌گراست.

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

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

شاید بگویید که کدتان در هر صورت اجرا می‌شود و مشکلی در روند کلی برنامه ایجاد نمی‌کند اما تنها کافی است پس از مدتی سراغ کدهای قبلی خود بروید تا متوجه عمق فاجعه شوید. در هر بار تکرار حلقه مقدار متغیر i و وضعیت متغیر sumofValue تغییر می‌کند، اما تغییر‌پذیری در حلقه‌های تکرار نیز قابل کنترل است. توابع قطعه کدهایی هستند که منطق برنامه ما را تعریف می کنند که در قالب کلاس و پکیج(namespace) دسته بندی می شوند. با جمع شدن و ترکیب توابع است که منطق برنامه شکل داده می شود. پس هرچقدر که توابع خوب و خوانا تعریف شده باشند برنامه راحت تر خوانده می شود و خوانایی کد شما بالا تر است و برعکس. این مطلب نکات و قوانینی برای شما در تعریف توابع مشخص می کند که می توانید با استفاده از آنها توابع و متدهای بهتری تعریف کنید و بعدا هم هنگام خواندن کد آسوده تر باشید.

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


برنامه نویسی یا طراحی سایت