شما نباید درستی پارامترهای ورودی توابع را چک کنید

آیا باید درستی پارامترهای ورودی توابع را چک کرد؟ پاسخ ما منفی است! پیشنهاد ما استفاده از decoratorها برای اعتبارسنجی پارامترهاست.
آیا شما پارامترهای ورودی توابع را اعتبارسنجی میکنید که به عنوان مثال null نباشد یا محدودیتهای مساله برای آن برقرار باشد؟ توصیه ما این است که حتی اگر قبلا چنین کاری میکردید دیگر انجام ندهید! اجازه دهید که تابع با null pointer یا خطاهای دیگر کرش کند. ممکن است در نگاه اول غیرمنطقی به نظر برسد اما پیشنهاد ما استفاده از decoratorهای اعتبارسنجی است.
decorator یکی از الگوهای طراحی است که به کلاسها اجازه میدهد رفتارشان به صورت پویا در زمان اجرا گسترش پیدا کند. یا به عبارتی به اشیا ویژگیهای جدیدی را در زمان اجرا اضافه میکند. اگر یک قاب عکس را در نظر بگیرید، عکس شی اصلی ماست که ویژگی های خاص خود را دارد. برای نمایش دادن و تزیین آن، یک قاب به عکس اضافه میشود که این قاب همان decorator به حساب میآید.
در صورتی از این الگو استفاده میشود که
- مسئولیتها و رفتار اشیا لازم است به صورت پویا تغییر کند.
- پیادهسازی اصلی لازم است از مسئولیتها و رفتارها جدا باشد.
هرچند این کار با تعریف چندین زیرکلاس قابل انجام است اما تعریف تعداد زیادی زیرکلاس کار خوبی نیست و فرایند مراقبت و نگهداری را پیچیده میکند چرا که برای هر ترکیب احتمالی از رفتار نیاز به یک زیر کلاس خواهد بود. پس در ادامه خواهیم گفت این الگو برای اعتبارسنجی دقیقا همان چیزی است که احتیاج داریم.
کد زیر را در نظر بگیرید:
1 | class Report { void export(File file) { if (file == null) { throw new IllegalArgumentException( "File is NULL; can't export." ); } if (file.exists()) { throw new IllegalArgumentException( "File already exists." ); } // Export the report to the file } } |
کاملا تدافعی است درسته؟ اگر کدهای اعتبارسنجی را از آن حذف کنیم کد خیلی کوتاهتر خواهد شد اما اگر پیام null باشد کرش میکند. یا حتی بدتر اگر فایل قبلا وجود داشته باشد بدون هیچ پرتاب خطایی، گزارش overwrite میشود. خطرناک است. درسته؟
بله باید خودمان را محافظت کنیم و باید تدافعی باشیم.
اما نه از این راه، نه از طریق غرق کردن کلاس در کدهای اعتبارسنجی که کاری در رابطه با قابلیت اصلی آن انجام نمیدهند. به جای آن لازم است که از decoratorها برای اعتبارسنجی استفاده کنیم. حال در این مثال نحوه انجام اینکار را نشان میدهیم. اول لازم است که یک interface تعریف کنیم:
1 | interface Report { void export(File file); } |
سپس یک کلاس که قابلیت اصلی را پیادهسازی میکند:
1 | class DefaultReport implements Report { @Override void export(File file) { // Export the report to the file } } |
و در نهایت تعدادی decorator که از ما محافظت میکنند:
1 | class NoWriteOverReport implements Report { private final Report origin; NoWriteOverReport(Report rep) { this.origin = rep; } @Override void export(File file) { if (file.exists()) { throw new IllegalArgumentException( "File already exists." ); } this.origin.export(file); } } |
حالا کلاینت این انعطافپذیری را دارد که اشیا پیچیدهای از decoratorها را که کار خاصی را انجام میدهند با هم ترکیب کند. شی اصلی کار گزارشدهی را انجام میدهد، در حالیکه decoratorها پارامترها اعتبارسنجی میکنند:
1 | Report report = new NoNullReport( new NoWriteOverReport( new DefaultReport() ) ); report.export(file); |
اما پیروی از چنین رویکردی چه سودی دارد؟
اولین و مهمترین مزیت آن کوچک شدن اشیا است. همواره نگهداری و مراقبت از اشیا کوچکتر سادهتر است. مهم نیست که در آینده چند نوع اعتبارسنج پیادهسازی میکنیم کلاس DefaultReport همواره کوچک خواهد ماند. هر چه اعتبارسنجی بیشتری نیاز باشد decoratorهای بیشتری ساخته خواهد شد و همه آنها کوچک و یکپارچه خواهد بود و این امکان را به ما خواهد داد که چند تا از آنها را در انواع مختلف در کنار هم قرار دهیم.
به علاوه، این رویکرد به کد قابلیت استفاده مجدد میدهد، چرا که کلاسها کار کمی انجام میدهند و به طور پیشفرض از خود دفاع نمیکنند. درست است که تدافعی بودن کد خیلی مهم است و برای این منظور ما از decoratorها استفاده میکنیم. اما اجرای کد در چنین حالت تدافعی همواره ممکن نیست. گاهی اعتبارسنجی خیلی از لحاظ زمان و حافظه هزینهبر خواهد بود و ما ترجیح خواهیم داد که مستقیما با اشیا کار کنیم و از آنها دفاع نکنیم.
نظر شما چیست؟ شما نیز با چنین رویکردی موافق هستید؟
منابع:
سلام،
ممنون از مطلب جالبی که گذاشتید.
به نظر من این روش در زبانی مثل جاوا که تایپ سیستم ضعیفی داره در ازای اتلاف وقت و پیچیده کردن کدی که ایجاد میکنه فایده زیادی نداره.
معمولا چک کردن ورودیها فقط در بالاترین سطح فراخوانی ضروری هست و در بقیه جاها جزو قرارداد تابع فرض میشه و بهتره که چک نشه.
اگر از زبانهایی با تایپ سیستم قوی مثل اسکالا یا اف شارپ هم استفاده کنید به راحتی میشه ورودیها رو از انواع داده ای مناسب تعریف کرد تا اصلا نیازی به چک کردن اونها نباشه و کامپایلر برای ما این کار رو انجام میده.
موفق باشید.
سلام
به نظرم خیلی کار جالبی نیست و یا دستکم خیلی حکم کلی نمیشود داد و موضوع نسبی است. مثلا از اون جایی که خطر null pointer معمولا برای هر پارامتری در هر تابعی وجود داره؛ برای چک کردن null، فکر کنید چقدر باید کلاس جدید اضافه کنیم که فقط میخواد این موضوع را چک کنه! ذستکم به ازای هر اینترفیس یک کلاس!
در این مواقع من ترجیح میدهم اصلا null pointer exception رخ دهد تا چک کردن این موضوع را خود صدا زننده متد به عهده بگیره:-) یا اگه موضوع critical هست با if چک کنم.
فرض کنید ۳-۴ تا اعتبارسنجی مختلف داشته باشیم. به نظرم ایجاد مثلا ۴تا کلاس و بعد هم تو در تو ایجاد کردن آنان جالب نیست.
شبیهش را در کتابخانههای در دسترس و مشهور نیز تقریبا ندیدهام.
سلام.
بله موافق هستم که حکم کلی نمیتوان داد و راه حل جامعی وجود ندارد. میتوان برای اعتبارسنجی رویکردهای مختلفی داشت که هرکدام مزایا و معایبی دارند و در جایگاههای مختلف میتوانند مورد استفاده قرار گیرند. مثل در نظر گرفتن آنها به عنوان cross-cutting concern و استفاده از aspect-oriented programming یا اعتبارسنجی در همان تابع اصلی یا مشابه چیزی که اینجا توضیح داده شد استفاده از الگوی decorator
این الگو هم مثل همه الگوها مزایا و معایبی دارد که باید در جایی استفاده شود که مزایایی که برای آن برشمرده شد (مثل نیاز به استفاده مجدد از تابع هسته یا نیاز به فعال و غیرفعال کردن اعتبارسنجها) نسبت به معایب آن ارزش داشته باشد.
ممنون از مطلب مفیدی که مطرح کردید. به نظر میاد راه حل های بهتری هم برای ساده نگهداشتن کد ها و تمرکز روی مسئولیت اصلی هر تابع وجود داشته باشه. مثل استفاده از روش های AOP.
که می تونه استفاده از سرویس ها رو ساده تر و منطقی تر بکنه. مثلا دیگه لازم نیست استفاده کننده از کلاس Report مراقب باشه حتما decorate class های مختلف ایجاد کنه و بهم پاس بده.
ممنون از توجهتان. ما همواره از راهحلهای بهتر استقبال میکنیم 🙂
اگر آن را با توضیح بیشتری نوشته و برای ما ارسال کنید در اسرع وقت با نام خودتان منتشر میکنیم.
نشانی انجمن: info@javacup.ir