欢迎关注个人公众号睿Talk
,获取我最新的文章:javascript
最近在作一些项目重构的工做,看了很多脏乱差的代码,身心疲惫。本文将讨论如何编写整洁的代码,不求高效运行,只求可读性强,便于维护。前端
做为一个合格的程序员,写出简洁的代码是基本的职业素养。相信绝大部分的程序员都不会故意写恶心代码的,不管是对本身或者对别人都没有任何好处。那么,是什么阻碍咱们写出优秀代码呢?有下面这么几种可能性:java
出来混早晚要还的,不管是上述哪一种缘由,混乱代码一旦被写出来,代码做者确定是要为其买单的,只是买单的方式会各有不一样。多是后期维护的时候边改边抽本身,也多是别人改代码的时候边改边骂你傻x。程序员
那么,代码写好了会有什么好处呢?起码有如下几方面:segmentfault
既然有这么多好处,那到底怎么评判代码写得好很差呢?是本身以为好就是好吗?显然不是。代码写得是否整洁是客观的,是 code review 的人或后期维护的人以为好才是真的好。因此增强 code review 也是倒逼写出优秀代码的一种方式。设计模式
我的认为代码的优秀程度分如下几个层次:api
层次越高,难度越大,挑战越大。做为一个有追求的程序员,咱们应该不断突破本身的边界,追求卓越,更上一层楼。前端框架
要想写出优雅整洁的代码,就要遵循特定的设计原则。透彻理解这些原则后,还要结合具体的项目落地,不断的练习和重构。下面总结出的一些通用原则供参考。网络
业务逻辑要直截了当,不要引入各类依赖,多层次调用。以 React 为例,常见的错误是将props
在state
里存一份,计算的时候再从state
中取。这样带来的问题是要时刻监听props
的变化,而后再同步到state
中。这彻底是画蛇添足,直接用props
进行计算便可。架构
// bad componentWillReceiveProps(nextProps) { this.setState({num: nextProps.num}); } render() { return( <div>{this.state.num * 2}</div> ); } /***************************/ // good render() { return( <div>{this.props.num * 2}</div> ); }
不用作机械式的复制粘贴,要锻炼本身抽象的能力,尽可能将通用的逻辑抽象出来,方便往后重用。
代码要对扩展开放,对修改封闭。尽可能作到在不修改原有代码的基础上,增长新的功能。React 的容器组件和展现组件分离用的就是这种思想。当数据来源不一样的时候,只须要修改或新增容器组件,展现组件维持不变。
function Comp() { ... } class ContainerComp extends PureComponent { async componentDidMount() { const data = await fetchData(); this.setState({data}); } render() { return (<Comp data={this.state.data}/>); } }
从架构层面说,微内核架构也是遵循这一设计原则。它能保证核心模块不变的状况下,经过插件机制为系统赋予新的能力。咱们经常使用的 Webpack 就是一个很好的例子,它经过引入 loader 和 plugin 的机制,极大的扩展了其文件处理的能力。
首先说一下类这个概念。本质上来讲,定义类就是为了代码的复用,对于须要同时建立多个对象实例的状况下,这种设计模式是很是有效的。好比说链接池中就须要同时存在多个链接对象,方便资源复用。而对于前端来讲,绝大部分的业务场景都是单例,这种状况下经过定义工具函数,或者直接使用对象字面量会更加高效。工具函数尽可能使用纯函数,使代码更易于理解,不用考虑反作用。这里说的仅限于业务代码的范畴,若是是框架型的项目,场景会复杂得多,类会更有用武之地。
既然类都不须要用了,继承就更无从谈起了。继承的问题是多级继承以后,定位问题会很是困难,要一级一级往上找才能找到错误出处。而组合就没有这种问题,由于多个功能都是平级的,哪里出问题一眼就能看出来。比较一下下面 2 种风格的代码:
继承的写法:
class Parent extends PureComponent { componentDidMount() { this.fetchData(this.url); } fetchData(url) { ... } render() { const data = this.calcData(); return ( <div>{data}</data> ); } } class Child extends Parent { constructor(props) { super(props); this.url = 'http://api'; } calcData() { ... } }
组合的写法:
class Parent extends PureComponent { componentDidMount() { this.fetchData(this.props.url); } fetchData(url) { ... } render() { const data = this.props.calcData(this.state); return ( <div>{data}</data> ); } } class Child extends PureComponent { calcData(state) { ... } render() { <Parent url="http://api" calcData={this.calcData}/> } }
哪一种更易于理解呢?
遵循单一职责的代码若是设计得好,组合起来的代码就会很是的清爽。举一个注册的场景,能够划分为下面几个职责:
伪代码以下:
// UI.js export default function UI() { ... } // api.js export function regist(name, email) { ... } // validate.js export function validateName(name) { ... } export function validateEmail(email) { ... } // Regist.js export default class Regist extends PureComponent { ... onSubmit = async () => { const {name, email} = this.state; if (validateName(name) && validateEmail(email)) { const resp = await regist(name, email); ... } } render() { <UI onSubmit={onSubmit}> } }
能够看到聚合层的代码很是简洁,哪里出问题了就到相应的地方改就行了,即便是多人协做,也不容易出问题。
关注点分离原则跟单一职责原则有点相似,但更强调的是系统架构层面的设计。典型的例子就是 MVC 模式,Model、View、Control 三层之间都有明确的职责划分,作到了高内聚低耦合。
React 的源码设计也是基于这一原则,分为ReactElement
, ReactCompositeComponent
和 ReactDomComponent
三层。ReactElement
负责描述页面的 DOM 结构,也就是著名的 Virtual DOM;ReactCompositeComponent
处理的是组件生命周期、组件 Diff 和更新等逻辑;而ReactDomComponent
是真正进行 DOM 操做的类。三层之间分工明确,紧密协做,共同组成了一个强大的前端框架。
提倡简洁易懂的代码,而不是晦涩难懂的“聪明”代码,以下面这种:
let a, b=3, t = (a=2, b<1) ? (console.log('Y'),'yes') : (console.log('N'),'no');
文件一旦超过 200 行,说明逻辑已经有点复杂了,要想办法抽离出一些纯函数工具方法,让主线逻辑更加清晰。工具方法能够放在另外的文件里面,减小读代码的心理压力。有一点须要说明一下,并非全部的文件都不能超过 200 行,像工具方法这种,都是各自独立的逻辑,写多少行都无所谓。须要控制的是紧密关联的业务代码的行数。
上面提到要合理的拆分代码,那到底怎么拆呢?对于前端组件代码,有下面一些拆分点以供参考:
须要说明的是展示逻辑和业务逻辑是两回事,最好不要混在一块儿写。好比组件的显示隐藏是展示逻辑,而数据的校验就是业务逻辑。
本文讨论了书写整洁代码的必要性和重要性,结合实例列出了一些设计原则,还给出了组件代码拆分的方式。程序员的职业生涯是一个自我修炼的过程,时刻关注代码质量,是提升技术水平的重要一环。