












برنامه ریزی برای روزهای خطایابی - قسمت دوم
سخنران : مهندس یاسر ژیان
گرداوری و تدوین گزارش : مریم ثابت قدم اصفهان
نخستین همایش ملی بازی های ریانه ای - دانشگاه صنعتی شریف
حقوق این گزارش برای سایت انیمیشن دیتا محفوظ است
برنامه ریزی برای روزهای خطایابی - قسمت اول

رشته ها مبحث عظیمی در پروژه بازی محسوب می شوند.اگرچه توقع آن را نداریم ؛ اما چه از نظر کارایی (Performance ) و چه از نظر انعطاف پذیری (Flexibility ) کار را برایمان دشوار می سازند.
باور کنید که ASCII مرده و رشته های default کد شما هرگز نباید ASCII باشد . کاراکتر 8 bit دیگر واقعاً وجود ندارد.نباید تصور کنید که مسیرهای فایل شما ASCII هستند . باید بیاندیشید که رشته های بازی شما که می خواهید به کاربر نشان دهید ؛ ASCIIنیست. اگر چه اغلب ، زبان C++ دارای پشتیبانی کاملی از Decode است ؛ اما متاسفانه دیگر مورد استفاده قرار نمی گیرد. پس به Decode نیز فکر کنید . بعلاوه ، رشته ها برای چیزهای بسیار مختلفی نظیر اسم فایل ها و مسیرها استفاده می شوند.متن ( Text ) ی که کاربر وارد می کند ، رشته مجزایی محسوب می شود ؛ زیرا از Type جداگانه ای برخوردار است و باید جداگانه هندل شود.
اسم handle object ها یا Resource هایی که در اختیار دارید می تواند همه ASCII باشد. ممکن است برای performance و حافظه ؛ اسم های ASCII بگذارید و کاراکتر 8T را استفاده کنید؛ لذا این می تواند جداگانه هندل شود.پیشنهاد می کنم text file که می خواهید در String خیلی طولانی لود کنید ؛ دارای type جداگانه ای باشد. خیلی از رشته هایی که در کد خود استفاده می کنید تقریبا ً در طول عمر خود ثابت (Constant ) هستند و این فرصت مناسبی جهت بهینه سازی (Optimization ) در اختیار شما قرار می دهد.

سعی کنید از Library standard زبان خود مستقل باشید. این شاید توصیه عجیبی به نظر آید . حتی با وجود امکاناتی که این گزینه در اختیار شما قرار داده است ؛ سعی کنید که abstract نموده و یک لایه بر آن بکشید. اصولاً استاندارد های مختلف ، گاه رفتارهای متفاوتی دارند . در طول زمان ورژن های مختلف ، رفتارهایشان تغییر می کند. ولی مهم تر اینکه استاندارد ها به ندرت کار مد نظر شما را تحقق می بخشند. شاید در وهله اول به نظر آید که کاری که مد نظر شما است را انجام می دهد ؛ اما در نهایت در می یابید که انجام یک تابع (function ) دیگر برای آن دشوار است.
بطور مثال ، اگر برای کار کردن با فایل ها ؛ از ifStream استفاده می کنید، بعداً نمی توانید براحتی فایل های خود را فشرده کنید. پس حتماً کلاس فایل خود را بنویسید و حتماً به asynchronous I/O فکر کنید. زیرا قسمت های مختلف کد شما نباید برای هر قسمت دیگر منتظر بماند.
Directory handling را حتماً در دست خود بگیرید. سعی کنید از مسیرهای عادی سیستم عامل استفاده نکنید. بلکه تلاش کنید یک جور meta directory برای آن در نظر بگیرید. فرضاً فایل هایی که در بازی با آن ها کار می کنید ، شامل Log ، media ، Configuration ، User profile در ده دایرکتوری قرار دارند که شما را قادر می سازد تا بطور مستقل از یکدیگر آن ها را جابه جا کنیدم.
تولید و بکارگیری اعداد تصادفی (Random numbers, generation and handling ) از اهمیت شایانی در بازی برخوردارند. اصولاً اعداد تصادفی که استاندارد زبان برای شما ایجاد می کند ؛ خوب و مناسب نیست. زیرا باید deterministic باشد و این ضعف بلافاصله در بازی های شبکه قابل رؤیت است.
اکثر کتابخانه های گرافیک و PhysiX نیز یک Math برای شما فراهم می آورد . اصلاً خوب نیست که در کد خود بنویسید : سینوس. حتماً اینها را بعداً abstract کنید.اگر در آینده تصمیم بگیرید که به جای آنکه سینوس های خود را runtime حساب کنید ؛ از یک table local استفاده کنید ؛ دیگر نمی توانید تابع سینوس Library standard را جابه جا کنید ؛ زیرا برخی جاها ممکن است سینوس هیچ ربطی به runtime نداشته باشد.
البته در مورد time handling بطور جداگانه صحبت خواهم کرد. این مبحث بسیار مهمی است و اگر فکر می کنید که با مطالعه اندک در مورد آن همه چیز را فرا خواهید گرفت ؛ سخت در اشتباه هستید.

