إعادة الهيكلة

بمجرد أن تعمل وظيفة، يمكننا التوقف والتفكير في أفضل طريقة لبناء الكود. الهدف هو جعل plug-in واضحًا ومفهومًا وقابلًا للصيانة قدر الإمكان. تُسمى عملية تحسين بنية الكود الموجود دون تغيير سلوكه refactoring (إعادة الهيكلة).

إليك الوظيفة الأولية مرة أخرى:

(define (scheme-hello-world)
  ;; ضبط معالج الرسائل لإخراج الرسالة إلى مربع حوار GUI
  (lumi-message-set-handler 0)
  (lumi-message "Hello world!\n")

  ;; ضبط معالج الرسائل لإخراج الرسالة إلى وحدة تحكم الأخطاء
  (lumi-message-set-handler 2)
  (lumi-message "Hello world!\n")

  ;; إرسال الرسالة إلى الطرفية، نافذة نظام التشغيل التي شغّلت Lumi
  (display "Hello world!\n"))

اسم الوظيفة هو اسمها، والمعلمة هي ما تقبله كمدخل. body (الجسم) هو كتلة التعليمات البرمجية التي تُنفَّذ عند استدعاء الوظيفة.

الصيغة المجردة:

(define (function-name parameter)
  body)

تكرار الكود

أزل التكرار مبكرًا. (lumi-message "Hello world!\n") مكرر مرتين، وسلسلة الرسالة مكررة ثلاث مرات. متغير يحل مشكلة تكرار السلسلة.

المتغيرات

في Scheme، للمتغير «نطاق» (scope) يحدد أين يكون معروفًا، ويُضبط هذا النطاق بعبارة let. يُربَط المتغير بقيمة في جزء الربط، ويكون نطاقه داخل جسم let. المتغير معروف فقط داخل كتلة let ولا يمكن الوصول إليه خارجها.

(let ((variable value))
  body)

إدخال متغير اسمه «message»:

(define (scheme-hello-world)
  (let ((message "Hello world!\n"))

    ;; ضبط معالج الرسائل لإخراج الرسالة إلى مربع حوار GUI
    (lumi-message-set-handler 0)
    (lumi-message message)

    ;; ضبط معالج الرسائل لإخراج الرسالة إلى وحدة تحكم الأخطاء
    (lumi-message-set-handler 2)
    (lumi-message message)

    ;; إرسال الرسالة إلى الطرفية، نافذة نظام التشغيل التي شغّلت Lumi
    (display message)))

في مثالنا، استخدمنا متغيرًا اسمه «message» مرتبطًا بالسلسلة "Hello world!\n". هذا يتيح تغيير محتوى الرسالة مرة واحدة بدلًا من ثلاث، فيقلّ خطر الأخطاء ويصبح الكود أكثر مرونة.

استخراج الوظائف

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

استخراج المنطق:

#!/usr/bin/env lumi-scheme-interpreter-0.1

;; الوظيفة الرئيسية
(define (scheme-hello-world)
  (let ((message "Hello world!\n"))

    (send-message message 'gui)
    (send-message message 'error-console)
    (send-message message 'terminal)))

;; وظيفة لمعالجة إخراج الرسائل إلى وجهات مختلفة
(define (send-message message output)
  (cond
    ;; الإرسال إلى وحدة تحكم الأخطاء
    ((eq? output 'error-console)
       ;; ضبط المعالج على وحدة تحكم الأخطاء
       (lumi-message-set-handler 2)
       (lumi-message message))

    ;; الإرسال إلى مربع حوار GUI
    ((eq? output 'gui)
       ;; ضبط المعالج على مربع حوار GUI
       (lumi-message-set-handler 0)
       (lumi-message message))

    ;; الإرسال إلى نافذة الطرفية
    ((eq? output 'terminal)
       ;; إخراج الطرفية يُعالَج بـ display
       (display message)))

  ;; استعادة معالج الرسائل الافتراضي إلى وحدة تحكم الأخطاء
  (lumi-message-set-handler 2))

(scheme-register-procedure "scheme-hello-world"
  "Hello world!"
  "A Scheme procedure plug-in"
  "Mark Sweeney"
  "Under GNU GENERAL PUBLIC LICENSE Version 3"
  "2024")

(scheme-menu-register
  "scheme-hello-world"
  "<Image>/Funky")

الرموز

في المثال أعلاه، يُستخدم نوع بيانات يسمى symbol (رمز)، مثل 'gui. تُمرَّر الرموز كمعلمات إلى وظيفة send-message ويمكن استخدامها لاتخاذ قرارات شرطية بسيطة. مثل المفاتيح الرمزية، هي معرّفات فريدة. لمزيد من المعلومات عن الرموز، راجع هذه الصفحة.

تبسيط الوظيفة الرئيسية

في الوظيفة الأصلية (scheme-hello-world)، كان كل منطق إرسال الرسائل إلى مخرجات مختلفة (GUI ووحدة تحكم الأخطاء والطرفية) ممزوجًا في الوظيفة الرئيسية. بعد إعادة الهيكلة، تركز الوظيفة الرئيسية ببساطة على ما يجب القيام به: إرسال الرسالة إلى وجهات مختلفة.

الوظيفة الرئيسية بعد إعادة الهيكلة أبسط:

  • تبيّن الغرض بوضوح: إرسال الرسالة نفسها إلى مخرجات متعددة.
  • تتجنب ازدحام المنطق الرئيسي بكود متكرر مثل ضبط معالجات الرسائل لمخرجات مختلفة.
  • أسهل للقراءة والفهم بلمحة.

تعقيد الوظيفة المستخرجة

على العكس، وظيفة send-message هي حيث يقبع المنطق التفصيلي. تتعامل الآن مع اختلافات السلوك لكل مخرج (GUI ووحدة تحكم الأخطاء والطرفية). الوظيفة أكثر تعقيدًا من قبل، لكنها الآن مركزية ومعزولة.

الربط بالبرمجة الوظيفية

في البرمجة الوظيفية، تُعامل الوظائف كـ مواطنين من الدرجة الأولى (first-class citizens)، أي يمكن إعادة استخدامها وتمريرها ودمجها لتكوين سلوك أكثر تعقيدًا. الهدف:

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

بإعادة الهيكلة، تصبح الوظيفة الرئيسية في هذا المثال تصريحًا (declarative) لما يحدث («أرسل رسالة إلى ثلاثة أماكن»)، بينما يُختزل تعقيد كيفية الإرسال في وظيفة send-message.