总结开源项目中的常见坏实践(Bad Practice)

2022-12-29 18:50:36 +08:00
 ngduncent

一些开源项目包含了各种编程的最佳实践供人参考学习和借鉴。但是也有一些开源项目虽然初衷是好的。但是包含了一些代码的坏实践。特别是对于一部分刚入行的大学生来说,可能会给到一些错误的示范。于是在此列举一些项目中的坏实践。

1.方法的用意判断是与否却返回字符串的“0”或者“1”

如果一个方法明确返回是与否这两种情况,那么没有必要返回字符串的 0 或者 1 。这样会造成很多地方需要使用 字符串的形式来匹配结果判断是与否。例如以下这种形式。 方法应该直接返回 true 或者 false 。 代码会简洁明了很多。

2.滥用三元运算符

3.滥用机翻英语

Poor 有不好,差的意思。 例如 My english is poor. 这里的 Poor 是差的意思。 但是下图这个方法 getDatePoor 。也用 poor 来表达获取时间“差”的含义。 英语还是程序员应该要掌握学习的。不能光高靠机翻英语,不然容易闹笑话。

4.造不必要的大量轮子

很多方法或者功能我们应该尽可能的搜索是否已经有开源成熟的 jar 包或者框架实现。成熟的开源 jar 包或者框架,有大量完备的测试以及广泛的用户来确保质量。 如果实在需要自己造小轮子,请使用单元测试来确保质量

5.大量 if else 语句

大量的 if-else 语句,具体情况具体分析。但是大部分都可以用卫语句提前返回结果。避免大量嵌套。

例如左边的写法可以改为右边的写法

像下图这种情况可以用 Stream Lambda 来进行简化

优化后

6.多余的代码判断

有些时候可能会写出一些不必要的冗余判断

7.大量的代码细节让阅读者增加心智负担

我们应该封装一部分代码细节,暴露出代码的主流程,优化后

8.繁琐的代码逻辑

像上图这种情形,我们其实可以使用一句 Stream 语句就可以描述出来。

9.数据库中是与否可以直接用 tinyInt 映射,不要用字符串来映射

这样会造成布尔字段取出时,还需要跟字符串 1 或者 0 进行比对,这是很尴尬的设计。

10.异常捕获之后不做任何处理

我们捕获异常之后一般都需要使用 log 来记录错误情形,如果什么都不做,就很可能丢失错误信息,并且使代码排查过程更加困难。

11.使用 Map 填充数据

使用 Java 是静态语言,使用 Map 填充数据,反而失去了静态语言带来了代码检查以及 IDEA 识别字段引用的功能。

12.混乱的常量

请不用将项目中所有的常量一股脑的放到一个类中。

可以使用像这种静态类的方式,分门别类地放入不同的常量

13.请使用驼峰命名

14.变量的定义请在系统内保持一致,比如 1 在系统内表示是。 请勿有时表示是,有时表示否。

有时候用 1 表示肯定,有时候用 0 表示肯定,有时候用 Y 表示肯定。

15.奇葩的代码脑回路

在外层方法判断一遍,在内层方法又进行一遍一样的判断

16.常量随意的命名格式

常量的命名请使用大写加下划线的格式

17.嵌套的 Switch 语句

就一种 case 了 完全没有必要使用 switch 语句

18.使用魔法值

例如项目中大量使用了“jpg”的字符串魔法值,使用魔法值使得我们无法统一找到代码的引用处。在重构的时候难免会有疏漏。

19.单个方法代码超过 80 行

如果单个方法的代码行超过 80 行,意味你的代码缺乏封装和可读性。例如这种一大坨的代码。

