在编程世界中,代码不仅仅是让事情正常运转。 它就像一件讲述故事的艺术品。 当代码干净时,它就像一个美丽的、精心制作的雕塑,既美观又运行良好。
但在急于按期完成任务的过程中,有时团队不会太注意保持代码的整洁。 这可能会导致项目变得混乱、复杂,变得更加难以开展。 随着情况变得更糟,生产力也会下降。 然后,公司需要引进更多的人来提供帮助,这使得一切都变得更加昂贵。
那么,干净的代码是什么样的呢? 它的代码易于理解,没有多余的部分,简单,并且可以通过测试。 换句话说,它是可读的、可重用的,并且在需要时易于更改。
为了帮助你编写出色的 JavaScript 代码,我将在今天的内容中与你分享 26 个写干净代码的技巧,这些技巧将指导你编写既优雅又高效的代码。
// Badconst yyyymmdstr = moment().format("YYYY/MM/DD");// Goodconst currentDate = moment().format("YYYY/MM/DD");
// BadgetUserInfo();getClientData();getCustomerRecord();// GoodgetUser();
我们将阅读比我们编写的更多的代码,我们编写的代码可读且可搜索,这一点很重要。
// Bad// What the heck is 86400000 for?setTimeout(blastOff, 86400000); // Good// Declare them as capitalized named constants.const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;setTimeout(blastOff, MILLISECONDS_PER_DAY);
// Badconst address = "One Infinite Loop, Cupertino 95014";const cityZipCodeRegex = /^[^,//]+[,///s]+(.+?)/s*(/d{5})?$/;saveCityZipCode( address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]);// Goodconst address = "One Infinite Loop, Cupertino 95014";const cityZipCodeRegex = /^[^,//]+[,///s]+(.+?)/s*(/d{5})?$/;const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];saveCityZipCode(city, zipCode);
显式的比隐式的好。
// Badconst locations = ["Austin", "New York", "San Francisco"];locations.forEach(l => { doStuff(); doSomeOtherStuff(); // ... // ... // ... // Wait, what is `l` for again? dispatch(l);});// Goodconst locations = ["Austin", "New York", "San Francisco"];locations.forEach(location => { doStuff(); doSomeOtherStuff(); // ... // ... // ... dispatch(location);});
如果您的类/对象名称告诉您一些信息,请不要在变量名称中重复该信息。
// Badconst Car = { carMake: "Honda", carModel: "Accord", carColor: "Blue"};function paintCar(car, color) { car.carColor = color;}// Goodconst Car = { make: "Honda", model: "Accord", color: "Blue"};function paintCar(car, color) { car.color = color;}
默认参数通常比短路更清晰。 请注意,如果您使用它们,您的函数将只为未定义的参数提供默认值。 其他“假”值(例如 ''、""、false、null、0 和 NaN)不会被默认值替换。
// Badfunction createMicrobrewery(name) { const breweryName = name || "Hipster Brew Co."; // ...}// Goodfunction createMicrobrewery(name = "Hipster Brew Co.") { // ...}
限制函数参数的数量非常重要,因为它使测试函数变得更加容易。 超过三个会导致组合爆炸,您必须使用每个单独的参数来测试大量不同的情况。
// Badfunction createMenu(title, body, buttonText, cancellable) { // ...}createMenu("Foo", "Bar", "Baz", true);//Goodfunction createMenu({ title, body, buttonText, cancellable }) { // ...}createMenu({ title: "Foo", body: "Bar", buttonText: "Baz", cancellable: true});
这是迄今为止软件工程中最重要的规则。 当函数做不止一件事时,它们就更难编写、测试和推理。 当您可以将一个函数隔离为一个操作时,就可以轻松重构它,并且您的代码读起来会更清晰。 如果您除了本指南之外没有任何其他内容,您将领先于许多开发人员。
// Badfunction emailClients(clients) { clients.forEach(client => { const clientRecord = database.lookup(client); if (clientRecord.isActive()) { email(client); } });}// Goodfunction emailActiveClients(clients) { clients.filter(isActiveClient).forEach(email);}function isActiveClient(client) { const clientRecord = database.lookup(client); return clientRecord.isActive();}
// Badfunction addToDate(date, month) { // ...}const date = new Date();// It's hard to tell from the function name what is addedaddToDate(date, 1);// Goodfunction addMonthToDate(month, date) { // ...}const date = new Date();addMonthToDate(1, date);
当你有多个抽象级别时,你的函数通常会做太多事情。 拆分功能可以实现可重用性和更容易的测试。
// Badfunction parseBetterJSAlternative(code) { const REGEXES = [ // ... ]; const statements = code.split(" "); const tokens = []; REGEXES.forEach(REGEX => { statements.forEach(statement => { // ... }); }); const ast = []; tokens.forEach(token => { // lex... }); ast.forEach(node => { // parse... });}// Goodfunction parseBetterJSAlternative(code) { const tokens = tokenize(code); const syntaxTree = parse(tokens); syntaxTree.forEach(node => { // parse... });}function tokenize(code) { const REGEXES = [ // ... ]; const statements = code.split(" "); const tokens = []; REGEXES.forEach(REGEX => { statements.forEach(statement => { tokens.push(/* ... */); }); }); return tokens;}function parse(tokens) { const syntaxTree = []; tokens.forEach(token => { syntaxTree.push(/* ... */); }); return syntaxTree;}
尽最大努力避免重复代码。 重复的代码是不好的,因为这意味着如果您需要更改某些逻辑,则需要在多个地方进行更改。
// Badfunction showDeveloperList(developers) { developers.forEach(developer => { const expectedSalary = developer.calculateExpectedSalary(); const experience = developer.getExperience(); const githubLink = developer.getGithubLink(); const data = { expectedSalary, experience, githubLink }; render(data); });}function showManagerList(managers) { managers.forEach(manager => { const expectedSalary = manager.calculateExpectedSalary(); const experience = manager.getExperience(); const portfolio = manager.getMBAProjects(); const data = { expectedSalary, experience, portfolio }; render(data); });}// Goodfunction showEmployeeList(employees) { employees.forEach(employee => { const expectedSalary = employee.calculateExpectedSalary(); const experience = employee.getExperience(); const data = { expectedSalary, experience }; switch (employee.type) { case "manager": data.portfolio = employee.getMBAProjects(); break; case "developer": data.githubLink = employee.getGithubLink(); break; } render(data); });}
// Badconst menuConfig = { title: null, body: "Bar", buttonText: null, cancellable: true};function createMenu(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);// Goodconst menuConfig = { title: "Order", // User did not include 'body' key buttonText: "Send", cancellable: true};function createMenu(config) { let finalConfig = Object.assign( { title: "Foo", body: "Bar", buttonText: "Baz", cancellable: true }, config ); return finalConfig // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} // ...}createMenu(menuConfig);
标志告诉你的用户这个函数不止做一件事。 函数应该做一件事。 如果函数遵循基于布尔值的不同代码路径,则拆分它们。
// Badfunction createFile(name, temp) { if (temp) { fs.create(`./temp/${name}`); } else { fs.create(name); }}// Goodfunction createFile(name) { fs.create(name);}function createTempFile(name) { createFile(`./temp/${name}`);}
在 JavaScript 中污染全局变量是一种不好的做法,因为你可能会与另一个库发生冲突,并且 API 的用户在生产中遇到异常之前不会意识到这一点。
// BadArray.prototype.diff = function diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem));};// Goodclass SuperArray extends Array { diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem)); }}
JavaScript 不像 Haskell 那样是一种函数式语言,但它具有函数式风格。 函数式语言可以更简洁、更容易测试。 尽可能喜欢这种编程风格。
// Badconst programmerOutput = [ { name: "Uncle Bobby", linesOfCode: 500 }, { name: "Suzie Q", linesOfCode: 1500 }, { name: "Jimmy Gosling", linesOfCode: 150 }, { name: "Gracie Hopper", linesOfCode: 1000 }];let totalOutput = 0;for (let i = 0; i < programmerOutput.length; i++) { totalOutput += programmerOutput[i].linesOfCode;}// Goodconst programmerOutput = [ { name: "Uncle Bobby", linesOfCode: 500 }, { name: "Suzie Q", linesOfCode: 1500 }, { name: "Jimmy Gosling", linesOfCode: 150 }, { name: "Gracie Hopper", linesOfCode: 1000 }];const totalOutput = programmerOutput.reduce( (totalLines, output) => totalLines + output.linesOfCode, 0);
// Badif (fsm.state === "fetching" && isEmpty(listNode)) { // ...}// Goodfunction shouldShowSpinner(fsm, listNode) { return fsm.state === "fetching" && isEmpty(listNode);}if (shouldShowSpinner(fsmInstance, listNodeInstance)) { // ...}
// Badfunction isDOMNodeNotPresent(node) { // ...}if (!isDOMNodeNotPresent(node)) { // ...}// Goodfunction isDOMNodePresent(node) { // ...}if (isDOMNodePresent(node)) { // ...}
回调不干净,并且会导致过多的嵌套。 在 ES2015/ES6 中,Promise 是内置的全局类型。 使用它们!
// Badimport { 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"); } }); } });// Goodimport { get } from "request-promise";import { writeFile } from "fs-extra";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin") .then(body => { return writeFile("article.html", body); }) .then(() => { console.log("File written"); }) .catch(err => { console.error(err); });
Promise 是回调的一个非常干净的替代方案,但 ES2017/ES8 带来了 async 和 wait,它提供了更干净的解决方案。
您所需要的只是一个以 async 关键字为前缀的函数,然后您可以命令式地编写逻辑,而无需 then 函数链。
// Badimport { get } from "request-promise";import { writeFile } from "fs-extra";get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin") .then(body => { return writeFile("article.html", body); }) .then(() => { console.log("File written"); }) .catch(err => { console.error(err); });// Goodimport { get } from "request-promise";import { writeFile } from "fs-extra";async function getCleanCodeArticle() { try { const body = await get( "https://en.wikipedia.org/wiki/Robert_Cecil_Martin" ); await writeFile("article.html", body); console.log("File written"); } catch (err) { console.error(err); }}getCleanCodeArticle()
抛出错误是一件好事! 它们意味着运行时已成功识别出程序中的某些问题,并且它会通过停止当前堆栈上的函数执行、终止进程(在 Node 中)并通过堆栈跟踪在控制台中通知您来通知您。
对捕获的错误不采取任何措施并不能让您有能力修复或对所述错误做出反应。 将错误记录到控制台 (console.log) 也好不了多少,因为它常常会迷失在打印到控制台的大量内容中。
如果您将任何代码包装在 try/catch 中,则意味着您认为那里可能会发生错误,因此您应该为错误发生时制定计划或创建代码路径。
// Badtry { functionThatMightThrow();} catch (error) { console.log(error);}// Goodtry { 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 中捕获的错误。
// Badgetdata() .then(data => { functionThatMightThrow(data); }) .catch(error => { console.log(error); });// Goodgetdata() .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! });
评论是道歉,而不是要求。 好的代码主要是文档本身。
// Badfunction hashIt(data) { // The hash let hash = 0; // Length of string const length = data.length; // Loop through every character in data for (let i = 0; i < length; i++) { // Get character code. const char = data.charCodeAt(i); // Make the hash hash = (hash << 5) - hash + char; // Convert to 32-bit integer hash &= hash; }}// Goodfunction hashIt(data) { let hash = 0; const length = data.length; for (let i = 0; i < length; i++) { const char = data.charCodeAt(i); hash = (hash << 5) - hash + char; // Convert to 32-bit integer hash &= hash; }}
版本控制的存在是有原因的。 将旧代码留在您的历史记录中。
// BaddoStuff();// doOtherStuff();// doSomeMoreStuff();// doSoMuchStuff();// GooddoStuff();
请记住,使用版本控制! 不需要死代码、注释代码,尤其是期刊注释。 使用 git log 获取历史记录!
// Bad/** * 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) */function combine(a, b) { return a + b;}// Goodfunction combine(a, b) { return a + b;}
它们通常只是增加噪音。 让函数和变量名称以及正确的缩进和格式为代码提供视觉结构。
// Bad////////////////////////////////////////////////////////////////////////////////// Scope Model Instantiation////////////////////////////////////////////////////////////////////////////////$scope.model = { menu: "foo", nav: "bar"};////////////////////////////////////////////////////////////////////////////////// Action setup////////////////////////////////////////////////////////////////////////////////const actions = function() { // ...};// Good$scope.model = { menu: "foo", nav: "bar"};const actions = function() { // ...};
从一开始就让代码干净并不总是那么容易。 重要的是要记住,没有必要执着于使每条线路都完美,尤其是当您的日程安排很紧时。 只是没有足够的时间来一遍又一遍地重写代码。
相反,专注于在有限的时间内编写最好的代码。 当下一轮更新到来并且您注意到可以改进的内容时,这是进行这些更改的好时机。
每个公司和每个项目都有自己的编码风格,它可能与我分享的技巧不同。 如果您加入一个已经启动并运行的项目,通常最好坚持使用已经存在的编码风格(除非您要重构它)。 这是因为在整个代码中保持一致的风格也是保持代码整洁的一种形式。
请记住,干净的代码是一个旅程,而不是目的地。 这是关于随着时间的推移做出小的改进,而不是陷入完美。 通过应用您所学到的技巧,您将能够编写出更简洁、更高效的 JavaScript 代码,未来的您和其他人都会感谢您。
最后,感谢您的阅读,也期待你的关注,祝编程快乐!
本文链接:http://www.28at.com/showinfo-26-80189-0.html26个写出简洁优雅JavaScript代码的技巧
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 高效运维指南:通过 CLI 管理阿里云 ECS 实例
下一篇: Htmx,它到底是框架还是库?