أسرع التقارير في الغرب المتوحش. وحفنة من البق بالإضافة ...

صورة 3

لم تنشر Microsoft مؤخرًا رمزًا مفتوح المصدر لمشاريعها الخاصة فحسب - بل تتبع شركات أخرى هذا الاتجاه أيضًا. بالنسبة لنا ، مطوري PVS-Studio ، هذه طريقة رائعة لاختبار المحلل مرة أخرى ، ومعرفة الأشياء المثيرة للاهتمام التي يمكن أن يجدها ، وإخطار مؤلفي المشروع بشأنها. اليوم ننظر داخل مشروع التقارير السريعة.

ما تم فحصه؟


FastReport هو مولد تقرير تم تطويره بواسطة Fast Reports . مكتوب بلغة C # ومتوافق مع .NET Standard 2.0+. تم نشر شفرة المصدر للمشروع مؤخرًا على GitHub ، حيث تم تنزيله لمزيد من التحليل.

تدعم التقارير استخدام النصوص والصور والخطوط والأشكال والرسوم البيانية والجداول والرموز الشريطية وما إلى ذلك. يمكن أن تكون صفحة واحدة وصفحات متعددة ، بما في ذلك ، بالإضافة إلى البيانات ، غلاف وصفحة خلفية. يمكن أن تكون مصادر البيانات XML و CSV و Json و MS SQL و MySql و Oracle و Postgres و MongoDB و Couchbase و RavenDB و SQLite.

هناك طرق مختلفة لإنشاء قوالب التقارير: من التعليمات البرمجية ؛ كملف XML. باستخدام مصمم عبر الإنترنت أو FastReport Designer Community Edition.

إذا لزم الأمر ، يمكن تنزيل المكتبات كحزم NuGet .

يمكنك قراءة المزيد عن ميزات المنتج في صفحة GitHub للمشروع .

صورة 8


لم تكن هناك مشاكل في تجميع المشروع - لقد قمت بتجميعه من Visual Studio 2017 ، حيث تم التحقق منه لاحقًا بواسطة المكون الإضافي PVS-Studio.

PVS-Studio هو محلل ثابت يبحث عن الأخطاء في C و C ++ و C # و Java code. عند تحليل رمز C # ، يمكنك استخدام أداة تحليل Visual Studio IDE باستخدام المكون الإضافي PVS-Studio ، أو يمكنك التحقق من المشروعات من سطر الأوامر ، والتي تستخدم الأداة المساعدة لسطر الأوامر PVS-Studio_Cmd.exe. إذا كنت ترغب في ذلك ، يمكنك تكوين التحليل على خادم البناء أو ربط نتائج التحليل بـ SonarQube .

حسنًا ، دعنا نرى ما كان مثيرًا للاهتمام هذه المرة.

نظرًا لأن المشروع صغير ، يجب ألا تعتمد على الكثير من الأخطاء المطبعية والأماكن المشبوهة. دعونا نلقي نظرة على ما تم العثور عليه ، وحتى نحاول إعادة إنتاج شيء ما في الممارسة.

نظارات وأكثر


استوفى الطريقة التالية:

public override string ToString() { if (_value == null) return null; return this.String; } 

تحذير PVS-Studio : V3108 لا يوصى بإرجاع "null" من طريقة "ToSting ()". البديل .1519

نعم ، لا يعد إرجاع قيمة خالية من أسلوب ToString () تم تجاوزه خطأ في حد ذاته ، ولكنه لا يزال نمطًا سيئًا. يشار إلى ذلك ، من بين أمور أخرى ، في الوثائق الواردة من Microsoft : يجب ألا يؤدي تجاوز ToString () إلى إرجاع سلسلة فارغة أو فارغة . قد يفاجأ المطورون الذين لا يتوقعون إرجاعًا فارغًا حيث أن القيمة المرتجعة لـ ToString () تفاجأ بشكل غير سار عندما يتم طرح ArgumentNullException أثناء تنفيذ الرمز أدناه (بشرط أن يتم استدعاء طريقة الامتداد لـ IEnumerable <T> ).

 Variant varObj = new Variant(); varObj.ToString().Contains(character); 

يمكنك العثور على خطأ في حقيقة أن المثال اصطناعي ، لكن جوهر هذا لا يتغير.

علاوة على ذلك ، تم التعليق على هذا الرمز على النحو التالي:

 /// <summary> /// Returns <see cref="String"/> property unless the value on the right /// is null. If the value on the right is null, returns "". /// </summary> /// <returns></returns> 

عفوًا بدلاً من إرجاع "" فارغة .

دعنا نواصل.

