۵ مهارت حل مسئله برای توسعه‌دهندگان نرم‌افزار

این متن ترجمه‌ایست از متن The 5 Problem-Solving Skills of Great Software Developers

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

زبان‌های برنامه‌نویسی اغلب بسیار ساده هستند، به‌خصوص زبان‌هایی که از زبان C الهام گرفته شده‌اند. زبان C تنها دارای ۳۲ کلمه‌ی کلیدی است که یاد گرفتن آنها بسیار ساده است:

کلمات کلیدی زبان C
کلمات کلیدی زبان C

همچنین ۱۴ دستور پیش‌پردازشی (Preprocessor Directive) دارد که به‌نظر نمی‌رسد یادگیری آنها چندان سخت باشد:

دستورات پیش‌پردازشی زبان C
دستورات پیش‌پردازشی زبان C

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

چگونه مانند یک برنامه‌نویس فکر کنیم

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

حالا می‌خواهم یک مسئله‌ی ساده مطرح کنم تا بر پایه‌ی آن ۵ مهارت را که یادگیری آنها ضروری است توضیح بدهم. مسئله این است: «چگونه برای ۴ نفر قهوه درست کنیم؟»

چهار نفر (الف، ب، پ و ت) درخواست قهوه دارند که هرکدام نوع خاصی را سفارش داده‌اند:
الف: قهوه تلخ
ب: قهوه با شیر
پ: قهوه با شیر و شکر
ت: قهوه با شکر

شما باید یک روش و یا الگوریتم پیدا کنید که قهوه‌ها در سریع‌ترین زمان و طبق سفارش افراد آماده شوند. هدف این است که راهی برای انجام این کار پیدا کنید که قهوه برای هر چهار نفر همزمان آماده شده و همچنین داغ به دست آنها برسد.

۵ مهارت حل مسئله

۱- اهداف بزرگ و پیچیده را به اهداف کوچکتر و ساده‌تر تقسیم کنید

هدف اصلی این مسئله آماده کردن قهوه برای چهار نفر طبق سفارش آنها است. با این حال شما نمی‌توانید «درست کردن قهوه» را بعنوان یک فعالیت درنظر بگیرید. اگر از هر فردی که تا به حال قهوه درست کرده است بپرسید، مراحلی را به شما می‌گوید که باید انجام دهید تا قهوه آماده شود. این مراحل باید ساده باشند تا بتوان آنها را به راحتی انجام داد.

در تصویر زیر مراحلی که برای آماده کردن قهوه‌ها و رسیدن به هدف نهایی مسئله باید انجام شود را مشاهده می‌کنید:

مراحل آماده‌سازی قهوه
مراحل آماده‌سازی قهوه

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

البته باید درنظر داشته باشید که بین یک مسئله روزمره مانند درست کردن قهوه و یک مسئله پیچیده مثل تولید یک نرم‌افزار تفاوتی وجود دارد و آن اینکه به‌ندرت پیش می‌آید که مراحل تولید یک نرم‌افزار بصورت مداوم و روزمره تکرار شوند و تمرین شده باشند. برای اینکه بتوانید مراحل مورد نیاز تولید یک قسمت از نرم‌افزار را بنویسید باید قبلاً آن کار را به اندازه‌ی کافی انجام داده باشید. که البته در حوزه‌ی تولید نرم‌افزار خیلی معمول نیست. به همین دلیل است که مهندسین نرم‌افزار باتجربه برای حل مسئله بلافاصله لیست فعالیت‌ها را نمی‌نویسند و ابتدا آن را به مسائل کوچکتر و ساده‌تر تقسیم می‌کنند.

مهندسین با رجوع به مسئله سعی می‌کنند پاسخ «چه‌چیز» را پیدا کنند و با رجوع به مراحلی که باید انجام شود به سوال «چگونه» پاسخ می‌دهند. در مسئله‌ی آماده کردن قهوه می‌توان لیست اهداف جزئی (Subgoals) را بصورت زیر درنظر گرفت:

