zl程序教程

您现在的位置是:首页 >  其他

当前栏目

什么是可维护性的代码

代码 什么
2023-09-14 09:02:36 时间

什么是可维护性的代码

今天我们不聊性能优化,只是从后期维护代码的角度谈谈如何优雅的书写代码

  • 为什么需要些可维护性高的代码 ?

在开发的过程中,迭代和维护是再正常不过的操作了
那么就必然要阅读别人的代码
你有没有遇到过一些尴尬的事情:
1、看不懂别人的代码,不知从何下手
2、修改一个功能,得读两天代码,改完发现 bug 最少的时候是修改以前
3、只是修改了一行代码,发现控制台报错好几十个

如果代码的可维护性高了,那么可以避免很多这些问题
编写可维护性高的代码, 从我做起 _

  • 什么是可维护性高的代码 ?

容易理解: 不需要求助源代码书写人员,就能看得懂
符合常识: 代码书写的自然通透
容易适配: 当数据发生变化的时候,不至于完全重写
容易扩展: 对于核心功能有可扩展性(适当利用策略模式)
容易调试: 当出现问题的时候,能给出明确且详细的错误提示,可以直接定位问题源

img

从下面几点做起:

一、代码可读性

  • 想要好维护, 那么第一任务就是你写的代码要让别人看得懂
  • 因为我们的代码,当他不运行的时候,就是一个纯文本
  • 想要让别人看得懂你写的一堆文本,那么就要从一切自定义的内容开始做起

二、代码缩进

  • 能区分是论文还是代码的第一因素,也是最直观的因素就是代码缩进
  • 代码没有缩进,或者随机缩进,那么和给你看一篇火星文论文没有区别
for (var i = 0; i < 100; i++) {
if (true) {
function fn() {   
for (var j = 0; j < 100; j++) {
}
} 
for (var j = 0; j < 100; j++) {
}
}
}

img整整齐齐的就是看不懂

  • 我们严格保持了代码缩进以后, 虽然代码意义不一定看得懂, 但是代码结构我能看得懂了
for (var i = 0; i < 100; i++) {
    if (true) {
        function fn() {   
            for (var j = 0; j < 100; j++) {
            }
        } 
        for (var j = 0; j < 100; j++) {
        }
    }
}
  • 这个时候就可以尝试下改一改了

三、注释

在任何一个语言里面,都是有注释的

语言规范里定义注释,不是为了让你学了玩的,就是为了让你对代码进行一些标注的

大型代码块,和大量变量堆积的地方,都要有清楚的注释,用来表明这个代码块或者说这一堆变量是干什么用的,尤其是函数,尽量做到每一个函数的前面都有一个说明注释。

/*
 * fn 获取范围之间随机整数的函数
 * @param {Number} a 范围开始的数字
 * @param {Number} b 范围结束的数字
 * @return {Number} 范围内的随机整数
*/
function fn(a, b) { ... }
    • 每一个函数都应该有参数说明,是否有返回值,返回值是什么
    • 因为这些内容在函数定义中是不能直观看到了,需要阅读代码才可以
    • 当你写明了这些以后,阅读性就大大提高了
    • 假设,你的函数块里面涉及到很复杂的算法,最好也是在说明注释里面标注出来

当你对于一些浏览器问题做出的修复,你使用了一些黑科技

    • 那么你一定要把这些黑科技标注出来,避免别人修改你的代码的时候
    • 觉得这些黑科技没有用,给你删掉了,导致你修改好的问题又重新出现了

四、变量和函数命名

变量的命名和函数的命名,是最能体现我们自定义的地方

对于每一个变量和函数的命名,我们都尽量准确的给到一个语义,不管你是使用 大驼峰 还是 小驼峰,都要保证看到名字就能知道这个变量或者函数的意义

从变量来说

1、尽量使用名词,而不是动词

比如:car / person / show / …

2、常量来说,要使用大写字母来表示

比如:TEST / BROWSER / …

