جعل الواجهة الأمامية "الخلفية" مرة أخرى

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



تم إعداد المواد على أساس تقرير من قبل نيكولاي ريجيكوف في مؤتمر الربيع HolyJS 2018 Piter .

يعمل نيكولاي ريجيكوف حاليًا في قطاع تكنولوجيا المعلومات الصحية لإنشاء أنظمة معلومات طبية. عضو في مجتمع سانت بطرسبرغ للمبرمجين الوظيفيين FPROG. عضو نشط في مجتمع Clojure عبر الإنترنت ، وهو عضو في معيار HL7 FHIR لتبادل المعلومات الطبية. تم البرمجة منذ 15 عامًا.



- كنت دائمًا أتألم من السؤال: لماذا كان من الصعب دائمًا القيام بواجهة المستخدم الرسومية؟ لماذا أثار هذا دائمًا العديد من الأسئلة؟

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

ما هي الكفاءة؟


دعونا نحدد ما هي الكفاءة. من وجهة نظر تطوير واجهة المستخدم ، تعني الكفاءة:

  • سرعة التطوير
  • عدد الأخطاء
  • المبلغ المنفق ...

هناك تعريف جيد للغاية:

الكفاءة تفعل أكثر مع القليل

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

ما هو التعقيد؟


في رأيي ، التعقيد هو المشكلة الرئيسية في التنمية.

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

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

قبل 12 عاما ...


بدأنا في تطوير نظام المعلومات الطبية قبل 12 عامًا. أولاً باستخدام الفلاش. ثم نظرنا إلى ما بدأ Gmail فعله. لقد أحببنا ذلك وأردنا التبديل إلى JavaScript باستخدام HTML.

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

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

طريقة القضبان + مسج


لقد حصلنا على شهادة النظام ، وكان من الضروري كتابة بوابة المريض. هذا هو النظام حيث يمكن للمريض الذهاب ورؤية بياناته الطبية.

ثم تمت كتابة الواجهة الخلفية لدينا في Ruby on Rails. على الرغم من أن مجتمع روبي أون ريلز ليس كبيرًا جدًا ، إلا أنه كان له تأثير كبير على الصناعة. من مجتمعك الشغوف الصغير ، جاء جميع مديري الحزم ، GitHub ، Git ، المكياج التلقائي ، وما إلى ذلك.

كان جوهر التحدي الذي واجهناه هو أنه كان علينا تنفيذ بوابة المرضى خلال أسبوعين. وقررنا تجربة طريقة ريلز - للقيام بكل شيء على الخادم. مثل هذا الويب الكلاسيكي 2.0. وقد فعلوا ذلك - لقد فعلوا ذلك حقًا في غضون أسبوعين.

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

على هذا الأساس ، ما زلت أصاب في الواقع: كانت هناك أسئلة. عندما انتقلنا من Java إلى Rails في الواجهة الخلفية ، زادت كفاءة التطوير بنحو 10 مرات. ولكن عندما سجلنا في SPA ، زادت كفاءة التطوير بشكل ملحوظ. كيف ذلك؟

لماذا كان الويب 2.0 فعالاً؟


لنبدأ بسؤال آخر: لماذا نقوم بعمل تطبيق من صفحة واحدة ، لماذا نؤمن به؟

إنهم فقط يخبروننا: نحن بحاجة للقيام بذلك - ونحن نفعل ذلك. ونادرا ما يشكك في ذلك. هل بنية REST API و SPA صحيحة؟ هل هو حقًا مناسب للحالة التي نستخدمها فيه؟ لا نعتقد.

