《Web全栈工程师的自我修养》读书笔记
【声明】
欢迎转载,但请保留文章原始出处→_→
生命壹号:http://www.cnblogs.com/smyhvae/
文章来源:http://www.cnblogs.com/smyhvae/p/5243181.html
【目录】
- 01 什么是全栈工程师
- 02 如何成为全栈工程师
- 03 从学生到工程师
- 04 野生程序员的故事
- 05 工程师事业指南
- 06 全栈工程师眼中的HTTP
- 07 高性能网站的关键:缓存
- 08 大前端
什么是全栈工程师
全栈工程师(Full-Stack Engineer):一个能处理数据库、服务器、系统工程和客户端的所有工作的工程师。根据项目的不同,客户需要的可能是移动栈、Web栈,或者原生应用程序栈。
全栈:表示为了完成一个项目,所需要的一系列技术的集合。应该从能力和思维方式两方面,来判定一个人是否是一个合格的全栈工程师。简单来说*全栈工程师就是可以独立完成一个产品的人。
1、Web开发流程
大中型互联网公司的产品研发流水线:产品设计–>交互设计–>视觉设计–>前端开发、后台开发–>测试–>发布。
产品经理:产品经理其实是对一个产品负根本责任的管理者。他通常的工作包括制订产品规划、协调多方资源、把控产品方向和质量细节,等等。有时候,他会从头策划一个新的产品,而更多的时候,他是在优化已有产品的一个部分。总之,在流水线中,产品经理需要从策划跟进到发布,是一个非常重要的角色。
用户研究员:用户研究员的工作是研究用户行为,有时候他会从宏观的角度分析数据,有时候也从微观的角度分解用户场景,有时候会召集一些用户专门来访谈,或者观察用户对产品的使用情况。从输出品的角度来说,用户研究员一般输出用户研究报告来交付给产品经理和交互设计师,作为产品设计的目标参考。
交互设计师:交互设计师常被简称为“交互”。他与视觉设计师最大的区别是,交互设计师更多着眼于如何优化用户界面的信息分布和操作流程。交互设计师的输出品一般是描述用户与网站“交互”过程的流程图,以及描述页面信息结构的线框图。输出的线框图会交付给视觉设计师。
视觉设计师:在细分交互设计师和视觉设计师的大公司,视觉设计师根据交互设计师输出的线框图来做一些润色和设计,输出最终的产品视觉稿之后将视觉稿交付给前端工程师。在一些不细分交互设计师和视觉设计师的小公司,二者被统称为“设计师”,他们的职责就是负责整个用户界面的设计。
前端工程师:产品视觉稿在得到产品经理和交互设计师等多方确认之后,会交给前端工程师,由前端工程师制作页面,实现视觉稿以及交互功能。从头衔上的变化就可以看出,这时候才真正开始编码。前端工程师需要非常熟悉HTML、CSS和JavaScript,以及性能、语义化、多浏览器兼容、SEO、自动化工具等广泛的知识。
后台工程师:使用服务器编程语言,进行服务器功能的开发。在编程语言的选择上,很多公司都会出于团队已有成员的知识储备、程序员的供给量或者语言性能方面来进行选择。在这一方面,后台语言的选择是相对自由的一件事,不像前端工程师,为了页面兼容性,必须使用HTML和CSS。如果关注各大公司招聘信息的话,您就会了解,不同公司使用不同的后台语言,比如传统的C#和C++、Java、PHP,或者新潮的RoR和Python。小公司的后台工程师除了负责功能开发,可能还会负责服务器的配置和调试、数据库的配置和管理等工作。在大公司,这些工作会分别委派给后台工程师、运维工程师、数据库管理员(DBA)等岗位。
运维工程师:运维工程师是跟服务器打交道的人,他会关注服务器的性能、压力、成本和安全等信息。
测试工程师:顾名思义,测试工程师保证产品的可用性,即使在小公司,这一职位也是不可或缺的。
备注:在项目管理中,经常会用到甘特图。甘特图(Gantt Chart)是柱状图的一种,显示项目、子项目、进度以及其他与时间相关的系统的进展情况。
2、技术的发展
提到全栈技术,不得不提一个代表性的全栈框架——MEAN,它是MongoDB-Express-AngularJs-Node.js的缩写,是从数据库、服务器到前端页面的一个完整技术栈。
MongoDB是一个面向文档的、NoSQL类型的数据库。MongoDB颠覆了传统的基于表的数据存储方式,而采取了类似JSON的文档结构来存储数据,因而它在储存数据时可以更加灵活。
Express是一个Node.js框架,可以创建灵活的Web服务,比如单页面应用程序、多页面应用程序和混合型App。
AngularJS是一个开源的JavaScript框架,由Google和开源社区共同维护,它用来创建单页面应用程序。它的目标是使用model-view-controller模式来规范Web应用程序,让开发和测试富交互的单页面应用程序变得更加轻松。
Node.js是一个运行在服务器端的JavaScript运行环境,它的底层是基于Chrome的JavaScript运行环境——V8引擎。Node.js可以作为服务器端语言,用来创建快速、可扩展的应用程序。Node.js也可以在本机运行,做一些本地操作,比如加速本地开发流程,或者实现一键发布。
MEAN可以说是传统的LAMP方案的有力竞争者。因为从服务器端到页面端都采用同样的语言(JavaScript)和同样的架构模式(MVC),所以一个擅长JavaScript的工程师可以兼顾前后端的开发,并且前端模板代码和后台模板代码是可以复用的。
3、提供PaaS服务的平台越来越多
随着Web技术的发展和开源社区的积极努力,有很多公司提供便宜又方便的一条龙服务,可以解决独立开发者的大量麻烦。
比如Amazon提供的PaaS(Platform as a Service,平台即服务),就可以让创业公司的开发者省去架设和维护服务器的麻烦。
而GitHub在2012年获得了一亿美元融资,也可以看出市场对代码托管市场的信心。可以预期,未来可能会出现越来越多为开发者提供服务的公司。以后,小公司也可以用更低廉的价格获得世界级的IT服务支持,毫无疑问,更多的IT服务将托管在第三方的服务器上。
VPS(Virtual Private Server,虚拟专用服务器)是把一台物理服务器虚拟成多个虚拟专用服务器的服务。每个VPS都可分配独立的公网IP地址,运行独立的操作系统,拥有独立的磁盘空间、内存、CPU资源、进程和系统配置,模拟出“独占”使用计算资源的体验。
4、一专多长
我跟一位行业专家讨论过全栈工程师的话题,他不是很赞同全栈工程师这个方向。他认为,工程师应该有专精的技能和目标,如果初学者贪图大而全,反而样样不精。我理解他的担心,如果一个工程师没有坚实的基础(比如专业理论知识,对常用设计模式的理解,或者特定职业的基础知识),那么了解的非本专业技能越多,越容易迷失。
所以我认为,全栈工程师首先要“一专多长”。一专多长的意思是,工程师首先有一个专精的方向,在这个方向上足够精通之后(高级工程师级别),以此为突破点去学习更多的知识,增加自己的长处。如果还没有获得某个方向上足够深入的理解,就不要囫囵吞枣地去学习其他领域的知识。
有些知识需要时间的积累,并不是快速阅读就可以掌握的。“全栈工程师”这个名词可能会引起读者的误解。勿在浮沙筑高台,“全栈”是一个长期积累的过程,是专精型工程师在不断解决问题的过程中积累知识和经验所形成的能力,而不是一蹴而就的过程。
5、解决问题,而不是醉心技术
公司存在的意义就是解决问题,公司要解决用户的问题,而员工要解决公司的问题。
公司的问题可能是降低成本、扩大用户群、增加成交量、优化性能,等等。不同的问题优先级不一样,投入同样的时间,有的项目能为公司增加上百万的收入,而有的项目却只能增加几万。
互联网领域发展很快,问题的优先级永远都是在动态变化的,所以团队往往每半年或者三个月就要回顾一下当前形势,并制定新的工作计划。如果新计划不是您擅长的,怎么办?您应该马上开始学习新的技术,这就是我说的关注问题,而不是醉心技术。
高级工程师可以选择往上下游去扩展自己的能力,并承担更多的责任,给公司带来更大的收益,也给自己带来更大的成长空间。程序员在小公司里主动去承担更多责任,自己跟公司都会获得相应的成长。在自由职业市场,全栈工程师是最闪耀的明星。全栈工程师还是天生的创业者。
延伸阅读:
- 《黑客与画家》(美)保罗·格雷厄姆,人民邮电出版社
- 《专业主义》(日)大前研一,中信出版社
如何成为全栈工程师
1、先精后广,一专多长
推荐采用“先精后广,一专多长”的流程来学习:先在一个特定的方向上有比较深入的钻研,然后再将学习目标渐渐推广开来。比如先从前端方向入手,掌握了基本的HTML、CSS、JavaScript之后,不要转头向服务器端语言或者App方向发展,而是深入到性能优化、SEO、多种框架、响应式页面等前端细节中去。经过一到两年的深入研究之后,再去学习其他方向。
采用这种方式来学习,不光可以触类旁通、举一反三,还让我们学习得更快,而且循序渐进更符合一般人的职业生涯发展。
腾讯社交用户体验设计部招聘前端开发,要求如下:
- 本科以上学历。
- 两年以上工作经验。
- 精通HTML、CSS、JavaScript等前端相关技术,熟悉W3C网页标准。
- 熟悉至少一种后台语言的开发机制(如Java、C++等)。
- 有一定架构能力和算法能力,有良好编码规范。
- 良好的学习能力、沟通能力,追求完美,有工作激情,能在较大强度下工作。
- 热爱互联网,喜欢研究各种互联网技术者更好
有的竞争者提到他很擅长页面性能优化、响应式、页面渲染效率,有的写过JavaScript框架……你需要在招聘要求的方向上以200%的能力来得到这个职位。
2、围绕商业目标
老板雇用一个员工,不是因为他能写程序,而是因为他能帮助自己赚钱。
我喜欢这样的态度:对未来有自己的方向,但也知道自己没法看得太清晰。对商业和市场有想法,而且自己也有足够的技术能力和自信向未来前进。
记住,当您只有一把锤子,您看什么都是钉子。而如果您痴迷于工具,反而看不到问题所在。因此,要先看看有哪些问题需要解决,然后再补充您的工具箱。永远从商业目标的角度来决定学习哪些东西,而不是纯粹为了锻炼技术能力而去学习。
3、用户是谁
这里的“用户”仍然是一个广义的定义:所有您为之服务的人。
4、大巧若拙
大巧若拙:指真正聪明的人,不会显露自己,反面从表面看好像还很笨拙。用户体验不只是界面和交互这样可以直观感受的东西,还包括一些隐藏在用户界面背后的细节和规范。
就像冰山,露出水面的部分只占整个冰山的1/9,用户看到的只是显露出来的部分。背后的部分一般用户是看不到的:比如用户研究,用研团队会通过调查,输出一些用户画像,影响整个产品的功能方向、设计风格;还有设计规范,设计团队在设计产品的一开始制定了规范之后,新增加的功能和页面都必须遵循已有的设计规范,这样整个产品是统一的,能够给用户专业的感觉。
我如果开创一个公司需要招聘“全栈工程师”,我要求的三个能力:一专多长、关注商业目标、关注用户体验。
延伸阅读:
- 《重来:更为简单有效的商业思维》 (美) 贾森·弗里德 / (丹) 戴维·海涅迈尔·汉森,中信出版社
- 《精益创业》(美) 埃里克·莱斯,中信出版社
从学生到工程师
前端工程师要有一个基本常识,那就是结构、表现和行为要分离。具体解释如下:
- 网站的内容使用语义化的HTML标签,而不掺杂任何表现和逻辑;
- 网站样式表现用CSS来描述,既能在多个页面之间复用,也可以根据不同用户来分别定义外观;
- 页面行为逻辑用JavaScript来实现,这样保证浏览器在禁用JavaScript的时候,页面也能正常渲染和使用。
岗位优先于公司,即使在一个很好的公司里面,如果只是做着自己不喜欢也不擅长的工作,那能有什么前途呢。
其实我的设计知识仅限于自学,来自于一本书——《写给大家看的设计书》。这本书非常入门,但是浅显易懂,既有设计理念,也有实际操作,到现在为止我反复看了3遍以上。
我理解了书里说的设计四大原则:对齐、对比、距离和重复。虽然我基本没有设计经验,只会一些基本的Photoshop操作,但我理解了这几个原则,每次看到好的设计和差的设计时,都能有所感悟。如果不理解,可能我只能用“上流”“高端”“简约”这样空泛的词汇来描述设计。关于设计原则,我在后面的章节中会单独提到。
校园招聘是很多大公司很喜欢的一个人才渠道,因为比起社会招聘的应聘者,毕业生更加有空杯心态、更正能量、更有激情,虽然缺少经验,但是经过一两年的培训也能很快成为团队骨干。而如果是本身有项目经验的毕业生,或者是在GitHub上有知名作品、知名博客、去过其他大公司实习的毕业生,那就更加抢手了。至于大学考试成绩,影响不大。
社会招聘的目标是有经验者,招聘时间没有校园招聘那么固定,随时都可能有职位空缺,但是每次放出的名额不会很多。而且这时候会根据招聘岗位,有针对性地考核应聘者的专业能力与综合能力,导致社招的竞争是非常激烈的。
相对而言,我认为校园招聘的门槛并不高,重要的是找对方法。如果您的学校不是顶级,您的成绩不是学霸,那就要走不寻常的道路。
1、获得面试机会
无论您是名牌大学的高材生,还是自学成才的专科生,在制作第一份简历的时候,我有这样几个建议:
- 首先确定自己的求职意向,针对特定意向填写您的简历。
- 如果您想表达出自己的创意,不要使用各大招聘网站提供的简历模版。
- 把简历发送到真正在招人的企业主管那里。
举一个例子,作为程序员和设计师,作品是排名最高的信号。在著名开源项目中贡献代码,说明您有能力阅读和编写好的代码,这是公司直接需要的技能。此外,这还能说明您有能力与他人协作:开源代码总是需要协作的。开源项目还能表明您对新鲜事物有热情,表明您也许英语能力不错,有查阅文档的能力……一个开源项目需要的精力也许不会特别多,但它的加分点可就非常多了,简直是一箭N雕!
为什么要把简历发送到真正招人的企业主管那里?因为HR没有能力辨别技术能力的高低,他只能根据学历、分数等硬指标来筛选。所以一些技术能力优秀但是分数不高的同学可能就很遗憾地失去了面试机会。
2、实习
实习能提升自己的实践能力,可以认为是从学生到社会人士的一个身份过渡。建议:
- 记住团队里的每一个人
- 有任何问题,主动问导师
- 主动介绍自己,告诉大家自己是新人,请多关照
- 每周发邮件记录心得总结、经验教训、学习成长
- 实习结束时,用邮件总结所有项目,给出交接文档,并向大家致谢
延伸阅读:
- 《编程之美:微软技术面试心得》《编程之美》小组,电子工业出版社
野生程序员的故事
野生程序员是指仅凭对计算机开发的兴趣进入这个行业,从前端到后台一手包揽,但各方面能力都不精通的人。野生程序员有很强大的单兵作战能力,但是在编入“正规军”之后,可能会不适应新的做事方法。
1、Web性能优化
- 压缩源码和图片
JavaScript文件源代码可以采用混淆压缩的方式,CSS文件源代码进行普通压缩,JPG图片可以根据具体质量来压缩为50%到70%,PNG可以使用一些开源压缩软件来压缩,比如24色变成8色、去掉一些PNG格式信息等。
- 选择合适的图片格式
如果图片颜色数较多就使用JPG格式,如果图片颜色数较少就使用PNG格式,如果能够通过服务器端判断浏览器支持WebP,那么就使用WebP格式和SVG格式。
- 合并静态资源
包括CSS、JavaScript和小图片,减少HTTP请求。
- 开启服务器端的Gzip压缩
这对文本资源非常有效,对图片资源则没那么大的压缩比率。
- 使用CDN
或者一些公开库使用第三方提供的静态资源地址(比如jQuery、normalize.css)。一方面增加并发下载量,另一方面能够和其他网站共享缓存。
- 延长静态资源缓存时间
这样,频繁访问网站的访客就能够更快地访问。不过,这里要通过修改文件名的方式,确保在资源更新的时候,用户会拉取到最新的内容。
- 把CSS放在页面头部,把JavaScript放在页面底部
这样就不会阻塞页面渲染,让页面出现长时间的空白。
备注:每一个条目都可以进一步深层挖掘下去。Web性能优化分为服务器端和浏览器端两个方面。
此外,由于中文的歧义性,Web性能优化这个词既可以解读成页面加载速度(Page Speed)的优化,也可以解读成页面渲染性能(Page Performance)的优化。或者是二者的集合。所以,应聘者如果能在这个问题上多做一些分析,会有很高的加分。但是如果你在网络性能方面的研究只是浅尝辄止,停留在压缩资源方面,这说明你还没有足够理解HTTP协议本身。
关于网络性能和HTTP协议,作为大公司的前端工程师是非常看重的,因为每一个页面都会有亿万用户访问量,任何一点对服务器带宽压力都会积少成多,最终造成很大的成本。关于这方面的技术详解,我在后面会有一篇单独的文章来分析。
2、知易行难
我问一个面试者:“关于服务器端MVC架构的技术实现,您是怎样理解的?”他说:“是数据模型、视图、控制器的分离。”
我更进一步问道:“这种架构方式有什么好处?您在项目中是如何应用这一架构的?”他回答说:“MVC的架构方式会让项目可维护性更高,所有涉及界面的代码都在视图(View)里面,所有涉及核心逻辑的代码都在模型(Model)里面,URL路由之类的代码都在控制器(Controller)里面。我在项目中使用了MVC架构的PHP框架——CodeIgniter。”
我一边打开他的网站,一边继续跟他电话沟通。当看到网站的CSS代码都直接内嵌在HTML头部的时候,我忍不住问他:“为什么您的网站的CSS代码都内嵌在HTML里面呢,是使用自动化工具合并进去的吗?”他支支吾吾地说:“因为在本地调试的时候,CSS文件修改经常不生效,所以就直接在HTML里面改了,这样比较快。”
好吧,我想这是一个典型的“知易行难”的开发者,他知道采用MVC架构的项目的可维护性更高,可是在分离样式与结构上面还没有达到最基本的要求,甚至把CSS写在HTML中。至于他说的在本地环境上发现CSS文件经常缓存,可能要看看本地服务器的缓存设置是否有问题,然后再做调试。稍微了解一点HTTP的浏览器端缓存,这就不是难事了。我更欣赏在开发流程上花工夫去理解和优化的应聘者,而不是马马虎虎,只是以完成需求为目标的人。
3、什么是“野生程序员”
“野生程序员”:就是没有计算机基础知识和相关教育经历,靠着对计算机开发的兴趣进入这个行业,虽然知识面比较广,但是各方面都一知半解的开发者。
这几年我从一个求职者,转变成一个招聘者,有一个感受就是,中国高等教育与市场需求不接轨。学校不了解市场究竟需要什么样的人才,其设立的课程和技术往往比市场技术现状落后了5年以上。我在大学学习用ASP建站,但是现在已经几乎没有人用ASP建站了。一个直接的后果是,很多高校毕业生不能满足企业的要求。
与此同时,中国互联网市场蓬勃发展,特别是移动互联网的发力,让中国跳过“WAP时代”,直接进入“App时代”。市场的热钱都投入到互联网行业,“BAT”等大公司不断扩张,创业公司也如雨后春笋,整个市场对软件工程师的需求缺口巨大,所以很多公司在招人的时候,没法招聘到“专业”的计算机专业毕业生。
在美国,因为教育与市场稳定发展了很多年,供求关系相对平衡,计算机相关专业本科已经成为基本要求。举例而言,美国的硅谷公司(如Google)绝大部分前端开发招聘岗位都有一个最低要求——本科学历,计算机相关专业。
相比而言,从中国的大公司(如腾讯)的招聘网站上可以看出,有一些前端开发岗位没有对学历的要求,也有一些要求“本科及以上学历”,少数才会要求“本科学历,计算机相关专业”。我们的团队中就有一些成员是大专学历。许多企业在招聘的时候往往放松了对学历的要求,只看重项目和经验,而不看重学历。这是一件好事,代表市场在高等教育的规模和质量都跟不上市场要求的情况下,给予更多有兴趣和能力的年轻人进入IT领域的机会,也填补了人才市场的空缺。
美国硅谷,是世界互联网公司的中心,是所有求职者梦寐以求的圣地。在最开始,硅谷之所以名字当中有一个“硅”字,是因为当地企业多数是从事加工制造高浓度硅的半导体行业和电脑工业。随后,互联网公司和软件公司渐渐取代传统的硬件公司,让硅谷获得了新的生命,但硅谷这个名字保留了下来。在硅谷从诞生到发展壮大的整个生命周期中,斯坦福大学起到了很大的作用,我认为称之为硅谷的母亲也不为过。
在中国,由于政策、环境、历史原因,还有大学教育投入上的差异,导致大学在整个互联网发展中起的作用没那么大。中美两国IT人才市场供求关系上的这些差别,也反映在整个行业文化中。
一个直观的反映就是软件工程师的“草根”化。其实很多软件工程师的收入都很高,处于中上层水平,相比金融行业的白领也毫不逊色,但是一谈起程序员,大家的印象还是“一年四季的T恤(在行业展会上免费拿的)牛仔裤,平时也喜欢宅在家里,不会像同样收入的金融白领,平时爱好听歌剧打高尔夫球”。这种差异一方面是外部人士对软件工程师职业的偏见,另一方面也是程序员行业的自黑习惯。在招聘时岗位要求就已经放到最低:不要求学历、上班不要求着装、上下班时间灵活,这样才好更方便地招聘。而金融行业有意识地塑造一种“精英”文化,从学历就设置高门槛,即使有些工作根本不需要那么高的学历。
回到毕业生的话题,很多跨专业的学生发现自己兴趣在互联网和计算机方向的时候,就开始了自学之路,基本上学习方式有这样几种:
书:在计算机图书领域,技术难度跟图书销量是成反比的,从标签教起的HTML/CSS基础书籍卖得最好,其次是关于JavaScript和jQuery的书,Angular和Node.js之类的就没那么畅销了。
互联网:得益于全世界都在互联网上共享的资源,现在的学习者有了更多的选择,比如关于Web开发基础教学的W3CSchool,还有海量的技术博客。我个人喜欢订阅一些英文大站,比如Smashing Magazine(http://www.smashingmagazine.com/)、tuts+(http://tutsplus.com/)等。我在读大学的时候,Google Reader还没有永久关闭,那时候我很喜欢用RSS来关注这些站点的更新情况。Google Reader下线后,就基本上废弃了RSS阅读的习惯,转而用一些社交网站来追踪更新情况,但是有时还是会淹没在大量无用的信息里面。
社团:学校的网站社团也孕育了许多能力很强的开发者,社团经过历届的传帮带,技术有所积累,比如师兄会教师弟用Sublime编辑器,这就比还在用Dreamweaver的同学更有优势。此外,学校社团有一些定点客户,比如学校教务处、周边商户,所以有更多的实战经验,在毕业时作品集也丰富了不少。
因为有这样一些自学渠道,所以不一定只有计算机专业毕业的学生才有机会进入互联网行业。毕业之后,这些计算机爱好者进入不同的工作岗位,不同的是,有些进入大公司,有些进入小公司。这两者的成长轨迹往往会不太一样。
4、大公司还是创业公司
如果你是毕业生,这种情况下我还是建议选择大公司,因为会选择创业公司的人往往有自己的主见,已经接受创业公司的邀请去工作了,不会去发帖询问大家的意见。当然这是开玩笑,真正的原因是,在大公司的头两年,是从学生到职场人士的一个转变,您可能会从大平台学习到一些规范的流程方法,养成一些足以影响您一生的习惯,认识更多的能对您职场有帮助的人脉。
大公司能给你的有:
- 较小的风险
每个公司都有倒闭的可能,但是,显然大公司比小公司的风险低多了。如果您的风险承受能力较低,那么不得不考虑这个因素。
- 技术最佳实践
在大公司,对代码质量和一致性的要求很高,所以一般在最终发布前会有代码审查(Code Review)流程和项目总结会等。如果您完成了一个任务,但是没有采用最佳实践,只是hack了一下,那么其他同事可能都会指出您的问题,并且要求您改正之后再提交。小公司或者创业公司人力比较紧张,在他们看来,快速实现和上线,比优雅地上线更重要,所以对于一些最佳实践类的问题,只能睁一只眼闭一只眼啦。
- 垂直专精的技能
大公司专业分工很细,而且有更多技术沟通和沉淀的氛围,所以容易让人在垂直专精的技术方向有足够的发展。在小公司更能锻炼技术的广度,深度上缺乏锻炼的环境。但是其实二者的利弊,都是外界的,技术人员的个人成长除了工作时间的锻炼,还要靠下班后的时间,外界只是给予一个环境或者机会。
- 服务海量用户的经验
同样是做一个网站,服务少数用户量和服务海量用户量时需要考虑的事情是完全不同的。小网站遇到的问题,大网站一定遇到过,而大网站遇到的问题,小网站就不一定遇到过了。当一个网站发展到业内最强时,它的问题没有人遇到过,这时候就不能凡事问百度、Google或Stack Overflow了,而要自己去探索解决方案。
- 软技能
硬技能是指每个职位需要的专业技能,软技能则是通用的技能,比如沟通、影响力、项目管理和演讲等。越是大公司,越是看重影响力,所以会有很多培训教您如何提高影响力。
我在面试一些来自小公司的应聘者时,就发现他平时的工作中,周边环境很少有分享和沉淀的习惯。沉淀和总结是很重要的,在腾讯,设计师做完一次设计定稿之后,就会把设计的思路,包括整体的设计风格、设计规范和色彩的确定等都总结成一封邮件或者PPT,发送给部门同事。每个人都要有意识地维护自己的作品集,它在半年一次的考核、晋升面试甚至以后的跳槽中都非常有用。但是小公司的设计师不太会总结个人作品集,时间紧急是一方面原因,另一个主要原因是环境不需要他这样做,因此就缺乏了这方面的锻炼。
- 人脉
每年都有不少人从大公司离职去创业,这是非常自然的事情。对于大公司出来的人来说,之前积累的人脉资源这时候会起到很大的作用,比如创业期间的一些合作机会或者资源的互利,等等。万一创业失败,也不会很惨,因为您之前接触的人脉可以给您提供工作机会。但如果您刚毕业就选择创业,创业失败之后没有人能给您提供工作机会。
- 心态
其实大公司能给予毕业生最大的优势,就是提供一个心智培育的土壤。之前参加面试官培训的时候,我大概了解过公司招聘一个毕业生投入的成本。从校园招聘,到安排面试官面试候选人,再到封闭培训和一些课程培训,再给一段时间熟悉项目,最后3个月试用期后可能还要淘汰掉一些。如果把成本平摊到每一个人身上,这些投入要一年才能收回来。而小公司不会有这么大的耐心去培育一个新人。如果没有足够的时间去学习和成长,可能在一两年后,员工的能力也比较全面,但是样样都不精通,也说不清楚自己的目标是什么,于是就变成了“野生程序员”。
综合来讲,在大公司中,从硬技能到软技能都会有很多经验丰富的前辈能够教您,您会在大平台上学习到很多东西。工作几年之后,员工的选择也很多,要么走技术路线继续发展下去,做高级工程师;要么学习管理和领导力;要么出去创业。
所以,我的个人建议是,从毕业生自己前途发展的角度来看,先加入一家上市大公司是个不错的选择。
延伸阅读:
- 《打造Facebook》王淮, 印刷工业出版社
工程师事业指南
我曾读过一本有意思的书,《您就是极客》,副标题是“软件开发人员生存指南”。其中第二章专门讲软件工程师事业的3个关键词:技术、成长和声望。前面的文章里已经讲了技术和成长,现在我们来谈谈声望。
1、重视作品集
作品集(portfolio),是指您个人的项目和作品的集合,一份精心准备的作品集比简历更能说服人。
我很重视作品集,一方面体现在我很在意维护自己的作品集,另一方面我也很喜欢面试的时候看到应聘者有自己的作品集。除了工作上安排的项目,我更在意一些课外项目,因为它展示了您的兴趣和热情所在。
从某种程度上来讲,重视展示项目这种态度确实会对编程的纯粹性有所腐蚀(如果您编程本身只是为了自己的兴趣),您编写一个项目的动机可能会从纯粹为了好玩,变成获取收益。但是在这个商业化的市场里,对方(高效地)得到了您的信息,您得到了您应有的评价,这对双方是互利的。
对于程序员来说,成本最低的一种作品展示方式就是把自己的代码发布到GitHub上。
名为“Open Source (Almoset) Everything”的一篇文章中,有这样一句话:“If you do it right, open sourcing code is great advertising for you and your company.”如果使用得当,开源代码是您和您的公司最好的广告。
另外,将代码开源,大家看到的是项目功能,而不是代码技巧。如果不是自己需要,没有人会闲得帮其他人优化代码。如果您的想法够好,那么就会收获来自社区的感谢、帮助,以及您应有的声望。
顺便提一下,如果您是擅长设计和编程的全栈工程师,并且对自己的设计能力非常有自信,那么同样推荐Dribbble。Dribbble是设计师的舞台,它的社交性让您的作品很容易传播和收获“赞”。如果是可以实际预览的页面,您可以在贴上设计稿之后,在下面留下站点的实际地址。
2、我想推荐的第二种方案是静态页(比如GitHub Pages)
GitHub Pages是GitHub在代码托管之外额外提供的一个非常方便的功能,它允许您创建一个gh-pages的分支(如果是用户或者项目的主页,就是master分支),然后向其中提交静态资源,包括HTML、CSS、JavaScript和图片,然后就可以通过username.github.io来访问。
我的个人博客就是建立在GitHub Pages上,因为我的用户名是yuguo,所以对应的域名是http://yuguo.github.io/ 。如果您访问的话,会跳转到http://yuguo.us/,因为GitHub提供免费域名绑定功能,这简直是业界良心,所以我绑定了自己的私人域名。
GitHub Pages的初衷是为您的项目提供一个简单的介绍页,它提供了一些固定的模板。在GitHub网页上直接选择这些模板,就会在您的某个项目中创建一个gh-pages分支,并且允许您在网页上使用Markdown格式直接编辑index.html的内容。所以在那个时代,所有的GitHub Pages的设计都局限于GitHub官方提供的几套默认模板。
后来,Jekyll改变了游戏规则。Jekyll是一个使用Ruby编写的博客站点编译软件,通过命令行来操作。用户只需要编写Markdown格式的内容“源文件”,就能快速编译出一个完整的静态网站。技术的发展总会带来新的应用场景,GitHub Pages与Jekyll结合在一起,发生了美妙的化学反应。现在只需要把Jekyll的日志源代码Markdown推送到GitHub Pages站点,就能生成一个编译后的静态页。
Jekyll让您可以利用简单的几行代码,就新建一个站点框架。
GitHub Pages支持Jekyll编译之后,用户只需推送源代码到GitHub,GitHub Pages就能自动编译。二者产生了奇妙的化学反应,GitHub Pages的灵活性变得无限大,越来越多的开发者使用GitHub托管博客,而作品集也是一种非常适合Jekyll生成的项目。
除了Jekyll这种博客编译器以外,还有一些专门的静态站点编译器,比如Dexy。与Jekyll不同的是,Dexy更擅长产品站点和文档的编译,比如可以直接引用某代码文件到HTML中。Dexy不被GitHub原生支持,所以您可以在本地编译出完整的静态页面之后,把生成的站点推送到GitHub Pages。
经常有人问我博客托管在哪个服务器,我会告诉他们托管在GitHub Pages,虽然速度不是特别快,但是很稳定,可用性可以保证在99.99%以上。
3、突出重点
如果作品集有一些动态生成的内容的话,可以选择自己架设服务器并绑定域名,VPS就是不错的选择。VPS成本比GitHub Pages高,因为需要付费和配置环境,但是最终跟GitHub Pages的效果是类似的。
最后我想说的是,任何作品集都需要有一个重点。如果您想重点突出自己某个技能的深度,可以针对这个技能列出大量作品、项目、专栏或者自己的书。如果想突出技能的广度,光列出您的技能集是不能说服人的,还要在自己的GitHub上提交各种使用相关技能的项目。如果自由开发者想招揽一些客户的话,漂亮的过往项目是最重要的。
作品集不一定是严谨而无趣的,曾经有一个前端开发者就将自己的作品集用一个HTML5游戏包装起来,让人印象非常深刻。
看到这里,您也许会说,有一些社交网络可以直接生成相关的作品集,比如LinkedIn、about.me等。但我的观点是,既然身为一个全栈工程师,那么花一点时间做一些特别的东西会更有趣,不是吗?
通过 about.me可以生成自己的作品集,截图来自about.me。
通过社会化媒体,树立起个人的品牌,即使不拿名片出去,也有人知道自己,这才是应该努力的方向。有人说过,“人到三十,不要去找工作,要让工作来找自己”,大概也是这个意思。
全栈工程师眼中的HTTP
HTTP,是Web工程师每天打交道最多的一个基本协议。很多工作流程、性能优化都围绕HTTP协议来进行,但是我们对HTTP的理解是否全面呢?如果前端工程师和后台工程师坐在一起玩捉鬼游戏,他们对HTTP的描述可能会截然不同,从这两个角色的视角看过去,HTTP呈现出截然不同的形态。
1、HTTP简介
超文本传输协议(HyperText Transfer Protocol,HTTP)是互联网上应用最为广泛的一种网络协议。设计HTTP的最初目的是提供一种发布和接收HTML页面的方法。
OSI七层模型:
OSI模型义了整个世界计算机相互连接的标准,总共分为7层,其中最上层(也就是第7层)就是应用层,HTTP、HTTPS、FTP、TELNET、SSH、SMTP和POP3都属于应用层。这是软件工程师最关心的一层。
OSI模型越靠近底层,就越接近硬件。在HTTP协议中,并没有规定必须使用它或它支持的层。事实上,HTTP可以在任何互联网协议或其他网络上实现。HTTP假定其下层协议提供可靠的传输,因此,任何能够提供这种保证的协议都可以被其使用,也就是其在TCP/IP协议族使用TCP作为其传输层。
备注:开放式系统互联通信参考模型(Open System Interconnection Reference Model),简称为OSI模型(OSI model)
关于HTTP版本:
HTTP已经演化出了很多版本,它们中的大部分都是向下兼容的。客户端在请求的开始告诉服务器它采用的协议版本号,而后者则在响应中采用相同或者更早的协议版本。
当前应用最广泛的HTTP版本为HTTP/1.1,它自从1999年发布以来,距写作本书时已有16年的时间。比起HTTP/1,它增加了几个重要特性,比如缓存处理(在下一章介绍)和持续连接,以及其他一些性能优化。
2015年2月,HTTP/2正式发布。新的HTTP版本有一些重大更新,除了一如既往地向下兼容HTTP/1以外,还有一些优化,比如减小网络传输延迟,并简化服务器向浏览器传输内容的过程。主流的服务器(Apache、Nginx等)和浏览器(Firefox、Chrome、Safari以及iOS和Android的浏览器等)的最新版都已经支持HTTP/2,剩下的就需要网站管理员把服务器升级到最新版了。
例子:
下面是一个HTTP客户端与服务器之间会话的例子,运行于www.google.com,端口80。
客户端首先发出请求:
<span class="hljs-keyword">GET</span> / HTTP/<span class="hljs-number">1.1</span>
<span class="hljs-symbol">Host:</span>www.google.com
上方第一行指定方法、资源路径、协议版本。当然这是一个简化后的例子,实际请求中还会有当前Google登录账户的cookie、HTTPS头、浏览器接受何种类型的压缩格式和UA代码等。备注:用户代理(User-Agent),是指一串字符,表明了当前用户使用什么样的代理在访问站点。浏览器是最常见的一种用户代理)
服务器随之应答:
<span class="hljs-string">HTTP/1.1</span> <span class="hljs-number">200</span> <span class="hljs-string">OK</span>
<span class="hljs-attr">Content-Length:</span> <span class="hljs-number">3059</span>
<span class="hljs-attr">Server:</span> <span class="hljs-string">GWS/2.0</span>
<span class="hljs-attr">Date:</span> <span class="hljs-string">Mon,</span> <span class="hljs-number">20</span> <span class="hljs-string">Apr</span> <span class="hljs-number">2015 20:30:45 </span><span class="hljs-string">GMT</span>
<span class="hljs-attr">Content-Type:</span> <span class="hljs-string">text/html</span>
<span class="hljs-attr">Cache-control:</span> <span class="hljs-string">private</span>
<span class="hljs-attr">Set-cookie:</span> <span class="hljs-string">PREF=ID=73d4aef52e57bae9:TM=1042253044:LM=1042253044:S=</span> <span class="hljs-string">SMCc_HRPCQiqy</span>
<span class="hljs-string">X9j;</span> <span class="hljs-string">expires=Sun,</span> <span class="hljs-number">17</span><span class="hljs-string">-Jan-2038</span> <span class="hljs-number">19</span><span class="hljs-string">:14:07</span> <span class="hljs-string">GMT;</span> <span class="hljs-string">path=/;</span> <span class="hljs-string">domain=.google.com</span>
<span class="hljs-attr">Connection:</span> <span class="hljs-string">keep-alive</span>
上方代码中,在这一串HTTPS头之后,会紧跟着一个空行,然后是HTML格式的文本组成的Google主页。
介绍完关于HTTP的基本知识,我们来分别看看前端工程师和后台工程师分别是怎样看待这个最熟悉的小伙伴的。
2、前端视角
前端工程师的职责之一是,让网站又快又好地展现在用户的浏览器中。
从这个角度来说,对HTTP的理解是这样的:打开HttpWatch,然后随意访问一个网站,HttpWatch会按照浏览器请求的次序,列出打开这个网站的时候发生的请求细节。包括如下内容:
- 发出的请求列表。
- 每个请求的开始时间。
- 每个请求从开始到结束花费的时间。
- 每个请求的类型(比如是文本、CSS、JS,还是图片或者字体等)。
- 每个请求的状态码(比如是200、还是from cache、304、404等)。
- 每个请求产生的流量消耗。
- 每个请求gzip压缩前的体积,以及在本地gzip解压后的体积。
通过查看站点的HTTP请求信息,可以得到很多优化信息。每一个前端工程师都知道的基本优化方法是:尽量减少同一域下的HTTP请求数,以及尽量减少每一个资源的体积。(通过Chrome开发者工具中的PageSpeed工具,可以快速获得关于站点性能优化的建议)
备注1:HttpWatch是一个浏览器插件,它可以用来检测页面中所有HTTP请求。类似的工具还有Fiddler,或者各种现代浏览器的开发者工具中的“网络”标签页
备注2:gzip是一种开源的数据压缩算法,其中g代表免费的意思(gratis)。HTTP/1.1协议允许客户端选择要求从服务器下载压缩内容,gzip是绝大多数客户端和服务器都支持的压缩算法,它在压缩文本文件(比如HTML、CSS、JavaScript)时压缩效果很好。
尽量减少同一域下的HTTP请求数:
浏览器常常限定了对同一域名发起的并发连接数的上限。IE6/7和Firefox2的设计规则是,同时只能对一个域名发起两个并发连接。新版本的各种浏览器普遍把这一上限设定为4至8个。如果浏览器需要对某个域进行更多的连接,则需要在用完了当前连接之后,重复使用或者重新建立TCP连接。
QQ空间的CSS贴图由程序自动生成,保证最佳的图片质量、最合理的图片摆放和最小的体积。
由于浏览器针对资源的域名限制并发连接数,而不是针对浏览器地址栏中的页面域名,所以很多静态资源可以放在其他域名下(不同的子域名也被认为是不同的域名)。如果您只有一台服务器,可以把这些不同的域名同时指向一个IP,也就提高了对这台服务器的并发连接数限制(不过要小心服务器压力过大)。
把静态资源放在非主域名下,这种做法除了可以增加浏览器并发,还有一个好处是,减少HTTP请求中携带的不必要的cookie数据。cookie是某些网站为了辨别用户身份而储存在用户浏览器中的数据。cookie的作用域是整个域名,也就是说如果某个cookie存放在google.com域名下,那么对于google.com域名下的所有HTTP请求头都会带上cookie数据。如果Google把所有的资源都放在google.com下,那么所有资源的请求都会带上cookie数据。对于静态资源来说,这是毫无必要的,因为这对带宽和链接速度都造成了影响。所以我们一般把静态资源放在单独的域名下。
除此之外,前端工程师经常做的优化是合并同一域名下的资源,比如把多个CSS合并为一个CSS,或者将图片组合为CSS贴图(这种做法被称为sprite image)。
还有一些优化建议是省掉不必要的HTTP请求,比如内嵌小型CSS、内嵌小型JavaScript、设置缓存,以及减少重定向。这些做法虽然各不相同,但是如果了解HTTP请求的过程,就知道这些优化方法的最终目的都是最大化利用有限的请求数。
尽量减少每一个资源的体积:
我们不光要限制请求数,还要尽量减少每一个资源的体积。因为资源的体积越大,在传输中消耗的流量就越多,等待时间也越久。
在面试应聘者的时候,我会问的一个基础题目是“常用的图片格式有哪些,它们的使用场景是什么”。如果能选择合适的图片格式,就能够用更小的体积,达到更好的显示效果。对图片格式的敏感,能反映出工程师对带宽和速度的不懈追求。
此外,对于比较大的文本资源,必须开启gzip压缩。因为gzip对于含有重复“单词”的文本文件,压缩率非常高,能有效提高传输过程。
对于一个CSS资源的请求耗时,我想说明两个细节:
- 这个CSS资源请求的体积是36.4KB(这是gzip压缩过的体积),解压缩之后,CSS内容实际上是263KB,可以算出压缩后体积是原来的13.8%。
- 整个连接的建立花费了30%的时间,发出请求到等待收到第一个字节回复花费了20%的时间,下载CSS资源的内容花费了50%的时间。
如果没有设置gzip,下载这个CSS文件会需要好几倍的时间。
3、后台视角
前端工程师对HTTP的关注点在于尽量减少同一域下的HTTP请求数,以及尽量减少每一个资源的体积。与之不同,后台工程师对于HTTP的关注在于让服务器尽快响应请求,以及减少请求对服务器的开销。
后台工程师知道,浏览器限定对某个域的并发连接数,很大程度上是浏览器对服务器的一种保护行为。浏览器作为一种善意的客户端,为了保护服务器不被大量的并发请求弄得崩溃,才限定了对同一个域的最大并发连接数。而一些“恶意”的客户端,比如一些下载软件,它作为一个HTTP协议客户端,不考虑到服务器的压力,而发起大量的并发请求(虽然用户感觉到下载速度很快),但是由于它违反了规则,所以经常被服务器端“防范”和屏蔽。
那么为什么服务器对并发请求数这么敏感?
虽然服务器的多个进程看上去是在同时运行,但是对于单核CPU的架构来说,实际上是计算机系统同一段时间内,以进程的形式,将多个程序加载到存储器中,并借由时间共享,以在一个处理器上表现出同时运行的感觉。由于在操作系统中,生成进程、销毁进程、进程间切换都很消耗CPU和内存,因此当负载高时,性能会明显降低。
提高服务器的请求处理能力:
在早期系统中(如Linux 2.4以前),进程是基本运作单位。在支持线程的系统(Linux2.6)中,线程才是基本的运作单位,而进程只是线程的容器。由于线程开销明显小于进程,而且部分资源还可以共享,因此效率较高。
Apache是市场份额最大的服务器,超过50%的网站运行在Apache上。Apache 通过模块化的设计来适应各种环境,其中一个模块叫做多处理模块(MPM),专门用来处理多请求的情况。Apache安装在不同系统上的时候会调用不同的默认MPM,我们不用关心具体的细节,只需要了解Unix上默认的MPM是prefork。为了优化,我们可以改成worker模式。
prefork和worker模式的最大区别就是,prefork的一个进程维持一个连接,而worker的一个线程维持一个连接。所以prefork更稳定但内存消耗也更大,worker没有那么稳定,因为很多连接的线程共享一个进程,当一个线程崩溃的时候,整个进程和所有线程一起死掉。但是worker的内存使用要比prefork低得多,所以很适合用在高HTTP请求的服务器上。
近年来Nginx越来越受到市场的青睐。在高连接并发的情况下,Nginx是Apache服务器不错的替代品或者补充:一方面是Nginx更加轻量级,占用更少的资源和内存;另一方面是Nginx 处理请求是异步非阻塞的,而Apache 则是阻塞型的,在高并发下Nginx 能保持低资源、低消耗和高性能。
由于Apache和Nginx各有所长,所以经常的搭配是Nginx处理前端并发,Apache处理后台请求。
值得一提的是,新秀Node.js也是采用基于事件的异步非阻塞方式处理请求,所以在处理高并发请求上有天然的优势。
DDoS攻击:
DDoS是Distributed Denial of Service的缩写,DDoS攻击翻译成中文就是“分布式拒绝服务”攻击。
简单来说,就是黑客入侵并控制了大量用户的计算机(俗称“肉鸡”),然后在这些计算机上安装了DDoS攻击软件。我们知道浏览器作为一种“善意”的客户端,限制了HTTP并发连接数。但是DDoS就没有这样的道德准则,每一个DDoS攻击客户端都可以自由设置TCP/IP并发连接数,并且连接上服务器之后,它不会马上断开连接,而是保持这个连接一段时间,直到同时连接的数量大于最大连接数,才断开之前的连接。
就这样,攻击者通过海量的请求,让目标服务器瘫痪,无法响应正常的用户请求,以此达到攻击的效果。
对于这样的攻击,几乎没有什么特别好的防护方法。除了增加带宽和提高服务器能同时接纳的客户数,另一种方法就是让首页静态化。DDoS攻击者喜欢攻击的页面一般是会对数据库进行写操作的页面,这样的页面无法静态化,服务器更容易宕机。DDoS攻击者一般不会攻击静态化的页面或者图片,因为静态资源对服务器压力小,而且能够部署在CDN上。
这里介绍的只是最简单的TCP/IP攻击,而DDoS是一个概称,具体来说,有各种攻击方式,比如CC攻击、SYN攻击、NTP攻击、TCP攻击和DNS攻击等。
3、BigPipe:
前端跟后端在HTTP上也能有交集,BigPipe就是一个例子。
现有的HTTP数据请求流程是:客户端建立连接,服务器同意连接,客户端发起请求,服务器返回数据,客户端接受并处理数据。这个处理流程有两个问题。
上图中是现有的阻塞模型,黄色代表服务器生成页面,白色代表网络传输,紫色代表浏览器渲染页面。
第一,HTTP协议的底层是TCP/IP,而TCP/IP规定3次握手才建立一次连接。每一个新增的请求都要重新建立TCP/IP连接,从而消耗服务器的资源,并且浪费连接时间。对于几种不同的服务器程序(Apache、Nginx和Node.js等),所消耗的内存和CPU资源也不太一样,但是新的连接无法避免,没有从本质上解决问题。
第二个问题是,在现有的阻塞模型中,服务器计算生成页面需要时间。等服务器完全生成好整个页面,才开始网络传输,网络传输也需要时间。整个页面都完全传输到浏览器中之后,在浏览器中最后渲染还是需要时间。三者是阻塞式的,每一个环节都在等上一个环节100%完成才开始。页面作为一个整体,需要完整地经历3个阶段才能出现在浏览器中,效率很低。
BigPipe是Facebook公司科学家Changhao Jiang发明的一种非阻塞式模型,这种模型能完美解决上面的两个问题。
通俗来解释,BigPipe首先把HTML页面分为很多部分,然后在服务器和浏览器之间建立一条管道(BigPipe就是“大管道”的意思),HTML的不同部分可以源源不断地从服务器传输到浏览器。BigPipe首先输送的内容是框架性HTML结构,这个框架结构可能会定义每个Pagelet模块的位置和宽高,但是这些pagelet都是空的,就像只有钢筋混泥土骨架的毛坯房。
BigPipe页面的渲染流程:
服务器传输完框架性HTML结构之后,对浏览器说:“我这个请求还没结束,我们保持这个连接不要断开,不过您可以先用我给您的这部分来渲染。”
所以浏览器就开始渲染这个“不完整的HTML”,毛坯房页面很快出现在用户眼前,具体的页面模块都显示“正在加载”。
接下来管道里源源不断地传输过来很多模块,这时候最开始加载在服务器中的JS代码开始工作,它会负责把每一个模块依次渲染到页面上。
在用户的感知上,页面非常快地出现在眼前,但是所有的模块都显示正在加载中,然后主要的区域(比如重要的用户动态)优先出现,接下来是logo、边栏和各种挂件等。
为什么BigPipe能够让服务器对浏览器说“我这个请求还没结束,我们保持这个连接不要断开”呢?答案是HTTP1.1的分块传输编码。
HTTP 1.1引入分块传输编码,允许服务器为动态生成的内容维持HTTP持久链接。如果一个HTTP消息(请求消息或应答消息)的Transfer-Encoding消息头的值为chunked,那么消息体由数量不确定的块组成——也就是说想发送多少块就发送多少块——并以最后一个大小为0的块为结束。
实现这个架构需要深刻理解HTTP 1.1的规则,而且要有前端的知识。在我看来,这就是一个极佳的全栈工程师改变世界的例子。
截止写书时,Chrome、Safari和Opera已经支持HTTP/2并默认开启,它允许服务器向浏览器“推送”内容。也就是说,返回的条目数可以比请求的条目数多,这样服务器可以在一开始就推送所有它认为浏览器“应该需要”的资源,而不需要浏览器接受并解析完HTML页面才开始请求下载CSS、JavaScript等。而且,后面的请求可以复用之前已经建立的底层连接。
延伸阅读
1.《图解HTTP》(日)上野宣,人民邮电出版社
2.《高性能网站建设进阶指南》(美)Steve Souders,电子工业出版社
高性能网站的关键:缓存
Phil Karlton说过:计算机科学中最无奈的两件事是缓存失效和命名。这是可能是因为,复杂性理论方面的难题,可能最终还是有解的。而缓存失效是分布式系统中最常见,也几乎没有最优解决方案的难题。
缓存对于站点性能起到举足轻重的作用,很多时候,优化算法和压缩图片带来的优化效果可能远远不如优化缓存。
计算机系统中的缓存有这样几种功效:(以图书为例)
- 存储频繁访问的数据(这里的数据是图书)。
- 内存缓存减少磁盘I/O(不用到6楼去找书)。
- 保存耗时的操作,以便下次使用(找书和整理书是耗时的操作)。
下面我来谈谈在一个Web站点中,它的数据流从服务器端到浏览器端,哪些地方可以使用缓存来优化。
1、服务器缓存
对于一些计算量大的Web服务、服务器内存或CPU等性能不好,或者像一些独立开发者跟其他人共享虚拟服务器(因此只能得到部分内存和CPU)的时候,服务器的计算时间可能占整个页面响应时间的很大一部分。这种情况下,优化服务器端的缓存就尤为重要了。
基本的数据库查询缓存:
我们从服务器到客户端,依次来讲解缓存的作用,首先从数据库开始。
对于大型网站来说,数据库里的数据量往往是非常大的,而对于数据的查询又是比较耗时的操作,所以我们可以开启MySQL查询缓存来提高速度,并且减少系统压力。MySQL默认不开启查询缓存,但我们可以通过修改MySQL安装目录中的my.ini来设置查询缓存。设置的时候可以根据实际情况配置缓冲区大小、单个查询的缓冲区大小等。
我们从服务器到客户端,依次来讲解缓存的作用,首先从数据库开始。
对于大型网站来说,数据库里的数据量往往是非常大的,而对于数据的查询又是比较耗时的操作,所以我们可以开启MySQL查询缓存来提高速度,并且减少系统压力。MySQL默认不开启查询缓存,但我们可以通过修改MySQL安装目录中的my.ini来设置查询缓存。设置的时候可以根据实际情况配置缓冲区大小、单个查询的缓冲区大小等。
如果您希望优化MySQL服务器的查询性能和速度,可以在MySQL配置中增加这两项:
<span class="hljs-attr">query_cache_size</span>=SIZE
<span class="hljs-attr">query_cache_type</span>=OPTION
上方第一行中,SIZE是指为查询缓存开辟多大的空间。默认是0,也就是禁用查询缓存。
设置查询缓存的类型,可选的值有以下这三种:
- 0:设置查询缓存的类型,可选的值有以下这三种。
- 1:所有的缓存结果都缓存起来,除非查询命令以SELECT S_NO_CACHE开始。
- 2:只缓存查询命令以SELECT SQL_CACHE开始的查询结果。
具体的设置方法不是我们讨论的重点,重点是要了解适合设置查询缓存的场景。因为每一次select查询的结果都会被缓存起来,如果数据库数据没有发生变化(没有运行INSERT/UPDATE/DELETE/MERGE等操作的话,数据库就不会变化),下一次查询就会直接从缓存里返回数据。但是如果数据库发生了变化,那么所有与该表有关的查询缓存全部失效。
所以,对于查询操作远远多于修改操作的数据库,开启数据库查询缓存是很有益的;但是对于修改操作很多的数据库,由于缓存经常会失效,就起不到加速的效果。不仅如此,由于数据库要花费时间写缓存,所以实际上速度更慢了。
这个问题就是“缓存命中率不高”,所以配置缓存之后第一件事就是查询命中率,如果命中率低,不如不做缓存。
这里需要注意的是,两次SQL文本必须完全相同。如果前后两次查询使用了不同的查询条件,就会重新查询。如第一次查询时没有输入where条件语句,后来发现数据量过多,于是利用where条件过滤查询的结果,此时即使最后的查询结果是相同的,系统仍然是从数据文件中获取数据,而不是从缓存结果中。再如,select后面所使用的字段名称也必须是相同的。如果查询语句中有一个字段名称不同,或者前后两次查询所使用的字段数量不同,都会被系统认为是不同的SQL语句,需要重新解析并查询。
扩展数据库缓存:memcached:
MySQL的自带缓存有一个问题,它的缓存池大小是在MySQL所在服务器上开辟,能使用的内存空间是有限的。在比较大型的网站中,缓存就不够用了,这时候需要使用服务器集群来实现数据库缓存。
memcached应运而生,它是一个高性能分布式内存对象缓存系统,用于减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。memcached可以与数据库查询缓存配合使用,查询流程如下图所示。
您也许发现了数据库查询缓存的一个设计原则:其缓存失效设计是很粗糙的。只要某个表发生了更新操作,所有对这个表的查询都会失效。这是为了保证数据的时效性而降低了数据的命中率。
memcached一般查询流程:
memcached的缓存失效与此不同,它采用的是按时间来过期的设计。memcached相当于应用程序和数据库之间的中间层,通过网络API设置和调用。memcached储存的是名值对,而且设置了一个过期时间,只要过期时间没有到,应用程序就会从memcached中获取数据。这时候即使发生了数据库更新操作,缓存的查询结果也仍然是之前保存的旧数据,直到设置的时间过期。这样提高了缓存的性能,带来的影响就是,数据可能是“不新鲜”的。
memcached支持集群,而且有诸多优点,所以可以有效利用多台机器的内存,提高命中率。
如果您使用WordPress,那么开启memcached是很简单的。在服务器安装好memcached后,再去WordPress的插件列表里,搜索cache或memcached之类的关键词,可以找到很多相关的插件。根据说明安装好这些插件后,一般就可以无缝衔接缓存软件和WordPress了。
但是memcached也不是总是那么有效,因为如果只有一台服务器,就用不到它的服务器集群的优势,反而让系统更慢。
再加一层文件缓存:
除了可以将数据库查询结果缓存在内存中,还可以将被频繁造访的数据缓存在文件中。文件I/O比起内存有以下几个好处:
- 硬盘容量比内存大,所以可以缓存更多数据。
- 数据更安全,断电之后数据还在。
- 易于扩展,硬盘不够用的时候还可以添加硬盘。
但是文件缓存没有内存缓存快,只能作为内存缓存的补充,在获取数据时,先从最快的地方读取,如果没有就继续往后找。查找优先级为:内存缓存→文件缓存→数据库。
PHP框架CodeIgniter的数据库缓存类,允许您把数据库查询结果保存在文本文件中,以减少数据库访问。
如果激活了CodeIgniter的缓存特性,那么在某页面首次被加载时,数据库查询的结果对象将会被序列化,并保存在服务器的文本文件中。而此页面再次被加载时,缓存文件将会替代数据库查询。如此,在被缓存的页面中,您的数据库使用率会降至零。
只有读类型查询会被缓存,因为只有这种查询会产生结果集。 而写类型查询,因为不会产生结果集,故缓存系统不对之进行缓存。
缓存文件不会过期,除非您删掉它,否则任何被缓存了的查询会一直存在。缓存系统允许您按页面清除,或把所有缓存都清除掉。一般来说,您可以在某些事件(比如向数据库添加了数据)发生时用特定的函数来清除缓存。
静态化:
有两种静态化的方法,其中一种是类似WordPress的静态化插件,安装很简单,每次有新文章就自动生成静态页面。这种方法还是将数据保存在数据库中,只是会读取数据库之后生成一些静态页。
这一种方法的原理跟文件缓存很相似。静态化页面之后,服务器每次接收到对这个页面的请求,都会直接给出这个页面的静态文件,所以就省略了后台运算和数据库查询。优点是能大大加快访问速度,同时减轻服务器处理大量请求的运算压力。在前面我们也说过,因为静态化的页面对服务器的压力小,能有效承担巨大的访问量,所以还能抵御DDoS攻击。
另一种方法就是直接抛弃数据库。比如有一些博客作者会用Jekyll系统来写博客,将整个博客站点静态化。完全抛弃数据库的好处是,可以将生成的静态网页直接托管在静态资源站点,比如GitHub Pages或者Amazon S3,而不用操心数据库服务器的问题,不光整个系统稳定很多,费用上也更加低廉(GitHub更是完全免费的,而且提交Markdown源代码后可以让它在服务器端生成站点)。
对于完全静态化的站点,可以采用第三方插件来支持用户生成内容。比如Disqus就是一个第三方的评论插件,通过JavaScript代码插入到静态页中。用户的评论数据都储存在Disqus的服务器上,对我们是透明的,很方便。
值得一提的是,我们从URL是无法判断后台是否真正静态化的。对于一个URL为/blog/index.html的页面,也有可能是PHP页面通过UrlRewrite来改写的。通过Apache或者Nginx可以将一个对静态资源的请求(index.html)转给一个动态应用程序(比如PHP)来处理。
2、浏览器缓存
前面说的缓存都是发生在服务器端的,适用的情况是那些服务器性能为主要瓶颈的场合,通过缓存来将一个长的计算时间跳过,起到提高性能的作用。而当浏览器访问一个站点的时候,网络连接是主要瓶颈,我们可以通过设置浏览器缓存来跳过HTTP请求。
如果在浏览器设置缓存,通常有两个主要作用。
- 对用户来说,减少请求可以更快地加载页面,节省流量。如果用户是在手机上用3G或4G访问页面,这一点就很关键。
- 对网站来说,减少带宽压力和费用。假设有1亿的访问量,如果能把大小为10KB的CSS缓存起来,可以节省不小的开支。
对于浏览器来说,如何缓存一个资源是服务器端制定的策略,自己只是根据服务器的“指令”来执行而已。服务器的“指令”就是前面介绍过的HTTPS头。
服务器通过对每个资源的HTTP响应头设置属性和值,来发出自己的缓存指令。主要会有两种缓存指令,我们以一个图片image.png为例。
第一种:Expires
对于一个普通的请求,服务器可能会说:“您拿着这个资源吧,直到2020年您都别再向我要了。”
<span class="hljs-attr">Expires:</span> <span class="hljs-string">Thu,</span> <span class="hljs-number">15</span> <span class="hljs-string">Apr</span> <span class="hljs-number">2020 20:00:00 </span><span class="hljs-string">GMT</span>
那么浏览器如果再次命中对这个资源的需求,就不会再去发起HTTP请求,而是直接从缓存(在硬盘中)读取。
<span class="hljs-number">200</span>(<span class="hljs-keyword">from</span> cache)
这种缓存是最快的,因为没有任何HTTP请求发生。当用户需要这个资源,浏览器就直接从缓存中读取,不再需要询问服务器端的意见(服务器端甚至不知道您在浏览image.png)。所以HttpWatch是推荐对所有的静态资源都设置Expires。
####第二种:Last-Modified
对于一些有可能过期的请求,服务器可能会比较慎重地说:“您拿着这个资源吧,这个资源上次修改时间是2014年3月1日,如果用户要用,您就问问我改变了没,或者直到2014年12月31日文件自动过期。”
<span class="hljs-attr">Last-Modified:</span> <span class="hljs-string">Tue,</span> <span class="hljs-number">01</span> <span class="hljs-string">Mar</span> <span class="hljs-number">2015 03:42:36 </span><span class="hljs-string">GMT</span>
那么浏览器如果在2015年3月10日访问了image.png,就会将这个图缓存在硬盘中。过了几天,浏览器又命中了对这个资源的需求,就会发起一个HTTP请求。
在HTTPS头中,浏览器问:“我这里有个image.png,它的最后修改时间是2015年3月1日,现在用户又要了,您那个文件有过更新没?”
<span class="hljs-attr">If-Modified-Since:</span> <span class="hljs-string">Tue,</span> <span class="hljs-number">01</span> <span class="hljs-string">Mar</span> <span class="hljs-number">2015 03:42:36 </span><span class="hljs-string">GMT</span>
服务器如果回答:“没更新,您直接用吧。”这个回答中就不需要带上请求的文件体了,只用一个HTTPS头表示文件未更新即可。304就是这句话的代号,代表资源未修改的意思。
304
另一种情况是,image.png后来更新过了,服务器就会说:“更新过了,我现在给您一个新的图片。”然后就正常返回请求文件(200),并且把整个图片作为HTTP正文发送给浏览器。
通过这种缓存方式,无论资源是否发生了更新,仍然至少会发生一来一去HTTPS头的传输和接收,所以速度比不上Expires。
从服务器端的角度来看,有时候我们并不希望对静态资源的请求中大部分都返回304。因为这可能说明我们的很多用户都在频繁访问站点,而且我们的资源很少更新,就好像它们一直问“资源修改了吗?”,我们一直回答“没有修改”。这里可以使用Expires来设置过期时间,这样它们就不会“烦我们”了。对于服务器管理员来说,保持304为一个合理的比例即可。我们可以通过查看服务器的log,查看304响应与200响应的比例,来做出一个合理的缓存策略。
Restful Web API:
表征性状态传输(Representational State Transfer,REST)是Roy Fielding博士在2000年发表的博士论文中提出来的一种软件架构风格。
目前,在3种主流的Web服务实现方案中,因为REST模式最简洁,也能合理地利用HTTP操作的语义,所以越来越多的Web服务开始采用REST风格设计和实现。比如,Amazon.com提供接近REST风格的Web服务进行图书查找。
Restful 的目的是定义如何正确地使用Web标准,优雅地使用HTTP本身的特性。原则上是对资源、集合、服务(URL)、get、post、put、delete(操作)的合理使用。
举例来说,如果请求一个资源,但是服务器上没有这个资源,这时候就应该对HTTPS头设置404,而不是设置200。
HTTP 1.1加入的Cache-Control:
其实Expires跟Last-Modified已经能满足我们绝大多数需求了,但是HTTP1.1又新增了一个属性Cache-Control,它的功能跟Expires类似,不过有更多的选项。
Expires的值是一个日期,表示某日期之前都不再询问。
Cache-Control的值是:max-age=7776000,max-age的单位是秒,从浏览器接收到文件之后开始计时。
如果您不知道怎么判断,就只用Expires,或者(为了兼容某些老客户端)同时设置Expires和Last-Modified。
如果topMenu.js设置了Expires直到2020年都不过期,那么怎么让客户端知道我们修改了topMenu.js呢?
答案是修改Query String。按照规范,Query String是URL中的一个部分,比如http://server/program/path/?query_string问号后面的字符串就是Query String。
按照HTTP规范,如果修改了请求资源的Query String,就应该被视为一个新的文件。
这个Query String可以被服务器端CGI或者应用程序理解,而且可以设置多个名值对(比如?foo=1&bar=2)。与缓存相关的一点是,如果Query String发生了改变,则被视为URL发生了改变。这时候,浏览器会认为这是一个新的资源。而对于服务器而言,如果有CGI或者应用程序捕捉或处理Query String,就会去处理,如果没有,就简单地忽略Query String,直接返回资源。
下面是推荐的浏览器缓存设置最佳实践:
- 对于动态生成的HTML页面使用HTTPS头:Cache-Control: no-cache。
- 对于静态HTML页面使用HTTPS头:Last-Modified。
- 其他所有的文件类型都设置Expires头,并且在文件内容有所修改的时候修改Query String。
浏览器缓存的现实世界:
服务器端可以设置缓存规则,告诉浏览器应该如何遵循和实现,但在服务器不能掌控的地方也许会出现一些意外:
- 缓存会被挤出。
- 文件有可能在运营商服务器上被劫持。
所谓缓存被挤出,是因为浏览器的缓存空间是有限的,所有网站希望缓存的文件都塞在用户硬盘,形成一个先进先出的队列。所以即使设置了20年的缓存时间,也基本上不能保证有那么久的生命期,而会在某一个时间点被其他网站设置的缓存挤出硬盘。而且用户也有可能主动清除浏览器缓存,某些系统清理软件也可能清理浏览器缓存。这一点无可厚非,我们从技术层面上也无法解决,大不了让用户重新加载一下资源就好了。
第二个问题是,用户的宽带运营商为了提高速度,可能会在自己某节点服务器上缓存您的文件(比如style.css?v1),好处是当用户请求这个文件的时候,运营商无需来您的服务器上请求文件,而自己直接就给出了。
问题来了,如果您的Query String更新了(style.css?v2),按照HTTP规范,这理应被视为一个新的文件,但是运营商仍然可能会拿自己节点的缓存,而不是遵循规范。有点可恶对不对?这就是我们在用户量极大的情况下侦测到的情况,虽不太常见,但是有可能发生。所以,为了保证更新的文件下发到所有的用户,我们会使用更加强硬的方法:修改文件名,而不是仅仅修改Query String。
这种流程比较复杂,需要同时修改文件名和引用它的文档里的文件名。在QQ空间,我们使用自动化的方式来生成新文件名并修改HTML中的引用。
修改资源的文件名,比修改后缀更可靠:
结论:
结合上面的分析,这是QQ空间静态资源在浏览器端使用的缓存策略:
- 对于动态生成的HTML页面使用HTTPS头:Cache-Control: no-cache。
- 对于静态HTML页面使用HTTPS头:Last-Modified。
- 其他所有的文件类型都设置Cache-Control头,并且在文件内容有所修改的时候修改文件名。
以上就是在Web栈流程中比较常见的缓存方面的问题。缓存能否获得性能增益,取决于很多因素。一些因素是关于您的服务器压力、数据库使用情况和提供的服务类型;另一些因素是关于您的用户如何访问您的网站,以及他们的网络环境。我们作为工程师,只能不断优化和调整策略,往更优的方向去优化。
延伸阅读:
- 《网站性能监测与优化》(美)Alistair Croll / Sean Power,人民邮电出版社
大前端
总体来讲,在计算机程序和系统中,“前端”(front-end)作用于采集和显示信息,“后端”(back-end)进行处理。Web应用程序和桌面应用程序的界面样式、视觉呈现、用户交互属于前端。
前端工程师:
我们最常见的Web栈中靠近用户输入的那一部分,也就是靠近浏览器的部分,属于前端的范畴。具体来说,浏览器中发生的一切和服务器中涉及输入输出的这一部分,都属于前端工程师的工作职责。
前端工程师主要使用的语言是HTML、CSS和JavaScript,有时候会写一些服务器的页面模板语言(比如PHP)。
从2010年直到今天,能够明显感受到的一点是,前端发展到今天,已经发生了很大的变化。
在2010年,前端开发岗位必须掌握的一项能力是对IE6和IE7的兼容性。工程师需要手动把图片拼接成CSS贴图,CSS也不经压缩就发布出去。渐渐地,IE用户越来越少,所以,我们现在已经不把IE7以下的浏览器兼容性作为招聘要求。不过由于移动设备的爆炸性增长,现在前端开发岗位开始要求有移动端页面开发的经验,或者熟悉响应式页面开发。Grunt等发布流程的成熟,也让前端工程师免除了很多枯燥的工作。
总之,变化一直都在发生,这是一个需要终身学习的行业。不夸张地说,如果我一个月没有关注行业动态,就会发现自己已经错过了很多新技术。
1、知识体系
前端工程师需要涉及的知识面比较广,我大概对我的个人偏好做一下梳理。
在招聘初级工程师的时候,我一般会考察应聘者对以下知识的掌握程度:
- 对浏览器兼容性的了解
- 对HTML/CSS/JavaScript语法和原理的理解
- 对编辑器和插件的熟悉程度
- 对调试工具的了解程度
- 对版本管理软件的熟悉和应用经验
- 对前端库/框架的使用
- 标准/规范
招聘中级工程师时,我一般考察应聘者对以下知识的掌握程度:
- 对代码质量、代码规范的理解。
- 对JavaScript单元测试的熟悉。
- 对性能优化的应用和理解。
- 对SEO的应用和理解。
- 代码部署。
- 移动Web。
对高级工程师,除了上面的考察点以外,还会问这些方面的经验:
- 代码架构。
- 安全。
- 对自动化测试的理解
越接近高级工程师,越考察对某个点的本质理解,以及在项目和团队中的引导作用,而不是对某工具的使用经验。对于上面的这些技术方向,我不会在本章中一一介绍,一个原因是篇幅所限,另一个原因是有一些方向并不只是前端工程师会遇到,而是跟后台工程师的知识体系相通。比如代码质量、规范、单元测试、性能、版本管理……对于这些话题,会在单独的章节中详细说明。
易于上手,难于精通:
不同于某些“难于上手、难于精通”的职业,前端这一岗位就像暴雪公司的游戏设计一样:“易于上手、难于精通”。
举例而言,HTML的全称是超文本标记语言,超文本的意思是说,比起我们在记事本中写的普通文本多了一些语义化的信息,比如链接、加粗和标题等。标记语言更加简单,就是用一些标签把普通文本标记起来。标记语言没有逻辑,只是描述性的标签,所以算不上是程序语言。程序语言有的循环判断等逻辑,HTML都没有。这就是它易于上手的地方。
这是一段最简单的HTML代码,但它也是一个完整的页面:
<span class="hljs-meta"><!DOCTYPE <span class="hljs-keyword">html</span>></span>
<span class="hljs-tag"><<span class="hljs-name">html</span>></span>
<span class="hljs-tag"><<span class="hljs-name">head</span>></span>
<span class="hljs-tag"><<span class="hljs-name">title</span>></span>Page Title<span class="hljs-tag"></<span class="hljs-name">title</span>></span>
<span class="hljs-tag"></<span class="hljs-name">head</span>></span>
<span class="hljs-tag"><<span class="hljs-name">body</span>></span>
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>Hello World!<span class="hljs-tag"></<span class="hljs-name">p</span>></span>
<span class="hljs-tag"></<span class="hljs-name">body</span>></span>
<span class="hljs-tag"></<span class="hljs-name">html</span>></span>
但是HTML又很难。前端工程师对照设计稿还原出页面之后,还要考虑机器是如何理解这个页面的。对于用户而言,视觉上加大加粗就是一个标题;但是对于机器,比如搜索引擎或者盲人使用的读屏软件,是否能理解它是一句标题?这需要我们使用语义化的标签。
有的时候,为了完美地实现设计稿还原,一个视觉上看上去像一个下拉选择器的输入框,我们会使用a或者span标签加上一些隐藏的列表模块来实现。当用户点击标签的时候,就用JavaScript让隐藏的列表模块滑出来。
使用这种方法,我们可以快速地创建出在各浏览器中表现一致的按钮,而且可以轻松地自定义样式,免受各种bug困扰,但同时这种“黑”科技也带来了可访问性问题。具体来讲,如何让盲人知道这是一个下拉选择器?这时候可以使用role属性来增强这个文档对象模型(DDM)的语义。这需要我们了解WAI-ARIA的知识。
HTML虽然是比较严格和简单的语言,但有时在写代码和阅读代码的时候效率比较低。John Gruber为了改变这种现状,在2004年发明了一种纯文本格式语法Markdown13。这种语法的目标是“提供一种读起来简单,写起来也简单的语法,并且如果您乐意的话,也可以随时转化成合法的HTML”。
很多书就是使用Markdown语言来编写的。它比Word更好用的地方是,写作者无需关注字号和样式,只需要设置“一级标题”“二级标题”“三级标题”“加粗”“引用”等语义即可。具体的样式可以在美编阶段统一调整。更美好的是,它不会产生任何无意义的标签,而Word经常产生无意义的标签。
Markdown比HTML更易读易写,但是浏览器是不会渲染的,那么就必须进行Markdown到HTML的转化。开发者可以选择两种转化方式。一种是在开发环境把Markdown转化成HTML,再发布到服务器上,或者直接由服务器自动转化成HTML文件(Jekyll支持这两种转化方式),总之浏览器得到的已经是一个正常的HTML页面了。
第二种方式是把包含Markdown代码的HTML页面发布到服务器上,然后当浏览器下载HTML页面之后,页面中的JavaScript代码开始把Markdown解析成对应的HTML代码。一般推荐第一种方式,因为不依赖浏览器端的JavaScript运行,可用性更好,也对SEO更有帮助。
有些在线工具可以实时把Markdown转化成HTML,比如markdownlivepreview.com。
也有人想出另一种方法,发明了zen-coding来加快前端工程师的编码速度。zen-coding现在改名为emmet,在各大编辑器中都有插件。
前端技术的“易于上手”导致它在某些技术人员那里不受待见。他们认为HTML与CSS根本都不是程序语言,甚至认为JavaScript是一种功能不全的玩具型语言。所以直到我几年前毕业的时候,大学都没有前端相关的课程和专业。而市场对前端工程师的需求又很大,学校的输出跟市场的要求没有对接上,所以往往出现学生找不到工作,公司又招不到人的现状。
我并不是建议直接开设“前端开发”专业,因为前端开发也是软件开发的一个分支,与服务器开发和其他软件开发共享一些基础课程,是所有工程师都需要学习的,比如项目管理、数据库、软件开发流程和C++等。我的建议是,在大三或者大四的方向课程设计上,或者选修课设计上增加与时俱进的前端开发课程,使用业界最新的编程语言去教学,这样对毕业生、对用人单位都是好事。
框架vs库:
框架(framework)和库(library)的区别是什么?其实这两个词在不同的语境下,有时候是可以相互替代的。但是严格来说,框架应该是比库更广泛的概念。
一个库是一系列对象、方法等代码,您的应用程序可以把这个库“链接”进来。这个库起到了重用代码的作用,为您省下了重写这部分代码的工作量。
而一个框架是一个软件系统中可重用的一部分。它可能包含子程序、库、胶水语言、图片等一些“资源”,这些资源一起组成了软件项目。框架不像库,可能包含多种语言,某些功能可能通过API的方式让主程序调用。
所以框架是一个更加灵活和宽松的名词,在具体的情景中,它可能指一个库、多个库、脚本代码,或者多个可单独运行的子程序的集合。
以前端来举例,jQuery就是一个库。jQuery是纯粹的JavaScript代码,它的目标是使用更少的代码处理DOM相关操作。当我们使用jQuery时,相当于引入了新的对象和方法,可以利用jQuery定义的代码,不需要重写这部分功能。
而Sencha公司的ExtJS是一个框架。首先ExtJS不仅包含JavaScript代码,还包含了CSS和图片。其次它的功能是方便开发者搭建可交互的Web应用程序,里面有一些完整功能的模块,比如绘制可交互的图表。所以ExtJS是一个框架。
作为一个前端工程师,面对的框架和库层出不穷,很容易陷入迷茫,到底应该使用哪种?一个常见的误区是,存在某个强大的“终极方案”,可以解决一切Web应用程序开发的问题。经常有一些人请我推荐一些好用的前端框架,我不知道如何回答。
每年都会有很多新的框架发布,它们的作者并不是无聊想要新写一个框架,而是为了解决一个新的问题。比如jQuery的优势在于方便地操作Web界面(DOM)。而Angular并不是“更好的jQuery”,而是提出一种新的解决问题的思路:通过数据绑定,让开发者直接修改数据模型,而不用关心界面(DOM)更新。GASP库发现jQuery动画慢的问题,就重点优化JavaScript动画部分,它号称动画速度比jQuery快20倍,而且能开启硬件加速,在某些情况下比CSS动画性能还要好。
因此,不要迷信大框架,有时候越是有名的框架,越需要满足更多人的需求,会封装很多您可能不需要的资源进去。对于您来说,可能只需要一小部分功能,但是引入了一个庞大的库。我常常看到,在某些人的简单的个人博客中引入了一些庞然大物,但是其实没有必要。这对应聘者来说,是减分的。
在出现一些热门框架时,建议开发者先去了解框架的创建初衷,合理使用,而不是盲目收集。
2、岗位细分
我们知道前端的领域很广,所以有些大公司对它进行了更进一步的细分,比如腾讯的两个职位“前端工程师”和“UI工程师(UI Engineer)”。
UI工程师 vs 前端工程师:
在国外,UI工程师是一个比较普及的岗位。以Google为例,一个叫Front End Software Engineer,属于软件工程部,另一个叫UX Engineer, Front End,有点类似Front End下的一个子项,属于用户体验设计部。
从使用语言的角度来分,UI工程师只负责HTML/CSS,前端工程师只负责JavaScript,这是一种简化问题的解释方法。但我认为这两种职位的区分的重点并不是二者语言不一样,而是责任和关注点不一样。UI工程师更关注视觉上和交互上的体验,而前端工程师更关注逻辑和数据方面的体验,二者是上下游合作的关系。
同时双方的工作也有一些交集,比如UI工程师也会负责一些交互或者动画相关的JavaScript,前端工程师也要熟悉HTML标签才能用JavaScript去操作。双方都必须对对方的知识有足够的理解,合作才能进行下去。两种职位的目标是一致的:给用户提供更好的体验。
腾讯的UI工程师曾经叫“网页重构工程师”。这个名称来自一本很有名的床头技术杂文书《网站重构》(Designing with Web Standards),作者是世界上最有名的网站设计师之一,Zeldman,J.(泽尔德曼)。这本书的主要观点是,用Web标准来“重构”您的网站,而不要用以前的非标准的方式来做网站。
用一个我个人不太喜欢的大白话来说就是:不要用table标签布局,而要用DIV+CSS。我不喜欢这句话的原因是它不严谨,容易在纠正一个过往的错误的时候“用力过猛”。矫枉过正的后果是,现在有一些人只要看到table标签就觉得是非语义化的,喜欢用DIV搞定一切。但是table并不邪恶。table用作数据表格的时候,是非常正确的。有些人在布局这一用途上用DIV干掉了table,却开始对DIV上瘾。
2003年《Designing with Web Standards》出版之前,全世界的设计师还没有Web标准的理念,都在用table标签布局,因为table可以让页面规整。这本书普及了Web标准的理念,在那之后,设计师开始使用语义化的HTML和灵活的CSS来设计页面。2005年此书中文版出版后,也带来了全新的Web标准的理念。我第一次看这本书是2009年,那时我还在读大三,读完这本书之后几个月就签约到了腾讯ISD部门,职位是“网站重构工程师”,所以我对这本书有特别的感情。备注:简历中不要出现“DIV+CSS”这样的字眼,会减分;也不要出现Dreamweaver,因为Dreamweaver是老式的“所见即所得”编辑器的代表。
不说远了,《网站重构》这本书的中文名是一个意译,表明在用Web标准来设计的过程中,我们要推倒以前的网站,“重构”一个新的网站。这也是“重构”这个词本来的意思——重新构造我们的代码。但是这个词被用在了一个希望能推进Web标准的职位上,给网站重构工程师这个职位带来了额外的风险:含糊不清。可能有人会认为这个职位一天到晚都在重写代码吧。这本书的译者之一王宗义在知乎上说:
“我是翻译《网站重构》一书的译者之一,当时我们3个译者各自起了不同的名字,这个名字是我起的,因为译者3个人中我是做程序开发的,借用了软件开发中的著名书籍《Refactoring》的中文翻译《重构》来影射当时国内网站需要用类似的方式来改变网站底层的本质。当时我们几个也有争议,不过阿捷和dodo最终接受了我的想法。就是没想到后来竟然能够成为Web前端的一个重要词汇。”
除了对岗位名字和职责的困惑,还有一个我常常被问到的问题是“重构只会HTML和CSS,有什么前途?”
我的回答是,首先重构不应该只会HTML和CSS。在前面“知识体系”一节中的所有知识点,重构工程师都需要了解和熟悉。特别是在性能、动画、SEO、可用性和移动等方面要有自己的优势。
不过,为了更好地跟国际接轨,同时避免“网页重构”与“代码重构”的混淆,我们在2015年推动了职位更名的行动,现在我们已经正式更名为“UI工程师”。
对于UI工程师来说,UI开发的平台可能不会一直是浏览器,还有可能是原生App。备注:大家都喜欢把它读成“诶批批”,就像一个缩写。但App不是一个缩写,而是对Application简写,所以正确地读法是[æp]。
App UI工程师:
iOS跟Android上的软件跟桌面软件,或者服务器端软件一样,都叫Application。不过由于苹果App Store的普及,加上中国所有做移动端软件的团队的推广,现在大家都默认App就是指手机端上软件。本书遵循约定俗成的规范,提到App时,就是指智能手机上的软件。
App的市场在短短几年时间内就从无到有,它的发展速度比传统互联网要快得多。就像最开始的网站只需要一个开发者,而现在需要很多工程师协力合作,App开发也在经历这样的变化。
传统的iOS开发流程是这样的:首先,设计师设计完PSD稿,做好标注,切出各种状态的图片,交给开发人员;其次,开发人员拿到各种切片,根据标注设计稿和切片,实现界面以及逻辑功能的开发。
从工程质量和进度角度讲,有这样几个问题:
- 开发周期长
因为一个工程师要同时完成界面和逻辑的部分,所以二者只能按队列进行,需要较长的开发周期。如果发生了设计或者逻辑的变更,则会需要更多的时间去修改。
- 代码耦合强
一个人去实现一个模块的时候,代码中难免会出现耦合比较强的情况,没有很好地MVC分离,关于视图的代码跟关于控制器的代码都混在一起,这为后期的修改带来了隐患。
- 沟通成本高
因为设计师与开发人员之间通过标注和切片来沟通,但是标注本身就很不可靠,一个标注了所有间距的设计稿往往并不是我们需要的,工程师需要的是一些常量,以及当界面发生变化时的“规律”。
- 设计还原质量低
传统的工程师在逻辑、健壮和成本上有非常敏锐的把控能力,但是在UI开发和用户体验方面的经验就略差一些。比如,标注了按钮与按钮之间的距离是20px并无太大参考性,因为按钮周围可能会有空白区域。如果开发人员迷信标注上的数字,在代码中直接写标注的数字,成品就会和设计稿效果出现很大的偏差。而且由于设计师和开发人员之间沟通不畅、开发时间紧急和代码耦合的问题,导致设计还原的质量低。
在时间紧急时,工程师更注重性能问题和数据逻辑是否正确,而不太关注“按钮尺寸是否正确”这样的问题。
所以我希望推进的流程是从Web开发中借鉴经验,让我们原本擅长用户体验的Web UI工程师来进行App UI开发,而原来的App开发人员负责除了UI之外的部分。优化之后的整个流程大概是这样的:
- UI工程师拿到需求单和设计稿之后,跟App开发人员一起沟通,明确哪些UI是这次需要重新做,哪些可以复用已有的UI组件。因为UI工程师可能刚接触到这个项目,需要清楚沟通,避免重复工作,也可以开始考虑如何建立公共UI组件。
- 如果是对于已有界面的修改,而无需改变逻辑的,UI工程师直接修改界面代码和图片资源,然后提交测试。
- 对于新增的UI和逻辑,UI工程师与App开发人员约定双方沟通的API,然后自己在视图中实现API的细节,并且在控制器中使用示例来告诉App开发人员如何使用API。App开发人员则同时启动工作,关注后台和App逻辑,涉及视觉层就调用约定的API。
- 界面和逻辑一起在测试环境上联调。
理想状态下,这个方案能解决上面的所有问题。不过有些同学可能会心里犯嘀咕,Android原生App开发需要有Java的知识,iOS开发需要熟悉Objective-C或者Swift语言,这些都不在前端工程师的技能树里面,如何能承担这部分的工作?
这就是我下一章要讲的:向移动端转型。
延伸阅读:
- 《精通CSS:高级Web标准解决方案(第2版)》(英)Andy Budd/Simon Collison/Cameron Moll,人民邮电出版社
- 《单页Web应用:JavaScript从前端到后端》(美)Michael S. Mikowski /Josh C. Powell,人民邮电出版社