از اولین خطوط کدهایی که نوشتم تا به الان همیشه این سوال برام مطرح بوده که چطور باید بفهمم نرم افزاری که ساختم خوبه یا نه. و اینکه آیا من برنامه نویس خوبی هستم یا نه. در طی این هفت سالی که به کد زدن مشغول بودم؛ معیارهایی رو کشف کردم که تاحدودی میتونن جواب این سوال من رو بدن.
زمانی فکر میکردم برای اینکه برنامه نویس خوبی باشیم باید ذهن قدرتمندی داشته باشیم. باید وقت بذاریم و مبانی کامپیوتر رو یاد بگیریم و عمیقا درک کنیم. درواقع باهوش بودن و آشنایی عمیق با کامپیوتر رو معادل برنامه نویس خوب بودن میدونستم. اما من در اشتباه بودم.
مدت زمانی که من با کامپیوترها گذروندم از مدت زمانی که با آدمها بودم بیشتره. اما کامپیوتر به من یاد داد که شناختن خودم به عنوان یک انسان، مهمتر از شناختن کامپیوتره. برنامهنویس یا مهندس خوب لزوما کسی نیست که تواناتر از دیگر انسانها باشه؛ بلکه کسیه که بدونه به عنوان یک انسان چه محدودیتهایی داره؛ و بلد باشه اونارو مدیریت کنه. سازگار شدن با محدودیتهامون به عنوان یک انسان، مهمتر از داشتن برتری نسبت به سایر انسانهاست. مطمئنم این مفهوم رو میشد با عبارتی گویاتر انتقال داد اما فعلا جمله بهتری پیدا نکردم.
در حوزه برنامه نویسی، شاید مهمترین اصل اینه که کار رو با پذیرش و درنظر گرفتن محدودیتهای انسانی جلو ببریم. یک انسان حوصله، حافظه و توانایی حل مسئله محدودی داره؛ و هنر اینه که نرمافزار ما برای ساخته شدن، مورد استفاده قرار گرفتن و نگهداری و تغییر، از محدوده حوصله و توان انسان خارج نشه.
همه برنامهنویسها شاید زمانی “منابع محدود” یک کامپیوتر رو هنگام نوشتن کدهاشون درنظر گرفتن. اما آیا همینقدر به محدودیت ذهن خودمون و کاربرمون دقت میکنیم؟ ما نرمافزار رو نه برای پردازندهها بلکه برای انسانها میسازیم. ممکنه کدهایی نوشته باشیم که کمترین میزان RAM و CPU cycle رو استفاده میکنن. اما تا زمانی که محدودیت رم و پردازنده خودمون در درک کردن و تغییر دادن اون کد رو درنظر نگرفته باشیم؛ در اشتباهیم. تا زمانی که کاربران ما نمیتونن نرمافزارمون رو بفهمن، کارمون درست نبوده.
پس نرمافزار باید در چهارچوب منابع محدود برنامهنویس و کاربر ساخته بشه. و این مهمترین اصل در مهندسیه؛ توجه به مدیریت منابع.
از نظر من یه نرمافزار باید ویژگیهای زیر رو داشته باشه تا بتونیم بگیم عاقلانه و براساس آگاهی درست و پذیرش محدودیتها ساخته شده: بدیهیه که نرمافزار خوب، باید درست عمل کنه. و تنها راه مطمئن شدن از اینکه چیزی درست عمل میکنه؛ تست کردنشه. بنظر میاد دنیای ما طوری ساخته شده که همیشه چک کردن درستی یک راه حل، آسونتر از پیدا کردن اون راه حله. کافیه به سودوکو یا جدول کلمات فکر کنیم؛ یا پاسخ یک معادله درجه چندم. چک کردن اینکه دوست شما یه جدول سودوکو رو درست حل کرده یا نه شاید کمتر از یک دقیقه زمان ببره؛ اما حل کردن همون سودوکو ممکنه ساعتها تلاش لازم داشته باشه و با دهها اشتباه همراه بشه. از طرفی گاهی اوقات با اینکه میتونیم نادرستی یک راه حل رو متوجه بشیم؛ پاسخ درست رو هم نمیتونیم پیدا کنیم. فهمیدن اینکه چیزی اشتباه کار میکنه لزوما به این معنی نیست که بلدیم چطوری درستش کنیم. برای همینه که تعمیرگاهها وجود دارن. مطابق مثال قبلی، یه کودک دبستانی میتونه متوجه بشه شما در حل سودوکو اشتباه کردید؛ اما شاید قادر نباشه حتی با ساعتها تلاش، از پس اون سودوکو بربیاد. پس چک کردن، آسونتر و دردسترستر از حل کردنه. این در دنیا به عنوان مسائل P و NP شناخته شده که میتونید دربارش بخونید. اما این فرآیند چک کردن با اینکه از پیدا کردن راه حل آسونتره؛ ممکنه اونقدرها هم آسون نباشه. مخصوصا وقتی که به صورت تکراری باید انجام بشه. همینجاست که ما به مشکل برمیخوریم؛ چک کردن صحت عملکرد یه نرمافزار بزرگ و پیچیده میتونه وقتگیر و حوصله سربر باشه. ما نمیتونیم با هر مرحله تغییر سورس کد، دوباره کل امکانات برنامه رو دستی تست کنیم و از عملکرد درستشون مطمئن بشیم. روال معمول این هست که وقتی قابلیت جدیدی به سیستم اضافه میکنیم؛ عملکرد اونو آزمایش میکنیم و اگه درست عمل کنه میریم سراغ امکانات بعدی و بعدی. کمتر پیش میاد با اضافه شدن هر تغییر جدید، کل نرمافزار رو از ابتدا تا انتها دوباره چک کنیم. همین باعث میشه به مرور زمان باگهایی در نرمافزارمون شکل بگیرن که از وجودشون بیاطلاعیم؛ چون نرمافزار بزرگتر شده و زحمت تست کردن همه قسمتهاش به قدری زیاد شده که دیگه وقت یا حوصلش رو نداریم و این کاربران ما هستن که دارن نرمافزار مارو تست میکنن. ایده اصلی تست پذیری اینجاست. وقتی صحتسنجی از حل مسئله آسونتره؛ چرا اول کدهایی ننویسیم که نرمافزار اصلی رو بهصورت خودکار تست کنن؟ چرا به عنوان یک برنامهنویس که کارش سپردن وظایف به کامپیوتره، باید چیزی که خودمون ساختیم رو دستی تست کنیم؟ آیا نمیتونیم همون مراحل چک کردن دستی رو بنویسیم و اجراش رو به کامپیوتر بسپاریم؟ فرض کنیم شما میخواید کدی بنویسید که تعداد واژگان مترادف درون یک متن رو بشماره. قبل از اینکه فکر کنیم این کار چطور قابل انجام هست؛ فرض میکنیم چنین نرم افزاری وجود داره و وظیفه ما اینه که ببینیم آیا درست کار میکنه یا خیر. قاعدتا باید متونی رو به عنوان ورودی به اون نرمافزار بدیم؛ و ببینیم که آیا خروجی اون با تعداد کلمات مترادف متن برابر هست یا نه. حالا باید همین کار رو به کامپیوتر بسپاریم. پس اول یک سری متن آماده میکنیم و تعداد کلمات مترادفشون رو میشماریم. سپس کدی مینویسیم که این متنهارو به نرمافزار اصلی میده؛ و چک میکنه که آیا خروجی اون با چیزی که ما انتظارش رو داشتیم برابر هست یا خیر. شما قرار بود این کارو به هرحال دستی انجام بدید؛ یعنی بعد از ساختن اون نرمافزار، یه متنی رو به عنوان ورودی بهش میدادید و چک میکردید آیا تعداد کلمات مترادف رو درست شمرده یا نه. ولی الان این فرآیند اتوماتیک شده و تستهای اتوماتیک به سرعت اجرا میشن و تکراری بودن کارشون، حوصلشون رو سر نمیبره. اگه برای همه ویژگیهای نرمافزارتون تست اتوماتیک بنویسید؛ درنهایت زمان زیادی رو ذخیره میکنید و بعد از هر جراحی شدیدی در کدها میتونید به راحتی متوجه بشید که آیا جایی مشکلی پیش اومده یا خیر. کافیه تستهای اتوماتیک رو اجرا کنید و کل نرمافزار اسکن و بررسی میشه. ساختن نرمافزار تست پذیر، کاری نیست که به یکباره شروع به انجامش کنیم و یک شبه هم توش حرفهای بشیم. بلکه مهارتیه که باید در طول زمان و با بررسی اشتباهات یاد گرفته بشه. رعایت اصول تست پذیری همچنین باعث میشه نرمافزار شما معماری مناسبتری داشته باشه. چون نیازه که هر قسمت از برنامه رو در انزوا تست کنید؛ کدهاتون طوری نوشته میشن که قسمتهای مختلف از همدیگه جدا باشن. نوشتن تست قبل از کد اصلی در دنیا به Test driven development معروف شده و اگه تا الان دربارش نمیدونستید؛ توصیه میکنم ساده از کنارش نگذرید. کدهای json parser و date converter من با همین روش نوشته شدن. بالا گفتم که نرمافزار خوب باید بهصورت اتوماتیک صحت عملکرد خودش رو تست کنه. این باعث میشه خطاهای نرمافزار به سرعت تشخیص داده بشن. ممکن هست که برخی خطاها هم از طرف کاربران گزارش بشن. درمرحله بعدی، نرمافزار باید طوری ساخته شده باشه که ریشه یابی و رفع این خطاها هم آسون باشه. یعنی دارای سورس کد خوانا و گزارش خطای شفاف باشه. دیباگ پذیری یعنی پذیرش اینکه نرمافزار ما در فرآیند توسعه دچار باگ خواهد شد. ما نمیتونیم بدون زمین خوردن، راه رفتن رو یاد بگیریم. بنابراین نرمافزار ما باید برای زمین خوردن آماده باشه. نمیگم باگها چیز خوبی هستن؛ بلکه میگم باگها طبیعی هستن، این غافلگیر شدن توسط باگهاست که چیز خوبی نیست. ما باید از ابتدا انتظار باگهارو داشته و براشون آماده باشیم. قبلا در این مورد نوشتم و بازهم شاید خواهم نوشت. نوشته پیشین من درباره شفافیت در اجرا رو اینجا بخونید. بنظر من، وقتی ما انسانها تو عملیاتهای منطقی ساده با چند متغیر محدود مثل ضرب و تقسیم هم لنگ می زنیم؛ پس قطعا در ساخت نرم افزارهای پیچیده متشکل از هزاران متغیر و شرط و حلقه اشتباه خواهیم کرد و این اشتباهات اجتناب ناپذیرند. باهوش بودن انسان حالت انفجاری و دفعی نداره. بلکه حالت تدریجی و قدم به قدم داره. انسان طوری بار نیومده که در طی یک تلاش بتونه مسائل رو حل کنه؛ حتی اینطور نیست که در دفعه اول قطعا متوجه صورت مسئله بشه. بلکه مجبور هست چندین و چندبار تلاش کنه، تدریجی پیش بره، مسئله رو به واحدهای کوچیکتر بشکنه روشهای مختلف رو امتحان کنه بازخورد بگیره و کمکم شاید از پس مسئله بربیاد؛ و البته در این حین هزاربار دور خودش بچرخه و اشتباه کنه و به زمین و زمان لعنت بفرسته. انسانها توانایی ذهنی یکسانی ندارن و شاید ذهن شما از ذهن دیگران سریعتر باشه؛ اما مسائل پیچیده، همیشه از ذهن همه ما بزرگتر خواهند بود. اشتباه کردن در طی تلاش برای حل این مسائل، اجتناب ناپذیره. جالبه که ما این رو درباره کارهای فیزیکی راحت درک میکنیم. هیچ انسانی تلاش نمیکنه یه دیوار رو یکدفعهای بسازه. هرچقدر هم قدرت بدنی زیادی داشته باشیم؛ نمیتونیم یه دیوار کامل رو جابه جا کنیم. روش منطقی اینه که بلوک به بلوک، یعنی در حد اون توانایی محدود خودمون پیش بریم تا درنهایت دیوار ساخته بشه. شاید کسی بتونه بلوکهایی سنگینتر و بزرگتر از دیگران جابهجا کنه اما هیچکس نمیتونه از پس کل دیوار بربیاد. و شاید شما تونسته باشید مسائل کوچیک بیشتری رو سریعتر حل کنید؛ اما به یک باره از پس نرمافزارهای پیچیده برنخواهید اومد. بنظر من هرکسی که با مهندسی و حل مسئله درگیره؛ با مفهوم هوش و توانایی ذهنی متغیر انسانها هم درگیر بوده. و چه بهتر که یک بار برای همیشه، این موضوع رو درک کنیم و کنار بگذاریم. حتی اگه به قیمت طولانی شدن این متن تموم بشه. انسانها قطعا توانایی ذهنی متفاوتی دارن. برخی از آدمها ذاتا باهوشتر از دیگران هستن و نپذیرفتن این موضوع انکار واقعیتهاست. تلخ بودن این موضوع هم به معنی واقعیت نداشتنش نیست. همونطور که هرکسی برای والیبال بازی کردن، برای کشتی گرفتن یا ژیمناستیک کار کردن ساخته نشده؛ و همونطور که هرکسی به یک اندازه از نعمت زیبایی و جذابیت برخوردار نیست؛ هرکسی هم از توانایی ذهنی بالا برخوردار نیست و این نعمت به صورت نابرابر بین انسانها پخش شده. اما توانایی ذهنی بالا، چیزی نیست که بتونیم بهش اتکا کنیم تا بیاد و مارو نجات بده؛ و مارو از اشتباه کردن مصون نخواهد کرد. و قسمت مهم ماجرا همینه؛ شما شاید باهوش باشید؛ اما همیشه برحق نخواهید بود. اشتباه خواهید کرد و بدجور هم اشتباه خواهید کرد؛ در طی همین اشتباههاست که کمکم یاد میگیریم چطوری از پس مسئله بربیایم. کسی که اشتباهات بیشتری دیده و از اونها درس گرفته، معمولا از کسی که توان ذهنی بالاتری داره، عاقلانهتر رفتار خواهد کرد. بنابراین ما باید تحمل مواجه شدن با اشتباهات خودمون رو داشته باشیم و سریع ناامید نشیم. برنامهنویسی یعنی داشتن تحمل اینکه موجودی به نام کامپیوتر، چندین وعده در روز به شما یادآوری کنه چقدر به عنوان یک انسان، در تفکر منطقی ضعیف هستید و اشتباه میکنید. اشتباهاتی مسخره و غیرقابل توجیه. شما در عین اینکه میدونید چقدر توانا هستید؛ باید با اشتباه کردن هم کنار بیاید و جنبه پذیرش اونو داشته باشید. نرمافزار شما باید برای اشتباهات شما آماده باشه. هر برنامهنویس تازهکاری باید بدونه اینکه در اولین تلاش نمیتونه از پس مسائل پیچیده کامپیوتری بربیاد؛ دلیل بر این نیست که شاید آدم باهوشی نیست. اینکه اون مسئله قبلا توسط اشخاص دیگهای حل شدن، به این معنی نیست که اونها در اولین تلاش موفق شدن و قبلا در اون باتلاقها تقلا نکردن. هیچکسی نمیتونه بدون اشتباه کردن، برنامه نویسی یاد بگیره. ما انسانها ظرفیت خیلی محدودی در پردازش مسئله و پیدا کردن راه حل داریم؛ و باید مشکلات رو طوری بشکنیم که همه واحدهای کوچیکتر در محدوده این ظرفیت ما قرار بگیرن. زمانی که کد مینویسیم ما درواقع داریم یک دیوار میسازیم؛ و این دیوار رو باید قسمت به قسمت و بلوک به بلوک بسازیم؛ طوری که هر بلوک رو بشه به تنهایی درک و جابه جا کرد. برنامه نویس تازهکار به پیچیده بودن کدهاش افتخار میکنه؛ چون فکر میکنه لابد چه مغز خفنی داشته که تونسته کدی به اون تودرتویی بنویسه. اما او گریه خواهد کرد؛ وقتی که همون مغز قادر نخواهد بود چیزی که خودش ساخته رو بفهمه و تغییر بده. وقتی که از نو نوشتن کل سیستم به نظر آسونتر از تعمیر کردنش خواهد بود. برنامه نویس بالغ، به سادگی کدهاش افتخار میکنه؛ چون نشون میده هم آگاه بوده و هم ماهر، و تونسته همه قسمتهای کد رو در سطحی نگه داره که برای ذهن محدود ما قابل فهم بمونن. افسانهها تصویری از “نوابغ” در ذهن ما ساختن که شاید با واقعیت متفاوت باشه. هرچقدر هم نابغه باشید بازهم قادر نخواهید بود مسائل مدرن و پیچیده رو در اولین تلاش حل کنید. مهارت حل مسئله، شامل اشتباه کردن هست. این طبیعت ماست و اشکالی هم نداره. باید با خودمون مهربون باشیم. مهم اینه که درک کنیم چطوری باید پیش بریم. این نوشته به تدریج ادامه پیدا خواهد کرد…تست پذیری: سنجش خودکار صحت عملکرد
یک json parser با رعایت تست پذیری
دیباگ پذیری: خوانایی کد و شفافیت اجرا
درباره برنامهنویسی و نبوغ
