IntelliJ IDEA تحليل ثابت مقابل العقل البشري

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


private static List<Integer> process(Map<String, Integer> options, List<String> inputs) { List<Integer> res = new ArrayList<>(); int cur = -1; for (String str : inputs) { if (str.startsWith("-")) if (options.containsKey(str)) { if (cur == -1) cur = options.get(str); } else if (options.containsKey("+" + str)) { if (cur == -1) cur = res.isEmpty() ? -1 : res.remove(res.size() - 1); if (cur != -1) res.add(cur + str.length()); } } return res; } 

الرمز يشبه الكود ، يتم تحويل شيء ما ، شيء ما يجري ، لكن المحلل الثابت لم يعجبه. هنا نرى تحذيرين:


لقطة شاشة للكود


في تعبير res.isEmpty() يقول IDE أن الشرط صحيح دائمًا ، cur أن المهمة لا معنى لها ، لأن نفس القيمة موجودة بالفعل في هذا المتغير. من السهل أن نرى أن مشكلة المهمة هي نتيجة مباشرة للمشكلة الأولى. إذا كان res.isEmpty() صحيحًا دائمًا ، فسيتم تقليل السلسلة إلى


 if (cur == -1) cur = -1; 

هذا هو في الواقع لا لزوم لها. لكن لماذا التعبير دائمًا صحيح؟ بعد كل شيء ، res عبارة عن قائمة ، يتم ملؤها في نفس الدورة. يعتمد عدد التكرارات للحلقة والفروع التي ندخلها على معلمات الإدخال التي يتعذر على IDE معرفتها. يمكننا إضافة عنصر res عند التكرار السابق ، ثم لن تكون القائمة فارغة.


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


أولاً ، سنضع علامة على جميع الأسطر التي تتغير فيها حالة الأسلوب. هذا تغيير في متغير cur أو تغيير إلى قائمة res :


 private static List<Integer> process(Map<String, Integer> options, List<String> inputs) { List<Integer> res = new ArrayList<>(); int cur = -1; for (String str : inputs) { if (str.startsWith("-")) if (options.containsKey(str)) { if (cur == -1) cur = options.get(str); // A } else if (options.containsKey("+" + str)) { if (cur == -1) cur = res.isEmpty() ? -1 : // B res.remove(res.size() - 1); // C if (cur != -1) res.add(cur + str.length()); // D } } return res; } 

الأسطر 'A' و 'B' ( 'B' هي الفرع الأول من البيان الشرطي) ، قم بتغيير متغير cur ، و 'D' يغير القائمة ، و 'C' (الفرع الثاني من البيان الشرطي) يغير كل من القائمة cur المتغيرة. من المهم بالنسبة لنا ما إذا كان cur 1 يكمن وما إذا كانت القائمة فارغة. وهذا هو ، تحتاج إلى مراقبة أربع حالات:



سلسلة 'A' التغييرات cur إذا كان هناك -1 قبل ذلك. ونحن لا نعرف ما إذا كانت النتيجة ستكون -1 أم لا. لذلك ، هناك خياران ممكنان:



تعمل السلسلة 'B' أيضًا فقط إذا كانت قيمة cur هي -1 . في الوقت نفسه ، كما لاحظنا بالفعل ، من حيث المبدأ ، فهي لا تفعل شيئًا. لكننا نلاحظ مع ذلك هذا الضلع لاستكمال الصورة:



تعمل السلسلة 'C' ، مثلها في السابق ، مع cur == -1 بشكل تعسفي (مثل 'A' ). ولكن في الوقت نفسه ، لا يزال بإمكانه تحويل القائمة غير الفارغة إلى قائمة فارغة ، أو ترك غير فارغة إذا كان هناك أكثر من عنصر واحد.



أخيرًا ، تزيد السلسلة 'D' من حجم القائمة: يمكن أن تتحول فارغة إلى غير فارغة أو تزيد غير فارغة. لا يمكن أن يتحول غير فارغ إلى فارغ:



ماذا يعطينا هذا؟ لا شيء على الاطلاق. من غير المفهوم تمامًا سبب كون الشرط res.isEmpty() صحيحًا دائمًا.


في الواقع ، لقد بدأنا خطأ. في هذه الحالة ، لا يكفي مراقبة حالة كل متغير على حدة. تلعب الدول المرتبطة هنا دورًا مهمًا. لحسن الحظ ، نظرًا لحقيقة أن 2+2 = 2*2 ، لدينا أيضًا أربعة منهم فقط:



مع حد مزدوج ، قمت بتمييز الحالة الأولية التي لدينا عند إدخال الأسلوب. حسنا ، حاول مرة أخرى. 'A' يتغير أو يحفظ cur لأي res ، res لا يتغير:



'B' يعمل فقط مع cur == -1 && res.isEmpty() ولا يفعل شيئًا. مضيفا:



'C' يعمل فقط مع cur == -1 && !res.isEmpty() . في الوقت نفسه ، يتغير كل من cur و res بشكل تعسفي: بعد 'C' ينتهي بنا الأمر في أي ولاية:



أخيرًا ، يمكن أن تبدأ 'D' في cur != -1 && res.isEmpty() وجعل القائمة غير فارغة ، أو تبدأ في cur != -1 && !res.isEmpty() هناك:



للوهلة الأولى ، يبدو أنها ازدادت سوءًا: أصبح الرسم البياني أكثر تعقيدًا ، وليس من الواضح كيفية استخدامه. ولكن في الواقع ، نحن قريبون من الحل. تُظهر الأسهم الآن التدفق الكامل لتنفيذ طريقة عملنا. نظرًا لأننا نعرف الحالة التي بدأنا منها ، دعنا نسير على طول الأسهم:



وهنا يتم الكشف عن شيء مثير للاهتمام للغاية. لا يمكننا الوصول إلى الزاوية اليسرى السفلى. وبما أننا لا نستطيع الدخول فيه ، فهذا يعني أنه لا يمكننا السير على أي من الأسهم 'C' . وهذا يعني أن الخط 'C' قابل للتحقيق حقًا ، ويمكن تنفيذ 'B' . هذا ممكن فقط إذا كانت الحالة res.isEmpty() صحيحة دائمًا! IntelliJ IDEA هو الصحيح تماما. آسف ، محلل ، عبثا اعتقدت أنك كنت عربات التي تجرها الدواب. أنت ذكي جدًا لدرجة أنه يصعب عليّ ، أي شخص عادي ، اللحاق بك.


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


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


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

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


All Articles