当前位置: 首页 > 工具软件 > dry > 使用案例 >

一文搞懂DRY原则

诸葛令
2023-12-01

定义

不要重复自己

注意:重复的代码不一定违反DRY原则,不重复的代码也有可能违反DRY原则

三种典型场景

代码逻辑重复
功能语义重复
代码重复执行

代码逻辑重复

比如UserService有两个方法,一个IsValidUserName校验用户名,一个IsValidPassword校验密码

type UserService2 struct{}

func (s *UserService2) IsValidUserName(username string) bool {
	if username == "" {
		return false
	}
	//只包含a~z、0~9、.
	for i := range username {
		u := username[i]
		if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9') || u == '.') == false {
			return false
		}
	}
	return true
}

func (s *UserService2) IsValidPassword(password string) bool {
	if password == "" {
		return false
	}
	//只包含a~z、0~9、.
	for i := range password {
		u := password[i]
		if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9') || u == '.') == false {
			return false
		}
	}
	return true
}

这里IsValidUserName和IsValidPassword两个方法的代码逻辑是一样的,但是它们的功能职责是不一样的,所以我们不认为违反DRY原则,不需要合并这两个方法为一个方法

因为这两个方法的代码逻辑相同可能是暂时的,后续比如用户名校验的规则变了,那么用户名校验的代码就需要修改,如果合成一个方法那么又需要将方法拆开

功能语义相同

比如IpUtil有两个校验Ip是否有效的方法,一个IsValidIp方法,一个CheckIfIpValid方法
IsValidIp方法采用正则表达式实现校验,CheckIfIpValid方法使用系统自带的类库实现校验,这两个方法虽然代码逻辑不一样,但是功能语义一样,我们认为违反DRY原则

我们应该统一Ip校验逻辑,统一为一个校验方法,这样在需要修改Ip校验规则时只需要修改那唯一一个方法即可,不会出现遗漏修改方法的情况,避免出现问题

代码执行多次

比如UserService有一个login方法,用来判断用户是否存在,如果存在那么返回用户信息

type UserInfo struct {
	id   string
	name string
}

type UserRepo struct{}

func (r *UserRepo) CheckUserExist(username string) bool {
	if username == "" {
		return false
	}
	//调用DB查询username是否存在
	return true
}

func (r *UserRepo) GetUserByUserNameAndPassword(username string, password string) *UserInfo {
	if username == "" {
		return nil
	}
	if password == "" {
		return nil
	}
	//调用DB根据username、password查询用户信息
	return &UserInfo{
		id:   "1",
		name: "Test",
	}
}

type UserService3 struct {
	userRepo *UserRepo
}

func (s *UserService3) Login(username string, password string) *UserInfo {
	if s.userRepo.CheckUserExist(username) {
		return nil
	}
	return s.userRepo.GetUserByUserNameAndPassword(username, password)
}

上述代码存在两处重复执行的地方,违反DRY原则

  1. CheckUserExist、GetUserByUserNameAndPassword这两个方法里都执行了username == “” 判断,如果这两个方法没有其他地方调用,我们可以将username == ""这个判断逻辑上移到Login方法里面
  2. Login方法不需要调用CheckUserExist方法,只需要调用GetUserByUserNameAndPassword就可以判断用户是否存在,减少DB查询IO操作,提高效率

代码复用、代码可复用性、DRY原则概念定义

代码复用:是一种行为,写新需求的时候尽量复用原来已存在的代码
代码可复用性:是指代码可被复用的能力,我们写代码的时候,要尽量让代码可复用
DRY原则:是一个设计原则,不要写"重复"的代码,这里的重复有多层含义

尽管这三者在概念上有区别,但是它们的目的都是减少代码量,提高可读性、可维护性。除此之外,复用线上已经验证过没问题的老代码,出现bug的可能性也比自己写要低

怎么提高代码的可复用性

  1. 减少代码耦合
    高度耦合的代码,如果我们希望复用其中的一个功能,将这个功能抽成单独的模块、类或者函数,那么影响的面就比较大,容易牵一发而动全身,移动一点代码需要修改很多依赖的代码
  2. 保证单一职责原则
    让模块、类的功能职责保持单一,这样它依赖的和依赖它的其他模块、类就比较少,代码的耦合度就低
  3. 模块化
    要善于将功能独立的代码,封装成单独的模块。独立的模块就像一块一块积木,更加容易复用,可以直接拿来搭建起更复杂的系统
  4. 业务和非业务逻辑分离
    越是和业务无关的代码越容易复用,我们可以将和业务无关的代码抽成单独的框架、类库、组件等
  5. 通用代码下沉
    从分层的角度来看,越下层的代码越通用会被更多的模块调用,越应该设计的足够复用。分层后,只允许上层调用下层以及同层之间的调用,不允许下层调用上层
  6. 继承、多态、抽象、封装
    继承:就是复用父类的属性和方法
    多态:可以动态替换某一段代码逻辑,复用其他不变的逻辑
    抽象:越抽象越不依赖底层实现,越容易复用
    封装:将功能独立的代码封装成单独的模块,隐藏实现细节,对外暴露稳定的接口,越容易复用
  7. 应用模板方法等设计模式
  8. 写代码的时候一定要具备复用意识,如果某段代码觉得可以复用,那么就应该将这段代码抽成单独的模块、类或者函数进行复用

如何应用DRY原则

灵活应用DRY原则
可以采用"Rule Of Three"原则,第一次写代码的时候,如果当下没有复用的需求,未来复用的需求也不明确,那么就不需要考虑代码的可复用性;第二次写代码的时候,发现之前某段代码可以复用,那么就可以将这段代码抽成单独的模块、类或者函数进行复用

 类似资料: