概述:
在重构广告‘老代码’的初期发生了一个突发的需求,下面我简单介绍一下,可能文中更多的是思路和结构上的东西。具体的业务实现也会简单说一些,但说的不多。
仅用文章纪录一下,保存我于广告项目珍贵的记忆。
突发事件:
上一篇文章——我和我的广告前端代码(一):打碎重来、化零为整,中提到我在将业务线甲的广告代码重构的事情,但是开发的过程中,有了一个小插曲——加标志。由于我国新出台的广告法规定,所有的广告必须注明“广告”。要求一周内,连开发带测试,打败这个半路杀出来的‘程咬金’。
应急方案:
这本是一个很简单的事情,如果在我新重构的广告代码中,只要在公用的父类中加入一个函数就可以批量的加入标识。但是新广告代码的开发,还需要很长一段时间,而且新广告代码也只是对应业务线甲,加标识的事必须是全站广告都加才行。
如果拖到新广告代码上线,那么这一段时间是非常危险的,而且业务线乙也照顾不到(两条业务线之间差别大是历史原因);如果在老代码中加标识,又将面临低复用的代码,以及逐个添加的工作量(上篇文章中提到,“老代码”是由不同广告的片段拼凑而成的,并不存在共用的部分和可控的生命周期)。公司同时希望不要因为‘加标识’的事情而影响广告代码重构的进度,尽量减小‘加标识’的成本。
通过和广告组以及两条业务线的沟通。借鉴了应用程序开发中“补丁”这个思想,最终我们商量出一个方案——“老代码标识补丁”。
来个补丁:
这应该是个很小的项目,但是他也是我的‘孩子’,也很具有典型性。所以我把这么一个“简单的”代码拿出来与大家分享一下。
既然是个补丁就要明确几个特点:成本低、体积小、依赖少、适用性强、污染小、使用方便。下面就结合这几个特点来说一下:
1、成本低、使用方便:
由于要快速上线且不能影响广告代码重构的进度。所以一定要避免“过度设计”,力求降低实现难度。等到新的广告代码一上线(2个月后),这个补丁将随着‘老代码’一同下线。搞一套太复杂的工程,看来没有必要。将‘加标识’的功能单独压成一个js文件,在页面的底部引用,不用的时候去掉。
对于样式我们不会单独再写一个css文件,所有的式样都写到DOM 自身的style属性中。
2、体积小、依赖少:
由于DOM这部分要用的jquery,如果将jquery与‘加标识’的功能,打包到一个文件,会将文件大小增加好几倍(可能业务代码还没有js库体积大)。好在我们公司的每一个页面上都有全局的$(jquery)。那么我们引用页面里的jquery,显得更加合适。
为了减小体积,就不要将太多的库打包到js中。比如underscore,我们可以在其源码中挑出几个需要的函数封装到我们的js中。比如我这次仅仅依赖jquery(页面)和underscore(一部分)还有自己封装的公共方法。毕竟减少依赖可以在很大程度上减少体积。我最终生成的js(压缩前)文件的大小也只有不到5k,压缩后为3k。
将式样写到DOM的style中的好处是不要求页面中必须引用一个css文件,并且css权限较高。
3、适用性强、污染小:
与大多数情况js服务于少量页面(或单一页面)不同,考虑到要在公司全站的每一个页面上加入这个js,因此我们必须保证我们的js在任何一个页面以及任何一个浏览器中稳定运行,浏览器兼容性以及IE hack、还有在不同浏览器中js的那些坑,都是必须考虑的。
污染小是双方面的:一方面宿主页面的其他js不要污染我们加标签的js;另一方面我们的js也不要影响上一个作用域的环境。一个良好的闭包是必须的。
代码结构怎么组织:
1、方案一:
利用webpack自身产生的闭包,屏蔽内外环境。另外webpack还能按照commonJS的方式组织我的js代码,多个模块打包成一个文件。问题是这个项目的规模过小,不打算持续维护,加之我当时对webpack并不是很熟悉。
2、方案二:
从一开始就只有一个js文件,所有的代码封装到一个立即执行函数中。最后用gulp工具压缩一下。好处是代码结构简单,没有编译过程中混入的代码。缺点是没有模块管理。
最终我选择了看似比较普通的方案二,原因是:方案二已经足够处理我们的需求了,加之我们并不打算长期维护这个工程。如果需求在复杂一些我会考虑用方案一的。
逻辑结构是怎样的:
1、拿到广告位
既然是加标识,就要先搞清,哪些地方是需要加标识的。由于每个广告都有一个占位id,我只要知道了今天这个页面都哪几个占位id上广告了,这事就好办了。问题来了,其实我还真不知道,今天页面上都哪几个广告位上广告了。找到后端和业务线甲的后端商量出两个方案,(1)提供一个获取id(今天当前页面且上广告)的接口;(2)将这个页面的所有占位id通过吐在页面上。
咋一看方案(1)会比较合理。但我们采用的是方案(2),考虑到成本,为了这样一个临时的需求,破坏广告后台系统和系统运营各自的结构,会增加页面相应的时间和耦合性方案中的占位id是固定的,并不影响页面的相应时间,而且两个后端组并不需要改变自身的系统结构。但是要注意方案(2)中拿到的仅仅是当前页面中有哪些广告位,但是却没有“今天上了哪些广告?”。
对于广告位中是否有广告,其实在前端其实有比较另类的方式判断——占位id的DOM中是否有特定结构的内容。一般情况下占位id对应的DOM一般默认收起(高度为0),且内容为空。有广告的占位id一般包含图片或flash。我可以通过判断占位id中是否存在特定结构来判断今天这个广告位是否上了广告。这时候来了一个新问题,老代码中的广告渲染是同过js片段拼装的js文件处理的,我必须在广告老代码渲染结束后判断广告的有无并添加标识。但是单纯的依靠DOM ready 或 简单延时,是不能准确找到那个时间点的,各个页面的广告加载时间也不尽相同,何况老代码中各个广告加载并没有固定统一的生命周期(今天上的广告位中,即使一部分加载好,另一部分也可能暂时为空)。
2、分组延时、计数停止
既然DOM ready 或 简单延时都不能准确的判断,我们就从原有的基于事件和时间改为基于数据结构的。
我们首先要定义一个专门记录占位id是否已被插入广告的数据结构。
在DOM ready 之后开启一个定时器(注意要等上一个的回调执行完了,才能开启下一个)。每次检查数据结构中还没被插入广告的DOM,如果状态变化就“加广告标识”。
这种脏检查的检查范围会越来越小。注意因为有未上广告的广告位存在,如果不加入限制,脏检查会永远下去,因此我会规定分组延时的组数。我在这里设定了5组从DOM ready开始分别3000ms, 7000ms, 11000ms, 15000ms,25000ms 来检查。最终被检出的都加标识,剩下的被认为今天没上广告。
3、怎么加标识
原则上是加载原有广告的左下角就可以了。但是这里有个坑——老页面以及老代码的页面结构不合理。导致相对于父级元素的左下角不一定是广告的左下角。比如有些广告是靠居中定位的,但标识却加到了页面左边,甚至超出了1200px以外(页面没有一个大的main-wrap)。
对于这种情况,我们一方面要求维护页面的前端修正布局,一方面尽可能的通过js判断广告的有效位置。以及考虑标识插入的节点位置,不一定以站位id位标识的父级。考虑到老代码中的广告形式只有img和flash,并且img和flash都有固定的组成结构。所以结构匹配是可行的。
4、标识用css还是图片
给广告加标识,我们统一加在广告的左下角。但是就这么一个小小的标识该怎么实现呢?一般第一反应肯定是用属性加一个span
1 <div id="站位id"> 2 <div><!--广告内容--></div> 3 <span style="position:absolut;bottom: 0;left: 0;.......">广告<span> 4 </div>
起初我也是这么做的,后来发现我们的标识中文字大小过于小。超过了一些浏览器的最小文字大小限制。另外还要在style中书写过多的式样,一方面要控制样式,另一方面要覆盖可继承的属性,可读性太差。(当然主要原因还是浏览器对最小文字大小的限制问题)
这是我不得不使用图片来加标识。这时我发现主站和其他网站也都使用了图片的方式,看来是都被坑到了。图片方式如下:
1 <div id="站位id"> 2 <div><!--广告内容--></div> 3 <span style="position:absolut;bottom: 0;left: 0; background-image: url(..);.......">广告<span> 4 </div>
另外有个问题是设计同学反馈给我的,就是在设计制作这个小图片的时候,要注意衬线字体与非衬线字体的区别,当图片小到一定程度的时候。“广告”两个字能不能显示全。
总结:
相比庞大的广告投放系统和广告展示系统,这个‘加标识’的项目小的不能再小了。但是作为一个“补丁”,这段js却五脏具全,且不失简单轻巧。这种“补丁”,不仅要适应不同业务中完成质量良莠不齐的页面环境。还要在‘老代码’不提供任何接口的情况下‘摸黑’作战。保证功能的前提下,还要尽可能的实现工程化且避免过度设计。
严格来说,这个小型项目很具有典型性。也趁机把公司所有的页面情况摸了个遍。最大的收获是在重构广告展示代码前总结了现有页面的坑。让我的重构之路少了不少弯路。
我和广告前端代码的故事,还在继续!