一句注释引发的思考 - 论代码质量

news2024/12/27 5:07:08

一句注释引发的思考

接到一个有鸡毛信般的紧急需求(当然,002的需求向来是如此紧急的):大屏展示原来只有二个品牌数据,现增加到三个品牌的数据。一句话的需求,且没有业务逻辑变更,我认为可以迅雷不及掩耳之势,2小时收拾干净交差。当我满腔激情的定位的核心逻辑部分时,这样一句注释(见下图),让我顿时思绪天马行空:

这个作者经历了什么样一个撕心裂肺的过程?但是可以肯点的是这一定是一个有想法的作者,不由得心中肃然起敬。

这段代码经历了多少次的蹂躏,才会让作者的心潮有如此波澜?

抑或,这到底提出了怎么样一个需求,让作者需要通过这样的注释来宣泄心中怨气?

注释图

   

  巴拉开代码修改记录,作者已经去别的地方高就了,要不是留了这些代码,实在想不起有这样的一个同事存在过;代码提交记录比较整洁,大部分代码是在5月29号提交,5月30大概是修复bug提交了小部分代码。如此看来,代码没有经历过什么苦难,这里的需求仅仅是每个品牌的门店按订单数量排序(如下图),想想怎么也闹出什么大动静... 再细读作者留下的代码,只能说作者给自己设置了难度系数(这说法太含蓄了),稍微有一点改动,便是牵一发而动全身,于是留了这样一个不太成熟且不太有价值的注释,想起了最近在读的一本书,Bob大叔的《代码整洁之道》,颇有一点不成熟的感触。

如何衡量代码质量

《代码整洁之道》一书开篇第一句话就是一个著名的论断:衡量代码质量的唯一有效标准:WTF/min。 一看到WTF这样的简称就不明觉厉,一顿搜索居然没有找到“标准”的解释,更觉得高深莫测。 也是在后来一个偶然的时间看到原来是 What-The-Fuck 的缩写,看重看这张经典的图,一下子恍然顿悟:原来如此,就该如此。

原本学得这是一本好书,甚至觉得大部分程序都应该去阅读这类的书籍一遍(比如 马丁·福勒出品的《重构-改善既有代码的设计》)。 看到这样的论述,更觉此书接地气,仿佛与大师拉近了距离了。回想起过往种种,以及最近修改历史代码时的反应,没什么比WTF更有表达力了。

 再回到开篇讲的注释,当时的作者必然也是有类似的反应,只是他的吐嘲对象是他自己,或者他只会认为这是需求的而不是代码的问题。所以 WTF/min作为衡量代码质量的唯一有效标准,还得加一个定语:优秀的Coder喊出的WTF次数,才是真正的标准。 至于如何为优秀的代码,在《代码整洁之道》,《重构-改善既有代码的设计》等经典书籍里都有详细的描述:

  • 从变量命名,到注释,到方法,到类,到模块都有非常详细的规范;
  • 从抽象到边界,到依赖也有完整的方法论;
  • 从SOLID设计原则到,组件相关的 CRP,CCP,CRP等原则都有从理论到落地的解说。

 坦白讲,读完这些书后,我感觉自己原来写的代码,很多都缺乏这样的思考,也有不少代码相当丑陋,甚至觉得:在code这件事让,只能算得上初窥门径。于是越时读书,越觉得自己无知。最近读技术书籍的间歇,顺便翻读了几本名人传记:奥本海默为了让人觉得他是天才,总是"偷偷"的读书学习;特斯拉甚至在病危之际也是一心读书;让我们有在剑手的钱学森利用所有空闲时间阅读方能一年完成硕士学位,并获得航空和数学双博士,成本最年轻的终生教授...(省略好多荣誉)。他们无一不是通过自己努力读书,思考,实践终成我等学习的楷模。最近招聘的经历中,大部分应聘的人几乎不看技术书籍的了,也是让我捉摸不透。

读技术书籍没用了吗

