「为什么代码要整洁?」——代码整洁度对于项目质量的影响,让我们通过这边文章来教你js和ts的代码整洁技巧,让你的项目更出众

这篇具有很好参考价值的文章主要介绍了「为什么代码要整洁?」——代码整洁度对于项目质量的影响,让我们通过这边文章来教你js和ts的代码整洁技巧,让你的项目更出众。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

为什么代码要整洁?

代码质量与整洁度成正比。有的团队在赶工期的时候,不注重代码的整洁,代码写的越来越糟糕,项目越来越混乱,生产力也跟着下降,那就必须找更多人来提高生产力,开发成本越来越高。

整洁的代码是怎样的?

清晰表达意图、消除重复、简单抽象、能通过测试。 换句话说:具有可读性、可重用性和可重构性。

命名

  1. 名副其实:不使用缩写、不使用让人误解的名称,不要让人推测。

// bad: 啥?
const yyyymmdstr = moment().format(YYYY/MM/DD);
// bad: 缩写
const cD = moment().format(YYYY/MM/DD);

// good:
const currentDate = moment().format(YYYY/MM/DD);
const locations = [Austin, New York, San Francisco];
// bad:推测l是locations的项
locations.forEach(l => doSomeThing(l));

// good
locations.forEach(location => doSomeThing(location));
  1. 使用方便搜索的名称:避免硬编码,对数据用常量const记录。

// bad: 86400000指的是?
setTimeout(goToWork, 86400000);

// good: 86400000是一天的毫秒数
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000;
setTimeout(goToWork, MILLISECONDS_PER_DAY);
  1. 类名应该是名词,方法名应该是动词。

// bad
function visble() {}

// good
function getVisble() {}
  1. 多个变量属于同一类型的属性,那就他们整合成一个对象。同时省略多余的上下文。

// bad:可以整合
const carMake = Honda,
const carModel = Accord,
const carColor = Blue,

// bad: 多余上下文
const Car = {
 carMake: Honda,
 carModel: Accord,
 carColor: Blue,
};

// good
const Car = {
 make: Honda,
 model: Accord,
 color: Blue,
};

其他:

  • 不要写多余的废话,比如theMessagethe可以删除。

  • 统一术语。比如通知一词,不要一会在叫notice,一会叫announce

  • 用读得通顺的词语。比如getElementById就比 useIdToGetElement好读。

函数(方法)

  • 删除重复的代码,don't repeat yourself。很多地方可以注意dry,比如偷懒复制了某段代码、try...catch或条件语句写了重复的逻辑。

 // bad
 try {
    doSomeThing();
    clearStack();
 } catch (e) {
    handleError(e);
    clearStack();
 }
 
 // good
 try {
   doSomeThing();
 } catch (e) {
   handleError(e);
 } finally {
   clearStack();
 }
  • 形参不超过三个,对测试函数也方便。多了就使用对象参数。

  • 同时建议使用对象解构语法,有几个好处:

    1. 能清楚看到函数签名有哪些熟悉,

    2. 可以直接重新命名,

    3. 解构自带克隆,防止副作用,

    4. Linter检查到函数未使用的属性。

// bad
function createMenu(title, body, buttonText, cancellable) {}

// good
function createMenu({ title, body, buttonText, cancellable }) {}
  • 函数只做一件事,代码读起来更清晰,函数就能更好地组合、测试、重构。

 // bad: 处理了输入框的change事件,并创建文件的切片,并保存相关信息到localStorage
 function handleInputChange(e) {
   const file = e.target.files[0];
   
   // --- 切片 ---
   const chunkList = [];
   let cur = 0;
   while (cur < file.size) {
     chunkList.push({
     chunk: file.slice(cur, cur + size)
   });
   cur += size;
 }
 // --- 保存信息到localstorage ---
 localStorage.setItem(file, file.name);
 localStorage.setItem(chunkListLength, chunkList.length);
 }
 
 // good: 将三件事分开写,同时自顶而下读,很舒适
 function handleInputChange(e) {
   const file = e.target.files[0];
   const chunkList = createChunk(file);
   saveFileInfoInLocalStorage(file, chunkList);
 }
 
 function createChunk(file, size = SLICE_SIZE) {
   const chunkList = [];
   let cur = 0;
   while (cur < file.size) {
     chunkList.push({
       chunk: file.slice(cur, cur + size)
     });
     cur += size;
   }
   return chunkList
 }
 
 function saveFileInfoInLocalStorage(file, chunkList) {
   localStorage.setItem(file, file.name);
   localStorage.setItem(chunkListLength, chunkList.length);
 }
  • 自顶向下地书写函数,人们都是习惯自顶向下读代码,如,为了执行A,需要执行B,为了执行B,需要执行C。如果把A、B、C混在一个函数就很难读了。(看前一个的例子)。

  • 不使用布尔值来作为参数,遇到这种情况时,一定可以拆分函数。

