اسم التنفيذ واسم النتيجة


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


بالطبع ، هناك أسماء لا تحمل معلومات على الإطلاق ، مثل int f(int x) . كما أنها لا تحتاج إلى استخدامها ، ولكنها لا تتعلق بها. في بعض الأحيان يحدث أن المعلومات الواردة في العنوان ممتلئة ، لكن لا فائدة منها على الإطلاق.


مثال 1: std :: log2p1 ()


في C ++ 20 ، تمت إضافة العديد من الوظائف الجديدة لعمليات البت إلى الرأس ، من بين أشياء أخرى std::log2p1 . يبدو مثل هذا:


 int log2p1(int i) { if (i == 0) return 0; else return 1 + int(std::log2(x)); } 

وهذا يعني ، بالنسبة لأي رقم طبيعي ، أن ترجع الدالة لوغاريتمها الثنائي بالإضافة إلى 1 ، وتُرجع إلى 0. وهذه ليست مشكلة مدرسة لمشغل if / else ، هذا أمر مفيد حقًا - الحد الأدنى لعدد البتات التي تناسبها هذه القيمة. مجرد التخمين حول هذا الموضوع باسم وظيفة يكاد يكون من المستحيل.


مثال 2: الأمراض المنقولة جنسيا :: bless ()


الآن لن يكون عن الاسم


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


 int obj = 0; int* ptr = &obj; ++ptr; //   