最近两月一直忙于面试,沟通了没有100个也有80个候选人,大部分人都没有了读书习惯,更不用说技术书籍了,倒是有部分说觉得blog比书籍有用多了。有些说工作太忙,有得说没有用....他们中有的在传统公司朝9晚5,有的在互联网企业996,有从小企业过来的,也有从阿里,快手过来的... 其中一个人的话,让我记忆非常深刻,最后环节,我问他现在还读书不,他说为了进阿里,他非常努力了2年,把《深入理解java虚拟机》读了2遍,《高性能mysql》,《深入理解kafka》... 都仔细阅读了,后来进了阿里,觉得这些书都没有没啥用了,也不在阅读其他技术书籍了,最后我不知道应该说啥,毕竟我没去过阿里,于是结束了面试。就我自己而言,早些年面试也是被问得体无完肤,也有过这样的心态阅读了大量类似 《深入理解java虚拟机》,《MySQL技术内幕:InnoDB存储引擎》,《RocketMQ技术内幕:RocketMQ架构设计与实现原理》,《从Paxos到Zookeeper:分布式一致性原理与实践》等书籍,确实也让自己在后来的面试中可以从容面对。但越是阅读,越是感觉自己不知道的东西越多,越是想要通过阅读来充实自己。于是又开始阅读系统设计,架构一类的书籍。时至今日,读到《代码整洁之道》时,依然觉得即使在做了10年的编码这件事上,不懂的依然有非常之多(哈哈,也许是悟性不够)。 从前面的名人,到我身边认识的人,大凡优秀的人的,独有阅读的习惯,并且有大量阅读自己专业相关的书籍。

最近和媳妇探讨读书这件事儿,说我周末在家除了溜娃就是刷新闻,现如今的新闻包括热搜又大部分是没有“营养”的,也聊到目下短视频盛行,下到3岁,上到70,地铁上,公园里,城市里,老家里... 到处都是,当然也包括我们自己的爸妈,谈及此,心中不觉升起一股名族忧心(哈哈,操心有点多了)。于是我放下了新闻,当然了周5晚上,等娃睡着后,我们买些宵夜,找一部金典电影还是保持着。 一段时间后,发现一个周阅读10几个小时好像也挺正常的,阅读成生活的一部分。也没有了过去那种读了多久了,要休息下的,看看新闻,看看电视的想法了。现在想来,读书也好,新闻,短视频也罢,本质且没啥差异,内心富足就好。

再回到前面的面试,也不知道,我在面试过程中,把读书这一块看得如此重,是否合适,但是我相信:喜欢阅读技术书籍的人,应该都不会太差。 

回到前面的代码

回家开篇的注释问题,想和大家一直分享下代码重构过程,如果不幸被作者看到,希望不要介怀,就如Bob大叔所讲,每个程序员都应该接专业眼光的检视(哈哈也许我也不是那么专业)。 需求比较简单,就是两个品牌下的门店根据订单数排序。 现在的需求是增加了第三个品牌,门店信息有品牌属性。

如果作者阅读了Clean Code ,他就会明白代码走向整洁的4原则:

  • 运行所有测试;
  • 不可重复;
  • 表达了程序员的意图;
  • 尽可能减少类和方法的数量。

就会把排序算法抽离出来,与业务逻辑分离,避免大量重复;

如果他深刻喊出了 Don't Repeat YourSelf, 就不会有么多 ConsultationOrderRank 对象的出事化,甚至不会单独处理有数据与没数据的情况。

如果作者阅读了Clean Architecture,他就会明白要面向抽象,而不是具体去编程,

他就会面向品牌这个概念去编程,而不是面向具体的品牌1,品牌2去实现。

