└── README.md
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # الكود النظيف لجافاسكريبت
4 | 
5 | يمكنك قراءة المقالة بتنسيق أفضل على الرابط التالي:
6 | [مدونة عبدالهادي الأندلسي](https://blog.abdelhadi.org/clean-code-javascript-in-arabic/)
7 |
8 | ## جدول المحتويات
9 |
10 | 1. [المقدمة](#المقدمة)
11 | 2. [المتغيرات](#المتغيرات)
12 | 3. [الدوال](#الدوال)
13 | 4. [الكائنات وهياكل البيانات](#الكائنات-وهياكل-البيانات)
14 | 5. [الأصناف](#الأصناف)
15 | 6. [مبادئ SOLID](#مبادئ-solid)
16 | 7. [الاختبار](#الاختبار)
17 | 8. [التزامن](#التزامن)
18 | 9. [التعامل مع الأخطاء](#التعامل-مع-الأخطاء)
19 | 10. [التنسيق](#التنسيق)
20 | 11. [التعليقات](#التعليقات)
21 | 12. [الترجمة](#الترجمة)
22 |
23 | ## المقدمة
24 |
25 | 
26 |
27 | هذا دليل عن مبادئ هندسة البرمجيات، مستوحًى من كتاب روبرت سي مارتن الشهير المسمى "Clean Code" أي "الكود النظيف"، كُيّفت هذه المبادئ لتُناسِب لغة جافاسكريبت. هدفُها توفير دليل عمليّ لمبرمجي جافاسكريبت لإنتاج برمجيات [مقروءة، قابلة لإعادة الاستخدام (reusable)، قابلة لإعادة هيكلتها (refactorable)](https://github.com/ryanmcdermott/3rs-of-software-architecture).
28 |
29 | المبادئ المذكورة هنا إرشاداتٌ عامةٌ لا أكثر، وليست قوانينَ صارمةً يُشترط اتباعها بحذافيرها. هي ليست محل اتفاق بين الجميع، إلا أنها نِتاجُ خبرةٍ جماعية على مدار سنوات لمؤلفي كتاب _الكود النظيف_.
30 |
31 | هندسة البرمجيات فتِيّة نسبيًا --إذ لا يزيد عمرها عن الخمسين سنة إلا قليلا--، لو كانت معمارية البرمجيات قديمةً مثل قِدَم الهندسة المعمارية ذاتها، لكان لدينا قواعدُ أكثر صرامة يجبُ اتباعها. أمّا الآن فلا يزال أمامنا الكثير لنتعلمه، لكن تنفع هذه الإرشادات معيارًا لتقييم جودة كود جافاسكريبت الذي تكتبه أنت وفريقك.
32 |
33 | هناك أمر آخر: معرفة هذه الإرشادات لن تجعلك مبرمجًا أفضلَ فورًا، وتطبيقها لسنواتٍ لن يجنّبك الخطأ. يبدأ كل جزء من الكود بمسودة أولى، كالفخّار في أطواره الأولى، ثم نراجع العيوب مع أقراننا ونصلحها. فلا تشعر بالذنب من ما يختلج المسودات الأولى من غلطات، بل اسع جاهدا لتحسين الكود بدًلا من ذلك.
34 |
35 | ## المتغيرات
36 |
37 | ### استخدم أسماء متغيرات معبّرة سهلة النطق
38 |
39 | **خطأ:**
40 |
41 | ```javascript
42 | const yyyymmdstr = moment().format("YYYY/MM/DD");
43 | ```
44 |
45 | **صواب:**
46 |
47 | ```javascript
48 | const currentDate = moment().format("YYYY/MM/DD");
49 | ```
50 |
51 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
52 |
53 | ### استخدم نفس المفردات لنفس نوع المتغيرات
54 |
55 | **خطأ:**
56 |
57 | ```javascript
58 | getUserInfo();
59 | getClientData();
60 | getCustomerRecord();
61 | ```
62 |
63 | **صواب:**
64 |
65 | ```javascript
66 | getUser();
67 | ```
68 |
69 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
70 |
71 | ### استخدم أسماء قابلة للبحث
72 |
73 | سنقرأ الأكواد أكثر مما نكتبها. من المهم أن يكون الكود الذي نكتبه مقروءًا قابلًا للبحث. عندما _لا نلتزم_ بتسمية المتغيرات بأسماء ذات معنى لفهم برنامجنا، فسنؤذي قرّاءنا. اجعل أسماءك قابلة للبحث. ستساعدك أدوات مثل [buddy.js](https://github.com/danielstjules/buddy.js) و[ESLint](https://github.com/eslint/eslint/blob/660e0918933e6e7fede26bc675a0763a6b357c94/docs/rules/no-magic-numbers.md) على التعرف على الثوابت غير المسمّاة.
74 |
75 | **خطأ:**
76 |
77 | ```javascript
78 | // ماذا تعني هذه 86400000؟!
79 | setTimeout(blastOff, 86400000);
80 | ```
81 |
82 | **صواب:**
83 | ```javascript
84 | // عرّف القيم العددية في هيئة ثوابت بحروف إنجليزية كبيرة
85 | const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;
86 |
87 | setTimeout(blastOff, MILLISECONDS_PER_DAY);
88 | ```
89 |
90 |
91 |
92 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
93 |
94 | ### استخدم متغيرات توضيحية
95 |
96 | **خطأ:**
97 |
98 | ```javascript
99 | const address = "One Infinite Loop, Cupertino 95014";
100 | const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
101 | saveCityZipCode(
102 | address.match(cityZipCodeRegex)[1],
103 | address.match(cityZipCodeRegex)[2]
104 | );
105 | ```
106 |
107 | **صواب:**
108 |
109 | ```javascript
110 | const address = "One Infinite Loop, Cupertino 95014";
111 | const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
112 | const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];
113 | saveCityZipCode(city, zipCode);
114 | ```
115 |
116 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
117 |
118 | ### تجنّب الالتباس
119 |
120 | التصريح خير من التلميح.
121 |
122 | **خطأ:**
123 |
124 | ```javascript
125 | const locations = ["Austin", "New York", "San Francisco"];
126 | locations.forEach((l) => {
127 | doStuff();
128 | doSomeOtherStuff();
129 | // ...
130 | // ...
131 | // ...
132 | // لحظة، ماذا تعني `l` في المرّة الثانية؟
133 | dispatch(l);
134 | });
135 | ```
136 |
137 | **صواب:**
138 |
139 | ```javascript
140 | const locations = ["Austin", "New York", "San Francisco"];
141 | locations.forEach((location) => {
142 | doStuff();
143 | doSomeOtherStuff();
144 | // ...
145 | // ...
146 | // ...
147 | dispatch(location);
148 | });
149 | ```
150 |
151 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
152 |
153 | ### لا داعي لتكرار الأسماء دون حاجة
154 |
155 | إذا كان الصنف (class) أو الكائن (object) يعبّر عن معنى، لا تكرر هذا المعنى في متغيراته الداخلية.
156 |
157 | **خطأ:**
158 |
159 | ```javascript
160 | const Car = {
161 | carMake: "Honda",
162 | carModel: "Accord",
163 | carColor: "Blue",
164 | };
165 |
166 | function paintCar(car, color) {
167 | car.carColor = color;
168 | }
169 | ```
170 |
171 | **صواب:**
172 |
173 | ```javascript
174 | const Car = {
175 | make: "Honda",
176 | model: "Accord",
177 | color: "Blue",
178 | };
179 |
180 | function paintCar(car, color) {
181 | car.color = color;
182 | }
183 | ```
184 |
185 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
186 |
187 | ### استخدم معاملات (parameters) افتراضية، وتجنب تعيين القيم الافتراضية في الجمل الشرطية
188 |
189 | إسناد قيم افتراضية للمعاملات غالبًا أوضحُ من طريقة الإسناد المختصرة بالعوامل المنطقية (short-circuit evaluation).
190 | انتبه إذا استخدمت الإسناد عن طريق العوامل المنطقية، فإنّ دالّتك ستسنِد قيمًا افتراضية للوسطاء (arguments) غير المعرّفة "undefined" فقط، أما القيم الخاطئة (falsy values) فلن تُستبدل بقيم افتراضية، مثل: `''` و `""` و `false` و `null` و `0` و `NaN`.
191 |
192 | **خطأ:**
193 |
194 | ```javascript
195 | function createMicrobrewery(name) {
196 | const breweryName = name || "Hipster Brew Co."; // short-circuit evaluation
197 | // ...
198 | }
199 | ```
200 |
201 | **صواب:**
202 |
203 | ```javascript
204 | function createMicrobrewery(name = "Hipster Brew Co.") {
205 | // ...
206 | }
207 | ```
208 |
209 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
210 |
211 | ## **الدوال**
212 |
213 | ### وسطاء الدوال (Function arguments) (وسيطيَن اثنين أو أقل في الحالة المثالية)
214 |
215 | تقليل معاملات الدالة أمرٌ مهمٌ للغاية؛ إذ يُسهّل اختبار الدالة. تؤدي كثرة المعاملات ( أكثر من ثلاثة) إلى إرباك شديد، إذ عليك اختبار حالات أكثر لكل وسيط على حِدة.
216 |
217 | وسيطٌ (arguments) واحد أو اثنان أفضل، وتجنّب ثلاثة وسطاء (arguments) قدر الإمكان. أمّا أكثر من ذلك، فعليك مراجعة المعاملات وربما دمجها.
218 |
219 | عادة، إذا كان للدالة أكثر من معاملين فهي تفعل أكثر مما يجب. فإن لم يكن الأمر كذلك أحيانًا، فمن الأفضل استعمال الكائن معاملًا.
220 |
221 | جافاسكريبت تتيح إنشاء كائنات على الفور بلا أكواد إضافية للأصناف، لذا استخدم كائنًا إن احتجت إلى كثير من الوسطاء.
222 |
223 | لتوضيح الخصائص التي تتوقعها الدالة، استخدم صياغة التفكيك (destructuring) حسب مواصفات ES2015/ES6. هذا له بعض المزايا:
224 |
225 | 1. عندما يطلّع شخص ما على بُنية الدالة، يفهم فورًا الخصائص المستخدمة.
226 | 2. يمكن استخدامها لمحاكاة المعاملات المسماة (named parameters).
227 | 3. يؤدي التفكيك (destructuring) أيضًا إلى نسخ قيم متغيرات الكائن البسيطة عند تمرير الكائن إلى الدالة، مما يمنع الآثار الجانبية. ملاحظة: عند تمرير الكائن فإن متغيراته الداخلية ذات نوع الأصناف أو المصفوفات **لا تُنسخ**.
228 | 4. ستحذرك المنسّقات الآلية (Linters) من الخصائص (properties) غير المستخدمة، وهذا مستحيل دون عملية التفكيك (destructuring).
229 |
230 | **خطأ:**
231 |
232 | ```javascript
233 | function createMenu(title, body, buttonText, cancellable) {
234 | // ...
235 | }
236 |
237 | createMenu("Foo", "Bar", "Baz", true);
238 | ```
239 |
240 | **صواب:**
241 |
242 | ```javascript
243 | function createMenu({ title, body, buttonText, cancellable }) {
244 | // ...
245 | }
246 |
247 | createMenu({
248 | title: "Foo",
249 | body: "Bar",
250 | buttonText: "Baz",
251 | cancellable: true,
252 | });
253 | ```
254 |
255 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
256 |
257 | ### يجب أن تؤدي الدوال مهمة واحدة
258 |
259 | هذه أهمّ قاعدةٍ في هندسة البرمجيات. عندما تؤدي الدالة أكثر من مهمّة، يصعب إنشاؤها واختبارها وفهمها.
260 | اعزل كل إجراء في دالة واحدة فقط، فتسهل إعادة هيكلتها ويبسُط الكود أكثر. إن كان هذا فقط ما تعلمته في هذا الدليل، فستتقدم على كثير من المطورين.
261 |
262 | **خطأ:**
263 |
264 | ```javascript
265 | function emailClients(clients) {
266 | clients.forEach((client) => {
267 | const clientRecord = database.lookup(client);
268 | if (clientRecord.isActive()) {
269 | email(client);
270 | }
271 | });
272 | }
273 | ```
274 |
275 | **صواب:**
276 |
277 | ```javascript
278 | function emailActiveClients(clients) {
279 | clients.filter(isActiveClient).forEach(email);
280 | }
281 |
282 | function isActiveClient(client) {
283 | const clientRecord = database.lookup(client);
284 | return clientRecord.isActive();
285 | }
286 | ```
287 |
288 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
289 |
290 | ### يجب أن تعبّر أسماء الدوال عمّا تفعله
291 |
292 | **خطأ:**
293 |
294 | ```javascript
295 | function addToDate(date, month) {
296 | // ...
297 | }
298 |
299 | const date = new Date();
300 |
301 | /// من الصعب أن تعرف من اسم الدالة ما سيُضاف
302 | addToDate(date, 1);
303 | ```
304 |
305 | **صواب:**
306 |
307 | ```javascript
308 | function addMonthToDate(month, date) {
309 | // ...
310 | }
311 |
312 | const date = new Date();
313 | addMonthToDate(1, date);
314 | ```
315 |
316 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
317 |
318 | ### يجب أن تكون الدوال ذات مستوى تجريد واحد
319 |
320 | إن كان للدالة مستويات تجريد كثيرة، فهي تفعل أكثر مما يجب. يؤدي تقسيم الدالة إلى دوال متعددة إلى قابلية أفضل لإعادة الاستخدام وسهولة أكثر في الاختبار.
321 |
322 | **خطأ:**
323 |
324 | ```javascript
325 | function parseBetterJSAlternative(code) {
326 | const REGEXES = [
327 | // ...
328 | ];
329 |
330 | const statements = code.split(" ");
331 | const tokens = [];
332 | REGEXES.forEach((REGEX) => {
333 | statements.forEach((statement) => {
334 | // ...
335 | });
336 | });
337 |
338 | const ast = [];
339 | tokens.forEach((token) => {
340 | // lex...
341 | });
342 |
343 | ast.forEach((node) => {
344 | // parse...
345 | });
346 | }
347 | ```
348 |
349 | **صواب:**
350 |
351 | ```javascript
352 | function parseBetterJSAlternative(code) {
353 | const tokens = tokenize(code);
354 | const syntaxTree = parse(tokens);
355 | syntaxTree.forEach((node) => {
356 | // parse...
357 | });
358 | }
359 |
360 | function tokenize(code) {
361 | const REGEXES = [
362 | // ...
363 | ];
364 |
365 | const statements = code.split(" ");
366 | const tokens = [];
367 | REGEXES.forEach((REGEX) => {
368 | statements.forEach((statement) => {
369 | tokens.push(/* ... */);
370 | });
371 | });
372 |
373 | return tokens;
374 | }
375 |
376 | function parse(tokens) {
377 | const syntaxTree = [];
378 | tokens.forEach((token) => {
379 | syntaxTree.push(/* ... */);
380 | });
381 |
382 | return syntaxTree;
383 | }
384 | ```
385 |
386 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
387 |
388 | ### احذف الكود المكرّر
389 |
390 | ابذل قصارى جهدك لتجنب تكرار الكود. الكود المكرر سيّئ، إذ ستحتاج إلى التعديل في أكثر من مكان مستقبلا.
391 |
392 | تخيل أنك تدير مطعمًا وتتابع مخزونك: طماطم، وبصل، وثوم، وتوابل، وغيرها. فإن احتفظت بها في قوائم مختلفة، فعليك تحديثها جميعًا، كلما قدمت طبقا مع الطماطم. فإن كانت قائمة واحدة فقط، فستحدثها في مكان واحد فقط!
393 |
394 | أحيانًا، تكرر كودًا لأن لديك مهمتين مختلفتين اختلافا بسيطا، ولهما قواسم مشتركة كثيرة، لكن الاختلافات بينهما تفرض عليك كتابة دالتين منفصلتين أو أكثر تؤديان بمهام متشابهة. لذا فإزالة كود مكرر تعني إنشاء تجريد يمكنه التعامل مع هذه المهام المتشابهة بدالة أو وحدة أو صنف واحد فقط.
395 |
396 | يُعدّ التجريد الصحيح أمرًا بالغ الأهمية، ولهذا عليك اتباع مبادئ SOLID الموضحة في قسم [الأصناف](#الأصناف). قد تكون التجريدات السيئة أسوأ من الكود المكرر، لذا تَوَخَّ الحذر! بعد قولي هذا، إذا استطعت عمل تجريد جيد، فافعل ذلك! لا تكرّر الكود، وإلا ستضطرّ لتعديل أماكن متعددة لتحديث شيء واحد.
397 |
398 | **خطأ:**
399 |
400 | ```javascript
401 | function showDeveloperList(developers) {
402 | developers.forEach((developer) => {
403 | const expectedSalary = developer.calculateExpectedSalary();
404 | const experience = developer.getExperience();
405 | const githubLink = developer.getGithubLink();
406 | const data = {
407 | expectedSalary,
408 | experience,
409 | githubLink,
410 | };
411 |
412 | render(data);
413 | });
414 | }
415 |
416 | function showManagerList(managers) {
417 | managers.forEach((manager) => {
418 | const expectedSalary = manager.calculateExpectedSalary();
419 | const experience = manager.getExperience();
420 | const portfolio = manager.getMBAProjects();
421 | const data = {
422 | expectedSalary,
423 | experience,
424 | portfolio,
425 | };
426 |
427 | render(data);
428 | });
429 | }
430 | ```
431 |
432 | **صواب:**
433 |
434 | ```javascript
435 | function showEmployeeList(employees) {
436 | employees.forEach((employee) => {
437 | const expectedSalary = employee.calculateExpectedSalary();
438 | const experience = employee.getExperience();
439 |
440 | const data = {
441 | expectedSalary,
442 | experience,
443 | };
444 |
445 | switch (employee.type) {
446 | case "manager":
447 | data.portfolio = employee.getMBAProjects();
448 | break;
449 | case "developer":
450 | data.githubLink = employee.getGithubLink();
451 | break;
452 | }
453 |
454 | render(data);
455 | });
456 | }
457 | ```
458 |
459 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
460 |
461 | ### عيّن الكائنات الافتراضية باستخدام Object.assign
462 |
463 | **خطأ:**
464 |
465 | ```javascript
466 | const menuConfig = {
467 | title: null,
468 | body: "Bar",
469 | buttonText: null,
470 | cancellable: true,
471 | };
472 |
473 | function createMenu(config) {
474 | config.title = config.title || "Foo";
475 | config.body = config.body || "Bar";
476 | config.buttonText = config.buttonText || "Baz";
477 | config.cancellable =
478 | config.cancellable !== undefined ? config.cancellable : true;
479 | }
480 |
481 | createMenu(menuConfig);
482 | ```
483 |
484 | **صواب:**
485 |
486 | ```javascript
487 | const menuConfig = {
488 | title: "Order",
489 | // لم يضف المستخدم مفتاح `body`
490 | buttonText: "Send",
491 | cancellable: true,
492 | };
493 |
494 | function createMenu(config) {
495 | let finalConfig = Object.assign(
496 | {
497 | title: "Foo",
498 | body: "Bar",
499 | buttonText: "Baz",
500 | cancellable: true,
501 | },
502 | config
503 | );
504 | return finalConfig;
505 | // الآن config تساوي: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
506 | // ...
507 | }
508 |
509 | createMenu(menuConfig);
510 | ```
511 |
512 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
513 |
514 | ### لا تستخدم [متغيرات الراية (flags)](https://stackoverflow.com/questions/17402125/what-is-a-flag-variable) معاملات للدوال
515 |
516 | تخبر متغيرات الراية (flags) المستخدم أن هذه الدالة تؤدي مهام كثيرة، يجب أن تؤدي الدوال عملا واحدًا. قسّم دالّتك إذا كانت تتبع مسارات كود مختلفة حسب شروط منطقية.
517 |
518 | **خطأ:**
519 |
520 | ```javascript
521 | function createFile(name, temp) {
522 | if (temp) {
523 | fs.create(`./temp/${name}`);
524 | } else {
525 | fs.create(name);
526 | }
527 | }
528 | ```
529 |
530 | **صواب:**
531 |
532 | ```javascript
533 | function createFile(name) {
534 | fs.create(name);
535 | }
536 |
537 | function createTempFile(name) {
538 | createFile(`./temp/${name}`);
539 | }
540 | ```
541 |
542 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
543 |
544 | ### تجنب التأثيرات الجانبية (side effects) (الجزء الأول)
545 |
546 | تؤثر الدالة تأثيرًا جانبيًا إن عملت عملا غير أخذ قيم وإرجاع قيم. قد يكون التأثير الجانبي كتابة في ملف، أو تعديل متغيرات عامة، أو تحويل أموالك خطأً إلى شخص غريب.
547 |
548 | الآن، أنت بالفعل قد تحتاج إلى تأثيرات جانبية في برنامجك أحيانًا. كما في المثال السابق، قد تحتاج إلى الكتابة على ملف. لذا عليك التركيز على موضع الآثر الجانبي. لا تستخدم العديد من الدوال والأصناف للكتابة على ملف، بل أنشئ خدمة (service) واحدة تفعل ذلك. خدمة واحدة فقط لا غير.
549 |
550 | المهم هنا أن تتجنب العثرات الشائعة مثل مشاركة الحالة بين الكائنات دون هيكلية واضحة، باستخدام أنواع بيانات قابلة للتغيير (mutable) يمكن أن يعدلها أي جزء من الكود. والصوابُ جعلُ الحالة متمركزة حيث تحدث التأثيرات الجانبية. إذا استوعبت ذلك، فستكون أسعد من أغلب المبرمجين.
551 |
552 | **خطأ:**
553 |
554 | ```javascript
555 | // متغير عام مشار إليه من قبل الدالة التالية.
556 | // إذا كانت لدينا دالة أخرى تستخدم هذا الاسم، فستكون الآن مصفوفة ويمكن أن تعطلها.
557 | let name = "Ryan McDermott";
558 |
559 | function splitIntoFirstAndLastName() {
560 | name = name.split(" ");
561 | }
562 |
563 | splitIntoFirstAndLastName();
564 |
565 | console.log(name); // ['Ryan', 'McDermott'];
566 | ```
567 |
568 | **صواب:**
569 |
570 | ```javascript
571 | function splitIntoFirstAndLastName(name) {
572 | return name.split(" ");
573 | }
574 |
575 | const name = "Ryan McDermott";
576 | const newName = splitIntoFirstAndLastName(name);
577 |
578 | console.log(name); // 'Ryan McDermott';
579 | console.log(newName); // ['Ryan', 'McDermott'];
580 | ```
581 |
582 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
583 |
584 | ### تجنب الآثار الجانبية (side effects) (الجزء الثاني)
585 |
586 | في جافاسكريبت، بعض القيم غير قابلة للتغيير (immutable) وبعضها قابلة للتغيير (mutable). الكائنات والمصفوفات هما نوعان قابلان للتغيير، لذلك من المهم التعامل معها بحذر عندما تُمرران معاملات للدالة. دالة جافاسكريبت يمكنها تغيير خاصيات الكائن أو تعديل محتوى المصفوفة مما قد يسبب أخطاء في أماكن أخرى.
587 |
588 | لنفترض أن هناك دالة تقبل معاملًا من نوع مصفوفة يعبر عن سلّة شراء. إذا كانت الدالة تغير تلك المصفوفة بإضافة عنصر للشراء مثلا، فإن أي دالة أخرى تستخدم نفس مصفوفة السلة `cart` ستتأثر بهذه الإضافة. هذا قد يكون جيّدا أحيانًا وسيئا أحيانًا أخرى، فلنتخيّل مثالا سيئا.
589 |
590 | ينقر المستخدم على زر "شراء" فيستدعي دالة `purchase` فتطلب عبر الشبكة وترسل مصفوفة `cart` إلى الخادوم. ستعيد دالة `purchase` الطلب لضعف اتصال الشبكة. الآن، ماذا لو نقر المستخدم --في نفس الوقت-- على زر "إضافة إلى السلة" بالخطأ على عنصر لا يريده قبل بدء طلب الشبكة؟ لو حدث ذلك وبدأ طلب الشبكة فإن دالة الشراء سترسل العنصر الذي أضيف بالخطأ لأن مصفوفة `cart` عُدّلت.
591 | A great solution would be for the `addItemToCart` function to always clone the `cart`, edit it, and return the clone. This would ensure that functions that are still using the old shopping cart wouldn't be affected by the changes.
592 |
593 | من الحلول المثلى أن تنسخ دالةُ `addItemToCart` محتوى مصفوفة `cart` ، وتعدلها ثم ترجع النسخة. هذا الأمر سيضمن أن الدوال التي ما زالت تستخدم سلة الشراء القديمة لن تتأثر بالتغييرات.
594 |
595 | انتبه لأمرين عند استخدام هذه الطريقة:
596 |
597 | 1. أحيانا، قد تريد فعلا تعديل كائن الإدخال، ولكن إن اتبعت هذه الممارسة البرمجية ستجد أنها حالات نادرة. معظم الأشياء قابلة لإعادة الهيكلة لكي تتجنب آثار جانبية!
598 |
599 | 2. قد يؤثر نسخ كائنات ضخمة على الأداء. لكنها ليست مشكلة كبيرة لحسن الحظ، فهذه [مكتبة رائعة](https://facebook.github.io/immutable-js/) تجعل هذا الأسلوب البرمجي سريعًا ولا يؤثر على الذاكرة على عكس أن تنسخ الكائنات والمصفوفات يدويًا.
600 |
601 | **خطأ:**
602 |
603 | ```javascript
604 | const addItemToCart = (cart, item) => {
605 | cart.push({ item, date: Date.now() });
606 | };
607 | ```
608 |
609 | **صواب:**
610 |
611 | ```javascript
612 | const addItemToCart = (cart, item) => {
613 | return [...cart, { item, date: Date.now() }];
614 | };
615 | ```
616 |
617 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
618 |
619 | ### لا تعدّل دوال المجال العام (global functions)
620 |
621 | يعدّ تعديل دوال المجال العام (global functions) ممارسة خاطئة في جافاسكريبت؛ لأنها قد تتعارض مع مكتبة أخرى، وسيختلط على من يستخدم واجهتك البرمجية (API) فستفاجأ حين يلقى الاستثناء أو الخطأ في التشغيل (exception in production).
622 |
623 | لنفكر بمثال: ماذا لو أردت تحسين دالة المصفوفات الأصلية في جافاسكريبت `Array` وأضفت دالة `diff` لتعطي الفرق بين مصفوفتين؟
624 | يمكنك إنشاء دالتك الجديدة عن طريق `Array.prototype` ولكن هذا قد يتعارض مع مكتبة أخرى أرادت عمل الشيء نفسه. ماذا لو استخدمَتْ المكتبة الثانية `diff` لمعرفة الفرق بين العنصرين الأول والأخير في مصفوفة؟
625 | لذلك من الأفضل استخدام أصناف ES2015/ES6 وببساطة تمديد كائن `Array` العامة.
626 |
627 | **خطأ:**
628 |
629 | ```javascript
630 | Array.prototype.diff = function diff(comparisonArray) {
631 | const hash = new Set(comparisonArray);
632 | return this.filter((elem) => !hash.has(elem));
633 | };
634 | ```
635 |
636 | **صواب:**
637 |
638 | ```javascript
639 | class SuperArray extends Array {
640 | diff(comparisonArray) {
641 | const hash = new Set(comparisonArray);
642 | return this.filter((elem) => !hash.has(elem));
643 | }
644 | }
645 | ```
646 |
647 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
648 |
649 | ### فضّل البرمجة الدالّية (functional) على البرمجة الأمرية (imperative)
650 |
651 | ليست جافاسكريبت لغة دالية كما هو الحال في لغة Haskell. ولكن لها بعض جوانب اللغات الدالّية. تكون اللغات الدالّية أوضح وأسهل في الاختبار. استخدم هذا الأسلوب البرمجي قدر ما تستطيع.
652 |
653 | **خطأ:**
654 |
655 | ```javascript
656 | const programmerOutput = [
657 | {
658 | name: "Uncle Bobby",
659 | linesOfCode: 500,
660 | },
661 | {
662 | name: "Suzie Q",
663 | linesOfCode: 1500,
664 | },
665 | {
666 | name: "Jimmy Gosling",
667 | linesOfCode: 150,
668 | },
669 | {
670 | name: "Gracie Hopper",
671 | linesOfCode: 1000,
672 | },
673 | ];
674 |
675 | let totalOutput = 0;
676 |
677 | for (let i = 0; i < programmerOutput.length; i++) {
678 | totalOutput += programmerOutput[i].linesOfCode;
679 | }
680 | ```
681 |
682 | **صواب:**
683 |
684 | ```javascript
685 | const programmerOutput = [
686 | {
687 | name: "Uncle Bobby",
688 | linesOfCode: 500,
689 | },
690 | {
691 | name: "Suzie Q",
692 | linesOfCode: 1500,
693 | },
694 | {
695 | name: "Jimmy Gosling",
696 | linesOfCode: 150,
697 | },
698 | {
699 | name: "Gracie Hopper",
700 | linesOfCode: 1000,
701 | },
702 | ];
703 |
704 | const totalOutput = programmerOutput.reduce(
705 | (totalLines, output) => totalLines + output.linesOfCode,
706 | 0
707 | );
708 | ```
709 |
710 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
711 |
712 | ### غلّف الجمل الشرطية (Encapsulate conditionals)
713 |
714 | **خطأ:**
715 |
716 | ```javascript
717 | if (fsm.state === "fetching" && isEmpty(listNode)) {
718 | // ...
719 | }
720 | ```
721 |
722 | **صواب:**
723 |
724 | ```javascript
725 | function shouldShowSpinner(fsm, listNode) {
726 | return fsm.state === "fetching" && isEmpty(listNode);
727 | }
728 |
729 | if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
730 | // ...
731 | }
732 | ```
733 |
734 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
735 |
736 | ### تجنّب الجمل الشرطية المنفية
737 |
738 | **خطأ:**
739 |
740 | ```javascript
741 | function isDOMNodeNotPresent(node) {
742 | // ...
743 | }
744 |
745 | if (!isDOMNodeNotPresent(node)) {
746 | // ...
747 | }
748 | ```
749 |
750 | **صواب:**
751 |
752 | ```javascript
753 | function isDOMNodePresent(node) {
754 | // ...
755 | }
756 |
757 | if (isDOMNodePresent(node)) {
758 | // ...
759 | }
760 | ```
761 |
762 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
763 |
764 | ### تجنّب الجمل الشرطية
765 | تبدو هذه مهمة مستحيلة. عند سماع ذلك لأول مرة ، يقول أغلبهم: "كيف يجدر بي أن أُبرمج بلا تعليمة الشرط"if"؟"
766 |
767 | الجواب: استخدم تعدد الأشكال (polymorphism) لتحقيق نفس المهمة في كثير من الحالات.
768 |
769 | السؤال الثاني هو عادة، "حسنًا هذا رائع، ولكن لماذا قد أرغب في فعل بذلك؟"
770 |
771 | الجواب أنّ مفهوم الكود النظيف الذي تعلمناه: يجب أن تؤدي الدالة عملا واحدا فقط. فإن كانت الأصناف والدوال فيها تعليمات "if" الشرطية، فأنت تخبر المستخدم أن دالتك تؤدي أعمالا كثيرة. تذكر ، فقط افعل شيئًا واحدًا.
772 |
773 | **خطأ:**
774 |
775 | ```javascript
776 | class Airplane {
777 | // ...
778 | getCruisingAltitude() {
779 | switch (this.type) {
780 | case "777":
781 | return this.getMaxAltitude() - this.getPassengerCount();
782 | case "Air Force One":
783 | return this.getMaxAltitude();
784 | case "Cessna":
785 | return this.getMaxAltitude() - this.getFuelExpenditure();
786 | }
787 | }
788 | }
789 | ```
790 |
791 | **صواب:**
792 |
793 | ```javascript
794 | class Airplane {
795 | // ...
796 | }
797 |
798 | class Boeing777 extends Airplane {
799 | // ...
800 | getCruisingAltitude() {
801 | return this.getMaxAltitude() - this.getPassengerCount();
802 | }
803 | }
804 |
805 | class AirForceOne extends Airplane {
806 | // ...
807 | getCruisingAltitude() {
808 | return this.getMaxAltitude();
809 | }
810 | }
811 |
812 | class Cessna extends Airplane {
813 | // ...
814 | getCruisingAltitude() {
815 | return this.getMaxAltitude() - this.getFuelExpenditure();
816 | }
817 | }
818 | ```
819 |
820 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
821 |
822 | ### تجنّب التحقق من أنواع البيانات (الجزء الأول)
823 |
824 | لغة جافاسكريبت لغة لا تلتزم بنوع البيانات (untyped)، مما يعني أن الدوال يقبل أي نوع من الوسطاء. أحيانًا يؤذيك هذا التسامح ويُغريك التحقق من الأنواع في الدوال. طرق عديدة تُجنبك الاضطرار إلى ذلك، أول ما يجب مراعاته هو واجهات برمجية متسقة.
825 |
826 | **خطأ:**
827 |
828 | ```javascript
829 | function travelToTexas(vehicle) {
830 | if (vehicle instanceof Bicycle) {
831 | vehicle.pedal(this.currentLocation, new Location("texas"));
832 | } else if (vehicle instanceof Car) {
833 | vehicle.drive(this.currentLocation, new Location("texas"));
834 | }
835 | }
836 | ```
837 |
838 | **صواب:**
839 |
840 | ```javascript
841 | function travelToTexas(vehicle) {
842 | vehicle.move(this.currentLocation, new Location("texas"));
843 | }
844 | ```
845 |
846 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
847 |
848 | ### تجنّب التحقق من أنواع البيانات (الجزء الثاني)
849 |
850 | إذا عملت بأنواع البيانات الأساسية البسيطة (primitive values) مثل سلاسل النصوص (string) والأعداد الصحيحة (integers)، ولا تستطيع استخدام تعدد الأشكال (polymorphism) ولكنك تحتاج إلى فعلا إلى التحقق من الأنواع (type-check)، ففكّر في استخدام TypeScript.فهي بديل ممتاز لجافاسكريبت العادية إذ تمكّنك من تعريف أنواع بيانات ساكنة للمتغيّرات (static typing) عدا عن أنها مبنية على صيغة جافاسكريبت القياسية.
851 |
852 | في جافاسكريبت، مشكلة التحقق اليدوي من الأنواع تكمن في أنه يتطلب إسهابًا لا يؤدي إلا إلى أمان زائف، ومع ذلك يُفقد مقروئية الكود (code readability).
853 |
854 | حافظ على نظافة كود جافاسكريبت، واكتب اختبارات جيدة، واحصل على مراجعات جيدة للكود (code review). بخلاف ذلك، افعل كل ذلك باستخدام TypeScript، فهي بديل رائع كما قلت.
855 |
856 | **خطأ:**
857 |
858 | ```javascript
859 | function combine(val1, val2) {
860 | if (
861 | (typeof val1 === "number" && typeof val2 === "number") ||
862 | (typeof val1 === "string" && typeof val2 === "string")
863 | ) {
864 | return val1 + val2;
865 | }
866 |
867 | throw new Error("Must be of type String or Number");
868 | }
869 | ```
870 |
871 | **صواب:**
872 |
873 | ```javascript
874 | function combine(val1, val2) {
875 | return val1 + val2;
876 | }
877 | ```
878 |
879 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
880 |
881 | ### لا تفرط في التحسين
882 |
883 | تُحَسِّن المتصفحات الحديثة الكثير خلف الكواليس أثناء التشغيل. في كثير من الأحيان، إذا كنت تنشغل بالتحسين، فأنت تضيع وقتك فقط. [هناك مصادر جيدة](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) لمعرفة الأماكن المحتاجة إلى التحسين. استهدفها في هذه الأثناء، حتى تُصلح إذا أمكن ذلك.
884 |
885 | **خطأ:**
886 |
887 | ```javascript
888 | // في المتصفحات القديمة، سيكون كل تكرار مع "list.length" غير المخزنة مؤقتًا مؤثرًا على الأداء
889 | // بسبب إعادة حساب "list.length". في المتصفحات الحديثة، هنا الأمر حُسّن أداؤه.
890 | for (let i = 0, len = list.length; i < len; i++) {
891 | // ...
892 | }
893 | ```
894 |
895 | **صواب:**
896 |
897 | ```javascript
898 | for (let i = 0; i < list.length; i++) {
899 | // ...
900 | }
901 | ```
902 |
903 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
904 |
905 | ### احذف الكود الميت
906 |
907 | الكود الميت سيّئ بقدر الكود المكرر. لا سبب لإبقائه في مجموع الكود. إن لم تعد تستدعيه فتخلص منه! سيظل محفوظًا آمنًا في نظام التحكم في الإصدارات مثل git إذا احتجت إليه لاحقا.
908 |
909 | **خطأ:**
910 |
911 | ```javascript
912 | function oldRequestModule(url) {
913 | // ...
914 | }
915 |
916 | function newRequestModule(url) {
917 | // ...
918 | }
919 |
920 | const req = newRequestModule;
921 | inventoryTracker("apples", req, "www.inventory-awesome.io");
922 | ```
923 |
924 | **صواب:**
925 |
926 | ```javascript
927 | function newRequestModule(url) {
928 | // ...
929 | }
930 |
931 | const req = newRequestModule;
932 | inventoryTracker("apples", req, "www.inventory-awesome.io");
933 | ```
934 |
935 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
936 |
937 | ## **الكائنات وهياكل البيانات**
938 |
939 | ### استخدم الجالبات (getters) والضوابط (setters)
940 |
941 | استخدام الجالبات (getters) والضوابط (setters) للوصول إلى بيانات الكائن قد يكون أفضل من مجرد البحث عن خاصية (property) في الكائن. قد تسأل "لماذا؟" حسنًا، إليك بعض الأسباب:
942 |
943 | - إن أردت أن تعمل ما يفوق مجرد الحصول على خاصية كائن ما، ليس عليك البحث عن كل استدعاء لها في أكوادك البرمجية وتغييرها.
944 | - يسهل إضافة التحقق (validation) عند استخدام الضابط (set).
945 | - يغلّف تفاصيل التمثيل الداخلي.
946 | - أسهل لإضافة سجل تتبع، والتعامل مع الأخطاء عند استخدام الجالبات (getters) والضوابط (setters).
947 | - يمكنك التحميل البطيء (lazy loading) لخصائص الكائن، مثلًا، عند جلبها من الخادوم.
948 |
949 | **خطأ:**
950 |
951 | ```javascript
952 | function makeBankAccount() {
953 | // ...
954 |
955 | return {
956 | balance: 0,
957 | // ...
958 | };
959 | }
960 |
961 | const account = makeBankAccount();
962 | account.balance = 100;
963 | ```
964 |
965 | **صواب:**
966 |
967 | ```javascript
968 | function makeBankAccount() {
969 | // هذه الخاصية خاصة
970 | let balance = 0;
971 |
972 | // الجالب جعلها عامة عن طريق الكائن الراجع في الأسفل
973 | function getBalance() {
974 | return balance;
975 | }
976 |
977 | // أما الضابط، جعلها عامة عن طريق الكائن الراجع في الأسفل
978 | function setBalance(amount) {
979 | //… التحقق قبل تحديث خاصة رصيد الحساب "balance"
980 | balance = amount;
981 | }
982 |
983 | return {
984 | // ...
985 | getBalance,
986 | setBalance,
987 | };
988 | }
989 |
990 | const account = makeBankAccount();
991 | account.setBalance(100);
992 | ```
993 |
994 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
995 |
996 | ### دع الكائنات تمتلك عناصر خاصة
997 |
998 | يمكن إنجاز هذا عن طريق التعابير المغلقة (closures) في إصدار ES5 وما دونه.
999 |
1000 | **خطأ:**
1001 |
1002 | ```javascript
1003 | const Employee = function (name) {
1004 | this.name = name;
1005 | };
1006 |
1007 | Employee.prototype.getName = function getName() {
1008 | return this.name;
1009 | };
1010 |
1011 | const employee = new Employee("John Doe");
1012 | console.log(`Employee name: ${employee.getName()}`); // اسم الموظف: John Doe
1013 |
1014 | delete employee.name;
1015 | console.log(`Employee name: ${employee.getName()}`); // اسم الموظف: غير معرّف "undefined"
1016 | ```
1017 |
1018 | **صواب:**
1019 |
1020 | ```javascript
1021 | function makeEmployee(name) {
1022 | return {
1023 | getName() {
1024 | return name;
1025 | },
1026 | };
1027 | }
1028 |
1029 | const employee = makeEmployee("John Doe");
1030 | console.log(`Employee name: ${employee.getName()}`); // اسم الموظف: John Doe
1031 |
1032 | delete employee.name;
1033 | console.log(`Employee name: ${employee.getName()}`); // اسم الموظف: John Doe
1034 | ```
1035 |
1036 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1037 |
1038 | ## الأصناف
1039 |
1040 | ### فضّل أصناف ES2015/ES6 على دوال ES5 العادية
1041 |
1042 | من الصعب جدًا الحصول على توريث صنف (class inheritance) ذي مقروئية، والحصول على دالة البناء (construction)، وتعريف الدوال في أصناف ES5 التقليدية. لو كنت تريد التوريث (وكن متيقظًا أنك قد لا تحتاج إليه بالأساس)، فاختر أصناف ES2015/ES6. ومع ذلك، فالأفضل لك الدوال الصغيرة بدلا من الأصناف إلا أن تحتاج إلى كائنات أكبر وأعقد.
1043 |
1044 | **خطأ:**
1045 |
1046 | ```javascript
1047 | const Animal = function (age) {
1048 | if (!(this instanceof Animal)) {
1049 | throw new Error("Instantiate Animal with `new`");
1050 | }
1051 |
1052 | this.age = age;
1053 | };
1054 |
1055 | Animal.prototype.move = function move() {};
1056 |
1057 | const Mammal = function (age, furColor) {
1058 | if (!(this instanceof Mammal)) {
1059 | throw new Error("Instantiate Mammal with `new`");
1060 | }
1061 |
1062 | Animal.call(this, age);
1063 | this.furColor = furColor;
1064 | };
1065 |
1066 | Mammal.prototype = Object.create(Animal.prototype);
1067 | Mammal.prototype.constructor = Mammal;
1068 | Mammal.prototype.liveBirth = function liveBirth() {};
1069 |
1070 | const Human = function (age, furColor, languageSpoken) {
1071 | if (!(this instanceof Human)) {
1072 | throw new Error("Instantiate Human with `new`");
1073 | }
1074 |
1075 | Mammal.call(this, age, furColor);
1076 | this.languageSpoken = languageSpoken;
1077 | };
1078 |
1079 | Human.prototype = Object.create(Mammal.prototype);
1080 | Human.prototype.constructor = Human;
1081 | Human.prototype.speak = function speak() {};
1082 | ```
1083 |
1084 | **صواب:**
1085 |
1086 | ```javascript
1087 | class Animal {
1088 | constructor(age) {
1089 | this.age = age;
1090 | }
1091 |
1092 | move() {
1093 | /* ... */
1094 | }
1095 | }
1096 |
1097 | class Mammal extends Animal {
1098 | constructor(age, furColor) {
1099 | super(age);
1100 | this.furColor = furColor;
1101 | }
1102 |
1103 | liveBirth() {
1104 | /* ... */
1105 | }
1106 | }
1107 |
1108 | class Human extends Mammal {
1109 | constructor(age, furColor, languageSpoken) {
1110 | super(age, furColor);
1111 | this.languageSpoken = languageSpoken;
1112 | }
1113 |
1114 | speak() {
1115 | /* ... */
1116 | }
1117 | }
1118 | ```
1119 |
1120 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1121 |
1122 | ### استخدام سَلسَلة الدوال
1123 |
1124 | هذا النمط مفيد جدًا في جافاسكريبت، ويمكنك رؤيته في العديد من المكتبات مثل jQuery و Lodash. يسمح لكودك أن يكون معبرًا، وأقل فوضى. لهذا، استخدم سَلسَلة الدوال وألق نظرة على مدى نظافة الكود. في دوال الصنف، ما عليك سوى إرجاع `this` في نهاية كل دالة، ويمكنك ربط المزيد من دوال الصنف بها.
1125 |
1126 | **خطأ:**
1127 |
1128 | ```javascript
1129 | class Car {
1130 | constructor(make, model, color) {
1131 | this.make = make;
1132 | this.model = model;
1133 | this.color = color;
1134 | }
1135 |
1136 | setMake(make) {
1137 | this.make = make;
1138 | }
1139 |
1140 | setModel(model) {
1141 | this.model = model;
1142 | }
1143 |
1144 | setColor(color) {
1145 | this.color = color;
1146 | }
1147 |
1148 | save() {
1149 | console.log(this.make, this.model, this.color);
1150 | }
1151 | }
1152 |
1153 | const car = new Car("Ford", "F-150", "red");
1154 | car.setColor("pink");
1155 | car.save();
1156 | ```
1157 |
1158 | **صواب:**
1159 |
1160 | ```javascript
1161 | class Car {
1162 | constructor(make, model, color) {
1163 | this.make = make;
1164 | this.model = model;
1165 | this.color = color;
1166 | }
1167 |
1168 | setMake(make) {
1169 | this.make = make;
1170 | // ملاحظة: إرجاع `this` للسلسلة
1171 | return this;
1172 | }
1173 |
1174 | setModel(model) {
1175 | this.model = model;
1176 | // ملاحظة: إرجاع `this` للسلسلة
1177 | return this;
1178 | }
1179 |
1180 | setColor(color) {
1181 | this.color = color;
1182 | // ملاحظة: إرجاع `this` للسلسلة
1183 | return this;
1184 | }
1185 |
1186 | save() {
1187 | console.log(this.make, this.model, this.color);
1188 | // ملاحظة: إرجاع `this` للسلسلة
1189 | return this;
1190 | }
1191 | }
1192 |
1193 | const car = new Car("Ford", "F-150", "red").setColor("pink").save();
1194 | ```
1195 |
1196 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1197 |
1198 | ### فضّل أسلوب التركيب (composition) على التوريث (inheritance)
1199 |
1200 | كما ذُكر في كتاب [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) الذي ألّفه أربعة من علماء الحاسوب --عُرفوا باسم "عصابة الأربعة"--، عليك أن تفضّل التركيب (composition) على التوريث (inheritance) ما أمكنك ذلك. لاستخدام التوريث أسباب وجيهة كثيرة، وكذلك لاستخدام التركيب أسباب وجيهة كثيرة.
1201 |
1202 | أهمّ ما في هذا المبدأ أنه إذا تبادر إلى ذهنك التوريث بديهيا، ففكّر إن كان التركيب قد يصوغ مشكلتك صياغة أفضل. أحيانا يمكن ذلك.
1203 |
1204 | قد تتساءل: "متى أستخدم التوريث؟" هذا يعتمد على مشكلتك، ولكن إليك حالات التوريث فيها منطقي أكثر من التركيب:
1205 |
1206 | 1. يمثل التوريث علاقة "is-a" (التعريف) وليس علاقة "has-a" (الامتلاك) (الإنسان-> الحيوان (الإنسان -هو- حيوان، أي ينتمي للمملكة الحيوانية) مقابل المستخدم-> تفاصيل المستخدم (حساب المستخدم يمتلك تفاصيل)).
1207 | 2. يمكنك إعادة استخدام الأكواد من الأصناف الأساسية (يتحرك البشر مثل سائر الحيوانات).
1208 | 3. تريد إجراء تغييرات عامة على الأصناف المشتقة عن طريق تغيير الصنف الأساسي. (تغيير إنفاق السعرات الحرارية لجميع الحيوانات حين تتحرك).
1209 |
1210 | **خطأ:**
1211 |
1212 | ```javascript
1213 | class Employee {
1214 | constructor(name, email) {
1215 | this.name = name;
1216 | this.email = email;
1217 | }
1218 |
1219 | // ...
1220 | }
1221 |
1222 | // سيئ لأن الموظفين "Employees" "لديهم" بيانات ضريبية. أما بيانات الضريبة للموظفين EmployeeTaxData ليس نوعًا من أنواع الموظفين
1223 | class EmployeeTaxData extends Employee {
1224 | constructor(ssn, salary) {
1225 | super();
1226 | this.ssn = ssn;
1227 | this.salary = salary;
1228 | }
1229 |
1230 | // ...
1231 | }
1232 | ```
1233 |
1234 | **صواب:**
1235 |
1236 | ```javascript
1237 | class EmployeeTaxData {
1238 | constructor(ssn, salary) {
1239 | this.ssn = ssn;
1240 | this.salary = salary;
1241 | }
1242 |
1243 | // ...
1244 | }
1245 |
1246 | class Employee {
1247 | constructor(name, email) {
1248 | this.name = name;
1249 | this.email = email;
1250 | }
1251 |
1252 | setTaxData(ssn, salary) {
1253 | this.taxData = new EmployeeTaxData(ssn, salary);
1254 | }
1255 | // ...
1256 | }
1257 | ```
1258 |
1259 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1260 |
1261 | ## مبادئ **SOLID**
1262 |
1263 | ### مبدأ المسؤولية الواحدة أو الفردية Single Responsibility Principle (SRP)
1264 |
1265 | كما ذُكر في كتاب الكود النظيف، "يجب ألا تَكثُر أسباب تغيير الصنف". من المغري أن تملأ صنفًا ما بكثير من الدوال، كما تحزم حقيبة سفر واحدة فقط على متن رحلتك. المشكلةُ أن الصنف لن يكون متماسكًا مفاهيميًا، وسيتعرّض للتغيير لأسباب عدّة.
1266 | من المهم تقليل مرّات الحاجة إلى تغيير الصنف. إنه أمر مهم، فإن كثرت الدوال في صنف واحد وعدّلت جزءًا منها، فسيصعب فهم تأثير ذلك على الوحدات التابعة الأخرى في أنحاء الكود.
1267 |
1268 | **خطأ:**
1269 |
1270 | ```javascript
1271 | class UserSettings {
1272 | constructor(user) {
1273 | this.user = user;
1274 | }
1275 |
1276 | changeSettings(settings) {
1277 | if (this.verifyCredentials()) {
1278 | // ...
1279 | }
1280 | }
1281 |
1282 | verifyCredentials() {
1283 | // ...
1284 | }
1285 | }
1286 | ```
1287 |
1288 | **صواب:**
1289 |
1290 | ```javascript
1291 | class UserAuth {
1292 | constructor(user) {
1293 | this.user = user;
1294 | }
1295 |
1296 | verifyCredentials() {
1297 | // ...
1298 | }
1299 | }
1300 |
1301 | class UserSettings {
1302 | constructor(user) {
1303 | this.user = user;
1304 | this.auth = new UserAuth(user);
1305 | }
1306 |
1307 | changeSettings(settings) {
1308 | if (this.auth.verifyCredentials()) {
1309 | // ...
1310 | }
1311 | }
1312 | }
1313 | ```
1314 |
1315 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1316 |
1317 | ### مبدأ الفتح والإغلاق Open/Closed Principle (OCP)
1318 |
1319 | كما ذكر برتراند ماير، "يجب أن تكون الكيانات البرمجية (مثل الأصناف، والوحدات، والدوال، إلخ) مفتوحة للتوسع والتمديد، ولكن مغلقة على التعديل."
1320 | ماذا يعني ذلك؟ يعني ببساطة أنه عليك السماح للمستخدمين بإضافة وظائف جديدة دون تغيير الكود الموجود.
1321 |
1322 | **خطأ:**
1323 |
1324 | ```javascript
1325 | class AjaxAdapter extends Adapter {
1326 | constructor() {
1327 | super();
1328 | this.name = "ajaxAdapter";
1329 | }
1330 | }
1331 |
1332 | class NodeAdapter extends Adapter {
1333 | constructor() {
1334 | super();
1335 | this.name = "nodeAdapter";
1336 | }
1337 | }
1338 |
1339 | class HttpRequester {
1340 | constructor(adapter) {
1341 | this.adapter = adapter;
1342 | }
1343 |
1344 | fetch(url) {
1345 | if (this.adapter.name === "ajaxAdapter") {
1346 | return makeAjaxCall(url).then((response) => {
1347 | // تحويل الاستجابة والإرجاع
1348 | });
1349 | } else if (this.adapter.name === "nodeAdapter") {
1350 | return makeHttpCall(url).then((response) => {
1351 | // تحويل الاستجابة والإرجاع
1352 | });
1353 | }
1354 | }
1355 | }
1356 |
1357 | function makeAjaxCall(url) {
1358 | // الطلب (request) وإرجاع الوعد (return promise)
1359 | }
1360 |
1361 | function makeHttpCall(url) {
1362 | // الطلب (request) وإرجاع الوعد (return promise)
1363 | }
1364 | ```
1365 |
1366 | **صواب:**
1367 |
1368 | ```javascript
1369 | class AjaxAdapter extends Adapter {
1370 | constructor() {
1371 | super();
1372 | this.name = "ajaxAdapter";
1373 | }
1374 |
1375 | request(url) {
1376 | // الطلب (request) وإرجاع الوعد (return promise)
1377 | }
1378 | }
1379 |
1380 | class NodeAdapter extends Adapter {
1381 | constructor() {
1382 | super();
1383 | this.name = "nodeAdapter";
1384 | }
1385 |
1386 | request(url) {
1387 | // الطلب (request) وإرجاع الوعد (return promise)
1388 | }
1389 | }
1390 |
1391 | class HttpRequester {
1392 | constructor(adapter) {
1393 | this.adapter = adapter;
1394 | }
1395 |
1396 | fetch(url) {
1397 | return this.adapter.request(url).then((response) => {
1398 | // الطلب (request) وإرجاع الوعد (return promise)
1399 | });
1400 | }
1401 | }
1402 | ```
1403 |
1404 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1405 |
1406 | ### مبدأ ليسكوف للاستبدال Liskov Substitution Principle (LSP)
1407 |
1408 | هذا مصطلح مخيفٌ لمفهومٍ سهلٍ للغاية. يُعّرف "رسميًا" على أنه "إذا كان S نوعًا فرعيًا من T، فيمكن استبدال كائنات من النوع T بكائنات من النوع S (مثلًا، قد تحل كائنات من النوع S محل كائنات من النوع T) دون تغيير أي من الخصائص المرغوبة لهذا البرنامج (الصحة، الأداء ، إلخ). " هذا تعريف أكثر ترويعًا!
1409 |
1410 | أفضل شرح لهذا المفهوم: ليكن لديك صنف أساسي وصنف فرعي، فيمكن التبديل بين الصنف الأساسي والصنف الفرعي دون مشاكل. يبدو هذا محيرًا، لذلك لنلقي نظرة على مثال "المربع-المستطيل" Square-Rectangle الكلاسيكي. من الناحية الرياضية، يعد المربع مستطيلًا، ولكن إذا نمذجته باستخدام علاقة التعريف "is-a" عبر التوريث ، فإنك ستقع في مشكلة بسرعة.
1411 |
1412 | **خطأ:**
1413 |
1414 | ```javascript
1415 | class Rectangle {
1416 | constructor() {
1417 | this.width = 0;
1418 | this.height = 0;
1419 | }
1420 |
1421 | setColor(color) {
1422 | // ...
1423 | }
1424 |
1425 | render(area) {
1426 | // ...
1427 | }
1428 |
1429 | setWidth(width) {
1430 | this.width = width;
1431 | }
1432 |
1433 | setHeight(height) {
1434 | this.height = height;
1435 | }
1436 |
1437 | getArea() {
1438 | return this.width * this.height;
1439 | }
1440 | }
1441 |
1442 | class Square extends Rectangle {
1443 | setWidth(width) {
1444 | this.width = width;
1445 | this.height = width;
1446 | }
1447 |
1448 | setHeight(height) {
1449 | this.width = height;
1450 | this.height = height;
1451 | }
1452 | }
1453 |
1454 | function renderLargeRectangles(rectangles) {
1455 | rectangles.forEach((rectangle) => {
1456 | rectangle.setWidth(4);
1457 | rectangle.setHeight(5);
1458 | const area = rectangle.getArea(); //خطأ: يرجع 25 للمربع، والصحيح هو 20
1459 | rectangle.render(area);
1460 | });
1461 | }
1462 |
1463 | const rectangles = [new Rectangle(), new Rectangle(), new Square()];
1464 | renderLargeRectangles(rectangles);
1465 | ```
1466 |
1467 | **صواب:**
1468 |
1469 | ```javascript
1470 | class Shape {
1471 | setColor(color) {
1472 | // ...
1473 | }
1474 |
1475 | render(area) {
1476 | // ...
1477 | }
1478 | }
1479 |
1480 | class Rectangle extends Shape {
1481 | constructor(width, height) {
1482 | super();
1483 | this.width = width;
1484 | this.height = height;
1485 | }
1486 |
1487 | getArea() {
1488 | return this.width * this.height;
1489 | }
1490 | }
1491 |
1492 | class Square extends Shape {
1493 | constructor(length) {
1494 | super();
1495 | this.length = length;
1496 | }
1497 |
1498 | getArea() {
1499 | return this.length * this.length;
1500 | }
1501 | }
1502 |
1503 | function renderLargeShapes(shapes) {
1504 | shapes.forEach((shape) => {
1505 | const area = shape.getArea();
1506 | shape.render(area);
1507 | });
1508 | }
1509 |
1510 | const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
1511 | renderLargeShapes(shapes);
1512 | ```
1513 |
1514 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1515 |
1516 | ### مبدأ فصل الواجهات Interface Segregation Principle (ISP)
1517 |
1518 | جافاسكريبت ليس فيها واجهات (interfaces)، لذا لا ينطبق هذا المبدأ انطباقًا صارمًا كالمبادئ الأخرى. ومع ذلك، فهو مهم وثيق الصلة، على الرغم من افتقار جافاسكريبت إلى نظام أنواع البيانات (type system).
1519 |
1520 | ينص مبدأ فصل الواجهات على أنه "ينبغي أن لا يُجبر العملاء على الاعتماد على واجهات (interfaces) لا يستخدمونها." الواجهات هي عقود ضمنية في JavaScript بسبب نظام التحقق من الأنواع.
1521 |
1522 | من الأمثلة الجيدة لتوضيح هذا المبدأ في جافاسكريبت هي الأصناف التي تتطلب كائن إعدادات كبير. لذا يُعد عدم مطالبة العملاء بإعداد خيارات كثيرة جدا أمرًا مفيدًا؛ لأنهم غالبا لن يحتاجوا إلى جميع الإعدادات. لذلك يساعد جعلها اختيارية على منع وجود واجهة متخمة (fat interface).
1523 |
1524 | **خطأ:**
1525 |
1526 | ```javascript
1527 | class DOMTraverser {
1528 | constructor(settings) {
1529 | this.settings = settings;
1530 | this.setup();
1531 | }
1532 |
1533 | setup() {
1534 | this.rootNode = this.settings.rootNode;
1535 | this.settings.animationModule.setup();
1536 | }
1537 |
1538 | traverse() {
1539 | // ...
1540 | }
1541 | }
1542 |
1543 | const $ = new DOMTraverser({
1544 | rootNode: document.getElementsByTagName("body"),
1545 | animationModule() {}, // في معظم الوقت، لن نحتاج إلى التحريك أثناء العبور
1546 | // ...
1547 | });
1548 | ```
1549 |
1550 | **صواب:**
1551 |
1552 | ```javascript
1553 | class DOMTraverser {
1554 | constructor(settings) {
1555 | this.settings = settings;
1556 | this.options = settings.options;
1557 | this.setup();
1558 | }
1559 |
1560 | setup() {
1561 | this.rootNode = this.settings.rootNode;
1562 | this.setupOptions();
1563 | }
1564 |
1565 | setupOptions() {
1566 | if (this.options.animationModule) {
1567 | // ...
1568 | }
1569 | }
1570 |
1571 | traverse() {
1572 | // ...
1573 | }
1574 | }
1575 |
1576 | const $ = new DOMTraverser({
1577 | rootNode: document.getElementsByTagName("body"),
1578 | options: {
1579 | animationModule() {},
1580 | },
1581 | });
1582 | ```
1583 |
1584 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1585 |
1586 | ### مبدأ عكس التابعيّة Dependency Inversion Principle (DIP)
1587 |
1588 | ينص هذا المبدأ على أمرين أساسيين:
1589 |
1590 | 1. يجب ألا تعتمد الوحدات عالية المستوى (high-level modules) على وحدات منخفضة المستوى (low-level modules). كلاهما يجب أن يعتمد على التجريدات (abstractions).
1591 | 2. يجب ألا تعتمد التجريدات (abstractions) على التفاصيل. بل يجب أن تعتمد التفاصيل على التجريدات.
1592 |
1593 | قد يصعب فهم هذا في البداية، ولكن إن تعاملت مع مكتبة AngularJS، فلربما شاهدت تطبيقًا لهذا المبدأ في شكل حقن التبعية (Dependency (Injection. على الرغم من أنها ليست مفاهيمَ متطابقة، إلا أن حقن التبعية تمنع الوحدات الأعلى من معرفة تفاصيل الوحدات الأدنى وإعدادها.
1594 |
1595 | يمكنها تحقيق ذلك من خلال حقن التبعية. ففائدتها الكبيرة أنها تقلل من الترابط (coupling) بين الوحدات. يعدّ الترابط نمط تطوير سيئًا للغاية؛ لأنه يصعّب إعادة هيكلة الكود (code refactor).
1596 |
1597 | كما ذكرنا سابقًا، لا تحتوي جافاسكريبت على واجهات (interfaces)، لذا فإن التجريدات التي تعتمد عليها هي عقود (contracts) ضمنية. بمعنى آخر، تعتمد على الدوال (methods) والخصائص (properties) التي يعرضها الكائن أو الصنف لكائن أو صنف آخر.
1598 | في المثال أدناه، العقد الضمني هو أن أي وحدة طلب (Request module) لصنف متابعة المخزون "InventoryTracker" سيكون لها دالة عناصر الطلب "requestItems".
1599 |
1600 | **خطأ:**
1601 |
1602 | ```javascript
1603 | class InventoryRequester {
1604 | constructor() {
1605 | this.REQ_METHODS = ["HTTP"];
1606 | }
1607 |
1608 | requestItem(item) {
1609 | // ...
1610 | }
1611 | }
1612 |
1613 | class InventoryTracker {
1614 | constructor(items) {
1615 | this.items = items;
1616 |
1617 | // سيء: لقد أنشأنا اعتمادية على تنفيذ طلب معيّن
1618 |
1619 | // يجب أن يكون لدينا فقط عناصر الطلب "requestItems" تعتمد على دالة الطلب: `request`
1620 |
1621 | this.requester = new InventoryRequester();
1622 | }
1623 |
1624 | requestItems() {
1625 | this.items.forEach((item) => {
1626 | this.requester.requestItem(item);
1627 | });
1628 | }
1629 | }
1630 |
1631 | const inventoryTracker = new InventoryTracker(["apples", "bananas"]);
1632 | inventoryTracker.requestItems();
1633 | ```
1634 |
1635 | **صواب:**
1636 |
1637 | ```javascript
1638 | class InventoryTracker {
1639 | constructor(items, requester) {
1640 | this.items = items;
1641 | this.requester = requester;
1642 | }
1643 |
1644 | requestItems() {
1645 | this.items.forEach((item) => {
1646 | this.requester.requestItem(item);
1647 | });
1648 | }
1649 | }
1650 |
1651 | class InventoryRequesterV1 {
1652 | constructor() {
1653 | this.REQ_METHODS = ["HTTP"];
1654 | }
1655 |
1656 | requestItem(item) {
1657 | // ...
1658 | }
1659 | }
1660 |
1661 | class InventoryRequesterV2 {
1662 | constructor() {
1663 | this.REQ_METHODS = ["WS"];
1664 | }
1665 |
1666 | requestItem(item) {
1667 | // ...
1668 | }
1669 | }
1670 |
1671 | // من خلال بناء الاعتماديات خارجيًا وحقنها، يمكننا ذلك بسهولة
1672 | // استبدال وحدة الطلب بوحدة جديدة رائعة تستخدم تقنية WebSockets.
1673 |
1674 | const inventoryTracker = new InventoryTracker(
1675 | ["apples", "bananas"],
1676 | new InventoryRequesterV2()
1677 | );
1678 | inventoryTracker.requestItems();
1679 | ```
1680 |
1681 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1682 |
1683 | ## **الاختبار**
1684 |
1685 | الاختبار أهم من إطلاق المنتج. إذا لم تختبر اختبارات كافية، فلن تتأكد أنك لن تفسد شيئا ما عند إطلاق المنتج. إن اختيار عدد الاختبارات الضروري يقرّره فريقك، ولكن اختبار (جميع البيانات والفروع) بنسبة 100٪ ضروري لكسب ثقة عالية جدًا للمطورين وإراحة بالهم. أي مع إطار عمل اختبار رائع، ستحتاج أيضًا إلى استخدام [أداة تغطية جيدة](https://gotwarlost.github.io/istanbul/).
1686 |
1687 | لا عذر لانعدام الاختبارات. هناك [الكثير من أطر عمل اختبار جافاسكريبت الجيدة](https://jstherightway.org/#testing-tools)، لذا ابحث عن إطار يناسب فريقك،ثم استهدف دائمًا كتابة اختبارات لكل ميزة/وحدة جديدة تقدّمها. إذا كانت طريقتك المفضلة هي التطوير الموجّه بالاختبار (TDD)، فهذا رائع، ولكن الأهم أن تتأكد من بلوغ أهداف التغطية قبل إطلاق أي ميزة، أو إعادة هيكلة ميزة موجودة مسبقًا.
1688 |
1689 | ### مفهوم واحد لكل اختبار
1690 |
1691 | **خطأ:**
1692 |
1693 | ```javascript
1694 | import assert from "assert";
1695 |
1696 | describe("MomentJS", () => {
1697 | it("handles date boundaries", () => {
1698 | let date;
1699 |
1700 | date = new MomentJS("1/1/2015");
1701 | date.addDays(30);
1702 | assert.equal("1/31/2015", date);
1703 |
1704 | date = new MomentJS("2/1/2016");
1705 | date.addDays(28);
1706 | assert.equal("02/29/2016", date);
1707 |
1708 | date = new MomentJS("2/1/2015");
1709 | date.addDays(28);
1710 | assert.equal("03/01/2015", date);
1711 | });
1712 | });
1713 | ```
1714 |
1715 | **صواب:**
1716 |
1717 | ```javascript
1718 | import assert from "assert";
1719 |
1720 | describe("MomentJS", () => {
1721 | it("handles 30-day months", () => {
1722 | const date = new MomentJS("1/1/2015");
1723 | date.addDays(30);
1724 | assert.equal("1/31/2015", date);
1725 | });
1726 |
1727 | it("handles leap year", () => {
1728 | const date = new MomentJS("2/1/2016");
1729 | date.addDays(28);
1730 | assert.equal("02/29/2016", date);
1731 | });
1732 |
1733 | it("handles non-leap year", () => {
1734 | const date = new MomentJS("2/1/2015");
1735 | date.addDays(28);
1736 | assert.equal("03/01/2015", date);
1737 | });
1738 | });
1739 | ```
1740 |
1741 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1742 |
1743 | ## **التزامن**
1744 |
1745 | ### استخدم الوعود (promises)، وليس دوال رد النداء (callbacks)
1746 |
1747 | دوال رد النداء (callbacks) ليست أكوادًا نظيفة، وتسبب تداخلًا مفرطًا. مع الإصدارات الحديثة ES2015/ES6، تعد الوعود نوعًا عامًا مدمجًا. استخدمها!
1748 |
1749 | **خطأ:**
1750 |
1751 | ```javascript
1752 | import { get } from "request";
1753 | import { writeFile } from "fs";
1754 |
1755 | get(
1756 | "https://en.wikipedia.org/wiki/Robert_Cecil_Martin",
1757 | (requestErr, response, body) => {
1758 | if (requestErr) {
1759 | console.error(requestErr);
1760 | } else {
1761 | writeFile("article.html", body, (writeErr) => {
1762 | if (writeErr) {
1763 | console.error(writeErr);
1764 | } else {
1765 | console.log("File written");
1766 | }
1767 | });
1768 | }
1769 | }
1770 | );
1771 | ```
1772 |
1773 | **صواب:**
1774 |
1775 | ```javascript
1776 | import { get } from "request-promise";
1777 | import { writeFile } from "fs-extra";
1778 |
1779 | get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
1780 | .then((body) => {
1781 | return writeFile("article.html", body);
1782 | })
1783 | .then(() => {
1784 | console.log("File written");
1785 | })
1786 | .catch((err) => {
1787 | console.error(err);
1788 | });
1789 | ```
1790 |
1791 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1792 |
1793 | ### استخدام Async/Await أنظف حتى من الوعود (Promises)
1794 |
1795 | تعد الوعود (promises) بديلاً نظيفًا جدًا لدوال رد النداء (callbacks)، ولكن ES2017/ES8 تجلب `async` و`await` التّين تقدّمان كودًا نظيفا أكثر. كل ما تحتاج إليه هو دالة مسبوقة بالكلمة المفتاحية `async`، وبعد ذلك اكتب الكود دون دوال `then` المسلسلة. استخدم هذا إذا قدرت أن تستفيد من ميزات ES2017/ES8 اليوم!
1796 |
1797 | **خطأ:**
1798 |
1799 | ```javascript
1800 | import { get } from "request-promise";
1801 | import { writeFile } from "fs-extra";
1802 |
1803 | get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
1804 | .then((body) => {
1805 | return writeFile("article.html", body);
1806 | })
1807 | .then(() => {
1808 | console.log("File written");
1809 | })
1810 | .catch((err) => {
1811 | console.error(err);
1812 | });
1813 | ```
1814 |
1815 | **صواب:**
1816 |
1817 | ```javascript
1818 | import { get } from "request-promise";
1819 | import { writeFile } from "fs-extra";
1820 |
1821 | async function getCleanCodeArticle() {
1822 | try {
1823 | const body = await get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin");
1824 | await writeFile("article.html", body);
1825 | console.log("File written");
1826 | } catch (err) {
1827 | console.error(err);
1828 | }
1829 | }
1830 |
1831 | getCleanCodeArticle();
1832 | ```
1833 |
1834 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1835 |
1836 | ## **التعامل مع الأخطاء**
1837 |
1838 | تعد الأخطاء الملقاة (thrown errors)جيّدة! فهي تعني أن خطأ أثناء التشغيل قد التقط بنجاح، ويخبرك عن طريق إيقاف تنفيذ الوظيفة على المكدّس الحالي (current stack)، ويُوقف العملية (في Node)، ويُنبّهك في وحدة التحكم بتتبّع المكدس (stack trace).
1839 |
1840 | ### لا تتجاهل الأخطاء المُكتَشفة
1841 |
1842 | تجاهل الخطأ مكتشف لا ينفعك في إصلاحه أو التعامل معه. لا يعد تسجيل الخطأ في السجّل بالتعليمة (`console.log`) أفضل كثيرًا لأنه في كثيرًا ما يضيع في بحر من النصوص المطبوعة على سجل التحكم (console).
1843 | إذا أحطت أي جزء من الكود بـ `try/catch`، فهذا يعني أنك تتوقع خطأ ما هناك، وعليه لديك خطة للتعامل مع الخطأ وتسييره.
1844 |
1845 | **خطأ:**
1846 |
1847 | ```javascript
1848 | try {
1849 | functionThatMightThrow();
1850 | } catch (error) {
1851 | console.log(error);
1852 | }
1853 | ```
1854 |
1855 | **صواب:**
1856 |
1857 | ```javascript
1858 | try {
1859 | functionThatMightThrow();
1860 | } catch (error) {
1861 | // الخيار الأول (أكثر ضوضاء من console.log)
1862 | console.error(error);
1863 | // خيار آخر:
1864 | notifyUserOfError(error);
1865 | // خيار آخر:
1866 | reportErrorToService(error);
1867 | // أو عمل الجميع!
1868 | }
1869 | ```
1870 |
1871 | ### لا تتجاهل الوعود المرفوضة (rejected promises)
1872 |
1873 | لنفس السبب ينبغي أن لا تتجاهل الأخطاء التي اكتشفت من `try/catch`.
1874 |
1875 | **خطأ:**
1876 |
1877 | ```javascript
1878 | getdata()
1879 | .then((data) => {
1880 | functionThatMightThrow(data);
1881 | })
1882 | .catch((error) => {
1883 | console.log(error);
1884 | });
1885 | ```
1886 |
1887 | **صواب:**
1888 |
1889 | ```javascript
1890 | getdata()
1891 | .then((data) => {
1892 | functionThatMightThrow(data);
1893 | })
1894 | .catch((error) => {
1895 | // الخيار الأول (أكثر ضوضاء من console.log)
1896 |
1897 | console.error(error);
1898 | // خيار آخر:
1899 |
1900 | notifyUserOfError(error);
1901 | // خيار آخر:
1902 |
1903 | reportErrorToService(error);
1904 | // أو عمل الجميع!
1905 | });
1906 | ```
1907 |
1908 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1909 |
1910 | ## **التنسيق**
1911 |
1912 | التنسيق أمر شخصي. مثل كثير مما ذكرنا من قواعد هنا، لا قاعدة مطلقة عليك اتباعها دون وعي. المهم، لا داعي للجدال على التنسيق. هناك [الكثير من الأدوات](https://standardjs.com/rules.html) لأتمتة هذا. استخدم واحدة! إن الجدل عن التنسيق ما هو إلا مضيعة للوقت والمال.
1913 |
1914 | أمّا ما لا يرِدُ ضمن مجال التنسيق التلقائي: كالمسافة البادئة (indentation)، وعلامات التبويب مقابل المسافات (tabs vs. spaces)، وعلامات الاقتباس المزدوجة مقابل علامات الاقتباس المفردة (double vs. single quotes)، إلخ، فابحث هنا عن بعض الإرشادات.
1915 |
1916 | ### استخدام الأحرف الكبيرة باتساق
1917 |
1918 | جافاسكريبت لغة غير محددة أنواع البيانات، لذا فإن الكتابة بالأحرف الكبيرة قد تساعدك في التفريق بين المتغيرات والدوال وما إلى ذلك. هذه قواعد ذاتية، لذا قد يختار فريقك ما يناسبه. المهم، هو الاتساق بغض النظر عما تختاره مع فريقك.
1919 |
1920 | **خطأ:**
1921 |
1922 | ```javascript
1923 | const DAYS_IN_WEEK = 7;
1924 | const daysInMonth = 30;
1925 |
1926 | const songs = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
1927 | const Artists = ["ACDC", "Led Zeppelin", "The Beatles"];
1928 |
1929 | function eraseDatabase() {}
1930 | function restore_database() {}
1931 |
1932 | class animal {}
1933 | class Alpaca {}
1934 | ```
1935 |
1936 | **صواب:**
1937 |
1938 | ```javascript
1939 | const DAYS_IN_WEEK = 7;
1940 | const DAYS_IN_MONTH = 30;
1941 |
1942 | const SONGS = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
1943 | const ARTISTS = ["ACDC", "Led Zeppelin", "The Beatles"];
1944 |
1945 | function eraseDatabase() {}
1946 | function restoreDatabase() {}
1947 |
1948 | class Animal {}
1949 | class Alpaca {}
1950 | ```
1951 |
1952 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
1953 |
1954 | ### يجب أن تكون الدوال المنادِية (callers) والمنادى عليها (callees) قريبة من بعضها
1955 |
1956 | إذا استدعت دالة أخرى، احتفظ بهذه الدوال قريبة عموديًا في ملف المصدر. من الناحية المثالية، احتفظ بالدالة المنادية فوق الدالة المُناداة مباشرة. نميل إلى قراءة الأكواد من أعلى إلى أسفل مثل الجريدة. لذا اجعل الكود يقرأ بهذه الطريقة.
1957 |
1958 | **خطأ:**
1959 |
1960 | ```javascript
1961 | class PerformanceReview {
1962 | constructor(employee) {
1963 | this.employee = employee;
1964 | }
1965 |
1966 | lookupPeers() {
1967 | return db.lookup(this.employee, "peers");
1968 | }
1969 |
1970 | lookupManager() {
1971 | return db.lookup(this.employee, "manager");
1972 | }
1973 |
1974 | getPeerReviews() {
1975 | const peers = this.lookupPeers();
1976 | // ...
1977 | }
1978 |
1979 | perfReview() {
1980 | this.getPeerReviews();
1981 | this.getManagerReview();
1982 | this.getSelfReview();
1983 | }
1984 |
1985 | getManagerReview() {
1986 | const manager = this.lookupManager();
1987 | }
1988 |
1989 | getSelfReview() {
1990 | // ...
1991 | }
1992 | }
1993 |
1994 | const review = new PerformanceReview(employee);
1995 | review.perfReview();
1996 | ```
1997 |
1998 | **صواب:**
1999 |
2000 | ```javascript
2001 | class PerformanceReview {
2002 | constructor(employee) {
2003 | this.employee = employee;
2004 | }
2005 |
2006 | perfReview() {
2007 | this.getPeerReviews();
2008 | this.getManagerReview();
2009 | this.getSelfReview();
2010 | }
2011 |
2012 | getPeerReviews() {
2013 | const peers = this.lookupPeers();
2014 | // ...
2015 | }
2016 |
2017 | lookupPeers() {
2018 | return db.lookup(this.employee, "peers");
2019 | }
2020 |
2021 | getManagerReview() {
2022 | const manager = this.lookupManager();
2023 | }
2024 |
2025 | lookupManager() {
2026 | return db.lookup(this.employee, "manager");
2027 | }
2028 |
2029 | getSelfReview() {
2030 | // ...
2031 | }
2032 | }
2033 |
2034 | const review = new PerformanceReview(employee);
2035 | review.perfReview();
2036 | ```
2037 |
2038 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
2039 |
2040 | ## **التعليقات**
2041 |
2042 | ### اكتب التعليقات على الأشياء المعقّدة فقط (business logic complexity).
2043 |
2044 | ليست التعليقات ضرورة مُلحّة. الكود الجيد _ تقريبًا_ يوثّق نفسه.
2045 |
2046 | **خطأ:**
2047 |
2048 | ```javascript
2049 | function hashIt(data) {
2050 | // التجزئة
2051 | let hash = 0;
2052 |
2053 | // طول النص
2054 | const length = data.length;
2055 |
2056 | // الدوران على كل حرف في البيانات
2057 | for (let i = 0; i < length; i++) {
2058 | // الحصول على كود الحرف
2059 | const char = data.charCodeAt(i);
2060 | // إنشاء التجزئة
2061 | hash = (hash << 5) - hash + char;
2062 | // التحويل إلى عدد صحيح من 32 بت
2063 | hash &= hash;
2064 | }
2065 | }
2066 | ```
2067 |
2068 | **صواب:**
2069 |
2070 | ```javascript
2071 | function hashIt(data) {
2072 | let hash = 0;
2073 | const length = data.length;
2074 |
2075 | for (let i = 0; i < length; i++) {
2076 | const char = data.charCodeAt(i);
2077 | hash = (hash << 5) - hash + char;
2078 |
2079 | // التحويل إلى عدد صحيح من 32 بت
2080 | hash &= hash;
2081 | }
2082 | }
2083 | ```
2084 |
2085 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
2086 |
2087 | ### لا تترك الأكواد المعلّقة في الكود
2088 |
2089 | أضيفت برمجيات التحكم في الإصدار (version control) لسبب وجيه. اترك الكود القديم في سجلّك.
2090 |
2091 | **خطأ:**
2092 |
2093 | ```javascript
2094 | doStuff();
2095 | // doOtherStuff();
2096 | // doSomeMoreStuff();
2097 | // doSoMuchStuff();
2098 | ```
2099 |
2100 | **صواب:**
2101 |
2102 | ```javascript
2103 | doStuff();
2104 | ```
2105 |
2106 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
2107 |
2108 | ### لا تحتفظ بالتعليقات التي تشبه المذكرات اليومية
2109 |
2110 | تذكر، استخدم برمجيات التحكم في الإصدار! ليست هناك حاجة للكود الميت، ولا الأكواد المعلّقة، وخاصة تعليقات المذكرات اليومية. استخدم `git log` للحصول على السجل!
2111 |
2112 | **خطأ:**
2113 |
2114 | ```javascript
2115 | /**
2116 | * 2016-12-20: حذف الmonads، لم أفهمها (مستخدم أ)
2117 | * 2016-10-01: تحسين استخدام نوع خاص من monads (مستخدم ب)
2118 | * 2016-02-03: حذف التحقق من أنواع البيانات (مستخدم ج)
2119 | */
2120 | function combine(a, b) {
2121 | return a + b;
2122 | }
2123 | ```
2124 |
2125 | **صواب:**
2126 |
2127 | ```javascript
2128 | function combine(a, b) {
2129 | return a + b;
2130 | }
2131 | ```
2132 |
2133 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
2134 |
2135 | ### تجنب العلامات الموضعية
2136 |
2137 | عادة ما تضيف العلامات الموضعية حشوًا لا غير. دع الدوال وأسماء المتغيرات جنبًا إلى جنب مع المسافة البادئة المناسبة وسيعطي التنسيق البنية المرئية للكود.
2138 |
2139 | **خطأ:**
2140 | ```javascript
2141 | ////////////////////////////////////////////////////////////////////////////////
2142 | // إنشاء نموذج Scope
2143 | ////////////////////////////////////////////////////////////////////////////////
2144 | $scope.model = {
2145 | menu: "foo",
2146 | nav: "bar",
2147 | };
2148 |
2149 | ////////////////////////////////////////////////////////////////////////////////
2150 | // إعداد ال action
2151 | ////////////////////////////////////////////////////////////////////////////////
2152 | const actions = function () {
2153 | // ...
2154 | };
2155 | ```
2156 |
2157 | **صواب:**
2158 |
2159 | ```javascript
2160 | $scope.model = {
2161 | menu: "foo",
2162 | nav: "bar",
2163 | };
2164 |
2165 | const actions = function () {
2166 | // ...
2167 | };
2168 | ```
2169 |
2170 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
2171 |
2172 | ## الترجمة
2173 |
2174 | هذا الدليل متوفر أيضًا بلغات أخرى:
2175 |
2176 | -  **Armenian**: [hanumanum/clean-code-javascript/](https://github.com/hanumanum/clean-code-javascript)
2177 | -  **Arabic - العربية**: [imAbdelhadi/clean-code-javascript-in-arabic](https://github.com/imAbdelhadi/clean-code-javascript-in-arabic/)
2178 | -  **Bangla(বাংলা)**: [InsomniacSabbir/clean-code-javascript/](https://github.com/InsomniacSabbir/clean-code-javascript/)
2179 | -  **Brazilian Portuguese**: [fesnt/clean-code-javascript](https://github.com/fesnt/clean-code-javascript)
2180 | -  **Simplified Chinese**:
2181 | - [alivebao/clean-code-js](https://github.com/alivebao/clean-code-js)
2182 | - [beginor/clean-code-javascript](https://github.com/beginor/clean-code-javascript)
2183 | -  **Traditional Chinese**: [AllJointTW/clean-code-javascript](https://github.com/AllJointTW/clean-code-javascript)
2184 | -  **French**: [eugene-augier/clean-code-javascript-fr](https://github.com/eugene-augier/clean-code-javascript-fr)
2185 | -  **German**: [marcbruederlin/clean-code-javascript](https://github.com/marcbruederlin/clean-code-javascript)
2186 | -  **Indonesia**: [andirkh/clean-code-javascript/](https://github.com/andirkh/clean-code-javascript/)
2187 | -  **Italian**: [frappacchio/clean-code-javascript/](https://github.com/frappacchio/clean-code-javascript/)
2188 | -  **Japanese**: [mitsuruog/clean-code-javascript/](https://github.com/mitsuruog/clean-code-javascript/)
2189 | -  **Korean**: [qkraudghgh/clean-code-javascript-ko](https://github.com/qkraudghgh/clean-code-javascript-ko)
2190 | -  **Polish**: [greg-dev/clean-code-javascript-pl](https://github.com/greg-dev/clean-code-javascript-pl)
2191 | -  **Russian**:
2192 | - [BoryaMogila/clean-code-javascript-ru/](https://github.com/BoryaMogila/clean-code-javascript-ru/)
2193 | - [maksugr/clean-code-javascript](https://github.com/maksugr/clean-code-javascript)
2194 | -  **Spanish**: [tureey/clean-code-javascript](https://github.com/tureey/clean-code-javascript)
2195 | -  **Spanish**: [andersontr15/clean-code-javascript](https://github.com/andersontr15/clean-code-javascript-es)
2196 | -  **Serbian**: [doskovicmilos/clean-code-javascript/](https://github.com/doskovicmilos/clean-code-javascript)
2197 | -  **Turkish**: [bsonmez/clean-code-javascript](https://github.com/bsonmez/clean-code-javascript/tree/turkish-translation)
2198 | -  **Ukrainian**: [mindfr1k/clean-code-javascript-ua](https://github.com/mindfr1k/clean-code-javascript-ua)
2199 | -  **Vietnamese**: [hienvd/clean-code-javascript/](https://github.com/hienvd/clean-code-javascript/)
2200 | -  **Persian**: [hamettio/clean-code-javascript](https://github.com/hamettio/clean-code-javascript)
2201 |
2202 | **[⬆ العودة إلى الأعلى](#جدول-المحتويات)**
2203 |
2204 | ترجمه إلى العربية واثق الشويطر وعبدالهادي الأندلسي -وبتصرف- لدليل [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript)
2205 |
2206 | شكر وامتنان لكل من راجع الترجمة (القائمة أبجدية):
2207 | - [طه زروقي](https://twitter.com/linuxscout)
2208 | - [عبد العزيز مخناش](https://twitter.com/Abdelaziz18003)
2209 | - [عيسى بوكرن](https://twitter.com/tutomena)
2210 | - [معتز الخطيب](https://twitter.com/muotaz5)
2211 | - [ناصر داخل](https://www.linkedin.com/in/naser-dakhel)
2212 |
2213 |
--------------------------------------------------------------------------------