شرح الكود النظيف: مقدمة عملية للبرمجة النظيفة للمبتدئين

دقائق القراءة: 9

“أي أحمق يمكنه كتابة كود يفهمه الحاسوب. المبرمجون الجيدون يكتبون كودًا يفهمه البشر.” – مارتن فاولر.

تُعد كتابة الكود النظيف، القابل للفهم، وسهل الصيانة مهارة أساسية لا غنى عنها لكل مطور برمجيات. في هذا المقال، سنتعمق في أهم المبادئ لتحسين جودة الكود، وسنقدم أمثلة عملية لكل مبدأ. تستند معظم الأمثلة إلى كتاب “الكود النظيف” (Clean Code) للمؤلف روبرت ج. مارتن (Robert J. Martin)، وهو عمل كلاسيكي في عالم البرمجة ننصح بشدة بقراءته كاملاً عند توفر الوقت.

كيفية تسمية المتغيرات (وغيرها من الكيانات البرمجية)

“هناك شيئان فقط صعبان في علوم الحاسوب: إبطال ذاكرة التخزين المؤقت (cache invalidation) وتسمية الأشياء.” – فيل كارلتون.

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

كيفية إنشاء أسماء ذات معنى

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

“يجب أن يخبرك الاسم لماذا وُجِد، وماذا يفعله، وكيف يُستخدم. إذا احتاج الاسم إلى تعليق، فهذا يعني أن الاسم لا يكشف عن قصده.” – كتاب Clean Code.

مثال سيء:

var d; // elapsed time in days

لقد رأيت هذا النوع من الكود مرات عديدة. من المفاهيم الخاطئة الشائعة أن التعليقات يمكن أن تخفي فوضى الكود. لا تستخدم أحرفًا مثل x أو y أو a أو b كأسماء للمتغيرات إلا إذا كان هناك سبب وجيه لذلك (متغيرات الحلقات التكرارية loop variables هي استثناء لهذه القاعدة).

مثال جيد:

var elapsedTimeInDays;
var daysSinceCreation;
var daysSinceModification;

هذه الأسماء أفضل بكثير؛ فهي توضح بوضوح ما يتم قياسه ووحدة القياس المستخدمة، مما يجعل الكود أكثر قابلية للقراءة والفهم.

تجنب التضليل في التسمية

كن حذرًا بشأن الكلمات التي تحمل معنى محددًا في سياق البرمجة. على سبيل المثال، لا تُشر إلى مجموعة من الحسابات باسم accountList إلا إذا كان نوعها الفعلي هو قائمة (List) أو مصفوفة (Array). كلمة “List” لها دلالة محددة وقد تؤدي إلى استنتاجات خاطئة. حتى لو كان النوع قائمة، فإن اسم accounts أبسط وأفضل وأكثر دلالة.

مثال سيء:

var accountList = [];

مثال جيد:

var accounts = [];

تجنب الكلمات الزائدة (Noise Words)

الكلمات الزائدة هي تلك التي لا تقدم أي معلومات إضافية عن المتغير أو الكيان البرمجي؛ إنها زائدة عن الحاجة ويجب إزالتها. من الكلمات الزائدة الشائعة:

  • The (كبادئة)
  • Info
  • Data
  • Variable
  • Object
  • Manager

إذا كان اسم الفئة (class) هو UserInfo، يمكنك ببساطة إزالة Info وتسميتها User. استخدام BookData بدلًا من Book كاسم للفئة هو أمر غير منطقي، لأن الفئة بطبيعتها تخزن البيانات (Data) على أي حال.

استخدم أسماء قابلة للنطق

إذا لم تتمكن من نطق اسم ما، فلن تتمكن من مناقشته مع زملائك دون أن تبدو سخيفًا أو تسبب التباسًا. الأسماء القابلة للنطق تسهل التواصل وتزيد من وضوح الكود.

مثال سيء:

const yyyymmdstr = moment().format("YYYY/MM/DD");

مثال جيد:

const currentDate = moment().format("YYYY/MM/DD");

استخدم أسماء قابلة للبحث

تجنب استخدام الأرقام السحرية (magic numbers) مباشرة في الكود الخاص بك. بدلًا من ذلك، اختر الثوابت المسماة (named constants) التي يمكن البحث عنها بسهولة. لا تستخدم أسماء أحرف فردية للثوابت، لأنها قد تظهر في أماكن كثيرة وبالتالي يصعب البحث عنها أو فهم سياقها.

