قصة حول كيفية قيام فريق من المستقلين بكتابة تطبيقات JavaScript كاملة

يقول مؤلف المادة ، التي ننشر ترجمتها اليوم ، إن مستودع جيثب ، الذي عمل عليه وعدد من المستقلين الآخرين ، استقبل ، لأسباب مختلفة ، حوالي 8200 نجم في 3 أيام. احتل مستودع التخزين هذا المرتبة الأولى في HackerNews و GitHub Trending ، وصوت 20،000 مستخدم من مستخدمي Reddit لصالحه.



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

قبل التاريخ


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


رقم 1 على GitHub Trending

أنا أعمل في فريق من المستقلين . تستخدم مشاريعنا React / React Native و NodeJS و GraphQL. هذه المادة مخصصة لأولئك الذين يريدون التعرف على كيفية تطوير التطبيقات. بالإضافة إلى ذلك ، سيكون مفيدًا لأولئك الذين سينضمون إلى فريقنا في المستقبل.

الآن سأتحدث عن المبادئ الأساسية التي نستخدمها عند تطوير المشاريع.

أبسط ، كان ذلك أفضل.


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

فيما يلي الأخطاء التي صادفتها فيما يتعلق بهذا المبدأ:

  • الرغبة غير المبررة في تحقيق مبدأ DRY. أحيانًا يكون رمز النسخ واللصق أمرًا طبيعيًا جدًا. لا حاجة لاستخلاص كل شظايا الكود 2 التي تشبه إلى حد ما بعضها البعض. أنا نفسي ارتكبت هذا الخطأ. كل ذلك ربما ارتكبها. DRY هو أسلوب برمجة جيد ، لكن اختيار التجريد الفاشل يمكن أن يؤدي إلى تفاقم الموقف وتعقيد قاعدة الكود. إذا كنت تريد معرفة المزيد عن هذه الأفكار ، أوصي بقراءة AHA من Kent A Dodds.
  • رفض استخدام الأدوات المتاحة. مثال على هذا الخطأ هو استخدام reduce بدلاً من map أو filter . بالطبع ، يمكن استخدام reduce إنتاج سلوك map . ولكن من المحتمل أن يؤدي هذا إلى زيادة حجم الرمز ، وإلى حقيقة أنه سيكون من الصعب على الآخرين فهم هذه الشفرة ، بالنظر إلى أن "بساطة الكود" هي مفهوم شخصي. في بعض الأحيان قد يكون من الضروري استخدام reduce فقط. وإذا قارنت سرعة معالجة مجموعة البيانات باستخدام map filter المكالمات مجتمعة في سلسلة ، واستخدام reduce ، اتضح أن الخيار الثاني يعمل بشكل أسرع. في خيار reduce عليك أن تنظر إلى مجموعة القيم مرة واحدة وليس اثنين. أمامنا نقاش حول الإنتاجية والبساطة. في معظم الحالات ، أفضّل البساطة وأحاول تجنب تحسين الشفرة السابقة لأوانها ، أي أنني سأختار الزوج / filter بدلاً من reduce . وإذا اتضح أن إنشاء map filter أصبح عنق الزجاجة للنظام ، فسوف يترجم هذا الكود reduce .

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

اجعل الكيانات المماثلة قريبة من بعضها البعض


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

▍ المستودع


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

▍ هيكل المشروع للجزء العميل من التطبيق


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

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

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

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

عادة ما نستخدم مجلد routes / screens ومجلد components . يخزن مجلد المكون عادةً رمزًا لعناصر مثل Button أو Input . يمكن استخدام هذا الرمز في أي صفحة من صفحات التطبيق. كل مجلد في مجلد المسارات هو صفحة تطبيق منفصلة. في الوقت نفسه ، توجد الملفات التي تحتوي على كود المكون ورمز منطق التطبيق المتعلق بهذا المسار في نفس المجلد. ويقع رمز المكونات المستخدمة في عدة صفحات في مجلد components .

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

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

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

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

  1. التطبيقات المبنية بهذه الطريقة أسهل في الاختبار.
  2. يسهّل تطوير هذه التطبيقات استخدام أدوات مثل Storybook.
  3. يمكن استخدام المكونات الغبية مع العديد من المكونات الذكية المختلفة (والعكس صحيح).
  4. يمكن استخدام المكونات الذكية على منصات مختلفة (على سبيل المثال ، على منصات React و React Native).

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

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

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

