Table of Contents
آشنایی با Legacy C Code
کد میراث C، اغلب چندین دهه، ستون فقرات سیستم های جاسازی شده بی شمار، سیستم های عامل و برنامه های سازمانی را تشکیل می دهد.این کد پایه ها در اصل تحت محدودیت حافظه محدود، پردازنده های آهسته و زنجیره ابزار اولیه نوشته شده اند، در حالی که آنها ممکن است به طور قابل اعتماد عمل کنند، آنها معمولا تعدادی از مشکلات را در سراسر ماژول ها پراکنده می کنند، به طور عمیق مشروط، اعداد جادویی، و یک پلت فرم سنگین در مورد تبدیل مجدد، و تغییر دادن به هدف های قابل حمل و تغییر دادن مجدد آن، و تغییر دادن مجدد آن، و تغییر دادن یک رفتار دارایی، و تغییر دادن آن.
قبل از لمس یک خط، درک کامل از سیستم موجود غیر قابل مذاکره است.خواندن مستندات (اگر وجود دارد)، کارشناسان دامنه مصاحبه می کنند و کد را تحت یک کنفدراسیون اجرا می کنند تا جریان اجرای آن را مشاهده کنند.برنامه ریزی وابستگی های ماژول و توجه داشته باشید که کدام بخش ها به سخت افزار یا یک سیستم عامل خاص تقسیم می شوند.
استراتژی های برای بازسازی موثر
استراتژی های زیر یک چارچوب سیستماتیک برای مدرن سازی کد C ایجاد می کنند.هر رویکرد بدهی فنی را کاهش می دهد در حالی که عملکرد اصلی نرم افزار را حفظ می کند.
۱- یک حساب نامه ی کامل کد را انجام دهید
یک حسابرسی کد نقاط دقیق درد را مشخص می کند.استفاده از ابزارهای تجزیه و تحلیل استاتیک برای تشخیص به طور خودکار اشکالات، آسیب پذیری های امنیتی و نقض استانداردهای برنامه نویسی مدرن. Cppcheck به طور خودکار تشخیص اشکالات، آسیب پذیری های سر و متغیرهای استفاده شده را دریافت می کند. [F:2Clang Static Analyzer [F3]
در طول حسابرسی، سیستم ساخت را نیز بررسی کنید. مدرن کردن فایل های آرایش یا CMakeLists برای حمایت از جمع آوری متقابل پلتفرم و فعال کردن هشدارهای کامپایلر مانند مستندسازی معماری و ایجاد یک گراف وابستگی - این کار بعداً هدایت خواهد کرد.
۲- ایجاد استانداردهای مدرن Coding
اتخاذ یک استاندارد کد نویسی شناخته شده برای ایجاد سازگاری در سراسر کد پایه (FLT:0) دستورالعمل های MISRA C (معمولا در سیستم های خودرو و ایمنی بحرانی استفاده می شود) رفتار نامشخص را کاهش می دهد و قابلیت خواندن را بهبود می بخشد.
به طور معمول، در این زمینه، به صورت استاندارد، ، ، indentation (tabs vs.Space) و سبک کامنت (استفاده از Doxygen یا مشابه) این قوانین را از طریق یک رابط مانند clang-dy در ادغام مداوم خود تقویت کنید.
۳- تنظیم کد
میراث C اغلب شامل توابع تکلیت است که صدها یا هزاران خط را در بر می گیرد. [۱] آنها را به توابع کوچکتر و منسجم تقسیم می کند که هر کدام یک از کارها را انجام می دهند.از فایل های هدر برای اعلام رابط های عمومی و فایل های منبع برای پیاده سازی استفاده می کنند. [۳]
همچنین شتاب سنج کردن به معنی کاهش متغیرهای جهانی است که آنها را با حالت محلی از طریق استدلال تابع یا امتیاز دهندگان عبور می کند، این باعث می شود وابستگی های صریح و واحد تست ممکن است. (اعلامیه های رو به جلو در هدرها، تعاریف تنها در فایل ها] برای پنهان کردن جزئیات پیاده سازی.
// Before: monolithic, global state
int buffer[256];
int index = 0;
void process_data() { /* manipulates global buffer and index */ }
// After: encapsulated module
// buffer.h
typedef struct Buffer Buffer;
Buffer* buffer_create(size_t size);
int buffer_push(Buffer* b, int value);
void buffer_destroy(Buffer* b);
// buffer.c
struct Buffer {
int* data;
size_t size;
size_t index;
};
Buffer* buffer_create(size_t size) { ... }
۴- جایگزین کردن توابع تثبیت شده و غیر امن
کتابخانه استاندارد C شامل چندین عملکرد نا امن است که یا در برنامه نویسی ایمن مدرن نادیده گرفته شده یا دلسرد می شوند.
- [در این باره] [[ویرایش]
- [در این میان] [[[ویرایش] [[۳]] یا [[[ویرایش]
- [در این میان] [در برابر [و] [از [و] [به] [و] [از [و] [و] [به] [و] [به] [و]] [و [از [و]]] [و [به]] [و [به]] [و [از [و]] [به [و]] [و [به [و] [و [و [و [به [و]] [و [و [و] [و [از [و [از [به [به [و] [به [و] [به [و] [و] [به [به [و] [و [و]]] [به [به [به [به [و [و [به [و [و [به [به [و [و] [از [از [و] [و]]] [از [از [از [از [از [از [از [از [از [از [از [از [از [از [از [از [از [از [و]]]]]] [و] [و] [از [از [از [از [از [از [از [و] [به [و
- [در این میان] [مشرکان] [۲۰]
- [در این میان] [مشرکان] [۲۲]
- [در این میان] [[[ویرایش]
- [در این میان] [FLT25] [FLT26] + [FLT27] با محدودیت های عرض [در زمینه]
این تغییرات باعث حذف سرریزهای بافر، منبع اصلی آسیب پذیری های امنیتی می شود، علاوه بر این، توابع قدیمی را با تعریف کردن (FLT:28) بر روی ویندوز یا استفاده از پرچم های کامپایلر که عملکرد های غیر قابل پیش بینی را به عنوان خطا درمان می کنند، غیرفعال می کند.
بهبود مدیریت حافظه
تخصیص حافظه پویا در میراث C اغلب مسائل رایج خطا شامل فراموش کردن حافظه آزاد، دو برابر و اشاره گرهای دمینگ است.مدیریت حافظه Refactor با این شیوه ها:
- استفاده از [FLT 29] به جای هنگامی که حافظه صفر-initialized مورد نیاز است.
- همیشه ارزش بازگشت تابع های تخصیص را بررسی کنید (FLT 31).
- در این میان، به صورت مستقیم به صورت زیر به صورت زیر عمل می کنند.
- اتخاذ یک مدل مالکیت ثابت: سند که دارای حافظه است و مسئول آزاد کردن آن است.
- استفاده از ابزارهایی مانند و یا آدرس Sanitizer (ASan) برای تشخیص نشت و دسترسی های خارج از محدوده در طول آزمایش.
در بخش های حساس عملکرد، استفاده از بافرهای استاتیک یا تخصیص دهنده های عرصه را برای جلوگیری از تقسیم بندی و سربار در نظر بگیرید.برای سیستم های جاسازی شده با حافظه محدود، جایگزینی تخصیص پویا با استخرهای پیش از حد.
۶- استفاده ایمن تر پوینتر
پوینترها یک شمشیر دو لبه هستند که استفاده از آنها را برای کاهش احتمال اشکالاتی مدرن می کند:
- از برای پارامترهای عملکردی که اصلاح نمی شوند استفاده کنید، این باعث می شود قرارداد روشن تر شود و به کامپایلر کمک می کند تا بهینه سازی شود.
- به جای آن، به جای آن، به سمت چپ و راست، به سمت چپ، به سمت چپ حرکت می کنند.
- از خواندن آن در هنگام خواندن از یک جریان بی نظیر خودداری کنید، به جای آنکه از نقض شدید اجتناب کنید، از آن استفاده کنید.
- جایگزین تابع اشاره گر با اشاره کنندگان عملکرد به درستی تایپ شده برای جلوگیری از رفتار نامشخص.
- استفاده از اعضای آرایه انعطاف پذیر (C99) به جای ( آرایه های اندازه در پایان ساختار).
// Avoid: casting void* to misaligned type
int value = *(int*)(byte_buffer + offset); // potential UB
// Prefer: memcpy
int value;
memcpy(&value, byte_buffer + offset, sizeof(value));
بهبود خطای مدیریت
میراث C اغلب از ترکیبی از [FLT 39]، کدهای بازگشتی و حالت های خطای جهانی استفاده می کند.
- استفاده از انواع بازگشتی برای عملکرد (به عنوان مثال،
- از بازگشت به برای کدهای خطا اجتناب کنید؛ اعداد صحیح اجازه می دهند ارزش های منفی برای خطا.
- برای سیستم های پیچیده، یک الگوی سبک دستی با استفاده از / را پیاده سازی کنید (اما از کم رنگ استفاده کنید، زیرا کنترل جریان پیچیده است).
- خطای ثبت شده در سطح بالا و منابع اختصاص داده شده با استفاده از الگوهای (FLT:44) (به طور جدی) برای جلوگیری از کد تمیز کردن تکراری.
8- معرفی Unit Testing
بدون تست، بازسازی وحشتناک است.یک چارچوب تست واحد را در اوایل تعیین کنید.انتخاب های محبوب برای C شامل موارد زیر است:
- [[۱] [۱۰] [۱۰] [۱۰] [۱] [۱]] [۱۰]] [۱]] [۱]] [۳]] [۱]] [۳] [۱]] [۳] [۱]] [۱]] [۳] [۱] [۱]] [۳] [۱] [۱] [۳] [۳] [۱] [۱] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۱] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳] [۳
- [[۱] [۱۰] [۱] [۱۰] [۱]] [۱]] [۱]] [۱]] [۱]] شامل حمایت از ماژول های انزوای است.
- CUnit – سنتی اما کاربردی
تست های واحد را برای هر ماژول اصلاح شده بنویسید.استفاده از توسعه تست محور (TDD) که امکان پذیر است: تستی را بنویسید که رفتار مورد نظر را تعریف می کند، سپس دوباره فعال شود تا زمانی که تست ها انجام شود، تمام سیستم را با ورودی های شناخته شده و خروجی های مورد انتظار اجرا کند. Automate همه آزمایشات در محیط CI برای گرفتن بلافاصله پس انداز.
9- نظر سنجی عملکرد
بازسازی اغلب عملکرد را بهبود می بخشد، اما همچنین می تواند سربار (به عنوان مثال، تماس های عملکردی بیشتر، بسته بندی حافظه) پروفایل قبل و بعد از تغییرات با استفاده از ابزار مانند ، ، یا Xcode Tool بهینه سازی تمرکز بر مسیرهای گرم ( یا توابع معماری [LT:48] را ذخیره می کند، در حالی که پرچم های خاص مونتاژ را ذخیره می کند.
تست و اعتبار
یک استراتژی تست مرحله ای در هنگام بازسازی کد میراث مهم است:
- تست های تهاجمی - اجرای مجموعه آزمایش های موجود (در صورت وجود) قبل از ایجاد تغییرات برای ایجاد یک پایه.
- - Refactor one Module در یک زمان، پس از هر تغییر، با پرچم های سخت و تست های واحد اجرا کنید.
- ادغام تجزیه و تحلیل آماری - Add Cppcheck و Clang-tidy به خط لوله CI خود را درمان هشدار به عنوان خطا برای اجرای کیفیت.
- تجزیه و تحلیل نام تجاری - در زیر Valgrind یا ASan در طول شب برای تشخیص مسائل حافظه معرفی شده توسط refactoring.
- تست پذیرش کاربر - سیستم بازسازی شده را به یک محیط مرحله ای اعمال کنید و کارشناسان دامنه تست های نهایی را انجام می دهند.
خودکار کردن این مراحل با یک سرور CI (GitHub Actions، Jenkins، GitLab CI) باعث کاهش سطح دستی و ایجاد اعتماد به نفس در فرآیند بازسازی می شود.
نتیجه گیری
بازسازی کد C یک پروژه یک بار نیست، بلکه یک نظم و انضباط مداوم است.با انجام حسابرسی کامل، ایجاد استانداردهای مدرن، ماژولیزه کردن کد پایه، جایگزینی توابع ناامن، بهبود مدیریت حافظه و اجرای تست دقیق، توسعه دهندگان می توانند یک مونولیث شکننده را به یک سیستم قوی و قابل نگهداری تبدیل کنند. سرمایه گذاری در کاهش نرخ های نقص، سریعتر برای اعضای جدید، و استراتژی های یکپارچه سازی مدرن، با استفاده از این ماژول های تعمیر و نرم افزار های تعمیر و تمیز، این ابزار امروز، شروع می کند.