为什么一个文件的代码不能超过300行?
先说观点:在进行前端开发时,单个文件的代码行数推荐最大不超过300行,而超过1000行的都可以认为是垃圾代码,需要进行重构。
为什么是300
当然,这不是一个完全精准的数字,你一个页面301行也并不是什么犯天条的大罪,只是一般情况下,300行以下的代码可读性会更好。
起初,这只是林叔根据自己多年的工作经验拍脑袋拍出来的一个数字,据我观察,常规的页面开发,或者说几乎所有的前端页面开发,在进行合理的组件化拆分后,页面基本上都能保持在300行以下,当然,一个文件20行也并没有什么不妥,这里只是说上限。
但是拍脑袋得出的结论是不能让人信服的,于是林叔突发奇想想做个实验,看看这些开源大佬的源码文件都是多少行,于是我开发了一个小脚本。给定一个第三方的源文件所在目录,读取该目录下所有文件的行数信息,然后统计该库下文件的最长行数、最短行数、平均行数、小于500行/300行/200行/100行的文件占比。
脚本实现如下,感兴趣的可以看一下,不感兴趣的可以跳过看统计结果。统计排除了css样式文件以及测试相关文件。
const fs = require('fs');
const path = require('path');
let fileList = []; //存放文件路径
let fileLengthMap = {}; //存放每个文件的行数信息
let result = { //存放统计数据
min: 0,
max: 0,
avg: 0,
lt500: 0,
lt300: 0,
lt200: 0,
lt100: 0
}
//收集所有路径
function collectFiles(sourcePath){
const isFile = function (filePath){
const stats = fs.statSync(filePath);
return stats.isFile()
}
const shouldIgnore = function (filePath){
return filePath.includes("__tests__")
|| filePath.includes("node_modules")
|| filePath.includes("output")
|| filePath.includes("scss")
|| filePath.includes("style")
}
const getFilesOfDir = function (filePath){
return fs.readdirSync(filePath)
.map(file => path.join(filePath, file));
}
//利用while实现树的遍历
let paths = [sourcePath]
while (paths.length){
let fileOrDirPath = paths.shift();
if(shouldIgnore(fileOrDirPath)){
continue;
}
if(isFile(fileOrDirPath)){
fileList.push(fileOrDirPath);
}else{
paths.push(...getFilesOfDir(fileOrDirPath));
}
}
}
//获取每个文件的行数
function readFilesLength(){
fileList.forEach((filePath) => {
const data = fs.readFileSync(filePath, 'utf8');
const lines = data.split('\n').length;
fileLengthMap[filePath] = lines;
})
}
function statisticalMin(){
let min = Infinity;
Object.keys(fileLengthMap).forEach((key) => {
if (min > fileLengthMap[key]) {
min = fileLengthMap[key];
}
})
result.min = min;
}
function statisticalMax() {
let max = 0;
Object.keys(fileLengthMap).forEach((key) => {
if (max < fileLengthMap[key]) {
max = fileLengthMap[key];
}
})
result.max = max;
}
function statisticalAvg() {
let sum = 0;
Object.keys(fileLengthMap).forEach((key) => {
sum += fileLengthMap[key];
})
result.avg = Math.round(sum / Object.keys(fileLengthMap).length);
}
function statisticalLt500() {
let count = 0;
Object.keys(fileLengthMap).forEach((key) => {
if (fileLengthMap[key] < 500) {
count++;
}
})
result.lt500 = (count / Object.keys(fileLengthMap).length * 100).toFixed(2) + '%';
}
function statisticalLt300() {
let count = 0;
Object.keys(fileLengthMap).forEach((key) => {
if (fileLengthMap[key] < 300) {
count++;
}
})
result.lt300 = (count / Object.keys(fileLengthMap).length * 100).toFixed(2) + '%';
}
function statisticalLt200() {
let count = 0;
Object.keys(fileLengthMap).forEach((key) => {
if (fileLengthMap[key] < 200) {
count++;
}
})
result.lt200 = (count / Object.keys(fileLengthMap).length * 100).toFixed(2) + '%';
}
function statisticalLt100() {
let count = 0;
Object.keys(fileLengthMap).forEach((key) => {
if (fileLengthMap[key] < 100) {
count++;
}
})
result.lt100 = (count / Object.keys(fileLengthMap).length * 100).toFixed(2) + '%';
}
//统计
function statistics(){
statisticalMin();
statisticalMax();
statisticalAvg();
statisticalLt500();
statisticalLt300();
statisticalLt200();
statisticalLt100();
}
//打印
function print(){
console.log(fileList)
console.log(fileLengthMap)
console.log('最长行数:', result.max);
console.log('最短行数:', result.min);
console.log('平均行数:', result.avg);
console.log('小于500行的文件占比:', result.lt500);
console.log('小于300行的文件占比:', result.lt300);
console.log('小于200行的文件占比:', result.lt200);
console.log('小于100行的文件占比:', result.lt100);
}
function main(path){
collectFiles(path);
readFilesLength();
statistics();
print();
}
main(path.resolve(__dirname,'./vue-main/src'))
利用该脚本我对Vue、React、ElementPlus和Ant Design这四个前端最常用的库进行了统计,结果如下:
库 | 小于100行占比 | 小于200行占比 | 小于300行占比 | 小于500行占比 | 平均行数 | 最大行数 | 备注 |
---|---|---|---|---|---|---|---|
vue | 60.8% | 84.5% | 92.6% | 98.0% | 112 | 1000 | 仅1个模板文件编译的为1000行 |
react | 78.0% | 92.0% | 94.0% | 98.0% | 96 | 1341 | 仅1个JSX文件编译的为1341行 |
element-plus | 73.6% | 90.9% | 95.8% | 98.8 | 75 | 950 | |
ant-design | 86.9% | 96.7% | 98.7% | 99.5% | 47 | 722 |
可以看出95%左右的文件行数都不超过300行,98%的都低于500行,而每个库中超过千行以上的文件最多也只有一个,而且还都是最复杂的模板文件编译相关的代码,我们平时写的业务代码复杂度远远小于这些优秀的库,那我们有什么理由写出那么冗长的代码呢?
从这个数据来看,林叔的判断是正确的,代码行数推荐300行以下,最好不超过500行,禁止超过1000行。
为什么不要超过300
现在,请你告诉我,你见过最难维护的代码文件是什么样的?它们有什么特点?
没错,那就是大,通常来说,难维护的代码会有3个显著特点:耦合严重、可读性差、代码过长,而代码过长是难以维护的最重要的原因,就算耦合严重、可读性差,只要代码行数不多,我们总还能试着去理解它,但一旦再伴随着代码过长,就超过我们大脑(就像计算机的CPU和内存)的处理上限了,直接死机了。
这是由于我们的生理结构决定的,大脑天然就喜欢简单的事物,讨厌复杂的事物,不信咱们做个小测试,试着读一遍然后记住下面的几个字母:
F H U T L P
怎么样,记住了吗?是不是非常简单,那我们再来看下下面的,还是读一遍然后记住:
J O Q S D R P M B C V X
这次记住了吗?这才12个字母而已,而上千行的代码中,包含各种各样的调用关系、数据结构等,为了搞懂一个功能可能还要跳转好几个函数,这么复杂的信息,是不是对大脑的要求有点过高了。
代码行数过大通常是难以维护的最大原因。
怎么不超过300
现在前端组件化编程这么流行,这么方便,我实在找不出还要写出超大文件的理由,我可以"武断"地说,凡是写出大文件的同学,都缺乏结构化思维和分治思维。
面向结构编程,而不是面向细节编程
以比较简单的官网开发为例,喜欢面向细节编程的同学,可能得实现是这样的:
<div>
<div class="header">
<img src="logo.png"/>
<h1>网站名称h1>
div>
<div class="main-content">
<div class="banner">
<ul>
<li><img src="banner1.png">li>
ul>
div>
<div class="about-us">
div>
div>
div>
其中省略了N行代码,通常他们写出的页面都非常的长,光Dom可能都有大几百行,再加上JS逻辑以及CSS样式,轻松超过1000行。
现在假如领导让修改"关于我们"的相关代码,我们来看看是怎么做的:首先从上往下阅读代码,在几千行代码中找到"关于我们"部分的DOM,然后再从几千行代码中找到相关的JS逻辑,这个过程中伴随着鼠标的反复上下滚动,眼睛像扫描仪一样一行行扫描,生怕错过了某行代码,这样的代码维护起来无疑是让人痛苦的。
面向结构开发的同学实现大概是这样的:
<div>
<Header/>
<main>
<Banner/>
<AboutUs/>
<Services/>
<ContactUs/>
main>
<Footer/>
div>
我们首先看到的是页面的结构、骨架,如果领导还是让我们修改"关于我们"的代码,你会怎么做,是不是毫不犹豫地就进入AboutUs组件的实现,无关的信息根本不会干扰到你,而且AboutUs的逻辑都集中在组件内部,也符合高内聚的编程原则。
特别是关于表单的开发,面向细节编程的情况特别严重,也造成表单文件特别容易变成超大文件,比如下面这个图,在一个表单中有十几个表单项,其中有一个选择商品分类的下拉选择框。
面向细节编程的同学喜欢直接把每个表单项的具体实现,杂糅在表单组件中,大概如下这样:
这还只是一个非常简单的表单项,你看看,就增加了这么多细节,如果是比较复杂点的表单项,其代码就更多了,这么多实现细节混合在这里,你能轻易地搞明白每个表单项的实现吗?你能说清楚这个表单组件的主线任务吗?
面向结构编程的同学会把它抽取为表单项组件,这样表单组件中只需要关心表单初始化、校验规则配置、保存逻辑等应该表单组件处理的内容,而不再呈现各种细节,实现了关注点的分离。
分而治之,大事化小
在进行复杂功能开发时,应该首先通过结构化思考,将大功能拆分为N个小功能,具体每个小功能怎么实现,先不用关心,在结构搭建完成后,再逐个问题击破。
仍然以前面提到的官网为例,首先把架子搭出来,每个子组件先不要实现,只要用一个简单的占位符占个位就行。
<div>
<Header/>
<main>
<Banner/>
<AboutUs/>
<Services/>
<ContactUs/>
main>
<Footer/>
div>
每个子组件刚开始先用个Div占位,具体实现先不管。
架子搭好后,再去细化每个子组件的实现,如果子组件很复杂,利用同样的方式将其拆分,然后逐个实现。相比上来就实现一个超大的功能,这样的实现更加简单可执行,也方便我们看到自己的任务进度。
可以看到,我们实现组件拆分的目的,并不是为了组件的复用(复用也是组件化拆分的一个主要目的),而是为了更好地呈现功能的结构,实现关注点的分离,增强可读性和可维护性,同时通过这种拆分,将复杂的大任务变成可执行的小任务,更容易完成且能看到进度。
总结
前端单个文件代码建议不超过300行,最大上限为500行,严禁超过100行。
应该面向结构编程,而不是面向细节编程,要能看到一个组件的主线任务,而不被其中的实现细节干扰,实现关注点分离。
将大任务拆分为可执行的小任务,先进行占位,后逐个实现。
来源:juejin.cn/post/7431575865152618511