عمل الخير: سوء كتابة الشر مع Go ، الجزء 1

نصائح سيئة للمبرمج الذهاب


صورة

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

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

تعتقد: " كم يمكنك تآكل الشفرة في لغة البرمجة؟ هل من الممكن أن تكتب هذه الشفرة الرهيبة على Go حتى يصبح مؤلفها لا غنى عنه في الشركة؟ »لا تقلق. عندما كنت طالبًا ، كان لدي مشروع دعمت فيه رمز Lisp-e لشخص آخر كتبه طالب دراسات عليا. في الواقع ، تمكن من كتابة كود Fortran-e باستخدام Lisp. بدا الرمز شيئًا مثل هذا:

(defun add-mult-pi (in1 in2) (setq a in1) (setq b in2) (setq c (+ ab)) (setq d (* 3.1415 c) d ) 

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

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

التعبئة والتغليف سيئة


الحزم هي موضوع مفيد للبدء به. كيف يمكن لمؤسسة الكود أن تضعف قابلية القراءة؟

في Go ، يتم استخدام اسم الحزمة للإشارة إلى الكيان المُصدر (على سبيل المثال ، " fmt.Println` أو" http.RegisterFunc` ). نظرًا لأننا نستطيع رؤية اسم الحزمة ، فإن مبرمجي Go "good" يتأكدون من أن هذا الاسم يصف ماهية الكيانات المصدرة. لا ينبغي لنا استخدام حزم ، لأن أسماء مثل ` util.JSONMarshal` لن تعمل من أجلنا - نحن بحاجة إلى" json.Marshal ".

لا يقوم مطورو Go "good" أيضًا بإنشاء حزمة منفصلة لـ DAO أو الطراز. بالنسبة لأولئك الذين لا يعرفون هذا المصطلح ، يعد DAO " كائن الوصول إلى البيانات " - طبقة كود تتفاعل مع قاعدة البيانات الخاصة بك. اعتدت أن أعمل لدى شركة استوردت فيها 6 خدمات Java نفس مكتبة DAO للوصول إلى نفس قاعدة البيانات ، التي شاركت فيها ، لأن " ... حسنًا ، كما تعلمون ، خدمات micros هي نفسها ... ".

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

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

واجهات غير مناسبة


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

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

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

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

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

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

تمرير مؤشرات الكومة


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

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

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

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

 func main() { //  a := 1 b := a b = 2 fmt.Println(a, b) // prints 1 2 //  c := &a *c = 3 fmt.Println(a, b, *c) // prints 3 2 3 } 

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

 type Foo struct { A int B string } func getA() int { return 20 } func getB(i int) string { return fmt.Sprintf("%d",i*2) } func main() { f := Foo{} fA = getA() fB = getB(fA) //  ,    f fmt.Println(f) } 

حسنًا ، كيف نصبح "شريرًا"؟ بسيط جدا - قلب هذا النموذج.

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

 type Foo struct { A int B string } func setA(f *Foo) { fA = 20 } //   fA! func setB(f *Foo) { fB = fmt.Sprintf("%d", fA*2) } func main() { f := Foo{} setA(&f) setB(&f) // ,  setA  setB //    ? fmt.Println(f) } 

الذعر تسطيح


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

 func (dus DBUserService) Load(id int) (User, error) { rows, err := dus.DB.Query("SELECT name FROM USERS WHERE ID = ?", id) if err != nil { return User{}, err } if !rows.Next() { return User{}, fmt.Errorf("no user for id %d", id) } var name string err = rows.Scan(&name) if err != nil { return User{}, err } err = rows.Close() if err != nil { return User{}, err } return User{Id: id, Name: name}, nil } 

تستخدم العديد من اللغات ، مثل C ++ و Python و Ruby و Java ، استثناءات لمعالجة الأخطاء. إذا حدث خطأ ما ، فإن مطوري هذه اللغات يرفضون أو يرفضون استثناءً ، ويتوقعون أن يتعامل معه بعض التعليمات البرمجية. بالطبع ، يتوقع البرنامج أن يكون العميل على علم بحدوث خطأ محتمل في موقع معين بحيث يكون من الممكن طرح استثناء. لأنه ، باستثناء (مع عدم وجود التورية المقصودة) التحقق من Java ، لا يوجد شيء في طريقة التوقيع باللغات أو الوظائف للإشارة إلى احتمال حدوث استثناء. فكيف يعرف المطورين أي استثناءات تقلقهم؟ لديهم خياران:

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

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

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

 func PanicIfErr(err error) { if err != nil { panic(err) } } 

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

 func (dus DBUserService) LoadEvil(id int) User { rows, err := dus.DB.Query( "SELECT name FROM USERS WHERE ID = ?", id) PanicIfErr(err) if !rows.Next() { panic(fmt.Sprintf("no user for id %d", id)) } var name string PanicIfErr(rows.Scan(&name)) PanicIfErr(rows.Close()) return User{Id: id, Name: name} } 

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

 func PanicMiddleware(h http.Handler) http.Handler { return http.HandlerFunc( func(rw http.ResponseWriter, req *http.Request){ defer func() { if r := recover(); r != nil { fmt.Println(", - .") } }() h.ServeHTTP(rw, req) } ) } 

تحديد الآثار الجانبية


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

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

 package account type Account struct{ Id int UserId int } func init() { fmt.Println("  !") } func init() { fmt.Println("   ,     init()") } 

غالبًا ما ترتبط وظائف التهيئة بالواردات الفارغة. لدى Go طريقة خاصة للإعلان عن الواردات ، والتي تبدو مثل "import _" github.com / lib / pq`. عندما تقوم بتعيين معرف اسم فارغ لحزمة مستوردة ، فإن طريقة init تعمل فيها ، لكنها لا تعرض أيًا من معرّفات الحزمة. بالنسبة لبعض مكتبات Go - مثل برامج تشغيل قواعد البيانات أو تنسيقات الصور - يجب تحميلها عن طريق تمكين استيراد الحزمة الفارغة ، فقط للاتصال بوظيفة init حتى تتمكن الحزمة من تسجيل الكود الخاص بها.

 package main import _ "github.com/lib/pq" func main() { db, err := sql.Open( "postgres", "postgres://jon@localhost/evil?sslmode=disable") } 

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

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

 package account import ( "fmt" "github.com/evil-go/example/registry" ) type StubAccountService struct {} func (a StubAccountService) GetBalance(accountId int) int { return 1000000 } func init() { registry.Register("account", StubAccountService{}) } 

إذا كنت تريد استخدام حساب ، فضع استيرادًا فارغًا في البرنامج. ليس من الضروري أن يكون الرمز الرئيسي أو رمزًا مرتبطًا به - بل يجب أن يكون "في مكان ما". هذا هو السحر!

 package main import ( _ "github.com/evil-go/example/account" "github.com/evil-go/example/registry" ) type Balancer interface { GetBalance(int) int } func main() { a := registry.Get("account").(Balancer) money := a.GetBalance(12345) } 

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

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


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

 func main() { b, err := ioutil.ReadFile("account.json") if err != nil { fmt.Errorf("error reading config file: %v", err) os.Exit(1) } m := map[string]interface{}{} json.Unmarshal(b, &m) prefix := m["account.prefix"].(string) maker := account.NewMaker(prefix) } type Maker struct { prefix string } func (m Maker) NewAccount(name string) Account { return Account{Name: name, Id: m.prefix + "-12345"} } func NewMaker(prefix string) Maker { return Maker{prefix: prefix} } 

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

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

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

 func (m maker) NewAccount(name string) Account { return Account{Name: name, Id: m.prefix + "-12345"} } var Maker maker func init() { b, _ := ioutil.ReadFile("account.json") m := map[string]interface{}{} json.Unmarshal(b, &m) Maker.prefix = m["account.prefix"].(string) } 

الأطر الوظيفية


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

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

الشر الحالي والمستقبلي


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

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


All Articles