//需求变更,改的像辣鸡。
if (CollectionUtils.isEmpty(orderList)) {
            List<CfgStore> allStoreList = cfgStoreService.getStoresBLAndBabyBL();
            List<CfgStore> bellaList = allStoreList.stream().filter(st -> {
                return st.getType() == 0;
            }).sorted(Comparator.comparingInt(CfgStore::getStoreId)).collect(Collectors.toList());

            ArrayList<ConsultationOrderRank> ballaResult = new ArrayList<>();
            int bellaIndex = 0;
            for (CfgStore store : bellaList) {
                ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank();
                consultationOrderRank.setStoreName(store.getNameAlias());
                consultationOrderRank.setStoreId(store.getStoreId());
                consultationOrderRank.setOrderNum(0);
                consultationOrderRank.setSort(bellaIndex);
                ballaResult.add(consultationOrderRank);
                bellaIndex++;
            }
            List<ConsultationOrderRank> blRankResult = ballaResult.stream()
                    .sorted(Comparator.comparing(ConsultationOrderRank::getSort)).collect(Collectors.toList());

            List<CfgStore> babyBellaList = storeList.stream().filter(st -> {
                return st.getType() == 1;
            }).sorted(Comparator.comparingInt(CfgStore::getStoreId)).collect(Collectors.toList());

            ArrayList<ConsultationOrderRank> babyBallaResult = new ArrayList<>();
            int babyIndex = 0;
            for (CfgStore store : babyBellaList) {
                ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank();
                consultationOrderRank.setStoreName(store.getNameAlias());
                consultationOrderRank.setStoreId(store.getStoreId());
                consultationOrderRank.setOrderNum(0);
                consultationOrderRank.setSort(babyIndex);
                babyBallaResult.add(consultationOrderRank);
                babyIndex++;
            }

            List<ConsultationOrderRank> babyRankResult = babyBallaResult.stream()
                    .sorted(Comparator.comparing(ConsultationOrderRank::getSort))
                    .collect(Collectors.toList());
            Order order = Order.builder().consultationOrderRankStBellaList(blRankResult)
                    .consultationOrderRankBabyBellaList(babyRankResult).build();

            return order;
        }

        List<CfgStore> others = storeList.stream().filter(store -> {
            return !Arrays.stream(storeIdArr).collect(Collectors.toList()).contains(store.getStoreId());
        }).collect(Collectors.toList());

        Map<Integer, CfgStore> storeMap = storeList.stream().collect(Collectors.toMap(CfgStore::getStoreId, store -> {
            return store;
        }));

        //品牌1门店ID
        List<Integer> blIdList = storeList.stream().filter(st -> st.getType().equals(0))
                .map(CfgStore::getStoreId).collect(Collectors.toList());
        //品牌2门店ID
        List<Integer> babyblIdList = storeList.stream().filter(st -> st.getType().equals(1))
                .map(CfgStore::getStoreId).collect(Collectors.toList());

        //品牌2分组数据
        Map<Integer, List<HeOrder>> babyblMap = orderList.stream()
                .filter(order -> babyblIdList.contains(order.getStoreId()))
                .collect(Collectors.groupingBy(HeOrder::getStoreId));
        //品牌2分组数据
        Map<Integer, List<HeOrder>> blMap = orderList.stream()
                .filter(order -> blIdList.contains(order.getStoreId()))
                .collect(Collectors.groupingBy(HeOrder::getStoreId));

        //品牌1排行数据
        List<ConsultationOrderRank> bellaList = new ArrayList<>();
        //品牌2排行数据
        List<ConsultationOrderRank> babyBellaList = new ArrayList<>();
        //品牌1
        for (Entry<Integer, List<HeOrder>> entry : babyblMap.entrySet()) {
            CfgStore cfgStore = storeMap.get(entry.getKey());
            String storeName = cfgStore.getNameAlias();
            if (Strings.isNotBlank(storeName)) {
                List<HeOrder> orderNum = entry.getValue();
                ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank();
                consultationOrderRank.setStoreName(storeName);
                consultationOrderRank.setStoreId(entry.getKey());
                consultationOrderRank.setOrderNum(orderNum == null ? 0 : orderNum.size());
                babyBellaList.add(consultationOrderRank);
            }
        }

        List<CfgStore> otherbabyBlList = others.stream().filter(store -> {
            return store.getType() == 1;
        }).collect(Collectors.toList());

        for (CfgStore store : otherbabyBlList) {
            ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank();
            consultationOrderRank.setStoreName(store.getNameAlias());
            consultationOrderRank.setStoreId(store.getStoreId());
            consultationOrderRank.setOrderNum(0);
            babyBellaList.add(consultationOrderRank);
        }

        //品牌2
        for (Entry<Integer, List<HeOrder>> entry : blMap.entrySet()) {
            CfgStore cfgStore = storeMap.get(entry.getKey());
            String storeName = cfgStore.getNameAlias();
            if (Strings.isNotBlank(storeName)) {
                List<HeOrder> orderNum = entry.getValue();
                ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank();
                consultationOrderRank.setStoreName(storeName);
                consultationOrderRank.setStoreId(entry.getKey());
                consultationOrderRank.setOrderNum(orderNum == null ? 0 : orderNum.size());
                bellaList.add(consultationOrderRank);
            }
        }

        List<CfgStore> otherBellaList = others.stream().filter(store -> {
            return store.getType() == 0;
        }).collect(Collectors.toList());

        for (CfgStore store : otherBellaList) {
            ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank();
            consultationOrderRank.setStoreName(store.getNameAlias());
            consultationOrderRank.setStoreId(store.getStoreId());
            consultationOrderRank.setOrderNum(0);
            bellaList.add(consultationOrderRank);
        }
        //品牌1排序
        List<ConsultationOrderRank> blRank = bellaList.stream().sorted(Comparator.comparing(ConsultationOrderRank::getOrderNum).reversed())
                .collect(Collectors.toList());
        int blSort = 0;
        int blIndexCounter = 0;
        for (int i = 0; i < blRank.size(); i++) {
            //订单=0, 订单值不同, 递增
            boolean flag = blRank.get(i).getOrderNum() == 0 || (i != 0 && blRank.get(i).getOrderNum() != blRank.get(i - 1).getOrderNum());
            if (flag) {
                blSort = blSort + blIndexCounter + 1;
                blIndexCounter = 0;
            } else {
                if (i != 0) {
                    blIndexCounter++;
                }
            }
            blRank.get(i).setSort(blSort);
        }

        //品牌2排序
        List<ConsultationOrderRank> babyBlRank = babyBellaList.stream().sorted(Comparator.comparing(ConsultationOrderRank::getOrderNum).reversed())
                .collect(Collectors.toList());
        int babySort = 0;
        int babyIndexCounter = 0;
        for (int i = 0; i < babyBlRank.size(); i++) {
            //订单=0, 订单值不同, 递增
            boolean flag = babyBlRank.get(i).getOrderNum() == 0 || (i != 0 && babyBlRank.get(i).getOrderNum() != babyBlRank.get(i - 1).getOrderNum());
            if (flag) {
                babySort = babySort + babyIndexCounter + 1;
                babyIndexCounter = 0;
            } else {
                if (i != 0) {
                    babyIndexCounter++;
                }
            }
            babyBlRank.get(i).setSort(babySort);
        }
 

 我相信作者如果经常阅读技术书籍,写出的代码应该是这样的。

  //统计每个品牌每个门店订单数量
        for (Integer brandType : brandTypeList){
            Map<Integer, Long> theBrandStoreOrderCount = orderList.stream().filter(order -> brandType.longValue() == order.getBrandType()).collect(Collectors.groupingBy(HeOrder::getStoreId, Collectors.counting()));
            List<CfgStore> brandStores = storeList.stream().filter(store -> store.getType().equals(brandType)).collect(Collectors.toList());

            List<ConsultationOrderRank> storeOrderRank = new ArrayList<>();

            brandStores.forEach(store ->{
                Long orderCount = 0L;
                if (theBrandStoreOrderCount.containsKey(store.getStoreId())){
                    orderCount = theBrandStoreOrderCount.get(store.getStoreId());
                }
                ConsultationOrderRank storeOrder = ConsultationOrderRank.builder()
                        .storeId(store.getStoreId())
                        .orderNum(orderCount.intValue())
                        .storeName(store.getStoreName())
                        .sort(0).build();
                storeOrderRank.add(storeOrder);
            });
            List<ConsultationOrderRank> sortedStoreRank = storeOrderRank.stream().sorted(Comparator.comparing(ConsultationOrderRank::getOrderNum).reversed()).collect(Collectors.toList());

            setSortWithSameRankNum(sortedStoreRank);

            BrandOrderStatistic statistic = BrandOrderStatistic.builder()
                    .name(CfgStoreEnum.getValueByCode(brandType))
                    .storeOrderRank(sortedStoreRank)
                    .brandType(brandType).build();
            brandOrderStatistics.add(statistic);
        }

