我们真的缺前端工程师吗

前言 这两天在好几个地方都看到了一篇关于为什么整个互联网行业都缺前端工程师?的文章,文章本身是去年的,中心思想是:其实我们并不缺前端工程师,我们缺的是优秀的前端工程师。我还是比较同意作者观点的,不过略有意犹未尽的感觉。于是我结合自己的经验,也来聊一下这个话题:我们真的缺前端工程师吗? These walls are kind of funny like that. First you hate them, then you get used to them.Enough time passed, get so you depend on them. That’s institutionalising. 传统软件公司划分开发者的方式下,在前端部门的程序员永远不会去读缓存数据部分的代码,设计师也不太可能去和开发坐在一起,开发也不知道软件最终软件会以何种方式部署在服务器上。 什么是“前端”工程师 我在招聘广告和办公室的一些对话中,听到了一个新的角色:UI Dev,事实上我在知乎上还回答过一个关于ThoughtWorks的UI Dev的问题。简而言之,UI Dev可以快速的把设计师的作品实现为HTML/CSS/JavaScript代码。 如果按照这个标准,我觉得UI Dev对自己的要求太低了。毕竟要学会HTML/CSS实现mockup并不困难,但是成为一名前端工程师则需要掌握更多的知识: 会用PS来进行图片的处理(比如切图,微调等) 用HTML/CSS实现mockup(可能还有SASS/LESS等工具) 熟悉JavaScript(比如前端的MVVM框架,客户端模板) 前端开发的工作流程(代码检查,精简化,模块化CSS,LiveReload,调试) 编写测试(静态检查,单元测试) 跨浏览器、跨设备的解决方法(不同分辨率,不同厂商) 会根据项目的特点选择不同的前端技术栈(移动端,Web站点,响应式设计等) 在有了基础的HTML/CSS/JS技能之后,你会尝试做的更好: 如何更高效的操作DOM 如何将CSS写的更加清晰易懂 如何编写更加易于维护的代码(更有意义的单元测试) 如何组织大型的项目结构,模块化,组件化等等 这些要求事实上已经不那么容易做到了。它可能会花费你2到3年时间来完全掌握。但是2到3年之后,即便你已经成为了一个“合格的”前端工程师,这也还远远不够。在现实世界中,一个软件产品除了前端,还有非常广阔的空间,还有很多有趣的东西值得学习: HTTP协议本身(缓存,鉴权) Web容器/HTTP服务器如何工作 无状态的Web应用的工作原理(如何让网站正确地运行在集群上) 动态,静态内容如何分离部署(反向代理配置) 安全机制如何配置 监控机制如何配置 有了这些,也算是有点端到端的意思了。这时你也已经不是一个“纯前端”工程师了,系统中的大部分问题你都可以搞定,不过日常工作中可能更多的职责还是做前端的开发。但是这些还不够,软件除了交付之外,还有一些非功能性的需求: 端到端测试(UI测试,比如selenium server/web driver) devops(比如数据库环境,测试服务器,CI服务器的自动化provision) 基本的UI设计原则(在某些页面确实的情况下,根据系统的已有UI做设计) 数据库性能优化 性能测试 不过这些还只是我对于Web开发这个领域的总结。其他领域,比如大数据,机器学习,GIS,图像/视频处理等等。...

June 14, 2015 1 min

又论函数式编程