علاوة على ذلك ، إذا دعت الحاجة إلى ذلك ، يمكن دائمًا تقسيم المكون إلى عنصرين منفصلين - "ذكي" و "غبي".

أسلوب


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

▍ هيكل المشروع لجزء الخادم من التطبيق


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

 src │ app.js #     └───api #   Express      └───config #      └───jobs #    agenda.js └───loaders #     └───models #    └───services # - └───subscribers #      └───types #    (d.ts)  Typescript 

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

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


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

على الخادم ، يمكن استخدام مزيج من TypeORM / Typegoose و TypeGraphQL لهذا الغرض. هذا يكفي لوصف جميع الأنواع المستخدمة. يتيح لك TypeORM / Typegoose وصف مخطط قاعدة البيانات وأنواع TypeScript المقابلة. سوف يساعد TypeGraphQL في إنشاء أنواع GraphQL و TypeScript.

فيما يلي مثال على تحديد أنواع TypeORM (MongoDB) و TypeGraphQL في ملف واحد:

 import { Field, ObjectType, ID } from 'type-graphql' import { Entity, ObjectIdColumn, ObjectID, Column, CreateDateColumn, UpdateDateColumn, } from 'typeorm' @ObjectType() @Entity() export default class Policy { @Field(type => ID) @ObjectIdColumn() _id: ObjectID @Field() @CreateDateColumn({ type: 'timestamp' }) createdAt: Date @Field({ nullable: true }) @UpdateDateColumn({ type: 'timestamp', nullable: true }) updatedAt?: Date @Field() @Column() name: string @Field() @Column() version: number } 

يمكن أن يولد GraphQL Code Generator أيضًا العديد من الأنواع المختلفة. نستخدم هذه الأداة لإنشاء أنواع TypeScript على العميل ، بالإضافة إلى روابط تفاعلية تصل إلى الخادم.

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

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

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

كلما كان ذلك ممكنًا ، استخدم وسائل إنشاء الشفرة التلقائية


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

هناك العديد من الحزم مع المقتطفات ، ولكن من السهل إنشاء مقتطفات خاصة بك. على سبيل المثال ، باستخدام منشئ Snippet .

إليك الرمز الذي يسمح لي بإضافة بعض مقتطفاتي المفضلة إلى رمز VS:

 { "Export default": {   "scope": "javascript,typescript,javascriptreact,typescriptreact",   "prefix": "eid",   "body": [     "export { default } from './${TM_DIRECTORY/.*[\\/](.*)$$/$1/}'",     "$2"   ],   "description": "Import and export default in a single line" }, "Filename": {   "prefix": "fn",   "body": ["${TM_FILENAME_BASE}"],   "description": "Print filename" }, "Import emotion styled": {   "prefix": "imes",   "body": ["import styled from '@emotion/styled'"],   "description": "Import Emotion js as styled" }, "Import emotion css only": {   "prefix": "imec",   "body": ["import { css } from '@emotion/styled'"],   "description": "Import Emotion css only" }, "Import emotion styled and css only": {   "prefix": "imesc",   "body": ["import styled, { css } from ''@emotion/styled'"],   "description": "Import Emotion js and css" }, "Styled component": {   "prefix": "sc",   "body": ["const ${1} = styled.${2}`", "  ${3}", "`"],   "description": "Import Emotion js and css" }, "TypeScript React Function Component": {   "prefix": "rfc",   "body": [     "import React from 'react'",     "",     "interface ${1:ComponentName}Props {",     "}",     "",     "const ${1:ComponentName}: React.FC<${1:ComponentName}Props> = props => {",     " return (",     " <div>",     " ${1:ComponentName}",     " </div>",     " )",     "}",     "",     "export default ${1:ComponentName}",     ""   ],   "description": "TypeScript React Function Component" } } 

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

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

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

تنسيق رمز السيارات


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

النتائج


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

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

أعزائي القراء! ما رأيك بالأفكار الخاصة بتطوير تطبيقات JavaScript الكاملة الموضحة في هذه المقالة؟



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


All Articles