الانتقال إلى المحتوى الرئيسي

توصيات عامة

فيما يلي توصيات وليست متطلبات. إذا كنت تُحرِّر الشيفرة، فمن المنطقي اتباع تنسيق الشيفرة الحالية. يُعد أسلوب كتابة الشيفرة ضروريًا لتحقيق الاتساق. فالانسجام في الأسلوب يجعل قراءة الشيفرة أسهل، كما يجعل البحث فيها أسهل أيضًا. كثير من القواعد ليست لها أسباب منطقية، بل تفرضها الممارسات الراسخة.

التنسيق

1. يتم تنسيق معظم الكود تلقائيًا عبر clang-format. 2. المسافة البادئة هي 4 مسافات. قم بضبط بيئة التطوير لديك بحيث تُضيف مفتاح Tab أربع مسافات. 3. يجب أن تكون الأقواس المعقوفة الفاتحة والغالقة في سطر منفصل.
inline void readBoolText(bool & x, ReadBuffer & buf)
{
    char tmp = '0';
    readChar(tmp, buf);
    x = tmp != '0';
}
4. إذا كان متن الدالة بأكمله عبارة عن statement واحدة، يمكن وضعه في سطر واحد. ضع مسافات حول الأقواس المعقوفة (باستثناء المسافة في نهاية السطر).
inline size_t mask() const                { return buf_size() - 1; }
inline size_t place(HashValue x) const    { return x & mask(); }
5. للدوال. لا تضع مسافات حول الأقواس.
void reinsert(const Value & x)
memcpy(&buf[place_value], &x, sizeof(x));
6. في تعبيرات if وfor وwhile وغيرها، تُضاف مسافة قبل القوس الافتتاحي (خلافًا لاستدعاءات الدوال).
for (size_t i = 0; i < rows; i += storage.index_granularity)
7. أضف مسافات حول العوامل الثنائية (+, -, *, /, %, …) والعامل الثلاثي ?:.
UInt16 year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0');
UInt8 month = (s[5] - '0') * 10 + (s[6] - '0');
UInt8 day = (s[8] - '0') * 10 + (s[9] - '0');
8. عند إدخال حرف تغذية السطر، ضع المعامل في سطر جديد وزد المسافة البادئة قبله.
if (elapsed_ns)
    message << " ("
        << rows_read_on_server * 1000000000 / elapsed_ns << " rows/s., "
        << bytes_read_on_server * 1000.0 / elapsed_ns << " MB/s.) ";
9. يمكنك استخدام المسافات للمحاذاة داخل السطر عند الحاجة.
dst.ClickLogID         = click.LogID;
dst.ClickEventID       = click.EventID;
dst.ClickGoodEvent     = click.GoodEvent;
10. لا تستخدم مسافات حول المعاملات .، ->. إذا لزم الأمر، يمكن نقل المعامل إلى السطر التالي. في هذه الحالة، تزداد المسافة البادئة أمامه. 11. لا تستخدم مسافة للفصل بين العوامل الأحادية (--, ++, *, &, …) والمعامل. 12. ضع مسافة بعد الفاصلة، لا قبلها. وتسري القاعدة ذاتها على الفاصلة المنقوطة داخل تعبير for. 13. لا تستخدم مسافات للفصل في المعامل []. 14. في تعبير template <...>، ضع مسافة بين template و<؛ ولا مسافات بعد < أو قبل >.
template <typename TKey, typename TValue>
struct AggregatedStatElement
{}
15. في الأصناف والبنى، اكتب public وprivate وprotected على نفس مستوى class/struct، وأضف مسافة بادئة لبقية الكود.
template <typename T>
class MultiVersion
{
public:
    /// Version of object for usage. shared_ptr manage lifetime of version.
    using Version = std::shared_ptr<const T>;
    ...
}
16. إذا كان نفس namespace مستخدمًا في الملف بأكمله ولم يكن هناك شيء آخر ذو أهمية، فلا حاجة إلى مسافة بادئة داخل namespace. 17. إذا كانت الكتلة الخاصة بـ if أو for أو while أو أي تعبير آخر تحتوي على جملة واحدة statement فقط، فإن الأقواس المعقوفة تكون اختيارية. ضع الجملة statement في سطر منفصل بدلاً من ذلك. تنطبق هذه القاعدة أيضاً على if وfor وwhile المتداخلة … لكن إذا احتوت statement الداخلية على أقواس معقوفة أو else، وجب كتابة الكتلة الخارجية بين أقواس معقوفة.
/// Finish write.
for (auto & stream : streams)
    stream.second->finalize();