يوجد فصل FastString في المكتبة . الوصف: بديل سريع لـ StringBuilder . في الواقع ، تحتوي هذه الفئة على حقل من نوع StringBuilder . يستدعي مُنشئو فئة FastString الأسلوب Init ، الذي يُهيئ الحقل المقابل.

كود أحد البنائين:

 public FastString() { Init(initCapacity); } 

ورمز الطريقة الأولية :

 private void Init(int iniCapacity) { sb = new StringBuilder(iniCapacity); //chars = new char[iniCapacity]; //capacity = iniCapacity; } 

إذا رغبت في ذلك ، يمكنك الوصول إلى حقل sb من خلال خاصية StringBuilder :

 public StringBuilder StringBuilder { get { return sb; } } 

في المجموع ، يحتوي FastString على 3 مُنشئات:

 public FastString(); public FastString(int iniCapacity); public FastString(string initValue); 

لقد أظهرت بالفعل جسد المصمم الأول أنهم قاموا بالتخمين المتبقيين ، كما أعتقد ، ليس صعباً أيضًا. والآن الانتباه. خمن ما سيخرجه الكود التالي:

 FastString fs = new FastString(256); Console.WriteLine(fs.StringBuilder.Capacity); 

انتباه ، الجواب:

الصورة 2


بشكل غير متوقع؟ دعونا نلقي نظرة على جسم المنشئ المقابل:

 public FastString(int iniCapacity) { Init(initCapacity); } 

ينبغي للقراء المنتظمين لمقالاتنا أن يتطلعوا بالفعل إلى إيجاد مشكلة هنا. محلل العين (الرائحة ، المنطق ، يطلق عليه ما تريد) هو خدر بالتأكيد ، ووجد مشكلة: V3117 معلمة المُنشئ 'iniCapacity' غير مستخدمة. الحلقة 434

يا لها من مصادفة أن كود الفئة يحتوي على initCapacity حقل ثابت ، والذي يتم تمريره كوسيطة للأسلوب Init بدلاً من معلمة المُنشئ iniCapacity ...

 private const int initCapacity = 32; 

عند استخدام أسماء متشابهة ، يجب أن تكون شديد الحذر. أينما ارتكبت أخطاء تتعلق باستخدام أسماء مماثلة - مشاريع في C و C ++ و C # و Java - كانت هناك أخطاء إملائية من هذا النوع في كل مكان ...

بالمناسبة ، حول الأخطاء المطبعية.

دعونا نجعل المثال البسيط التالي ونرى كيف يعمل:

 static void Main(string[] args) { TextObject textObj = new TextObject(); textObj.ParagraphFormat = null; Console.WriteLine("Ok"); } 

كما قد تكون خمنت ، سيكون الناتج مختلفًا عن السلسلة "Ok" :)

أيهما؟ على سبيل المثال:

الصورة 1


تكمن المشكلة في خاصية ParagraphFormat وفي استخدام أسماء مشابهة:

 public ParagraphFormat ParagraphFormat { get { return paragraphFormat; } set { ParagraphFormat = value; } } 

تحذير PVS-Studio : V3110 العودية المحتملة لانهائية داخل خاصية "ParagraphFormat". 281

الخاصية ParagraphFormat عبارة عن مجمّع فوق حقل paraFormat. علاوة على ذلك ، يتم تهجئة ملحق الحصول على الخاصية بشكل صحيح ، ولكن تعيين ملحق الخاصية يحتوي على خطأ مطبعي مزعج: بدلاً من الحقل ، يحدث السجل في نفس الخاصية ، مما يؤدي إلى التكرار. مرة أخرى ، خطأ متعلق بأسماء مماثلة.

خذ بعين الاعتبار مقتطف الرمز التالي.

 public override Run Split(float availableWidth, out Run secondPart) { .... if (r.Width > availableWidth) { List<CharWithIndex> list = new List<CharWithIndex>(); for (int i = point; i < size; i++) list.Add(chars[i]); secondPart = new RunText(renderer, word, style, list, left + r.Width, charIndex); list.Clear(); for (int i = 0; i < point; i++) list.Add(chars[i]); r = new RunText(renderer, word, style, list, left, charIndex); return r; } else { List<CharWithIndex> list = new List<CharWithIndex>(); for (int i = point; i < size; i++) list.Add(chars[i]); secondPart = new RunText(renderer, word, style, list, left + r.Width, charIndex); list.Clear(); for (int i = 0; i < point; i++) list.Add(chars[i]); r = new RunText(renderer, word, style, list, left, charIndex); return r; } .... } 

تحذير PVS-Studio : V3004 عبارة "ثم" تعادل عبارة "آخر". HtmlTextRenderer.cs 2092

نسخ ولصق صغير ، والآن ، بغض النظر عن قيمة التعبير r.Width> availableWidth ، سيتم تنفيذ نفس الإجراءات. إما أن تزيل عبارة if أو تغير المنطق في أحد الفروع.

 public static string GetExpression(FindTextArgs args, bool skipStrings) { while (args.StartIndex < args.Text.Length) { if (!FindMatchingBrackets(args, skipStrings)) break; return args.FoundText; } return ""; } 

تحذير المحلل : V3020 "عودة" غير مشروطة داخل حلقة. CodeUtils.cs 262

نظرًا لبيان الإرجاع غير المشروط ، لن يتم تنفيذ أكثر من تكرار واحد للحلقة أعلاه. ربما تحول هذا الرمز بعد إعادة الهيكلة ، أو ربما أنها مجرد طريقة غير عادية للقيام بما يمكن القيام به بدون حلقة.

 private int FindBarItem(string c) { for (int i = 0; i < tabelle_cb.Length; i++) { if (c == tabelle_cb[i].c) return i; } return -1; } internal override string GetPattern() { string result = tabelle_cb[FindBarItem("A")].data + "0"; foreach (char c in text) { int idx = FindBarItem(c.ToString()); result += tabelle_cb[idx].data + "0"; } result += tabelle_cb[FindBarItem("B")].data; return result; } 

تحذير PVS-Studio : V3106 قيمة فهرس سلبية محتملة. يمكن أن تصل قيمة مؤشر "idx" إلى -1. 70 الرمز الشريطي

رمز خطير محتمل. يمكن أن ترجع أسلوب FindBarItem -1 إذا لم يجد العنصر الذي تم تمريره كمعلمة. في رمز الاستدعاء (طريقة GetPattern ) ، تتم كتابة هذه القيمة إلى متغير idx وتستخدم كمؤشر لصفيف tabelle_cb بدون التحقق الأولي. عند الوصول إلى الفهرس -1 ، سيتم طرح استثناء من نوع IndexOutOfRangeException .

دعنا نواصل.

 protected override void Finish() { .... if (saveStreams) { FinishSaveStreams(); } else { if (singlePage) { if (saveStreams) { int fileIndex = GeneratedFiles.IndexOf(singlePageFileName); DoPageEnd(generatedStreams[fileIndex]); } else { .... } .... } .... } .... } 

تحذير PVS-Studio : التعبير "saveStreams" في V3022 دائمًا ما يكون خاطئًا. 849

الكود أعلاه مع الحصول على قيمة fileIndex واستدعاء طريقة DoPageEnd لن يتم تنفيذه مطلقًا ، لأن نتيجة التعبير saveStreams الثاني في الكود ستكون دائمًا خاطئة .

من بين الأكثر إثارة للاهتمام ، ربما يكون هذا هو كل شيء (لم تتوقع مقالًا بروح تحليل مونو ؟). كانت هناك تحذيرات أخرى من المحللين ، لكنها لم تبدو مثيرة للاهتمام بما يكفي بالنسبة لي لتضمينها في المقالة (يبقى الجزء دائمًا خلف الكواليس).

إن معرفة التصميم سيكون مفيدًا لتحليلهم ، لذا من الأفضل أن ينظر المؤلفون إلى هذه التحذيرات بمفردهم. هذه تحذيرات مثل V3083 (استدعاء خطير محتمل لمعالجات الأحداث) ، V3022 (الحالة دائمًا صحيحة / خاطئة (في هذه الحالة ، غالبًا بسبب الأساليب التي تُرجع قيمة واحدة)) ، V3072 ، V3073 (العمل مع IDisposable ) وغيرها.

إذا كان أي من هذا غير ذي صلة ، فيمكنك:


الخاتمة


صورة 4


على الرغم من حقيقة أن المقالة كانت قصيرة ، فقد سررت "لمس" تحذيرات المحلل بيدي - لمعرفة كيف يتجلى المحلل في الممارسة العملية.

أتمنى لمؤلفي المشروع النجاح ، وتصحيح المشاكل المكتشفة ، وأريد أن أشيد بخطوة واحدة نحو مجتمع المصادر المفتوحة!

أنصح البقية بتجربة المحلل على التعليمات البرمجية الخاصة بك ومعرفة ما يمكن أن تجده مثيرًا للاهتمام.

كل التوفيق!



إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فالرجاء استخدام الرابط الخاص بترجمة: Sergey Vasiliev. أسرع التقارير في الغرب المتوحش - وحفنة من الحشرات ...

Source: https://habr.com/ru/post/ar431370/


All Articles