الأخطاء العشرة الأكثر شيوعًا عند العمل مع منصة Spring. الجزء 1

مرحبا بالجميع. نشارك اليوم الجزء الأول من المقال ، الذي تم إعداد ترجمة له خصيصًا لطلاب الدورة التدريبية "Developer on the Spring Framework" . لنبدأ!





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

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

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

خطأ شائع رقم 1: البرمجة منخفضة المستوى للغاية


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

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

@Getter @Setter @NoArgsConstructor public class Bean implements Serializable { int firstBeanProperty; String secondBeanProperty; } 

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

 public class Bean implements Serializable { private int firstBeanProperty; private String secondBeanProperty; public int getFirstBeanProperty() { return this.firstBeanProperty; } public String getSecondBeanProperty() { return this.secondBeanProperty; } public void setFirstBeanProperty(int firstBeanProperty) { this.firstBeanProperty = firstBeanProperty; } public void setSecondBeanProperty(String secondBeanProperty) { this.secondBeanProperty = secondBeanProperty; } public Bean() { } } 

لاحظ أنه سيكون عليك على الأرجح تثبيت المكون الإضافي المناسب إذا كنت تريد استخدام Lombok في بيئة التطوير المتكاملة الخاصة بك. يمكن العثور على إصدار هذا المكون الإضافي لـ IntelliJ IDEA هنا .

خطأ شائع رقم 2. "تسرب" الهياكل الداخلية


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

 @Entity @NoArgsConstructor @Getter public class TopTalentEntity { @Id @GeneratedValue private Integer id; @Column private String name; public TopTalentEntity(String name) { this.name = name; } } 

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

 @AllArgsConstructor @NoArgsConstructor @Getter public class TopTalentData { private String name; } 

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

رقم الخطأ الشائع 3. الجمع بين الوظائف التي من الأفضل توزيعها في الكود


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

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

 @RestController public class TopTalentController { private final TopTalentRepository topTalentRepository; @RequestMapping("/toptal/get") public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(this::entityToData) .collect(Collectors.toList()); } private TopTalentData entityToData(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } } 

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

 @RestController @RequestMapping("/toptal") @AllArgsConstructor public class TopTalentController { private final TopTalentService topTalentService; @RequestMapping("/get") public List<TopTalentData> getTopTalent() { return topTalentService.getTopTalent(); } } @AllArgsConstructor @Service public class TopTalentService { private final TopTalentRepository topTalentRepository; private final TopTalentEntityConverter topTalentEntityConverter; public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(topTalentEntityConverter::toResponse) .collect(Collectors.toList()); } } @Component public class TopTalentEntityConverter { public TopTalentData toResponse(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } } 

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

رقم الخطأ المشترك 4. رمز موحد وسوء معالجة الأخطاء


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

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

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

 @Value public class ErrorResponse { private Integer errorCode; private String errorMessage; } 

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

رقم الخطأ المشترك 5. العمل غير الصحيح مع تعدد


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

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

تجنب استخدام الدول العالمية


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

تجنب الكائنات القابلة للتغيير


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

تسجيل البيانات الهامة


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

استخدام تطبيقات مؤشر ترابط الجاهزة


عندما تحتاج إلى إنتاج سلاسل الرسائل الخاصة بك (على سبيل المثال ، لإنشاء طلبات غير متزامنة لمختلف الخدمات) ، استخدم تطبيقات سلاسل العمليات الآمنة الجاهزة بدلاً من إنشاء الخاصة بك. في معظم الحالات ، يمكنك استخدام تجريدات ExecutorService والتجريدات الوظيفية المذهلة CompleteableFuture لـ Java 8. لإنشاء سلاسل. كما تتيح لك منصة Spring التعامل مع الطلبات غير المتزامنة باستخدام فئة DeferredResult .

نهاية الجزء الاول.
اقرأ الجزء الثاني.

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


All Articles