18. يجب ألا تكون هناك مسافات في نهايات الأسطر. 19. ملفات المصدر مُرمَّزة بـ UTF-8. 20. يمكن استخدام الأحرف غير ASCII في القيم الحرفية للسلاسل النصية.
<< ", " << (timer.elapsed() / chunks_stats.hits) << " μsec/hit.";
21. لا تكتب عدة تعبيرات في سطر واحد. 22. اجمع أجزاء الشيفرة داخل الدوال، وافصل بينها بسطر فارغ واحد كحد أقصى. 23. افصل بين الدوال والأصناف وما شابهها بسطر فارغ واحد أو سطرين فارغين. 24. يجب أن يُكتب A const (المرتبط بقيمة) قبل اسم النوع.
//correct
const char * pos
const std::string & s
//incorrect
char const * pos
25. عند التصريح بمؤشر أو مرجع، يجب وضع مسافات على جانبي الرمزين * و &.
//correct
const char * pos
//incorrect
const char* pos
const char *pos
26. عند استخدام أنواع القوالب، استخدم الكلمة المفتاحية using لتعريف أسماء مستعارة لها (باستثناء أبسط الحالات). بعبارة أخرى، لا تُحدَّد معلمات القالب إلا في using ولا تُكرَّر في الشيفرة. يمكن التصريح عن using محليًا، مثلًا داخل دالة.
//correct
using FileStreams = std::map<std::string, std::shared_ptr<Stream>>;
FileStreams streams;
//incorrect
std::map<std::string, std::shared_ptr<Stream>> streams;
27. لا تُعلن عدة متغيرات من أنواع مختلفة في تعليمة واحدة.
//incorrect
int x, *y;
28. لا تستخدم تحويلات الأنواع بصيغة C.
//incorrect
std::cerr << (int)c <<; std::endl;
//correct
std::cerr << static_cast<int>(c) << std::endl;
29. في صنف وبنية، اجمع الأعضاء والدوال كلًّا على حدة داخل كل مستوى من مستويات الإتاحة. 30. في صنف وبنية الصغيرة، ليس من الضروري فصل تعريف method عن تنفيذها. وينطبق الأمر نفسه على method الصغيرة في أي صنف أو بنية. في Template صنف وبنية، لا تفصل تعريفات method عن تنفيذها (لأنه بخلاف ذلك يجب تعريفها في وحدة الترجمة نفسها). 31. يمكنك لفّ الأسطر عند 140 حرفًا بدلًا من 80. 32. استخدم دائمًا معاملي الزيادة/الإنقاص السابقين إذا لم تكن الصيغة اللاحقة مطلوبة.
for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)

التعليقات

1. احرص على إضافة تعليقات إلى كل أجزاء الشيفرة غير الواضحة. هذا مهم جدًا. فقد تساعدك كتابة تعليق على إدراك أن الشيفرة غير ضرورية، أو أن تصميمها خاطئ.
/** Part of piece of memory, that can be used.
  * For example, if internal_buffer is 1MB, and there was only 10 bytes loaded to buffer from file for reading,
  * then working_buffer will have size of only 10 bytes
  * (working_buffer.end() will point to position right after those 10 bytes available for read).
  */