// bad
function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

// good
function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}
  • 避免副作用。

    • 常见就是陷阱就是对象之间共享了状态,使用了可变的数据类型,比如对象和数组。对于可变的数据类型,使用immutable等库来高效克隆。

    • 避免用可变的全局变量。

    • 副作用的缺点:出现不可预期的异常,比如用户对购物车下单后,网络差而不断重试请求,这时如果添加新商品到购物车,就会导致新增的商品也会到下单的请求中。

    • 集中副作用:遇到不可避免的副作用时候,比如读写文件、上报日志,那就在一个地方集中处理副作用,不要在多个函数和类处理副作用。

    • 其它注意的地方:

// bad:注意到cart是引用类型!
const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};
// good
const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
};
  • 封装复杂的判断条件,提高可读性。

// bad
if (!(obj => obj != null && typeof obj[Symbol.iterator] === 'function')) {
  throw new Error('params is not iterable')
}

// good
const isIterable = obj => obj != null && typeof obj[Symbol.iterator] === 'function';
if (!isIterable(promises)) {
  throw new Error('params is not iterable')
}
  • 在方法中有多条件判断时候,为了提高函数的可扩展性,考虑下是不是可以使用能否使用多态性来解决。

// 地图接口可能来自百度,也可能来自谷歌
const googleMap = {
  show: function (size) {
    console.log('开始渲染谷歌地图', size));
  }
};

const baiduMap = {
  render: function (size) {
    console.log('开始渲染百度地图', size));
  }
};

// bad: 出现多个条件分支。如果要加一个腾讯地图,就又要改动renderMap函数。
function renderMap(type) {
  const size = getSize();
  if (type === 'google') {
    googleMap.show(size);
  } else if (type === 'baidu') {
    baiduMap.render(size);
  }
};
renderMap('google')

// good:实现多态处理。如果要加一个腾讯地图,不需要改动renderMap函数。
// 细节:函数作为一等对象的语言中,作为参数传递也会返回不同的执行结果,也是“多态性”的体现。
function renderMap (renderMapFromApi) {
  const size = getSize();
  renderMapFromApi(size);
}
renderMap((size) => googleMap.show(size));

其他

  • 如果用了TS,没必要做多余类型判断。

注释

  1. 一般代码要能清晰的表达意图,只有遇到复杂的逻辑时才注释。

// good:由于函数名已经解释不清楚函数的用途了,所以注释里说明。
// 在nums数组中找出 和为目标值 target 的两个整数,并返回它们的数组下标。
const twoSum = function(nums, target) {
 let map = new Map()
 for (let i = 0; i < nums.length; i++) {
   const item = nums[i];
   const index = map.get(target - item)
   if (index !== undefined){
     return [index, i]
   }
   map.set(item, i)
 }
 return []
};

// bad:加了一堆废话
const twoSum = function(nums, target) {
 // 声明map变量
 let map = new Map()
 // 遍历
 for (let i = 0; i < nums.length; i++) {
   const item = nums[i];
   const index = map.get(target - item)
   // 如果下标为空
   if (index !== undefined){
     return [index, i]
   }
   map.set(item, i)
 }
 return []
};
  1. 警示作用,解释此处不能修改的原因。

// hack: 由于XXX历史原因,只能调度一下。
setTimeout(doSomething, 0)
  1. TODO注释,记录下应该做但还没做的工作。另一个好处,提前写好命名,可以帮助后来者统一命名风格。

class Comment {
 // todo: 删除功能后期实现
 delete() {}
}
  1. 没用的代码直接删除,不要注释,反正git提交历史记录可以找回。

// bad: 如下,重写了一遍两数之和的实现方式
// const twoSum = function(nums, target) {
//     for(let i = 0;i<nums.length;i++){
//         for(let j = i+1;j<nums.length;j++){
//             if (nums[i] + nums[j] === target) {
//                 return [i,j]
//             }
//         }
//     }
// };