public static void initColumnField(GenTableColumn column, GenTable table)
{
    String dataType = getDbType(column.getColumnType());
    String columnName = column.getColumnName();
    column.setTableId(table.getTableId());
    column.setCreateBy(table.getCreateBy());
    // 设置 java 字段名
    column.setJavaField(StringUtils.toCamelCase(columnName));
    // 设置默认类型
    column.setJavaType(GenConstants.TYPE_STRING);
    column.setQueryType(GenConstants.QUERY_EQ);

    if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType))
    {
        // 字符串长度超过 500 设置为文本域
        Integer columnLength = getColumnLength(column.getColumnType());
        String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT;
        column.setHtmlType(htmlType);
    }
    else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType))
    {
        column.setJavaType(GenConstants.TYPE_DATE);
        column.setHtmlType(GenConstants.HTML_DATETIME);
    }
    else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType))
    {
        column.setHtmlType(GenConstants.HTML_INPUT);

        // 如果是浮点型 统一用 BigDecimal
        String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
        if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0)
        {
            column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
        }
        // 如果是整形
        else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10)
        {
            column.setJavaType(GenConstants.TYPE_INTEGER);
        }
        // 长整形
        else
        {
            column.setJavaType(GenConstants.TYPE_LONG);
        }
    }

    // 插入字段(默认所有字段都需要插入)
    column.setIsInsert(GenConstants.REQUIRE);

    // 编辑字段
    if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk())
    {
        column.setIsEdit(GenConstants.REQUIRE);
    }
    // 列表字段
    if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk())
    {
        column.setIsList(GenConstants.REQUIRE);
    }
    // 查询字段
    if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk())
    {
        column.setIsQuery(GenConstants.REQUIRE);
    }

    // 查询字段类型
    if (StringUtils.endsWithIgnoreCase(columnName, "name"))
    {
        column.setQueryType(GenConstants.QUERY_LIKE);
    }
    // 状态字段设置单选框
    if (StringUtils.endsWithIgnoreCase(columnName, "status"))
    {
        column.setHtmlType(GenConstants.HTML_RADIO);
    }
    // 类型&性别字段设置下拉框
    else if (StringUtils.endsWithIgnoreCase(columnName, "type")
            || StringUtils.endsWithIgnoreCase(columnName, "sex"))
    {
        column.setHtmlType(GenConstants.HTML_SELECT);
    }
    // 图片字段设置图片上传控件
    else if (StringUtils.endsWithIgnoreCase(columnName, "image"))
    {
        column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD);
    }
    // 文件字段设置文件上传控件
    else if (StringUtils.endsWithIgnoreCase(columnName, "file"))
    {
        column.setHtmlType(GenConstants.HTML_FILE_UPLOAD);
    }
    // 内容字段设置富文本控件
    else if (StringUtils.endsWithIgnoreCase(columnName, "content"))
    {
        column.setHtmlType(GenConstants.HTML_EDITOR);
    }
}

20.避免使用反逻辑

像图中!(x == 0) 可以直接改成x != 0 即可

还有以下这种冗余代码。

21.复杂的判断使用有意义的变量来替代

比如上图的判断我们可以用一个变量 isLongField 来替代 提高代码的可读性。

22.常量没有使用 final 来修饰

如果没有使用 final 来修饰的话,就有可能在代码中被修改。

23.字符编码直接用字符串表示

字符编码,JDK 中都有常量可以直接表示,我们可以直接使用

24.多余的方法修饰符

Java 中 interface 类,方法默认都是 Public 的,没必要再加上 public 修饰符

25.不必要的 ToString

26.多余的变量声明

如果变量声明之后没有做任何处理,请直接通过 return 返回,不要多声明一个变量

27.使用语义不清晰的方法

例如 String 的 indexOf 方法 我们完全可以使用 contains 方法来替代,使代码的语义更一目了然。

28.毫无必要的包装语句 unboxing 和 boxing

Integer.valueOf

返回的本身就是 int, 没有必要再调用 intValue 方法

29.使用+=进行在循环中字符串拼接

+=会造出临时的字符串,我们应该使用 StringBuilder 在循环中拼接字符串

以上就是总结的关于项目中的一些坏实践,请大家务必使用。有其他坏实践,恳请大家继续补充。


鄙人在业余时间弄了一个全栈项目 Agileboot ,初衷是想做一个代码规范,项目结构良好,可供大学生或者入门 3 年内的开发者参考使用的项目。

后端地址: https://github.com/valarchie/AgileBoot-Back-End
鄙人能力水平有限,如果项目中发现不足或者错误,恳请指正。欢迎 PR 。一起构建一个规范完善的后端项目。

前端地址: https://github.com/valarchie/AgileBoot-Front-End

鄙人前端小白,关于前端项目的规范以及优化仅作了力所能及的部分,还有很多优化空间。哪位前端大佬有兴趣一起帮忙规范和优化吗?

演示地址 www.agileboot.vip

欢迎加入全栈技术交流群:1398880
6636 次点击
所在节点    程序员
57 条回复
Hccake
2022-12-29 19:35:35 +08:00
第 n 个基于 ruoyi 修改的项目了= =
Aloento
2022-12-29 19:39:45 +08:00
文章虽好,但是推广
ngduncent
2022-12-29 19:43:14 +08:00
@Aloento 我也没有盈利目的 的确是希望有人一起来构建一个规范的全栈项目 所以放了链接...
很过分吗....
TWorldIsNButThis
2022-12-29 20:25:59 +08:00
我也来 review 一下
为什么用 StringUtils 判断 isNull ,而且这里转成-1 是为了?
这里不是直接用 name 查出来库内值,判断库内值为 null 或者上传值 id 等于库内值的 id 就行了?

2202 年了还用在 Date ,这么喜欢受虐嘛

这个 foreach + if else add + removeAll null 把我看笑了

另外优化后的 allOnlineUsers 为什么要 collect 出来,就算要起个名字直接用 stream 不就好了,collect 就会执行至少一次迭代

这么喜欢用 map 建议用 php


看不下去了,原来的代码写的啥玩意。。
ngduncent
2022-12-29 20:43:48 +08:00
@TWorldIsNButThis
嗯 这还是 3 万星的项目...



“优化后的 allOnlineUsers 为什么要 collect 出来,就算要起个名字直接用 stream 不就好了,collect 就会执行至少一次迭代”

嗯 感谢你的指正。晚上 fix~
hsfzxjy
2022-12-29 20:45:20 +08:00
建议改为《从 Java 开源项目中总结出的 Java 项目的常见坏实践》
ngduncent
2022-12-29 20:49:49 +08:00
@hsfzxjy 好的 哈哈哈哈 不然其他语言会躺枪
netabare
2022-12-29 21:22:30 +08:00
感觉和开源与否没什么关系,因为看下来很多都是新人容易犯的错误,在私有仓库里面估计也是泛滥成灾。而且开源项目也不是只有 java 吧。

话说过来一大半的截图都有下划线,也就是说很多问题都是可以靠 intellisence 和 lint 来解决的吧。感觉把 idea 的提示用好,并且装上一个够用的 linter ,基本上就能避免大部分这样类型的错误顺便学到一些良好的编码习惯。

op 的题目会让人认为是和开源项目的贡献、组织管理或者维护有关的事情,多少有点名不副实了。
Bingchunmoli
2022-12-29 21:26:33 +08:00
@TWorldIsNButThis 说实话无脑用 Date 的报错真的少,降低心智,LocalDateTime jackson 需要配置,springboot 接受参数也要配置。Date 一把梭, 虽然我也很认同 Date 在不同时区造成的各种问题,但是大部分公司的项目,至少小公司的项目不需要考虑时区,甚至程序员自身都不懂时区,Date 也不是那么的不行
Bingchunmoli
2022-12-29 21:33:06 +08:00
大部分时认同的,不过 steam 的看法就有些出入,如果他是针对新手的项目 一定程度刻意不用 steam 我觉得是 OK 的,我也经历过项目中一段一段的 stream 看不懂在干啥(现在也许不该用过去的眼光看,这个我持谨慎态度, 毕竟 JDK1.8 是老特性了)。
misaka19000
2022-12-29 21:35:35 +08:00
不错
ngduncent
2022-12-29 22:32:52 +08:00
@netabare sorry 题目的确有歧义 我的锅
主要想表达的是高星开源项目( 3W star )中一些坏的代码实践。或许称不上坏实践,而是纯粹新手错误。
ngduncent
2022-12-29 22:35:48 +08:00
@Bingchunmoli 嗯 一开始我也是抵触 stream 的 后面用完感觉还是挺香的
IvanLi127
2022-12-29 23:06:53 +08:00
第 24 条,虽然默认是 public ,但是经常切换不同语言的开发者不一定记得,这时候减少心智负担显式声明,还算可以理解吧。。。。毕竟你搞全栈的😏
adoal
2022-12-29 23:13:23 +08:00
getDatePoor 实在蚌埠柱了
FrankHB
2022-12-30 00:40:23 +08:00
你这些开不开源没直接关系吧。
给你补个强相关的:
0. 瞎写 copyright notice/瞎用许可证。
WilliamYang
2022-12-30 00:52:46 +08:00
第 26 不完全同意,我看过谷歌开源项目的代码,有时即时同个值,也会重新定义一个更符合当前语义的变量,让别人阅读代码更好理解,否则,按你所说就会出现 return getxxxXXx() + getYyyyyYY() + magicNum 很长的语句了
Macolor21
2022-12-30 01:51:56 +08:00
一眼 ruoyi
Jtyczc
2022-12-30 05:43:15 +08:00
19. 确实有这么多字段,怎么优化呢
ngduncent
2022-12-30 08:11:26 +08:00
@FrankHB 好的 感谢指正

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/905432

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX