1649 واژه
8 دقیقه
کدهای شیشه‌ای: شفافیت اجرا

دفترچه خاطرات کدها#

یکی از عادتهای خوبی که یک برنامه نویس میتونه داشته باشه؛ اینه که کدهایی بنویسه که خودشون هم اهل نوشتن باشند. یعنی نرم‌افزار print statement های زیادی داشته باشه و اونارو در یک فایل ذخیره کنه. این باعث میشه رفع عیب کد به مراتب آسون‌تر باشه؛ چون خود کد در حین اجرا دفترچه خاطرات مینویسه و داستان اتفاقی که افتاده رو برای شما تعریف میکنه.

البته که این یه کشف جدید نیست؛ بلکه یه استاندارد شناخته شدست و حتی ماژولهای متنوعی برای این کار وجود دارن که به اسم logger شناخته میشن و مفاهیمی مثل log level و tag و logcat و… برای برنامه نویسها آشنا هستن.

اما آشنایی به معنی این نیست که اهمیت موضوع رو درک کرده باشیم؛ و درک اهمیت موضوع هم لزوما باعث ساخته شدن یه عادت مفید و به کار بستن دانشمون نخواهد شد. همچنین ممکنه بدون درک اهمیت موضوع، عادت کرده باشیم که کار درست رو انجام بدیم. که بسیار بهتر از اینه که موضوع برای ما تکراری و بدیهی بنظر بیاد؛ اما در عمل ازش دور باشیم و چوبش رو بخوریم. مهم اینه که آیا عادت داریم کار درست رو انجام بدیم یا خیر. پس اگه عادت به داشتن یه سیستم گزارش درست و حسابی توی نرم افزارهاتون ندارید؛ این نوشته برای شماست.

console.error("Task failed successfully!")
// a documented failure is a successful failure!

ارائه نسخه جدید برای دیباگ#

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

اگر شما کارتون رو اصولی انجام داده باشید؛ نیاز نمیشه حدس بزنید مشکل از کجاست، که بعد یه نسخه جدید از نرم‌افزار رو برای کاربرتون بفرستید که قراره بازهم مشکل دار باشه، اما اینبار احتمالا بهتون بگه که دردش چیه!

درواقع نیاز به دادن یه نسخه جدید به کاربر صرفا برای اینکه بفهمیم چه اتفاقی داره در اپلیکیشن ما میفته؛ نشان دهنده یک اشتباه حرفه‌ای هست. از ابتدا باید ماژول عیب یابی و گزارش در سیستم ما موجود باشه و نسخه جدید باید تلاشی برای رفع مسئله باشه؛ نه تلاشی برای تشخیص مسئله

طبیعتا درصورتی که حدس اولیه شما درست نباشه؛ این پروسه ارسال نسخه جدید رو باید چندبار تکرار کنید تا اینکه بالاخره باگ پیدا بشه. همچنین وقتی کد بکند شما رفته روی پروداکشن (یعنی درحال استفاده توسط کاربران هست) نیاز نمیشه کامیتهای عجیب و غریبی مثل “added console log” ایجاد کنید و پوش بزنید؛ که کل سابقه ریپوی شما رو تحت تاثیر زشتی خودش قرار بده. قطعا خود من مرتکب این اشتباهات شدم؛ اون هم نه یکبار بلکه صدبار، تا اینکه یاد گرفتم از اول، به نرم افزارم یاد بدم که دفترچه خاطرات بنویسه و این گزارشهارو در دسترس من قرار بده. درواقع اولین کاری که توی یه پروژه جدید باید انجام داد همینه که روشی برای مرور اتفاقاتی که در حین اجرای نرم‌افزار افتاده ایجاد کنیم. چند دقیقه بیشتر هم زمان نمیبره؛ اما باعث میشه ساعتها زمان در آینده ذخیره بشن.

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

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

چنین گفت امین:

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

اطلاعات بستر و ورودیها#

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

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

مورد دیگه‌ای که باید توی گزارشهامون بیاریم؛ درخواستهایی هست که به سایر api ها ارسال میشن. پاسخ این apiها ممکنه با چیزی که انتظارش رو داریم متفاوت باشه؛ و بهتره که محتوای ارسالی و دریافتی خودمون رو گزارش کنیم.

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

