【java基础】如何写出好的代码

一、什么是好代码

衡量代码质量的唯一有效标准:WTF/min

在这里插入图片描述

顾名思义就是少骂街
我们在阅读别人代码甚至自己代码的时候,经常都会不禁大骂“ what the fuck”,这句话的意思就是骂出来的次数就是评价代码的标准。

机器能运行,能高效运行时基本要求,而代码还是给人阅读的,所以能让人快速看懂的代码才是好代码。

在蚂蚁的时候,有个优码会,经常搞些形式的代码的东西,当时感觉“代码是给人看的”就是在骂街,搞成形式主义就没意思了,但不得不承认这句话并没有错,只是被使用者用的比较扭曲,不过阿里的价值观都是这样,每句话都很有哲学很上进,但我们用起来就变成鞭挞别人的有力武器

所以好代码应该是整洁的,让人读起来爽。整洁的代码不一定是好代码,但好代码一定是整洁的,这句话应该没问题。除了整洁好代码还应该是高内聚低耦合的,同时也是可读性强,易维护的。

二、如何写出好的代码

1. 高内聚低耦合

高内聚低耦合我们有一些通用的面向对象设计原则来衡量:

  • 开闭原则OCP
  • 单一职责原则SRP
  • 依赖倒置原则DIP
  • 最少知识原则LKP
  • 里氏替换原则LSP
  • 接口隔离原则ISP
  • 组合/聚合复用原则CARP

但这些原则并不是死板的教条,我们也经常会因为其他的权衡(例如可读性、复杂度等)违背或者放弃一些原则。比如子类拥有特性的方法时,我们很可能打破里氏替换原则。再比如,单一职责原则跟接口隔离原则有时候是冲突的,我们通常会舍弃接口隔离原则,保持单一职责。只要打破原则的理由足够充分,也并不见得是坏的代码。

2. 可读性
代码只要具有了高内聚和低耦合就足够好了吗?并不见得,我认为代码还必须是易读的。好的代码无论是风格、结构还是设计上都应该是可读性很强的。可以从以下几个方面考虑整洁代码,提高可读性。

(1) 命名
大到项目名、包名、类名,小到方法名、变量名、参数名,甚至是一个临时变量的名称,其命名都是很严肃的事,好的名字需要斟酌。

名副其实
好的名称一定是名副其实的,不需要注释解释即可明白其含义的。

/**

  • 创建后的天数
    **/
    int d;
    int daysSinceCreation;
    后者比前者的命名要好很多,阅读者一下子就明白了变量的意思。

容易区分
我们很容易就会写下非常相近的方法名,仅从名称无法区分两者到底有啥区别(eg. getAccount()与getAccountInfo()),这样在调用时也很难抉择要用哪个,需要去看实现的代码才能确定。

可读的
名称一定是可读的,易读的,最好不要用自创的缩写,或者中英文混写。

足够短
名称当然不是越长越好,应该在足够表达其含义的情况下越短越好。

(2) 格式
良好的代码格式也是提高可读性非常重要的一环,分为垂直格式和水平格式。

垂直格式
通常一行只写一个表达式或者子句。一组代码代表一个完整的思路,不同组的代码中间用空行间隔。

public class MemberRepositoryImpl implements MemberRepository {

    @Resource
    private List<MemberRepositoryHandler> handlerList;

    private Map<DingGroupTypeEnum, MemberRepositoryHandler> handlerMap = new ConcurrentHashMap<>();

    @PostConstruct
    private void init() {
        if (!CollectionUtils.isEmpty(handlerList)) {
            for (MemberRepositoryHandler handler : handlerList) {
                handlerMap.put(handler.getType(), handler);
            }
        }
    }

    @Override
    public Result<Boolean> addMembers(Long groupId, DingGroupTypeEnum dingGroupTypeEnum,
        List<DingUserEntity> dingUserEntityList, XiaoErUserEntity operator) {
        MemberRepositoryHandler handler = getHandler(dingGroupTypeEnum);
        if (null == handler) {
            return Result.returnFailed(ErrorCode.MEMBER_REPOSITORY_HANDLER_NOT_FOUND_ERROR);
        }
        return handler.addMembers(groupId, dingUserEntityList, operator);
    }
}

如果去掉了空行,可读性大大降低