مثال سيء:

if (student.classes.length < 7) {
    // Do something
}

هذا المثال يثير تساؤلات في ذهن القارئ: ما أهمية الرقم 7 هنا؟

مثال جيد:

if (student.classes.length < MAX_CLASSES_PER_STUDENT) {
    // Do something
}

هذا أفضل بكثير لأن الثابت MAX_CLASSES_PER_STUDENT يمكن استخدامه في عدة أماكن في الكود. إذا احتجنا إلى تغيير قيمته إلى 6 في المستقبل، يمكننا ببساطة تعديل الثابت في مكان واحد. يجب عليك أيضًا الاستفادة من اصطلاحات تسمية وإعلان الثوابت الخاصة بلغتك البرمجية، مثل استخدام private static final في لغة Java أو const في لغة JavaScript.

كن متسقًا في التسمية

اتبع قاعدة “كلمة واحدة لكل مفهوم”. لا تستخدم fetch و retrieve و get لنفس العملية في فئات مختلفة. اختر كلمة واحدة منها واستخدمها في جميع أنحاء المشروع لكي يتمكن الأشخاص الذين يقومون بصيانة الكود (codebase) أو مستخدمو واجهة برمجة التطبيقات (API) الخاصة بك من العثور على الدوال التي يبحثون عنها بسهولة. الاتساق يقلل من الحمل المعرفي ويسرع عملية الفهم.

كيفية كتابة الدوال (Functions) بفعالية

اجعلها صغيرة ومحددة

يجب أن تكون الدوال صغيرة، بل صغيرة جدًا. نادرًا ما يجب أن تتجاوز 20 سطرًا. كلما طالت الدالة، زادت احتمالية قيامها بأكثر من مهمة واحدة ووجود آثار جانبية (side effects).

تأكد أنها تؤدي وظيفة واحدة فقط

“يجب أن تؤدي الدوال شيئًا واحدًا. يجب أن تؤديه جيدًا. يجب أن تؤديه فقط.” – كتاب Clean Code.

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

تغليف الشروط المعقدة داخل دوال

تُعد إعادة هيكلة الشرط ووضعه داخل دالة مسماة طريقة ممتازة لجعل الشروط (conditionals) الخاصة بك أكثر قابلية للقراءة. إليك جزء من الكود من أحد مشاريعي المدرسية. هذا الكود مسؤول عن إدخال قطعة (chip) في لوحة لعبة Connect4. الدالة isValidInsertion تتولى مهمة التحقق من صحة رقم العمود وتسمح لنا بالتركيز على منطق إدخال القطعة نفسها.

public void insertChipAt (int column) throws Exception {
    if (isValidInsertion(column)) {
        insertChip(column);
        boardConfiguration += column;
        currentPlayer = currentPlayer == Chip.RED ? Chip.YELLOW : Chip.RED;
    } else {
        if (!columnExistsAt(column)) throw new IllegalArgumentException();
        else if (isColumnFull(column - 1) || getWinner() != Chip.NONE) throw new RuntimeException();
    }
}

وإليك كود الدالة isValidInsertion، إذا كنت مهتمًا:

private boolean isValidInsertion (int column) {
    boolean columnIsAvailable = column <= NUM_COLUMNS && column >= 1 && numberOfItemsInColumn[column - 1] < NUM_ROWS;
    boolean gameIsOver = getWinner() != Chip.NONE;
    return columnIsAvailable && !gameIsOver;
}

بدون هذه الدالة، كان الشرط if سيبدو كالتالي:

if (column <= NUM_COLUMNS && column >= 1 && numberOfItemsInColumn[column - 1] < NUM_ROWS && getWinner() != Chip.NONE)

مروع، أليس كذلك؟ أتفق تمامًا.

استخدم عددًا قليلاً من المعاملات (Arguments)

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

تجنب استخدام معاملات التحديد (Flag Arguments)

معامل التحديد (flag argument) هو معامل منطقي (boolean argument) يتم تمريره إلى دالة، ويتم اتخاذ إجراءين مختلفين بناءً على قيمته. على سبيل المثال، لنفترض أن هناك دالة مسؤولة عن حجز تذاكر لحفل موسيقي، وهناك نوعان من المستخدمين: مميز (Premium) وعادي (Regular). قد تجد كودًا كهذا:

public Booking book (Customer aCustomer, boolean isPremium) {
    if (isPremium)
        // logic for premium book
    else
        // logic for regular booking
}

