关于对业务代码编写的思考
我为什么有这篇思考?
我在一次偶然的时间点产生了“致力于打通黑白盒测试”的想法,然后开始了漫长的自学编码的“生涯”。这个过程是坎坷的,其中产生很多问题,本文是我遇到并且克服的问题之一——众所周知,在开始学习一个新知识或者技能的时候,我们都是满怀激情的,但是随着遇到的困难或者说游戏难度的增加,大家都会逐渐焦虑。但是本着不抛弃不放弃的精神,我们需要努力战胜一个又一个阻碍。
我们在学习了一个个单个技术的时候可能一马平川,勇猛精进,但是在结合这些知识进行完整的业务编码时却是犯了难。怎么将学习的编码知识运用到实践中呢?本文所述的即是我对这个过程的思考总结。我在这块一度卡壳,我猜大多数同学曾经都可能遇到过这样的问题。
本篇我们以一个开源论坛项目的通用逻辑编写作为案例。(参考小滴课堂的课程demo)
怎么定位,与处理字段?
编写业务中的数据处理逻辑时,可以查看sql与实体中的字段,分析哪些字段需要特别处理(需要从dao层逆推到service层)。
例如,分页查询回复接口中,需要向前端返回一个具备页码、页面大小、总页数以及当前页的数据,service层会先获取总的回复记录数作为需要查询的数据条数,作为limit??的第二个参数。(其实第一个参数也算属于特殊处理的,它通过(page-1)*pagesize计算得出从多少条数据之后进行查询。)
这种处理是基于sql语句中的用法来考虑的——limit??表示从a+1条数据开始,查询b条数据。(其中a、b分别代表第一个、第二个占位的参数)
分析数据是输入型还是输出型?
对于数据处理中,还有根据情况分析是输出型处理还是输入型处理。
输出型数据一般是提供给用户使用 考虑的是友好可理解,还是例如上文的例子——用户需要的不是一个大列表数据,而是一个具备页码等元素的ui,它的实现要求后端返回的是一个包含多层的复杂对象的json字符串。
在这种情况下,我们要通过数据的组装,然后往外输出。例如 一直讨论中的分页查询逻辑中,将输入进接口的页码、页面size大小、经过计算得出页数(通过查询出来的总记录数/页面size,这个计算在dto中进行)以及最后查询得出的列表对象 一起组装 进一个分页dto中,最后返回出去。
输入型数据与输出型数据关系紧密,正是由于有输出数据的要求,导致在某些情况下输入型需要获取一些比较麻烦的数据。
例如增加逻辑中,例如评论发布(我认为增加逻辑也是和输出联系的,因为任何逻辑流程都是有输入输出的 增加逻辑也不例外,从接口层面来看它增加成功后也会给一个字段反馈, 从业务层面来看,有的场景会及时更新页面,也就是查询一次页面)输入的数据有文章的id、文本内容,但是插入到数据库中时,我们需要的还要插入的用户信息(用户id、名称、头像等)、评论的创建时间、更新时间、逻辑删除标志、评论的楼层等。
在这个例子中,除去输入的文章id、文本之外,其它数据都足以处理。它们分别有各自不一点获取方式——用户id可以从session中获取,用户其它信息则额外通过查询用户表来得知;因为是增加行为,日期时间通过自己创建,如果是更新动作,那么创建的时间需要通过查询历史记录;楼层是通过查询不要来得知上一个评论的位置;因为是新增操作,删除标志位设置为0(假设0为未删除,一般0表示false)。
得到上面这些数据之后,根据编码规范 也不会硬生生的直接 传要堆参数到下一层,而是能够装入对象的就分别转入对象中(存在多个对象的情况下分别传多个对象),然后再传入到下一层。
下一层就是dao层,在dao层处理则清晰多了,因为是与数据库进行操作,这里考虑的更多是数据库需要什么,我有什么?如果没有,那么就需要返回上一层甚至是上上层进行获取了。当然这个思考在编写service层的时候就需要根据dao层的处理进行逆推考虑。
在controller层进行处理还是在service层进行处理
这段我先从“”非专业的野路子开始逆推思考”。分析在哪一层进行处理一个数据,不如分析这个数据是属于哪一个“范围“的,例如用户session信息在request范围,我们肯定不会去,下一层进行处理。还有处于不同的service层的方法,我们也是在更高的一 层进行处理。这样看显而易见了,原则就是范围最小化处理 。
其实如果是“正统”的技术路线出发,大概不会这种烦恼。因为javaweb有一个MVC的分层设计,基础数据都在数据模型中进行处理的,也就是Model中,然后Model提供数据给控制层进行处理。
注意接口的约定
参照事先约定的接口要求,可以得知最初的controller层输入与输出的要求。
假定这些输入是恰当的,我们就可以开始分析需要输出的数据应该从哪些服务中获取(写好对应的方法需要的输入),如果这些服务中没有相关方法则需要对其进行补充。这个时候在service 层我们考虑的是controller层调用的方法,对该service的输入输出约定。
编写到对应service层之后,我们就开始考虑该层需要的数据从哪个dao层中进行获取以及向其输入什么样的数据,其实也就是对dao 层的方法进行了一个约定。
其中,在进行输入时,比如说更改时,需要考虑更改的实体的字段。有些情况下传入的参数不能刚好满足,需要经过从表中再次查询或者通过一些简单的计算等获取到。
这就是整个编写过程了。
怎么使得编写的思路变得清晰
即便以上文章叙述了很大的篇幅,看上去准备充分了,编写的时候仍然卡壳在怎么办?
我们来理一理思路,如果我们开始已经确定了接口约定,也就上我知道要这个接口输入什么和输出什么,即已确定了controller 层的输入输出了。
接下来应该根据dao层所需来逆推其它层需要处理的数据。即便我们dao层方法可能还不具备,但是我们在约定接口之前就进行er图设计、写好了实体类以及建立了对应的数据表。所以我们是清楚的知道这个 程序的这个接口是要进行一个怎么样的数据库操作(例如,输入多少数据进行 插入)。
只不过我们不是一次“干脆点“输入——而是输入一部分,再通过其它办法获取一部分,再拼装在一块数据表。
所以对于一个插入接口,如果我将实体类字段与er设计图摆在旁边,我是清楚的知道我除了用户端传入的数据之外,还要通过程序自己的处理要获取的数据有哪些。
到了这个阶段,我需要分析的则是哪些数据在哪一层获取比较合适。这个我们可以遵循范围最小化处理的原则进行 。(其实这里通过背景的补充,我们知道它们是在遵循MVC/MTV设计)
同理,对于返回的数据也可以这样考虑。不论是输入还是输出,它们都有一个共同的特点——可能不是一次性处理就可以得到的,而是需要进行一定的组装。
思考到这里,我们可以发现,接口代码的编写不是线性思考的,而是类似立体的考虑的。当然经验的积累有助于这一能力。