۱- فنجان‌ها آماده باشند.
۲- قهوه‌ی آسیاب‌شده به اندازه‌ی کافی داشته باشیم.
۳- قهوه‌ساز به اندازه‌ی کافی آب داشته باشد.
۴- یک فیلتر قهوه که درون آن قهوه ریخته شده است داشته باشیم. پیش‌نیاز این هدف، هدف ۲ است.
۵- فیلتر قهوه داخل قهوه‌ساز قرار گرفته باشد. پیش‌نیاز این هدف، هدف ۴ است.
۶- قهوه‌ساز درحال دم کردن قهوه باشد. پیش‌نیاز آن تکمیل هدف ۳ و ۵ است.
۷- دم کردن قهوه تمام شده باشد. نیازمند آن است که هدف ۶ انجام شده مدت زمان کافی از آن گذشته باشد.
۸- فنجان‌های قهوه با قهوه پر شده و آماده باشند. نیازمند آن است که هدف ۱ و ۷ انجام شده باشند.
۹- به دو فنجان از قهوه‌ها شیر اضافه شده باشد. نیازمند هدف ۱ است.
۱۰- به دو فنجان، که یکی از آنها دارای شیر و یکی فاقد شیر است، شکر اضافه شده باشد. نیازمند انجام هدف ۱ و ۹ است.
۱۱- قهوه‌ها به‌خوبی هم‌زده شده باشند. نیازمند هدف ۸، ۹ و ۱۰ است.

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

در تصویر زیر می‌توانید این اهداف و روابط آنها را مشاهده کنید. فلش‌های ورودی به هر هدف مشخص می‌کند که برای رسیدن به آن لازم است چه اهدافی پیش از آن به انجام رسیده باشند.

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

نوشتن اهداف جزئی به ما این امکان را می‌دهد تا نتایج را از فعالیت‌ها جدا کنیم که بسیار مهم است. برای مثال، هدف ۴ «یک فیلتر قهوه که درون آن قهوه ریخته شده است داشته باشیم» ممکن است نیازمند این باشد که برویم و یک فیلتر قهوه بخریم اما چیزی که در اهداف جزئی مهم است نتیجه است و نه چگونگی رسیدن به آن. چگونگی رسیدن به آن یک انتخاب است که بستگی به موقعیت دارد. (مثلاً آیا فیلتر قهوه داریم یا باید بخریم؟)

۲- موازی فکر کنید

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

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

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

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

تعیین ترتیب انجام کارها

حال ترتیب انجام کارها را مشخص می‌کنیم بصورتی که هرجا امکان انجام کارها بصورت موازی وجود دارد حتما بصورت موازی آن‌ها را انجام می‌دهیم، این کار باعث می‌شود تا زمان رسیدن به هدف اصلی کوتاه‌تر شده و همچنین یکی از مراحل (هم زدن قهوه‌ها) حذف شود. لیست نهایی کارها در حالت بهینه بصورت زیر است:

۱- دانه‌های قهوه را در آسیاب الکتریکی بریز و آن را روشن کن.
۲- درحالی که آسیاب درحال آسیاب کردن است، مخزن آب قهوه‌ساز را پر کن.
۳- وقتی آسیاب کردن قهوه‌ها تمام شد قهوه‌ی آسیاب‌شده را در فیلتر قهوه بریز.
۴- فیلتر را در قهوه‌ساز قرار بده.
۵- قهوه‌ساز را روشن کن.
۶- در زمانی که قهوه‌ساز درحال دم کردن قهوه است فنجان‌ها را از کابینت خارج کن.
۷- در دو فنجان شیر بریز.
۸- در یکی از فنجان‌های حاوی شیر و یک فنجان خالی شکر بریز.
۹- منتظر باش تا قهوه دم بکشد.
۱۰- قهوه را در فنجان‌ها بریز.