خطاهای پیشبنی نشده#

معمولا ما انتظار برخی خطاها رو داریم و براشون آمادهایم. اما موضوع شاید مهم‌تر برخورد با Uncaught Exceptionها هست. یعنی خطاهایی که در نرم‌افزار ما پیشبینی نشدن؛ و پیش اومدن اونها باعث توقف کامل اجرای کدها میشه. مثلا تو اپلیکیشنهای اندروید در این حالت پنجره App has stopped working یا App keeps stopping نمایش داده میشن. درصورتی که ما آمادگی این خطاهارو نداشته باشیم؛ مجبور هستیم خودمون روی امولاتور یا گوشی شخصی اپ رو نصب کنیم و عین مراحلی که کاربر انجام میده رو ببینیم؛ و با بررسی logcat بالاخره متوجه بشیم قضیه از چه قرار بوده. این هم فقط در شرایطی ممکنه که شما دسترسی مستقیم به کاربر داری و میتونی ازش بپرسی که چه مراحلی رو انجام داده. ممکنه یکی از کاربراتون بیاد توی نظرات اپلیکیشن بنویسه: باز نمیشه! یا کار نمیکنه! و منظورش همین crash کردن نرم افزارتون هست اما شما نمیدونید کجا و در چه شرایطی این مشکل پیش اومده.

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

Ereza
/
CustomActivityOnCrash
Waiting for api.github.com...
00K
0K
0K
Waiting...

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

public static String getExeptionStackTraceAsString(Throwable t{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
return sw.toString();
}

درصورتی که از Obfuscation استفاده میکنید ممکن هست اطلاعات line code حفظ نشن و اسم کلاسها و متغیرها در stacktrace با سورس کد متفاوت باشن. در این حالت باید از preserveLineNumber و فایلهای mapping استفاده کنید تا بتونید موقعیت خطا در سورس کد رو پیدا کنید.

همچنین مثلا توی NodeJs میتونیم برای گزارش کردن خطاهای پیشبینی نشده کدی شبیه به این بنویسیم:

process.on('uncaughtException', (err) => {
// Perform synchronous logging before exiting with a non-zero code.
process.exit(1);
});

این باعث میشه که پردازش ما درصورت توقف، علت ماجرا رو در گزارشها بیاره.

مرتب کردن گزارشها#

از اونجایی که هر اتفاقی در نرم افزارمون باید مستند بشه؛ مشخصه که فایل گزارش خیلی طولانی و آشفته خواهد بود؛ برای همین باید از tag ها استفاده کنیم تا بتونیم از بین همه گزارشها، اونایی که به هم مرتبط هستن رو پیدا کنیم.

در نرم افزارهایی که چندین thread دارن هم، بهتره شماره thread رو به tag اضافه کنیم. در این حالت میتونیم گزارشهارو به یکی از thread ها که برای ما جالب توجهه محدود کنیم.

علاوه بر tag و thread بهتره که verbosity level رو هم مشخص کنیم. معمولا یه عدد هست که نشون میده که هر خط از گزارش ما تا چه حد مفصل و جزئی هست؛ و با توجه به اون در آینده میتونیم گزارشهارو براساس میزان جزئی بودنشون فیلتر کنیم. این به کاهش خطوط گزارش کمک میکنه و باعث میشه بتونیم به جای شروع از خوندن هزاران خط شامل گزارش‌ها ریز و درشت؛ اول از چند خط خلاصه وار شروع کنیم و بعد وارد جزئیاتش بشیم. مثلا در ابتدا تنظیم کنیم که گزارشهایی که verbosity کمتر از 3 دارن رو نشون بده؛ و با دیدن یک مورد غیرعادی در اون مرحله، توی قسمت خاصی که به نظر مشکلدار هست جزئی‌تر بشیم و اطلاعات مفصلتری رو ببینیم.

حریم خصوصی و گزارش‌های دیباگ#

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

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

قابلیت غیرفعال کردن و حذف کردن#

کار خوبی هست که امکان حذف کردن گزارشهارو به کاربر بدیم. درسته که فایلهای متنی فضای کمی اشغال میکنن اما همین میتونه برای برخی کاربران مشکل ساز باشه.

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