سنضع كلمة حول تحليل الشفرة: البرمجة السياقية

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


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


فكرة


الفكرة نفسها بسيطة وتعتمد على العبارات التالية:


  • يتكون أي منطق دائمًا من خطوات أولية ؛
  • لكل خطوة ، هناك حاجة إلى بيانات معينة ، والتي تطبق منطقها وتنتج إما نتيجة ناجحة أو غير ناجحة.

على مستوى الكود الزائف ، يمكن تمثيل ذلك على النحو التالي:


do-something-elementary(context) -> [:ok updated_context] | [:error reason]


أين:


  • do-something-elementary - اسم الوظيفة ؛
  • context - حجة الدالة ، بنية البيانات مع السياق الأولي الذي تستمد منه الوظيفة جميع البيانات اللازمة ؛
  • updated_context - بنية البيانات مع السياق المحدث ، إذا نجحت ، حيث تضيف الوظيفة نتيجة تنفيذها ؛
  • reason - بنية البيانات ، سبب الفشل ، مع الفشل.

هذه هي الفكرة كلها. وبعد ذلك - مسألة تقنية. مع 100،500 مليون جزء.


مثال: مستخدم يقوم بعملية شراء


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


buy-lot(user_id, lot_id) -> [:ok updated_user] | [:error reason]


من أجل البساطة ، سنقوم بتخزين كمية المال والكثير من المستخدمين في هيكل المستخدم نفسه.


من أجل التنفيذ ، نحتاج إلى العديد من الوظائف المساعدة.


وظيفة until-first-error


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


until-first-error(fs, init_context) -> [:ok updated_context] | [:error reason]


أين:


  • fs هو سلسلة من الدوال (الإجراءات الأولية) ؛
  • init_context هو السياق الأولي.

يمكن الاطلاع على تنفيذ هذه الميزة على GitHub هنا .


الدالة with-result-or-error


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


with-result-or-error(f, key, context) -> [:ok updated_context] | [:error reason]


بشكل عام ، الغرض الوحيد من هذه الوظيفة هو تقليل حجم الكود.


وأخيرًا ، "جمالنا" ...


الوظيفة التي تنفذ الشراء


 1. (defn buy-lot [user_id lot_id] 2. (let [with-lot-fn (partial 3. util/with-result-or-error 4. #(lot-db/find-by-id lot_id) 5. :lot) 6. 7. buy-lot-fn (fn [{:keys [lot] :as ctx}] 8. (util/with-result-or-error 9. #(user-db/update-by-id! 10. user_id 11. (fn [user] 12. (let [wallet_v (get-in user [:wallet :value]) 13. price_v (get-in lot [:price :value])] 14. (if (>= wallet_v price_v) 15. (let [updated_user (-> user 16. (update-in [:wallet :value] 17. - 18. price_v) 19. (update-in [:lots] 20. conj 21. {:lot_id lot_id 22. :price price_v}))] 23. [:ok updated_user]) 24. [:error {:type :invalid_wallet_value 25. :details {:code :not_enough 26. :provided wallet_v 27. :required price_v}}])))) 28. :user 29. ctx)) 30. 31. fs [with-lot-fn 32. buy-lot-fn]] 33. 34. (match (util/until-first-error fs {}) 35. 36. [:ok {:user updated_user}] 37. [:ok updated_user] 38. 39. [:error reason] 40. [:error reason]))) 

دعنا نذهب من خلال الرمز:


  • ص 34: match هو ماكرو لمطابقة القيم من قالب من مكتبة clojure.core.match ؛
  • ص.34-40: نطبق دالة until-first-error على الخطوات الأولية fs ، ونأخذ البيانات التي نحتاجها من السياق ونعيدها ، أو نرفع الخطأ ؛
  • ص 2-5: نحن نبني أول إجراء أولي (سيتم تطبيق السياق الحالي عليه فقط) ، والذي يضيف ببساطة البيانات على المفتاح :lot إلى السياق الحالي ؛
  • ص.7-29: هنا نستخدم الوظيفة المألوفة with-result-or-error ، لكن الإجراء الذي يتم الالتفاف عليه أصبح أكثر صعوبة: في معاملة واحدة ، نتحقق من أن المستخدم لديه ما يكفي من المال ، وإذا نجح ، فإننا نجري عملية شراء (لأن ، افتراضيًا ، يكون تطبيقنا متعدد مؤشرات الترابط (ومن شاهد في آخر مرة تطبيقًا واحد الخيوط؟) ويجب أن نكون مستعدين لهذا).

وبضع كلمات عن الوظائف الأخرى التي استخدمناها:


  • lot-db/find-by-id(id) - تُرجع الكثير ، id ؛
  • user-db/update-by-id!(user_id, update-user-fn) - يطبق وظيفة update-user-fn على user_id المستخدم (في قاعدة بيانات وهمية).

وللاختبار؟ ...


اختبار هذا التطبيق عينة من clojure REPL. نبدأ REPL من وحدة التحكم من جذر المشروع:


 lein repl 

ما مستخدمينا بالأموال:


 context-aware-app.core=> (context-aware-app.user.db/enumerate) [:ok ({:id "1", :name "Vasya", :wallet {:value 100}, :lots []} {:id "2", :name "Petya", :wallet {:value 100}, :lots []})] 

ما لدينا الكثير (السلع):


 context-aware-app.core=> (context-aware-app.lot.db/enumerate) [:ok ({:id "1", :name "Apple", :price {:value 10}} {:id "2", :name "Banana", :price {:value 20}} {:id "3", :name "Nuts", :price {:value 80}})] 

تشتري Vasya تفاحة:


 context-aware-app.core=>(context-aware-app.processing/buy-lot "1" "1") [:ok {:id "1", :name "Vasya", :wallet {:value 90}, :lots [{:lot_id "1", :price 10}]}] 

والموز:


 context-aware-app.core=> (context-aware-app.processing/buy-lot "1" "2") [:ok {:id "1", :name "Vasya", :wallet {:value 70}, :lots [{:lot_id "1", :price 10} {:lot_id "2", :price 20}]}] 

و "المكسرات":


 context-aware-app.core=> (context-aware-app.processing/buy-lot "1" "3") [:error {:type :invalid_wallet_value, :details {:code :not_enough, :provided 70, :required 80}}] 

لم يكن هناك ما يكفي من المال للمكسرات.


المجموع


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


  • توفير الوقت لقراءة وفهم الكود ؛
  • اختبار كود مبسط
  • القدرة على إعادة استخدام الكود (بما في ذلك استخدام النسخ واللصق + ملف dopilivanie) ؛
  • تبسيط إعادة هيكلة الكود.

على سبيل المثال كل ما نحب ونمارسه.


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


All Articles