تتعارض معاملات التحديد بطبيعتها مع مبدأ المسؤولية الواحدة (Single Responsibility Principle). عندما تصادفها، يجب أن تفكر في تقسيم الدالة إلى دالتين منفصلتين، كل منهما تؤدي مهمة واحدة محددة.

تجنب الآثار الجانبية (Side Effects)

الآثار الجانبية هي نتائج غير مقصودة للكود الخاص بك. قد تكون تغيير المعاملات التي تم تمريرها (في حالة التمرير بالمرجع passing by reference)، أو ربما تغيير متغير عام (global variable). النقطة الأساسية هي أن الدالة وُعدت بالقيام بشيء واحد، ولكنك تحتاج إلى قراءة الكود بعناية لملاحظة هذا الأثر الجانبي. يمكن أن تؤدي هذه الآثار إلى أخطاء برمجية خطيرة. إليك مثال من الكتاب:

public class UserValidator {
    private Cryptographer cryptographer;

    public boolean checkPassword (String userName, String password) {
        User user = UserGateway.findByName(userName);
        if (user != User.NULL) {
            String codedPhrase = user.getPhraseEncodedByPassword();
            String phrase = cryptographer.decrypt(codedPhrase, password);
            if ("Valid Password".equals(phrase)) {
                Session.initialize(); // هذا هو الأثر الجانبي
                return true;
            }
        }
        return false;
    }
}

هل يمكنك رؤية الأثر الجانبي لهذه الدالة؟ إنها تتحقق من كلمة المرور، ولكن عندما تكون كلمة المرور صحيحة، فإنها تقوم أيضًا بتهيئة الجلسة (Session.initialize())، وهو ما يُعد أثرًا جانبيًا. يمكنك تغيير اسم الدالة إلى شيء مثل checkPasswordAndInitializeSession لجعل هذا التأثير واضحًا. ولكن عندما تفعل ذلك، ستلاحظ أن دالتك تقوم في الواقع بمهمتين، وبالتالي يجب ألا تقوم بتهيئة الجلسة هنا.

لا تكرر نفسك (DRY – Don’t Repeat Yourself)

قد يكون تكرار الكود هو أصل كل الشرور في تطوير البرمجيات. الكود المكرر يعني أنك ستحتاج إلى تغيير الأشياء في أماكن متعددة عند حدوث تغيير في المنطق، وهذا يزيد من احتمالية حدوث الأخطاء بشكل كبير. استخدم ميزات إعادة الهيكلة (refactoring) المتوفرة في بيئة التطوير المتكاملة (IDE) الخاصة بك، وقم باستخراج دالة (extract method) كلما صادفت جزءًا متكررًا من الكود.

صورة توضيحية لميزة استخراج الدالة (Extract Method) في برنامج IntelliJ IDEA

ميزة استخراج الدالة في IntelliJ IDEA.

لا تترك الأكواد المعلقة في التعليقات

رجاءً، لا تفعل ذلك. هذا الأمر خطير لأن المطورين الآخرين الذين يرون الكود سيخشون حذفه لأنهم لا يعرفون ما إذا كان موجودًا لسبب ما. سيبقى هذا الكود المعلق هناك لفترة طويلة. وعندما تتغير أسماء المتغيرات (variable names) أو الدوال (method names)، يصبح غير ذي صلة، ولكن لا أحد يحذفه. ببساطة، احذفه. حتى لو كان مهمًا، فإن أنظمة التحكم في الإصدار (version control) موجودة لهذا الغرض؛ يمكنك دائمًا العثور عليه واستعادته إذا لزم الأمر.

اعرف اصطلاحات لغتك البرمجية

يجب أن تكون على دراية باصطلاحات لغتك البرمجية فيما يتعلق بالمسافات (spacing)، والتعليقات (comments)، وتسمية الكيانات البرمجية (naming conventions). تتوفر أدلة الأسلوب (style guides) للعديد من اللغات. على سبيل المثال، يجب عليك استخدام camelCase في لغة Java ولكن snake_case في لغة Python. تضع الأقواس المعقوفة الافتتاحية (opening braces) على سطر جديد في لغة C#، بينما تضعها على نفس السطر في لغتي Java و JavaScript. هذه الأمور تختلف من لغة إلى أخرى ولا يوجد معيار عالمي موحد.

إليك بعض الروابط المفيدة التي قد تساعدك:

الخلاصة التقنية

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

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *