您的位置  > 互联网

(干货)如何理解clean代码?——,代码

1. 基本规格 (1) 常数

常量必须是命名的,在进行逻辑判断时不允许直接比较未命名的常量。

  switch(num){
case 1:
...
case 3:
...
case 7:
...
}

if(x === 0){
...
}

上面的例子中,我不知道1 3 7对应的含义是什么,这种写法基本看不懂。

    enum DayEnum {
oneDay = 1,
threeDay = 3,
oneWeek = 7,
}
let num = 1;
switch(num){
case DayEnum.oneDay:
...
case DayEnum.threeDay:
...
case DayEnum.oneWeek:
...
}


const RightCode = 0;
if(x === RightCode)

从上面正确的写法可以看出,常量是有命名的。 在进行if、if等逻辑判断时,我们可以从变量名中知道常量的具体含义,增加了可读性。

(2) 枚举

除了常量枚举之外,在编译阶段,枚举会生成一个对象,如果不是字符串枚举,甚至会生成一个双向对象。 因此,在我们的业务代码中,有了枚举,就不需要枚举值相关的数组了。

enum FruitEnum {
tomato = 1,
banana = 2,
apple = 3
}

const FruitList = [
{
key:1,
value: 'tomato'
},{
key:2,
value: 'banana'
},{
key:3,
value: 'apple'
}
]

这里错误的原因是冗余。 如果我们想要得到一个,我们不需要新的。 相反,我们可以直接根据枚举生成一个数组。 原理就是我们之前提到的枚举。 除了常量枚举之外,在编译时还会生成一个map对象。

enum FruitEnum {
tomato = 1,
banana = 2,
apple = 3
}
const FruitList = Object.entity(FruitEnum)

以上是正确的写法。 这种写法不仅不冗余,而且如果修改了枚举的类型,我们只需要直接修改枚举,派生的数组也会发生变化。

另外,字符串枚举值和字符串也有本质的区别。 定义类型时请小心,否则你编写的代码将是多余的。

enum GenderEnum{
'male' = '男生',
'female' = '女生'
}
interface IPerson{
name:string
gender:string
}
let bob:IPerson = {name:"bob",gender:'male'}

<span>{Gender[bob.gender as keyof typeof GenderEnum]}</span>

出现上述错误的原因是类型定义不应该是,而应该是枚举键。 因此,在转换枚举值时,必须添加as keyof的断言。

enum GenderEnum{
'male' = '男生',
'female' = '女生'
}
interface IPerson{
name:string
gender:keyof typeof GenderEnum
}
let bob:IPerson = {name:"bob",gender:'male'}

<span>{Gender[bob.gender]}</span>

以上是正确的写法。 字符串枚举和字符串类型之间有明显的区别。 当变量需要使用枚举时,不能将其定义为。

(3) ts- 和任何

中应严格禁止使用 ts-。 ts- 是影响代码质量最重要的一个因素。 关于any,我曾经想在项目中禁止any,但是有些场景是必须使用any的,所以并没有粗暴禁止使用any。 但在大多数情况下,您可能不需要使用任何。 需要使用any的场景可以具体情况具体分析。

 //@ts-ignore 
import Plugin from 'someModule' //如果someModule的声明不存在
Plugin.test("hello world")

以上就是ts-最经典的使用场景。 如果按上述方式使用 ts-,则类型将被视为 any。 正确的做法是通过方法自定义你需要使用的类型。

import Plugin from 'someModule'
declare module 'someModule' {
export type test = (arg: string) => void;
}

语句可以在内部定义,同名的语句遵循一定的合并原则。 如果你想扩展第三方模块,这非常方便。

在大多数情况下,您不需要使用任何。 在某些场景下,如果无法立即确定某个值的类型,我们可以使用any。

Any都会完全失去类型判断,这其实是相当危险的。 使用any就相当于放弃了类型检测,基本上就是放弃了。 例如:

let fish:any = {
type:'animal',
swim:()=> {

}
}
fish.run()

在上面的例子中,我们调用了一个不存在的方法。 因为使用了any,所以跳过了静态类型检查,所以是不安全的。 运行时会出现错误。 如果不能立即确定值的类型,我们可以使用any 代替。

let fish:unknown = {
type:'animal',
swim:()=> {

}
}
fish.run() //会报错

是任何类型的子类型,因此与any一样,可以分配任何类型。 与any 不同,变量必须有自己的类型。 只有在类型收缩或类型断言之后,变量才能使用其上定义的方法和变量。

简单来说,使用前需要强制判断其类型。

(4)

在代码中,尤其是面向业务的开发中,基本上不需要它。 另外,ES6(next)中自然也支持它。 另外,es也成为了语言级别的规范,因此也是官方推荐的。

简单来说,它是一个全局对象。 当然我们也可以把它放在中间,但是放在中间也存在问题。

//在一个shapes.ts的模块中使用