سخن در مورد خطا یابی (Debugging ) کد و امکاناتی که شما برای خود فراهم می آورید بسیار زیاد است. به این بیاندیشید کدی را که می نویسید ، هدفش اجراء نیست ؛ بلکه هدفش آن است که خوب دیباگ شود. فکر نکنید که باید سریع اجراء شده و خوب نتیجه دهد. مطمئن باشید که زمان بیشتری صرف اشکال زدایی کد خواهید کرد تا نوشتن آن. لذا اگر هنگام نوشتن کد ، 10 % زمان بیشتری بگذارید و امکاناتی را برای خود فراهم کنید تا راحت تر دیباگ شود ؛ مطمئن باشید که این زمان مازاد به شما کمک شایانی خواهد کرد.
به سیاست های ثبت وقایع (Logging ) و اعلان (assertion ) فکر کنید. سعی کنید تا می توانید context meta data گرد آورید. کد شما باید سریع دریابد که از کدام function ناشی می شود و چندمین بار است که از یک تابع ؛ فراخوان می شود و یا این تابع در کدام نخ (thread ) ، فراخوان شده است تا اگر خواستید Log تولید کنید ؛ بتوانید این اطلاعات را خیلی سریع و راحت بنویسید. یا اینکه همواره این اطلاعات در اختیار Log شما باشد.
Crash ها را به خوبی هندل کنید.زمانی که کد شما crash می کند ؛ لزوماً شما در debugger نیستید تا بتوانید کد خود را تست کنید ؛ بلکه باید از crash dump استفاده کنید. تمام اطلاعاتی که می توانید موقع crash جمع آوری کنید ؛ بسیار حائز اهمیت است و می تواند به شما کمک کند.

یک ایده مهم در ثبت وقایع (Log ) آن است که صدها هزار کد log تولید نکنید. زیرا یافتن اطلاعات مفید در میان کل اطلاعات بسیار دشوار است. بعلاوه کسی که این Log ها را همراه با error های غیر ضروری مشاهده می کند ؛ نسبت به تمامی خطاها بی تفاوت می شود. باید واقعاً تولید Log همراه با اطلاعات مفید باشد این اطلاعات در زمان های مختلف برای هر یک از افراد تیم فرق می کند. لازم است تا Log تان براحتی پیکر بندی ( Configure ) شود تا به صورت runtime مشخص گردد که چه چیز Log شده و چه چیز Log نشود وآیا تنها در یک ماژول خاص یا در قسمت فیزیکی این اتفاق رخ دهد.
یک ایده خوب دیگر آن است که هر چه می توانید اطلاعات جمع آوری کنید ؛ ولی تنها اطلاعات لازم را به کاربر نشان دهید. زیرا همانطور که گفتم وقتی noise زیاد می شود ؛ یافتن اطلاعات مفید در آن ، بسیار دشوار است. ولی این گرداوری اطلاعات در نهایت ممکن است برای کسی مفید واقع شود. پس تا می توانید اطلاعات زیادی ذخیره کنید ؛ ولی Log شما حاوی اطلاعات مفید و ضروری باشد.
این Log باید از runtime قابل کنترلی برخوردار باشد تا قسمت های مختلف آن را ON و OFF کرد و بتوان به سمت چندین خروجی مختلف ارسال نمود. و یا آن را بر حسب اطلاعات مختلف فیلتر کرد. در عین حال ، Logging باید سریع باشد. در غیر اینصورت بر فرض وقتی من که در حال نوشتن کدی هستم که rate frame برای تست من مهم است؛ Log ها را disable خواهم کرد.
همچنین باید استفاده ساده ای از آن داشته باشید . اگر قرار باشد برای هر اطلاعات کوچکی ، ده خط کد بنویسم ؛ سعی می کنم تنها در جاهایی که لازم است از آن استفاده کنم.
از همان روزهای نخست ، ثبت وقایع را مورد استفاده قرار دهید. اگر در آخر پروژه معلوم شود که آن کدی که در یک ماه اول پروژه نوشتید و هنوز آن زمان Library logging شما آماده نبوده و اصلاً حاوی Log نیست و اتفاقاتی که در آنجا رخ می دهد ؛ ثبت نمی شود ؛ برایتان مشکل ساز خواهد بود.

