前言
首先,小菜从12306说起,记得在《大型网站架构设计》的书中李智慧说过:12306已经不是技术上谈优化的问题了,而是应该在业务流程上优化,去帮助12306为用户更好的服务。
现实的开发场景中,作为开发者的小菜时常发现会有的程序员以代码的视角去完成业务,结果产生了一些问题程序,可是往往我们在程序本身看并不能很好的进行优化,就像这样...
场景1.
void MergeRoad() { SqlCommand command=new SqlCommand(); string sql = @"select count(*) from TableA a join TableB b on a.roadid=b.roadid "; long roadCount = (long)command.ExecuteScalar(); if(roadCount > 0) { Console.WriteLine("Find {0} lines",roadCount); throw new Exception("Find {0} lines error."); } else { //Todo } }
结合上下文我们了解到,这段代码的逻辑是会跨库连表查询满足某些条件的数量(roadCount),但是roadCount怎么用呢?
仅仅是用来和0比对,当roadCount>0说明目前的上下文不符合目前需要处理的条件,程序弹出异常,并终止。而为0,业务可正常进行下一步。
开发者使用了long说明,他知道这个结果是一个较大的值。程序运行后期项目经理发现这阶段程序运行耗时较长,向程序员提出要求:对sql优化,或将远程sql脚本放在存储过程当中。
目前来说这里的最大问题是,这样一步是否真的很必要,产品经理给的答案是:这样有更好的用户体验,可以告诉用户有多少数量的信息需要处理。而实际的结果是程序执行到这里不仅需要做跨库链表,而且结果仅仅是和0做比较。
解决方案:
场景2:
void LoadFileFromServer(string serverPath) { CreateLoadFileLine(serverPath); DownLoad(serverPath); } void CreateLoadFileLine(string serverPath) { //todo //这段程序目的: //递归获取服务端要下载的文件路径,持久化本地, //为后续的断点下载提供服务 } void DownLoad(string serverPath) { //todo //真正的下载实现 }
我们将上面代码视为一段实现了断点下载的下载工具。
需求:实现工具在下载过程之中出现,断电,磁盘空间不足等异常情况,记录当前下载进度。当断电恢复,磁盘满足之后继续完成下载。
实现方式:程序下载之前获取本地是否有此次任务未完成任务。
有:则继续完成断点下载。
无:遍历获取服务路径下所有需要下载的文件路径,持久化本地为断点下载实现做准备。
于是产生以上代码。
但是在程序使用过程当中发现,方法CreateLoadFileLine(string serverPath)是相当耗时的,于是经理提出:看看比对那里有什么问题,是不是算法有什么问题,修改个算法怎么样,找一个最好的算法。于是一个只是拥有开发下载工具能力的程序员又去研究他不擅长的算法去了,对程序员来说很痛苦,程序进展也不大。
其实研究发现当要下载的文件数目大于90w的时候它的递归查找是相当耗时的。假想一下90w个不足100K的文件,比对的时间甚至会大于下载的时间。
思考:先不说一年遇到断电的情况有多少,磁盘亦可以肉眼看到,再说我们开发下载工具的目的是什么?我们只是想下载我们需要的文件,断点只是能更好的完善下载,但当我们将更大的精力放在“断点”时,我们就很难提供好的下载服务了。于是就会出现我们一天前开始的下载,现在一看进度还在文件递归比对中...
问题:我们一门心思用文件路径帮助我们实现断点下载,可是谁帮我们实现CreateLoadFileLine(string serverPath)的断点呢?如果在比对过程当中出现断电,程序就悲剧了,继续比对。
解决方案:
其实真正的开发场景中类似的为了需求而产生性能问题的情况很常见,比如:
话说小菜曾经做过一个O2O的电商项目(见http://www.cnblogs.com/xiguain/p/3330381.html),那需求想起就头疼,最终的解决方案还是在业务上做了优化,不然在一个url变化的情况下,控制对应的N多因素的变化,熬死人啊。
结语:
都说技术是给人服务的,可是在软件开发过程当中,总会出现期望完全以技术优化的情况,其实换个想法,简单的业务变更往往胜过10倍的技术优化。