توصيات عامة
التنسيق
clang-format.
2. المسافة البادئة هي 4 مسافات. قم بضبط بيئة التطوير لديك بحيث تُضيف مفتاح Tab أربع مسافات.
3. يجب أن تكون الأقواس المعقوفة الفاتحة والغالقة في سطر منفصل.
statement واحدة، يمكن وضعه في سطر واحد. ضع مسافات حول الأقواس المعقوفة (باستثناء المسافة في نهاية السطر).
if وfor وwhile وغيرها، تُضاف مسافة قبل القوس الافتتاحي (خلافًا لاستدعاءات الدوال).
+, -, *, /, %, …) والعامل الثلاثي ?:.
.، ->.
إذا لزم الأمر، يمكن نقل المعامل إلى السطر التالي. في هذه الحالة، تزداد المسافة البادئة أمامه.
11. لا تستخدم مسافة للفصل بين العوامل الأحادية (--, ++, *, &, …) والمعامل.
12. ضع مسافة بعد الفاصلة، لا قبلها. وتسري القاعدة ذاتها على الفاصلة المنقوطة داخل تعبير for.
13. لا تستخدم مسافات للفصل في المعامل [].
14. في تعبير template <...>، ضع مسافة بين template و<؛ ولا مسافات بعد < أو قبل >.
public وprivate وprotected على نفس مستوى class/struct، وأضف مسافة بادئة لبقية الكود.
namespace مستخدمًا في الملف بأكمله ولم يكن هناك شيء آخر ذو أهمية، فلا حاجة إلى مسافة بادئة داخل namespace.
17. إذا كانت الكتلة الخاصة بـ if أو for أو while أو أي تعبير آخر تحتوي على جملة واحدة statement فقط، فإن الأقواس المعقوفة تكون اختيارية. ضع الجملة statement في سطر منفصل بدلاً من ذلك. تنطبق هذه القاعدة أيضاً على if وfor وwhile المتداخلة …
لكن إذا احتوت statement الداخلية على أقواس معقوفة أو else، وجب كتابة الكتلة الخارجية بين أقواس معقوفة.
A const (المرتبط بقيمة) قبل اسم النوع.
* و &.
using لتعريف أسماء مستعارة لها (باستثناء أبسط الحالات).
بعبارة أخرى، لا تُحدَّد معلمات القالب إلا في using ولا تُكرَّر في الشيفرة.
يمكن التصريح عن using محليًا، مثلًا داخل دالة.
صنف وبنية، اجمع الأعضاء والدوال كلًّا على حدة داخل كل مستوى من مستويات الإتاحة.
30. في صنف وبنية الصغيرة، ليس من الضروري فصل تعريف method عن تنفيذها.
وينطبق الأمر نفسه على method الصغيرة في أي صنف أو بنية.
في Template صنف وبنية، لا تفصل تعريفات method عن تنفيذها (لأنه بخلاف ذلك يجب تعريفها في وحدة الترجمة نفسها).
31. يمكنك لفّ الأسطر عند 140 حرفًا بدلًا من 80.
32. استخدم دائمًا معاملي الزيادة/الإنقاص السابقين إذا لم تكن الصيغة اللاحقة مطلوبة.
التعليقات
///، وتبدأ التعليقات متعددة الأسطر بـ /**. وتُعد هذه التعليقات “توثيقًا”.
ملاحظة: يمكنك استخدام Doxygen لإنشاء التوثيق من هذه التعليقات. لكن Doxygen لا يُستخدم عمومًا لأن التنقل في الشيفرة داخل بيئة التطوير المتكاملة (IDE) أكثر ملاءمة.
9. يجب ألا تحتوي التعليقات متعددة الأسطر على أسطر فارغة في البداية أو النهاية (باستثناء السطر الذي يُغلق التعليق متعدد الأسطر).
10. عند التعليق على الشيفرة لإلغائها مؤقتًا، استخدم التعليقات العادية، وليس تعليقات “التوثيق”.
11. احذف الأجزاء المُعلَّق عليها من الشيفرة قبل إجراء commit.
12. لا تستخدم ألفاظًا نابية في التعليقات أو الشيفرة.
13. لا تستخدم الأحرف الكبيرة. ولا تُكثر من علامات الترقيم.
الأسماء
camelCase بحيث يبدأ بحرف صغير.
using بالطريقة نفسها التي تُسمّى بها الأصناف.
5. أسماء وسيطات أنواع القوالب: في الحالات البسيطة، استخدم T؛ T، U؛ T1، T2.
في الحالات الأكثر تعقيدًا، اتبع قواعد تسمية الأصناف، أو أضف البادئة T.
N في الحالات البسيطة.
I.
define والثوابت العامة بصيغة ALL_CAPS مع استخدام الشرطة السفلية.
- بالنسبة إلى أسماء المتغيرات، يجب أن يُكتب الاختصار بأحرف صغيرة
mysql_connection(وليسmySQL_connection). - بالنسبة إلى أسماء الأصناف والدوال، احتفظ بالأحرف الكبيرة في الاختصار
MySQLConnection(وليسMySqlConnection).
enum، استخدم CamelCase بحيث يبدأ بحرف كبير. كما يُقبل أيضًا استخدام ALL_CAPS. إذا كان enum غير محلي، فاستخدم enum class.
AST، SQL.
ليس NVDH (مجرد حروف عشوائية)
الكلمات غير المكتملة مقبولة إذا كانت الصيغة المختصرة شائعة الاستخدام.
يمكنك أيضًا استخدام اختصار إذا كان الاسم الكامل مذكورًا بجواره في التعليقات.
17. يجب أن تحمل أسماء الملفات التي تحتوي على شيفرة مصدرية C++ الامتداد .cpp. ويجب أن تحمل ملفات الترويسة الامتداد .h.
18. يُكتب اسم المنتج ClickHouse كلمةً واحدة، بحرف C كبير وحرف H كبير. يقبل فحص النمط clickhouse_spelling أيضًا التهجئات الاصطلاحية للرموز clickhouse وCLICKHOUSE؛ وتُعد جميع الصيغ الأخرى أخطاءً إملائية. ويتحقق من الشيفرة والتعليقات والرسائل والوثائق وأسماء الملفات. استخدم clickhouse للرموز التي تستخدم الأحرف الصغيرة فقط، مثل أسماء الحزم والملفات التنفيذية وأسماء المضيفين؛ واستخدم ClickHouse في المعرّفات بأسلوب CamelCase؛ واستخدم CLICKHOUSE للماكرو ومتغيرات البيئة.
ClickHouse، clickhouse-client، CLICKHOUSE_DATABASE
ليس Clickhouse، clickHouse، click_house، CLICK_HOUSE، click-house، Click House
كيفية كتابة الشيفرة
delete) إلا في شيفرة المكتبة.
في شيفرة المكتبة، لا يجوز استخدام العامل delete إلا داخل الدوال الهدّامة.
في شيفرة التطبيق، يجب أن يحرّر الذاكرةَ الكائنُ الذي يملكها.
أمثلة:
- أسهل طريقة هي وضع كائن على المكدس، أو جعله عضوًا في
صنفأخرى. - عند وجود عدد كبير من الكائنات الصغيرة، استخدم الحاويات.
- للتحرير التلقائي لعدد قليل من الكائنات الموجودة على الكومة، استخدم
shared_ptr/unique_ptr.
RAII وراجع ما سبق.
3. معالجة الأخطاء.
استخدم الاستثناءات. في معظم الحالات، كل ما تحتاج إليه هو رمي استثناء، ولا تحتاج إلى التقاطه (بفضل RAII).
في تطبيقات معالجة البيانات غير المتصلة، يكون من المقبول غالبًا عدم التقاط الاستثناءات.
في الخوادم التي تتعامل مع طلبات المستخدمين، يكفي عادةً التقاط الاستثناءات عند المستوى الأعلى من معالج الاتصال.
في دوال الخيوط، ينبغي التقاط جميع الاستثناءات والاحتفاظ بها لإعادة رميها في الخيط الرئيسي بعد join.
errno، تحقّق دائمًا من النتيجة وألقِ استثناءً في حال حدوث خطأ.
- أنشئ دالة (
done()أوfinalize()) تنفّذ مسبقًا كل العمل الذي قد يؤدي إلى استثناء. إذا جرى استدعاء تلك الدالة، فلا ينبغي أن تحدث أي استثناءات لاحقًا في الـ destructor. - يمكن وضع المهام شديدة التعقيد (مثل إرسال الرسائل عبر الشبكة) في دالة منفصلة يجب على مستخدم الـ صنف استدعاؤها قبل التدمير.
- إذا حدث استثناء في الـ destructor، فمن الأفضل تسجيله بدلًا من إخفائه (إذا كان logger متاحًا).
- في التطبيقات البسيطة، من المقبول الاعتماد على
std::terminate(في حالاتnoexceptالافتراضية في C++11) للتعامل مع الاستثناءات.
- حاول الحصول على أفضل أداء ممكن على نواة CPU واحدة. بعد ذلك يمكنك جعل الشيفرة متوازية إذا لزم الأمر.
- استخدم thread pool لمعالجة الطلبات. حتى الآن، لم تكن لدينا أي مهام تتطلب تبديل السياق في فضاء المستخدم.
joinAll).
إذا كانت المزامنة مطلوبة، ففي معظم الحالات يكفي استخدام mutex ضمن lock_guard.
في الحالات الأخرى، استخدم بدائيات المزامنة الخاصة بالنظام. لا تستخدم الانتظار النشط.
يجب استخدام العمليات الذرية فقط في أبسط الحالات.
لا تحاول تنفيذ هياكل بيانات خالية من الأقفال إلا إذا كان ذلك مجال خبرتك الأساسي.
9. المؤشرات مقابل المراجع.
في معظم الحالات، فضّل المراجع.
10. const.
استخدم المراجع الثابتة، والمؤشرات إلى الثوابت، وconst_iterator، ودوال const.
اعتبر const هو الخيار الافتراضي، ولا تستخدم غير const إلا عند الضرورة.
عند تمرير المتغيرات بالقيمة، لا يكون استخدام const منطقيًا عادةً.
11. unsigned.
استخدم unsigned عند الحاجة.
12. الأنواع العددية.
استخدم الأنواع UInt8 وUInt16 وUInt32 وUInt64 وInt8 وInt16 وInt32 وInt64، بالإضافة إلى size_t وssize_t وptrdiff_t.
لا تستخدم هذه الأنواع للأعداد: signed/unsigned long وlong long وshort وsigned/unsigned char وchar.
13. تمرير الوسائط.
مرّر القيم المعقّدة بالقيمة إذا كان سيتم نقلها، واستخدم std::move؛ ومرّرها بالمرجع إذا كنت تريد تحديث القيمة داخل حلقة.
إذا كانت الدالة تنقل ملكية كائن أُنشئ على heap، فاجعل نوع الوسيط shared_ptr أو unique_ptr.
14. قيم الإرجاع.
في معظم الحالات، استخدم فقط return. لا تكتب return std::move(res).
إذا كانت الدالة تخصّص كائنًا على heap ثم تعيده، فاستخدم shared_ptr أو unique_ptr.
في حالات نادرة (مثل تحديث قيمة داخل حلقة)، قد تحتاج إلى إرجاع القيمة عبر وسيط. في هذه الحالة، يجب أن يكون الوسيط مرجعًا.
namespace.
لا حاجة إلى استخدام namespace منفصل لشيفرة التطبيق.
ولا تحتاج المكتبات الصغيرة إلى ذلك أيضًا.
أما في المكتبات المتوسطة والكبيرة، فضع كل شيء داخل namespace.
في ملف .h الخاص بالمكتبة، يمكنك استخدام namespace detail لإخفاء تفاصيل التنفيذ غير اللازمة لشيفرة التطبيق.
وفي ملف .cpp، يمكنك استخدام static أو namespace مجهول لإخفاء الرموز.
كذلك، يمكن استخدام namespace مع enum لمنع تسرّب الأسماء المقابلة إلى namespace خارجي (لكن من الأفضل استخدام enum class).
16. التهيئة المؤجلة.
إذا كانت التهيئة تتطلب معاملات، فعادةً لا ينبغي لك كتابة مُنشئ افتراضي.
وإذا احتجت لاحقًا إلى تأجيل التهيئة، فيمكنك إضافة مُنشئ افتراضي ينشئ كائنًا غير صالح. أو، إذا كان عدد الكائنات قليلًا، يمكنك استخدام shared_ptr/unique_ptr.
std::string و char *. لا تستخدم std::wstring و wchar_t.
19. التسجيل.
راجع الأمثلة في مختلف أنحاء الشيفرة.
قبل إجراء commit، احذف كل رسائل السجل غير المفيدة ورسائل Debug، وأي أنواع أخرى من مخرجات Debug.
يجب تجنّب التسجيل داخل الحلقات، حتى على مستوى Trace.
يجب أن تكون السجلات قابلة للقراءة عند أي مستوى من مستويات التسجيل.
ينبغي استخدام التسجيل في شيفرة التطبيق فقط، في الغالب.
يجب أن تُكتب رسائل السجل باللغة الإنجليزية.
ويُفضَّل أن يكون السجل مفهومًا لمسؤول النظام.
لا تستخدم الألفاظ النابية في السجل.
استخدم ترميز UTF-8 في السجل. وفي حالات نادرة، يمكنك استخدام محارف غير ASCII في السجل.
20. الإدخال والإخراج.
لا تستخدم iostreams في الحلقات الداخلية الحسّاسة لأداء التطبيق (ولا تستخدم stringstream أبدًا).
استخدم مكتبة DB/IO بدلًا من ذلك.
21. التاريخ والوقت.
راجع مكتبة DateLUT.
22. include.
استخدم دائمًا #pragma once بدلًا من حواجز التضمين.
23. using.
لا تستخدم using namespace. يمكنك استخدام using لشيء محدد، لكن اجعله محليًا داخل صنف أو دالة.
24. لا تستخدم trailing return type للدوال إلا عند الضرورة.
virtual في الصنف الأساسي، لكن اكتب override بدلًا من virtual في الأصناف الفرعية.
ميزات C++ غير المستخدمة
المنصة
clang. وقت كتابة هذا النص (مارس 2025)، تُترجم الشفرة البرمجية باستخدام clang بالإصدار >= 19.
تُستخدم المكتبة القياسية (libc++).
4. نظام التشغيل: Ubuntu Linux، على ألا يكون أقدم من Precise.
5. تُكتب الشفرة البرمجية لمعمارية CPU من نوع x86_64.
مجموعة تعليمات CPU هي الحد الأدنى المدعوم بين خوادمنا. وهي حاليًا SSE 4.2.
6. استخدم أعلام الترجمة -Wall -Wextra -Werror -Weverything مع بعض الاستثناءات.
7. استخدم الربط الثابت مع جميع المكتبات باستثناء تلك التي يصعب ربطها ربطًا ثابتًا (راجع خرج الأمر ldd).
8. تُطوَّر الشفرة البرمجية ويُجرى Debug لها باستخدام إعدادات الإصدار.
الأدوات
gdb وvalgrind (memcheck) وstrace و-fsanitize=... أو tcmalloc_minimal_debug.
3. لتحليل الأداء، استخدم Linux Perf أو valgrind (callgrind) أو strace -cf.
4. الشيفرة المصدرية موجودة في Git.
5. يُستخدم CMake للبناء.
6. تُصدر البرامج باستخدام حزم deb.
7. يجب ألا تؤدي عمليات commit على master إلى كسر عملية البناء.
مع أن مراجعات محددة فقط تُعد صالحة للعمل.
8. نفّذ عمليات commit بأكبر قدر ممكن من التكرار، حتى لو كانت الشيفرة جاهزة جزئيًا فقط.
استخدم الفروع لهذا الغرض.
إذا كانت الشيفرة الخاصة بك في فرع master غير قابلة للبناء بعد، فاستبعدها من عملية البناء قبل push. ستحتاج إلى إكمالها أو إزالتها خلال بضعة أيام.
9. بالنسبة إلى التغييرات غير البسيطة، استخدم الفروع وانشرها على الخادم.
10. تُزال الشيفرة غير المستخدمة من المستودع.
المكتبات
boost وPoco.
2. لا يُسمح باستخدام مكتبات من حزم نظام التشغيل، كما لا يُسمح باستخدام المكتبات المثبّتة مسبقًا. يجب وضع جميع المكتبات على هيئة شيفرة مصدرية في دليل contrib وبنائها مع ClickHouse. راجع إرشادات إضافة مكتبات خارجية جديدة وصيانتها لمزيد من التفاصيل.
3. تُمنح الأفضلية دائمًا للمكتبات المستخدمة مسبقًا.
توصيات عامة
using بدلًا من الأصناف أو البُنى.
5. إذا أمكن، فلا تكتب copy constructors أو assignment operators أو destructors (باستثناء destructor افتراضي، إذا كان الصنف يحتوي على دالة افتراضية واحدة على الأقل) أو move constructors أو move assignment operators. وبعبارة أخرى، يجب أن تعمل الدوال التي يُنشئها المصرّف تلقائيًا بشكل صحيح. يمكنك استخدام default.
6. يُشجَّع تبسيط الشيفرة. قلّل حجم الشيفرة قدر الإمكان.
توصيات إضافية
std:: صراحةً للأنواع القادمة من stddef.h
غير مستحسن. بمعنى آخر، نوصي بكتابة size_t بدلًا من std::size_t لأنه أقصر.
ومع ذلك، لا بأس بإضافة std::.
2. عدم التصريح بـ std:: صراحةً للدوال القادمة من مكتبة C القياسية
غير مستحسن. بمعنى آخر، اكتب memcpy بدلًا من std::memcpy.
والسبب هو وجود دوال مشابهة غير قياسية، مثل memmem. ونحن نستخدم هذه الدوال أحيانًا. وهذه الدوال غير موجودة في namespace std.
إذا كتبت std::memcpy بدلًا من memcpy في كل مكان، فسيبدو memmem من دون std:: غريبًا.
ومع ذلك، يمكنك استخدام std:: إذا كنت تفضل ذلك.
3. استخدام دوال من C عندما تكون الدوال نفسها متاحة في مكتبة C++ القياسية.
هذا مقبول إذا كان أكثر كفاءة.
على سبيل المثال، استخدم memcpy بدلًا من std::copy لنسخ كتل كبيرة من الذاكرة.
4. وسيطات الدالة متعددة الأسطر.
أيٌّ من أنماط التفاف الأسطر التالية مسموح به: