之前接手一个项目代码时,当时服务是有状态的,代码里有这样一个验证,对于接受到的请求,首先会去根据配置的路由规则来验证当前的请求是不是应该路由到当前实例,后期在我做完无状态化改造的时候,这部分逻辑被我移除了,我个人认为,这部分检验没必要,因为服务间的路由调度,由专门的服务去负责的,如果路由服务本身有问题,导致接收到了不该自己处理的请求,这是路由服务本身的问题,此时就算做了验证也并不能拯救什么,按正常逻辑处理,在处理中遇到问题是输出必要的异常或者日志即可。今天看代码,看到了同事一下代码:
//rpc 调用rpc服务
Longid=IdGenerater.generate();
if(id==null||id
LOGGER.warn("IdGenerater.generate ERROR!");
}这个我表示不认同,生成了不合法的值,那是你rpc server服务需要负责的,我只关心成功和失败,不关系具体的值。这个话题延伸了讲,这又涉及防御式编程和契约式编程。防御式编程《代码大全》给我们提供了一个定义,人类都是不安全、不值得信任的,所有的人,都会犯错误,而你写的代码,应该考虑到所有可能发生的错误,让你的程序不会因为他人的错误而发生错误。 程序需要对可能的错误输入,做出兼容,例如一个除法的函数,你必须判断分母可能为0的情况,从而给调用者返回错误提示。另外,一般的高级编程语言,都提供了『断言』和『异常』两种方式来进行错误处理。断言断言是指在开发期间使用的、让程序在运行时进行自检的代码。断言为真,则表明程序运行正常,而断言为假,则意味着它已经在代码中发现了意料之外的错误。断言对于大型的复杂程序或可靠性要求极高的程序来说尤其有用。通过使用断言,程序员能更快速地排查出因修改代码或者别的原因,而弄进程序里的不匹配的接口假定和错误等。一个断言通常包含两个参数:一个描述假设为真时的情况的布尔表达式,和一个断言为假时需要显示的信息。一般bvt里用的比较多:
Assert.assertTrue("login failed",user.login());断言主要是用于开发和维护阶段。在开发阶段,断言可以帮助查清相互矛盾的假定,预料之外的情况以及传给子程序的错误数据等。在生成产品代码时,为了系统的性能,不把断言编译进目标代码里。异常当一个子程序中遇到了预料之外的情况,但不知道该如何处理的话,它就可以抛出一个异常。对出错的前因后果不甚了解的代码,可以把控制权转交给系统中其他能更好的解释错误并采取措施的部分。大部分代码,如果有异常,java中是异常是必须强制显示抛出的,最简单的就
try{
}catch() {
}异常机制的优越之处在于它能提供一种无法被忽略的错误通知机制,只在真正例外的情况下才抛出异常 错误不是异常,比如数组下标越界等。防御式编程就是持怀疑态度审视所有的代码,在游戏行业里,游戏服务端承担着游戏复杂业务逻辑实现,玩家数据持久化等重要作用,很多计算都是客户端发给服务器进行计算的,客户端很容易受到恶意用户的恶意修改,所以服务端会针对收到的数据进行严格的认证,在支付,金融行业更是如此。这些只是代码层面的,延伸了讲,个人认为用户行为日志,配置开关,一定的扩展涉及,等都是防御式编程思想的应用。契约式编程契约作用于两方,每一方都会完成一些任务,从而促成契约的达成,但同时,每一方也会接受一些义务,作为制定契约的前提,有任意一方无视了必尽义的义务,则契约失败。函数调用者应该保证传入函数的参数是符合函数的要求,如果不符合函数要求,函数将拒绝继续执行。如果按照契约式编程的思想编写代码,就要求我们写函数时检查函数参数。有时候是简单的判断某个参数不能为空,或者数值不能小于0。如果在项目中全面应用契约式编程,则应该有一个“契约框架”帮我们来做这些事情,全部手动检查,代码就繁琐不堪了。在面向对象中,我们认为“接口”是唯一重要的东西,接口定义了组件,接口确定了系统,接口是我们唯一需要关心的东西,仅仅通过接口还不足以传达足够的信息,为了正确使用接口,必须考虑契约。只有考虑契约,才可能实现面向对象的目标:可靠性、可扩展性和可复用性。防御式编程和契约式编程都是一种理想化的,全面实现都是比较困难的。但必要的防御措施以及必要的契约还是要有的,让每个人都对自己写的代码负责,建立起良好的信任关系,服务模块之间保持必要的信任。常见的开发模式中,lib库或者rpc服务是server端,调用方式client端,个人觉得服务端需要做更多的防御式验证,不能因为非法调用而运行异常,但调用方要相信server端,不要在做二次验证,就拿那个争论来说,你给我返回了非null的Long对象,我就默认是正确的,我不去做额外的检查他是-1还是-2,在调用方看来只有成功和失败。
领取专属 10元无门槛券
私享最新 技术干货