این مراحل را در تصویر زیر مشاهده می‌کنید:

توجه کنید که ترتیب انجام کارها تأثیری روی لیست اهداف جزئی نداشته است هرچند که به آن وابسته است و باتوجه به آن تعیین شده است ولی نباید آنرا تغییر دهد. کارهایی که به هیچ‌یک از کارهای دیگر وابستگی ندارند می‌توانند با هر ترتیبی انجام شوند که به بیشترین حالت موازی‌کاری برسید.

زمانی که درمورد اهداف جزئی مسئله فکر می‌کنید و آنها را لیست می‌کنید نیازی نیست درمورد چگونگی انجام آنها تصمیم‌گیری کنید. اهداف جزئی، مسئله اصلی را به مسائل کوچک‌تری تقسیم می‌کنند که هرکدام بعدا به‌تنهایی قابل تصمیم‌گیری و انجام هستند.

۳- تعمیم بدید ولی بیش از حد نه!

هدف مسئله‌ی بالا که آماده کردن قهوه طبق سفارش ۴ نفر بود خیلی محدود و دستورالعمل‌گونه است. مثلا اگر مسئله تغییر کند و نیاز باشد که برای ۵ نفر قهوه آماده کنید که یکی از آنها وانیل و دیگری بدون کافئین بخواهد، باید لیست اهداف و کارها تغییر کند.

برنامه‌نویسان یاد می‌گیرند که راه‌حل‌هایشان را جوری طراحی کنند که با تغییر پارامترها نیاز به طراحی مجدد نداشته باشند. آنها یاد می‌گیرند تا راه‌حل‌ها را بصورتی تعمیم دهند که برای مسائل تقریباً مشابه قابل استفاده باشد.

تعمیم دادن مسئله‌ی آماده کردن قهوه

شاید در مسئله‌ی آماده کردن قهوه شما بخواهید اهداف را جوری تعمیم دهید که بجای تعداد ثابت ۴ نفر برای هر تعداد N از افراد جوابگو باشد. یا شاید بخواهید بجای شیر و شکر مفهومی به نام «افزودنی» تعریف کنید که شامل موارد مختلفی مانند شیر، شکر، شیره، کف و... باشد. همچنین می‌توان تعداد افزودنی‌ها را نیز تحت متغیر X تعریف کرد که در هر فنجان بتوان تعداد دلخواه از افزودنی‌ها را اضافه کرد.

یکی دیگر از مواردی که می‌توان آن را تعمیم داد نوع قهوه است. در ابتدایی‌ترین حالت قهوه با کافئین و بدون کافئین می‌تواند درنظر گرفته شود. اما می‌تواند حالت‌های بیشتری داشته باشد.

از آنجایی که قهوه‌سازها در یک زمان مشخص تنها قادر به آماده کردن یک نوع قهوه هستند، اضافه شدن این متغیر (نوع قهوه) می‌تواند همه‌چیز را به‌کل تغییر دهد. اگر یکی از چهار نفر قهوه با کافئین و سه نفر دیگر قهوه‌ی بدون کافئین درخواست کنند، آماده کردن قهوه برای همه‌ی آنها بصورت همزمان کمی پیچیده خواهد شد. در این شرایط باید مفهومی تعریف کنید که تعداد قهوه‌سازها را تعمیم دهید و بصورت متغیر تعریف شود. حداقل Y قهوه‌ساز درصورتی که شما Y نوع قهوه مختلف باید آماده کنید. همچنین موازی‌سازی کارها درصورتی که Y قهوه‌ساز داشته باشیم که بصورت مستقل کار کنند سخت‌تر می‌شود. در این حالت شاید یک برنامه‌نویس تصمیم بگیرد که تعداد قهوه‌چی‌ها (باریستا) را نیز تعمیم دهد. اگر شما K قهوه‌چی داشته باشید که باهم کار کنند راه‌حل چگونه تغییر می‌کند؟

