在工作中时常会遇到一些很随意的代码,有代码洁癖的我会尝试修改一下,一般我的优化方向是:提升性能或是减少代码冗余,往往两者不可兼得,需要有所取舍。下面例子是我的一些总结,仅供参考。欢迎大家留言讨论指教。
1、分页代码
这是大家常用的分页查询,组装分页对象。
总结:主要是提升代码的可读性,抽取通用的逻辑,封装成pageData对象的方法(之所以放在对象而不是工具类中,是为了内聚和单一职责)。
原始代码
var pageData PageData
records, total := service.FindList(req.PageNum, req.PageSize)
pageData.TotalRecord = total
if total%req.PageSize == 0 {
pageData.TotalPage = total / req.PageSize
} else {
pageData.TotalPage = (total / req.PageSize) + 1
}
pageData.Records = records
type PageData struct {
TotalRecord int64 `json:"totalRecord"` //总记录数
TotalPage int64 `json:"totalPage"` //总页数
Records any `json:"records"` //数据list
}
优化后代码
type PageData struct {
TotalRecord int64 `json:"totalRecord"` //总记录数
TotalPage int64 `json:"totalPage"` //总页数
Records any `json:"records"` //数据list
}
// SetTotal 赋值记录条数,分页条数
func (p *PageData) SetTotal(totalCount int, pageSize int) {
var value = (totalCount + pageSize - 1) / (pageSize)
p.TotalRecord = int64(totalCount)
p.TotalPage = int64(value)
}
//直接使用封装的方法 赋值
pageData.SetTotal(total, req.PageSize)
pageData.Records = records
2、枚举值使用
当有重复的东西时,可以抽取成通用的,比如下面的switch可以提取为预定义的map。只在一个地方维护状态枚举值。
总结:一是避免重复写状态枚举值映射;二是方便维护,当扩展枚举值时,只需修改一个地方(提高代码维护性)。
原始代码
type AuditStatus int
// 定义审核状态枚举值
const (
Audit_UnReviewed AuditStatus = 0 // 待审批
Audit_InReview AuditStatus = 1 // 审核中
Audit_Approved AuditStatus = 2 // 已通过
Audit_Rejected AuditStatus = 3 // 已驳回(可重新提交)
Audit_Disapproved AuditStatus = 4 // 不通过(不可重新提交)
)
func (s AuditStatus) String() string {
switch s {
case Audit_UnReviewed:
return "待审批"
case Audit_InReview:
return "审核中"
case Audit_Approved:
return "已通过"
case Audit_Rejected:
return "已驳回"
case Audit_Disapproved:
return "不通过"
default:
return ""
}
}
// 返回所有审核状态枚举值
func AuditStatusMap() map[int]string {
return map[int]string{
int(Audit_UnReviewed): "待审批",
int(Audit_InReview): "审核中",
int(Audit_Approved): "已通过",
int(Audit_Rejected): "已驳回",
int(Audit_Disapproved): "不通过",
}
}
优化后代码
// 预定义状态映射表
var auditStatusText = map[AuditStatus]string{
Audit_UnReviewed: "待审批",
Audit_InReview: "审核中",
Audit_Approved: "已通过",
Audit_Rejected: "已驳回",
Audit_Disapproved: "不通过",
}
func (s AuditStatus) String() string {
if text, ok := auditStatusText[s]; ok {
return text
}
return ""
}
// 返回所有审核状态枚举值
func AuditStatusMap() map[int]string {
m := make(map[int]string, len(auditStatusText))
for status, text := range auditStatusText {
m[int(status)] = text
}
return m
}
3、保护内部的属性(如array、slice)
内部的数据,有时为了保护属性不对外直接暴露,不建议直接返回内部引用,如果不希望调用者去更新,也可以返回不可变对象。
原始代码
var clientList []*ChatClient
var mutex sync.Mutex
func GetList() []*ChatClient {
return clientList
}
func UnRegisterClient(client *ChatClient) {
mutex.Lock()
defer mutex.Unlock()
for i, c := range clientList {
if c == client { // 直接比较指针是否相同
// 从切片中删除元素
clientList = append(clientList[:i], clientList[i+1:]...)
break
}
}
}
优化后代码
var clientList []*ChatClient
var spareUsers []string
func GetList() []*ChatClient {
list := make([]*ChatClient, len(clientList))
copy(list, clientList)
return list
}
假设要使用这个GetList()方法: 当要停止全部的clientList时: 旧代码中,由于GetList()直接暴露了clientList,range时操作clientList会出现bug。 新代码中,由于GetList()返回的是副本,所以range时操作,不会影响clientList,代码执行符合预期。
//全部停止执行
func Stop(ctx *gin.Context) {
for _, client := range script.GetList() {
script.UnRegisterClientclient(client)
}
ctx.JSON(200, gin.H{
"isOk": true,
"msg": "操作成功",
})
}
4、减少对象的创建
5、抽取方法,一个方法实现一个功能
旧代码如下:缺点是 一个方法里面包含了多个逻辑,不易维护。
/**
* 审批号生成器
*/
public class AutoFlowNoGenerator {
static final Map<String,String> last = new ConcurrentHashMap<>();
//审批号生成
public synchronized static String autoFlowNo(@NotNull String processDefinitionKey) {
var prefix = "";
//生成编号前缀
var split = processDefinitionKey.split("_");
if (split.length == 1) {
prefix = split[0].substring(0, 2);
} else {
prefix = split[0].charAt(0) + "" + split[1].charAt(0);
}
//生成编号,根据当前时间戳毫秒值生成
var autoNo = prefix.toUpperCase() + MyIdGenerator.idByTimestamp();
//如果有重复则重新生成
if (Objects.equals(autoNo, last.get(prefix))) {
return autoFlowNo(processDefinitionKey);
} else {
last.put(prefix, autoNo); //更新值
}
return autoNo;
}
/**
* 测试
*/
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
var flowNo = autoFlowNo("ask_leave");
System.out.println(flowNo); //ALxxxxxxxxxx
}
}
}
优化后的代码如下:
//审批号生成
public synchronized static String autoFlowNo(@NotNull String processDefinitionKey) {
//将编号前缀生成逻辑提取为方法
var prefix = getPrefix(processDefinitionKey)
while (true) {
var autoNo = prefix + MyIdGenerator.idByTimestamp();
if (!Objects.equals(autoNo, last.get(prefix))) {
last.put(prefix, autoNo); //更新值
return autoNo;
}
}
}
// 审批号前缀。可以把prefix缓存,但是为了简单直接去生成
private static String getPrefix(@NotNull String processDefinitionKey) {
var prefix = "";
var split = processDefinitionKey.split("_");
if (split.length == 1) {
prefix = split[0].substring(0, 2);
} else {
prefix = getFirstChar(split[0]) + "" + getFirstChar(split[1]);
}
return prefix.toUpperCase();
}
//安全获取字符串的第一个字符,如果字符串为空则返回默认字符
static char getFirstChar(@NotNull String str) {
return str.isEmpty() ? '-' : str.charAt(0);
}
优化的思路:
旧代码的一个方法里面的逻辑全在一个大方法里面,代码不直观导致后期维护或者扩展时代码腐化。
将前缀逻辑提前成为单独的getPrefix(),将获取首字母的逻辑提取为单独的getFirstChar();
原以为缩小synchronized锁的粒度,会提高性能,结果压测之后发现直接在方法上修饰synchronized性能更好。