资讯专栏INFORMATION COLUMN

代码整洁之道

stefan / 1343人阅读

摘要:代码写得是否整洁是客观的,是的人或后期维护的人觉得好才是真的好。三代码设计原则要想写出优雅整洁的代码,就要遵循特定的设计原则。

欢迎关注我的公众号睿Talk,获取我最新的文章:

一、前言

最近在做一些项目重构的工作,看了不少脏乱差的代码,身心疲惫。本文将讨论如何编写整洁的代码,不求高效运行,只求可读性强,便于维护。

二、为什么要写简洁的代码

作为一个合格的程序员,写出简洁的代码是基本的职业素养。相信绝大部分的程序员都不会故意写恶心代码的,无论是对自己或者对别人都没有任何好处。那么,是什么阻碍我们写出优秀代码呢?有下面这么几种可能性:

时间紧任务重,没那么多时间考虑代码设计

偷懒图方便,不过脑子机械式的写代码

注意力只集中在功能的实现,不考虑后期的维护成本

知识储备不够,不知道怎样写出优雅的代码

出来混迟早要还的,无论是上述哪种原因,混乱代码一旦被写出来,代码作者肯定是要为其买单的,只是买单的方式会各有不同。可能是后期维护的时候边改边抽自己,也可能是别人改代码的时候边改边骂你傻x。

那么,代码写好了会有什么好处呢?起码有以下几方面:

后期维护更高效,无论是改 bug 还是新增功能,无论是自己改还是别人改

后期更少的加班

思考如何编写整洁代码的过程中,技术能力会随之提高

写出优雅的代码,会更有成就感,更热爱自己的工作

别人看到这么优雅的代码,会赞不绝口,个人影响力会放大

既然有这么多好处,那到底怎么评判代码写得好不好呢?是自己觉得好就是好吗?显然不是。代码写得是否整洁是客观的,是 code review 的人或后期维护的人觉得好才是真的好。所以加强 code review 也是倒逼写出优秀代码的一种方式。

个人认为代码的优秀程度分以下几个层次:

程序能正常运行

异常情况有应对方法

简单明了,易于后期维护

团队成员间能高效协作

能高性能运行

层次越高,难度越大,挑战越大。作为一个有追求的程序员,我们应该不断突破自己的边界,追求卓越,更上一层楼。

三、代码设计原则

要想写出优雅整洁的代码,就要遵循特定的设计原则。透彻理解这些原则后,还要结合具体的项目落地,不断的练习和重构。下面总结出的一些通用原则供参考。

KISS(keep it simple, stupid)

业务逻辑要直截了当,不要引入各种依赖,多层次调用。以 React 为例,常见的错误是将propsstate里存一份,计算的时候再从state中取。这样带来的问题是要时刻监听props的变化,然后再同步到state中。这完全是多此一举,直接用props进行计算即可。

// bad
componentWillReceiveProps(nextProps) {
    this.setState({num: nextProps.num});
}

render() {
    return(
        
{this.state.num * 2}
); } /***************************/ // good render() { return(
{this.props.num * 2}
); }

DRY (don’t repeat yourself)

不用做机械式的复制粘贴,要锻炼自己抽象的能力,尽量将通用的逻辑抽象出来,方便日后重用。

Open/Closed (open to extension but closed to modification)

代码要对扩展开放,对修改封闭。尽量做到在不修改原有代码的基础上,增加新的功能。React 的容器组件和展示组件分离用的就是这种思想。当数据来源不同的时候,只需要修改或新增容器组件,展示组件维持不变。

function Comp() {
    ...
}

class ContainerComp extends PureComponent {

    async componentDidMount() {
        const data = await fetchData();
        this.setState({data});
    }
    
    render() {
        return ();
    }
}

从架构层面说,微内核架构也是遵循这一设计原则。它能保证核心模块不变的情况下,通过插件机制为系统赋予新的能力。我们常用的 Webpack 就是一个很好的例子,它通过引入 loader 和 plugin 的机制,极大的扩展了其文件处理的能力。

Composition > Inheritance

首先说一下类这个概念。本质上来说,定义类就是为了代码的复用,对于需要同时创建多个对象实例的情况下,这种设计模式是非常有效的。比如说连接池中就需要同时存在多个连接对象,方便资源复用。而对于前端来说,绝大部分的业务场景都是单例,这种情况下通过定义工具函数,或者直接使用对象字面量会更加高效。工具函数尽量使用纯函数,使代码更易于理解,不用考虑副作用。这里说的仅限于业务代码的范畴,如果是框架型的项目,场景会复杂得多,类会更有用武之地。

既然类都不需要用了,继承就更无从谈起了。继承的问题是多级继承之后,定位问题会非常困难,要一级一级往上找才能找到错误出处。而组合就没有这种问题,因为多个功能都是平级的,哪里出问题一眼就能看出来。比较一下下面 2 种风格的代码:

继承的写法:

class Parent extends PureComponent {
    componentDidMount() {
        this.fetchData(this.url);
    }
    
    fetchData(url) {
        ...
    }
    
