总结开源项目中的常见坏实践(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
6638 次点击
所在节点    程序员
57 条回复
ngduncent
2022-12-30 08:13:06 +08:00
@WilliamYang 嗯 你这个情况是对的
我主要是想说 这种简单的情况
Money m = getMoney();
return m;
ngduncent
2022-12-30 08:18:58 +08:00
@Jtyczc 第 19 条问题并不出在 这么多字段
而是像 JavaType , htmlType, queryType 这几个字段的设值, 在整段代码中出现很多处。
如何对几个字段依情况设值, 可以抽取出小段代码封装。
代码会明了很多
wu67
2022-12-30 09:02:47 +08:00
那个 0 1 字符串的经常见到. 之前有个同事就喜欢这种, 啥都是 0 1, 都没见过 boolean 型, 问就说可能后续会加其他状态, 特么的我前端绑在开关上的变量, 开和关还有啥其他状态...
ngduncent
2022-12-30 09:14:52 +08:00
@wu67 是的 像文中提到的方法 检查名字在系统内是否唯一 结果 要么唯一 要么不唯一
用 Boolean 型 简单很多
dengji85
2022-12-30 09:27:56 +08:00
mark , 我下载过很多这种开源脚手架学习过,那时就有类似的感受,这代码和我写的也差不多,因为工作以后一直在搞一个内部系统,代码就是这种风格,没机会去学习到比较规范的实现。不过 ruoyi 已经是为数不多可用的,因为确实能跑起来,可以做生产;因为有很多假开源的,文档要钱,限制并发数,连支持事务都要上商业版的
zhanlanhuizhang
2022-12-30 09:31:38 +08:00
代码质量和开源没有什么关系。不然开源怎么兴起。怎么说了,你用了它的代码,你觉得不好,请提交你的代码合并请求。你只批评,没有意思。
dengji85
2022-12-30 09:33:38 +08:00
@wu67 我是前端后端一把梭的也有这个习惯,状态用字符,确实考虑的是扩展,你那个关和开是一种特殊情况,有时用习惯了就这样
ngduncent
2022-12-30 09:39:34 +08:00
@zhanlanhuizhang 如果是少部分代码不好 我会选择提 PR 。(我也不想浪费时间,自己起一套项目)
绝大部分代码都不好。 我选择自己改一套。
ngduncent
2022-12-30 09:42:04 +08:00
@zhanlanhuizhang 再者 我的 PR 作者也不一定认同。 大家各自理念不一样。
senninha
2022-12-30 09:43:53 +08:00
29. 1.5 开始编译器会自动转换成 stringbuilder
ngduncent
2022-12-30 09:44:30 +08:00
@senninha 感谢你的提醒! got it.
senninha
2022-12-30 09:52:30 +08:00
@ngduncent 刚看了一下 29 在 1.8 下的情况,虽然用了 stringbuilder 拼接,但是还是会产生大量的 stringbuilder 临时对象,我想当然了。
ngduncent
2022-12-30 09:55:07 +08:00
@senninha 嗯, 因为 IDEA 是有提示不要 loop 中+=。 所以我有点怀疑。


所以也自己试验了一遍,100w 次 +=耗时 4 秒多 stringBuilder.append 耗时 1 秒
public class TestString {

public static void main(String[] args) {
testStringBuilder();
}

private static final int loop = 1000000;

public static void testPlus() {
System.out.println(">>> testPlus() <<<");

long start = System.currentTimeMillis();

for (int i = 0; i < loop; i++) {
String str = "";
for (int j = 0; j < 100; j++) {
str += j;
}
}
long end = System.currentTimeMillis();
long cost = end - start;

System.out.println("{str+=} cost=" + cost);
}

public static void testStringBuilder() {
long start = System.currentTimeMillis();

for (int i = 0; i < loop; i++) {
StringBuilder str = new StringBuilder();

for (int j = 0; j < 100; j++) {
str.append(j);
}
}

long end = System.currentTimeMillis();
long cost = end - start;

System.out.println("{stringBuilder.append} cost=" + cost);
}
}
wizzer
2022-12-30 10:10:27 +08:00
loopinfor
2022-12-30 10:21:08 +08:00
文不对题,就拿了个开源的噱头。
说是开源项目的坏实践,但是截图连来源于哪个项目都没讲。
这样的案例,是不是淘宝买几份毕业设计源码,然后自己上传 github ,就可以拿学生练手项目的代码作为开源坏实践来讲了?
ngduncent
2022-12-30 10:21:38 +08:00
@wizzer nice
ngduncent
2022-12-30 10:25:53 +08:00
@loopinfor 标题起的不好 我的锅
说了哪个项目 可能要喷我引战
不说哪个项目 又说我连个项目都不说

我好难啊
btw. 这是三万星项目的代码~ 的确很多大学生拿去毕设~~
opentrade
2022-12-30 10:27:37 +08:00
差评
ngduncent
2022-12-30 10:28:14 +08:00
@loopinfor 我要是直接说了哪个项目~ 引流效果更好哦~~ 但是没必要~
ngduncent
2022-12-30 10:30:52 +08:00
@opentrade 感谢你的评价 ( doge )

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

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

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

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

© 2021 V2EX