const twoSum = function(nums, target) {
 let map = new Map()
 for (let i = 0; i < nums.length; i++) {
   const item = nums[i];
   const index = map.get(target - item)
   if (index !== undefined){
     return [index, i]
   }
   map.set(item, i)
 }
 return []
};
  1. 避免循规式注释,不要求每个函数都要求jsdocjsdoc一般是用在公共代码上。

// bad or good?
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
 const twoSum = function(nums, target) {}

对象

  • 多使用gettersetter(getXXX和setXXX)。好处:

    • set时方便验证。

    • 可以添加埋点,和错误处理。

    • 可以延时加载对象的属性。

// good
function makeBankAccount() {
 let balance = 0;
 function getBalance() {
   return balance;
 }
 function setBalance(amount) {
   balance = amount;
 }
 return {
   getBalance,
   setBalance
 };
}
const account = makeBankAccount();
account.setBalance(100);
  • 使用私有成员。对外隐藏不必要的内容。

  // bad
  const Employee = function(name) {
    this.name = name;
  };
  Employee.prototype.getName = function getName() {
    return this.name;
  };
  
  const employee = new Employee(John Doe);
  delete employee.name;
  console.log(employee.getName()); // undefined
  
  // good
  function makeEmployee(name) {
   return {
     getName() {
       return name;
     }
   };
  }

solid

  • 单一职责原则 (SRP) - 保证“每次改动只有一个修改理由”。因为如果一个类中有太多功能并且您修改了其中的一部分,则很难预期改动对其他功能的影响。

  // bad:设置操作和验证权限放在一起了
  class UserSettings {
   constructor(user) {
     this.user = user;
   }
   changeSettings(settings) {
     if (this.verifyCredentials()) {
       // ...
     }
   }
   verifyCredentials() {
     // ...
   }
  }
  
  // good: 拆出验证权限的类
  class UserAuth {
   constructor(user) {
     this.user = user;
   }
   verifyCredentials() {
     // ...
   }
  }
  
  class UserSettings {
   constructor(user) {
     this.user = user;
     this.auth = new UserAuth(user);
   }
   changeSettings(settings) {
     if (this.auth.verifyCredentials()) {
       // ...
     }
   }
  }
  • 开闭原则 (OCP) - 对扩展放开,但是对修改关闭。在不更改现有代码的情况下添加新功能。比如一个方法因为有switch的语句,每次出现新增条件时就要修改原来的方法。这时候不如换成多态的特性。

  // bad: 注意到fetch用条件语句了,不利于扩展
  class AjaxAdapter extends Adapter {
   constructor() {
     super();
     this.name = ajaxAdapter;
   }
  }
  
  class NodeAdapter extends Adapter {
   constructor() {
     super();
     this.name = nodeAdapter;
   }
  }
  
  class HttpRequester {
   constructor(adapter) {
     this.adapter = adapter;
   }
   fetch(url) {
     if (this.adapter.name === ajaxAdapter) {
       return makeAjaxCall(url).then(response => {
         // transform response and return
       });
     } else if (this.adapter.name === nodeAdapter) {
       return makeHttpCall(url).then(response => {
         // transform response and return
       });
     }
   }
  }
  function makeAjaxCall(url) {
   // request and return promise
  }
  function makeHttpCall(url) {
   // request and return promise
  }
  
  // good
  class AjaxAdapter extends Adapter {
   constructor() {
     super();
     this.name = ajaxAdapter;
   }
   request(url) {
     // request and return promise
   }
  }
  
  class NodeAdapter extends Adapter {
   constructor() {
     super();
     this.name = nodeAdapter;
   }
   request(url) {
     // request and return promise
   }
  }
  
  class HttpRequester {
   constructor(adapter) {
     this.adapter = adapter;
   }
   fetch(url) {
     return this.adapter.request(url).then(response => {
       // transform response and return
     });
   }
  }
  • 里氏替换原则 (LSP)

    • 如果S是T的子类,则T的对象可以替换为S的对象,而不会破坏程序。

    • 所有引用其父类对象方法的地方,都可以透明的替换为其子类对象。

    • 也就是,保证任何父类对象出现的地方,用其子类的对象来替换,不会出错。下面的例子是经典的正方形、长方形例子。

    • 两个定义

  // bad: 用正方形继承了长方形
  class Rectangle {
   constructor() {
     this.width = 0;
     this.height = 0;
   }
   setColor(color) {
     // ...
   }
   render(area) {
     // ...
   }
   setWidth(width) {
     this.width = width;
   }
   setHeight(height) {
     this.height = height;
   }
   getArea() {
     return this.width * this.height;
   }
  }
  
  class Square extends Rectangle {
   setWidth(width) {
     this.width = width;
     this.height = width;
   }
   setHeight(height) {
     this.width = height;
     this.height = height;
   }
  }
  
  function renderLargeRectangles(rectangles) {
   rectangles.forEach(rectangle => {
     rectangle.setWidth(4);
     rectangle.setHeight(5);
     const area = rectangle.getArea(); // BAD: 返回了25,其实应该是20
     rectangle.render(area);
   });
  }
  const rectangles = [new Rectangle(), new Rectangle(), new Square()];// 这里替换了
  renderLargeRectangles(rectangles);
  
  // good: 取消正方形和长方形继承关系,都继承Shape
  class Shape {
   setColor(color) {
     // ...
   }
   render(area) {
     // ...
   }
  }
  class Rectangle extends Shape {
   constructor(width, height) {
     super();
     this.width = width;
     this.height = height;
   }
   getArea() {
     return this.width * this.height;
   }
  }
  
  class Square extends Shape {
   constructor(length) {
     super();
     this.length = length;
   }
   getArea() {
     return this.length * this.length;
   }
  }
  
  function renderLargeShapes(shapes) {
   shapes.forEach(shape => {
     const area = shape.getArea();
     shape.render(area);
   });
  }
  const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
  renderLargeShapes(shapes);
  • 接口隔离原则 (ISP) - 定义是客户不应被迫使用对其而言无用的方法或功能。常见的就是让一些参数变成可选的。

// bad
 class Dog {
   constructor(options) {
     this.options = options;
   }
   run() {
     this.options.run(); // 必须传入 run 方法,不然报错
   }
 }
 const dog = new Dog({}); // Uncaught TypeError: this.options.run is not a function
  
 dog.run()
 
 // good
 class Dog {
   constructor(options) {
     this.options = options;
   }
   run() {
   if (this.options.run) {
     this.options.run();
     return;
   }
     console.log('跑步');
   }
 }
  • 依赖倒置原则(DIP) - 程序要依赖于抽象接口(可以理解为入参),不要依赖于具体实现。这样可以减少耦合度。

// bad
 class OldReporter {
   report(info) {
     // ...
   }
 }
 
 class Message {
   constructor(options) {
     // ...
     // BAD: 这里依赖了一个实例,那你以后要换一个,就麻烦了
     this.reporter = new OldReporter();
   }
   share() {
     this.reporter.report('start share');
     // ...
   }
 }
 
 // good
 class Message {
   constructor(options) {
     // reporter 作为选项,可以随意换了
     this.reporter = this.options.reporter;
   }
   share() {
     this.reporter.report('start share');
     // ...
   }
 }
 class NewReporter {
   report(info) {
     // ...
   }
 }
 new Message({ reporter: new NewReporter });

其他

  • 优先使用 ES2015/ES6 类而不是 ES5 普通函数。

  • 多使用方法链。

  • 多使用组合而不是继承。

错误处理

  • 不要忽略捕获的错误。而要充分对错误做出反应,比如console.error()到控制台,提交错误日志,提醒用户等操作。

  • 不要漏了catch promise中的reject

格式

可以使用eslint工具,这里就不展开说了。

最后

接受第一次愚弄

让程序一开始就做到整洁,并不是一件很容易的事情。不要强迫症一样地反复更改代码,因为工期有限,没那么多时间。等到下次需求更迭,你发现到代码存在的问题时,再改也不迟。

入乡随俗

每个公司、项目的代码风格是不一样的,会有与本文建议不同的地方。如果你接手了一个成熟的项目,建议按照此项目的风格继续写代码(不重构的话)。因为形成统一的代码风格也是一种代码整洁。文章来源地址https://www.toymoban.com/news/detail-465846.html