前言 4月初在北京的时候,徐昊同学表示我们公司的同事们写的文章都太简单,太注重细节,然后捡起了芝麻丢了西瓜,于是我就不再更新博客(其实根本原因是项目太忙)。上周和其他几个同事一起参加“Martin Fowler深圳行”的活动,我和同事扎西贡献了一个《FullStack Language JavaScript》,一起的还有杨云(江湖人称大魔头)的话题是《掌握函数式编程,控制系统复杂度》,李新(江湖人称新爷)的话题是《并发:前生来世》。 和其他同事预演的时候,突然发现其实我们的主题或多或少都有些关联,我讲的部分也涉及到了基于事件的并发机制和函数式编程。仔细想想,应该与JavaScript本身的特性不无关系: 基于事件(Event-Based)的Node.js的正是并发中很典型的一个模型 函数式编程使其天然支持回调,从而非常适合异步/事件机制 函数式编程特性使其非常适合DSL的编写 会后的第二天,我在项目代码里忽然想要将一个聚合模型用函数式编程的方式重写一下,结果发现思路竟然与NoSQL依稀有些联系,进一步发现自己很多不足。 下面这个例子来自于实际项目中的场景,不过Domain做了切换,但是丝毫不影响阅读和理解背后的机制。 一个书签应用 设想有这样一个应用:用户可以看到一个订阅的RSS的列表。列表中的每一项(称为一个Feed),包含一个id,一个文章的标题title和一个文章的链接url。 数据模型看起来是这样的: var feeds = [ { 'id': 1, 'url': 'http://abruzzi.github.com/2015/03/list-comprehension-in-python/', 'title': 'Python中的 list comprehension 以及 generator' }, { 'id': 2, 'url': 'http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/', 'title': '使用inotify/fswatch构建自动监控脚本' }, { 'id': 3, 'url': 'http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/', 'title': '使用underscore.js构建前端应用' } ]; 当这个简单应用没有任何用户相关的信息时,模型非常简单。但是很快,应用需要从单机版扩展到Web版,也就是说,我们引入了用户的概念。每个用户都能看到一个这样的列表。另外,用户还可以收藏Feed。当然,收藏之后,用户还可以查看收藏的Feed列表。 由于每个用户可以收藏多个Feed,而每个Feed也可以被多个用户收藏,因此它们之间的多对多关系如上图所示。可能你还会想到诸如: $ curl http://localhost:9999/user/1/feeds 来获取用户1的所有feed等,但是这些都不重要,真正的问题是,当你拿到了所有Feed之后,在UI上,需要为每个Feed填加一个属性makred。这个属性用来标示该feed是否已经被收藏了。对应到界面上,可能是一枚黄色的星星,或者一个红色的心。 服务器端聚合 由于关系型数据库的限制,你需要在服务器端做一次聚合,比如将feed对象包装一下,生成一个FeedWrapper之类的对象: public class FeedWrapper { private Feed feed; private boolean marked; public boolean isMarked() { return marked; } public void setMarked(boolean marked) { this....

May 23, 2015 2 min

Python中的List表达式

一个小故事 三年前,我在一篇博客里不无自豪的记录了python编写的小函数,当时感觉python真强大,11行代码就写出了一个配置文件的解析器。 def loadUserInfo(fileName): userinfo = {} file = open(fileName, "r") while file: line = file.readline() if len(line) == 0: break if line.startswith('#'): continue key, value = line.split("=") userinfo[key.strip()] = value.strip() return userinfo 最近正在跟同事学习python在数据挖掘中的应用,又专门学习了一下python本身,然后用list comprehension简化了以下上面的代码: def loadUserInfo(file): return dict([line.strip().split("=") for line in open(file, "r") if len(line) > 0 and not line.startswith("#")]) 这个函数和上面的函数的功能一样,都是读取一个指定的key=value格式的文件,然后构建出来一个映射(当然,在Python中叫做字典)对象,该函数还会跳过空行和#开头的行。 比如,我想要查看一下.wgetrc配置文件: if __name__ == "__main__": print(loadUserInfo("/Users/jtqiu/.wgetrc")) 假设我的.wgetrc文件配置如下: http-proxy=10.18.0.254:3128 ftp-proxy=10.18.0.254:3128 #http_proxy=10.1.1.28:3128 use_proxy=yes 则上面的函数会产生这样的输出: {'use_proxy': 'yes', 'ftp-proxy': '10.18.0.254:3128', 'http-proxy': '10.18.0.254:3128'} list comprehension(列表推导式) 在python中,list comprehension(或译为列表推导式)可以很容易的从一个列表生成另外一个列表,从而完成诸如map, filter等的动作,比如:...

March 30, 2015 2 min

基于inotify的告警脚本

自动告警脚本 最近项目上有这样一个需求:系统中有一个后台服务会不断的生成监控日志,根据系统的运行情况,它每天会在目录/var/alarms下生成一个文件,文件名带有时间戳,其中内容格式如下: $ cat /var/alarms/alarms-20150228130522.csv node,summary,occurrence,proiority VIQ002,heartbeat failure,2/12/2015 01:23 AM,critical VIQ002,packages are rejected,2/12/2015 01:22 AM,major VIQ002,connection cannot be established,2/11/2015 01:23 AM,medium VIQ001,packages are rejected,2/11/2015 01:23 AM,warning VIQ001,connection cannot be established,2/09/2015 01:23 AM,medium ... 运维团队需要监控这个目录,如果里边的文件发生了变化,就要及时的发送邮件给工程团队解决。我们当然不可能人工的监控该目录,然后编写邮件,再拷贝粘贴,所以需要编写一个脚本来自动化这个任务。 处理方法有两种: 编写一个crontab的任务,每隔五分钟轮询一下,然后编写脚本来探测变化,发送邮件 使用操作系统提供的inotify相关API探测变化,编写脚本发送邮件 不过作为程序员,第二种方法显然更高级一些。另外相对于检测文件变化(对比目录树,检查时间戳,而且还要记录上一次变更的状态等),编写一个发送邮件的脚本要简单得多。 使用inotify 如果在Linux下,我们可以使用inotify相关的工具,你可以使用你正在使用的系统下的包管理工具来安装。也可以直接从源码包编译安装。 安装之后,系统中就有了一个叫做inotifywait的命令,这个命令提供多个参数。默认的inotifywait在接收到指定的事件(文件变化)后,会打印信息并退出。可以使用-m参数让inotifywati处于监听状态。-e参数指定需要监听的事件类型,下面是几个常见的事件类型: CREATE,创建 MODIFY,修改 CLOSE_WRITE,CLOSE,写入成功 还可以通过--format来指定事件的输出,%w表示监控的文件名,%f表示如果被监控的对象是目录,则当发生事件时返回文件名。比如下面的命令: $ inotifywait -m -e close_write /var/alarms --format "%w%f" 表示以监控模式(事件发生后不退出,继续监听),监听close_write事件,在/var/alarms目录上,并且输出的格式为%w%f。 这样我们在另一个窗口上模拟事件发生: $ touch /var/alarms/alarms-20150228130522.csv 当前的窗口就会出现/var/alarms/alarms-20150228130522.csv这样的输出。有了这个功能,我们只需要编写一段简单的脚本就可以完成上一小节中的问题了: #!/bin/bash DIR=$1 inotifywait -m -e close_write $DIR --format "%w%f" | while read FILE do cat ${FILE} | mail -s "Alarm: $FILE" juntao....

March 1, 2015 1 min

underscore中的集合操作

underscore.js中的集合操作 书接前文,我们在上一篇中将一个文本划分成了单词的数组,并统计了每个单词出现的频率。现在我们需要将排行前10的单词找出来。那么第一步就是将所有单词按照频率排序,然后将这个集合的前10个拿出来。 underscore.js为集合提供了丰富的API,这与函数式编程的鼻祖LISP语言有着直接的继承关系。LISP围绕着List提供了的众多函数。 排序 var contacts = [ { "name": "Juntao", "age": 29 }, { "name": "Abruzzi", "age": 30 }, { "name": "Sara", "age": 29 } ]; 比如想要将上面这个集合按照age排序,可以使用sortBy函数: var sorted = _(contacts).sortBy("age"); 默认的sortBy的返回值是按照升序排列的,不过JavaScript的数组原生就有reverse的API用以翻转数组,因此如果要得到降序的排列,只需要: var sorted = _(contacts).sortBy("age").reverse(); 抽取 有时候,我们需要从众多的信息中抽取自己关心的,比如上例中的contacts集合,我们在界面上仅仅需要name属性组成的集合,这时候可以通过pluck来完成抽取: var names = _.pluck(contacts, "name"); //["juntao", "abruzzi", "sara"] underscore.js默认的pluck只能抽取一层,如果遇到下面这种场景: var contacts = [ { "name": "Juntao", "age": 29, "address": { "street": "Dengling Rd" } }, { "name": "Sara", "age": 29, "address": { "street": "Zhangba 4th Rd" } } ] 想要抽取address....

February 21, 2015 3 min