我对于“合适的代码”的一些想法

想要写正确的代码不难,但是要写好的代码并不是很容易。经过最近的工作和前辈的指导,算是有了一点个人的心得体会,写出来记录一下。

其实这个话题有点大。个人观点,如有不准确之处,请不吝赐教。

 

一、举一些有待改善的例子

拿我最近接触的一些代码作为例子:

(1)防止代码(方法)变臃肿

给出伪代码:

TestFake.java

所有代码逻辑全都写在了一个方法中,整个方法大概有400行左右,没有任何可读性,简直就是一场灾难。

如果某人接手了这段代码(维护、修bug、拓展),他不得不把整个方法看上一遍,直到理解了代码的整体逻辑,才能开始动手。问题是,要看懂这400行代码是相当费力的(某些代码甚至连注释都没有),肯定要花费大量时间。

举个例子:我想要展示更多的数据,就要修改一下“包装玩家的展示数据到结果集中”(封装dto)的代码。问题是,这个方法多达400行,我不得不从头看起,从第1行开始,慢慢往下找,直到看到350行,才找到了我想要拓展的代码。

再举个例子:某天这个方法报错了,我想debug一下。那么问题来了,断点要打在哪里?这个方法长达400行,我可能要从头开始一步一步往下调试(实际上,如果分离出几个独立的方法,那么就会是独立的方法报错,问题就好锁定了)。

综上,我们应该把不同的逻辑抽出来写成独立的方法,并且加上合适的访问权限(如果某个方法基本上只会在这个类中会用到,那就用private,可以被复用就用public),部分常用的逻辑更应该设计为util。

更正后的伪代码:

TestFake.java

修正之后的“获取玩家的活动展示数据”方法大概只有100行左右,如果我要修改“统计刷新次数”这个功能,只需要找到对应的方法即可,别的无关紧要的方法(比如“判断活动是否开启”)只要看到方法名基本上就可以忽略过去了,让代码更好维护。

(2)不要硬编码

最近经常看到硬编码的情况出现,非常让人蛋疼。

举个例子:一个游戏界面需要展示n个物品,给玩家购买。按照常理来说,这n个物品的id肯定要在数据库的某个表中做好配置。如果我要替换掉某个物品,只要找到表中相应的id,替换掉即可。

然而某一天,策划决定拓展这个活动。只要购买a物品满m个,就会赠送k个b物品。那么问题来了,我要怎么实现这个功能呢?

如果是负责任的程序员,会这样进行拓展:

玩家购买任意物品时,读取一下活动的配置的param1(假设)字段,如果param1不为空,那就说明这个物品买够一定次数就要赠送某个东西,那么我就要记录玩家这个物品的购买次数。如果购买次数大于等于param1,我就读取活动配置的param2字段,解析一下字符串(比如“13042:5,13043:1”,意为id为13042的物品5个,id为13043的物品1个),把相应的物品通过某种方式(比如邮件)发给玩家。

如果是不负责的(或者水平欠佳)程序员,会这样写:

玩家购买物品时,如果购买的物品是a(购买物品id = 物品a的id,物品a的id写死到代码中),我把次数记录到某个位置。如果购买次数大于等于m(m写死在代码中),我就赠送k个物品b(k和物品b的id同样写死到代码中)。

同样是实现一个功能,你觉得那种方式比较优秀呢?

个人认为,硬编码是偷懒、设计不当的结果,是完全可避免的。配置就应该待在配置文件、数据库里,千万不要在代码里写死,不然修改、维护的时候就很蛋疼了(改到你想把作者找出来打一顿)。

(3)使用静态变量

如果某个字符串、数字经常出现,最好写成静态变量的形式,不然修改的时候就要到处去找。改漏一个就出bug。

举个例子,一句简单的输出:

如果只有这样一句代码,当然不用写成静态变量的形式。问题是,如果有很多地方需要用到”欢迎来到”这段字符串,那就有必要单独抽出来:

如果我要把”欢迎来到”改成”您已到达”,只需要把word变量改一下就行了。如果不这样写,可能要多改很多地方。