到了这里,关于「为什么代码要整洁?」——代码整洁度对于项目质量的影响,让我们通过这边文章来教你js和ts的代码整洁技巧,让你的项目更出众的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 低代码是什么意思?企业为什么要用低代码平台?

    低代码是什么意思?企业为什么要用低代码平台? 这两个问题似乎困扰了很多人,总有粉丝跟小简抱怨, 一天到晚念叨低代码,倒是来个人解释清楚啊! 来了,这次一文让你全明白。 在此之前,先了解什么是云计算。 “云” :指的就是互联网,因为之前互联网(Internet)

    2024年02月07日
    浏览(57)
  • 华为云低代码问答——低代码为什么这么“香”

    在数字化转型的大潮中,快速实现业务需求并迅速推出应用程序是企业成功的关键。而低代码平台,则成为了开发者的好帮手,为他们提供了高效、可扩展且可靠的开发解决方案。本期【云享问答】通过这11个问题,带你熟悉低代码开发,认识华为云低代码平台Astro,实现应用

    2024年02月21日
    浏览(43)
  • Zookeeper 和 Redis 哪种更好? 为什么使用分布式锁? 1. 利用 Redis 提供的 第二种,基于 ZK 实现分布式锁的落地方案 对于 redis 的分布式锁而言,它有以下缺点:

    关于这个问题,我们 可以从 3 个方面来说: 为什么使用分布式锁? 使用分布式锁的目的,是为了保证同一时间只有一个 JVM 进程可以对共享资源进行操作。 根据锁的用途可以细分为以下两类: 允许多个客户端操作共享资源,我们称为共享锁 这种锁的一般是对共享资源具有

    2024年01月16日
    浏览(48)
  • 为什么 Python 代码在函数中运行得更快?

    哈喽大家好,我是咸鱼 当谈到编程效率和性能优化时,Python 常常被调侃为“慢如蜗牛” 有趣的是,Python 代码在函数中运行往往比在全局范围内运行要快得多 小伙伴们可能会有这个疑问:为什么在函数中运行的 Python 代码速度更快? 今天这篇文章将会解答大家心中的疑惑 原

    2024年02月08日
    浏览(59)
  • 屎山代码为什么不去重构的探讨

    现在公司的产品,从出生到现在已经有十多年了,现在仍然在持续的功能构建中。当然我来这里,看代码才几天的时间,那是憋得是相当难受,看一下,其中的一段比较核心的逻辑的代码,一张图截不了,是的,你没看错,上千行的一个函数挺多,里面大量的if else,上面还有

    2024年01月18日
    浏览(45)
  • 为什么说低代码的出现是时代的选择?

    前言:作为一种软件开发工具,低代码平台一定程度上提升了企业的软件开发效率,适应了整体的数字化发展趋势,故可以看到,低代码近几年的热度不可小觑。 随着企业对应用程序的开发和升级需求不断激增,许多低代码工具越来越受欢迎,国内有各类SaaS、云服务等厂商

    2024年02月03日
    浏览(50)
  • 为什么越来越多公司开始用低代码开发?

    时代洪流的走向,我们无法左右,能够把握的,只有做好自己。如何在寒冬来之不易的机会中,生存并且壮大。 不知道大家有没有发现,今年的低代码赛道异常火热,但火热的背后才值得思考,市场需求持续被挖掘,是什么造就了目前诸多低代码平台的井喷? 在低代码应用

    2024年02月04日
    浏览(63)
  • 从CPU的视角看 多线程代码为什么那么难写!

      当我们提到多线程、并发的时候,我们就会回想起各种诡异的bug,比如各种线程安全问题甚至是应用崩溃,而且这些诡异的bug还很难复现。我们不禁发出了灵魂拷问 “为什么代码测试环境运行好好的,一上线就不行了?”。 为了解决线程安全的问题,我们的先辈们在编

    2024年02月02日
    浏览(66)
  • 代码的坏味道(二)——为什么建议使用模型来替换枚举?

    在设计模型时,我们经常会使用枚举来定义类型,比如说,一个员工类 Employee,他有职级,比如P6/P7。顺着这个思路,设计一个 Level 类型的枚举: 假设哪天悲催的打工人毕业了,需要计算赔偿金,简单算法赔偿金=工资*工龄 后来,随着这块业务逻辑的演进,其实公司是家具备

    2024年02月08日
    浏览(55)
  • 一行代码就能完成的事情,为什么要写两行?

    前后端面试题库 (面试必备) 推荐:★★★★★ 地址:前端面试题库  web前端面试题库 VS java后端面试题库大全 三元运算符 用三元运算符代替简单的 if else 改用三元运算符,一行就能搞定 复杂的判断三元运算符就有点不简单易懂了 判断 当需要判断的情况不止一个时,第一个

    2023年04月16日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包