2. يمكن أن تكون التعليقات مفصّلة بالقدر المطلوب. 3. ضع التعليقات قبل الشيفرة التي تشرحها. وفي حالات نادرة، يمكن أن تأتي التعليقات بعد الشيفرة، في السطر نفسه.
/** Parses and executes the query.
*/
void executeQuery(
    ReadBuffer & istr, /// Where to read the query from (and data for INSERT, if applicable)
    WriteBuffer & ostr, /// Where to write the result
    Context & context, /// DB, tables, data types, engines, functions, aggregate functions...
    BlockInputStreamPtr & query_plan, /// Here could be written the description on how query was executed
    QueryProcessingStage::Enum stage = QueryProcessingStage::Complete /// Up to which stage process the SELECT query
    )
4. يجب أن تكون التعليقات مكتوبة باللغة الإنجليزية فقط. 5. إذا كنت تكتب مكتبة، فأدرج في ملف الترويسة الرئيسي تعليقات تفصيلية تشرحها. 6. لا تُضِف تعليقات لا توفّر معلومات إضافية. وعلى وجه الخصوص، لا تترك تعليقات فارغة مثل هذه:
/*
* Procedure Name:
* Original procedure name:
* Author:
* Date of creation:
* Dates of modification:
* Modification authors:
* Original file name:
* Purpose:
* Intent:
* Designation:
* Classes used:
* Constants:
* Local variables:
* Parameters:
* Date of creation:
* Purpose:
*/
المثال مقتبس من المصدر http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/. 7. لا تكتب تعليقات عديمة الفائدة (اسم المؤلف، تاريخ الإنشاء ..) في بداية كل ملف. 8. تبدأ التعليقات أحادية السطر بثلاث شرطات مائلة: ///، وتبدأ التعليقات متعددة الأسطر بـ /**. وتُعد هذه التعليقات “توثيقًا”. ملاحظة: يمكنك استخدام Doxygen لإنشاء التوثيق من هذه التعليقات. لكن Doxygen لا يُستخدم عمومًا لأن التنقل في الشيفرة داخل بيئة التطوير المتكاملة (IDE) أكثر ملاءمة. 9. يجب ألا تحتوي التعليقات متعددة الأسطر على أسطر فارغة في البداية أو النهاية (باستثناء السطر الذي يُغلق التعليق متعدد الأسطر). 10. عند التعليق على الشيفرة لإلغائها مؤقتًا، استخدم التعليقات العادية، وليس تعليقات “التوثيق”. 11. احذف الأجزاء المُعلَّق عليها من الشيفرة قبل إجراء commit. 12. لا تستخدم ألفاظًا نابية في التعليقات أو الشيفرة. 13. لا تستخدم الأحرف الكبيرة. ولا تُكثر من علامات الترقيم.
/// WHAT THE FAIL???
14. لا تستخدم التعليقات كفواصل.
///******************************************************
15. لا تفتح نقاشات في التعليقات.
/// Why did you do this stuff?
16. لا حاجة لكتابة تعليق في نهاية كتلة يوضح ما الذي كانت تتناوله.
/// for

الأسماء

1. استخدم الأحرف الصغيرة والشرطات السفلية في أسماء المتغيرات وأعضاء الأصناف.
size_t max_block_size;
2. بالنسبة إلى أسماء الدوال (الطرائق)، استخدم camelCase بحيث يبدأ بحرف صغير.
std::string getName() const override { return "Memory"; }
3. بالنسبة إلى أسماء الأصناف (البُنى)، استخدم أسلوب CamelCase على أن يبدأ بحرف كبير. ولا تُستخدم للواجهات أي بادئات باستثناء I.
class StorageMemory : public IStorage
4. تُسمّى عبارات using بالطريقة نفسها التي تُسمّى بها الأصناف. 5. أسماء وسيطات أنواع القوالب: في الحالات البسيطة، استخدم T؛ T، U؛ T1، T2. في الحالات الأكثر تعقيدًا، اتبع قواعد تسمية الأصناف، أو أضف البادئة T.
template <typename TKey, typename TValue>
struct AggregatedStatElement
6. أسماء وسيطات الثوابت في القوالب: إمّا أن تتبع قواعد تسمية المتغيّرات، أو استخدام N في الحالات البسيطة.
template <bool without_www>
struct ExtractDomain
7. بالنسبة إلى الأصناف المجرّدة (الواجهات)، يمكنك إضافة البادئة I.
class IProcessor
8. إذا كنت تستخدم متغيّرًا محليًا، فيمكنك استخدام الاسم المختصر. في جميع الحالات الأخرى، استخدم اسمًا يعبّر عن المعنى.
bool info_successfully_loaded = false;
9. تُكتب أسماء define والثوابت العامة بصيغة ALL_CAPS مع استخدام الشرطة السفلية.
#define MAX_SRC_TABLE_NAMES_TO_STORE 1000
10. يجب أن تتبع أسماء الملفات النمط نفسه المستخدم في محتواها. إذا كان الملف يحتوي على صنف واحدة، فسمِّ الملف بالطريقة نفسها التي تُسمّى بها الصنف (CamelCase). إذا كان الملف يحتوي على دالة واحدة، فسمِّ الملف بالطريقة نفسها التي تُسمّى بها الدالة (camelCase). 11. إذا كان الاسم يحتوي على اختصار، فحينها:
  • بالنسبة إلى أسماء المتغيرات، يجب أن يُكتب الاختصار بأحرف صغيرة mysql_connection (وليس mySQL_connection).
  • بالنسبة إلى أسماء الأصناف والدوال، احتفظ بالأحرف الكبيرة في الاختصار MySQLConnection (وليس MySqlConnection).
12. يجب أن تُسمّى وسائط المُنشئ التي تُستخدم فقط لتهيئة أعضاء الصنف بالطريقة نفسها التي تُسمّى بها أعضاء الصنف، ولكن مع إضافة شرطة سفلية في النهاية.
FileQueueProcessor(
    const std::string & path_,
    const std::string & prefix_,
    std::shared_ptr<FileHandler> handler_)
    : path(path_),
    prefix(prefix_),
    handler(handler_),
    log(&Logger::get("FileQueueProcessor"))
{
}
يمكن حذف اللاحقة _ إذا لم تُستخدم الوسيطة داخل جسم المُنشئ. 13. لا يوجد فرق بين أسماء المتغيرات المحلية وأعضاء الصنف (ولا حاجة إلى أي بادئات).
timer (not m_timer)
14. بالنسبة إلى الثوابت في enum، استخدم CamelCase بحيث يبدأ بحرف كبير. كما يُقبل أيضًا استخدام ALL_CAPS. إذا كان enum غير محلي، فاستخدم enum class.
enum class CompressionMethod
{
    QuickLZ = 0,
    LZ4     = 1,
};
15. يجب أن تكون جميع الأسماء باللغة الإنجليزية. لا يُسمح بالنقل الصوتي للكلمات العبرية. ليس T_PAAMAYIM_NEKUDOTAYIM 16. الاختصارات مقبولة إذا كانت معروفة على نطاق واسع (عندما يمكنك العثور بسهولة على معنى الاختصار في Wikipedia أو عبر محرك بحث). AST، SQL. ليس NVDH (مجرد حروف عشوائية) الكلمات غير المكتملة مقبولة إذا كانت الصيغة المختصرة شائعة الاستخدام. يمكنك أيضًا استخدام اختصار إذا كان الاسم الكامل مذكورًا بجواره في التعليقات. 17. يجب أن تحمل أسماء الملفات التي تحتوي على شيفرة مصدر C++ الامتداد .cpp. ويجب أن تحمل ملفات الترويسة الامتداد .h.