3、区分全局和私有变量,函数内的私有变量我会以 _ 开头

比如: _this / …

从函数来说

1、当函数返回布尔值的时候, 一般会以 is 开头

比如:isEnabled() / isSelected() / …

2、获取类的函数一般以 get 开头

比如:getUserList() / getUserInfo() / …

3、设置类的一般使用 set 开头

比如:setName() / setUserInfo() / …

4、修改类的一般使用 update 开头

比如:updateName() / updatePrice() / …

4、程序处理类函数使用 handler 结尾

比如:showEditHandler() / submitHandler() / …

5、尽可能的通过名字描述清楚函数的作用,不用担心太长,因为后期打包工具会帮我们处理掉的

比如: getUserInfoById() / delGoodsParamsById() / …

五、变量类型透明化

因为 JS 是一个弱类型语言,在定义变量的时候,不会限制数据类型

但是我们在给变量赋值的时候,也要尽可能的做到数据类型统一

当你需要定义一些变量,在后期操作中进行赋值的时候

尽可能在定义的时候,给一个初始值表示一下你变量将来要存储的数据类型

比如:
var count = 0;
var name = ‘’;
var boo = false;
var person = null;
var todoList = [ ];

如果你实在不想给一个初始值

也可以使用注释的形式表明一下你定义的变量, 将来存储的是什么类型的数据

var count /* Number /;
var name /
String /;
var boo /
Boolean */;

六、代码书写习惯

我们要保证一个良好的代码书写习惯

七、链式编程的习惯

我们来看一下下面这个代码

[ ... ].map(function () {
 // code ...
}).filter(function () {
 // code ...
}).reduce(function () {
 // code ...
})

其实没啥问题, 而且也挺好的
更甚至当代码简单一些的时候有人把它写成一行

[ ... ].map(function () { ... }).filter(function () { ... }).reduce(function () { ... })

但是到了后期修改的时候,问题就会逐步显示,一旦修改了第一个,那么后面的都有可能会出现问题
而且当代码量过大的时候,很难保证你不修改串行了

  • 我们可以把上面的代码换成下面的方式
[ ... ]
    .map(function () {
    // code ...
    })
    .filter(function () {
    // code ...
    })
    .reduce(function () {
    // code ...
    })

这样的话,看起来会舒服的多

而且可以利用编辑器的代码折叠,一个函数一个函数的来书写

八、书写运算符的习惯

很多人喜欢相对紧凑的书写结构

比如下面的代码

if (year%4===0&&year%100!==0||year%400===0) { ... }

很简单的一个判断闰年的代码

但是当你的运算符很紧凑的时候,那么看起来就会比较费眼睛

相对来说,我更喜欢在运算符两边都加上空格

让结构相对松散一些,看起来可能也容易一些

我们也不用担心这些空格,后期处理都会帮我们处理掉的

if ( year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) { ... }

还有一种写法

if ( 
 year % 4 === 0 && 
 year % 100 !== 0 || 
 year % 400 === 0
) { ... }

这个适用于条件比较长的时候使用
看起来会更加清晰一些

九、函数调用传递参数

  • 当调用一个函数,需要传递一个函数作为参数的时候
  • 我们通常都会直接书写一个匿名函数或者箭头函数在参数位置
  • 或者说传递一个复杂数据类型作为参数的时候,都会直接讲对应的数组或者对象写在参数位置
  • 比如下面这段代码
$.get('/xxx', {
    a: 100,
    b: 200
}, function (res) {
    // code ...
}, 'json')

代码没有问题,但是一旦对象中数据过多
或者函数中代码过多的时候
后期看起来就会很复杂

我会建议把这些内容单独书写出来

var params = {
    a: 100,
    b: 200
}

function success(res) {
    // code ...
}

$.get('/xxx', params, success, 'json')

这样一来, 不管是修改, 还是增加一些内容, 都会比较方便了

十、功能性函数的单独封装

