- مقدمة
- المتغيرات
- الدوال
- Objects و Data Structures
- Classes
- SOLID
- التجريب
- التنسيق (Concurrency)
- معالجة الاخطاء (Error Handling)
- التشكيل
- الملاحظات
- ترجمة
مبادئ هندسة البرمجيات ، من كتاب روبرت سي مارتن Clean Code ، مكيفة مع JavaScript. هذا ليس دليل أسلوب. إنه دليل للإنتاج برنامج قابل للقراءة وقابل لإعادة الاستخدام وقابل لإعادة التصنيع في جافا سكريبت.
ليس كل مبدأ هنا يجب اتباعه بدقة ، وحتى أقل من ذلك سيكون متفق عليه عالميا. هذه إرشادات وليس أكثر ، لكنها إرشادات تم تدوينها على مدى سنوات عديدة من الخبرة الجماعية من قبل مؤلفي _ clean code _.
يزيد عمر حرفة هندسة البرمجيات عن 50 عامًا ، وكذلك لازلنا نتعلم الكثير من الأشياء. "عندما تكون هندسة البرمجيات قديمة قدم الهندسة في حد ذاتها" ، ربما حينئذٍ سيكون لدينا قواعد أصعب يجب اتباعها. الآن ، دع هؤلاء المبادئ التوجيهية بمثابة محك لتقييم جودة كود JavaScript الذي تنتجه أنت وفريقك.
شيء آخر: معرفة هذه المبادئ لن تجعلك على الفور أفضل مبرمج على الإطلاق ، والعمل معها لسنوات عديدة لا يعني أنك لن ترتكب اخطاء. يبدأ كل جزء من الكود كمسودة أولى ، مثل العمل على طين رطب لن تحصل على الشكل النهائي من الوهلة الأولى فنحن دائما ما نقوم بإزالة العيوب عندما . نراجعها مع أقراننا.إذن لا تضغط على نفسك بسبب المسودات الأولى التي تحتاج التحسين. تغلب على الكود بدلا من ذلك!
سيئ:
const yyyymmdstr = moment().format("YYYY/MM/DD")
جيد:
const currentDate = moment().format("YYYY/MM/DD")
سيئ:
getUserInfo() getClientData() getCustomerRecord()
جيد:
getUser()
سنقرأ كود أكثر مما سنكتبه. من المهم أن الكود الذي نكتبه قابل للقراءة والبحث. عدم تسمية المتغيرات التي ينتهي بها الأمر إلى أن تكون ذات مغزى لفهم برنامجنا ، يجعل الامر صعبا على القراء. اجعل أسماءك قابلة للبحث. يمكن أن تساعد أدوات مثل buddy.js و ESLint في تحديد الثوابت غير المسماة.
سيئ:
// What the heck is 86400000 for? setTimeout(blastOff, 86400000)
جيد:
// Declare them as capitalized named constants. const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000 //86400000; setTimeout(blastOff, MILLISECONDS_PER_DAY)
سيئ:
const address = "One Infinite Loop, Cupertino 95014" const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/ saveCityZipCode( address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2] )
جيد:
const address = "One Infinite Loop, Cupertino 95014" const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/ const [_, city, zipCode] = address.match(cityZipCodeRegex) || [] saveCityZipCode(city, zipCode)
الواضح دائمًا أفضل من الضمني.
سيئ:
const locations = ["Austin", "New York", "San Francisco"] locations.forEach((l) => { doStuff() doSomeOtherStuff() // ... // ... // ... // Wait, what is `l` for again? dispatch(l) })
جيد:
const locations = ["Austin", "New York", "San Francisco"] locations.forEach((location) => { doStuff() doSomeOtherStuff() // ... // ... // ... dispatch(location) })
إذا أخبرك اسم class/object بشيء ما ، فلا تكرر ذلك في اسم المتغير الخاص بك.
سيئ:
const Car = { carMake: "Honda", carModel: "Accord", carColor: "Blue", } function paintCar(car, color) { car.carColor = color }
جيد:
const Car = { make: "Honda", model: "Accord", color: "Blue", } function paintCar(car, color) { car.color = color }
Default arguments are often cleaner than short circuiting. Be aware that if you use them, your function will only provide default values for undefined
arguments. Other "falsy" values such as ''
, ""
, false
, null
, 0
, and NaN
, will not be replaced by a default value.
سيئ:
function createMicrobrewery(name) { const breweryName = name || "Hipster Brew Co." // ... }
جيد:
function createMicrobrewery(name = "Hipster Brew Co.") { // ... }
من الصعب جدا الحصول على وراثة classes قابلة للقراءة ,للبناء والتعريفات الكلاسيكية لوراثة ES5 classes . إذا كنت بحاجة إلى الوراثة (وكن على دراية بأنك قد لا تحتاج إليه) ، فستفضل ES2015/ES6 classes. ومع ذلك ، حاول ان تفظل Functions الصغيرة على Classes, حتى تجد نفسك بحاجة إلى objects أكبر وأكثر تعقيدا .
سيئ:
const Animal = function (age) { if (!(this instanceof Animal)) { throw new Error("Instantiate Animal with `new`") } this.age = age } Animal.prototype.move = function move() {} const Mammal = function (age, furColor) { if (!(this instanceof Mammal)) { throw new Error("Instantiate Mammal with `new`") } Animal.call(this, age) this.furColor = furColor } Mammal.prototype = Object.create(Animal.prototype) Mammal.prototype.constructor = Mammal Mammal.prototype.liveBirth = function liveBirth() {} const Human = function (age, furColor, languageSpoken) { if (!(this instanceof Human)) { throw new Error("Instantiate Human with `new`") } Mammal.call(this, age, furColor) this.languageSpoken = languageSpoken } Human.prototype = Object.create(Mammal.prototype) Human.prototype.constructor = Human Human.prototype.speak = function speak() {}
جيد:
class Animal { constructor(age) { this.age = age } move() { /* ... */ } } class Mammal extends Animal { constructor(age, furColor) { super(age) this.furColor = furColor } liveBirth() { /* ... */ } } class Human extends Mammal { constructor(age, furColor, languageSpoken) { super(age, furColor) this.languageSpoken = languageSpoken } speak() { /* ... */ } }
هذا النمط مفيد جدا في جافا سكريبت وتراه في العديد من المكتبات مثل jQuery و Lodash. يسمح للكود الخاص بك أن يكون معبرا ، وأقل ثرثرة. لهذا السبب ، أقترح ، استخدام طريقة التسلسل وألق نظرة على مدى نظافة الكود الخاصة بك. في class functions الخاص بك ، ما عليك سوى إرجاع هذا في نهاية كل function، ويمكنك ربط class methods الإضافية بها.
سيئ:
class Car { constructor(make, model, color) { this.make = make this.model = model this.color = color } setMake(make) { this.make = make } setModel(model) { this.model = model } setColor(color) { this.color = color } save() { console.log(this.make, this.model, this.color) } } const car = new Car("Ford", "F-150", "red") car.setColor("pink") car.save()
جيد:
class Car { constructor(make, model, color) { this.make = make this.model = model this.color = color } setMake(make) { this.make = make // NOTE: Returning this for chaining return this } setModel(model) { this.model = model // NOTE: Returning this for chaining return this } setColor(color) { this.color = color // NOTE: Returning this for chaining return this } save() { console.log(this.make, this.model, this.color) // NOTE: Returning this for chaining return this } } const car = new Car("Ford", "F-150", "red").setColor("pink").save()
وكما جاء في [Design Patterns] (https://en.wikipedia.org/wiki/Design_Patterns) من قبل Gang of Four، يجب أن تفضل composition على inheritance حينما يمكنك ذلك. هناك الكثير من أسباب الوجيهية لإستخدام inheritance والكثير من الأسباب الوجيهة لإستخدام composition. النقطة الرئيسية لهذا المبدأ هي أنه إذا كان عقلك يذهب غريزيا للinheritance ، حاول التفكير فيما إذا كان composition يمكن أن يحل مشكلتك بشكل أفضل. في بعض الحالات التي يمكنها.
قد تتساءل بعد هذا، "متى يجب أن أستخدم inheritance؟" هذا يعتمد على مشكلتك التي تريد حلها ، ولكن هذه قائمة لائقة عندما يكون inheritance أكثر منطقية من composition:
-
يمثل ميراثك علاقة "هو" وليس "لديه" العلاقة (الإنسان->الحيوان مقابل المستخدم->تفاصيل المستخدم).
-
يمكنك إعادة استخدام االكود من classes الأساسية (يمكن للبشر التحرك مثل جميع الحيوانات).
-
تريد إجراء تغييرات عامة على derived classes عن طريق تغيير class الأساسية. (تغيير حرق السعرات الحرارية لجميع الحيوانات عندما تتحرك).
سيئ:
class Employee { constructor(name, email) { this.name = name this.email = email } // ... } // Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee class EmployeeTaxData extends Employee { constructor(ssn, salary) { super() this.ssn = ssn this.salary = salary } // ... }
جيد:
class EmployeeTaxData { constructor(ssn, salary) { this.ssn = ssn this.salary = salary } // ... } class Employee { constructor(name, email) { this.name = name this.email = email } setTaxData(ssn, salary) { this.taxData = new EmployeeTaxData(ssn, salary) } // ... }