كيفية كتابة الشيفرة

1. إدارة الذاكرة. لا يجوز استخدام التحرير اليدوي للذاكرة (delete) إلا في شيفرة المكتبة. في شيفرة المكتبة، لا يجوز استخدام العامل delete إلا داخل الدوال الهدّامة. في شيفرة التطبيق، يجب أن يحرّر الذاكرةَ الكائنُ الذي يملكها. أمثلة:
  • أسهل طريقة هي وضع كائن على المكدس، أو جعله عضوًا في صنف أخرى.
  • عند وجود عدد كبير من الكائنات الصغيرة، استخدم الحاويات.
  • للتحرير التلقائي لعدد قليل من الكائنات الموجودة على الكومة، استخدم shared_ptr/unique_ptr.
2. إدارة الموارد. استخدم RAII وراجع ما سبق. 3. معالجة الأخطاء. استخدم الاستثناءات. في معظم الحالات، كل ما تحتاج إليه هو رمي استثناء، ولا تحتاج إلى التقاطه (بفضل RAII). في تطبيقات معالجة البيانات غير المتصلة، يكون من المقبول غالبًا عدم التقاط الاستثناءات. في الخوادم التي تتعامل مع طلبات المستخدمين، يكفي عادةً التقاط الاستثناءات عند المستوى الأعلى من معالج الاتصال. في دوال الخيوط، ينبغي التقاط جميع الاستثناءات والاحتفاظ بها لإعادة رميها في الخيط الرئيسي بعد join.
/// If there weren't any calculations yet, calculate the first block synchronously
if (!started)
{
    calculate();
    started = true;
}
else /// If calculations are already in progress, wait for the result
    pool.wait();

if (exception)
    exception->rethrow();
لا تُخفِ الاستثناءات أبدًا من دون معالجتها. ولا تكتفِ بمجرد تسجيل جميع الاستثناءات عشوائيًا.
//Not correct
catch (...) {}
إذا كنت بحاجة إلى تجاهل بعض الاستثناءات، فافعل ذلك فقط مع استثناءات محددة، وأعِد رمي البقية.
catch (const DB::Exception & e)
{
    if (e.code() == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION)
        return nullptr;
    else
        throw;
}
عند استخدام الدوال مع رموز الاستجابة أو errno، تحقّق دائمًا من النتيجة وألقِ استثناءً في حال حدوث خطأ.
if (0 != close(fd))
    throw ErrnoException(ErrorCodes::CANNOT_CLOSE_FILE, "Cannot close file {}", file_name);
يمكنك استخدام assert للتحقق من الشروط الثابتة في الشيفرة. 4. أنواع الاستثناءات. لا حاجة إلى استخدام تسلسل هرمي معقد للاستثناءات في شيفرة التطبيق. يجب أن يكون نص الاستثناء مفهومًا لمسؤول النظام. 5. إطلاق الاستثناءات من الـ destructor. لا يُنصح بهذا، لكنه مسموح. استخدم الخيارات التالية:
  • أنشئ دالة (done() أو finalize()) تنفّذ مسبقًا كل العمل الذي قد يؤدي إلى استثناء. إذا جرى استدعاء تلك الدالة، فلا ينبغي أن تحدث أي استثناءات لاحقًا في الـ destructor.
  • يمكن وضع المهام شديدة التعقيد (مثل إرسال الرسائل عبر الشبكة) في دالة منفصلة يجب على مستخدم الـ صنف استدعاؤها قبل التدمير.
  • إذا حدث استثناء في الـ destructor، فمن الأفضل تسجيله بدلًا من إخفائه (إذا كان logger متاحًا).
  • في التطبيقات البسيطة، من المقبول الاعتماد على std::terminate (في حالات noexcept الافتراضية في C++11) للتعامل مع الاستثناءات.
