یک نرمافزار به هزار و یک دلیل سقوط میکند. کاری با بازاریابی ناکارآمد یا دلایل ناشناختهی دیگر ندارم و تنها چند تجربهی فنی خودم را مینویسم. چرا یک نرمافزار با گذر زمان شکنندهتر میشود و هرچه جلوتر بروید، کنترل و تسلط کمتری احساس میکنید؟
زمانی تستنوشتن را بیهوده میدانستم و درک نمیکردم که تستنوشتن چه فایدهای دارد. بعداً با پروژههای مختلفی سر و کار داشتم که بزرگتر و بزرگتر میشدند و تغییردادنشان ریسک داشت و ممکن بود با تغییر یک متد، قسمتهای مختلف پروژه از کار بیفتند. تستها باید به صورت استاندارد و با دقت نوشته شوند و بخشهای مختلف نرمافزار را پوشش دهند. برای هر چیزی که ممکن است در آینده با مشکل مواجه شود (یعنی تقریباً همهچیز D:) تست بنویسید.
در این باره، Michael Feathers نوشته است:
The tests that we write to characterize code are very important. They are the documentation of the system’s actual behavior. Like any documentation that you write, you have to think about what will be important to the reader. Put yourself in the reader’s shoes. (Working Effectively with Legacy Code, p189)
تستهایی که برای توصیف کد خود مینویسیم، بسیار مهم هستند. آنها مستندات رفتار واقعی سیستم هستند. مثل هر مستنداتی که مینویسید، باید به این فکر کنید که چه چیزی برای خواننده اهمیت دارد. خودتان را در کفشهای خواننده قرار دهید و جای او باشید.
رابرت مارتین هم در کتاب Clean Code مانند آن را میگوید:
Well-written unit tests are also expressive. A primary goal of tests is to act as documentation by example. Someone reading our tests should be able to get a quick understanding of what a class is all about. (Clean Code, p175)
یونیتتستهایی که خوب نوشته شده باشند، واضح و رسا هستند. یکی از اهداف اصلی تستها این است که به عنوان مستندات عمل کنند و کسی که تستهایمان را میخواند، فهم مختصری از اینکه یک کلاس چهکاره است به دست بیاورد.
پروژهای که مستندات نداشته باشد، مانند جعبهای است که راهی برای کشف محتویاتش نیست، جز اینکه آن را تکان دهیم! مستندات باید کلید فهم ساختار پروژه، موجودیتها و اصطلاحات بهکاررفته در پروژه را به برنامهنویسان جدید بدهد و آنها را در کمترین زمان با کارهایی که در گذشته انجام شده آشنا کند.
نام جدولهای دیتابیس، ماژولها، کلاسها، متدها و متغیرها را با دقت انتخاب کنید، حتی اگر به قیمت طولانیشدنشان تمام شود! این موضوع آن قدر مهم است که رابرت مارتین، فصل دوم کتاب Clean Code را به آن اختصاص داده است و میگوید:
Choosing good names takes time but saves more than it takes. So take care with your names and change them when you find better ones. Everyone who reads your code (including you) will be happier if you do. (Clean Code, p18)
انتخاب نام خوب، وقتگیر است ولی در عوض وقت بیشتری را به شما برمیگرداند، بنابراین مراقب نامهایی که انتخاب میکنید باشید و هر وقت جایگزین بهتری پیدا کردید، آنها را تغییر دهید. اگر چنین کنید، کسانی که کد شما را میخوانند (از جمله خود شما) شادتر خواهند بود!
شاید کلاس CheckUser برای شما آشنا باشد، ولی نفر بعدی که به تیم میپیوندد و روی کد شما کار میکند هیچ درکی از آن نخواهد داشت و باید به اجبار تمام کد را بخواند تا بفهمد منظور از CheckUser چیست و این کلاس چه مسئولیتی دارد!
برنامهنویسان میتوانند زبان مشترکی داشته باشند و حرف همدیگر را بهتر بفهمند. فریمورک لاراول Queue را پیادهسازی کرده است و هر برنامهنویسی که با آن آشنا باشد، میفهمد که پوشهی Jobs برای چیست و Job چه کاری دارد. اما تصور کنید که Queue خودم را از صفر پیادهسازی کرده باشم و نامش را Servant بگذارم، چه کسی آن را کشف میکند؟! قابلیتهای فریمورک را بشناسید و چرخ را از نو اختراع نکنید، دیزاینپترنها را به خوبی یاد بگیرید و در جای مناسب به کار ببرید تا زبان مشترک بین شما و سایر برنامهنویسان باشند. اگر چنین باشد، آنها خواهند دانست که کلاس CoffeeFactory یک Factory است!
ابتدا یک نیاز وجود دارد، شما هم یک کلاس با یک متد مینویسید. بعد از مدتی، قابلیت جدیدی خواسته میشود و شما هم یک متد دیگر اضافه میکنید. با هر درخواست، یک متد به کلاس اضافه میشود و پس از مدتی با کلاسی همهکاره سر و کار داریم که چند هزار خط کد نامرتب دارد. (The blob، یک آنتیپترن مشهور)
در کتاب Michael Feathers آمده است:
When we avoid creating new classes and methods, the existing ones grow larger and harder to understand. (Working Effectively with Legacy Code, p8)
وقتی از ساختن کلاسها و متدهای جدید خودداری میورزیم، چیزی که در حال حاضر وجود دارد، بزرگتر و بزرگتر میشود و به دشواری درک خواهد شد!
سناریوی دیگر این است که دستهبندی درستی وجود ندارد و همهچیز مثل اجرام فضایی سرگردان است. اگر مدیر از شما بخواهد که قابلیت جدیدی اضافه کنید یا چیزی را تغییر دهید، راهی جز زیر و رو کردن فایلهای پراکنده ندارید و تازه، تکرار (Duplication) آنقدر زیاد است که اگر چیزی را تغییر دهید، استرس میگیرید و با خود میگویید: نکنه یه جای دیگه هم هست ولی من پیداش نمیکنم؟!
ریفکتورینگ مداوم به حذف کدهای زائد و رفع خیلی از مشکلات دیگر کمک کرده و مانع از پراکندگیهایی میشود که فهم پروژه را مشکل میکنند. کدهایی که دیگر استفاده نمیشوند را باید حذف کرد؛ رابرت مارتین حذف آنها را به کامنتکردنشان ترجیح میدهد، توجیهاش این است که حتی اگر در آینده به آنها نیازی داشته باشیم، با وجود سیستمهای کنترل سورس کد (مانند git که برایتان آشناست!) قابل بازیابی هستند. (p69)
اگر دربارهی این مطمئن نیستید، یک کامنت TODO که در آینده به راحتی پیدا میشود، میتواند مفید باشد و ضرورت تصمیمگیری دربارهی موارد مبهم را به برنامهنویسهای دیگر تیم (مخصوصاً آنهایی که در جریان کاربرد آن تکهکد هستند) یادآوری میکند.
همانطور که میبینید، بخشی از این موارد به دانش فنی برنامهنویسان برمیگردد و بخشی دیگر (گفتن اینکه برنامهنویس باید/نباید به خواستهی مدیریت تن بدهد را کنار بگذاریم) مربوط به تصمیمگیریهای مدیریتی است. باید برای نوشتن تست و ریفکتورینگ زمان خاصی در نظر گرفت، وگرنه مدیری که اینها را تجملی غیر ضروری میداند، سقوط نرمافزار را تضمین میکند!
شما با چه مواردی سر و کار داشتهاید؟