مسابقه ابربازی و پیاده‌سازی یک کامپایلر ساده با Go

https://www.arvancloud.com/fa/abrbaziامروز در مسابقه #ابربازی شرکت کردم. مسابقه چالشی و قشنگی بود.دوست داشتم بعد از دو سه سال دوباره خودم رو در یک مسابقه جدی محک بزنم. به این خاطر شرکت کردم.قبلا دو بار در مسابقه فن‌آورد شرکت کرده بودم و در مسابقه اول زبان برنامه‌نویسی‌ام پایتون بود و دوم شده بودم.که کدهای مسابقه در گیت‌هاب موجود است.کدهای مسابقه امروز رو هم روی گیت‌هاب قرار دادم.الان دوست دارم که خودم رو در این مسابقه تحلیل کنم.انتخاب زبان برنامه‌نویسیاستفاده از Goدر این مسابقه از زبان‌برنامه‌نویسی Go استفاده کردم. زبان Go مثل پایتون ساده است مدیریت خطای کارائي داره و static type است. تجربه‌ام از مسابقات می‌گه که عموما لازم نیست که خیلی خوب کد بنویسید؛ گرچه من همیشه سعی می‌کنم به بهترین شکل کد بنویسم و از کدنویسی لذت ببرم. مفاهیم شی‌گرائي در Goبیائید یکی از سوالات رو به صورت مختصر بررسی کنیم. در این سوال می‌خواهیم یک برنامه‌ی کوچیک بنویسیم که خط‌های یک زبان مختصر رو parse و اجرا کنه.دستورات مثل زیر هستندنمونه ورودی سوال دومزبان Go به سادگی پایتونه و کد اضافه زیادی هم نمی‌شه توش نوشت. مثلا قطعه کد زیر رو ببینیم هر کدوم از خط‌های بالا رو می‌گیره و یک Statement ازشون می‌سازه.پارس کردن هر یک دستورات ورودی کد بالا رو می‌شه از چیزی هست هم بهتر نوشت، اما وقت مسابقه بیشتر از این اجازه نمی‌ده.اما اجازه بدید که یک گام این کد رو بهبود بدیم. می‌شه به راحتی از دست switch-case خلاص شیم، مشکلی که داره که اگر بخواهیم یک دستور جدید اضافه کنیم باید کدش رو دست بزنیم. یعنی کد Open–closed principle رو رعایت نکرده.مرحله۱: اعمال OCP و حذف switch- caseتعداد خط از کد قبل کمتر نشد، اما می‌شه با یک گام بهبود، به یک کد بهتر رسید. ببینید که چقدر ساده یک نوع جدید تعریف کردیم به نام Parser. باز اشکالی که در مورد کد قبل گفتم رو باز داره. هنوز نیاز داریم که کد درونی رو عوض کنیم.مرحله۲: اعمال OCP و حذف switch- caseنتیجه‌ی پایانی، ساده، مختصر و زیباست. مگه زیباتر از این هم می‌شه. زبان Go به راحتی هر چه ساده‌تر اجازه می‌ده یک type جدید تعریف کنیم و type ها هم overhead حافظه ندارند و می‌شه رو این type ها متد جدید (در اینجا متد Parse) تعریف کرد.این بحث رو همین‌جا ببندیم سراغ یک خصوصیت زیبای دیگه Go بریم و اون هم استفاده از interface هاست.استفاده از Interface به عنوان Contract در Goبه نظرم interface در Go همون چیزیه که در زبان‌های برنامه‌نویسی بهش قرارداد (contract) می‌گیم و خیلی خوب پیاده‌سازی شده.من فقط یک interface در برنامه داشتم.یک اینترفیس ساده با Goاین interface کارم رو جواب داد اما بی‌ایراد نیست چون واقعا دلیل نداره که همه دستورات مقدار داشته باشند. بگذریم (بهبود دم دستی اینه که متد Eval چیزی بر نگردونه، اما همین بهبود روی خیلی از قسمت‌های دیگه تاثیر می‌ذاره که فعلا به این تاثیر نیاز ندارم)همین interface ساده یک حرف ساده رو هم می‌زنه هر چیزی که بخواد یک Statement باشه باید یک متد به نام Eval داشته باشه و یک int64 برگردونهمثلا یک عدد ساده یک Statement است و به صورت ضمنی interface مورد نظرمون رو پیاده‌سازی کرده.تعریف یک struct برای نگهدارییک دلیلی که Value رو به Eval پاس دادم این بود که بتونم باهاش مقدار متغییرها رو بدست بیارم. خیلی ساده به صورت زیر می‌شه یک متغییر رو تعریف کرد.تعریف یک struct برای نگهداری متغییرها (مثلا برای متغییر a)دستوری که مقدار متغییرها رو عوض می‌کنه دستور assign بود، که یک جمع ساده است (به همین خاطر حالت خیلی ساده در نظر گرفتمش)تعریف یک struct برای دستور به فرمت out = op1 + op2مدیریت خطا در Goیکی از زیبایی‌های Go مفهوم error handling اش است. که باز به نظرم به طراحی دلچسب‌تر برنامه خیلی کمک می‌کنه و عملا باعث می‌شه خطای احتمالی از زیر دستمون در نره.مثلا از کد بالاتر، قسمت زیر رو ببینیممدیرت خطا در Go به صورت سادهدر اینجا اگر ورودی دستوری باشه که هیچ کاریش نمی‌تونیم بکنیم panic می‌دیم. می‌شه از log.Fatalf هم استفاده کنم اما همین panic ساده برام کافی بود. یک نمونه‌ی دیگرش در parse کردن یک رشته و تبدیل اون به Number استفاده کردم. به نظرم با این نوع از مثال‌ها مشخص می‌شه که چقدر برنامه‌نویسی با Go خیال آدم رو راحت می‌کنه و آدم رو برنامه کنترل خیلی بهتری می‌تونه داشته باشه.نمونه‌ای از parse کردن یک رشته عددی و تبدیل آن به Numberکد می‌تونست به صورت زیر نوشته بشه و بعد خطا رو توی لایه بالاتر مدیریت کرد. اما لایه بالاتر هم عملا کاری نمی‌تونست انجام بده، پس همون panic رو زدم و بقیه رو هم راحت کردم.بهبود parse یک رشته‌ی عددیهمین مدیریت خطای ساده، جلوی یک سری اشتباهات رو از سمت برنامه‌نویس می‌گیره. چون من با یک پیش‌فرض‌هایی کد نوشتم که ممکنه اشتباه باشه. اما همون پیش‌فرض‌ها رو چک کردم. حالا اگر همین کد رو بر روی یک ورودی درست اجرا کنم و خطا بگیریم یعنی پیش‌فرضم در اونجائی که خطا داده اشتباه بوده و باید برم تصحیحش کنم. اما اگر چنین مکانیزمی نداشته باشم، باید خیلی وقت رو debug کردن بذارم و با سعی و خطا برنامه رو ببرم جلو.اما بریم سراغ بعد غیر فنی.نحوه‌ی پاسخ‌دهیاولین بار بود که Quera مسابقه می‌دادم، برام خیلی جالب بود. مسابقه پنچ تا سوال داشت.در این مسابقه چندتا ضعف داشتم.اولین ضعفی که داشتم در تحلیل دقیق و سریع سوالات بود که به نظرم رسید نمی‌تونم خیلی سریع سوالات رو تحلیل کنم، ۵ تا سوال بود هر سوال به صورت میانگین باید ۳۶ دقیقه زمان می‌برد.این زمان باید صرف چند تا کار می‌شد. خواندن و درک سوالتحلیل ورودی و خروجیپیدا کردن راه‌حل مناسبپیاده‌سازی نرم‌افزاریتست و اجراطبق قاعده اگر راه‌حل مناسب داشتیم مساله حل شده است اما در عمل اینطور نیست. جواب باید در فضا و زمان مناسب هم جواب بدهد. مثلا نباید بیشتر از ۲ ثانیه طول بکشه یا بیشتر از ۲۵۶ مگ رم مصرف کنه. اما فرض کنیم که برنامه‌مون عالیه. مشکل دیگر اینه که باید بشه ورودی‌هایی که برنامه را گیر می‌اندازه رو هم پیدا کنیم.من در قسمت ۲ موارد بالا، ضعف داشتم و این ضعف در جوابی که quera بر گردوند کاملا مشخص بود.نمونه خروجی ارسال شده از سمت کوئرایکی دیگر از مشکلاتم درک کردن سوال بود، وقتی سوال رو نشه درک کرد، نمی‌توان آن‌را حل کرد. مثلا یکی از سوال‌ها ۵ صفحه PDF بود و طولانی بودن صورت سوال، تاثیر منفی رو ذهنم گذاشت.اشکال دیگر، مدیریت استرس بود. در حل سوال دوم، زمان زیادی صرف شد. تا حدی مدیریتش کردم اما باز فشار ذهنی داشت.جمع‌بندیهدفم از مسابقه به چالش کشیدن خودم بود. آشنا شدن با نقاط ضعف و قوت خودم هدف دیگرم بود. از این جهت، از مسابقه‌ای که دادم راضی هستم.جا داره از سایت کوئرا بخاطر پلتفرم خوبشون و از ابرآروان به خاطر برگزاری مسابقه هم تشکر کنم.در پایان مشتاق شدم که در مسابقات دیگر هم شرکت کنم و تا کمک کنه که خودم رو بیشتر و بهتر محک بزنم.