جدیداً خیلی به این مسئله پی بردم که مدل جستجو کردن افراد مختلف برای حل مشکلات کامپیوتری و برنامهنویسی متفاوته. در ادامهی این پست اول سعی میکنیم با جستجو در گوگل یک مسئلهی فرضی رو حل بکنیم و بعدش مشکلات روشمون رو بررسی بکنیم.
به عنوان مثال فرض کنید که میخواید یک برنامه به زبان پایتون بنویسید که تعداد فولدرهای داخل یک آدرس خاص از سیستم عامل رو مانیتور بکنه. برای سادهتر شدن مسئله فرض کنید هر ۵ ثانیه یه بار این تعداد رو برامون چاب بکنه.
خب، شروع میکنیم به حل مسئله:
اگر کمی لینوکس بلد باشید میدونید که دستور ls
تا حدی این کار رو انجام میده. با این تفاوت که فقط فولدرها رو نشون نمیده و تمامی فایلها رو هم نشون میده. پس فقط کافیه که یه خورده پارامترهای ورودی دستور ls
رو عوض بکنیم و بعدش هم سعی کنیم که این دستور رو از توی پایتون اجرا بکنیم و خروجیش رو پردازش کنیم. بقیهی کار هم به کمک sleep
و print
انجام میشه.
اول تو گوگل جستجو میکنیم "ls only show directories" یا یه همچین چیزی. نتیجهی اول جستجو از stackoverflow هستش و بسته به این که چقدر مطمئن هستیم توی اولین لینک جوابمون پیدا بشه دو سه تا لینک اول رو باز میکنیم و میریم توی قسمت جوابها (بدون دیدن سوال). چشممون به کامند /* ls -d
میخوره که به نظر چیز معقولی میاد. امتحانش میکنیم و میبینیم که کار میکنه.
قسمت اول مسئله حل شد. حالا باید این کامند رو توی محیط پایتون اجرا کنیم.
اینبار توی گوگل سرچ میکنیم "run shell commands in python" و دوباره مثل مرحلهی قبل چند جواب رو بررسی میکنیم. یک تکه کد مشابه کد پایین مشاهده میکنیم که به نظر منطقی و ساده میاد:
import os os.system('ls') 12
یه shell پایتون باز میکنیم و امتحانش میکنیم. مشکلی که بهش برمیخوریم اینه که خروجی این تابع 0
هستش و نمیتونیم خروجیش رو توی یه متغیری بریزیم. حالا کمی بیشتر جستجو میکنیم و کمی عبارت جستجو رو عوض میکنیم: "run shell and get output python"
اینبار هم خوشبختانه خیلی زود به جواب درست میرسیم و توی stackoverflow یه همچین کدی رو میبینیم
import subprocess p = subprocess.Popen("echo a b | rev", stdout=subprocess.PIPE, shell=True) print(p.communicate()) 123
توی خروجی کامند میبینیم که فولدرها با n\
جدا شدن. برای همین این قسمت از کار هم راحت میشه و بعد از کمی استرینگ بازی کدمون در نهایت این مدلی میشه:
import subprocess import time while True: out = subprocess.Popen('ls -d */', shell=True, stdout=subprocess.PIPE).communicate() out = out[0].decode() folders = out.split('\n') folders.pop() # last chracter is \n, so the last split is empty print(len(folders)) time.sleep(5) 12345678910
مسئله حل شد! اما چقدر تونستیم مسئله رو خوب حل بکنیم؟ چقدر راهحلمون تمیز و ایدهآل بود؟
بیایم فرض کنیم در مرحله اول جستجو میکردیم: "list directories python"
اون موقع در اولین مرحله میتونستیم به یه نتیجه کامل برسیم و کدمون در نهایت این شکلی بشه:
from os import listdir from os.path import isfile, join import time while True: folders = [f for f in listdir('.') if not isfile(f)] print(len(folders)) time.sleep(5) 12345678
این کد نسبت به کد قبلی خواناتر، سادهتر، و جامعتر هستش.
توی مغز ما چه اتفاقی میافته که سراغ راه حل اول میریم و نه دوم؟ چرا به طور نسبتاً ناخودآگاه یه مرحله مسئله رو تو ذهنمون شکوندیم و حل کردیم و بعدش به جستجو در گوگل پرداختیم؟
در ادامه چند تا نکته اصلی که خیلی وقتا توی این فرایند جستجو یادمون میره و حس میکنم به بهینهتر حل کردن مسئله، چه از نظر زمانی و چه از نظر کیفیت خروجی، کمکمون میکنه رو بررسی میکنیم.
به نظرم یه اتفاق خیلی رایج در روند مسئله حل کردنمون این هستش که سعی میکنیم از ابزارهای و چیزهایی که بلد هستیم استفاده بکنیم و یه جوری اینها رو به هم بچسبونیم. یک ضربالمثل خیلی معروف هستش که میگه
if all you have is a hammer, everything looks like a nail
توی این مثال ما اول نمیدونستیم که پایتون خودش ابزارها و کتابخونههای خیلی کاملی داره که میتونه کارهایی مشابه ls
رو برامون انجام بده، برای همین از چکش ls
استفاده کردیم. در واقع مسئله رو به دو قسمت استفاده از ls
و اجرای ls
در پایتون شکوندیم اما شاید اگر نمیدونستیم ls
چی هستش مستقیماً به راهحل بهترs میرسیدیم.
یه دوستی میگفت این رفتار خیلی در افراد المپیادی شایعتر هستش. چون که چندین سال در تلاش بودند که با دانش خودشون و بدون دسترسی به اینترنت مسائل برنامهنویسی رو حل بکنند و ممکنه توی حل مسائل صنعتی هم با همون رویکرد جلو برن.
خیلی وقتا ما مسئله رو درست برای خودمون نمیشکونیم و به دنبال راه حلهای بهینهای نمیریم. حتی شاید راه حل دوم هم اونقدر برای مسئلهی اصلیمون ایده آل نباشه چون ممکنه کسی که به ما این تسک رو داده مسئلهی اصلی رو درست نشکونده. خیلی مهم هستش که به دنبال حل مسئلهی درستی باشیم و این اهمیت وقتی مسئله بزرگتر و پیچیدهتر میشه چندین برابر میشه چون تغییر راه حل هزینهی خیلی بالایی داره. مثلاً دیتابیس PostgreSQL ویژگیهای خیلی زیاد و متنوعی داره و حتی عکس رو هم میتونه تو خودش ذخیره بکنه. اما آیا این کار درستی هستش که هر نیاز ذخیرهسازی اطلاعات رو با PostgreSQL برآورده بکنیم؟ بهتر نیست به جای how to store images in postgresql
جستجو بکنیمwhere to store uploaded images
؟
یا مثلاً جانگو یه فریمورک بسیار کامل و بالغ برای طراحی اپلیکیشن تحت وب هستش ولی آیا به درد طراحی یه وبسایت کاملاً استاتیک و ساده هم میخوره؟
قطعاً برای هممون پیش اومده که سعی کردیم یه مشکلی در کدمون رو حل بکنیم و بعد چند ساعت میبینیم ۱۰-۲۰ تا تب تو مرورگر بازه که چند تاش جستجوی گوگل و بیشترش stackoverflow هستش. خیلی از مسئلهی اصلیای که میخواستیم حل بکنیم فاصله گرفتیم و از چاله افتادیم تو چاه. نیرویی که باعث میشه این فرایند برامون خستهکننده و آزاردهنده نباشه حس پیشرفت داشتنه. مثلاً اول نصف مسئله حل میشه و توی نصفه دوم به مشکل میخوریم. بعدش نصف قسمت دوم حل میشه و تو ۱/۴ اش به مشکل میخوریم؛ همینطور میریم جلوتر و مسائل کوچیکتر و بیاهمیتتری رو حل میکنیم اما چون حلشون میکنیم، حس پیشرفت داریم و ادامه میدیم. چیزی که اینجور وقتا کمتر بهش توجه میکنیم سوالات مهمتری هستش که باید از خودمون بپرسیم:
این حس خوشبینی که «الان دیگه درست میشه» و «فقط ۵ دقیقه کار داره» تو برنامهنویسها خیلی زیاده و به نظرم باید سعی بکنیم کمی واقعگرایانه نگاه بکنیم. با خودمون بگیم «این تسک تخمینش ۳ ساعت بوده امّا من دو ساعت هستش که درگیر یه قسمت خیلی احمقانه شدم. بهتره که ولش کنم و برم سمت قسمتهای مهمتر و بعداً برگردم سراغش».
فهمیدن زمانی که باید به خودمون بگیم «رها کن» خیلی مهارت ارزشمندی هستش و میتونه نمودهای مختلف و مفیدی برامون داشته باشه.
وقتی که یه صفحه stackoverflow رو باز میکنیم به چه قسمتهاییش دقت میکنیم؟ در حد چند ثانیه صورت سوال رو میبینیم. بعدش اسکرول میکنیم تا به جواب برسیم. تعداد امتیازهای جواب و تیک Best Answer رو میبینیم و توی جواب به قسمتی که کد نوشته شده دقت میکنیم. اگر حس کردیم که جواب منطقی هستش کمی با دقت بیشتری میخونیمش و امتحانش میکنیم. یا مثلاً اگر جوابمون وسط یه tutorial پیدا شده باشه، وقتی صفحه باز شد فقط همون قسمتش رو میخونیم.
مشکل اصلی این جستجوها دقت نکردن به جزییات و context مطلب هستش. جزییاتی که در ظاهر بیاهمیت هستش ولی میتونه خیلی سریعتر ما رو به جواب برسونه. همین که مطمئن باشیم سوالی که توی stackoverflow پرسیده شده همون سوال ماست یا نه؛ خوندن کامنتهایی که روی سوال و جواب گذاشتند و کامل خوندن و فهمیدن راهحلها همشون میتونن در نهایت باعث سریعتر حل شدن مسئله بشن.
توی کامنت اولین جوابی که انتخاب کردیم نوشته شده بود که این جواب کاری که ما میخواستیم رو نمیکنه، ولی ما وقت نداشتیم که کامنتها رو بخونیم. حتی توی قسمت دوم جواب هم راهحل بعدیمون نوشته شده بود ولی بعد این که به مشکل خوردیم دیگه سراغ این جواب نیومدیم.
مثال دیگری که وجود داره خوندن API References ها هستش. عموماً توی این صفحات فقط توضیحات APIای که میخوایم ازش استفاده بکنیم رو میخونیم. اما شاید اگر بقیه APIهای موجود رو هم یه مروری بکنیم، بتونیم از اونهایی که بهینهتر هستند استفاده بکنیم یا بفهمیم که برای حل یه قسمت دیگه از مسئله هم چنین راهحلی وجود داره. برای همین هستش که مستندات خوب یه قسمت See Also داره که بهمون کمک میکنه توابع مرتبط با تابعی که داریم مطالعه میکنیم رو راحتتر پیدا کنیم. به عبارتی context مرتبطی رو در اختیار ما قرار میدن.
چیزهایی که الان در دنیای توسعه دهندگان خیلی بهش اهمیت داده میشه تکنولوژی و زیرساخت و .... هستش اما به نظرم practice های یک توسعهدهنده میتونه خیلی بیشتر از اینها در پیشرفتش نقش داشته باشه. حتی practice های سادهای مثل چگونه جستجو کردن.