export namespace Shapes {
export class Triangle {
/* ... */
}
export class Square {
/* ... */
}
}

//我们使用shapes.ts的时候
//shapeConsumer.ts

import * as shapes from "./shapes";
let t = new shapes.Shapes.Triangle(); // shapes.Shapes?

export class Triangle {
/* ... */
}
export class Square {
/* ... */
}

以上直接使用才是正确的方法。 它可以避免模块系统本身变量命名的重复,因此没有意义。

(5)限制函数参数个数

定义函数时,应减少函数参数的数量。 建议不要超过3个。

function getList(searchName:string,pageNum:number,pageSize:number,key1:string,key2:string){
...
}

不建议函数参数超过3个,当参数超过3个时,应使用对象进行聚合。

interface ISearchParams{
searchName:string;
pageNum:number;
pageSize:number;
key1:string;
key2:string;
}

function getList(params:ISearchParams){

}

React项目也是如此,道理也一样

const [searchKey,setSearchKey] = useState('');
const [current,setCurrent] = useState(1)
const [pageSize,setPageSize] = useState(10) //错误的写法

const [searchParams,setSearchParams] = useState({
searchKey: '',
current:1,
pageSize:10
}) //正确的写法

(6) 模块尽量保证没有副作用

请不要使用模块作为副作用。 确保模块先用后用。

//Test.ts
window.x = 1;
class Test{

}
let test = new Test()


//index.ts
import from './test'
...

上述index.ts中的模块在test.ts文件中被调用。 该方法是一个有副作用的模块。

正确的方法应该是保证模块非变量的纯度,调用者在使用模块时必须先调用再调用。

//test.ts
class Test{
constructor(){
window.x = 1
}

}
export default Test

//index.ts
import Test from './test'
const t = new Test();

(7) 禁止使用!.non-null断言

非空断言本身是不安全的,主观判断存在错误。 从防御性编程的角度来看,不建议使用非空断言。

let x:string|undefined = undefined
x!.toString()

因为使用了非空断言,所以编译时不会报错,但运行时会报错。

比较推荐使用。 的形式。

(8) 使用的内置函数

许多内置函数可以重用某些定义。 这里我就不一一介绍了。 常见的有,Pick,Omit,,,infer等。如果需要从现有类型派生新类型,使用内置函数简单方便。

还可以使用并集类型、交集类型和类型合并。

//基本类型
let x:number|string
x= 1;
x = "1"

//多字面量类型 
let type:'primary'|'danger'|'warning'|'error' = 'primary'

值得注意的是文字的赋值。

let type:'primary'|'danger'|'warning'|'error' =  'primary'

let test = 'error'
type = test //报错

let test = 'error' as const
type = test //正确

interface ISpider{
type:string
swim:()=>void
}
interface IMan{
name:string;
age:number;
}
type ISpiderMan = ISpider & IMan
let bob:ISpiderMan = {type:"11",swim:()=>{},name:"123",age:10}

最后说一下类型合并,这是一种极不推荐的方法。 在业务代码中,不建议使用类型合并,这样会增加代码的阅读复杂度。

类型合并存在于很多地方。 类之间可以进行类型合并,等等,举个例子:

interface Box {
height: number;
width: number;
}

interface Box {
scale: number;
}

let box: Box = { height: 5, width: 6, scale: 10 };

对于上述同名的 Box 将会发生类型合并。 不仅and可以进行类型合并,class and、class and等也可以进行同名类型合并。 我个人不建议在业务代码中使用类型合并。

(9) 封装ts的条件语句和类型保护

if (fsm.state === 'fetching' && isEmpty(listNode)) {
// ...
}

function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}

正确的写法是,我们将条件判断的逻辑封装成一个独立的函数。 这种写法可读性更强,我们从函数名就可以知道做了什么判断。

另外,封装的条件语句还可以链接到ts的自定义类型保护。 让我们看一下封装条件语句的最简单的自定义类型保护。

function IsString (input: any): input is string { 
return typeof input === 'string';
}
function foo (input: string | number) {
if (IsString(input)) {
input.toString() //被判断为string
} else {

}
}

在项目中合理使用自定义守卫可以帮助我们减少很多不必要的类型断言,提高代码的可读性。

(10) 不要使用非变量

无论是变量名还是函数名,请不要使用未命名的名称。 我在工作中也遇到过这个问题。 后端以未命名的形式定义了一个变量:

let isNotRefresh = false  //是否不刷新,否表示刷新

表示不刷新。 这样定义的变量会导致很多与这个变量相关的逻辑被颠倒。 正确的形式应该是定义一个变量来表示是否刷新。

let isRefresh = false  //是否刷新,是表示刷新

2. 函数式

就我个人而言,我强烈推荐函数式编程。 我主观上认为链式调用比回调更好,函数式方法比链式调用更好。 近年来,函数式编程越来越流行,许多开源库如、RxJS、等都使用了函数式特性。 本文主要介绍如何使用它来简化代码。