6. كتل الشيفرة المجهولة. يمكنك إنشاء block شيفرة منفصلة داخل دالة واحدة لجعل بعض المتغيرات محلية النطاق، بحيث تُستدعى الـ destructors عند الخروج من الـ block.
Block block = data.in->read();

{
    std::lock_guard<std::mutex> lock(mutex);
    data.ready = true;
    data.block = block;
}

ready_any.set();
7. تعدد الخيوط. في برامج معالجة البيانات دون اتصال:
  • حاول الحصول على أفضل أداء ممكن على نواة CPU واحدة. بعد ذلك يمكنك جعل الشيفرة متوازية إذا لزم الأمر.
في تطبيقات الخادم:
  • استخدم thread pool لمعالجة الطلبات. حتى الآن، لم تكن لدينا أي مهام تتطلب تبديل السياق في فضاء المستخدم.
لا تُستخدم عملية fork للتوازي. 8. مزامنة الخيوط. غالبًا ما يكون من الممكن جعل الخيوط المختلفة تستخدم خلايا ذاكرة مختلفة (والأفضل من ذلك: خطوط cache مختلفة)، وألا تستخدم أي مزامنة بين الخيوط (باستثناء 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. في حالات نادرة (مثل تحديث قيمة داخل حلقة)، قد تحتاج إلى إرجاع القيمة عبر وسيط. في هذه الحالة، يجب أن يكون الوسيط مرجعًا.
using AggregateFunctionPtr = std::shared_ptr<IAggregateFunction>;

/** Allows creating an aggregate function by its name.
  */
class AggregateFunctionFactory
{
public:
    AggregateFunctionFactory();
    AggregateFunctionPtr get(const String & name, const DataTypes & argument_types) const;
15. namespace. لا حاجة إلى استخدام namespace منفصل لشيفرة التطبيق. ولا تحتاج المكتبات الصغيرة إلى ذلك أيضًا. أما في المكتبات المتوسطة والكبيرة، فضع كل شيء داخل namespace. في ملف .h الخاص بالمكتبة، يمكنك استخدام namespace detail لإخفاء تفاصيل التنفيذ غير اللازمة لشيفرة التطبيق. وفي ملف .cpp، يمكنك استخدام static أو namespace مجهول لإخفاء الرموز. كذلك، يمكن استخدام namespace مع enum لمنع تسرّب الأسماء المقابلة إلى namespace خارجي (لكن من الأفضل استخدام enum class). 16. التهيئة المؤجلة. إذا كانت التهيئة تتطلب معاملات، فعادةً لا ينبغي لك كتابة مُنشئ افتراضي. وإذا احتجت لاحقًا إلى تأجيل التهيئة، فيمكنك إضافة مُنشئ افتراضي ينشئ كائنًا غير صالح. أو، إذا كان عدد الكائنات قليلًا، يمكنك استخدام shared_ptr/unique_ptr.
Loader(DB::Connection * connection_, const std::string & query, size_t max_block_size_);

/// For deferred initialization
Loader() {}
17. الدوال الافتراضية. إذا لم يكن الصنف مخصّصًا للاستخدام متعدد الأشكال، فلا حاجة إلى جعل الدوال افتراضية. وينطبق ذلك أيضًا على الدالة الهادمة. 18. الترميزات. استخدم UTF-8 في كل مكان. استخدم 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 للدوال إلا عند الضرورة.
auto f() -> void
25. التصريح عن المتغيرات وتهيئتها.
//right way
std::string s = "Hello";
std::string s{"Hello"};

//wrong way
auto s = std::string{"Hello"};
26. بالنسبة إلى الدوال الافتراضية، اكتب virtual في الصنف الأساسي، لكن اكتب override بدلًا من virtual في الأصناف الفرعية.

ميزات C++ غير المستخدمة

1. لا يُستخدم التوريث الافتراضي. 2. التراكيب التي تتوفر لها اختصارات نحوية مريحة في C++ الحديثة، مثلًا.
// Traditional way without syntactic sugar
template <typename G, typename = std::enable_if_t<std::is_same<G, F>::value, void>> // SFINAE via std::enable_if, usage of ::value
std::pair<int, int> func(const E<G> & e) // explicitly specified return type
{
    if (elements.count(e)) // .count() membership test
    {
        // ...
    }

    elements.erase(
        std::remove_if(
            elements.begin(), elements.end(),
            [&](const auto x){
                return x == 1;
            }),
        elements.end()); // remove-erase idiom

    return std::make_pair(1, 2); // create pair via make_pair()
}

// With syntactic sugar (C++14/17/20)
template <typename G>
requires std::same_v<G, F> // SFINAE via C++20 concept, usage of C++14 template alias
auto func(const E<G> & e) // auto return type (C++14)
{
    if (elements.contains(e)) // C++20 .contains membership test
    {
        // ...
    }

    elements.erase_if(
        elements,
        [&](const auto x){
            return x == 1;
        }); // C++20 std::erase_if

    return {1, 2}; // or: return std::pair(1, 2); // create pair via initialization list or value initialization (C++17)
}

المنصة

1. نكتب الشفرة البرمجية لمنصة محددة. لكن عند تساوي العوامل الأخرى، تُفضَّل الشفرة متعددة المنصات أو القابلة للنقل. 2. اللغة: C++20 (راجع قائمة ميزات C++20 المتاحة). 3. المترجم: 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 لها باستخدام إعدادات الإصدار.

الأدوات

1. يُعد KDevelop بيئة تطوير متكاملة جيدة. 2. لتصحيح الأخطاء، استخدم 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. تُزال الشيفرة غير المستخدمة من المستودع.

المكتبات

1. تُستخدم مكتبة C++20 القياسية (ويُسمح بالامتدادات التجريبية)، وكذلك إطارا العمل boost وPoco. 2. لا يُسمح باستخدام مكتبات من حزم نظام التشغيل، كما لا يُسمح باستخدام المكتبات المثبّتة مسبقًا. يجب وضع جميع المكتبات على هيئة شيفرة مصدرية في دليل contrib وبنائها مع ClickHouse. راجع إرشادات إضافة مكتبات خارجية جديدة وصيانتها لمزيد من التفاصيل. 3. تُمنح الأفضلية دائمًا للمكتبات المستخدمة مسبقًا.

توصيات عامة

1. اكتب أقل قدر ممكن من الشيفرة. 2. جرّب أبسط حل ممكن. 3. لا تكتب شيفرة قبل أن تعرف كيف ستعمل، وكيف ستعمل الحلقة الداخلية. 4. في أبسط الحالات، استخدم using بدلًا من الأصناف أو البُنى. 5. إذا أمكن، فلا تكتب copy constructors أو assignment operators أو destructors (باستثناء destructor افتراضي، إذا كان الصنف يحتوي على دالة افتراضية واحدة على الأقل) أو move constructors أو move assignment operators. وبعبارة أخرى، يجب أن تعمل الدوال التي يُنشئها المصرّف تلقائيًا بشكل صحيح. يمكنك استخدام default. 6. يُشجَّع تبسيط الشيفرة. قلّل حجم الشيفرة قدر الإمكان.

توصيات إضافية

1. عدم التصريح بـ 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. وسيطات الدالة متعددة الأسطر. أيٌّ من أنماط التفاف الأسطر التالية مسموح به:
function(
  T1 x1,
  T2 x2)
function(
  size_t left, size_t right,
  const & RangesInDataParts ranges,
  size_t limit)
function(size_t left, size_t right,
  const & RangesInDataParts ranges,
  size_t limit)
function(size_t left, size_t right,
      const & RangesInDataParts ranges,
      size_t limit)
function(
      size_t left,
      size_t right,
      const & RangesInDataParts ranges,
      size_t limit)
آخر تعديل في ٢٩ يونيو ٢٠٢٦