At a high level, maintainability defines the ease with which changes can be made correctly. Correctness in this sense means that the intended changes are made without introducing unexpected side effects. Code should be structured so as to be easily modifiable. Tests should be in place to prevent regression, ensuring that existing functionality is unaffected by changes. Additional tests should be developed to verify new functionality. By developing tests concurrently with code changes, you are both validating that the change being made is the one intended to be made, and providing regression protection for future changes. Further, automated processes should be in place to continuously verify the correctness of the code base and to identify any issues as early in the process as possible.
قابلیت نگهداری (Maintainability) در کد نویسی، در واقع رعایت یک سری قوانین است که باعث میشود تغییرات کد در آینده راحت تر و بدون ایجاد عوارض جانبی غیر منتظره انجام شود. کد باید به گونه ای ساختار یافته باشد که به راحتی قابل تغییر باشد. تست هایی باید برای جلوگیری از تغییرات ناخواسته در سایر قسمت های برنامه وجود داشته باشد و اطمینان حاصل شود که عملکرد موجود تحت تأثیر تغییرات قرار نمیگیرد. همچنین تست هایی هم باید برای تأیید عملکرد جدید برنامه ایجاد شود. با نوشتن تست ها همزمان با تغییرات کد، هم تأیید میکنید که تغییر ایجاد شده همان تغییری است که باید انجام شود و هم کد را در برابر تغییرات آینده محافظت میکنید (Regression Testing). علاوه بر این، فرآیندهای خودکار (automated processes) باید برای تأیید مداوم صحت کد وجود داشته باشند و بتوانند هر گونه مشکل را خیلی سریع پیدا کنند.
Maintainable code is clear, readable, testable, understandable, well-organized, consistent, and highly cohesive. It eases ongoing enhancements, bug fixes, and modifications; quickens the ability to onboard new team members, transfer responsibility, and overall increases confidence in changes. It is enabled by high quality code, verified by a mature automated test and scan process, and facilitated by a close relationship between the business, designers, developers, and testers.
کدی قابل نگهداری است که واضح، خوانا، قابل تست، قابل درک، به خوبی سازماندهی شده، سازگار و بسیار منسجم باشد. چنین کدی بهبودهای مداوم، رفع اشکال ها و تغییرات را آسان می کند؛ توانایی حضور اعضای جدید در تیم، انتقال مسئولیت و به طور کلی افزایش اعتماد به نفس در تغییرات را افزایش می دهد. این مهم وقتی میسر میشود که یک رابطه نزدیک بین متولیان کسب و کار، طراحان، توسعه دهندگان و تست کنندگان شکل گرفته باشد و یک کد با کیفیت بالا نوشته شده باشد، و آن کد توسط یک فرآیند اسکن و تست خودکار تأیید شود.
From a development standpoint, there are a number of aspects that can be followed to increase maintainability. This blog will briefly go over some of those qualities, focusing on how developers can structure code. Not discussed in detail are the multitude of ways and tools that can be employed to verify and enforce code quality, both during local development and as part of an automated continuous integration process.
به عنوان یک توسعه دهنده نرم افزار، مواردی هست که می توان برای افزایش قابلیت نگهداری رعایت کرد. این مطلب به برخی از این ویژگی ها می پردازد و بر چگونگی ساختار کد توسعه دهندگان تمرکز می کند؛ در اینجا به طور مختصر بسیاری از راهها و ابزارهایی را معرفی میکنیم که میتوانید برای نوشتن یک کد با کیفیت، هم در طول توسعه اولیه و هم به عنوان بخشی از فرآیند یکپارچهسازی مداوم خودکار (automated continuous integration) از آنها استفاده کنید.
1. Follow a clean and consistent coding standard
There should be a formalized coding standard followed by all developers. This enables an application with many contributors to have a single consistent standard, which increases the ability to make modifications. There are many tools that can verify, enforce, and automatically correct code style based on customizable rules, including: ESLint (JavaScript), TSLint (TypeScript), Prettier (multiple languages), RuboCop (Ruby), and Checkstyle (Java). These tools can be both incorporated into a developer’s IDE for scanning during development, and into a continuous integration (CI) process. Where feasible, use a community-maintained ruleset. This enables the application not only to be internally consistent but to be externally consistent with best practices.
۱. از یک استاندارد کدنویسی تمیز و سازگار پیروی کنید
باید یک استاندارد کدنویسی رسمی وجود داشته باشد که همه توسعه دهندگان آن را رعایت کنند. این امر باعث میشود توانایی ایجاد تغییرات توسط برنامه نویسان افزایش یابد. ابزارهای زیادی وجود دارند که می توانند کدهای نوشته شده را بررسی و یا به طور خودکار تصحیح کنند (که البته قوانین این ابزار ها قابل تنظیم هستند)؛ از جمله: ESLint (جاوا اسکریپت)، TSLint (تایپ اسکریپت)، Prettier (تعدادی از زبان ها)، RuboCop (روبی) و Checkstyle (جاوا). این ابزارها می توانند هم در IDE شما برای اسکن در حین توسعه و هم در یک فرآیند یکپارچه سازی مداوم (CI) گنجانده شوند. در صورت امکان، از یک مجموعه قوانین شناخته شده توسط جامعه استفاده کنید. در این صورت برنامه شما نه تنها در مجموعه داخلی خودتان سازگار هست، بلکه با بهترین روش های برنامه نویسی سازگار می باشد.
2. Use human readable and sensible names
Variable, method, and class names should both follow a defined structure, and be human readable and descriptive of their intended purpose. Camel case (e.g. camelCase) and snake case (e.g. snake_case) are two examples of how to structure variable names. It is recommended that different types of components (e.g. classes and objects) follow different standards. For example, the standard in many languages is that classes should be upper camel case (or pascal case) while objects should be lower camel case. This standardization can easily be enforced by the tools discussed in the previous section. Not only should variable names be similarly structured, but they should also be sufficiently descriptive. The variable `firstName` is much more descriptive than the variable `aString`, and its intended purpose is easily understood.
۲. از اسامی خوانا و معقول استفاده کنید
نام متغیرها، متدها و کلاس ها باید برای انسان قابل خواندن و توصیف کننده هدف مورد نظر خود باشند؛ همچنین باید از ساختارهای استاندارد نامگذاری استفاده کنند. camelCase و snake_case دو نمونه از ساختار نامگذاری متغیرها هستند. توصیه می شود که انواع مختلف مؤلفه ها (مانند کلاس ها و آبجکت ها) از استانداردهای متفاوتی پیروی کنند. به عنوان مثال، استاندارد در بسیاری از زبانها این است که نام کلاسها باید PascalCase باشند، در حالی که آبجکت ها باید camelCase باشند. این استانداردسازی را می توان به راحتی با ابزارهای مورد بحث در بخش قبل اجرا کرد. نام متغیرها نه تنها باید ساختاری مشابه داشته باشند، بلکه باید به اندازه کافی گویا نیز باشند. متغیر "firstName" بسیار خواناتر از متغیر "aString" است و هدف مورد نظر آن به راحتی قابل درک است.
3. Be clear and concise
While it may be tempting to develop applications with the minimal amount of lines possible, there is a risk of making the code overly complex. Clever and complicated logic should be minimized, with a preference for verbosity and ease of understanding over complexity. Code should focus more on what is being done rather than how it is being done. Code comments should not be required to explain the logic and flow.
۳. واضح و مختصر باشید
در حالی که ممکن است وسوسه انگیز باشد که برنامه ها را با حداقل تعداد خطوط ممکن بنویسید، اما ممکن است کد شما بیش از حد پیچیده شود. در کدنویسی ترجیح دهید پرحرفی کنید و روش های هوشمندانه و پیچیده را به حداقل برسد تا فهم آن کد راحت تر شود. کد باید بیشتر بر آنچه انجام می شود تمرکز کند تا اینکه چگونه انجام می شود. کد باید به گونه ای نوشته شود که برای توضیح منطق و جریان آن، نیازی به نوشتن کامنت (Comment) نباشد.
4. Minimize complex conditional and nested logic
Nested code should be minimized as it is more difficult to follow the possible paths and logic flow of deeply nested code blocks. It is preferable to extract conditional logic to methods or variables so it is easier to understand what is being evaluated. Early method returns are preferable to large blocks of code contained within a conditional. Exceptions should be thrown as applicable.
۴. کدهای پیچیده شرطی و تودرتو را به حداقل برسانید
کد تودرتو (Nested Code) باید به حداقل برسد چرا که دنبال کردن مسیرهای احتمالی کد و جریان منطقی بلوکهای کد عمیق تودرتو دشوارتر است. بهتر است کدهای شرطی را با متدها یا متغیرها تفکیک کنیم تا درک آنچه اتفاق میافتد آسانتر شود. توصیه میشود در یک متد، هر جا میتوانید از return و Throw Exception استفاده کنید، به جای آنکه بلوک های شرطی (if) بزرگ داشته باشید.
5. Methods should be small and singularly focused
Methods should do one thing and do it well. This will naturally lead to reasonably sized methods. To improve readability, and if methods are becoming overly complex or large, look for opportunities to pull out logic into separate methods. It is preferable to have methods that do not require external dependencies and do not mutate external state. While code line length is very subjective, and it is a mistake to put in place a strict limit, it is reasonable that a sufficiently focused method would be 25 lines or fewer.
۵. متدها باید کوچک و متمرکز باشند
متدها باید فقط یک کار را انجام دهند و آن را به خوبی انجام دهند. طبیعتاً این هدف باعث میشود متدها اندازه معقولی داشته باشند. اگر متدها بیش از حد پیچیده یا بزرگ شده اند، برای خوانایی بیشتر آنها را به متدهای کوچکتری بشکنید. ترجیحاً متدهایی داشته باشید که وابستگی خارجی (External Dependency) نداشته باشند و Stateهای خارجی را نیز تغییر ندهند. هر چند که تعداد خط یک متد خیلی مهم نیست، و نمیشود محدودیت دقیقی برای آن تعریف کرد، اما بهتر است تعداد خطوط، ۲۵ یا کمتر باشد.
6. Classes should be focused
Similar to methods, classes should also be focused. All class methods and parameters should be related to solving a specific need, having a cohesive responsibility. Again, code line length is very subjective, but it is reasonable that a sufficiently focused class would be 1000 lines or fewer. If all other points are followed, class size will inherently be minimized while still maintaining readability.
۶. کلاس ها باید متمرکز باشند
مشابه متدها، کلاس ها نیز باید بر یک موضوع خاص تمرکز داشته باشند. تمام متدها و پارامترهای یک کلاس باید مرتبط با رفع یک نیاز خاص باشند و همچنین انسجام لازم در جهت انجام یک مسئولیت را دارا باشند. باز هم، تعداد خطوط یک کلاس خیلی ملاک نیست، اما منطقی است که یک کلاس به اندازه کافی متمرکز، 1000 خط یا کمتر باشد. اگر تمام نکات دیگر رعایت شود، اندازه کلاس ذاتاً به حداقل می رسد و در عین حال خوانایی حفظ می شود.
7. Decouple and organize
The code should be decoupled and organized. This can be manifested in a Model-View-Controller (MVC) organization, or by using a similarly enforced architecture. Concerns should be separated. Two common ways of organizing files is either by type or by subject. For example, organizing files by type would be grouping all models together, while organizing files by subject would be grouping all User-related files together. Dependency injection is preferred to increase decoupling and improve testability.
۷. جداسازی و سازماندهی
کد باید جداشده (Decoupled) و سازماندهی شده (Organized) باشد. این می تواند با معماری Model-View-Controller (MVC) یا با استفاده از یک معماری مشابه اعمال شود. موضوعات مهم را باید از هم جدا کرد (Seperation of Concerns). دو روش متداول برای سازماندهی فایل ها بر اساس نوع یا موضوع وجود دارد. برای مثال، در سازماندهی فایلها بر اساس نوع، همه مدلها را کنار هم قرار میدهیم، در حالی که در سازماندهی فایلها بر اساس موضوع، همه فایلهای مرتبط با یک موضوع در کنار هم قرار میگیرند. برای افزایش جداسازی و بهبود تست پذیری ترجیحاً از تزریق وابستگی (Dependency Injection) استفاده کنید.
8. Minimize redundancy
Common code should be extracted to shared methods and utility libraries. When code changes are inevitably required, they only need to be made in a single location. This aspect is focused on the Don’t repeat yourself (DRY) principle. Using well-designed frameworks can help reduce redundant boilerplate code.
۸. افزونگی (Redundancy) را به حداقل برسانید
کدهای مشترک (Common Code) باید از کد اصلی جدا شوند و در متدهای مشترک (Shared Methods) و یا کتابخانه های ابزار (Utility Libraries) نوشته شوند. در این صورت هنگامی که نیاز به تغییر آنها باشد، فقط کافی است یک جا را تغییر دهید. این جنبه بر اصل "خودت را تکرار نکن" (DRY) دلالت دارد. استفاده از فریم ورک های خوب می تواند به کاهش کد اضافی تکراری کمک کند.
9. Leverage existing libraries and frameworks
It is better to leverage existing libraries and frameworks than it is to recreate the wheel. Ensure that the libraries chosen are actively maintained and from reputable sources. Using third-party libraries lowers the amount of code needed to be tested and maintained.
۹. از کتابخانه ها و فریم ورک های موجود استفاده کنید
بهتر است از کتابخانه ها و فریم ورک های موجود استفاده کنید تا اینکه چرخ را دوباره اختراع کنید. البته منظور کتابخانه های معتبری است که به طور فعال پشتیبانی میشوند. استفاده از این کتابخانه ها (third-party libraries)، مقدار کد مورد نیاز برای تست و نگهداری را کاهش می دهد.
10. Clearly track dependencies
All dependencies should be explicitly declared and used. There should be no dependence on implicit or system level packages. A manifest file should be used to track all dependencies (and the exact versions), along with a process in place to ensure these dependencies are downloaded and used. This allows developers and the CI process to remain in sync, and quickens the ability of a new developer to begin contributing. Dependencies that are no longer used should be removed from the manifest file.
۱۰. وابستگی ها (Dependencies) را به وضوح دنبال کنید
همه وابستگی ها باید به صراحت اعلام و استفاده شوند. هیچ وابستگی به پکیج های ضمنی (implicit packages) یا پکیج های سطح سیستم (system level packages) نباید وجود داشته باشد. از یک فایل بیانیه (Manifest File) باید برای مشخص کردن همه وابستگیها (و نسخههای دقیق آنها) استفاده کنید؛ همچنین فرآیندی تعریف کنید که مطمئن شوید آن وابستگی ها دانلود میشوند و قابل استفاده هستند. این به توسعهدهندگان و فرآیند CI امکان میدهد تا به روز بمانند و همچنین کار یک توسعهدهنده جدید را تسریع میکند. وابستگی هایی که دیگر استفاده نمی شوند باید از فایل مانیفست حذف شوند.
11. Remove unused code
Unused and orphaned code should be removed. Removing unused functionality makes clear what code needs to be maintained and tested, increasing its readability. It is insufficient to simply comment out the unused code for possible use in the future. With modern version control systems, old code can be easily retrieved if it is determined that it is once more needed. If developing an externally used API or library, a deprecation strategy should be employed. Related to this topic is to avoid TODO or similar comments. Missing features or changes should be tracked externally, and not littered throughout the code.
۱۱. کدهای استفاده نشده را حذف کنید
کدهای استفاده نشده (unused) و یتیم (orphaned) باید حذف شوند. حذف توابع استفاده نشده باعث میشود که کدهای اضافه را تست و نگهداری نکنیم و خوانایی کد افزایش می یابد. این خوب نیست که کدها را کامنت کنیم که شاید در آینده بخواهیم از آنها استفاده کنیم؛ با سیستم های کنترل نسخه مدرن (مثل git)، به راحتی میتوان آنها را بازیابی کنیم. اگر در حال نوشتن یک کتابخانه یا API با استفاده خارجی هستید، حتما از یک استراتژی منسوخ شدن (Deprecation Strategy) استفاده کنید؛ همچنین از کامنت های TODO یا مشابه آن اجتناب کنید. کدهای نوشته نشده نباید در سراسر کد پخش شده باشند.
12. Create API and method-level documentation/contracts
It is far better to write self-documenting code and to not rely on inline comments to explain logic. However, this does not mean that there should be zero documentation. This is especially true when working with multiple teams or when developing a consumable library or extensible application. The public API should be accurately documented, describing the inputs, outputs, and functionality. This enables API consumers to understand and use the available functionality.
۱۲. داکیومنت های API و در سطح متد ایجاد کنید
به مراتب بهتر است کدی بنویسید که خود گویای همه چیز باشد (self-documenting code) و برای فهم منطق آن نیازی به کامنت های درون کد نداشته باشد. با این حال، این بدان معنا نیست که هیچ داکیومنتی برای کد وجود نداشته باشند. این امر به ویژه هنگام کار با چندین تیم یا هنگام توسعه یک کتابخانه یا برنامه قابل توسعه صادق است. API عمومی باید به طور دقیق مستند شده و ورودی ها، خروجی ها و عملکردها را توصیف کند. این به مصرف کنندگان آن API امکان می دهد تا عملکرد موجود را درک کرده و از آن استفاده کنند.
13. Separate configuration from code
Configuration should be clear, consistent, and separate from the source code. It should be organized and defined in a central location. All available options should be described with how they are used and any applicable default values. It is preferable to use environment variables. In this way, the source code can remain unchanged, and how those environment variables are defined can change based on use case or environment. Anything that can vary between environments or deployments should not be located within the code itself. Organizing and sufficiently describing the available configuration options makes it much easier to deploy to new environments and for new team members to begin contributing.
۱۳. پیکربندی (Configuration) را از کد جدا کنید
پیکربندی (Configuration) باید واضح، سازگار و جدا از کد اصلی باشد. باید در یک مکان مرکزی سازماندهی و تعریف شود. تمام گزینه های موجود باید با نحوه استفاده از آنها و مقادیر پیش فرض قابل اعمال توضیح داده شود. استفاده از متغیرهای محیطی (Environment Variables) ترجیح داده می شود. به این ترتیب، کد اصلی می تواند بدون تغییر باقی بماند، و نحوه تعریف آن متغیرهای محیطی می تواند بر اساس موارد استفاده یا محیط تغییر کند. هر چیزی که می تواند بین محیط ها یا استقرارها متفاوت باشد نباید در خود کد قرار گیرد. سازماندهی و توصیف کافی گزینه های پیکربندی موجود، استقرار در محیط های جدید و شروع مشارکت اعضای تیم جدید را بسیار آسان تر می کند.
(لینک منبع: ذکر شده در اولین کامنت)