- Notifications
You must be signed in to change notification settings - Fork3
🛁 کدنویسی تمیز در جاوا اسکریپت
License
hamettio/clean-code-javascript
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Original Repository:ryanmcdermott/clean-code-javascript
- مقدمه
- متغیرها
- توابع
- آبجکت ها و ساختارهای داده
- کلاس ها
- SOLID
- تست
- همزمانی
- ارور هندلینگ
- قالب بندی
- کامنت گذاری
- ترجمه
در این مطلب، اصول مهندسی نرم افزار برای جاوا اسکریپت از کتابClean Codeاثر رابرت سی مارتین برگرفته شده و به عنوان راهنمایی برای نگارش نیست، بلکه راهنمایی برای ایجاد کدهایخوانا, قابل استفاده مجدد و قابل تغییر به زبان جاوا اسکریپت هست.
لازم نیست که هر اصلی که گفته شده حتما رعایت شود و حتی تعداد کمتری از این اصول مورد توافق همه توسعه دهدگان قرار خواهند گرفت. این موارد چیزی جز راهنمای مسیر نیستند و حاصل چندین سال تجربه جمعی نویسندگانClean Code هستند.
با اینکه صنعت مهندسی نرم افزار کمی بیش از 50 سال قدمت دارد ولی ما هنوز چیزهای زیادی برای یادگیری داریم. شاید با بالا رفتن قدمت معماری نرم افزار به اندازه خود معماری ، قوانین سخت تری برای پیروی از آن داشته باشیم. الان اجازه دهید این دستورالعمل ها به عنوان یک معیار مهم برای کیفیت سنجی کدی که شما و تیم تان با جاوا اسکریپت تولید می کنید، باشد.
مورد مهمی که باید در نظر داشته باشید ، اینکه دانستن این موارد باعث نمیشه که دیگر هیچ خطایی نداشته باشید و باور کنید که توسعه دهنده قوی تری هستید . برای قوی تر شدن بهتر است که بیشتر کد بزنید و تمرین کنید و فقط روی یک قطعه کد ساده و پیش نویس کار نکنید ، برای مثال خاک رس مرطوب به تنهایی چیز خاصی نیست ولی اگر با فوت کوزه گری به آن حالت بدید و به دفعات کارتان را باز بینی کنید ، قطعا محصول نهایی جذاب تری تولید می کنید.
بد
constyyyymmdstr=moment().format("YYYY/MM/DD");
خوب
constcurrentDate=moment().format("YYYY/MM/DD");
بد
getUserInfo();getClientData();getCustomerRecord();
خوب
getUser();
ما بیشتر از اینکه کد می زنیم ، کدها را می خوانیم. خوانا بودن و قابل جستجو بودن کد ما اهمیت بالایی دارد .پس با نام گذاری بی معنی و غیر قابل فهم متغیرها، کسانی که کدمان را میخوانند را آزار ندهیم. از نام های قابل جستوجو استفاده کنیم و برای این منظور ابزارهایی مانندbuddy.js وESLintمی توانند به شناسایی constant های بدون نام کمک کنند.
بد
// What the heck is 86400000 for?setTimeout(blastOff,86400000);
خوب
// Declare them as capitalized named constants.constMILLISECONDS_PER_DAY=60*60*24*1000;//86400000;setTimeout(blastOff,MILLISECONDS_PER_DAY);
بد
constaddress="One Infinite Loop, Cupertino 95014";constcityZipCodeRegex=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;saveCityZipCode(address.match(cityZipCodeRegex)[1],address.match(cityZipCodeRegex)[2]);
خوب
constaddress="One Infinite Loop, Cupertino 95014";constcityZipCodeRegex=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;const[_,city,zipCode]=address.match(cityZipCodeRegex)||[];saveCityZipCode(city,zipCode);
بد
constlocations=["Austin","New York","San Francisco"];locations.forEach(l=>{doStuff();doSomeOtherStuff();// ...// ...// ...// Wait, what is `l` for again?dispatch(l);});
خوب
constlocations=["Austin","New York","San Francisco"];locations.forEach(location=>{doStuff();doSomeOtherStuff();// ...// ...// ...dispatch(location);});
اگر نامی برای کلاس یا آبجکت خودتان انتخاب کردید از آنها درنامگذاری متغیر ها استفاده نکنید.
بد
constCar={carMake:"Honda",carModel:"Accord",carColor:"Blue"};functionpaintCar(car,color){car.carColor=color;}
خوب
constCar={make:"Honda",model:"Accord",color:"Blue"};functionpaintCar(car,color){car.color=color;}
آرگومان های پیش فرض اغلب از اتصال کوتاه تمیزترند . توجه داشته باشید که اگر از آنها استفاده کنید، تابع شما فقط مقادیر پیش فرض آرگومان هایundefined
را ارائه می دهد. سایر مقادیر"falsy" مثل''
،""
،false
،null
،0
، وNaN
، با مقدار پیش فرض جایگزین نخواهند شد.
بد
functioncreateMicrobrewery(name){constbreweryName=name||"Hipster Brew Co.";// ...}
خوب
functioncreateMicrobrewery(name="Hipster Brew Co."){// ...}
محدود کردن تعداد آرگومان های یک تابع بسیار اهمیت دارد، چون تست کردن تابع را برایتان آسان تر می کند. داشتن بیش از سه مورد آرگومان باعث سخت تر شدن پروسه تست کدها می شود.
استفاده از یک یا دو آرگومان ایده آل است و در صورت امکان ،در استفاده از سه آرگومان باید خودداری کرد و باید از ادغام کردن استفاده کرد. معمولاً، اگر بیش از دو آرگومان دارید، تابع شما سعی می کند کارهای زیادی را انجام دهد. در مواردی که اینطور نیست ، بیشتر اوقات یک آبجکت سطح بالاتر برای آرگومان کافی است.
از آنجا که ، جاوا اسکریپت به شما این امکان را می دهد، بدون استفاده از کدهای اضافی که برای ساخت کلاس استفاده می شوند، به راحتی آبجکت بسازید. در صورت نیاز به آرگومان های زیاد در یک تابع می توانید از یک آبجکت استفاده کنید و همه آنها را داخل آن بگذارید.
برای اینکه مشخص شود تابع مورد نظر چه ویژگی هایی را مدنظر دارد، می توانید از destructuring syntax در ES2015 / ES6 استفاده کنید، که چند مزیت اصلی دارد:
- وقتی کسی به ساختار تابع نگاه می کند، فورا روشن می شود که چه property هایی درون تابع استفاده می شود.
- می توان از آن برای شبیه سازی named parameters استفاده کرد.
- Destructuring همچنین از primitive value های مشخص شده از آبجکت که به عنوان آرگومان به تابع پاس داده شده یک کپی ایجاد می کند. با کمک آن ها می توان از side effectsها جلوگیری کرد و نکته مهم اینکه ،آبجکت ها و آرایه هایی که از آبجکت درون آرگومان یک تابع destructured می شوند ، یک کپی به حساب نمی آیند.
- Linter هایی مثل eslint در مورد property های استفاده نشده هشدار می دهند که بدون destructuring غیر ممکن است.
بد
functioncreateMenu(title,body,buttonText,cancellable){// ...}createMenu("Foo","Bar","Baz",true);
خوب
functioncreateMenu({ title, body, buttonText, cancellable}){// ...}createMenu({title:"Foo",body:"Bar",buttonText:"Baz",cancellable:true});
این قانون با فاصله زیاد با اهمیت ترین قانون در مهندسی نرم افزار است. وقتی توابع بیش از یک کار انجام می دهند، نوشتن، تست کردن و استدلالشان، سخت تر می شود. وقتی می توانید یک تابع را فقط برای انجام یک کار به صورت ایزوله بنویسید، می توان به راحتی آن را تغییر داد و کد شما بسیار تمیزتر خواهد شد. اگر از کل این راهنما فقط همین یک مورد را استفاده کنید شما از تعداد زیادی از توسعه دهندگان جلو خواهید زد.
بد
functionemailClients(clients){clients.forEach(client=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}
خوب
functionemailActiveClients(clients){clients.filter(isActiveClient).forEach(email);}functionisActiveClient(client){constclientRecord=database.lookup(client);returnclientRecord.isActive();}
بد
functionaddToDate(date,month){// ...}constdate=newDate();// It's hard to tell from the function name what is addedaddToDate(date,1);
خوب
functionaddMonthToDate(month,date){// ...}constdate=newDate();addMonthToDate(1,date);
زمانی که بیش از یک سطح abstraction داشته باشید، معمولا از تابع شما کار زیادی کشیده می شود . تقسیم کردن توابع به قابل استفاده مجدد بودن و تست کردن آسان تر آن منجر می شود.
بد
functionparseBetterJSAlternative(code){constREGEXES=[// ...];conststatements=code.split(" ");consttokens=[];REGEXES.forEach(REGEX=>{statements.forEach(statement=>{// ...});});constast=[];tokens.forEach(token=>{// lex...});ast.forEach(node=>{// parse...});}
خوب
functionparseBetterJSAlternative(code){consttokens=tokenize(code);constsyntaxTree=parse(tokens);syntaxTree.forEach(node=>{// parse...});}functiontokenize(code){constREGEXES=[// ...];conststatements=code.split(" ");consttokens=[];REGEXES.forEach(REGEX=>{statements.forEach(statement=>{tokens.push(/* ... */);});});returntokens;}functionparse(tokens){constsyntaxTree=[];tokens.forEach(token=>{syntaxTree.push(/* ... */);});returnsyntaxTree;}
تمام سعی خود را بکنید تا از نوشتن کدهای تکراری خودداری کنید. وجود کد تکراری خوب نیست، چون به این معنی است که وقتی نیاز به تغییر منطق در برنامه باشد، نیاز به تغییر در بیش از یک نقطه از برنامه را داریم.
تصور کنید در صورتی که یک رستوران را اداره می کنید و موجودی انبار خود از جمله تمام گوجه فرنگی ها ، پیاز ها ، سیر ، ادویه جات و … را پیگیری می کنید و چند لیست دارید که این کار را در آن انجام می دهید و باید همه ی این موجودی ها بعد از سرو یک غذا درست شده با گوجه فرنگی، به روز شوند. در صورتی که اگر یک لیست داشته باشید فقط و فقط یک جا برای آپدیت کردن هست و نه بیشتر.
اغلب اوقات به این علت کدهای تکراری دارید که دو یا چند چیز با تفاوت خیلی جزئی دارید که اشتراکات فراوانی با هم دارند. ولی این تفاوت ها ، شما را مجبور می کند که دو یا چند تابع جداگانه با عملکرد مشابه بسازید. حذف کد تکراری به معنی ایجاد یک abstraction است که می تواند مجموعه ای از موارد مختلف را فقط با یک تابع یا ماژول یا کلاس مدیریت کند.
درست گرفتن abstraction بسیار با اهمیت است، به همین دلیل باید از اصول SOLID مندرج در بخش Class ها پیروی کنید. abstraction های بد ممکن است از کد تکراری بدتر باشد، پس مراقب باشید! با در نظر گرفتن این گفته، اگر می توانید abstraction خوبی انجام دهید، آن را انجام دهید! کدتان را مجددا تکرار نکنید، در غیر این صورت هر زمان که بخواهید یک چیز را ویرایش کنید، باید بخش های مختلفی را تغییر دهید.
بد
functionshowDeveloperList(developers){developers.forEach(developer=>{constexpectedSalary=developer.calculateExpectedSalary();constexperience=developer.getExperience();constgithubLink=developer.getGithubLink();constdata={ expectedSalary, experience, githubLink};render(data);});}functionshowManagerList(managers){managers.forEach(manager=>{constexpectedSalary=manager.calculateExpectedSalary();constexperience=manager.getExperience();constportfolio=manager.getMBAProjects();constdata={ expectedSalary, experience, portfolio};render(data);});}
خوب
functionshowEmployeeList(employees){employees.forEach(employee=>{constexpectedSalary=employee.calculateExpectedSalary();constexperience=employee.getExperience();constdata={ expectedSalary, experience};switch(employee.type){case"manager":data.portfolio=employee.getMBAProjects();break;case"developer":data.githubLink=employee.getGithubLink();break;}render(data);});}
بد
constmenuConfig={title:null,body:"Bar",buttonText:null,cancellable:true};functioncreateMenu(config){config.title=config.title||"Foo";config.body=config.body||"Bar";config.buttonText=config.buttonText||"Baz";config.cancellable=config.cancellable!==undefined ?config.cancellable :true;}createMenu(menuConfig);
خوب
constmenuConfig={title:"Order",// User did not include 'body' keybuttonText:"Send",cancellable:true};functioncreateMenu(config){letfinalConfig=Object.assign({title:"Foo",body:"Bar",buttonText:"Baz",cancellable:true},config);returnfinalConfig// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}// ...}createMenu(menuConfig);
flagها به کاربر شما می گویند که این تابع بیش از یک کار انجام می دهد. توابع باید یک کار انجام دهند. اگر توابع شما براساس مسیرهای وابسته به boolean هستند، توابع خود را به صورت جدا از هم بنویسید .
بد
functioncreateFile(name,temp){if(temp){fs.create(`./temp/${name}`);}else{fs.create(name);}}
خوب
functioncreateFile(name){fs.create(name);}functioncreateTempFile(name){createFile(`./temp/${name}`);}
یک تابع در صورتی side effect تولید می کند که کاری به غیر از گرفتن یک مقدار و برگرداندن یک مقدار یا مقادیر دیگر نکند.یک side effect می تواند برای یک فایل نوشته شودیا برای اصلاح چند متغیر global باشد یا به طرز عجیبی یک مسیر از تمام پول های داخل حسابتان به یک شخص غریبه ایجاد کند.
حالا اگر تحت یک موقعیت که مجبور به داشتن side effect ها در یک برنامهبودید مثل مثال قبل که برای یک فایل نوشته بودید، کاری که نیاز هست انجام بدید این هست که به جایی که در آن هستید متمرکز شوید.از کلاس ها و توابع متعدد در نوشتن یک فایل استفاده نکنید و فقط و فقط یک سرویس بنویسید که این کار را انجام دهد.
نکته اصلی اینجاست که باید از اشتراک گذاری state بین آبجکت های بدون ساختار و استفاده از data type های قابل تغییر که می توانند توسط هر چیزی نوشته شوند و همچنین متمرکز نبودن در جایی که side effect ها رخ می دهند خودداری کرد. اگر شما بتوانید این کارها را انجام بدید، نسبت به خیلی از برنامه نویسان خوشحال تر خواهید بود.
بد
// متغیر global که توسط این تابع ارجاع داده شده// اگر ما یک تابع دیگر داشته باشیم که از این نام استفاده کند، این نام تبدیل به یک آرایه خواهد شد و می تواند کد ما را break کندletname="Ryan McDermott";functionsplitIntoFirstAndLastName(){name=name.split(" ");}splitIntoFirstAndLastName();console.log(name);// ['Ryan', 'McDermott'];
خوب
functionsplitIntoFirstAndLastName(name){returnname.split(" ");}constname="Ryan McDermott";constnewName=splitIntoFirstAndLastName(name);console.log(name);// 'Ryan McDermott';console.log(newName);// ['Ryan', 'McDermott'];
در جاوااسکریپت ، بعضی مقادیر قابل تغییر هستند و بعضی ها هم غیر قابل تغییرند. آبجکت ها و آرایه ها، 2 نوع قابل تغییر هستند و با احتیاط مدیریت کردن آنها در زمان پاس دادن به عنوان آرگومان به یک تابع بسیار با اهمیت هست. یک تابع جاوااسکریپت می تواند property های یک آبجکت یا محتویات یک آرایه را تغییر دهد که این موضوع باعث ایجاد باگ هایی در جاهای دیگر می شود.
فرض کنید که یک تابع داریم که با گرفتن یک آرایه به عنوان آرگومان یک سبد خرید ایجاد می کند، برای مثال اگر تابع با اضافه کردن یک آیتم جدید برای خرید در آرایه سبد خرید تغییر کند، هر تابع دیگری که از همین آرایهcart
استفاده می کند، تغییر می کند.این تغییر همانطور که می تواند خوب باشد به همان اندازه می تواند بد باشد. تصور کنید که در موقعیت بد قرار داریم:
کاربر روی دکمه "Purchase" می زند که تابعpurchase
را که یک درخواست به شبکه را ایجاد می کند و آرایهcart
را به سرور می فرستد فراخوانی می کند. بدلیل یک درخواست شبکه بد، تابعpurchase
مجبور به تلاش مجدد برای ارسال درخواست می شود.حالا اگر کاربر در همین حین لحظه روی دکمه "Add to cart" به طور اتفاقی برای محصولی که اصلا نمی خواهد قبل از اینکه درخواست شبکه شروع به ارسال کند بزند ، چه اتفاقی می افتد؟ اگر این اتفاق بیافتد و درخواست شبکه شروع شود، آن وقت تابع خرید به صورت تصادفی آیتم اضافه شده را می فرستد ، چون آرایهcart
تغییر کرده است.
یک راه حل عالی می تواند تابعaddItemToCart
برای کپی کردنcart
و ویرایش آ ن و در نهایت بازگرداندن آن کپی باشد. این کار تضمین می کند که توابعی که هنوز از سبد خرید قدیمی استفاده می کنند ، تغییرات در آن ها اعمال نمی شود.
دو هشدار درمورد این نگرش:
شاید مواردی باشد که شما واقعا بخواهید که آبجکت ورودی را تغییر بدهید، ولی وقتی شما طبق این نگرش برنامه نویسی پیش می روید آن موارد را خیلی کمیاب تلقی می کنید. در اکثر موارد می توان تغییراتی در آنها ایجاد کرد که هیچ side effect نداشته باشند!
کپی کردن آبجکت های بزرگ می تواند در performance برای ما گران تمام شوند، خوشبختانه در عمل این مشکل بزرگی نیست وکتابخانه های خوبی وجود دارند که به شما اجازه می دهند این نگرش برای شما سریع و با مصرف مموری کمتر و امکان کپی کردن دستی آبجکت ها و آرایه ها را فراهم می کند.
بد
constaddItemToCart=(cart,item)=>{cart.push({ item,date:Date.now()});};
خوب
constaddItemToCart=(cart,item)=>{return[...cart,{ item,date:Date.now()}];};
شلوغکاری با توابع global کار خوبی در جاوااسکریپت نیست چون امکان تقابل با یک کتابخانه دیگر هست و کاربر استفاده کننده از API شما را تا زمان دریافت یک استثنا در production کلافه می کند.بیاید با هم یک مثال را بررسی کنیم:
چی می شود اگر بخواهیم متد آرایه های native در جاوااسکریپت را با یک متدdiff
گسترش بدهیم که می تواند اختلاف بین 2 آرایه را نشان دهد؟شما می توانید یک تابع جدید باArray.prototype
ایجاد کنید ولی با یک کتابخانه دیگر که سعی می کند کار مشابه ای را انجام دهد تداخل می کند.چه می شود اگر آن کتابخانه فقط ازdiff
برای پیدا کردن اختلاف بین المان های اول و آخر آرایه استفاده کند؟ به همین خاطر هست که بهتر هست که از کلاس در ES2015/ES6 استفاده کنیم و به آسانیArray
global را گستربش بدیم.
بد
Array.prototype.diff=functiondiff(comparisonArray){consthash=newSet(comparisonArray);returnthis.filter(elem=>!hash.has(elem));};
خوب
classSuperArrayextendsArray{diff(comparisonArray){consthash=newSet(comparisonArray);returnthis.filter(elem=>!hash.has(elem));}}
جاوااسکریپت در مقایسه با زبان برنامه نویسی haskell یک زبان functional به حساب نمی آید ولی خیلی تمایل به functional بودن دارد.زبان های functional می توانند بسیار تمیزتر و آسانتر برای تست کردن باشند. اگر می توانید دوستدار این استایل برنامه نویسی باشید.
بد
constprogrammerOutput=[{name:"Uncle Bobby",linesOfCode:500},{name:"Suzie Q",linesOfCode:1500},{name:"Jimmy Gosling",linesOfCode:150},{name:"Gracie Hopper",linesOfCode:1000}];lettotalOutput=0;for(leti=0;i<programmerOutput.length;i++){totalOutput+=programmerOutput[i].linesOfCode;}
خوب
constprogrammerOutput=[{name:"Uncle Bobby",linesOfCode:500},{name:"Suzie Q",linesOfCode:1500},{name:"Jimmy Gosling",linesOfCode:150},{name:"Gracie Hopper",linesOfCode:1000}];consttotalOutput=programmerOutput.reduce((totalLines,output)=>totalLines+output.linesOfCode,0);
بد
if(fsm.state==="fetching"&&isEmpty(listNode)){// ...}
خوب
functionshouldShowSpinner(fsm,listNode){returnfsm.state==="fetching"&&isEmpty(listNode);}if(shouldShowSpinner(fsmInstance,listNodeInstance)){// ...}
بد
functionisDOMNodeNotPresent(node){// ...}if(!isDOMNodeNotPresent(node)){// ...}
خوب
functionisDOMNodePresent(node){// ...}if(isDOMNodePresent(node)){// ...}
کار غیر ممکنی به نظر می رسد.در حالی که اولین بار که این را می شنویم ، بیشتر مردم می گویند : "من چطور باید با دستور شرطیif
کار کنم؟" در جواب باید گفت شما می توانید از polymorphism در اکثر موارد برای انجام این کار استفاده کنید؟ سوال دوم معمولا این است که "این که عالیه ولی من چرا باید بخواهم که با آن کار کنم؟" جواب در مفهوم کد تمیزی هست که اخیرا یاد گرفتیم : اینکه یک تابع باید فقط یک کار انجام دهد وقتی شما کلاس ها یا توابعی کهif
دارند را دارید، به کاربرتان می گویید که تابع شما بیش از یک کار انجام می دهد. فراموش نکنید که با تابع فقط یک کار انجام دهید.
بد
classAirplane{// ...getCruisingAltitude(){switch(this.type){case"777":returnthis.getMaxAltitude()-this.getPassengerCount();case"Air Force One":returnthis.getMaxAltitude();case"Cessna":returnthis.getMaxAltitude()-this.getFuelExpenditure();}}}
خوب
classAirplane{// ...}classBoeing777extendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude()-this.getPassengerCount();}}classAirForceOneextendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude();}}classCessnaextendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude()-this.getFuelExpenditure();}}
جاوااسکریپت، زیان برنامه نویسی است که در type نداریم، به این معنی که تابع شما می تواند هر نوع داده ای را به عنوان آرگومان بگیرد. گاهی اوقات شما از این آزادی ضربه می خورید و مایلید که از type-checking در تابع خود استفاده کنید. راه های زیادی برای خودداری از این کار هست که اولینش API های سازگار هستند.
بد
functiontravelToTexas(vehicle){if(vehicleinstanceofBicycle){vehicle.pedal(this.currentLocation,newLocation("texas"));}elseif(vehicleinstanceofCar){vehicle.drive(this.currentLocation,newLocation("texas"));}}
خوب
functiontravelToTexas(vehicle){vehicle.move(this.currentLocation,newLocation("texas"));}
اگر شما با مقادیر primitive پایه مثل string ها و integer ها و شما نمی توانیداز polymorphism استفاده کنید و نیاز به type-checking را احساس می کنید، باید از تایپ اسکریپت استفاده کنید. یک جایگزین عالی برای جاوااسکریپت معمولی است که برای شما typing را به صورت پیش فرض بر اساس سینتکس جاوااسکریپت اصلی مهیا کرده است. مشکلی که type-checking دستی در جاوااسکریپت دارد این هست که برای انجام درست آن به توابع و کدهای اضافی احتیاج دارد به طوری که type-safety ساختگی شما خوانایی از دست رفته را جبران نمی کند. کدهای جاوا اسکریپت خود را تمیز نگه دارید، تست های خوبی بنویسید و به خوبی بازنگری کنید. در غیر این صورت با TypeScript که یک گزینه ی خوب می باشد همه ی این کارها را انجام دهید.
بد
functioncombine(val1,val2){if((typeofval1==="number"&&typeofval2==="number")||(typeofval1==="string"&&typeofval2==="string")){returnval1+val2;}thrownewError("Must be of type String or Number");}
خوب
functioncombine(val1,val2){returnval1+val2;}
مرورگرهای مدرن بهینه سازی زیادی در بطن خود دارند که در زمان اجرا انجام می دهند. بیشتر اوقات ، بهینه سازی شما چیزی جز اتلاف وقت نیست.منابع خوبیبرای بهینه سازی در وجود دارد که از آنها در زمان درست استفاده کنید
بد
// On old browsers, each iteration with uncached `list.length` would be costly// because of `list.length` recomputation. In modern browsers, this is optimized.for(leti=0,len=list.length;i<len;i++){// ...}
خوب
for(leti=0;i<list.length;i++){// ...}
کد مرده به اندازه کد تکراری بد است. دلیلی برای نگهداری آن در کدهایتان ندارید. اگر فراخوانی نمی شود، از شر آن خلاص شوید! کماکان در تارخچه ورژن های شما وجود خواهد داشت و اگر نیاز داشتید براحتی به آن دسترسی خواهید داشت.
بد
functionoldRequestModule(url){// ...}functionnewRequestModule(url){// ...}constreq=newRequestModule;inventoryTracker("apples",req,"www.inventory-awesome.io");
خوب
functionnewRequestModule(url){// ...}constreq=newRequestModule;inventoryTracker("apples",req,"www.inventory-awesome.io");
استفاده از getters و setters برای دسترسی به داده های داخل آبجکت می تواند بهتر از دسترسی مستقیم به اعضای آن آبجکت باشد. شاید بپرسید چرا؟ در اینجا به لیستی از دلایل اشاره می کنیم:
زمانی که می خواهید کارهایی فراتر از به دست آوردن یک property در آبجکت را دارید، نیازی نیست به جستجو و تغییر دسترسی نیست.
ساده سازی اضافه کردن اعتبارسنجی در زمان
set
کردن.ارائه های داخلی را کپسوله سازی می کند.
به آسانی error handling و logging اضافه کنید.
در زمان دریافت اطلاعات از سرور می توانید به property های آبجکت خود ویژگی lazy loading اضافه کنید
بد
functionmakeBankAccount(){// ...return{balance:0// ...};}constaccount=makeBankAccount();account.balance=100;
خوب
functionmakeBankAccount(){// this one is privateletbalance=0;// a "getter", made public via the returned object belowfunctiongetBalance(){returnbalance;}// a "setter", made public via the returned object belowfunctionsetBalance(amount){// ... validate before updating the balancebalance=amount;}return{// ... getBalance, setBalance};}constaccount=makeBankAccount();account.setBalance(100);
این ویژگی را می توان از طریق closure ها (برای ES5 و نسخه های پایین تر) ایجاد کرد.
بد
constEmployee=function(name){this.name=name;};Employee.prototype.getName=functiongetName(){returnthis.name;};constemployee=newEmployee("John Doe");console.log(`Employee name:${employee.getName()}`);// Employee name: John Doedeleteemployee.name;console.log(`Employee name:${employee.getName()}`);// Employee name: undefined
خوب
functionmakeEmployee(name){return{getName(){returnname;}};}constemployee=makeEmployee("John Doe");console.log(`Employee name:${employee.getName()}`);// Employee name: John Doedeleteemployee.name;console.log(`Employee name:${employee.getName()}`);// Employee name: John Doe
گرفتن ارث بری خوانا ، ساختار و تعاریف متدها با استفاده از کلاس های ES5 سخت هست .اگر به ارث بری نیاز دارید(توجه داشته باشید که ممکن هست اصلا نیاز نداشته باشید) از کلاس های ES6 استفاده کنید. توابع کوچک را به کلاس ترجیح دهید، اما زمانی که احساس کردید به آبجکت های بزرگتر و پیچیده نیاز دارید از کلاس استفاده کنید.
بد
constAnimal=function(age){if(!(thisinstanceofAnimal)){thrownewError("Instantiate Animal with `new`");}this.age=age;};Animal.prototype.move=functionmove(){};constMammal=function(age,furColor){if(!(thisinstanceofMammal)){thrownewError("Instantiate Mammal with `new`");}Animal.call(this,age);this.furColor=furColor;};Mammal.prototype=Object.create(Animal.prototype);Mammal.prototype.constructor=Mammal;Mammal.prototype.liveBirth=functionliveBirth(){};constHuman=function(age,furColor,languageSpoken){if(!(thisinstanceofHuman)){thrownewError("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=functionspeak(){};
خوب
classAnimal{constructor(age){this.age=age;}move(){/* ... */}}classMammalextendsAnimal{constructor(age,furColor){super(age);this.furColor=furColor;}liveBirth(){/* ... */}}classHumanextendsMammal{constructor(age,furColor,languageSpoken){super(age,furColor);this.languageSpoken=languageSpoken;}speak(){/* ... */}}
این الگو در جاوااسکریپت بسیار مفید هست و در بسیاری از کتابخانه ها مثل jQuery و Lodash دیده می شود.این کار به شما اجازه می دهد تا کد خوانا با حجم کمتری داشته باشید. به این دلیل است که می گوییم، از chain کردن توابع استفاده کنید و و دریابید که کد های شما چقدر تمیز خواهند بود. در توابع کلاس خود به راحتی دستورthis
را می توانید پایان هر تابع بگذارید و متدهای کلاس بیشتری را می توانید chain کنید.
بد
classCar{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);}}constcar=newCar("Ford","F-150","red");car.setColor("pink");car.save();
خوب
classCar{constructor(make,model,color){this.make=make;this.model=model;this.color=color;}setMake(make){this.make=make;// NOTE: Returning this for chainingreturnthis;}setModel(model){this.model=model;// NOTE: Returning this for chainingreturnthis;}setColor(color){this.color=color;// NOTE: Returning this for chainingreturnthis;}save(){console.log(this.make,this.model,this.color);// NOTE: Returning this for chainingreturnthis;}}constcar=newCar("Ford","F-150","red").setColor("pink").save();
همانطور که درDesign Patterns توسط Gang of Four مطرح شد، در صورت امکان باید ترکیب کردن را به ارث بری ترجیح دهید. دلایل زیاد خوبی هم برای استفاده از ارث بری و هم برای ترکیب کردن وجود دارد . دلیل اصلی برای این کار این هست که اگر ذهنتان به طور غریزی به سمت ارث بری رفت ، به این فکر کنید که آیا ترکیب کردن می تواند بهتر مشکل شما را مدل سازی کند.در برخی موارد این کار شدنی است.
شاید به این فکر بیافتید که "کی باید از ارث بری استفاده کنم؟" این سوال به این بستگی دارد که با چه مشکلی در حال حاضر روبرو هستید .در اینجا به لیست خوبی از موقعیت هایی اشاره شده که در آن ارث بری نسبت به ترکیب کردن معقولانه تر است:
ارث بری شما به یک رابطه ی "is-a" اشاره دارد و نه یک رابطه ی "has-a" (انسان->حیوان vs. کاربر->جزئیات کاربر)
می وانید از کدها متعلق به کلاس های اصلی استفاده کنید (انسان می تواند مثل همه حیوانات حرکت کند).
زمانی که می خواهید تغییرات کلی در کلاس های وابسته از طریق تغییر در کلاس اصلی ایجاد کنید. (مصرف کالری همه حیوانات را زمانی که حرکت می کنند تغییر دهید).
بد
classEmployee{constructor(name,email){this.name=name;this.email=email;}// ...}// Bad because Employees "have" tax data. EmployeeTaxData is not a type of EmployeeclassEmployeeTaxDataextendsEmployee{constructor(ssn,salary){super();this.ssn=ssn;this.salary=salary;}// ...}
خوب
classEmployeeTaxData{constructor(ssn,salary){this.ssn=ssn;this.salary=salary;}// ...}classEmployee{constructor(name,email){this.name=name;this.email=email;}setTaxData(ssn,salary){this.taxData=newEmployeeTaxData(ssn,salary);}// ...}
همانطور که در Clean Code مطرح شد، "هرگز نباید بیش از یک دلیل برای یک کلاس در تغییر کردن باشد". پر کردن یک کلاس با تعداد زیادی از عملکرد وسوسه بر انگیز هست، مثل زمانی که شما فقط یک چمدان برای پرواز با هواپیما برمیدارد. مشکل این موضوع، این هست که کلاس شما مفهوم منسجمی نخواهد داشت و دلایل زیادی برای تغییر ایجاد میکند به حداقل رساندن تعداد دفعات تغییر دادن یک کلاس بسیار با اهمیت .است. با اهمیت به این دلیل که اگر عملکرد های بسیار زیادی در یک کلاس باشد و شما یک قسمت کوچک آن را تغییر دهید، فهمیدن اینکه این تغییر چطور بر سایر اجزای مستقل در کد شما تاثیر می گذارد سخت می شود.
بد
classUserSettings{constructor(user){this.user=user;}changeSettings(settings){if(this.verifyCredentials()){// ...}}verifyCredentials(){// ...}}
خوب
classUserAuth{constructor(user){this.user=user;}verifyCredentials(){// ...}}classUserSettings{constructor(user){this.user=user;this.auth=newUserAuth(user);}changeSettings(settings){if(this.auth.verifyCredentials()){// ...}}}
همانطور که توسط Bertrand Meyer بیان شد، "موجودیت های نرم افزار (کلاس ها ، ماژول ها، توابع و غیره) باید برای گسترده شدن باز باشند ولی برای تغییرات بسته باشند." حالا این یعنی چه؟ این اصل اساسا بیان می کند که باید به کاربران این اجازه را بدهید که عملکرد های جدید بدون تغییر کد فعلی اضافه کنند.
بد
classAjaxAdapterextendsAdapter{constructor(){super();this.name="ajaxAdapter";}}classNodeAdapterextendsAdapter{constructor(){super();this.name="nodeAdapter";}}classHttpRequester{constructor(adapter){this.adapter=adapter;}fetch(url){if(this.adapter.name==="ajaxAdapter"){returnmakeAjaxCall(url).then(response=>{// transform response and return});}elseif(this.adapter.name==="nodeAdapter"){returnmakeHttpCall(url).then(response=>{// transform response and return});}}}functionmakeAjaxCall(url){// request and return promise}functionmakeHttpCall(url){// request and return promise}
خوب
classAjaxAdapterextendsAdapter{constructor(){super();this.name="ajaxAdapter";}request(url){// request and return promise}}classNodeAdapterextendsAdapter{constructor(){super();this.name="nodeAdapter";}request(url){// request and return promise}}classHttpRequester{constructor(adapter){this.adapter=adapter;}fetch(url){returnthis.adapter.request(url).then(response=>{// transform response and return});}}
این یک اصطلاح ترسناک برای یک مفهوم ساده است.به صورت رسمی اینگونه تعریف می شود که، "اگر S یک subtype برای T باشد، آن وقت آبجکت های نوع T ممکن است جایگزین آبجکت های نوع S شوند بدون اینکه تغییری در property های آن برنامه ایجاد کنند (اصلاحیه، تسک اجرا شده است). که این خودش تعریف ترسناکتری است."
بهترین توضیحش این است که اگر یک کلاس اصلی دارید و یک کلاس فرزند ، کلاس اصلی و کلاس فرزند می توانند بدون خروجی و نتایج اشتباه بجای هم استفاده شوند. شاید هنوز گیج کننده باشد، بذارید به مثال کلاسیک مربع-مستطیل نگاهی بیاندازیم. از دیدگاه ریاضی ، مربع یک مستطیل است، اما اگر شما آن را با استفاده از رابطه ی "is-a" با ارث بری مدلسازی کنید، شما سریعا به مشکل بر می خورید.
بد
classRectangle{constructor(){this.width=0;this.height=0;}setColor(color){// ...}render(area){// ...}setWidth(width){this.width=width;}setHeight(height){this.height=height;}getArea(){returnthis.width*this.height;}}classSquareextendsRectangle{setWidth(width){this.width=width;this.height=width;}setHeight(height){this.width=height;this.height=height;}}functionrenderLargeRectangles(rectangles){rectangles.forEach(rectangle=>{rectangle.setWidth(4);rectangle.setHeight(5);constarea=rectangle.getArea();// بد Returns 25 for Square. Should be 20.rectangle.render(area);});}constrectangles=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles(rectangles);
خوب
classShape{setColor(color){// ...}render(area){// ...}}classRectangleextendsShape{constructor(width,height){super();this.width=width;this.height=height;}getArea(){returnthis.width*this.height;}}classSquareextendsShape{constructor(length){super();this.length=length;}getArea(){returnthis.length*this.length;}}functionrenderLargeShapes(shapes){shapes.forEach(shape=>{constarea=shape.getArea();shape.render(area);});}constshapes=[newRectangle(4,5),newRectangle(4,5),newSquare(5)];renderLargeShapes(shapes);
جاوااسکریپت Interfaces ندارد، پس این اصل به طور قطع مانند دیگر اصول صدق نمی کند. با این حال این اصل مهم و مرتبط است حتی با وجود کمبود سیستم type در جاوااسکریپت.
ISP بیان می کند که "کلاینت ها نباید به وابستگی به interface هایی که استفاده نمی کنند مجبور شوند". Interface ها بدلیل duck typing ها قراردادهای ضمنی در جاوااسکریپت هستند.
یک مثال خوب که این اصل را در جاوااسکریپت نمایش می دهد، برای کلاس هایی است که تنظیمات بزرگی برای آبجکت ها نیاز دارند. نیاز نداشتن به کلاینت ها برای تنظیم گزینه های زیاد با منفعت است، چون اکثر اوقات آنها به همه تنظیمات نیاز پیدا نمی کنند. دلخواه کردن آنها، از داشتن یک "fat interface" جلو گیری می کند.
بد
classDOMTraverser{constructor(settings){this.settings=settings;this.setup();}setup(){this.rootNode=this.settings.rootNode;this.settings.animationModule.setup();}traverse(){// ...}}const$=newDOMTraverser({rootNode:document.getElementsByTagName("body"),animationModule(){}// Most of the time, we won't need to animate when traversing.// ...});
خوب
classDOMTraverser{constructor(settings){this.settings=settings;this.options=settings.options;this.setup();}setup(){this.rootNode=this.settings.rootNode;this.setupOptions();}setupOptions(){if(this.options.animationModule){// ...}}traverse(){// ...}}const$=newDOMTraverser({rootNode:document.getElementsByTagName("body"),options:{animationModule(){}}});
این اصل دو چیز اساسی را بیان می کند:
- ماژول های سطح بالا نباید به ماژول های سطح پایین وابسته باشند، هر دو باید به Abstraction ها بستگی داشته باشند.
- Abstraction ها نباید به جزئیات بستگی داشته باشد. جزئیات باید به Abstraction ها بستگی داشته باشد.
این موضوع در نگاه اول ممکن است سخت بنظر برسد، اما اگر شما با AngularJS کار کرده باشید، پیاده سازی این اصل در قالب DependencyInjection (DI) را دیده اید. درحالی که آنها مفاهیم یکسانی نیستند ، DIP ماژول های سطح بالا را از مطلع شدن از جزئیات ماژول های سطح پایین مخفی نگه می دارد. این کار می تواند بوسیله DI محقق شود. بزرگترین منفعت این کار کاهش coupling بین ماژول هاست. coupling یک الگوی بد برای توسعه هست، چون قابلیت تغییر کدها را سخت می کند.
همانطور که بیان شد، جاوااسکریپت interfaces ندارد، پس abstraction هایی که به آن وابسته هستند ، قراردادهای ضمنی هستند. گفتنی است که متدها و property ها که یک آبجکت یا کلاس را در معرض آبجکت یا کلاسی دیگر قرار می دهند. در مثال زیر، قرارداد ضمنی چیزی است که هر ماژول درخواستی برای یInventoryTracker
یک متدrequestItems
خواهد داشت.
بد
classInventoryRequester{constructor(){this.REQ_METHODS=["HTTP"];}requestItem(item){// ...}}classInventoryTracker{constructor(items){this.items=items;// بد We have created a dependency on a specific request implementation.// We should just have requestItems depend on a request method: `request`this.requester=newInventoryRequester();}requestItems(){this.items.forEach(item=>{this.requester.requestItem(item);});}}constinventoryTracker=newInventoryTracker(["apples","bananas"]);inventoryTracker.requestItems();
خوب
classInventoryTracker{constructor(items,requester){this.items=items;this.requester=requester;}requestItems(){this.items.forEach(item=>{this.requester.requestItem(item);});}}classInventoryRequesterV1{constructor(){this.REQ_METHODS=["HTTP"];}requestItem(item){// ...}}classInventoryRequesterV2{constructor(){this.REQ_METHODS=["WS"];}requestItem(item){// ...}}// By constructing our dependencies externally and injecting them, we can easily// substitute our request module for a fancy new one that uses WebSockets.constinventoryTracker=newInventoryTracker(["apples","bananas"],newInventoryRequesterV2());inventoryTracker.requestItems();
تست کد بسیار مهمتر از انتقال کد می باشد. زمانی که شما هیچ تستی ندارید یا به اندازه کافی تست ندارید، در هر بار انتقال کد به سرور نمی توانید مطمئن شوید که کد شما break نمی شود تصمیم گیری اینکه برا چه اساسی میزان تست نوشته شده کافی است به تیم شما بستگی دارد و ولی پوشش 100% (تمام statement ها و branche ها ) که اعتماد بالا و آرامش خاطر توسعه دهندگان را به همراه دارد این به این معنی است که شما علاوه بر داشتن یک فریمورک تست خوب نیاز به یکابزار پوششی خوب هم دارید .
هیچ عذر برای ننوشتن تست ها نیست.تعداد زیادی فریمورک تست نویسی برای جاوااسکریپت وجود دارد، یکی از آنهایی که تیم شما ترجیح می دهد را پیدا کنید. وقتی شما فریمورک تستی را پیدا می کنید که در تیم شما کارایی دارد ،هدف گذاری کنید که برای هر feature یا module جدید که معرفی می کنید تست نوشته شود. اگر متد ترجیح داده توسط شما روش Test Driven Development یا (TDD) می باشد ، بسیار عالی است ، اما هدف اصلی اطمینان حاصل کردن از رسیدن و پوشش اهداف تعیین شده قبل از ارائه هر feature و یا تغییر دادن feature های موجود می باشد.
بد
importassertfrom"assert";describe("MomentJS",()=>{it("handles date boundaries",()=>{letdate;date=newMomentJS("1/1/2015");date.addDays(30);assert.equal("1/31/2015",date);date=newMomentJS("2/1/2016");date.addDays(28);assert.equal("02/29/2016",date);date=newMomentJS("2/1/2015");date.addDays(28);assert.equal("03/01/2015",date);});});
خوب
importassertfrom"assert";describe("MomentJS",()=>{it("handles 30-day months",()=>{constdate=newMomentJS("1/1/2015");date.addDays(30);assert.equal("1/31/2015",date);});it("handles leap year",()=>{constdate=newMomentJS("2/1/2016");date.addDays(28);assert.equal("02/29/2016",date);});it("handles non-leap year",()=>{constdate=newMomentJS("2/1/2015");date.addDays(28);assert.equal("03/01/2015",date);});});
callback ها تمیز نیستند، و باعث ایجاد تو در تویی بیش از حد می شود.در ES2015/ES6 ویژگی Promise به عنوان global type از پیش ساخته شده می باشد، از آنها استفاده کنید!
بد
import{get}from"request";import{writeFile}from"fs";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin",(requestErr,response,body)=>{if(requestErr){console.error(requestErr);}else{writeFile("article.html",body,writeErr=>{if(writeErr){console.error(writeErr);}else{console.log("File written");}});}});
خوب
import{get}from"request-promise";import{writeFile}from"fs-extra";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin").then(body=>{returnwriteFile("article.html",body);}).then(()=>{console.log("File written");}).catch(err=>{console.error(err);});
Promise جایگزین های تمیزی برای callback ها هستندو ولی در ES2017/ES8 ویژگی Async / Await اضافه شد که می تواند راه حل تمیزتری را پیشنهاد کند. تمام چیزی که نیاز دارید یک تابع با کلمه کلیدی به عنوان پیشوندasync
می باشد و سپس می توانید منطق کد خودرا بدون زنجیرهthen
بنویسید. اگر می توانید همین امروز از منفعت ویژگی های ES2017/ES8 بهره مند شوید.
بد
import{get}from"request-promise";import{writeFile}from"fs-extra";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin").then(body=>{returnwriteFile("article.html",body);}).then(()=>{console.log("File written");}).catch(err=>{console.error(err);});
خوب
import{get}from"request-promise";import{writeFile}from"fs-extra";asyncfunctiongetCleanCodeArticle(){try{constbody=awaitget("https://en.wikipedia.org/wiki/Robert_Cecil_Martin");awaitwriteFile("article.html",body);console.log("File written");}catch(err){console.error(err);}}getCleanCodeArticle()
خطاهای ایجاد شده چیز خوبی هستند! آنها به این معنی هستند که در زمان اجرا مشکلی در برنامه بوجود آمده را با موفقیت شناسایی کرده است و با متوقف کردن اجرای تابع و از بین بردن process در Node ، شمارا در جریان می گذارد و با stack trace در کنسول به شما خبر می دهد.
از کنار خطا هایی که با catch گرفته شدند گذشتن به شما توانایی بطرف کردن یا واکنش به ارور ایجاد شده را نمی دهد. ورود خطا به کنسول (console.log) خیلی خوب نیست، زیرا اغلب اوقات ممکن است در دریایی از چیزهای چاپ شده در کنسول گم شود. اگر هر بیتی از کد را در try / catch قرار دهید، به این معنی است که فکر می کنید ممکن است در آنجا خطایی رخ دهد و بنابراین باید برنامه ای تنظیم کنید یا یک مسیر کد برای زمان بروز آن ایجاد کنید.
بد
try{functionThatMightThrow();}catch(error){console.log(error);}
خوب
try{functionThatMightThrow();}catch(error){// One option (more noisy than console.log):console.error(error);// Another option:notifyUserOfError(error);// Another option:reportErrorToService(error);// OR do all three!}
به همین دلیل نباید از ارورهای گرفته شده با try / catch چشم پوشی کنید.
بد
getdata().then(data=>{functionThatMightThrow(data);}).catch(error=>{console.log(error);});
خوب
getdata().then(data=>{functionThatMightThrow(data);}).catch(error=>{// One option (more noisy than console.log):console.error(error);// Another option:notifyUserOfError(error);// Another option:reportErrorToService(error);// OR do all three!});
قالب بندی یک عمل ذهنی است. مانند بسیاری از قوانین در اینجا، هیچ قانون سخت و سریعی وجود ندارد که شما باید از آن پیروی کنید. نکته اصلی این است که در مورد قالب بندی بحث نکنید.ابزارهای زیادی برای خودکار کردن این کار وجود دارد. یکی از آن ها را استفاده کنید! بحث و جدال مهندسان درباره قالب بندی، اتلاف وقت و هزینه است.
برای مواردی که تحت عنوان قالب بندی خودکار قرار نمی گیرند. (تورفتگی، tabها در مقابل spaceها، ” یا ‘ و …)
جاوااسکریپت type ندارد، پس با حروف بزرگ نوشتن به شما چیزهای زیادی در مورد متغیرها و توابع و غیر می گوید. این قوانین ذهنی هستند، پس تیم شما می تواند هر آنچه را که می خواهد انتخاب کند. مهم نیست که همه شما چه چیزی را انتخاب می کنید، فقط باید در این موضوع ثابت قدم باشید.
بد
constDAYS_IN_WEEK=7;constdaysInMonth=30;constsongs=["Back In Black","Stairway to Heaven","Hey Jude"];constArtists=["ACDC","Led Zeppelin","The Beatles"];functioneraseDatabase(){}functionrestore_database(){}classanimal{}classAlpaca{}
خوب
constDAYS_IN_WEEK=7;constDAYS_IN_MONTH=30;constSONGS=["Back In Black","Stairway to Heaven","Hey Jude"];constARTISTS=["ACDC","Led Zeppelin","The Beatles"];functioneraseDatabase(){}functionrestoreDatabase(){}classAnimal{}classAlpaca{}
وقتی یک تابع تابع دیگری را صدا می زند، آن دو تابع را به صورت عمودی درون فایل سورس نزدیک هم نگه دارید. به طور ایده آل ، تابع caller را بالای تابع callee نگه دارید.ما بیشتر مایلیم کد را از بالا به پایین بخوانیم مثل یک روزنامه . بخاطر این موضوع، کد شما هم همانطور خوانده می شود.
بد
classPerformanceReview{constructor(employee){this.employee=employee;}lookupPeers(){returndb.lookup(this.employee,"peers");}lookupManager(){returndb.lookup(this.employee,"manager");}getPeerReviews(){constpeers=this.lookupPeers();// ...}perfReview(){this.getPeerReviews();this.getManagerReview();this.getSelfReview();}getManagerReview(){constmanager=this.lookupManager();}getSelfReview(){// ...}}constreview=newPerformanceReview(employee);review.perfReview();
خوب
classPerformanceReview{constructor(employee){this.employee=employee;}perfReview(){this.getPeerReviews();this.getManagerReview();this.getSelfReview();}getPeerReviews(){constpeers=this.lookupPeers();// ...}lookupPeers(){returndb.lookup(this.employee,"peers");}getManagerReview(){constmanager=this.lookupManager();}lookupManager(){returndb.lookup(this.employee,"manager");}getSelfReview(){// ...}}constreview=newPerformanceReview(employee);review.perfReview();
کامنت ها یک ضرورت نیستند. یک کد خوب در اکثر مواقع خودش را داکیومنت می کند و نیازی به کامنت ندارد.
بد
functionhashIt(data){// The hashlethash=0;// Length of stringconstlength=data.length;// Loop through every character in datafor(leti=0;i<length;i++){// Get character code.constchar=data.charCodeAt(i);// Make the hashhash=(hash<<5)-hash+char;// Convert to 32-bit integerhash&=hash;}}
خوب
functionhashIt(data){lethash=0;constlength=data.length;for(leti=0;i<length;i++){constchar=data.charCodeAt(i);hash=(hash<<5)-hash+char;// Convert to 32-bit integerhash&=hash;}}
ورژن کنترل به این دلیل وجود دارد. کدهای قدیمی را از تاریخچه دنبال کنید.
بد
doStuff();// doOtherStuff();// doSomeMoreStuff();// doSoMuchStuff();
خوب
doStuff();
به یاد داشته باشید که از ورژن کنترل استفاده کنید! نیازی به کد مرده ، کدهای کامنت شده و کامنت های ژورنالی نیست. ازgit log
برای اطلاع از تاریخچه استفاده کنید.
بد
/** * 2016-12-20: Removed monads, didn't understand them (RM) * 2016-10-01: Improved using special monads (JP) * 2016-02-03: Removed type-checking (LI) * 2015-03-14: Added combine with type-checking (JR) */functioncombine(a,b){returna+b;}
خوب
functioncombine(a,b){returna+b;}
آنها معمولا فقط کدمان را شلوغ می کنند. اجازه دهید که نام توابع و متغیرها دارای تورفتگی و فرمت مناسب باشند تا ساختار دیداری مناسبی را در کد شما ایجاد کنند.
بد
////////////////////////////////////////////////////////////////////////////////// Scope Model Instantiation////////////////////////////////////////////////////////////////////////////////$scope.model={menu:"foo",nav:"bar"};////////////////////////////////////////////////////////////////////////////////// Action setup////////////////////////////////////////////////////////////////////////////////constactions=function(){// ...};
خوب
$scope.model={menu:"foo",nav:"bar"};constactions=function(){// ...};
Armenian:hanumanum/clean-code-javascript/
Bangla(বাংলা):InsomniacSabbir/clean-code-javascript/
Brazilian Portuguese:fesnt/clean-code-javascript
Traditional Chinese:AllJointTW/clean-code-javascript
Indonesia:andirkh/clean-code-javascript/
Japanese:mitsuruog/clean-code-javascript/
Spanish:tureey/clean-code-javascript
Turkish:bsonmez/clean-code-javascript
Ukrainian:mindfr1k/clean-code-javascript-ua
Vietnamese:hienvd/clean-code-javascript/
Persian:hamettio/clean-code-javascript
About
🛁 کدنویسی تمیز در جاوا اسکریپت
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Languages
- JavaScript100.0%