• Feeds

  • 技术团队的标准化与可复用文化

    一次某用户在使用系统时候碰到一个问题,但不确认是系统的bug,于是问题通过各级的微博@消息反馈到产品与技术团队。在反馈链中,每一个同事都需要确认一下自己是否也出现这个问题,以便确认是否属实以及问题的范围(你可以理解互联网的从业人员为什么那么忙了)。

    当这个@消息最终传递到当事的工程师手里,他也需将描述的问题再测试一遍,如果不能重现,问题就变得更加复杂,工程师需要从一堆海量的日志去定位当时用户出现这种现象的原因。那一次排查到最后,发现原因是某台服务器的返回的数据不对导致,那台服务器可能是由于在某些特殊情况下,启动了一个不同的版本而导致用户的调用不能正常返回。如果你也是一个工程师,是否有几分熟悉的感觉?

    自从工程师键入第一行代码那一时刻起,开发流程中每个环节都存在出错的可能。比如

    • 代码可能未被单元测试覆盖,当然有更多的团队根本没有编写单元测试的习惯;业务流程未被功能测试覆盖;因此当最终用户才发现不正确的问题时(如文首描述的情况),整个技术团队需要更多的时间去解决。
    • 服务之间的调用方式、参数或逻辑不正确;当服务由不同的团队维护时,出错的概率会更大;另外一个团队的服务往往只有几行文字的描述的邮件,很多参数语焉不详,你需要通过以前的代码或者亲自调用一下来推测这些参数的作用。这时候如果你需要的功能刚好能调通,欢呼之外就是接着提交及发布代码了,因为上面已经开始对你联调时间过长有些不满。
    • 发布的版本、配置、顺序、服务器等不正确。尽管大家都觉得有一个上线手册之类的指导文档,但在实际工作中开发与运维的交互可能会是通过IM工具,开发工程师说,今天上线模块的顺序是ABDC,运维工程师虽然对为什么不是ABCD的这样的顺序有些疑惑,但也依照开发的顺序做完了。而下一次上线前,开发工程师没提及上线顺序,运维工程师可能纳闷,到底是ABCD还是ABDC呢?

    上面种种情况,如果每一次代码修改及发布都依赖每一位工程师的细心及考虑周全的话,则高概率的质量问题不可避免。而工程师则很沮丧的发现经常在解决同一类型的问题。而将全流程更多不易变的单元编程标准化,由程序来控制,则可以从源头上减少问题,让软件开发变得简单及享受。在上面例子中,可以将(1)测试过程标准化。(2)在服务化架构中,将更多的单元标准化。(3)发布流程标准化。这些点更多的看各团队工程的成熟程度。团队中标准化程度越高,软件的可靠性就会越好,更多的精力将会从各种不确定性问题中解放出来。

    说下第2点服务标准化的一个例子,Tim所在团队最近几年做的系统密集的使用了消息队列,经过几年的演进,也将各种很复杂的场景实现在系统内,比如多级串联、并联、实时性要求非常高的PUB/SUB,用自定义各种服务池来做任务调度,踩了很多坑也积累了很多经验。但是标准化意识以及抽象能力不足,业务架构与技术架构也缺少分开去考虑,因此大部分系统主要针对自己特定场景的设计去实现,即使有很多有价值的特性,但无法复用到另外的场合。当一些新人去学习Kafka这些系统时,老的架构师也会去反思标准化的问题。当然,3年以上的技术团队才会有这些感悟,年轻的团队通常会沉浸在搭建自己系统的快感中。

    再说另外一个例子,曾经有一篇技术贴说某个美国的工程师Twitter技术面试失败的经历。

    然后我们聊了一小会关于在twitter的生活。
    我挂掉电话的那一秒我意识到了我的答案是错的。
    ……
    现在我扪心自问:在这件事我学到了什么?客观地说——不多。对于面试官没有问我正确的问题来引导我向正确的方向思考,我很难过。当我的解答实际上不正确的时候,我不知道为什么Justin告诉我“这应该有用”。

    从中可以部分感受到硅谷科技公司对工程师的严格要求。但是国内的情况有所区别,由于互联网行业有些过热,合格的人才处于供不应求的情况,因此大部分岗位要求都比上文中描述的要求低。工程师只要具备基本的学习及模仿能力,比如能参考一个现有的软件能够实现相同的功能,那这个工程师基本能进入各个互联网技术岗位(当然表面的工作经历也是需要的)。

    在这种情况下,整个技术群体很难对代码质量形成太多共识。再加上大多团队是业务驱动型(技术驱动的凤毛麟角),企业对于技术的要求局限在按时交付。精心雕琢的软件相比于打补丁修补而成的软件并不会得到更好的价值体现,因此追求高质量软件的风气也不易形成。

    企业里面的软件可复用还有恶性循环的情况,比如某个工程师做了一个公共组件来解决一些公共问题,但是另外一个团队在使用后发现存在一些问题,理想的情况是,反馈的问题得到了及时修复,解决了各个团队共性的需求,公共组件在更多场合得到了推广。但在现实中,具备开发良好公共组件能力的人是非常稀缺的,尽管每个Web开发团队都有开发一套自己的MVC框架,但在做到优雅的业务实现方面尚有不足的情况下,一厢情愿的去做自己的标准组件是较难把握共性的需求及得到使用认可。

    在另外一方面,其他的团队也容易把“那个版本没有很好的满足我们需求”的情况成了自起炉灶的理由,通过各自建设,表面上完成了自己的内部需求,自认为团队内部的认知成本是降低了,但人的精力毕竟有限的,有限的时间只能做好有限的事情,最终各自搭建可能也只能建成一些不可复用的系统,除了短期的自我充实感之外较难有更多的价值。

    修改WordPress主题以适配移动浏览

    考虑到网站不少用户是从移动媒体点过来阅读的,但之前的网站WordPress主题只是针对PC端设计,且几年未做过修改,因此周末将网站对移动端浏览适配做了优化。

    首先博客界面以内容阅读为主,一直选择极简风格,去掉了不必要的组件。由于主机不在国内,考虑到访问速度,也尽量减少CSS及JS文件的数量。因此在官网可选的主题就很不多,一些免费的主题也对移动界面做自动适配,但是测试后加载速度及一些细节问题不理想,另外大多主题内部的PHP结构复杂,不利用自定义修改。因此打算发挥工程师的hacker精神,自行修改一下CSS及相关的代码来适配移动浏览。

    Tim的WordPress是在一个叫R755的主题上修改的,这个主题比较简单,仅有一个CSS文件,PHP也是按照功能划分,所有PHP文件在10个以内,再加上页面布局主要是通过div来控制,因此比较容易修改。

    为了适应移动浏览,首先在HTML的meta里面添加viewport,
    <meta name=viewport content="width=device-width, initial-scale=1">
    主要是让iOS能够按照1倍初始缩放来展现文字。Viewport参数说明可以参看官方文档

    字体方面,由于主要为屏幕浏览设计,首选无衬线字体,这方面支持最广泛的就是Helvetica Neue,Mac及iOS的浏览器都支持,它据说也是乔布斯最喜爱的字体。如果你想单独对Mac做优化,可以考虑使用“Lucida Grande”,在Mac上有出色表现,但未在iOS支持。另外Mac/iOS也都支持Avenir,据说是下一代Mac/iOS的favorite字体,显示效果也不错,但个人感觉用在input/button/link等元素上面没有Helvetica正式,所以最终还是选择Helvetica Neue。

    字号方面可以选择16px或者18px,Tim是选择了18px。因此最终修改body的css如下
    font: 18px/1 "Helvetica Neue", Helvetica, Tahoma, Arial, \5b8b\4f53, sans-serif;

    其他div的字体定义最好通过使用em单位来定义,这样后续只需要修改一处字号就可以全局联动。

    排版方面,首先将原先所有div定义的width进行修改,先都去掉注释掉,改成自动适配宽度。但去掉宽度之后在PC端排版会横向无限拉宽,因此需要添加一个max-width来限制最大的宽度。右侧栏考虑到在移动屏幕的大小无法展示,因此通过JavaScript来控制,只在屏幕超过一定宽度时候才展现。

    由于Tim的blog浏览用户50%以上都是Chrome浏览器,因此优化调试过程中主要利用Chrome的developer模式来进行,它可以直接在打开页面时候在浏览器控制台调整CSS来实时观察效果,优化的大部分工作是将div的margin, border, padding等参数微调之后再在手机的Safari上验证效果。
    div
    (如图所示,Chrome中可以直接通过点击上图中边框的相应元素来直接修改展示效果,非常便利)

    最后,由于Gravatar也遭到了封禁,考虑到个人blog评论量不是特别大,而且考虑到加载速度,因此在后台禁用了显示Gravatar,但界面留下了一块空白区域,因此加了一个静态的默认头像。

    由于Tim是在Mac的工作环境修改的,修改完成之后,再在Windows下观察了Chrome, FireFox及IE下的表现,确保没有大的排版问题。

    如果装了xcode,可以在Mac上通过iOS模拟器来测试浏览器效果,不需要启动xcode的模拟器打开方法,命令行输入
    open "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Applications/iPhone Simulator.app"

    另外也测试了Samsung自带的浏览器,其他手机终端未进行测试,目前测试本网站所有页面已经可以在iPhone/iPad正常显示,如果有严重的排版问题,也欢迎留言提出,留言时请告知手机型号及浏览器版本。如果你在PC浏览,可以通过下面二维码进入首页测试。
    timyang_net_qrcode

    架构面试题 – 为什么我的朋友圈不见了?

    经常有朋友问到,“感觉你们的系统最近没什么太大变化,你们那么多工程师在忙什么?”,下面的这个场景,可能是工程师花费了不少时间的情况之一。

    有如下一个场景,某个服务需要构建一个列表数据返回给调用方(调用方通常是客户端),服务本身是一个数据聚合器,它由内部多个远程服务的数据聚合而生成。在正常情况下,需要将所有内部服务的结果全获取成功后再返回。但是在一个大系统中,多个服务中某个服务出现不稳定的概率会比较大,当出现如图远程服务3不可用的时候,有三种不同的解决思路。

    list1

    • 方案1:忽略出错的数据(图中数据3),直接返回数据1、2、4。
    • 方案2:遇到任意失败,整个请求返回错误503 service unavailable。
    • 方案3:忽略出错的数据(图中数据3),并告知调用方出错的范围,需要自定义的返回格式。如 {“load_data3_success”: false}

    如果你作为一个架构师,会选择哪种方案?

    方案一类似架构设计里面常说的优雅降级,在出现问题情况下,除了数据3不能返回之外,其它数据可以正常返回,原理上可以将损失降低到最低。但这种方案会给用户体验带来一定伤害,用户在使用系统时候会存在不确定性的心理感受。

    方案二比较依赖调用方的容错逻辑,如果调用方保存了上一次缓存,且容错逻辑处理得当,用户表面会感受不到这个异常。如果没有容错逻辑,最坏情况则将会返回白页。但是即使有容错逻辑,由于正常的数据也不能及时返回,从工程师到用户可能不太容易接受这个结果。

    方案三是一个看起来相对合理的方案,但是需要添加自定义的字段,本来这个调用是一个标准的LIST数据返回,但如要判断每个数据项是否返回失败,需要额外添加一些标识字段如 {“load_data3_success”: false},用于标识哪些数据返回失败了。因此,接口设计及实现变得更加繁琐,调用方也需要实现缓存及容错逻辑,从服务方到调用方的熵都增加了很多。

    因此,这个选择题已经不好做了。但雪上加霜的是,在大部分应用中,对于数据列表访问同时还存在未读数的功能,如下图中的小红点数字。如果这个未读数由另外一个API提供(本讨论假设未读数API功能正常),情况就更复杂。

    补充讨论一下,如果不提供单独的未读数API,客户端需要每次需要加载新的全量数据才能本地算出未读数,会带来访问速度的下降及客户端更多流量的消耗。因此大多数情况提供一个未读数API整体开销会更低。通过未读数API判断当服务端有新数据时候才去访问列表接口。

    list2

    这时候如果未读数都出来了,远程数据又取不到的情况下,你作为架构师,会选择何种方案?至少,碰到这种情况时如果还未找到理想方案,建议不要盲目优化,因为它除了增加系统的熵,不会将事情变得更好。