把我们自定义的一些功能性函数进行单独的封装,放在一个单独的 JS 文件中进行引入或者导入使用,其实就是模块化的概念

十一、松散耦合

对于比较难以阅读的代码来说,强耦合的代码是最难阅读的,JS 代码本身层面上的耦合我们就不说了,大家都应该了解面向对象编程和模块化编程

十二、HTML 和 JavaScript 的耦合

在前端开发中,我们经常会见到有些人写代码会把一些简单的事件直接写到 html 结构上

<button onclick="doSomething()" ></button> 

从代码层面上来说完全没有问题
但是实际上,这个是 HTML 和 JavaScript 的强耦合现象
第一: 每次对于代码进行的修改,都要从 HTML 和 JavaScript 两个位置去进行修改
第二: 代码引入位置不可变,一定要保证在用户点击之前就已经有函数存在了,不然一定会报错的

比较好的方法就是进行 HTMLJavaScript 的分离

.js 文件中获取 DOM 元素
通过事件绑定的形式来完成操作

var btn = document.querySelector('button')
btn.addEventListener('click', doSomething)

还有一种情况更常见, 就是在 JS 代码中为了渲染页面而进行字符串拼接

container.innerHTML = `
	<div>
		...
		<p> ... </p>
		<span> ... </span>
	</div>
`

这个代码也是完全没有问题的,而且大部分同学都会这样书写代码,因为省时省力
但是这样的情况,一旦渲染到页面上,出现样式问题需要调整的时候
我们在 HTML 结构中很难找到内容来修改,必须要到 JavaScript 代码里面去修改
如果我们的字符串拼接是在循环里面完成的话,那么有可能你添加一个或者删除一个标签的时候,导致整个页面崩溃

比较好的做法

使用一些第三方小脚本或者模板引擎来进行渲染:

比如:art-template / e.js / …
真的需要这样渲染的时候,那么在原始 html 结构中以注释的形式留下一部分渲染内容

<div class="container">
    <!-- 商品详情信息渲染结构
    <div>
        ...
        <p> ... </p>
        <span> ... </span>
    </div>
    -->
</div>
  • 当 HTML 和 JavaScript 解耦以后
  • 可以大量节省我们的排错时间, 和错误的准确定位

十三、CSS 和 JavaScript 的耦合

在前端的开发中,使用 JS 来操作一些元素的样式,是在常见不过的事情了

比如我们经常会写

ele.style.color = 'red' ;
ele.style.display = 'none' ;

这样书写代码其实没有大问题
对于渲染也不会造成很大的困扰
但是,一旦我们需要修改样式的时候,那么就比较麻烦了
因为有的样式可能需要在 .css 文件内修改,有的样式需要在 .js 文件内修改

  • 比较好的做法是, 把我们需要修改的样式写成一个单独类名下
  • 放在 .css 文件内
  • 我们在代码里面通过操作元素的类名来进行修改
ele.classList.add('active') 
ele.classList.remove('active') 

这样做保证了样式和行为的分离,我们在调整页面样式的时候,不需要 JS,直接在 CSS 中修改就可以

十四、事件处理 和 应用逻辑 的耦合

在开发过程中, 我们经常要处理一些事件,并且在事件里面要进行一些逻辑的运算

比如:我们在点击登录的时候,要对用户填写的内容进行一个正则的验证,然后提交到服务器

ele.addEventListener('submit', function () {
    let username = xxx.value
    let password = xxx.value
    
    // 正则验证
    if ( ... ) { ... }
    
    if ( ... ) { ... }
    
    // 提交到服务器
   	var xhr = new XMLHttpRequest()
    xhr.open( ... )
    xhr.send( ... )
    xhr.onload = function () { ... }
})

这是一段合法的代码
但是函数里面包含了太多的内容
有事件处理
有逻辑处理
有请求发送
这样就相当于在一个函数里面做了太多的事情
这个代码的逻辑运算还是比较少的,但是一旦逻辑运算多了以后,那么后期阅读的时候就很麻烦了

我们可以把里面的逻辑运算和请求发送都单独提取出来,变成下面这个形式:

function validateValue(val) {
    // 正则验证
    if ( ... ) { ... }
    
    if ( ... ) { ... }
    
    // 将验证结果返回
    return true // or false
}

function sendAjax() {
    // 发送请求的业务逻辑
}

ele.addEventListener('submit', function () {
    let username = xxx.value
    let password = xxx.value
    
    // 正则验证
    if (!validateValue( xxx )) return
    
    // 提交到服务器
   	sendAjax()
})

这样一来,只要我们给函数写好注释,那么后期的时候,哪里出现问题,我们可以快速准确的定位问题所在位置

十五、尊重对象所有权

  • JavaScript 的动态天性决定了没有什么是不能修改的
  • 从代码层面出发,我们可以修改任何内容,包括向 Object 的 prototype 上扩展一些方法,,向 Array 的 prototype 上扩展一些方法
  • 但是在真实的企业级开发过程中,我们要绝对的尊重每一个对象的所有权

不要修改任何不属于你的代码,如果某一个对象不是由你负责创建或者维护,那么你也不要修改他的构造函数

在好久好久以前:

我接触过一个叫做 prototype 的第三方库
它里面向 document 对象上扩展了一个叫做 getElementsByClassName 的方法
是不是看起来很无聊,但是在没有 getElementsByClassName 的年代,确实很好用
并且,扩展的这个 getElementsByClassName 方法的返回值是一个 Array 并不是我们后来使用的 NodeList
而且还在实例身上扩展了一个叫做 each() 的方法,专门用来遍历
我们用起来的时候就会很方便

document.getElementsByClassName('item').each() 

这个很好,而且对代码开发进行了简化
但是,一旦浏览器厂商开始支持这个方法了,那么你的方法就会出现问题了
后来,在所有浏览器厂商都支持了 getElementsByClassName 以后
当在使用这个方法的时候,因为和原生的重名了
会出现代码的大面积报错

这个就是尊重代码所有权

因为你不知道浏览器厂商什么时候会 告知 或 不告知 的更新一些内容,或者修改一些 API

所以,不要修改任何不属于你的内容

十六、尽量不声明全局变量

和尊重对象所有权有密切关系的就是尽可能少的声明全局变量

抛开变量污染的层面不说,我们的每一个全局变量其实都是在向 window 上添加成员

var name = 'Jack' 
function getInfo() { ... } 

这都是全局变量,用起来也没什么问题
但是也确实是在 window 上挂载了两个名字

我们在开发自己的代码的时候, 尽可能的在全局制作一个命名空间,然后把我们所有需要的内容全部放在里面

var myApp = {
    name: 'jack',
    getInfo () { ... }
}

这样一来, 我们只是向 window 上挂载了一个 myApp
剩下的所有东西都在我自己的命名空间里面
一旦出现问题,你能准确的知道是你自己定义的变量或者方法出现了问题,还是原生的变量或者方法出现了问题

这个也是前端从没有模块化到模块化开发的演变过程的原始阶段:

    • 独立命名空间
    • IIFE
    • AMD / CMD
    • CommonJS
    • ES6 模块化

十七、习惯使用常量

我们在开发的过程中,经常要使用一些变量来操作某些内容

    • 任何出现一次以上的内容,都应该提取出来变成一个常量的定义
    • 任何一个需要显示给用户看到的文本内容,都应该提取出来变成一个常量
    • 任何一个变量,在定义的时候都要考虑,将来会不会发生变化,如果不发生变化,那么就直接定义成常量
    • 包括我们在操作一些类名的时候,应该把这些类名提取出来做成常量,然后统一操作

这样一来,我们可以避免因为不小心修改变量而导致出现的问题,也可以在代码的各个部分保证代码数据的统一,避免一个东西这里修改了,那里没有修改的问题

img