کمی بیشتر تعمیم بدهیم

در این مرحله شاید احساس کنید که خودِ قهوه را نیز می‌توان بعنوان یکی از اجزاء نوشیدنی تعریف کرد، پس چه دلیل دارد که این مسئله را برای قهوه محدود کنیم؟ شما می‌توانید تصمیم بگیرید که قهوه هم یکی از افزودنی‌ها است و همه‌ی قسمت‌هایی که بصورت اختصاصی برای قهوه درنظر گرفته شده است را حذف کنید. اگرچه قهوه نیازمند آماده‌سازی خاصی است ولی می‌توانیم درنظر بگیریم که امکان دارد هریک از افزودنی‌ها نیازمند آماده‌سازی خاصی باشد. برای مثال اگر اگ‌ناگ (Eggnog) یکی از افزودنی‌ها باشد می‌توانیم مراحل آماده‌کردن آن را هم در راه‌حل مسئله درنظر بگیریم. اما در این حالت شما می‌توانید این را به درست کردن هرچیزی تعمیم بدهید که اگ‌ناگ هم صرفاً یکی از چیزهایی است که می‌توانید درست کنید.

بیش از حد مهندسی کردن

سردرد گرفتید؟ این دنیای پیچیده بخاطر مهندسی کردن بیش از حد است. می‌توانید ببینید که چطور تعمیم دادن می‌تواند به‌سرعت همه‌چیز را پیچیده کند اگر بیش از حد از آن استفاده کنید. ما با یک نوع قهوه، چهار نفر، چند افزودنی مشخص و یک قهوه‌چی شروع کردیم که خیلی ساده بود. اگر شما سعی کنید که همه‌ی جنبه‌های آماده کردن قهوه را تعمیم دهید و به این فکر کنید که احتمالات موجود برای N نفر، X افزودنی، Y نوع قهوه و K قهوه‌چی چیست همه‌چیز پیچیده و پیچیده‌تر می‌شود، مخصوصا اگر بخواهید در هزینه و زمان نیز بهینه باشد. خیلی سریع از مسئله‌ی آماده کردن قهوه به آماده کردن هر چیزی که در دنیا وجود دارد می‌رسید!

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

تعادل

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

۴- چرخ را دوباره اختراع نکن!

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

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

پیدا کردن راه‌حل‌های مناسبی که قبلا پیاده‌سازی شده و قابل استفاده هستند یکی از مهارت‌های حل مسئله است که یک برنامه‌نویس باید داشته باشد.

۵- به جریان داده فکر کنید

برنامه‌نویسان باتجربه بعد از سال‌ها تمرین، به نرم‌افزار و حل مسئله در قالب جریان داده‌ها فکر می‌کنند. در مسئله‌ی آماده کردن قهوه، به جریان آب، قهوه، فنجان و مواد افزودنی از مبدأ تا مقصد فکر کنید.

آب از لوله‌های آب می‌آید، قهوه از قوطی قهوه در آشپزخانه (پیش از آن از فروشگاه)، فنجان‌ها از کابینت و افزودنی‌ها از منابع مختلف دیگر.

مواد اولیه (داده) از مراحلی می‌گذرند که باعث تغییر، تبدیل و یا مخلوط شدن آنها می‌شوند. در مرحله‌ی اول مواد خام در مبدأ خود قرار دارند و در مرحله‌ی نهایی تبدیل به تعدادی فنجان قهوه می‌شوند.

فکر کردن در قالب جریان داده باعث می‌شود که هدف نهایی و اهداف جزئی را بصورت تعدادی جعبه و فلش که آنها را بهم متصل می‌کنند تصور کنید. جعبه‌ها نمایانگر فعالیت‌هایی هستند که باعث تغییر مواد (داده) جاری در سیستم می‌شوند و فلش‌ها نیز لوله‌هایی هستند که مواد در آنها جریان پیدا می‌کند و بین مراحل منتقل می‌شود.