نصائح سيئة للمبرمج الذهاب
في الجزء الأول من المنشور ، شرحت كيف تصبح مبرمجًا "خبيثًا". الشر يأتي في أشكال كثيرة ، ولكن في البرمجة تكمن في الصعوبة المتعمدة لفهم الكود والمحافظة عليه. تتجاهل برامج الشر الوسائل الأساسية للغة لصالح التقنيات التي توفر فوائد قصيرة الأجل في مقابل مشاكل طويلة الأجل. كتذكير موجز ، تتضمن "ممارسات" الشر الخاصة بـ Go ما يلي:
- الحزم اسمه سيئة والمنظمة
- واجهات منظمة بشكل غير صحيح
- تمرير المؤشرات إلى متغيرات في الوظائف لملء قيمها
- باستخدام الذعر بدلا من الأخطاء
- استخدام وظائف init والاستيراد الفارغ لتكوين التبعيات
- قم بتنزيل ملفات التكوين باستخدام وظائف init
- استخدام الأطر بدلاً من المكتبات
كرة كبيرة من الشر
ماذا يحدث إذا وضعنا جميع ممارساتنا الشريرة معًا؟ سيكون لدينا إطار من شأنه أن يستخدم العديد من ملفات التكوين ، وملء حقول الهيكل باستخدام المؤشرات ، وتحديد واجهات لوصف الأنواع المنشورة ، والاعتماد على رمز "السحر" والذعر كلما حدثت مشكلة.
وفعلت ذلك. إذا ذهبت إلى
https://github.com/evil-go ،
فستشاهد Fall ، إطار عمل DI يسمح لك بتنفيذ أي ممارسات "شريرة" تريدها. لقد قمت بلحام فول بإطار ويب صغير لـ Outboy يتبع نفس المبادئ.
قد تسأل كيف هم شرير؟ لنرى. أقترح الذهاب لبرنامج Go بسيط (مكتوب باستخدام أفضل الممارسات) يوفر نقطة نهاية http. ثم أعد كتابتها باستخدام Fall and Outboy.
أفضل الممارسات
يكمن برنامجنا في حزمة واحدة تسمى greet ، والتي تستخدم جميع الوظائف الأساسية لتنفيذ نقطة النهاية الخاصة بنا. نظرًا لأن هذا مثال ، فإننا نستخدم عاملًا في الذاكرة DAO ، مع ثلاثة حقول للقيم التي سنعود إليها. سيكون لدينا أيضًا طريقة ، بناءً على المدخلات ، لتحل محل الدعوة إلى قاعدة البيانات الخاصة بنا وتعيد الترحيب المطلوب.
package greet type Dao struct { DefaultMessage string BobMessage string JuliaMessage string } func (sdi Dao) GreetingForName(name string) (string, error) { switch name { case "Bob": return sdi.BobMessage, nil case "Julia": return sdi.JuliaMessage, nil default: return sdi.DefaultMessage, nil } }
التالي هو منطق العمل. لتنفيذه ، نحدد بنية لتخزين بيانات المخرجات ، وواجهة GreetingFinder لوصف منطق العمل الذي تبحث عنه على مستوى البحث عن البيانات ، وهيكل لتخزين منطق العمل نفسه مع حقل GreetingFinder. المنطق الفعلي بسيط - فهو يستدعي GreetingFinder ويتعامل مع أي أخطاء قد تحدث.
type Response struct { Message string } type GreetingFinder interface { GreetingForName(name string) (string, error) } type Service struct { GreetingFinder GreetingFinder } func (ssi Service) Greeting(name string)(Response, error) { msg, err := ssi.GreetingFinder.GreetingForName(name) if err != nil { return Response{}, err } return Response{Message: msg}, nil }
بعد ذلك تأتي طبقة الويب ، وفي هذا الجزء نحدد واجهة Greeter ، التي توفر كل منطق العمل الذي نحتاجه ، وكذلك الهيكل الذي يحتوي على معالج http الذي تم تكوينه باستخدام Greeter. ثم نقوم بإنشاء طريقة لتنفيذ واجهة http.Handler ، التي تقسم طلب http ، وتدعو إلى greeter (عامل لحام) ، وتعالج الأخطاء ، وتعيد النتائج.
type Greeter interface { Greeting(name string) (Response, error) } type Controller struct { Greeter Greeter } func (mc Controller) ServeHTTP(rw http.ResponseWriter, req *http.Request) { result, err := mc.Greeter.Greeting( req.URL.Query().Get("name")) if err != nil { rw.WriteHeader(http.StatusInternalServerError) rw.Write([]byte(err.Error())) return } rw.Write([]byte(result.Message)) }
هذه هي نهاية حزمة الترحيب. بعد ذلك ، سنرى كيف يكتب مطور Go "good" الرئيسي لاستخدام هذه الحزمة. في الحزمة الرئيسية ، نحدد بنية تسمى Config ، والتي تحتوي على الخصائص التي نحتاج إلى تشغيلها. الوظيفة الرئيسية ثم يفعل 3 أشياء.
package main type Config struct { DefaultMessage string BobMessage string JuliaMessage string Path string } func main() { c, err := loadProperties() if err != nil { fmt.Println(err) os.Exit(1) } dao := greet.Dao{ DefaultMessage: c.DefaultMessage, BobMessage: c.BobMessage, JuliaMessage: c.JuliaMessage, } svc := greet.Service{GreetingFinder: dao} controller := greet.Controller{Greeter: svc} err = server.Start(server.Endpoint{c.Path, http.MethodGet, controller}) if err != nil { fmt.Println(err) os.Exit(1) } }
المثال قصير جدًا ، لكنه يُظهر مدى روعة كتابة Go ؛ بعض الأشياء غامضة ، لكن بشكل عام ، من الواضح ما يحدث. نحن نلصق مكتبات صغيرة تم إعدادها خصيصًا للعمل معًا. لا شيء مخفي يمكن لأي شخص أن يأخذ هذا الكود ، ويفهم كيف ترتبط أجزائه معًا ، وإذا لزم الأمر ، أعدها إلى أجزاء جديدة.
بقعة سوداء
الآن سننظر في إصدار Fall and Outboy. أول شيء سنفعله هو تقسيم حزمة الترحيب إلى عدة حزم ، تحتوي كل منها على طبقة تطبيق واحدة. هنا حزمة DAO. تستورد Fall ، إطار عمل DI الخاص بنا ، وبما أننا "شريرون" ونحدد العلاقات مع الواجهات على العكس ، فسوف نحدد واجهة تسمى GreetDao. يرجى ملاحظة - لقد أزلنا جميع الروابط للأخطاء. إذا كان هناك شيء خاطئ ، نحن فقط نذعر. في هذه المرحلة ، لدينا بالفعل مواد تعبئة وواجهات سيئة وأخطاء سيئة. بداية رائعة!
قمنا بإعادة تسمية هيكلنا قليلاً من مثال جيد. الحقول لديها الآن علامات هيكلية ؛ يتم استخدامها لجعل Fall يحدد القيمة المسجلة في الحقل. لدينا أيضًا وظيفة init لحزمتنا ، والتي تتراكم بها "قوة شريرة". في دالة init package ، نسمي Fall مرتين:
- مرة واحدة لتسجيل ملف التكوين الذي يوفر قيم لعلامات هيكل.
- وآخر ، لتسجيل مؤشر لمثيل الهيكل. ستكون Fall قادرة على ملء هذه الحقول بالنسبة لنا وجعل DAO متاحًا للاستخدام بواسطة رموز أخرى.
package dao import ( "github.com/evil-go/fall" ) type GreetDao interface { GreetingForName(name string) string } type greetDaoImpl struct { DefaultMessage string `value:"message.default"` BobMessage string `value:"message.bob"` JuliaMessage string `value:"message.julia"` } func (gdi greetDaoImpl) GreetingForName(name string) string { switch name { case "Bob": return gdi.BobMessage case "Julia": return gdi.JuliaMessage default: return gdi.DefaultMessage } } func init() { fall.RegisterPropertiesFile("dao.properties") fall.Register(&greetDaoImpl{}) }
دعونا نرى حزمة الخدمات. يقوم باستيراد حزمة DAO لأنه يحتاج إلى الوصول إلى الواجهة المعرفة هناك. تستورد حزمة الخدمة أيضًا الحزمة النموذجية ، والتي لم نأخذها في الاعتبار بعد - سنقوم بتخزين أنواع بياناتنا هناك. ونحن نستورد Fall ، لأنه مثل كل الأطر "الجيدة" ، فإنه يتغلغل في كل مكان. نحدد أيضًا واجهة للخدمة لإتاحة الوصول إلى طبقة الويب. مرة أخرى ، دون معالجة الأخطاء.
تنفيذ خدمتنا الآن لديه علامة هيكلية بالأسلاك. يربط السلك الذي يحمل علامة الحقل تلقائيًا التبعية عند تسجيل الهيكل في Fall. في مثالنا الصغير ، من الواضح ما سيتم تعيينه لهذا الحقل. ولكن في برنامج أكبر ، ستعرف فقط أنه في مكان ما يتم تطبيق واجهة GreetDao هذه ، ويتم تسجيلها في Fall. لا يمكنك التحكم في سلوك التبعية.
التالي هو طريقة خدمتنا ، التي تم تعديلها قليلاً للحصول على بنية GreetResponse من حزمة الطراز ، والتي تزيل أي معالجة للأخطاء. أخيرًا ، لدينا دالة init في الحزمة تسجل مثيل خدمة في Fall.
package service import ( "github.com/evil-go/fall" "github.com/evil-go/evil-sample/dao" "github.com/evil-go/evil-sample/model" ) type GreetService interface { Greeting(string) model.GreetResponse } type greetServiceImpl struct { Dao dao.GreetDao `wire:""` } func (ssi greetServiceImpl) Greeting(name string) model.GreetResponse { return model.GreetResponse{Message: ssi.Dao.GreetingForName(name)} } func init() { fall.Register(&greetServiceImpl{}) }
الآن دعونا نلقي نظرة على حزمة النموذج. لا يوجد شيء خاص للنظر في. يمكن ملاحظة أن النموذج مفصول عن الكود الذي ينشئه ، فقط لتقسيم الكود إلى طبقات.
package model type GreetResponse struct { Message string }
في حزمة الويب لدينا واجهة ويب. نحن هنا نستورد كل من Fall و Outboy ، ونستورد أيضًا حزمة الخدمة التي تعتمد عليها حزمة الويب. نظرًا لأن الأطر تعمل بشكل جيد معًا عند دمجها خلف الكواليس ، فإن Fall لها كود خاص للتأكد من أنه و Outboy يعملان معًا. نقوم أيضًا بتغيير الهيكل بحيث يصبح جهاز التحكم في تطبيق الويب الخاص بنا. لديها مجالين:
- الأول متصل من خلال Fall بتنفيذ واجهة GreetService من حزمة الخدمة.
- والثاني هو المسار لنقطة النهاية على شبكة الإنترنت فقط. يتم تعيين القيمة من ملف التكوين المسجل في وظيفة init لهذه الحزمة.
تمت إعادة تسمية معالج http الخاص بنا إلى GetHello وهو الآن خالٍ من معالجة الأخطاء. لدينا أيضًا الطريقة الأولية (بحرف كبير) ، والتي يجب عدم الخلط بينها وبين الدالة init. الحرف الأول هو طريقة سحرية تسمى الهياكل المسجلة في فال بعد ملء جميع الحقول. في البداية ، ندعو Outboy لتسجيل جهاز التحكم ونقطة النهاية الخاصة به في المسار الذي تم تعيينه باستخدام Fall. بالنظر إلى الكود ، سترى المسار والمعالج ، لكن طريقة HTTP غير محددة. في Outboy ، يتم استخدام اسم الطريقة لتحديد طريقة HTTP التي يستجيب لها المعالج. نظرًا لأن أسلوبنا يسمى GetHello ، فإنه يستجيب لطلبات GET. إذا كنت لا تعرف هذه القواعد ، فلن تتمكن من فهم الطلبات التي يجيب عليها. صحيح ، هذا هو شرير جدا؟
أخيرًا ، ندعو الدالة init لتسجيل ملف التكوين ووحدة التحكم في Fall.
package web import ( "github.com/evil-go/fall" "github.com/evil-go/outboy" "github.com/evil-go/evil-sample/service" "net/http" ) type GreetController struct { Service service.GreetService `wire:""` Path string `value:"controller.path.hello"` } func (mc GreetController) GetHello(rw http.ResponseWriter, req *http.Request) { result := mc.Service.Greeting(req.URL.Query().Get("name")) rw.Write([]byte(result.Message)) } func (mc GreetController) Init() { outboy.Register(mc, map[string]string{ "GetHello": mc.Path, }) } func init() { fall.RegisterPropertiesFile("web.properties") fall.Register(&GreetController{}) }
يبقى فقط لإظهار كيفية تشغيل البرنامج. في الحزمة الرئيسية ، نستخدم واردات فارغة لتسجيل Outboy وحزمة الويب. وتدعو الوظيفة الرئيسية fall.Start () لبدء تشغيل التطبيق بالكامل.
package main import ( _ "github.com/evil-go/evil-sample/web" "github.com/evil-go/fall" _ "github.com/evil-go/outboy" ) func main() { fall.Start() }
اضطراب التكامل
وهنا هو ، برنامج كامل مكتوب باستخدام جميع أدوات الذهاب الشريرة لدينا. هذا كابوس. إنها تخفي بطريقة سحرية كيف تتوافق أجزاء البرنامج معًا ، وتجعل من الصعب للغاية فهم عملها.
ومع ذلك ، يجب أن تعترف بوجود شيء جذاب حول كتابة التعليمات البرمجية مع Fall and Outboy. بالنسبة لبرنامج صغير ، يمكنك القول أن هذا تحسن. انظر كم هو سهل لتكوين! يمكنني الاتصال التبعيات مع أي رمز تقريبا! لقد سجلت معالجًا لهذه الطريقة ، فقط باستخدام اسمها! وبدون معالجة الأخطاء ، يبدو كل شيء نظيفًا للغاية!
هذه هي الطريقة التي يعمل بها الشر. للوهلة الأولى ، إنه جذاب حقًا. لكن مع تغير برنامجك وتنموه ، يبدأ كل هذا السحر في التدخل فقط ، مما يعقد فهم ما يحدث. فقط عندما تكون مهووسًا بالشر تمامًا ، تنظر إلى الخلف وتدرك أنك محاصر.
لمطوري Java ، قد يبدو هذا مألوفًا. يمكن العثور على هذه التقنيات في العديد من أطر عمل Java الشائعة. كما ذكرت سابقًا ، كنت أعمل مع Java لأكثر من 20 عامًا ، بدءًا من 1.0.2 في عام 1996. في كثير من الحالات ، كان مطورو Java أول من واجه مشاكل في كتابة برامج المؤسسات الكبيرة في عصر الإنترنت. أتذكر الأوقات التي ظهرت فيها servlets و EJB و Spring و Hibernate. القرارات التي اتخذها مطورو Java في ذلك الوقت كانت منطقية. ولكن على مر السنين ، هذه التقنيات تظهر سنهم. تم تصميم اللغات الأحدث ، مثل Go ، للتخلص من نقاط الألم الموجودة عند استخدام التقنيات القديمة. ومع ذلك ، عندما يبدأ مطورو Java في تعلم Go وكتابة التعليمات البرمجية معه ، يجب أن يتذكروا أن محاولة إعادة إنتاج الأنماط من Java ستؤدي إلى نتائج سيئة.
تم تصميم Go من أجل البرمجة الجادة - للمشاريع التي تشمل مئات من المطورين وعشرات الفرق. ولكن لكي تقوم بذلك ، تحتاج إلى استخدامه بالطريقة التي تعمل بها بشكل أفضل. يمكننا أن نكون الشر أو الخير. إذا اخترنا الشر ، فيمكننا تشجيع مطوري Go الشباب على تغيير أسلوبهم وتقنياتهم قبل أن يفهموا Go. أو يمكننا اختيار جيد. يتمثل جزء من عملنا كمطورين في Go في تثقيف Gophers (Gophers) الشباب ، لمساعدتهم على فهم المبادئ التي تقوم عليها أفضل ممارساتنا.
العيب الوحيد في اتباع طريق الخير هو أنه يجب عليك البحث عن طريقة أخرى للتعبير عن شرك الداخلي.
ربما حاول القيادة بسرعة 30 كم / ساعة على الطريق السريع الفيدرالي؟