به جای استفاده از آن برای یافتن باگ، تجزیه و تحلیل استاتیک را در فرآیند پیاده سازی کنید

با توجه به حجم زیادی از مطالب در مورد تجزیه و تحلیل استاتیک که به طور فزاینده ای مورد توجه من قرار می گیرد، من را وادار به نوشتن این مقاله کرد. اولا این وبلاگ استودیو PVS، که به طور فعال خود را در Habré با کمک بررسی خطاهای یافت شده توسط ابزار آنها در پروژه های منبع باز تبلیغ می کند. به تازگی PVS-studio پیاده سازی شده است پشتیبانی از جاواو البته توسعه دهندگان IntelliJ IDEA که تحلیلگر داخلی آن احتمالاً پیشرفته ترین برای جاوا امروزی است، نمی توانست دور بماند.

هنگام خواندن چنین بررسی هایی، این احساس را به شما دست می دهد که در مورد یک اکسیر جادویی صحبت می کنیم: دکمه را فشار دهید، و اینجاست - لیستی از نقص ها در مقابل چشمان شما. به نظر می رسد با بهبود تحلیلگرها، به طور خودکار باگ های بیشتری پیدا می شود و محصولات اسکن شده توسط این روبات ها بدون هیچ تلاشی بهتر و بهتر می شوند.

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

به جای استفاده از آن برای یافتن باگ، تجزیه و تحلیل استاتیک را در فرآیند پیاده سازی کنید
جغجغه (منبع: ویکیپدیا).

کاری که تحلیلگرهای استاتیک هرگز نمی توانند انجام دهند

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

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

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

تجزیه و تحلیل استاتیک در مورد یافتن باگ نیست

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

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

آیا این بدان معناست که نباید از آنالیز استاتیک استفاده کرد؟ البته که نه! و دقیقاً به همان دلیلی که ارزش بررسی هر رمز عبور جدید را دارد تا مطمئن شوید که در لیست توقف رمزهای عبور "ساده" گنجانده شده است.

تجزیه و تحلیل استاتیک بیشتر از یافتن باگ است

