这是一个真实案例,本周在工作中发现的,案例情况比较极端,因此显得很滑稽很搞笑。但是深入一下,还是有些东西值得思考。
先来看这个案例,在性能优化的过程中,通过thread dump发现有非常多的线程都在执行同一个数据库访问。而按照分析,在cache开启的情况下应该只访问一次才是,后面的数据库访问都是不应该的。
随即跟踪到问题代码:
//
1. get pk as method parameter
public
TrafficProfile createTrafficProfile(
long
serviceCapabilityPrimaryKey, String serviceProviderId,
String applicationId)
throws
NotFoundException {
//
2. do database query to get serviceCapabilityProfile by pk
ServiceCapabilityProfile serviceCapabilityProfile
=
new
ServiceCapabilityProfilePreLoadFullSerializableImpl(getContext(),
serviceCapabilityPrimaryKey);
//
3. generate key using obj serviceCapabilityProfile
String key
=
buildTrafficProfileCacheKey(serviceProviderId, applicationId, serviceCapabilityProfile);
TrafficProfile trafficProfile
=
(TrafficProfile) trafficProfileCache.get(key);
//
5. found in cache and return
if
((trafficProfile
!=
null
)) {
return
trafficProfile;
}
trafficProfile
=
new
TrafficProfilePreLoadFullSerializableImpl(getContext(), serviceCapabilityProfile,
serviceProviderId, applicationId);
trafficProfileCache.put(key, trafficProfile);
return
trafficProfile;
}
//
4. notice: in fact only pk is used
private
String buildTrafficProfileCacheKey(String serviceProviderId, String applicationId,
ServiceCapabilityProfile serviceCapabilityProfile) {
return
serviceCapabilityProfile.getServiceCapabilityPrimaryKey()
+
"
,
"
+
serviceProviderId
+
"
,
"
+
applicationId;
}
因此可以看到,如果cache有效,我们其实只需要一个pk就可以组合出key从而从cache中得到保存的trafficProfile
对象。但是现在在我们的代码中
,为了得到key,我们进行了一个从pk -> serviceCapabilityProfile 对象的数据库查询,而在使用这个serviceCapabilityProfile 对象的函数中,很惊讶的发现,其实这里真正用到的不过是一个pk而且,而这个pk我们本来就持有,何须去数据库里跑一回?
pk
---->
get serviceCapabilityProfile from database by pk
--->
get pk by serviceCapabilityProfile.getServiceCapabilityPrimaryKey();
让我们来看看为什么会犯下如此可笑的错误,随即在这个类中我们找到了另外一个createTrafficProfile():
//
parameter is serviceCapabilityProfile obj
public
TrafficProfile createTrafficProfile(
ServiceCapabilityProfile serviceCapabilityProfile,
String serviceProviderId, String applicationId)
throws
NotFoundException {
//
pass to buildTrafficProfileCacheKey() is obj, not pk
String key
=
buildTrafficProfileCacheKey(serviceProviderId, applicationId, serviceCapabilityProfile);
现在原因就很清楚了:在方法buildTrafficProfileCacheKey()中,实际只需要一个long类型的pk值,但是在它的方法参数定义中,它却要求传入一个serviceCapabilityProfile 的对象。
可以想象一下这个代码开发的过程:
1. 第一个人先增加了以serviceCapabilityProfile对象为参数的createTrafficProfile()方法
2. 他创建了buildTrafficProfileCacheKey()方法,因为手头就有serviceCapabilityProfile对象,因此他选择了将整个对象传入
3. 这两个函数工作正常,虽然这个参数传递的有点感觉不大好,但至少没有造成问题
4. 后来,另外一个人来修改这个代码,他添加了使用long serviceCapabilityPrimaryKey的createTrafficProfile()方法
5. 他试图调用buildTrafficProfileCacheKey()方法,然后发现这个方法需要一个serviceCapabilityProfile 对象
6. 他不得不进行一次数据库访问来获取整个对象数据......
从这个案例中,我们可以看到,一个含糊的参数是如何导致我们最终犯错的 ^0^
这个错误的修改当然非常简单,将buildTrafficProfileCacheKey()方法的参数调整为传入long类型的pk就解决了问题。
在日常代码中,我们有非常多的大对象诸如“****DTO/context/profile”,而它们经常被作为参数在代码之间传递。因此需要小心:
1. 当定义一个类似buildTrafficProfileCacheKey()的方法时
尽量将接口的参数简单化,如果我们确认只是需要使用到某个大对象的一两个简单属性,请将方法定义为简单类型,不需要传入整个对象。
或者在方法上通过javadoc说明我们只需要这个对象的某个或某几个属性。
2. 当调用类似buildTrafficProfileCacheKey()的方法时
需要稍微谨慎一些,进去目标方法,看看代码实现,到底是需要什么数据,是否真的需要整个对象从而导致我们需要进行数据库查询这种的重量级操作。
例如上面的例子,如果原有buildTrafficProfileCacheKey()的方法不容许修改,那么我们大可以new 一个serviceCapabilityProfile 对象,然后setPK()来解决,比访问数据库快捷多了。
前面提到说这个案例有点"极端",这里的极端指的是buildTrafficProfileCacheKey()方法本身就在这个类之中,代码量也非常少,意图非常明确,本来应该很容易被发现的。因此犯错的情况显得比较可笑,但是我们推开来想一想,问题似乎没有这么简单了:如果buildTrafficProfileCacheKey()中的代码比较复杂,可能还通过调用其他的类从而将对serviceCapabilityProfile对象的时候的代码逻辑转移,恶劣的情况下可能还有多层调用,甚至出现接口抽象实际代码运行时注入等复杂场景,再假设我们没有办法直接看到最终的使用代码,我们无法知道原来底层只是需要一个pk而已!那么这个问题就一点都不可笑,上面这个白白访问一次数据库的错误一定会再次发生,因为上层调用者不知道到底需要什么数据,只好整个对象全给!何况通常上层都有良好的代码封装,通过一个pk获取一个对象这种事情,可能只需要一两行代码调用就搞定,于是我们很可能轻松自如的,一脚踩进坑里!
所以说想复杂点问题就变得严峻起来:底层代码的实现者,需要如何设计接口参数,才能准确的告知上层调用者,到底哪些数据是真实需要的?上面的案例中将参数简单的简化为只传入一个pk值就明确的达到了目标,对调用者来说足够清晰明确。但是我们考虑一下复杂场景:如果底层的实现逻辑没有这么简单明确,底层代码的实现者可能担心未来的实现逻辑会发生更改,比如需要serviceCapabilityProfile的其他数据,因此为了保持接口稳定,底层代码的实现者一定会倾向于使用serviceCapabilityProfile对象作为参数从而保留未来不需要修改接口/函数定义就可以扩展的自由。不经意间,挖了一个坑...
我们似乎又回到了原来犯错的轨道中,那个看似搞笑的错误似乎又在对我们挥手微笑......
只是现在,我颇有点笑不起来了:下一次,如果我面对一个函数/接口,要求传入一个大对象,我手头只有一个pk,还有一个现成的函数可以一行代码就搞定查询,我要如何才能挡住诱惑?
分享到:
相关推荐
标题中的"【滑稽】害怕表情包.zip"指的是一个包含了一系列滑稽风格、表达害怕情绪的表情图片的压缩文件。 "滑稽"表情包通常以夸张的面部表情、肢体动作或情境来引发人们的笑声,这些图片源自各种网络资源,可能是由...
滑稽东试用助手是一款专为用户设计的软件试用辅助工具,其最新版本为V1.58.0.5716。这个压缩包包含该助手的完整组件,确保用户能够顺利安装并体验软件的各项功能。接下来,我们将详细讨论这款助手的主要特点、可能...
滑稽五子棋软件是一款专为安卓用户设计的创新五子棋游戏,具有超轻量级的特点,确保在各种设备上都能流畅运行。...无论你是想独自享受思考的乐趣,还是想与朋友共享游戏时光,这款软件都是一个不错的选择。
滑稽表情
滑稽流水灯是一种趣味性的电子项目,常用于学习和展示微控制器编程,特别是单片机控制LED灯的技巧。在本资源"滑稽流水灯代码.rar"中,我们可以期待找到实现这种效果的程序代码,这通常涉及到C语言或汇编语言编程。 ...
标题"0.1.5-64 全键盘移植by滑稽的字母哥"和描述"0.1.5_64 全键盘移植by滑稽的字母哥"暗示了一个软件更新或者应用程序版本,这个版本0.1.5_64是由名为“滑稽的字母哥”的开发者进行全键盘移植的。全键盘移植通常指的...
【完整版】滑稽东试用助手 V1.63.0.6118.zip 是一个针对京东平台设计的自动化工具,旨在帮助用户进行日常的签到活动和参与京东农场的游戏。该软件的主要功能是简化用户的操作流程,提高效率,让用户在繁忙的日程中也...
可以恢复被滑稽病毒感染的图标
"滑稽空间项目"是一个基于原生开发的网络应用程序,其设计灵感来源于广受欢迎的QQ空间。这个项目采用JavaServer Pages(JSP)技术来构建,旨在提供一个类似QQ空间的社交体验。JSP是一种服务器端脚本语言,用于创建...
Python源码分享,海龟画图-表情绘制——滑稽表情
在使用过程中,用户需要注意的是,尽管滑稽表情可以为对话增添乐趣,但也要尊重他人,避免过度使用或在不适当的情境下使用,以免引起不必要的误解。此外,合理使用网络表情,也是网络礼仪的一部分,可以帮助构建和谐...
这个误解很普遍,人们经常错误地将9/11事件描述为一场滑稽模仿。但实际上,9/11是一场悲剧,而不是滑稽模仿。如果你听到一个Weird Al Yankovic的专辑被描述为一个滑稽模仿,这个用法是正确的。这个单词常被误用于...
文件名称:Win10滑稽壁纸-无水印.png 作者:永远也长不大的小新 文件分类:操作系统-桌面系统 所需积分:1 资源标签:【永远也长不大的小新】我的文件、(其它文件) 下载须知:良心作者,不喜勿喷,请大家好好...
这个特效使得滑稽的表情图片以快速冲击的方式出现在屏幕上,为用户带来惊喜和娱乐性,特别适合用来装饰个人网页或作为课程设计的一部分。 HTML(HyperText Markup Language)是网页的基础结构语言,用于定义网页...
滑稽东试用助手 现有功能 1、自动申请试用 2、自动领取京豆 3、自动签到领取京豆 说明: 该软件使用C#开发,若要运行此应用程序,您必须首先安装 .NET4.0 1、双击软件无法打开怎么办? 该程序需要安装...
【完整版】滑稽东试用助手 V1.26.0.3458
在"error-master"这个文件夹名中,我们可以推测这可能是一个用于管理错误页面的项目,可能包含了各种错误类型(不仅仅是404)的模板、脚本或配置文件。在这个项目中,可能有HTML文件、CSS样式表和JavaScript脚本,...
4. “讽谏”是一种委婉的劝说方式,通过寓言、笑话或者比喻等手法,引导对方认识到自身的错误或不合理之处。 5. 孙叔敖是楚国的名相,他在临终前预见到子孙可能会陷入贫困,嘱咐儿子找优孟寻求帮助。优孟通过模仿...
3. 教师小结:小丑都有一个红红的鼻子,大大的嘴巴,有趣的眼睛,看上去很可笑,很滑稽。 B、欣赏范例,讨论制作方法。 1. 请幼儿观察并讨论:制作小丑需要哪些材料? 2. 教师讲解小丑的制作顺序。 ① 先在脸的...
2. **图形用户界面(GUI)开发**:"让滑稽飞"可能包含一个用户友好的GUI,用于展示游戏界面和接收用户输入。Java提供了Swing和JavaFX库来创建GUI,学生可能使用其中之一来构建游戏的视图部分。 3. **事件驱动编程**...