如此这般,我们当时践行了编码里的童子军规:当你离开营地时候,要让它比你来的时候更整洁干净。

成为一名优秀的程序员!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1892578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LineageOs-21.0系统编译问题

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

如何使用 3D 建模库在 C# 中将 3DS 转换为 USDZ?

USDZ/USD是一种 3D 文件格式&#xff0c;被广泛用于跨平台共享 3D 资产。另一方面&#xff0c;3DS是另一种以块形式存储数据的 3D 文件格式。在某些情况下&#xff0c;您需要将3DS 文件转换为 USDZ/USD文件格式。因此&#xff0c;本篇博文介绍了一个功能丰富的3D 建模库&#x…

MySQL—常用的数据类型

数据类型 整型 1.创建一个含有无符号/有符号整型的字段的表 CREATE TABLE L1(id tinyint unsigned #无符号 ) CREATE TABLE L2(id tinyint #默认为有符号 ) 数值型(bit) 2.数值型(bit)的使用 小数 3.数值型(小数)的基本使用 字符串 4.字符串的基本使用 #演示字符串类型…

urfread刷算法|构建一棵树

大意 示例标签串&#xff1a; 处理结果&#xff1a; 题目1 根据标签串创建树 需求 需求&#xff1a;给出一个字符串&#xff0c;将这个字符串转换为一棵树。 字符串可以在代码里见到&#xff0c;是以#开头&#xff0c;按照\分割的字符串。 你需要将这个字符串&#xff0…

详解COB封装的定义

COB封装全称是Chip on Board&#xff08;板上芯片封装&#xff09;&#xff0c;是一种非常先进的电子封装工艺&#xff0c;其会涉及到将发光芯片直接封装于印刷电路板&#xff08;PCB&#xff09;或者其他类型的互连电气基板上&#xff0c;通过细小的金属线进行键合&#xff0c…

golang出现panic: runtime error: index out of range [0] with length 0(创建n阶矩阵时)

本打算创建一个n阶的二维数组&#xff1a;以下两种情况都试了但都会出现如图片中的错误 ans : make([][]int, n)//① var ans [][]int //② 原因是初始化问题&#xff1a; 虽然创建了切片 ans&#xff0c;但是没有初始化其内部的切片。这会导致在尝试访问 ans[i][j] 等位置时出…

还是NC,项目代码开源|scRNA+bulkRNA+因子分析验证地塞米松治疗Covid19

说在前面 平时发文章的话&#xff0c;做药物用的大多都是仅仅是GEO的bulkRNA&#xff0c;有人的有鼠的&#xff0c;然后做做流水线分析&#xff0c;最后面PCR。今天看一篇发NC的工作量&#xff0c;怎么用转录组分析做药物的转化免疫学 这篇文章作者已经上传Github了&#xff…

vue3中使用弹幕组件vue-danmaku

1、最开始使用的是vue3-marquee&#xff0c;后面发现一直有一个bug无法解决&#xff0c;就是鼠标hover到第一个弹幕上字体就会变粗&#xff0c;已经提了issue给作者&#xff0c;但是目前还未答复&#xff0c;所以就换了方案。 地址如下&#xff1a; https://github.com/megasa…

Zoom视颊会议软件使用

GPT-3.5 (OpenAI) Zoom是一款极受欢迎的视频会议软件。使用Zoom可以方便地进行视频会议、远程授课、在线研讨会等活动。以下是Zoom的使用步骤&#xff1a; 1. 下载Zoom客户端 可以在Zoom官网上下载对应平台的Zoom客户端。下载并完成安装后&#xff0c;双击打开客户端。 2. 创建…

用Python制作动态钟表:实时显示时间的动画

文章目录 引言准备工作前置条件 代码实现与解析导入必要的库初始化Pygame绘制钟表函数主循环 完整代码 引言 动态钟表是一种直观且实用的UI元素&#xff0c;能够实时显示当前时间。在这篇博客中&#xff0c;我们将使用Python创建一个动态钟表&#xff0c;通过利用Pygame库来实…

动手学Avalonia:基于SemanticKernel与硅基流动构建AI聊天与翻译工具

Avalonia是什么&#xff1f; Avalonia是一个跨平台的UI框架&#xff0c;专为.NET开发打造&#xff0c;提供灵活的样式系统&#xff0c;支持Windows、macOS、Linux、iOS、Android及WebAssembly等多种平台。它已成熟并适合生产环境&#xff0c;被Schneider Electric、Unity、Jet…

高薪程序员必修课-Java中 ReentrantLock的公平锁和非公平锁底层实现原理

目录 前言 公平锁&#xff08;Fair Lock&#xff09; 原理 实现 示例代码 底层实现 非公平锁&#xff08;Non-Fair Lock&#xff09; 原理 实现 示例代码 底层实现 比较与选择 总结 ⭐️ 好书推荐 前言 在Java中&#xff0c;ReentrantLock 提供了公平锁和非公平锁…

详解 RisePro 信息窃密木马

RisePro 是一种窃密木马&#xff0c;以恶意软件即服务&#xff08;MaaS&#xff09;的模式在地下论坛出售。该恶意软件家族最早在 2022 年被发现&#xff0c;近期攻击行为快速增长。 RisePro 不依赖特定的感染媒介&#xff0c;可以通过多种方式植入失陷主机&#xff0c;通常使…

抖音本地生活服务商入驻流程须知指南!

近日&#xff0c;抖音发布关于新增《【到家外卖】内容服务商开放准入公告》的意见征集通知&#xff08;以下简称“通知”&#xff09;&#xff0c;并在其中公布了抖音外卖服务商入驻的一系列申请条件。在此背景下&#xff0c;许多想要成为抖音本地生活服务商的创业者在关注抖音…

java对象的访问定位的两种方式

句柄访问&#xff1a;reference中存储的稳定&#xff0c;对象实例位置改变&#xff0c;只需要改变句柄池中的对象实例指针 直接指针访问&#xff1a;HotSpot方式&#xff1a;效率高

【实验室优选】PP比色管 带刻度 聚丙烯试管 化学实验专用

PP比色管是一种实验室常用的容器&#xff0c;通常用于化学分析、比色实验、样品储存等。 以下是关于PP比色管的一些基本信息&#xff1a; 1. 材质&#xff1a; PP比色管由聚丙烯&#xff08;Polypropylene&#xff0c;简称PP&#xff09;材料制成&#xff0c;这种材料具有较高…

14-4 深入探究小型语言模型 (SLM)

大型语言模型 (LLM) 已经流行了一段时间。最近&#xff0c;小型语言模型 (SLM) 增强了我们处理和使用各种自然语言和编程语言的能力。但是&#xff0c;一些用户查询需要比在通用语言上训练的模型所能提供的更高的准确性和领域知识。此外&#xff0c;还需要定制小型语言模型&…

汇凯金业:数字货币对经济的影响有哪些

随着信息技术的飞速发展&#xff0c;数字货币作为一种新兴的货币形态&#xff0c;正逐步走进人们的视野&#xff0c;并对传统经济体系产生着深远影响。它不仅革新了交易方式&#xff0c;更在重塑金融格局、赋能经济发展等方面展现出巨大潜力。 一、交易效率的“加速器” 数字…

CentOS 7.9 快速更换 阿里云源教程

CentOS 7.9 更换源教程 总结 # 下载 wget yum -y install wget # 备份 yum 源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak # 下载阿里云的yum源到 /etc/yum.repos.d/ # 此处以 CentOS 7 为例&#xff0c;如果是其它版本或者系统的话&#…

适合职场小白的待办事项管理方法和工具

刚入职场那会儿&#xff0c;我每天都像只无头苍蝇&#xff0c;忙得团团转却效率低下。待办事项像潮水般涌来&#xff0c;会议、报告、客户跟进……每一项都像是悬在头顶的利剑&#xff0c;让我焦虑不堪。我深知&#xff0c;管理好待办事项是职场生存的必修课&#xff0c;但该如…