در واقع، مسائلی که عملاً با تجزیه و تحلیل حل می شوند بسیار گسترده تر هستند. پس از همه، به طور کلی، تجزیه و تحلیل استاتیک هر گونه تأیید کدهای منبع است که قبل از راه اندازی آنها انجام می شود. در اینجا چند کار وجود دارد که می توانید انجام دهید:

  • بررسی سبک کدنویسی به معنای وسیع کلمه. این شامل بررسی قالب‌بندی، جستجوی استفاده از پرانتزهای خالی/اضافی، تعیین آستانه‌های معیارهایی مانند تعداد خطوط/پیچیدگی چرخه‌ای یک روش و غیره می‌شود - هر چیزی که به طور بالقوه مانع خوانایی و نگهداری کد شود. در جاوا، چنین ابزاری Checkstyle، در Python - flake8 است. برنامه های این کلاس معمولاً "linters" نامیده می شوند.
  • نه تنها کدهای اجرایی قابل تجزیه و تحلیل هستند. فایل های منبعی مانند JSON، YAML، XML، .properties را می توان (و باید!) به طور خودکار از نظر اعتبار بررسی کرد. به هر حال، بهتر است متوجه شویم که ساختار JSON به دلیل برخی نقل قول‌های جفت‌نشده در مراحل اولیه تأیید خودکار Pull Request خراب است تا در زمان اجرای آزمایش یا زمان اجرا؟ ابزارهای مناسب در دسترس هستند: به عنوان مثال. YAMLlint, JSONLint.
  • کامپایل (یا تجزیه برای زبان های برنامه نویسی پویا) نیز نوعی تجزیه و تحلیل استاتیک است. به طور کلی، کامپایلرها قادر به تولید اخطارهایی هستند که نشان دهنده مشکلات کیفیت کد منبع هستند و نباید آنها را نادیده گرفت.
  • گاهی اوقات کامپایل چیزی بیش از کامپایل کردن کدهای اجرایی است. به عنوان مثال، اگر اسنادی در قالب دارید Ascii دکتر، سپس در لحظه تبدیل آن به HTML/PDF کنترل کننده AsciiDoctor (پلاگین Maven) می تواند هشدارهایی را صادر کند، به عنوان مثال، در مورد شکستگی لینک های داخلی. و این دلیل خوبی برای عدم پذیرش درخواست کشش با تغییرات اسناد است.
  • چک کردن املا نیز نوعی تحلیل استاتیک است. سودمند آسپل قادر به بررسی املا نه تنها در اسناد، بلکه در کدهای منبع برنامه (نظرات و کلمات واقعی) در زبان های برنامه نویسی مختلف از جمله C/C++، جاوا و پایتون است. خطای املایی در رابط کاربری یا مستندات نیز یک نقص است!
  • تست های پیکربندی (درباره آنچه هستند - ببینید. این и این گزارش‌ها، اگرچه در زمان اجرای تست واحد مانند pytest اجرا می‌شوند، اما در واقع نوعی تجزیه و تحلیل استاتیک هستند، زیرا کدهای منبع را در طول اجرای خود اجرا نمی‌کنند.

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

کدام یک از این انواع تحلیل استاتیک را باید در پروژه خود استفاده کنید؟ البته هر چه بیشتر بهتر! نکته اصلی اجرای صحیح آن است که در ادامه مورد بحث قرار خواهد گرفت.

خط لوله تحویل به عنوان فیلتر چند مرحله ای و تجزیه و تحلیل استاتیک به عنوان اولین مرحله آن

استعاره کلاسیک برای ادغام پیوسته خط لوله ای است که از طریق آن تغییرات از تغییر کد منبع تا تحویل به تولید جریان می یابد. توالی استاندارد مراحل در این خط لوله به صورت زیر است:

  1. تجزیه و تحلیل استاتیک
  2. کامپیله
  3. تست های واحد
  4. تست های ادغام
  5. تست های رابط کاربری
  6. بررسی دستی

تغییرات رد شده در مرحله نهم خط لوله به مرحله N+1 منتقل نمی شود.

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

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

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

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

به جای استفاده از آن برای یافتن باگ، تجزیه و تحلیل استاتیک را در فرآیند پیاده سازی کنید
فیلتر چند مرحله ای منبع: در ویکیانبار موجود

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

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

هدف از "تله گل" این است که آبشارهای بعدی را از ابتلا به نقص های بسیار فاحش خلاص کند. به عنوان مثال، حداقل، شخصی که بازبینی کد را انجام می دهد نباید با کدهای با فرمت نادرست و نقض استانداردهای کدگذاری تعیین شده (مانند پرانتزهای اضافی یا شاخه های خیلی عمیق تو در تو) منحرف شود. باگ‌هایی مانند NPE باید توسط تست‌های واحد شناسایی شوند، اما اگر حتی قبل از آزمایش آنالیزور به ما نشان دهد که احتمالاً یک باگ اتفاق می‌افتد، این امر به طور قابل توجهی سرعت رفع آن را افزایش می‌دهد.

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

پیاده سازی در یک پروژه قدیمی

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

هنگامی که برای اولین بار از آنالایزر استفاده می شود، تعداد زیادی هشدار در هر پروژه ای ایجاد می کند که اکثریت قریب به اتفاق آنها به عملکرد مناسب محصول مربوط نمی شود. اصلاح همه این نظرات به یکباره غیرممکن است و بسیاری از آنها ضروری نیستند. پس از همه، ما می دانیم که محصول ما به عنوان یک کل، حتی قبل از معرفی تجزیه و تحلیل استاتیک، کار می کند!

در نتیجه، بسیاری از آنها محدود به استفاده گاه به گاه از تجزیه و تحلیل استاتیک هستند، یا از آن فقط در حالت اطلاعات استفاده می کنند، زمانی که گزارش تحلیلگر به سادگی در حین مونتاژ صادر می شود. این معادل عدم وجود هرگونه تحلیل است، زیرا اگر ما قبلاً هشدارهای زیادی داشته باشیم، وقوع یک هشدار دیگر (هرچقدر هم جدی باشد) هنگام تغییر کد مورد توجه قرار نمی گیرد.

روش های زیر برای معرفی دروازه های با کیفیت شناخته شده است:

  • تعیین محدودیت برای تعداد کل اخطارها یا تعداد اخطارها تقسیم بر تعداد خطوط کد. این عملکرد ضعیفی دارد، زیرا چنین دروازه ای آزادانه اجازه می دهد تا تغییرات با نقص های جدید از آن عبور کنند، تا زمانی که از حد آنها فراتر نرود.
  • در یک لحظه معین، تمام اخطارهای قدیمی موجود در کد را که نادیده گرفته شده‌اند، برطرف می‌کنند و در صورت بروز هشدارهای جدید از ساختن خودداری می‌کنند. این قابلیت توسط PVS-studio و برخی منابع آنلاین، به عنوان مثال، Codacy ارائه شده است. من فرصت کار در PVS-studio را نداشتم، در مورد تجربه من با Codacy، مشکل اصلی آنها این است که تعیین خطای "قدیمی" و چیستی "جدید" یک الگوریتم نسبتا پیچیده است که همیشه کار نمی کند. به درستی، به خصوص اگر فایل ها به شدت تغییر کرده یا نامشان تغییر کند. در تجربه من، Codacy می‌توانست هشدارهای جدید را در یک درخواست کشش نادیده بگیرد، در حالی که در همان زمان درخواست کشش را به دلیل هشدارهایی که به تغییرات در کد یک PR معین مربوط نبودند، پاس نمی‌کرد.
  • به نظر من موثرترین راه حل همان راه حلی است که در کتاب توضیح داده شده است تحویل مداوم "روش جغجغه زنی". ایده اصلی این است که تعداد هشدارهای تجزیه و تحلیل ایستا ویژگی هر نسخه است و فقط تغییراتی مجاز هستند که تعداد کل هشدارها را افزایش ندهند.

جغجغه دار

این روش کار می کند:

  1. در مرحله اولیه، یک رکورد در ابرداده در مورد انتشار تعداد اخطارها در کد یافت شده توسط تحلیلگرها ایجاد می شود. بنابراین، هنگامی که upstream می‌سازید، مدیر مخزن شما نه تنها «انتشار 7.0.2» را می‌نویسد، بلکه «انتشار 7.0.2 حاوی 100500 هشدار استایل چک» را می‌نویسد. اگر از یک مدیر مخزن پیشرفته (مانند Artifactory) استفاده می کنید، ذخیره چنین ابرداده هایی در مورد نسخه خود آسان است.
  2. اکنون هر درخواست کشش، زمانی که ساخته می‌شود، تعداد اخطارهای حاصل را با تعداد هشدارهای موجود در نسخه فعلی مقایسه می‌کند. اگر روابط عمومی منجر به افزایش این تعداد شود، کد از دروازه کیفیت برای تجزیه و تحلیل استاتیک عبور نمی کند. اگر تعداد اخطارها کاهش یابد یا تغییر نکند، می گذرد.
  3. در نسخه بعدی، تعداد اخطارهای محاسبه شده مجدداً در فراداده انتشار ثبت می شود.

بنابراین کم کم اما به طور پیوسته (مانند زمانی که یک جغجغه کار می کند)، تعداد اخطارها به صفر خواهد رسید. البته سیستم را می توان با ارائه اخطار جدید اما اصلاح اخطار دیگری فریب داد. این طبیعی است، زیرا در یک مسافت طولانی نتیجه می دهد: اخطارها، به عنوان یک قاعده، نه به صورت جداگانه، بلکه در یک گروه از یک نوع خاص به یکباره تصحیح می شوند، و همه هشدارهای به راحتی قابل جابجایی به سرعت حذف می شوند.

این نمودار مجموع اخطارهای Checkstyle را برای شش ماه کارکرد چنین "ضلمه ای" روی آن نشان می دهد یکی از پروژه های منبع باز ما. تعداد اخطارها یک مرتبه کاهش یافته است و این به طور طبیعی و به موازات توسعه محصول اتفاق افتاد!

به جای استفاده از آن برای یافتن باگ، تجزیه و تحلیل استاتیک را در فرآیند پیاده سازی کنید

من از نسخه اصلاح شده این روش استفاده می کنم، به طور جداگانه اخطارها را بر اساس ماژول پروژه و ابزار تجزیه و تحلیل شمارش می کنم، که منجر به یک فایل YAML با متادیتای ساخت می شود که چیزی شبیه به این است:

celesta-sql:
  checkstyle: 434
  spotbugs: 45
celesta-core:
  checkstyle: 206
  spotbugs: 13
celesta-maven-plugin:
  checkstyle: 19
  spotbugs: 0
celesta-unit:
  checkstyle: 0
  spotbugs: 0

در هر سیستم پیشرفته CI، Ratchet را می توان برای هر ابزار تحلیل استاتیکی بدون تکیه بر افزونه ها و ابزارهای شخص ثالث پیاده سازی کرد. هر تحلیلگر گزارش خود را در قالب متن یا XML ساده تولید می کند که به راحتی قابل تجزیه و تحلیل است. تنها چیزی که باقی می ماند نوشتن منطق لازم در اسکریپت CI است. می‌توانید ببینید که چگونه این کار در پروژه‌های منبع باز ما مبتنی بر جنکینز و Artifactory پیاده‌سازی می‌شود اینجا یا اینجا. هر دو نمونه به کتابخانه بستگی دارد ratchetlib: روش countWarnings() تگ های xml را در فایل های تولید شده توسط Checkstyle و Spotbugs به روش معمول می شمارد و compareWarningMaps() همان جغجغه را پیاده سازی می کند و هنگامی که تعداد اخطارها در هر یک از دسته ها افزایش می یابد، خطا ایجاد می کند.

یک پیاده سازی جالب از "ratchet" برای تجزیه و تحلیل املای نظرات، کلمات متنی و اسناد با استفاده از aspell امکان پذیر است. همانطور که می دانید، هنگام بررسی املا، همه کلمات ناشناخته برای فرهنگ لغت استاندارد نادرست نیستند، آنها را می توان به فرهنگ لغت کاربر اضافه کرد. اگر یک فرهنگ لغت سفارشی را بخشی از کد منبع پروژه می‌سازید، دروازه کیفیت املا را می‌توان به این شکل فرموله کرد: اجرای aspell با یک فرهنگ لغت استاندارد و سفارشی نباید هیچ غلط املایی پیدا نکنید

درباره اهمیت تعمیر نسخه آنالایزر

در پایان، نکته ای که باید به آن توجه داشت این است که مهم نیست که چگونه آنالیز را در خط لوله تحویل خود پیاده سازی می کنید، نسخه آنالایزر باید ثابت باشد. اگر به تحلیلگر اجازه دهید به طور خود به خود به روز شود، در هنگام مونتاژ درخواست کشش بعدی، ممکن است نقص های جدیدی ظاهر شوند که مربوط به تغییرات کد نیستند، اما مربوط به این واقعیت است که تحلیلگر جدید به سادگی قادر به یافتن نقص های بیشتری است - و این روند شما را برای پذیرش درخواست های کشش شکست خواهد داد. ارتقاء یک تحلیلگر باید یک اقدام آگاهانه باشد. با این حال، تثبیت سفت و سخت نسخه هر جزء مونتاژ به طور کلی یک نیاز ضروری و موضوعی برای بحث جداگانه است.

یافته ها

  • تجزیه و تحلیل استاتیک اشکالات را برای شما پیدا نمی کند و کیفیت محصول شما را در نتیجه یک برنامه بهبود نمی بخشد. تأثیر مثبت بر کیفیت تنها با استفاده مداوم از آن در طول فرآیند تحویل حاصل می شود.
  • یافتن اشکالات به هیچ وجه وظیفه اصلی تجزیه و تحلیل نیست؛ اکثریت قریب به اتفاق توابع مفید در ابزارهای منبع باز موجود هستند.
  • گیت های با کیفیت را بر اساس نتایج تجزیه و تحلیل استاتیک در اولین مرحله خط لوله تحویل، با استفاده از یک "جغجغه" برای کد قدیمی پیاده سازی کنید.

مراجع

  1. تحویل مداوم
  2. A. Kudryavtsev: تجزیه و تحلیل برنامه: چگونه بفهمیم که شما یک برنامه نویس خوب هستید گزارش در مورد روش های مختلف تحلیل کد (نه فقط ایستا!)

منبع: www.habr.com

اضافه کردن نظر