public class MemberRepositoryImpl implements MemberRepository {
    @Resource
    private List<MemberRepositoryHandler> handlerList;
    private Map<DingGroupTypeEnum, MemberRepositoryHandler> handlerMap = new ConcurrentHashMap<>();
    @PostConstruct
    private void init() {
        if (!CollectionUtils.isEmpty(handlerList)) {
            for (MemberRepositoryHandler handler : handlerList) {
                handlerMap.put(handler.getType(), handler);}}}
    @Override
    public Result<Boolean> addMembers(Long groupId, DingGroupTypeEnum dingGroupTypeEnum,
        List<DingUserEntity> dingUserEntityList, XiaoErUserEntity operator) {
        MemberRepositoryHandler handler = getHandler(dingGroupTypeEnum);
        if (null == handler) {
            return Result.returnFailed(ErrorCode.MEMBER_REPOSITORY_HANDLER_NOT_FOUND_ERROR);
        }
        return handler.addMembers(groupId, dingUserEntityList, operator);}
}

类静态变量、实体变量应定义在类的顶部。类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。

水平格式
要有适当的缩进和空格。

团队统一
通常,同一个团队的风格尽量保持一致,可以参考阿里的代码规范标准,在其基础上做一些适合自己团队的调整。

(3)类与函数
类和函数应短小,更短小
类和函数都不应该过长(阿里要求函数长度最多不能超过80行),过长的函数可读性一定差,往往也包含了大量重复的代码。

函数只做一件事
同一个函数的每条执行语句应该是统一层次的抽象。例如,我们经常会写一个函数需要给某个DTO赋值,然后再调用接口,接着返回结果。那么这个函数应该包含三步:DTO赋值,调用接口,处理结果。如果函数中还包含了DTO赋值的具体操作,那么说明此函数的执行语句并不是在同一层次的抽象。

参数越少越好
参数越多的函数,调用时越麻烦。尽量保持参数数量足够少,最好是没有。

(4)注释
别给糟糕的代码加注释,重构他
注释不能美化糟糕的代码。当企图使用注释前,先考虑是否可以通过调整结构,命名等操作,消除写注释的必要,往往这样做之后注释就多余了。

好的注释提供信息、表达意图、阐释、警告
我们经常遇到这样的情况:注释写的代码执行逻辑与实际代码的逻辑并不符合。大多数时候都是因为代码变化了,而注释并没有跟进变化。所以,注释最好提供一些代码没有的额外信息,展示自己的设计意图,而不是写具体如何实现。

删除掉注释的代码
git等版本控制已经帮我们记录了代码的变更历史,没必要继续留着过时的代码,注释的代码也会对阅读等造成干扰。

(4)错误处理
错误处理很重要,但他不能搞乱代码逻辑
错误处理应该集中在同一层处理,并且错误处理的函数最好不包含其他的业务逻辑代码,只需要处理错误信息即可。

抛出异常时提供足够多的环境和说明,方便排查问题
异常抛出时最好将执行的类名,关键数据,环境信息等均抛出,此时自定义的异常类就派上用场了,通过统一的一层处理异常,可以方便快速地定位到问题。

特例模型可消除异常控制或者null判断
大多数的异常都是来源于NPE,有时候这个可以通过Null Object来消除掉。

尽量不要返回null,不要传null参数
不返回null和不传null也是为了尽量降低NPE的可能性。

三、如何判断不是好的代码

讨论了好代码的必要条件,我们再来看看好代码的否定条件:什么不是好的代码。Kent Beck使用味道来形容重构的时机,我认为当代码有坏味道的时候,也代表了其并不是好的代码。

(1)代码的坏味道
重复
很多时候,当我们消除了重复代码之后,发现代码就已经比原来整洁多了。

函数过长、类过大、参数过长
过长的函数解释能力、共享能力、选择能力都较差,也不易维护。
过大的类代表了类做了很多事情,也常常有过多的重复代码。
参数过长,不易理解,调用时也容易出错。

发散式变化、霰弹式修改、依恋情结
如果一个类不是单一职责的,则不同的变化可能都需要修改这个类,说明存在发散式变化,应考虑将不同的变化分离开。
如果某个变化需要修改多个类的方法,则说明存在霰弹式修改,应考虑将这些需要修改的方法放入同一个类。
如果函数对于某个类的兴趣高于了自己所处的类,说明存在依恋情结,应考虑将函数转移到他应有的类中。

数据泥团
有时候会发现三四个相同的字段,在多个类和函数中均出现,这时候说明有必要给这一组字段建立一个类,将其封装起来。

过多的if…else 或者使用switch
过多的if…else或者switch,都应该考虑用多态来替换掉。甚至有些人认为除个别情况外,代码中就不应该存在if…else。

最后推荐两本书《代码整洁之道》《重构-改善基友(既有)代码的设计》。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

【江湖】三津

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值