احتمالاً با مبحث اعلان ( assertion ) آشنا هستید. بر فرض اگر ادعا می کنید که a=0 است ؛ اگر حقیقتاً a=0 باشد؛ runtime از روی آن رد شده و هیچ اتفاقی نمی افتد. ولی اگر اینچنین نباشد ؛ اجرای کد را در همان جا متوقف کرده و برنامه را می بندد و می گوید آن خطی که نوشتید a=0 اشتباه است.
این اعلان ها ، معمولاً در کد بهینه سازی شده (optimized ) که کمپایلر به طور اتوماتیک آن ها را از کد استخراج می کند دیگر اتفاق نمی افتد. برای تست کردن شرایطی که واقعاً در حال اتفاق است ؛ این اعلان کد نهایی هرگز هزینه ای برای شما در بر ندارد. هر گز خطای عادی که فرضاً توسط کاربر رخ می دهد را توسط این اعلان ها چک نکنید. زیرا ممکن است کاربر سهواً یک رشته را اشتباهاً وارد کند و واقعاً یک فایلی وجود نداشته باشد.
این اعلان ها ، معمولاً در کد بهینه سازی شده (optimized ) که کمپایلر به طور اتوماتیک آن ها را از کد استخراج می کند دیگر اتفاق نمی افتد. برای تست کردن شرایطی که واقعاً در حال اتفاق است ؛ این اعلان کد نهایی هرگز هزینه ای برای شما در بر ندارد. هر گز خطای عادی که فرضاً توسط کاربر رخ می دهد را توسط این اعلان ها چک نکنید. زیرا ممکن است کاربر سهواً یک رشته را اشتباهاً وارد کند و واقعاً یک فایلی وجود نداشته باشد.
سعی کنید از اعلان استاندارد استفاده نکنید و یک assertion را خودتان بنویسید. استاندارد در کد بهینه سازی شده از آن خارج شده و دیگر وجود خارجی ندارد. اگر زمانی در کد بهینه (optimized ) هم بخواهید این اعلان ها را چک کنید ؛ اطلاعاتی که اعلان عادی در اختیار شما قرار می دهد ؛ خیلی کم است. و در عین حال بدون در نظر گرفتن تمایل شما به ادامه اجرای برنامه تان ؛ آن را می بندد.

اگرچه گرداوری نمونه اطلاعات از Context کدی که اجراء می شود با محدودیتی همراه نیست ؛ اما باید یک تعادل میان Performance کد و حافظه ای که طی آن، دیتا به جمع آوری اطلاعات پرداخته ؛ برقرار نمایید. حداقل اطلاعاتی که می توانید در مورد Context metadata جمع آوری کنید ؛ این است که در این لحظه در کدام function قرار دارد و یا در چه فایل و شماره خطی موجود است و اینکه از چه تابعی آن را جمع آوری کرده اید و اگر بگویید که در مرحله رندر در چه فریمی قرار دارید ؛ بسیار خوب است. این اطلاعات به ویژه در ثبت وقایع ، crash dump ، profiling مورد استفاده قرار می گیرند.

چیزی که باید در مورد crash و از کار ماندن (fail ) بدانید آن است که اگر کد شما مجبور به crash باشد؛ هر چه زودتر اینکار را انجام دهید بهتر است. اگر Pointer از functionبیرون می آید ؛ آن pointer را در همان جا چک و assert کنید. اگرnull بود در همان جا ( یک خط بعد از جایی که اشاره گر تنظیم شده است ) crash کنید ؛ نه اینکه در function داخلی آنقدر جلو بروید که بالاخره یافتن جایگاه آن اشاره گر دشوار شود.
هرچه که می توانید در هنگام crash به جمع آوری اطلاعات بپردازید. دیگر ، آن لحظه performance مهم نیست. به جمع آوری اطلاعات بپردازد و در یک فایل نگاه دارید و فکر کنید که اطلاعات حاصل از crash را می توانید بعد از آن که کد شما release شد ؛ حتی برای تیم خود ارسال نمایید. بطور مثال ، فایرفاکس این اطلاعات را به طور اتوماتیک برای سرورهای Mozilla جهت بررسی crash ارسال می کند.
اطلاعات در یک جعبه سیاه (Black box ) مانندی که نگاه دارنده اطلاعات محسوب می شود ؛ اضافه می گردد. و فقط در زمانی که crash می کنید ؛ این اطلاعات قابل استفاده است.

یک کنسول و یک menu system در همه بازی ها وجود دارد که فرمان ها (commands ) شان را در آن می نویسند و یا موارد مختلف را تست می کنند و یا در وسط بازی ، دشمن را می افزایند و یا دشمنی را حذف می کنند ؛ از دیوار عبور می کنند و موارد دیگری که تولید کنندگان بازی (developers ) می توانند آن را در فرایند قرار دهند. در این قسمت ، بسیار وقت بگذارید و برای آن ارزش قائل شوید. زیرا کمک بسیاری به شما می کند. بعضی از بازی ها دارای یک menu system بسیار پیچیده و عجیب هستند و برخی بازی ها نیز از یک menu system ساده و یا یک shell برخوردارند.

مبحث serialization ،_ بطور مثال اگر بتوانید یک آبجکت را روی دیسک بنویسید و سپس به همان آبجکت قبلی برگردانید،_ خیلی کاربردهای زیادی دارد و لذا از همان ابتدا به آن فکر کنید .کاربرد اصلی آن در save / load یا Network communication و به ویژه در debugging است. اگر من asset یک آبجکت را در Log بنویسم ،با یک کد serialization براحتی می توانم آن را تحقق بخشم. در این زمان حتماًً از دو فرمت binary , و human readable ( که شما را قادر به مشاهده و Edit آن می سازد ) استفاده کنید.

زمان را تحت کنترل خود بگیرید. هرگز بطور مستقیم از سیستم عامل زمان نگیرید. هر جا که خواستید کد را abstract کنید ؛ حتماً باید کنترل زمان خود را در اختیار بگیرید.
فرق است میان زمانی که کاربر در بازی می گذراند با زمانی که در منوها می گذراند و یا زمانی که در سیستم عامل می گذراند. حتماً میان آن ها تفاوت قائل شوید. مدیریت زمان با دقت بالا به ویژه در سیستم های multicore کار چندان ساده ای نیست.

سیستم هایی را در اختیار داشته باشید که بتوانید از بازی خود یک screen shot و review بگیرید. هر نوع اطلاعاتی که در بازی اتفاق می افتد را ذخیره کنید و در جایی دوباره آن را پخش کنید. این می تواند در اشکال زدایی شما را یاری داده و امکانات خطایابی (debugging ) را که هرگز تصور نمی کردید ؛ در اختیار شما قرار دهد .
© Copyright 2009 - 2010/ Animationdata.com & Partners
تاریخ آپلود : 18/11/2010