    render() {
        const data = this.calcData();
        return (
            
{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 (
            
{data} ); } } class Child extends PureComponent { calcData(state) { ... } render() { } }

哪种更易于理解呢?

Single Responsibility

遵循单一职责的代码如果设计得好,组合起来的代码就会非常的清爽。举一个注册的场景,可以划分为下面几个职责:

UI 展示

输入合法性判断

网络请求

聚合层

伪代码如下:

// 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() {
        
    }
}

可以看到聚合层的代码非常简洁,哪里出问题了就到相应的地方改就好了,即使是多人协作,也不容易出问题。

Separation of Concerns

关注点分离原则跟单一职责原则有点类似,但更强调的是系统架构层面的设计。典型的例子就是 MVC 模式,Model、View、Control 三层之间都有明确的职责划分,做到了高内聚低耦合。

React 的源码设计也是基于这一原则,分为ReactElement, ReactCompositeComponentReactDomComponent 三层。ReactElement 负责描述页面的 DOM 结构,也就是著名的 Virtual DOM;ReactCompositeComponent 处理的是组件生命周期、组件 Diff 和更新等逻辑;而ReactDomComponent是真正进行 DOM 操作的类。三层之间分工明确,紧密协作,共同组成了一个强大的前端框架。

Clean Code > Clever Code

提倡简洁易懂的代码,而不是晦涩难懂的“聪明”代码,如下面这种:

let a, b=3, t = (a=2, b<1) ? (console.log("Y"),"yes") : (console.log("N"),"no");

单一代码文件不超过 200 行

文件一旦超过 200 行,说明逻辑已经有点复杂了,要想办法抽离出一些纯函数工具方法,让主线逻辑更加清晰。工具方法可以放在另外的文件里面,减少读代码的心理压力。有一点需要说明一下,并不是所有的文件都不能超过 200 行,像工具方法这种,都是各自独立的逻辑,写多少行都无所谓。需要控制的是紧密关联的业务代码的行数。

四、前端代码如何拆分

上面提到要合理的拆分代码,那到底怎么拆呢?对于前端组件代码,有下面一些拆分点以供参考:

UI

展现逻辑

事件处理

业务逻辑

网络请求

配置类纯JSON对象

工具类纯函数

需要说明的是展现逻辑和业务逻辑是两回事,最好不要混在一起写。比如组件的显示隐藏是展现逻辑,而数据的校验就是业务逻辑。

五、总结

本文讨论了书写整洁代码的必要性和重要性,结合实例列出了一些设计原则,还给出了组件代码拆分的方式。程序员的职业生涯是一个自我修炼的过程,时刻关注代码质量,是提高技术水平的重要一环。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105330.html

相关文章

  • 代码整洁之道

    摘要:在代码整洁之道,提出一种软件质量,可持续开发不仅在于项目架构设计,还与代码质量密切相关,代码的整洁度和质量成正比,一份整洁的代码在质量上是可靠的,为团队开发,后期维护,重构奠定了良好的基础。 现在的软件系统开发难度主要在于其复杂度和规模,客户需求也不再像Winston Royce瀑布模型期望那样在系统编码前完成所有的设计满足用户软件需求。在这个信息爆炸技术日新月异的时代,需求总是在不停...

    icattlecoder 评论0 收藏0
  • 代码整洁之道 - 有意义的命名

    摘要:我们这里再介绍一下,朱重八家族的名字,都很有特点。取这样的名字不是因为朱家是搞数学的,而是因为在元朝,老百姓如果不能上学和当官就没有名字,只能以父母年龄相加或者出生的日期命名。所以说命名不仅仅是一种科学,更是一种艺术。 在小朱元璋出生一个月后,父母为他取了一个名字(元时惯例):朱重八,这个名字也可以叫做朱八八。我们这里再介绍一下,朱重八家族的名字,都很有特点。朱重八高祖名字:朱百六;朱...

    mengbo 评论0 收藏0
  • 代码整洁之道 - 有意义的命名

    摘要:我们这里再介绍一下,朱重八家族的名字,都很有特点。取这样的名字不是因为朱家是搞数学的,而是因为在元朝,老百姓如果不能上学和当官就没有名字,只能以父母年龄相加或者出生的日期命名。所以说命名不仅仅是一种科学,更是一种艺术。 在小朱元璋出生一个月后,父母为他取了一个名字(元时惯例):朱重八,这个名字也可以叫做朱八八。我们这里再介绍一下,朱重八家族的名字,都很有特点。朱重八高祖名字:朱百六;朱...

    Cobub 评论0 收藏0
  • 架构整洁之道(二)——两个价值的故事

    摘要:每个软件系统都提供两个价值给利益相关者表现和结构。来自利益相关者的观点,开发者仅仅只提供了一些形态上的粗略改变,来自开发者的观点,老板的需求越来越难。记住,作为一个开发者,你就是利益相关者,你需要维护的软件里有你的利益。 每个软件系统都提供两个价值给利益相关者:表现和结构。软件开发者应的确保这两个价值尽量高负责。然而很不幸,程序员很多只关心其中一个而忽略另一个,甚至更不幸,他们可能关注...

    denson 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<