لكن مثل هذا التقييد يعلن عن وجود كمية كبيرة من السلوكيات الموجودة لأجل غير مسمى. على سبيل المثال ، فيما يلي تطبيق مبسط std::vector<T>::reserve() :


 void reserve(std::size_t n) { //      auto new_memory = (T*) ::operator new(n * sizeof(T)); //    … //   auto size = this->size(); begin_ = new_memory; //   end_ = new_memory + size; //     end_capacity_ = new_memory + n; //    } 

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


بطبيعة الحال ، فإن المبرمج ليس هو المسؤول. تكمن المشكلة في معيار C ++ نفسه ، الذي يعلن أن هذا الجزء المعقول من الكود هو سلوك غير محدد. لذلك ، يقترح P0593 تصحيح المعيار بإضافة بعض الوظائف (مثل ::operator new و std::malloc ) إلى القدرة على إنشاء صفائف حسب الحاجة. ستصبح جميع المؤشرات التي تم إنشاؤها من خلالها مؤشرات سحرية للصفائف ، ويمكن إجراء العمليات الحسابية معهم.


لا يزال ليس عن الأسماء ، انتظر ثانية.


لكن في بعض الأحيان ، تكون العمليات على المؤشرات مطلوبة عند العمل مع الذاكرة التي لم تخصصها إحدى هذه الوظائف. على سبيل المثال ، تعمل الدالة deallocate() بشكل أساسي مع الذاكرة الميتة ، حيث لا توجد كائنات على الإطلاق ، ولكن لا يزال يتعين عليك إضافة مؤشر وحجم المنطقة. في هذه الحالة ، عرض P0593 الدالة std::bless(void* ptr, std::size_t n) (كانت هناك وظيفة أخرى هناك ، تُسمى أيضًا bless ، ولكن هذا لا يتعلق بذلك). ليس له أي تأثير على جهاز كمبيوتر فعلي حقيقي ، ولكنه يخلق كائنات لجهاز تجريدي يسمح باستخدام حساب المؤشر.


الاسم std::bless كان مؤقتًا.


لذلك ، الاسم.


في كولونيا ، تم تكليف LEWG بمهمة البحث عن اسم لهذه الوظيفة. تم اقتراح الخيارات implicitly_create_objects() و implicitly_create_objects_as_needed() ، لأن هذا هو ما تفعله الوظيفة.


لم يعجبني هذه الخيارات.


مثال 3: الأمراض المنقولة جنسيا :: الجزئية ()


مثال مأخوذ من عرض كيت


هناك دالة std::sort ، والتي تقوم بفرز عناصر الحاوية:


 std::vector<int> vec = {3, 1, 5, 4, 2}; std::sort(vec.begin(), vec.end()); // vec == {1, 2, 3, 4, 5} 

هناك أيضًا std::partial_sort ، والتي تقوم بفرز جزء فقط من العناصر:


 std::vector<int> vec = {3, 1, 5, 4, 2}; std::partial_sort(vec.begin(), vec.begin() + 3, vec.end()); // vec == {1, 2, 3, ?, ?} ( ...4,5,  ...5,4) 

ولا يزال هناك std::partial_sort_copy ، std::partial_sort_copy ، والتي تقوم أيضًا بفرز جزء من العناصر ، ولكن في نفس الوقت لا تتغير الحاوية القديمة ، ولكن تنقل القيم إلى الجديدة:


 const std::vector<int> vec = {3, 1, 5, 4, 2}; std::vector<int> out; out.resize(3); std::partial_sort_copy(vec.begin(), vec.end(), out.begin(), out.end()); // out == {1, 2, 3} 

تزعم Kate أن std::partial_sort_copy ، وأنا أتفق معها.


اسم التنفيذ واسم النتيجة


لا يوجد أي من الأسماء المدرجة غير صحيح بالمعنى الدقيق للكلمة: فهم جميعًا يصفون تمامًا ما تقوم به الوظيفة. std::log2p1() تحسب حقًا اللوغاريتم الثنائي وتضيف واحدة إليه ؛ implicitly_create_objects() std::partial_sort_copy() implicitly_create_objects() ضمنيًا كائنات ، و std::partial_sort_copy() بترتيب الحاوية جزئيًا ونسخ النتيجة. ومع ذلك ، أنا لا أحب كل هذه الأسماء ، لأنها عديمة الفائدة .


لا يوجد مبرمج يجلس ويفكر "أتمنى أن أتمكن من أخذ اللوغاريتم الثنائي وإضافة واحد إليه". إنه بحاجة إلى معرفة عدد البتات التي bit_width ، وهو يبحث بنجاح في الاحواض عن شيء مثل bit_width . بحلول الوقت الذي يصل فيه إلى مستخدم المكتبة ، ما علاقة اللوغاريتم الثنائي به ، وقد كتب بالفعل تنفيذه (وعلى الأرجح أخطأ التحقق من الصفر). حتى إذا تبين أن std::log2p1 بمثابة بعض المعجزات في الكود ، يجب أن يفهم الرمز التالي مرة أخرى ما هو عليه ولماذا هو مطلوب. bit_width(max_value) مشكلة من هذا القبيل.


وبالمثل ، لا يحتاج أي شخص إلى "إنشاء كائنات ضمنيًا" أو "فرز نسخة المتجه جزئيًا" - يحتاجون إلى إعادة استخدام الذاكرة أو الحصول على أكبر 5 قيم بترتيب تنازلي. سيكون شيء مثل recycle_storage() (والذي تم اقتراحه أيضًا باسم std::bless ) و top_n_sorted() أكثر وضوحًا.


تستخدم Kate اسم تطبيق المصطلح std::partial_sort_copy() ، لكنه يناسب وظيفتين أخريين أيضًا. وصفت حقا تنفيذ اسمهم تماما. هذا فقط يحتاج المستخدم إلى اسم النتيجة - ما يحصل عليه من خلال استدعاء الوظيفة. بالنسبة إلى بنيتها الداخلية ، فهو لا يهتم ، إنه يريد فقط معرفة الحجم بالبت أو إعادة استخدام الذاكرة.


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


هذا يبدو مبتذل ، نعم. لكن إذا حكمنا من خلال std::log2p1() ، هذا أبعد ما يكون عن الوضوح للجميع. علاوة على ذلك ، في بعض الأحيان لا يكون الأمر بهذه البساطة.


مثال 4: std :: popcount ()


std::popcount() ، مثل std::log2p1() ، في C ++ 20 ، يُقترح إضافتها إلى <bit> . وهذا ، بالطبع ، اسم سيء للغاية. إذا كنت لا تعرف ما تفعله هذه الوظيفة ، فمن المستحيل تخمينها. ليس فقط الاختصار مربكًا (هناك فرقعة في الاسم ، ولكن pop / push ليس له علاقة به) - فك شفرة عدد السكان (عد السكان؟ عدد السكان؟) لا يساعد أيضًا.


من ناحية أخرى ، تعد std::popcount() مثالية لهذه الوظيفة لأنها تستدعي popcount لتعليمات التجميع. هذا ليس مجرد اسم التنفيذ - إنه وصفه الكامل.


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


بالمناسبة ، سؤال جيد: هل تفكر في الأسماء الملائمة للمبتدئين ، أم تتركها مألوفة لكبار السن؟


نهاية سعيدة؟


يقترح P1956 إعادة تسمية std::log2p1() إلى std::bit_width() . من المحتمل قبول هذا الاقتراح في C ++ 20. سيتم أيضًا إعادة تسمية std :: ceil2 و std :: floor2 إلى std :: bit_ceil () و std :: bit_floor () على التوالي. لم تكن أسماءهم القديمة أيضًا كبيرة ، ولكن لأسباب أخرى.


لم تحدد LEWG في كولونيا إما implicitly_create_objects[_as_needed] ولا recycle_storage كاسم لـ std::bless . قرروا عدم إدراج هذه الوظيفة في المعيار على الإطلاق. يمكن تحقيق نفس التأثير من خلال إنشاء مجموعة من البايت بشكل صريح ، لذلك ، كما يقولون ، ليست هناك حاجة إلى الوظيفة. لا يعجبني هذا لأن استدعاء std::recycle_storage() سيكون أكثر قابلية للقراءة. لا يزال يوجد std::bless() ، لكن يُطلق عليه الآن start_lifetime_as . أنا أحب ذلك. يجب أن تذهب إلى C ++ 23.


بالطبع ، std::partial_sort_copy() يتم إعادة تسمية std::partial_sort_copy() - بعد هذا الاسم ، دخلت المعيار مرة أخرى في عام 1998. ولكن std::log2p1 إصلاح std::log2p1 على الأقل ، وهذا ليس سيئًا.


في الخروج بأسماء الوظائف ، عليك التفكير في من سيستخدمها وما يريدها منها. على حد تعبير كيت ، فإن التسمية تتطلب التعاطف .

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


All Articles