一句注释引发的思考 - 论代码质量
一句注释引发的思考
接到一个紧急需求(当然,002的需求向来是如此紧急的):大屏展示原来只有二个品牌数据,现增加到三个品牌的数据。一句话的需求,且没有业务逻辑变更,我认为可以迅雷不及掩耳之势,2小时收拾干净交差。当我满腔激情的定位的核心逻辑部分时,这样一句注释(见下图),让我顿时思绪天马行空:
这个作者经历了什么样一个心碎的过程?但是可以肯点的是这一定是一个有想法的作者,不由得心中肃然起敬。
这段代码经历了多少次的重写,才会让作者的心潮有如此波澜?
抑或,这到底提出了怎么样一个需求,让作者需要通过这样的注释来宣泄心中怨气?
巴拉开代码修改记录,作者已经去别的地方高就了,要不是留了这些代码,实在想不起有这样的一个同事存在过;代码提交记录比较整洁,大部分代码是在5月29号提交,5月30大概是修复bug提交了小部分代码。如此看来,代码没有经历过什么苦难,这里的需求仅仅是每个品牌的门店按订单数量排序(如下图),想想怎么也闹出什么大动静... 再细读作者留下的代码,只能说作者给自己设置了难度系数(这说法太含蓄了),稍微有一点改动,便是牵一发而动全身,于是留了这样一个不太成熟且不太有价值的注释,想起了最近在读的一本书,Bob大叔的《代码整洁之道》,颇有一点不成熟的感触。
如何衡量代码质量
《代码整洁之道》一书开篇第一句话就是一个著名的论断:衡量代码质量的唯一有效标准:WTF/min。 一看到WTF这样的简称就不明觉厉,一顿搜索居然没有找到“标准”的解释,更觉得高深莫测。 也是在后来一个偶然的时间看到原来是 What-The- 的缩写,看重看这张经典的图,一下子恍然顿悟:原来如此,就该如此。
原本学得这是一本好书,甚至觉得大部分程序都应该去阅读这类的书籍一遍(比如 马丁·福勒出品的《重构-改善既有代码的设计》)。 看到这样的论述,更觉此书接地气,仿佛与大师拉近了距离了。回想起过往种种,以及最近修改历史代码时的反应,没什么比WTF更有表达力了。
再回到开篇讲的注释,当时的作者必然也是有类似的反应,只是他的吐嘲对象是他自己,或者他只会认为这是需求的而不是代码的问题。所以 WTF/min作为衡量代码质量的唯一有效标准,还得加一个定语:优秀的Coder喊出的WTF次数,才是真正的标准。 至于如何为优秀的代码,在《代码整洁之道》,《重构-改善既有代码的设计》等经典书籍里都有详细的描述:
- 从变量命名,到注释,到方法,到类,到模块都有非常详细的规范;
- 从抽象到边界,到依赖也有完整的方法论;
- 从SOLID设计原则到,组件相关的 CRP,CCP,CRP等原则都有从理论到落地的解说。
坦白讲,读完这些书后,我感觉自己原来写的代码,很多都缺乏这样的思考,也有不少代码相当丑陋,甚至觉得:在code这件事让,只能算得上初窥门径。于是越时读书,越觉得自己无知。最近读技术书籍的间歇,顺便翻读了几本名人传记:奥本海默为了让人觉得他是天才,总是"偷偷"的读书学习;特斯拉甚至在病危之际也是一心读书;让我们有在剑手的钱学森利用所有空闲时间阅读方能一年完成硕士学位,并获得航空和数学双博士,成本最年轻的终生教授...(省略好多荣誉)。他们无一不是通过自己努力读书,思考,实践终成我等学习的楷模。最近招聘的经历中,大部分应聘的人几乎不看技术书籍的了,也是让我捉摸不透。
读技术书籍没用了吗
最近两月一直忙于面试,沟通了没有100个也有80个候选人,部分人没有了读书习惯,更不用说技术书籍了,倒是有部分说觉得blog比书籍有用多了。有些说工作太忙,有得说没有用....他们中有的在传统公司,有的在互联网企业,有从小企业过来的,也有从阿-里,快-手过来的... 其中一个人的话,让我记忆非常深刻,最后环节,我问他现在还读书不,他说为了进阿-里,他非常努力了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); }
如此这般,我们当时践行了编码里的铁律:当你离开营地时候,要让它比你来的时候更整洁干净。
成为一名优秀的程序员!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
开源日报 | 李彦宏称开源模型是智商税;Windows下的GUI开发;有趣的人形机器人;边学AI边赚钱
欢迎阅读 OSCHINA 编辑部出品的开源日报,每天更新一期。 # 2024.7.5 今日要闻 李彦宏:开源模型是智商税,智能体正在爆发 李彦宏认为,开源其实是一种智商税。“当你理性地去想,大模型能够带来什么价值,以什么样的成本带来价值的时候,就会发现,你永远应该选择闭源模型。今天无论是ChatGPT、还是文心一言等闭源模型,一定比开源模型更强大,推理成本更低。” 谈及“AI超级应用什么时候出现”时,李彦宏表示,“不是说一定在等待一个超级应用的出现”。他认为,在基础模型之上,应该能够诞生数以百万计的各种各样的应用。“如果仅仅是从0到1,你可能会希望出现某几个Super APP,也就是几个公司从中受益。但是今天,几乎各行各业所有的公司,被大模型加持之后,它都能受益。这种影响力,对于整个社会、对于人类来说,无疑是更大的。” 李彦宏称,智能体代表着AI时代的未来趋势。“智能体正在爆发,只是现在基数还比较小,大家的体感没有那么强烈。”基础模型需要靠应用才能显现出价值,智能体是一个几乎“放之四海而皆准”的基于大模型的应用,由于它门槛足够低,可能你连编程都不用,就可以做出一个效果不错的智能体。“让...
- 下一篇
酷瓜云课堂(开源版)v1.7.1 发布,在线教育解决方案
更新内容 更新layui-v2.9.10 更新docker国内镜像地址 增加导入镜像构建容器的方式 调整微信公众号模板消息 移除加载富文本编辑器初始化的语言文件 移除consult中多余的chapter_id属性 修正课程列表顶部过滤条件区块不能收缩问题 用户中心第三方登录列表增加过滤条件 后台增加打开/关闭左侧菜单提示 优化整理文件mimeType iconfont资源本地化 优化UploadController 优化富文本内容显示样式 简化内容图片放大监听 去除课程打赏相关内容 课程增加能否发布检查 系统介绍 酷瓜云课堂,依托腾讯云基础服务架构,采用C扩展框架Phalcon开发,GPL-2.0开源协议,致力开源网课系统,开源网校系统,开源知识付费系统,开源在线教育系统。 友情提示: 演示系统配置低(2核,2G,1M 跑多个容器)切莫压测 课程数据来源于网络(无实质内容)切莫购买 管理后台已禁止数据提交,私密配置已过滤 桌面端演示: 前台演示 后台演示 演示账号:100015@163.com / 123456 (前后台通用) H5手机端演示: 演示账号:13507083515 / 1...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8编译安装MySQL8.0.19