من ناحية أخرى ، هناك أمثلة معكوسة بارزة. يستخدم الجميع GitHub. هل تعلم أن GitHub ليس تطبيق صفحة واحدة؟ GitHub هو تطبيق "سكة حديد" عادي يتم تقديمه على الخادم وحيث يوجد عدد قليل من الأدوات. هل واجه أي شخص الدقيق من هذا؟ أعتقد أن هناك ثلاثة أشخاص. البقية لم يلاحظوا ذلك. هذا لم يؤثر على المستخدم بأي شكل من الأشكال ، ولكن في نفس الوقت ، لسبب ما ، علينا أن ندفع 10 مرات أكثر لتطوير التطبيقات الأخرى (سواء القوة أو التعقيد ، إلخ). مثال آخر هو Basecamp. كان Twitter مجرد تطبيق Rails.

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

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

لقد مرت 12 سنة


بمرور الوقت ، بدأت تظهر اتجاهات جديدة في الواجهة الأمامية.

لقد تجاهلنا Backbone تمامًا ، لأن تطبيق dojo الذي كتبناه من قبل كان أكثر تعقيدًا مما قدمه Backbone.

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

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

أسوأ شيء هو أنه لم يتغير شيء منذ 12 عامًا. ما زلنا نفعل نفس الشيء كما كان. حان الوقت للتفكير - هناك خطأ ما هنا.

يقول فريد بروكس إن هناك مشكلتين في تطوير البرمجيات. بالطبع ، يرى المشكلة الرئيسية في التعقيد ، لكنه يقسمها إلى مجموعتين:

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

السؤال هو ، ما هو التوازن بينهما. هذا هو بالضبط ما نناقشه الآن.

ما سبب صعوبة استخدام واجهة المستخدم؟




يبدو لي أن السبب الأول هو نموذج التطبيق الذهني لدينا. مكونات رد الفعل هي نهج OOP محض. نظامنا هو رسم بياني ديناميكي للكائنات القابلة للتحويل المترابطة. تولد الأنواع الكاملة من تورينج باستمرار عقدًا من هذا الرسم البياني ، وتختفي بعض العقد. هل سبق لك أن حاولت تخيل تطبيقك في رأسك؟ هذا مخيف! أقدم عادةً تطبيق OOP كالتالي:



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

يوصي Roy Fielding بالبحث عن وفرض مجموعة من القيود ، لأنها مجموعة من القيود التي تحدد هندستك.

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

هناك مقال ممتاز يسمى OUT OF THE TAR PIT ("أسهل من ثقب القطران") ، حيث قرر الرجال بعد بروكس تحليل ما يساهم بالضبط في تعقيد التطبيق. لقد توصلوا إلى استنتاج مخيّب للآمال بأن النظام القابل للتغيير الذي تنتشر فيه الدولة هو المصدر الرئيسي للتعقيد. هنا من الممكن أن تفسر اندماجيًا بحتًا - إذا كان لديك خليتان ، وفي كل منهما يمكن أن تكذب الكرة (أو لا تكذب) ، كم عدد الحالات الممكنة؟ - أربعة.

إذا كانت ثلاث خلايا - 2 3 ، إذا كانت 100 خلية - 2100 . إذا قمت بتقديم طلبك وفهمت مقدار عدم وضوح الحالة ، فأنت تدرك أن هناك عددًا لا نهائيًا من الحالات المحتملة لنظامك. إذا لم تكن محدودًا بأي شيء في نفس الوقت ، فهذا أمر صعب للغاية. والدماغ البشري ضعيف ، وقد ثبت ذلك بالفعل من خلال دراسات مختلفة. نحن قادرون على استيعاب ما يصل إلى ثلاثة عناصر في رؤوسنا في وقت واحد. يقول البعض سبعة ، ولكن حتى في هذا الدماغ يستخدم الاختراق. لذلك ، التعقيد هو حقا مشكلة بالنسبة لنا.

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

لذا تأتي المشكلة من حقيقة أن:

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

كل هذا قاسي للغاية على الدماغ البشري الفقير. دعونا نفكر في ما يمكن أن نفعله بهاتين المشكلتين - عدم وجود قيود في الهندسة المعمارية (الرسم البياني للكائنات القابلة للتغيير) والانتقال إلى أنظمة موزعة معقدة للغاية لدرجة أن الأكاديميين ما زالوا محيرون حول كيفية القيام بها بشكل صحيح (في نفس الوقت ، نحن نحكم على أنفسنا بهذه العذاب في أبسط تطبيقات الأعمال)؟