(4)初始化时使用断言

如果需要在系统启动时初始化一些东西、执行一些操作。对此,我们可以使用spring的@PostConstrut/继承InitializingBean使用afterPropertiesSet。

初始化过程中执行的操作往往非常重要(比如从数据库中把一些配置读到缓存中)。

举个例子:游戏中的物品数据需要在系统初始化时读取到缓存中,如果读取失败/没有读取完(并且没有相应的检测措施),展示/操作的时候肯定会出问题。如果问题将会泄漏到线上,后果就比较严重了。

(用spring boot结合mybatis的同学应该知道,启动spring boot时,如果某个配置文件有问题、缺少某个bean、bean无法注入,spring boot会自动报错中断。在启动时就找出错误,好过运行时报错。)

总而言之,如果初始化有问题,系统就不应该启动。如何阻止系统启动?使用断言。

举一个spring断言的例子:

(5)适当注释

个人认为:适当的注释 > 一大堆注释 > 完全没有注释 > 错误的注释。

1.每写一个接口方法,都要写上相应的注释

参数是什么,返回是什么,有什么注意事项,加上简明易懂的方法名,就能让调用者放心指数x10,舒适度x10。

2.读代码时加注释

在看一大段没有注释、逻辑又比较复杂的代码的时候,最好看懂一段就加一段注释,不但利于自己理解代码,也方便了后人。

3.类开头的tips

把以上注释写在实现类开头,说明使用了ActivityInfoReward表,配置在第几行,配置所对应的功能,可以极大方便后人工作。个人认为把自己的一些想法感受写在注释里也不错。

有人认为:这些东西应该写在文档里面(而且应该写得很清楚)。但是实际中,一个开发团队未必有很标准的文档(甚至根本没文档)。反正写这些注释全看自觉,当然多多益善。

4.不要写误导性的注释!

常发生于“给看不懂的代码加上个人理解”的情况。注释就是代码的说明书,如果注释本身有问题,看的人基本上都会中招,破坏力极大。

之前改硬编码的配置,根据注释改,改来改去都不对。后来把代码重新理解了一遍,才发现注释有问题(要改的地方不全)…

二、写合适的代码

想要实现某个功能,写代码的方式有千种万种,并没有强制哪一种实现方式是正确的。有时候我们总是强调实现,当这个功能成功跑起来了,就觉得这段代码是成功的。

个人认为,这只是“会写正确的代码”,并不是“会写合适的代码”。等到团队合作、代码拓展。代码维护的时候,就会暴露出很多问题。

软件工程是一项团队工作,在写代码时,一定要多为别人想想,怎样才能方便别人的工作。这样配合下来,团队才会有进步,工作才能更加轻松。

就像上面硬编码那一节提到的两种情况,负责任的程序员明显考虑得更加周到,逻辑更合理。以后有类似的代码,基本上可以复用,这才是合理的代码。如果贪图一时方便,写成硬编码的形式,毫无疑问会给别人(自己)添麻烦。

除此之外,我们还可以想得深一点:这段代码中有什么方法可以复用吗?

我们发现需要解析一段字符串:“13042:5,13043:1”,需要解析成HashMap。这段代码在很多地方都能用上,可以写成一个工具方法string2HashMap。

我们发现数据库中有很多活动配置表(每一个表对应着一个活动的配置),配置也不是很多,相当浪费。我们可以专门独立出一个统一的活动配置表,以后所有的活动配置都写在这个表里,通过独立的活动编号管理。

我们发现,凡是要写活动,都要自己解析活动配置表,才能拿到数据。于是我们专门写了一个活动配置工具类,装载着一堆类似string2HashMap的方法,方便对活动配置进行操作。

同理,玩家在活动中产生的数据(比如记录购买道具的次数)也可以写成一个玩家活动数据表…

总之,程序员的工作就是让项目变得更好。很多东西整着整着就会变成框架,往傻瓜化方向发展,生怕你不会用。

三、总结

还是要多写代码,争取往写框架的方向努力。

发表评论

电子邮件地址不会被公开。 必填项已用*标注