代码优化-简单例子

67 阅读5分钟

在工作中时常会遇到一些很随意的代码,有代码洁癖的我会尝试修改一下,一般我的优化方向是:提升性能或是减少代码冗余,往往两者不可兼得,需要有所取舍。下面例子是我的一些总结,仅供参考。欢迎大家留言讨论指教。

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性能更好。