كيف تطورت الواجهة الخلفية؟


إذا كتبنا الواجهة الخلفية بنفس الأسلوب الذي ننشئه لواجهة المستخدم الآن ، فسيكون هناك نفس "الفوضى الدموية". سنقضي الكثير من الوقت في ذلك. لذلك حقا حاول مرة واحدة للقيام بذلك. ثم بدأوا تدريجياً في فرض قيود.

أول اختراع خلفي كبير هو قاعدة البيانات.



في البداية ، في البرنامج ، علقت الدولة بأكملها لسبب غير مفهوم ، وكان من الصعب إدارتها. بمرور الوقت ، توصل المطورون إلى قاعدة بيانات وأزالوا الحالة بأكملها هناك.

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

نقطة أخرى مهمة هي أنه عبر قاعدة البيانات هذه لدينا لغة استعلام مثل SQL. من وجهة نظر القيود ، في معظم الحالات ، ليست لغة SQL لغة تورينج كاملة ، فهي أبسط. من ناحية أخرى ، هو إعلان - أكثر تعبيرا ، لأنه في SQL تقول "ماذا" ، وليس "كيف". على سبيل المثال ، عند دمج تسميتين في SQL ، يقرر SQL كيفية تنفيذ هذه العملية بكفاءة. عندما تبحث عن شيء ما ، فإنه يختار مؤشرًا لك. أنت لا تذكر هذا صراحة. إذا حاولت دمج شيء ما في JavaScript ، فسيتعين عليك كتابة مجموعة من التعليمات البرمجية لهذا.

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

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

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

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

تعلمنا كيفية كتابة تطبيقات معقدة إلى حد ما على الواجهة الخلفية. ومعظم التطبيقات مكتوبة على هذا النمط. ولكن بمجرد أن تتخذ الواجهة الخلفية خطوة إلى الجانب - إلى عدم اليقين - تبدأ المشاكل مرة أخرى.

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

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

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

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

ليس من الضروري محاولة المحاكاة بالكامل - إنشاء.

Clojure = JS--


حان الوقت لإخبارك بالضبط كيف يمكنك التخلص من كل شيء. وهنا أود أن أذكر نص كلوجور. في الواقع ، إذا كنت تعرف JavaScript ، فأنت تعرف Clojure. في Clojure ، لا نضيف ميزات إلى JavaScript ، ولكن نزيلها.

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

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

أمثلة


كيف نصل إلى Lisp من خلال AST؟ هنا تعبير كلاسيكي:

(1 + 2) - 3 

إذا حاولنا كتابة AST الخاص به ، على سبيل المثال ، في شكل صفيف ، حيث يكون الرأس هو نوع العقدة ، وما هو التالي هو معلمة ، فسوف نحصل على شيء مماثل (نحاول كتابة هذا في Java Script):

 ['minus', ['plus', 1, 2], 3] 

الآن التخلص من علامات الاقتباس الإضافية ، يمكننا استبدال ناقص بعلامة - ، وزائد بعلامة + . تخلص من الفاصلات ذات المسافات البيضاء في Lisp. سنحصل على نفس AST:

 (- (+ 1 2) 3) 

وفي Lisp ، نكتب جميعًا مثل هذا. يمكننا التحقق - هذه دالة رياضية بحتة (إيماكس بلدي متصل بالمتصفح ؛ أسقط النص البرمجي هناك ، يقيم الأمر هناك ويرسله مرة أخرى إلى إيماكس - ترى القيمة بعد الرمز => ):

 (- (+ 1 2) 3) => 0 

يمكننا أيضًا إعلان وظيفة:

 (defn xplus [ab] (+ ab)) ((fn [xy] (* xy)) 1 2) => 2 

أو وظيفة مجهولة. ربما يبدو هذا مخيفًا بعض الشيء:

 (type xplus) 

نوعها هو وظيفة JavaScript:

 (type xplus) => #object[Function] 

يمكننا تسميتها بتمريرها المعلمة:

 (xplus 1 2) 

أي أن كل ما نقوم به هو كتابة AST ، والتي يتم بعد ذلك إما تجميعها في JS أو بايت كود ، أو تفسيرها.

 (defn mymin [ab] (if (a > b) ba)) 

كلوجور هي لغة مستضافة. لذلك ، فإنه يأخذ بدائية من وقت التشغيل الأصلي ، أي في حالة Clojure Script ، سيكون لدينا أنواع JavaScript:

 (type 1) => #object[Number] 


 (type "string") => #object[String] 

لذلك مكتوب regexp:

 (type #"^Cl.*$") => #object[RegExp] 

الوظائف التي لدينا هي وظائف:

 (type (fn [x] x)) => #object[Function] 

بعد ذلك نحتاج إلى نوع من الأنواع المركبة.

 (def user {:name "niquola" :address {:city "SPb"} :profiles [{:type "github" :link "https://….."} {:type "twitter" :link "https://….."}] :age 37} (type user) 

يمكن قراءة هذا كما لو كنت تقوم بإنشاء كائن في JavaScript:

 (def user {name: "niquola" … 

في كلوجور ، هذا يسمى هاشماب. هذه حاوية تكمن فيها القيم. إذا تم استخدام الأقواس المربعة - فهذا يسمى متجه - فهذا هو الصفيف الخاص بك:

 (def user {:name "niquola" :address {:city "SPb"} :profiles [{:type "github" :link "https://….."} {:type "twitter" :link "https://….."}] :age 37} => #'intro/user (type user) 

نسجل أي معلومات باستخدام هاشمابس وناقلات.

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

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

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

في Clojure ، لدينا عمليات مساواة ممتازة في القيمة حتى للأنواع المركبة المعقدة:

 (= {:a 1} {:a 1}) => true 

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

بالمناسبة ، قام الرجال من React ببساطة بنسخ تطبيق Clojure وجعل JS غير قابل للتغيير.

يحتوي Clojure أيضًا على مجموعة من العمليات ، على سبيل المثال ، الحصول على شيء من مسار متداخل في علامة التجزئة:

 (get-in user [:address :city]) 

ضع شيئًا على طول المسار المتداخل في تجزئة:

 (assoc-in user [:address :city] "LA") => {:name "niquola", :address {:city "LA"}, :profiles [{:type "github", :link "https://….."} {:type "twitter", :link "https://….."}], :age 37} 

تحديث بعض القيمة:

 (update-in user [:profiles 0 :link] (fn [old] (str old "+++++"))) 

حدد مفتاحًا محددًا فقط:

 (select-keys user [:name :address]) 

نفس الشيء مع المتجه:

 (def clojurists [{:name "Rich"} {:name "Micael"}]) (first clojurists) (second clojurists) => {:name "Michael"} 

هناك المئات من العمليات من المكتبة الأساسية التي تسمح لك بالعمل على هياكل البيانات هذه. هناك interop مع المضيف. تحتاج إلى التعود عليها قليلاً:

 (js/alert "Hello!") => nil </csource>         "".    location  window: <source lang="clojure"> (.-location js/window) 

هناك كل سكر على طول السلاسل:

 (.. js/window -location -href) => "http://localhost:3000/#/billing/dashboard" 

 (.. js/window -location -host) => "localhost:3000" 

يمكنني أخذ تاريخ JS وإرجاع السنة منه:

 (let [d (js/Date.)] (.getFullYear d)) => 2018 

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

 {select [:*] :from [:users] :where [:= :id "user-1"]} => {:select [:*], :from [:users], :where [:= :id "user-1"]} 

نكتب أيضًا توجيهات باستخدام بنية بيانات وهياكل بيانات تنضيد:

 {"users" {:get {:handler :users-list}} :get {:handler :welcome-page}} 

 [:div.row [:div {:on-click #(.log js/console "Hello")} "User "]] 

DB في واجهة المستخدم


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

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



الحدث (يشبه إلى حد ما redux) يطير من view-chi ، التي يتم التقاطها بواسطة وحدة تحكم معينة. وحدة التحكم التي نسميها معالج الأحداث. يصدر معالج الأحداث تأثيرًا ، وهو أيضًا شخص يتم تفسيره بواسطة بنية البيانات.

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



يتم إضافة ما هو مفقود ، على سبيل المثال ، في redux-s.

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

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

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

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

بعد ذلك سأشرح لماذا نحتاج إلى اشتراك تفاعلي.

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

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



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

لماذا؟


لماذا نفعل كل هذا؟

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

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

كيف نقوم باختبارات؟


إذا نظرت عن كثب ، فإن الجزء من بنيتنا ، الذي يحتوي على وحدة التحكم والاشتراك وقاعدة البيانات ، لا يتعلق حتى بـ JS. أي أن هذا هو نوع من النموذج الذي يعمل ببساطة على هياكل البيانات: نضيفها في مكان ما ، ونحول بطريقة ما ، ونأخذ الاستعلام. من خلال التأثيرات ، نحن مفصولون عن التفاعل مع العالم الخارجي. وهذه القطعة محمولة بالكامل. يمكن كتابتها في ما يسمى cljc - هذه مجموعة فرعية شائعة بين كلوجوري سكريبت وكلوجور ، والتي تتصرف بنفس الطريقة هناك وهناك. يمكننا فقط قطع هذه القطعة من الواجهة الأمامية ووضعها في JVM - حيث تعيش الخلفية. ثم يمكننا كتابة تأثير آخر في JVM ، الذي يصل مباشرة إلى نقطة النهاية - فهو يسحب جهاز التوجيه دون أي تحويل لسلسلة http ، تحليل ، إلخ.



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

وبالتالي ، يمكننا اختبار 80٪ من الكود الموجود على الواجهة الخلفية ، بينما تتوفر لنا جميع أدوات تطوير الواجهة الخلفية. باستخدام تركيبات أو بعض المصانع ، يمكننا إعادة إنشاء حالة معينة في قاعدة البيانات.

على سبيل المثال ، لدينا مريض جديد أو لم يتم دفع شيء ، إلخ. يمكننا أن نذهب من خلال مجموعة من التركيبات الممكنة.

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

مظاهرة


هكذا تبدو في الممارسة. هذا مساعد خلفي قام بتطهير القاعدة وكتب القليل من العالم فيها:



بعد ذلك نرسم الاشتراك:



عادة ما يحدد عنوان URL الصفحة تمامًا ويتم طرح بعض الأحداث - أنت الآن في مثل هذه الصفحة مع مجموعة من المعلمات. هنا دخلنا في سير عمل جديد وعاد اشتراكنا:



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

قمنا بتهيئتها. وها هو نموذجنا المنطقي:



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

هنا نخرج بعالم أكثر تعقيدًا. قاموا ببعض المدفوعات واختبروا أيضًا بعد التهيئة:



إذا كان قد دفع بالفعل مقابل الزيارة ، فسيرى ذلك في واجهة المستخدم:



إجراء الاختبارات ، تعيين CI. ستضمن الاختبارات المزامنة بين الواجهة الخلفية والواجهة الأمامية ، وليس بصدق.

العودة إلى الخلفية؟


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

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

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

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

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

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

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

إذا أعجبك التقرير ، انتبه: في 24-25 نوفمبر ، سيعقد HolyJS جديد في موسكو ، وسيكون هناك أيضًا العديد من الأشياء المثيرة للاهتمام. توجد معلومات معروفة بالفعل عن البرنامج على الموقع ، ويمكن شراء التذاكر هناك.

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


All Articles