这里涉及到的系统是一个7年的遗留系统,技术栈是.NET MVC2,即将被客户淘汰。这篇博文的主题无关技术本身,文中谈到的技术细节也不是什么高大上的,更多的是想记录因这件事情触发的非技术思考。
早上7点45分来到公司,我坐在办公桌旁边开始考虑今天的工作事项。想到客户一直抱怨的电子表单系统在产品环境上8000多个无法重现的错误日志就亚历山大,“替换成微软类库也并不一定解决问题,客户又在捣乱。今天一定要和夏夏阿哲一起看看这个问题,优先级得提上来”,我心里暗自的想着,并把它加到了待办事项的第一条,优先级标为高,截止时间是今天。
开了个好头,但遭遇IE-Only问题
开完早会后,我和夏夏了解了问题上下文,然后开始分析错误日志。我俩惊讶的发现,其中7000多条错误日志是发生在表单导航部分!夏夏说,“就先从它开始吧。” 我俩很快统一思路,瞄准这几个页面就开始搭建环境尝试问题的复现。
按下遇到的各种环境问题不提,这个错误很快就在IE浏览器(文中统称IE)上重现了,而且只在IE上才有这个问题:页面缺少Anti-CSRF Token,导致请求被拒绝。“哎,这不错!”,夏夏用他一贯的幽默风格说道。我想:是的,好兆头,万事开头难,我们似乎开了个好头,然而这怎么好像又是IE啊,真不靠谱。
更不靠谱的e.preventDefault?
时间已经转眼到了10点半,我们开始尝试定位代码,寻找问题的根源。咦,貌似看起来页面前进后退的按钮触发了Form的POST请求,而服务端收到的请求中并没有Token。那么“我们页面AntiCSRF Token是怎么产生的呢?”我问夏夏,夏夏说:“娴静,你看这个js文件”。
<code>
form.submit(function() {
if ("AntiCSRF-TOKEN" element not exists) {
form.append( input 'AntiCSRF-TOKEN' with value)
}
});
</code>
“哦,横切了一刀”,我说。夏夏说:“恩,你说的太对了!是在所有Form提交时自动追加Token”。我想这看起来没问题,在早期系统中经常这样干。那么是谁动了我的Token呢?
11点了,我们的诊断工作紧张而有序的继续进行着,分析各种可能出现的异常路径以及可能性。各种测试验证貌似都没有问题。“这不应该呀。夏夏,我们在里面加上e.preventDefault,不让它提交,手工测测看。”这时我开始乱入,怀着试试看的态度对夏夏说。心想,怎么有些像回到了5年前工作在这个系统上的状态。夏夏改了代码并编译运行,奇怪的事情发生了:Form提交成功,并且错误被修复了!!不光开了个好头,好像我们还中彩蛋了的感觉。
我和夏夏都惊呆了:“这怎么可能?” 夏夏说。“是啊”,我说,“e.preventDefault不是应该阻止提交吗?” 暗想我就是最近一段时间没写前端代码而已,世界变化这么快?我和夏夏又过了一遍Anti-CSRF Token处理代码,做了各种尝试,仍然没有头绪。即使e.preventDefault可以解决问题,但我们仍然不知道问题根源。谁动了我的Token!
又是IE浏览器实现的问题?
思维似乎有些短路了,我便建议:“我们来求助一下网络吧”。果然StackOverflow上发现了同样的问题,IE上Form提交时丢失动态添加的字段,不过是IE9版本。看完了推荐的答案很是吐血 -- IE兼容模式?熟悉IE的程序员都知道,这基本可以作为修复IE问题的万能解药。
夏夏说,“那我们来试试”。修改代码运行系统,叮,问题也被修复了!天哪,IE啊~~~各种黑在我的心里飘过,IE的浏品又一次被拉低了(如果它有的话)。好吧,微软的实现总是跟别人不一样。于是我开始寻找各种微软的文档、论坛,补丁网站,反馈网站,IE Google group试图从里面找到更多的讨论和证据,然各种同样的讨论贴最后都指向了兼容性这个文章,貌似,就是IE的问题。然而没有找到明确的官方支持让我们仍然不太确定。
同时,在这众多的相似问题中还有一个现象引起了我们的注意:网络似乎只报了IE9的问题,而我们是IE9以上都有这个问题,似乎不太对”。我们继续翻阅着代码进行各种尝试,思路再次陷入了僵局。到底是谁动了我的Token!!
时间过的很快,已经晚上6点多了,解决方案是什么?产品环境的问题怎么办?我和夏夏纠结着:“那要不就这样,我们先用第二个方案把产品问题修了......”。是啊,需要赶紧修复产品环境的问题。然与此同时,没能最终找到悬案的罪魁祸首让我俩纠结万千。
这讲不通啊?
正好这时强哥背着电脑包从外面走了进来,和他聊起了这个问题,强哥说:“这不大对啊”,经过一番讨论,在夏夏离开去讨论另一个问题的时候,强哥终于抢到了键盘。同样的复现步骤和思路最终也得到了同样的IE兼容性的解决方案等等。“但这说不通啊?”强哥不断的重复着这句话。心中不解的疑惑使得我们三个又重新加入了新一轮的分析中:“等等,好像这里执行了两次,第一次失败,而第二次就成功了”,强哥敏锐的扑捉到了又一丝新的线索,事情好像有了新的转机。
Form提交了两次?
“我们再来抓一下包看看”,夏夏说。打开Fiddler,重现问题。果不其然,同一个请求出现两次,第一次失败,第二次成功。问题转移了:“为什么会出现重复提交呢?” 时间一分一分的过去。已经晚上8点多了,我的肚子很饿,胃有些隐隐作疼。办公室里也只有少数一部分人了,沙沙帮我们找来了救命的小浣熊。这个时候,我们三个都不约而同的看到了下面这篇Stackoverflow的帖子:
强哥说,“这好像没关系,他这代码写的不对,Form上的按钮是Submit类型,还绑定Click去提交。”夏夏说,“是的,代码问题。” 气氛似乎有些缓和,强哥随口说,“不会,我们就是这么干的吧?”
真相大白
突然,空气好像被凝固了一般,夏夏和强哥对视x秒。强哥说,“要不咱查查代码”。夏夏说,“我也觉的得查查”。我说,“这怎么可能? 这是基本知识好不好”。然后夏夏就真的打开代码库查了起来。几分钟后,只听夏夏:“@#¥%%@#%&&”。 强哥也凑了过去,然后从椅子上“跳”了起来,我默默的在一旁画圈圈,原来是你这厮动了我的Token!!!。
“啊!我也知道为什么e.preventDefault能解决问题了”,我拍着桌子说道。夏夏和强哥互相看了一眼,哈哈大笑:“因为它干掉了一次。”
问题的罪魁祸首就这样找到了,我们通过Git提交历史也知晓了这个问题是在解决按钮多次点击问题时引入的。然故事并没有在此结束,找出罪魁祸首的兴奋激动过后,带给我们三个更多的是凝固的空气和沉闷的心情。夏夏说,“这真是打脸。”是的,在这一点上没什么可矫情的。
你离它只有一步之遥
在回家的地铁上,我们三个臭皮匠仍然在交换那酸甜苦辣的各种复杂心情,反思和讨论我们的工作以及白天错过了什么。这让我记起了那个关于吃馍的故事:第一个、第二个、第三个、在吃第七个馍的时候饱了,并不意味着我们能抹杀前六个馍的功劳,只吃第七个馍就可以了。然现实的情况下很多人在第六个馍的时候就放弃了。我和夏夏在整个的过程中,就像警察找到了失踪的物品和一只替罪羊,疑团仍然没被打破。整个事情并不正常,甚至e.preventDefault的行为都很诡异。事出反常必有妖,而最终的我们不管怎样其实选择的都是放弃。
如果重来?
科学需要的是严谨、怀疑和批判的态度,软件工程也是一样。在问题面前,我们需要严谨的态度并抛弃既有的偏见,抽茧剥丝,不放过一丝的线索直到找到那个动了我们代码的爬虫。
“IE总有各种稀奇古怪的问题,出问题也是它自己的问题,要不其他浏览器为啥没问题?”带了这样的有色眼镜去解决问题,会遮挡住我们敏锐的眼睛,误入歧途。也许我们并没有真正在解决问题,只是在给自己的偏见找到一个借口而已。
只有这些吗?
我们常说作为ThoughtWorks作为一家服务公司要具备专业化的服务精神,工作中要具有专业精神,然什么是专业化?我们常说作为技术是ThoughtWorks的核心竞争力,我们要追求技术卓越,然什么是卓越?这次事件给我上了深刻的一课。
我想,在交付压力面前,在客户挑战面前,我们对于问题的响应度和处理方式反映了我们的专业度有多少。
当我们修复一个产品问题的时候,是不是把这个问题解决了就结束了?
当我们无法解决一个产品问题的时候,是不是将问题抛给客户,“我加了点日志过两天再看看”,就结束了?
当我们无法解决一个第三方技术问题的时候,是不是一个简单的“要升级”就结束了呢?
当交付压力一次次被当做不能技术卓越的挡箭牌,当面对各种无奈与挑战的时候,是不是经常说“算了,就这样吧”。那你有没有发现,悄然间我们的专业化服务底线一次次的被触碰。与此同时,我们技术前进的步伐也已经悄悄的停了下来。
三指规则:当你用一个手指指向别人时,注意另外三个手指所指向的方向。-温格伯
我们那些还可以做得更好?我们的客户面临什么样的问题?我们还能做些什么来帮助他们解决这些问题?如果让我只选择一个品质来提升我们的专业服务精神,那就是“死磕到底”。
死磕到底
死磕是什么?普通话就是“较劲儿”、“不达目的不罢休”的意思。
死磕就如凌晨四点洛杉矶的科比,就如他成名后依然每天完成800个投篮的高强度训练以保持专业的水准。
死磕就如逻辑思维罗振宇每天坚持6:30发60秒语音,每周发优酷视频。
死磕就如这次事件中的强哥,一次次从问题中找到线索找到根源。
试想一下如果没有强哥的加入,真相可能就会被淹没。他们所展现的是与常人所不同的专业精神和匠人精神,而这种精神是为客户创造价值的根本!
写在最后
当面包成为习惯的时候,甚至有时会有夹心的时候,当抱怨成为习惯,当环境变得越来越舒适的时候,会让人忘记初心,丧失竞争和生存的动力。
“Stay Hungry,Stay Foolish” 与诸君共享。
2016年3月23日特写此文纪念。