热爱web前端
技术分享平台

跟烂代码相关的那些事儿

琴歌阅读(845)评论(1)

1.摘要

最近写了不少代码,review了不少代码,也做了不少重构,总之是对着烂代码工作了几周。为了抒发一下这几周里好几次到达崩溃边缘的情绪,我决定写一篇文章谈一谈烂代码的那些事。 这里是上篇,谈一谈烂代码产生的原因和现象。

2.写烂代码很容易

刚入程序员这行的时候经常听到一个观点:你要把精力放在ABCD(需求文档/功能设计/架构设计/理解原理)上,写代码只是把想法翻译成编程语言而已,是一个没什么技术含量的事情。

当时的我在听到这种观点时会有一种近似于高冷的不屑:你们就是一群傻X,根本不懂代码质量的重要性,这么下去迟早有一天会踩坑,呸。

可是几个月之后,他们似乎也没怎么踩坑。而随着编程技术一直在不断发展,带来了更多的我以前认为是傻X的人加入到程序员这个行业中来。

语言越来越高级、封装越来越完善,各种技术都在帮助程序员提高生产代码的效率,依靠层层封装,程序员真的不需要了解一丁点技术细节,只要把需求里的内容逐行翻译出来就可以了。

很多程序员不知道要怎么组织代码、怎么提升运行效率、底层是基于什么原理,他们写出来的是在我心目中烂成一坨翔一样的代码。

但是那一坨翔一样代码竟然他妈的能正常工作。

即使我认为他们写的代码是坨翔,但是从不接触代码的人的视角来看(比如说你的boss),代码编译过了,测试过了,上线运行了一个月都没出问题,你还想要奢求什么?

所以,即使不情愿,也必须承认,时至今日,写代码这件事本身没有那么难了。

3.烂代码终究是烂代码

但是偶尔有那么几次,写烂代码的人离职了之后,事情似乎又变得不一样了。

想要修改功能时却发现程序里充斥着各种无法理解的逻辑、改完之后莫名其妙的bug一个接一个,接手这个项目的人开始漫无目的的加班,并且原本一个挺乐观开朗的人渐渐的开始喜欢问候别人祖宗了。

 

我总结了几类经常被艹祖宗的烂代码:

3.1.意义不明

能力差的程序员容易写出意义不明的代码,他们不知道自己究竟在做什么.

就像这样:

public void save() {
    for(int i=0;i<100;i++) {
        //防止保存失败,重试100次
        document.save(); 
    }
}

对于这类程序员,我一般建议他们转行。

3.2.不说人话

不说人话是新手最经常出现的问题,直接的表现就是写了一段很简单的代码,其他人却看不懂。

比如下面这段:

public boolean getUrl(Long id) {
    UserProfile up = us.getUser(ms.get(id).getMessage().aid);
    if (up == null) {
        return false;
    }
    if (up.type == 4 || ((up.id >> 2) & 1) == 1) {
        return false;
    } 
    if(Util.getUrl(up.description)) {
        return true;
    } else {
        return false;
    }
}

很多程序员喜欢简单的东西:简单的函数名、简单的变量名、代码里翻来覆去只用那么几个单词命名;能缩写就缩写、能省略就省略、能合并就合并。这类人写出来的代码里充斥着各种g/s/gos/of/mss之类的全世界没人懂的缩写,或者一长串不知道在做什么的连续调用。

还有很多程序员喜欢复杂,各种宏定义、位运算之类写的天花乱坠,生怕代码让别人一下子看懂了会显得自己水平不够。

简单的说,他们的代码是写给机器的,不是给人看的。

3.3.不恰当的组织

不恰当的组织是高级一些的烂代码,程序员在写过一些代码之后,有了基本的代码风格,但是对于规模大一些的工程的掌控能力不够,不知道代码应该如何解耦、分层和组织。

这种反模式的现象是经常会看到一段代码在工程里拷来拷去;某个文件里放了一大坨堆砌起来的代码;一个函数堆了几百上千行;或者一个简单的功能七拐八绕的调了几十个函数,在某个难以发现的猥琐的小角落里默默的调用了某些关键逻辑。

 

这类代码大多复杂度高,难以修改,经常一改就崩;而另一方面,创造了这些代码的人倾向于修改代码,畏惧创造代码,他们宁愿让原本复杂的代码一步步变得更复杂,也不愿意重新组织代码。当你面对一个几千行的类,问为什么不把某某逻辑提取出来的时候,他们会说:

“但是,那样就多了一个类了呀。”

3.4.假设和缺少抽象

相对于前面的例子,假设这种反模式出现的场景更频繁,花样更多,始作俑者也更难以自己意识到问题。比如:

public String loadString() {
    File file = new File("c:/config.txt");
    // read something
}

文件路径变更的时候,会把代码改成这样:

public String loadString(String name) {
    File file = new File(name);
    // read something
}

需要加载的内容更丰富的时候,会再变成这样:

public String loadString(String name) {
    File file = new File(name);
    // read something
}
public Integer loadInt(String name) {
    File file = new File(name);
    // read something
}

之后可能会再变成这样:

public String loadString(String name) {
    File file = new File(name);
    // read something
}
public String loadStringUtf8(String name) {
    File file = new File(name);
    // read something
}
public Integer loadInt(String name) {
    File file = new File(name);
    // read something
}
public String loadStringFromNet(String url) {
    HttpClient ...
}
public Integer loadIntFromNet(String url) {
    HttpClient ...
}

这类程序员往往是项目组里开发效率比较高的人,但是大量的业务开发工作导致他们不会做多余的思考,他们的口头禅是:“我每天要做XX个需求”或者“先做完需求再考虑其他的吧”。

这种反模式表现出来的后果往往是代码很难复用,面对deadline的时候,程序员迫切的想要把需求落实成代码,而这往往也会是个循环:写代码的时候来不及考虑复用,代码难复用导致之后的需求还要继续写大量的代码。

一点点积累起来的大量的代码又带来了组织和风格一致性等问题,最后形成了一个新功能基本靠拷的遗留系统。

3.5.还有吗

烂代码还有很多种类型,沿着功能-性能-可读-可测试-可扩展这条路线走下去,还能看到很多匪夷所思的例子。

那么什么是烂代码?个人认为,烂代码包含了几个层次:

  • 如果只是一个人维护的代码,满足功能和性能要求倒也足够了。
  • 如果在一个团队里工作,那就必须易于理解和测试,让其它人员有能力修改各自的代码。
  • 同时,越是处于系统底层的代码,扩展性也越重要。

所以,当一个团队里的底层代码难以阅读、耦合了上层的逻辑导致难以测试、或者对使用场景做了过多的假设导致难以复用时,虽然完成了功能,它依然是坨翔一样的代码。

3.6.够用的代码

而相对的,如果一个工程的代码难以阅读,能不能说这个是烂代码?很难下定义,可能算不上好,但是能说它烂吗?如果这个工程自始至终只有一个人维护,那个人也维护的很好,那它似乎就成了“够用的代码”。

很多工程刚开始可能只是一个人负责的小项目,大家关心的重点只是代码能不能顺利的实现功能、按时完工。

过上一段时间,其他人参与时才发现代码写的有问题,看不懂,不敢动。需求方又开始催着上线了,怎么办?只好小心翼翼的只改逻辑而不动结构,然后在注释里写上这么实现很ugly,以后明白内部逻辑了再重构。

再过上一段时间,有个相似的需求,想要复用里面的逻辑,这时才意识到代码里做了各种特定场景的专用逻辑,复用非常麻烦。为了赶进度只好拷代码然后改一改。问题解决了,问题也加倍了。

几乎所有的烂代码都是从“够用的代码”演化来的,代码没变,使用代码的场景发生变了,原本够用的代码不符合新的场景,那么它就成了烂代码。

4.重构不是万能药

程序员最喜欢跟程序员说的谎话之一就是:现在进度比较紧,等X个月之后项目进度宽松一些再去做重构。

不能否认在某些(极其有限的)场景下重构是解决问题的手段之一,但是写了不少代码之后发现,重构往往是程序开发过程中最复杂的工作。花一个月写的烂代码,要花更长的时间、更高的风险去重构。

曾经经历过几次忍无可忍的大规模重构,每一次重构之前都是找齐了组里的高手,开了无数次分析会,把组内需求全部暂停之后才敢开工,而重构过程中往往哀嚎遍野,几乎每天都会出上很多意料之外的问题,上线时也几乎必然会出几个问题。

 

从技术上来说,重构复杂代码时,要做三件事:理解旧代码、分解旧代码、构建新代码。而待重构的旧代码往往难以理解;模块之间过度耦合导致牵一发而动全身,不易控制影响范围;旧代码不易测试导致无法保证新代码的正确性。

这里还有一个核心问题,重构的复杂度跟代码的复杂度不是线性相关的。比如有1000行烂代码,重构要花1个小时,那么5000行烂代码的重构可能要花2、3天。要对一个失去控制的工程做重构,往往还不如重写更有效率。

而抛开具体的重构方式,从受益上来说,重构也是一件很麻烦的事情:它很难带来直接受益,也很难量化。这里有个很有意思的现象,基本关于重构的书籍无一例外的都会有独立的章节介绍“如何向boss说明重构的必要性”。

重构之后能提升多少效率?能降低多少风险?很难答上来,烂代码本身就不是一个可以简单的标准化的东西。

举个例子,一个工程的代码可读性很差,那么它会影响多少开发效率?

你可以说:之前改一个模块要3天,重构之后1天就可以了。但是怎么应对“不就是做个数据库操作吗为什么要3天”这类问题?烂代码“烂”的因素有不确定性、开发效率也因人而异,想要证明这个东西“确实”会增加两天开发时间,往往反而会变成“我看了3天才看懂这个函数是做什么的”或者“我做这么简单的修改要花3天”这种神经病才会去证明的命题。

而另一面,许多技术负责人也意识到了代码质量和重构的必要性,“那就重构嘛”,或者“如果看到问题了,那就重构”。上一个问题解决了,但实际上关于重构的代价和收益仍然是一笔糊涂账,在没有分配给你更多资源、没有明确的目标、没有具体方法的情况下,很难想象除了有代码洁癖的人还有谁会去执行这种莫名其妙的任务。

于是往往就会形成这种局面:

  • 不写代码的人认为应该重构,重构很简单,无论新人还是老人都有责任做重构。
  • 写代码老手认为应该迟早应该重构,重构很难,现在凑合用,这事别落在我头上。
  • 写代码的新手认为不出bug就谢天谢地了,我也不知道怎么重构。

5.写好代码很难

与写出烂代码不同的是,想写出好代码有很多前提:

  • 理解要开发的功能需求。
  • 了解程序的运行原理。
  • 做出合理的抽象。
  • 组织复杂的逻辑。
  • 对自己开发效率的正确估算。
  • 持续不断的练习。

写出好代码的方法论很多,但我认为写出好代码的核心反而是听起来非常low的“持续不断的练习”。这里就不展开了,留到下篇再说。

很多程序员在写了几年代码之后并没有什么长进,代码仍然烂的让人不忍直视,原因有两个主要方面:

  • 环境是很重要的因素之一,在烂代码的熏陶下很难理解什么是好代码,知道的人大部分也会选择随波逐流。
  • 还有个人性格之类的说不清道不明的主观因素,写出烂代码的程序员反而都是一些很好相处的人,他们往往热爱公司团结同事平易近人工作任劳任怨–只是代码很烂而已。

而工作几年之后的人很难再说服他们去提高代码质量,你只会反复不断的听到:“那又有什么用呢?”或者“以前就是这么做的啊?”之类的说法。

那么从源头入手,提高招人时对代码的质量的要求怎么样?

前一阵面试的时候增加了白板编程、最近又增加了上机编程的题目。发现了一个现象:一个人工作了几年、做过很多项目、带过团队、发了一些文章,不一定能代表他代码写的好;反之,一个人代码写的好,其它方面的能力一般不会太差。

举个例子,最近喜欢用“写一个代码行数统计工具”作为面试的上机编程题目。很多人看到题目之后第一反映是,这道题太简单了,这不就是写写代码嘛。

从实际效果来看,这道题识别度却还不错。

首先,题目足够简单,即使没有看过《面试宝典》之类书的人也不会吃亏。而题目的扩展性很好,即使提前知道题目,配合不同的条件,可以变成不同的题目。比如要求按文件类型统计行数、或者要求提高统计效率、或者统计的同时输出某些单词出现的次数,等等。

从考察点来看,首先是基本的树的遍历算法;其次有一定代码量,可以看出程序员对代码的组织能力、对问题的抽象能力;上机编码可以很简单的看出应聘者是不是很久没写程序了;还包括对于程序易用性和性能的理解。

最重要的是,最后的结果是一个完整的程序,我可以按照日常工作的标准去评价程序员的能力,而不是从十几行的函数里意淫这个人在日常工作中大概会有什么表现。

但即使这样,也很难拍着胸脯说,这个人写的代码质量没问题。毕竟面试只是代表他有写出好代码的能力,而不是他将来会写出好代码。

6.悲观的结语

说了那么多,结论其实只有两条,作为程序员:

  • 不要奢望其他人会写出高质量的代码
  • 不要以为自己写出来的是高质量的代码

如果你看到了这里还没有丧失希望,那么可以期待一下这篇文章的第二部分,关于如何提高代码质量的一些建议和方法。

前端技术学习路径的一点感想

琴歌阅读(752)评论(3)

前言

之前不经意看到这篇文章,那么就整理之后给大家分享一下,本文主要说明前端学习的过程和路径。

路漫漫其修远兮,吾将上下而求索~~

正文

明确你的意愿

其实不管是前端还是后端,首先要清楚,自己是想从事业务型的技术工作,还是 Research 型的技术工作。

前者可以理解为:互联网公司的程序员。业务型的前端工程师,最大的一个特点是:从客户需求出发,去真真正正地做出一个产品来,交付客户,让客户满意,并对客户产生实际的价值,这也是大部分互联网技术从业人员的工作内容。业务型的技术人员使用的技术往往不是最新的前沿技术,而是经过验证的效益更高的技术,用最小化的成本来服务客户。这需要不停地实践与实战,以便更快、更高效地实现客户需求的满足。

后者可以理解为:行业的科学家。这种角色时常走在行业的前面,去带领互联网公司,甚至是技术行业,探索更先进的技术、挖掘更有价值的数据、构建服务大众的基础设施平台等……

目前,绝大部分互联网技术人员都从事着业务型的技术工作,少部分极其优秀的技术人员可以从事 Research 型的技术工作。文章一开始就强调要了解清楚自己想从事的是哪个类型的技术工作,目的是树立一个终极目标。而要从事 Research 型的技术工作,首先必定是需要多年的业务型技术工作的经验积累的。

补充一个题外话

很多没有接触过编程的人都以为编程很难,要具备很高的数学能力,是因为他们往往都把“编程”这个活动理解为从事 Research 型的技术工作了。这是他们的一大误区。

其实大部分的编程活动,都是对“业务逻辑”的理解,然后用代码拼凑出产品和服务,这里面涉及到多深的数学知识?我看不多……

编程的总体思路就像是写书。写书前,你已经识字了,这是你的基础,你还会运用积累下来的一些表达技巧,让你的书更加吸引人,实现更高的销量。写书前,你已经知道了很多前人规划好的、约定俗成的东西了,而你要做的是,利用这些约定俗成的东西,去写一本自己的书。这本书可以参考前人的书,也可以参考已有的论文。

编程,往往是一个创造过程,而不是一个发明过程。

前端入门指南口语版

前端作为一个新兴工种和职业,它一直未能有幸像 Java、C 等后端语言一样,进入高校,成为一门课程。这就导致几乎所有的前端工程师,要么是从后端工程师转型而来,要么从设计师转行而来,都靠自学成才。而我也发现,校内极少人从事前端开发,大部分人都走后端和客户端路线。

而前端从诞生至今,一直未能跳脱 HTML、CSS、JavaScript 这三门“语言”。自然地,入门指南当然围绕三者讲起:

HTML、HTML5

记住至少 90% 的 HTML 标签及其语义,重点是 headerfooterarticlemainsectionnavaside 这种语义特别明显的用于布局的标签。

CSS、CSS3

记住至少 90% 的 CSS 属性与写法,重点是盒模型(marginpaddingborderbox-sizing)、页面布局相关(position 的用法、淘宝的双飞翼布局)、页面渲染相关(背景、阴影、字体样式等)……

以上是编写静态页面的基础,重点是多实践。

Bootstrap

CSS 框架 Bootstrap 应该是大部分前端工程师绕不过的一个点。它流行到以至于现在有些前端开发人员都刻意避免使用 Bootstrap 来防止页面撞脸了。

即使如此,还是阻挡不了大部分前端开发人员使用它的热情。毕竟它能帮助我们快速实现页面响应式布局、快速编写出可交互的页面。

我的建议:觉得 HTML、CSS 基础了解得差不多了,我主张先学习 Bootstrap,重点是学会 Bootstrap 的栅格系统及其原理,了解响应式网页设计是如何实现的,能够用 Bootstrap 拼出一套管理后台界面。

Bootstrap 的进阶用法就是:自行定制 Bootstrap。虽然已有 Bootswatch 这样的第三方 Bootstrap 主题能满足大部分一般需求,但总有些时候需要自定义样式。而在使用 Bootstrap 的过程中,修改 Bootstrap 默认主题的最佳方式不是覆盖默认设置,而是自定义主题后构建出自己的 Bootstrap 主题。

关于定制 Bootstrap 主题,官网上有一个现成的网页。而如果要在自己的电脑定制,需要掌握 LESS 或者 Sass(两者都是 CSS 编译器,为编写 CSS 引入了编程语言的变量、复用等特性)。

JavaScript

我个人是从设计师转型而来的前端工程师,所以偏重页面的设计、对设计稿的还原程度、更好的页面语义和页面布局、SEO 等,对 JavaScript 的了解并不深。这样的角色,在腾讯的岗位叫 UI 开发或网页重构。小公司的话,不分前端工程师、网页重构工程师、UI 开发工程师。所以遇到以编写 JavaScript 为己任的前端开发工程师,我的 OS 是这样的:

没想到你是这种前端工程师。

哈哈,开个玩笑。回到正题上来:

由于上面提到的缘由,对于 JavaScript,我主张是边用边学。当然首先是要已经看过 W3School 的 JavaScript 文字教程,了解 JavaScript 在浏览器中的能力(操作 DOM、BOM,知道 AJAX 是什么),并知道它拥有什么样的 API,什么效果能实现,什么效果不能实现。当 JavaScript 用于编写程序逻辑时,要知道需要用到什么语法、内置方法、内置 API。再后来,是了解 HTML5 中 WebSQLWeb Storage(Local Storage 和 Session Storage)、应用缓存(Application Cache)、Cookie 是怎么回事,能实现什么功能。

以上算是接触并使用 JavaScript,还没进入大量实战阶段。

jQuery 与 Zepto.js

我的主张是,JavaScript 一开始并不需要学得太深。对于业务型的前端工程师,完成上面提到的 JavaScript 部分,基本就够用了。

而基本上,一定要做的一件事是:学习 jQuery、Zepto.js(人称移动端的 jQuery)。两者的实现极其相像,可以只学 jQuery,Zepto 基本就无师自通了。

jQuery 和 Zepto.js 之于 JavaScript,就像 Bootstrap 之于 HTML 和 CSS。都是为了简化代码、更高效地完成业务而开发的 JavaScript 类库。它们封装了很多原生 JavaScript 的语法和方法,使之编写代码时更方便,同时保证对旧版浏览器的兼容性。

对于两者,建议:了解如何使用 jQuery 操作 DOM、常用的数据处理方法(数组项如何增删查改、对象如何访问及选取、方法如何调用、JSON 数据如何处理及使用)。

所谓中级前端工程师

所谓中级前端工程师的范畴:学会使用前端工具。

工具包括但不限于:

  • Sublime Text、Atom 等代码编辑器(认真记住各种快捷键,好好阅读各大编辑器的插件使用说明)。
  • 方便前端开发的利器:浏览器自动刷新 Browser-sync、调试工具 Chrome 控制台、Fiddler 和 Weinre、模拟请求 Postman、图片压缩 iSparta……
  • Node.js 平台下的 NPM(一个集合了几乎所有优秀前端开源项目的社区,用来管理项目中用到的开源技术、资源、插件等)
  • CSS 预处理器与后处理器:Autoprefixer、PostCSS(一个庞大的社区,提供各种 CSS 处理能力的插件)。

    由于各大浏览器对标准 CSS 的支持情况不一样,都或多或少地拥有带自身特殊前缀的私有 CSS 属性,例如 -webkit-border-radius 这个私有 CSS 属性是 Webkit 内核的浏览器所支持的写法。

    以前没有 CSS 预处理器与后处理器时,往往需要前端开发人员手写带有各种特殊前缀的私有 CSS 属性。有了这些处理器之后,只需要写标准的 CSS,如 border-radius,再用这些处理器处理一下 CSS,就能自动补齐浏览器私有 CSS 属性,实现前端页面的最大化兼容性,例如兼容 IE9、IE8 等等……

    当然,这个栗子只是众多 CSS 预处理器与后处理器最简单的一个应用而已,还有更多高阶属性与使用技巧。

  • JavaScript 编译器:之所以 JavaScript 有编译器,是因为自从 2015 年,JavaScript 的标准制定组织 ECMAScript 委员会决定今后每年都推出新版的 ECMAScript 标准,也就是新版的 JavaScript。而又因为各大浏览器的开发进度不同,对新标准的实现还没完全跟进,而一些喜欢追新的前端开发人员又想早早地开始使用新版的 JavaScript,所以就出现了 Babel 这样的 JavaScript 编译器。

    Babel 的主要功能是将新版 JavaScript 编译成旧版 JavaScript,使得前端工程师既可以编写最新版的 JavaScript,而前端页面也可以完美兼容各大浏览器。

  • 前端开发构建工具:Gulp。前两年还流行 Grunt,这两年流行 Gulp,所以直接学习并使用 Gulp吧。

    Gulp 的最大一个用处是:打通开发过程中的工作流程。例如开发过程中,自动刷新浏览器、将 SCSS 编译成 CSS、将新版 JavaScript 编译成各大浏览器支持的旧版 JavaScript、项目开发完毕后的性能优化:压缩图片、CSS、JavaScript 等静态资源、合并雪碧图,把项目源码上传服务器等。

    其实每个小操作都有工具能完成,而像 Gulp 这样的构建工具的作用是:将所有分散的小操作和小流程,通过 Gulp 平台上的插件,集合成一条龙服务,一次编写前端 Workflow,就可以免去很多分散精力的小操作,实现开发流程自动化。

    上面提到的 CSS 处理器、JavaScript 编译器,都有 Gulp 插件,例如 gulp-autoprefixergulp-cssnanogulp-image……

  • JavaScript 前端框架:Vue.js、Angular.js、React、React Native……

    JavaScript 前端框架的主要作用是,让前端工程师也能像后端工程师一样,以一个已经验证有效的开发范式来支撑项目,降低耦合度,提高项目可靠性和可维护性。

一点补充

由于前端刚从刀耕火种的时代,步入前端工程化的初级阶段,此时会出现大量的工具,例如 Grunt 还没开始用,Gulp 就来了,Gulp 还没领略其精粹,Webpack 又款款而至。迷恋工具,每每追新,必然不是前端工程师的修养。对于工具,我们强调使用场景。在什么情况下,需要使用什么工具,帮助提高效率,才应该是前端工程师所要关注的重点。

所谓高级前端工程师

所谓高级前端工程师的范畴:深入 JavaScript 底层、深入浏览器底层。

包括但不限于(由于本人远不及这个层次,下面的罗列可能不正确):

  • 玩转 SVG 绘图、Canvas 绘图
  • 页面性能调优
  • 玩转 Web Socket、Web Worker
  • 自行开发 JavaScript 插件、工具、框架等
  • 玩转 Chrome V8 引擎

共同进步

如有错误,欢迎在下方评论区指正。

写给想做前端或者已经在做前端的你

琴歌阅读(909)评论(0)

写这篇文章希望和大家分享、交流,共同进步;更希望能够给将要在前端行业工作、实习的同学一些帮助。

P.S. 喷神请绕道,大神勿喷,不引战,不攻击,不钻牛角尖。

第一次接触前端时。许多同学估计都想过要做一个网站,大部分又是从PHP开始的(谁让它是世界上最好的语言呢)。后端语言参与渲染HTML一直很主流,跟着教程做,你会写一堆样式表,到后来也许是需要在提交表单时进行客户端验证,你开始写一些JS。
想做个网站啥的(以前app没有那么多),必须要学会HTML、CSS、JS,HTML构造结构,CSS表现样式,JS决定行为。JS似乎充满奇技淫巧,可以做各种效果啊,飞雪花片啊,搞咱们选课、评老师的网站啊。

后来接触了jQuery,用起来无比顺手,特别是看完《DOM编程艺术》以后。那段时间担心JS掌握得不好,心想总是用jQuery以后不会写Native怎么办?也会关注“可以直接学习jQuery吗”这样一些问题。学习了Ajax后又做过瀑布流图片墙,觉得无比兴奋。不过认识也仅仅停留在异步加载局部更新DOM可以创造更好地用户体验。

实习期间,看到公司前端做雪碧图、切片、搞div、css布局,然后花很多时间在浏览器兼容的问题的调试上。尤其是活动页面,写HTML、CSS基本占到工程的80%,JS写起来很快。后来Bootstrap逐渐流行起来,后端同学可以直接写后台,甚至都不需要前端和射鸡师了。加点栅格,加几个类,轮播组件啥的样样有,再引入jQuery,前端便成为了顺带做的事了。

我接触前端的过程没有系统性,充满了探(瞎)索(搞),也缺少引导。不过细想想,我邮只有前端的选修课啥的,课程也不是那么就业导向。就像论坛里的同学,自己搞,自己提升。

我们把上面这些点提取出来:HTML、CSS、JS、jQuery、Bootstrap,再刷刷题,看看基础知识,基本就可以参加校招了。

那些求入门、求实习、赶春招、赶秋招的同学一定来得及:不过泼一盆冷水,进入大公司只是开始,你的认识、习惯、思维方式最终会决定你事业或者专业的高度。

面对新技术我觉得了解它为什么产生,解决什么问题,会怎么发展,如何在现有的工程中进行实践,比讨论它们的优劣更有意义:

首先 html5 不是一个新技术,而是在现代浏览器中使用CSS 3等特性进行前端开发的过程。以前我们更关注浏览器的差异性,而现代的浏览器对标准的支持越来越统一。

回到jQuery,在web app中使用越来越少了,一方面这个库太“大”了(吃流量),从页面加载、打开速度理论看,英明的老大会把它砍掉。针对库大小的问题,Zepto.js是一个解决方案。这个库与jQuery API相对统一,抛却了很多浏览器的兼容性的代码。

但是现在浏览器的querySelector方法,已经很好地解决了jQuery中的“Query”,使用原生的fetch方法请求数据,返回Promise也能比jQuery.ajax()的实现更好、更清晰的解决问题。

看待jQuery,我觉得应该更多看到它的历史意义。jQuery,一定程度上成为了工业标准,影响了JS语言的发展和其他JS库的构建。如同coffeescript对ES2015的影响。至于实践,越来越多的web已经不依赖jQuery进行开发了。

前端这几年进步太快了,我尝试按照不同方向讨论一下这些技术栈。

脱离浏览器的JavaScript

Node.js

我们讨论的JavaScript更多是以浏览器为宿主的ECMAScript,同源的ActionScript以Adobe的Flash作为宿主的。

浏览器中的JavaScript提供了大量与浏览器相关的API。脱离这些特定API看JavaScript,异步是它特别重要的一个特性。Google的V8引擎,让JavaScript的运行时性能大大提升,是Node.js的产生另一个必要的条件。

Jser突然可以全栈了,面对新的技术,不乏布道师。国内第一本Node.js书籍是BYVoid写的,当时盛传这个同学拿到了我司的60w的offer,一片沸沸扬扬;大家可以向他学习,在一个技术还未在祖国大地流行起来时,迅速写一本书。

至少目前,很少有大公司完全把JavaScript作为前后端通用的技术栈。传统的后端语言和技术并未没有被代替的危险。不能把Node.js简单看做是JavaScript在服务端的延展。

我觉得,Node.js很大程度拓展了JavaScript的使用范围,改变了传统前端的工作流程(后面提)。特别是NPM的产生,意义非常之大,它让JavaScript成为了一个生态系统,CommonJs也在JavaScript模块化未成熟之前,提供了优秀的模块化解决方案。

通过package.json,我们可以依赖已有的NPM项目构建自己的库。前段时间,某个同学因为法律的问题,撤消了发布在NPM上的leftpad包,短短11行代码的包撤消后,造成了React-Native、Babel等项目构建失败的灾难。

module.exports = leftpad;
function leftpad (str, len, ch) {
  str = String(str);
  var i = -1;
  if (!ch && ch !== 0) ch = ' ';
  len = len - str.length;
  while (++i < len) {
    str = ch + str;
  }
  return str;
}

许多人反思,为啥那么简单的代码要依赖,不自己写?是不是Jser忘记了怎么写代码了?我觉得,盲目解除对其他库的依赖会失去NPM社区的初衷,除非你想做超级轮子哥。NPM生态圈制定一下撤包的规则,这种灾难或许以后就不会发生。自给自足的同学们,如果lodash撤包了,或者是tj holowaychuk大神激情起来删了所有包,你们怎么办?

Node.js也让服务端渲染成为可能(universal),代码段技能在服务端运行也可以在客户端运行(universal)。从这点看,代码更容易维护和编写了,部分解耦了前端和服务端。对于SEO这个令人头大的商业问题,服务端渲染可以较好地解决。感兴趣的同学,可以去了解一下搜索引擎的爬虫是如何干活的。

总结一下,Node.js的产生完善了JavaScript生态圈,让大家看到了JavaScript的潜力,让构建更为庞大的JavaScript项目成为可能,同时前端可以使用更为工程化的流程和工具进行开发。

推荐大家一定去了解和使用Node.js,使用NPM构建自己的项目。

JSON

JSON变成了事实上Web数据的传输标准,这个是JavaScript另一大贡献。

对终端的开发,使用JSON数据后使得服务端的开发更加专注和统一。

 data.each do |key, value|
   puts "Key #{key.inspect} has value #{value.inspect}"
 end

这种代码展示了服务端渲染的能力。但是对于对于iOS、Android等原生应用,除了在WebView中,无法消费这些渲染好的HTML。

JSON和App的流行,让后端语言在渲染方面逐渐让道,向纯粹的Service发展。比如beta版本的 Rails 5 大大增强了 Rails 作为 API Service的能力。

从Ruby 或者 PHP转换到JSON需要相应的映射函数,Node.js来得更为直接,因为JSON就是一个普通的JavaScript对象。

大家可以去学习成熟的JSON风格指南。同时通过实践逐步加强对JSON的认识,设计更为规范的JSON(这个会森森影响到Mongo的存储,查询效率,React的性能)。

二进制处理后的JSON,是MongoDB存储的内容,这个基于文档的数据库被越来越多的公司使用,使得编写嵌套数据,存储流数据更为容易。传统的关系数据库,将查询结果表示为JSON,需要经过查询、封装、Transform等多个步骤,而MongoDB的查询结果就是JSON,直接查询直接使用。当然我们看到,在处理事务型问题上,关系型数据库还是首选,比如电商。我们去褒贬关系型数据库是否落后没有意义——使用场景不同。

如果大家有兴趣,可以去尝试MongoDB,感受一下冲击。

前端工程化

接下来提一下前端工程化。脚本语言并不一定需要编译再执行。传统的工程中,通过<script>标签引入依赖的JavaScript库、样式表后直接开发,写样式表。

当工程规模增大后,传统实践维护成本直线上升——于是模块化开发成为工程上的最佳实践。我们可以使用特定的技术对JavaScript、CSS进行压缩,同时合并JS与CSS解决脚本间的依赖问题,以此减少线上环境中HTTP请求的数量。

Sass Less Stylus

CSS在前端领域,进步最慢。声明式的样式表缺乏逻辑性,层层嵌套,维护成本高且不易团队合作,样式覆盖这个问题也相当恼人。

Sass等技术,输出编译后的CSS样式表,把开发过程和实际样式表分开。.scss文件结构清晰,通过变量定义、mixin等的使用让样式表的开发更加正规化。

Less与Sass类似,Stylus是TJ大神的作品,实在非常简约抽象,个人感觉不易维护。最新的Bootstrap 4使用Sass,放弃了Bootstrap 3中使用的Less。

个人觉得,这些技术深入掌握一门就可以了。实际开发还要看公司的技术栈。

CoffeeScript 与 TypeScript

与Sass等技术思想类似,Coffee 与 TypeScript 也是一个编译技术——把.coffee.ts文件编译成javascript。编译这个思想在前端领域很重要:不改变现有的语言环境(JS、CSS)同时进行最佳的工程实践。

使用过一段TypeScript,真心觉得是神器,在无类型语言中写类型不是倒退吗?请摒弃这些激进思想(世界上最好的语言第7代不是也支持类型了吗),尝试在项目中使用TypeScript,你就会感知到它的神器之处。首先,多人协作更为容易了,结合IntelliSense,IDE更为智能,开发快感直线提升。

TypeScript是微软的开源项目,Angular 2 放弃 Dart完全拥抱了TypeScript,TypeScript与Angular 2 强强合作,加入了许多构建 Angular 2的新特性。

我们整理一下这些工程化的实践:预编译、合并、压缩、打包,引入下一个概念Package工具:

Webpack Gulp Grunt Browerify

Package工具,是任务驱动型的工程化工具。通过打包构建,线上代码更为精简,循环引用等问题迎刃而解。

上述这些工具变化极快,Webpack 2快接近稳定了,JSPM这个新的工具也得到了使用:但是没有最好的,只有最合适的工具。都说Grunt已经过时,jQuery等库还一度使用Grunt进行构建。对于新的工具,我们可以了解它们的思想,不要被它们压得喘不过气:比如Gulp更像是一个PipeLine,对代码流一步步通过Loader进行加工。

在淘宝无线时,有些H5前端用Grunt构建工程,使用Less写样式表(还有些人什么都不用,直接在JsBin里面写样式、写JS)。

任务工具结合CommonJS后,可以只引用需要的模块、样式表。这样打包后,文件更小:当然如果结合sourcemap,调试和定位问题会更为容易。

JavaScript Libraries

工程化、模块化解决了code如何生产,模块、结构如何组织等问题。大家也在不断思考在前端与数据的关系。传统前端开发并不是数据导向的,多数页面由服务端渲染,前端的重心不在数据而是聚焦在用户行为的响应上,这时前端仅仅是产品的视觉末梢。

单页应用开发技术越来越多地被实践后,前端逐步变得更为数据导向(JSON API),由末梢变为大脑——业务逻辑前移;对浏览器历史的管理也是也是单页应用的另外一个中心,前端也逐步变得更为历史导向。

为了更为数据,JavaScript 库借鉴了很多服务端的思想比如MVC。MVC将数据抽象为模型,在模型中定义操作数据的基本方法;在控制器中定义商业逻辑,并控制模型的渲染。

这个阶段的代表是Backbone.js。Backbone有一个可以自定义的依赖于jQuery的前端模版引擎,是MVC模式在前端开发中的实践。同时Backbone.js 依赖于同一个作者创建的 Underscore 库,以函数式编程思想操作数据或者集合(这个Jser创造了CoffeScript、Backbone、UnderScore,Lodash是从Underscore项目派生出来的,大家可以膜拜大神Jeremy Ashkenas)。Backbone影响深远。比如facebook的 parse(中国克隆版叫LeanCloud)这种无后端服务就从Backbone借鉴了很多。

Angular.js的产生是跨时代的,这个大而全的框架让单页应用的开发更为容易。
最开始Jser们并不是很适应Angular,反倒Java程序员可以很快的上手,因为Angular 借助了很多 Spring的思想比如依赖注入。

Angular 仍然深受 jQuery 的影响,内部实现了极简版本的选择器。Angular进行双向绑定,这个牛逼的特性也有时因为性能问题被诟病。

JavaScript 库也借鉴了很多客户端开发的思想,比如React、Vue。

个人觉得,拿Angular、React、Vue这些库比较,论其优劣意义不大。他们产生的时代不同,解决的问题不同。React、Vue离开Router和生态圈的其他组件也无法构建单页应用。

React.js

React并不是一个大而全的框架,主要专注在UI层,React以Component的方式组织应用:一个Component包含这个组件的模版(样式)和行为逻辑。多个Component可以组合,产生兄弟、父子的Component结构关系。

以往开发强调结构、样式、行为的分离。但从组件角度看来,所有这些都是构成组件不可分割的整体。JSX使得HTML与JS的混编更为容易,同时React组件也可以使用内联方式来组织样式。

React默认单向绑定,在State发生变换后重新渲染DOM。从Component的生命周期中,我们可以看到客户端开发的影子(特别有iOS开发的经验的话)。譬如componentWillMount、componentDidMount、componentWillReceiveProps、componentWillUnmount这些生命周期的钩子就是像客户端开发学习的例子。一方面,生命周期的增加细化了开发的粒度,另一方,也为前端的再一次拓展做了充分地准备。

React调用render方法对Component进行渲染,其中涉及了Virtual DOM机制和只能的diff算法——只更新发生变化的DOM,以此提高渲染的效率。

React并没有提供完成的数据管理方案,Flux也仅仅是一个实践的建议。去年,各种Flux解决方案百花齐放,Redux获得了最大的关注度。使用Redux是一个从入门到懵逼的过程,而且往往不知所以然,然后“这厮”有引入了Store、Action、这些概念。建议大家不要为了Flux而Flux,多做一些实践,了解函数式编程,了解Map、Reduce的概念,再深入Redux:

在这里分享些自己的认识:每一个组件都有自己的State,有层次关系的State共同组成一颗状态树来记录整个应用的状态。当Aciton被触发后,State随之更新,React以此部分地更新应用的状态和视图(State —Action—> State’)。贴一个自己总结的白板图:

这里第一次提到Immultable这个概念:总是不改变原来的对象而生成新的对象。Immultable,让时光大法得以实现。我们如果把DOM看做是State在UI层的映射,难么从State’到State后,自然能把UI层还原到原来的状态:Redux黑魔法

Angular 2

Angular 2 已经到了Realease Candidate阶段,从alpha阶段开始,每一次release都有一大堆BREAKING CHANGES(MD API说变就变)。

不过好在基本每个版本我都在跟进,还是做了些实践。

如果大家抱着学习的心态,一定不要去看国外或者知乎大神们对各种框架的褒贬,也不要去搜“我究竟是学习(使用)React、Angular 2、Ember还是Vue这种问题”,了解它们的思想和解决问题的方式。

Angular 一直使用依赖注入的单例模式解决数据管理的问题。Angular 2使用zone.js实现了一个新的脏值检查机制,并在浏览器端使用System.js管理code的依赖。

使用 TypeScript 进行开发,意味着从生产到上线过程必须经过编译和转换,特别装饰符的使用,让代码表意性更强,同时装饰符作为元数据,指导TypeScript的编译过程。

举个例子,我们看看Angular 2如何解决表单的验证问题:
Angular 2将每一个表单项(比如 input、textArea、radio)抽象为一个Control,将整个表单抽象为一个ControlGroup。每一个Control都有自己的验证规则,ControlGroup的合法性又由其包含的所有Control共同决定。这个实践使得验证逻辑与表单DOM实现了分离,更为清晰,同时原本操蛋的表单测试变得简单易行,因为测试ControlGroup时并不需要依赖于DOM。

分享一篇我翻译的文章:Angular 2核心概念,阅读后可以对Angular 2 有一个大致的了解。

Angular 2是对WebComponent的渐进,通过WebComponent,我们可以导入和使用各种成熟的组件,而不用关心它的具体实现,就像黑盒一样进行使用:例如我们想嵌入一个百度地图的WebComponent,可以这么写:

<BaiduMap [width]=300 [height]=300 [user-scalable]=false [latitude]=... [longitude]=... />

于是就I生成一个指定大小和位置的地图组件(百度地图没有这东西,我YY的,股沟有)。

Vue

这个框架我不是很了解,开发者是尤雨溪大神,在github上stars超过10,000。
手机淘宝的勾股大神一直在布道、推广、实践。个人觉得Vue的核心贡献者太少了,拉上阿里巴巴是一个明智的选择,毕竟很多前端大神可以共同完善它。

脱离浏览器的 JavaScript Libraries

再看Virtual DOM这个概念,就像是薛定谔的猫,在渲染前什么都是、什么都不是。Virtual DOM是一个抽象的概念,也组成了另一个抽象概念—— Component。(这个堪称是FB的野心,以后估计很多人被这个东西搞失业)。

一个视觉元素,一个用户事件,可以做如下抽象:

/*****
          <div></div>
    /(html)
View
    \(iOS)
           UIView

          click
    /(html)
Click
    \(iOS)
          TouchUpInside

*****/

我们发现,如果在编译时View、Click才与运行环境相关,那么app、web app的开发其实是可以共享大部分代码的,这个时候JavaScript变为了中间语言。

这个想法早在cocos2d中就已经实现,进行游戏开发的同学使用c++进行游戏开发,编译后产生安卓和 iOS 的版本平台相关的游戏。

React-Native就是这个思路的实践者,通过js绑定了OC或者安卓的运行环境,开发出性能接近于原生的原生应用。React-Native大法延展了JS的广度,也填补了iOS和安卓开发间的技术沟壑。

另一个黑魔法是热更新,以往大幅度更新App只能在App Store、安卓各大渠道发布更新,然后从应用商店提示用户升级,每一次提交都需要被审核,更何况并不是每一个用户都知道或者想去安装每一个更新版本(比如我妈)。通过React-Native可以直接下载新的bundle,实现热更新。

论坛里,有人讨论React-Native热度骤减,你们去官网看看,人家才v0.25。很多公司用不好驾驭不了React-Native原因出在缺少既了解客户端开发有了解前端开发的程序猿(媛):不能否认,Reactive-Natvie的产生是大势所趋。

说道阿里在搞的Weex,也是在向这个方向探索,如果特别成功并且使用广泛的话一定会把Vue搞得更大:尤雨溪大神在这个问题上相当的明智的。

再回到Angular 2,DomRenderer.getNativeElementSync(elementRef)也不是在做同样的事情吗?相关项目详见:NativeScript

除了JavaScript,CSS 也在尝试成为与平台无关的样式语言。React-Native就实现了CSS 的部分子集与iOS、Android的绑定。

大前端

未来的前端是一个包含但不仅限于app和web,从事多端构建任务的程序员工种。团队中单一技能的人员会越来越少,客户端与web端的界限也会越来越模糊。

同时新的技术大多会在不同领域交叉点产生。网络提速,设别处理能力提高后,应用大小、性能可能退居第二,用户体验和开发效率提升到第一。

比如很多公司,由于担心js、css打包后过大,放弃使用框架。这点我持保留意见,快速迭代的产品必须有敏捷的技术栈和稳定的框架。

目前新版本的Chrome、Node.js对ES6标准的支持已经超过90%,Babel这一类工具的生命周期不会很长,TypeScript可能会越走越远。

给大家的建议

  1. 道阻且长,冰冻三尺非一日之寒;
  2. 广泛地学习,有条件和能力的同学尽早地接触客户端开发,更多地了解服务端开发;
  3. 前端大有可为,新技术的发明者大多不是老东西,老东西经验足但是历史包袱重;
  4. 只在浏览器中思考必死无疑;
  5. 像一位同学提到的,打好基础,offer就有。学校里倒腾几年真的很难搞出什么大新闻,面试官也不会刻意为难你;
  6. 测试测试测试,前端测试值得学习掌握,比如e2e,这是一个机会:我能告诉你很多公司的前端测试都瞎JB点吗?
  7. 学习一些函数式编程的思想,例如:lodash、Redux、RxJS;
  8. 拿到offer只是开始不要嘚瑟。

番外篇——理性看待前端紧缺的现象

刚入职淘宝时,团队里有许多前端外包同学,后来很大一部分转正了,有经验的前端工程师一直稀缺。

2012年,PC购物还是主流,我们见证了无线成交额(GMV)逐渐赶超PC的时刻:端的重心越来越向mobile(App)移动。

Hybrid App很流行,部分是因为Native开发更为复杂,同时审核、更新机制缓慢。每一次手淘release都要考虑与老版本的兼容性,几百号人同时开发二个(安卓、iOS)App想想多复杂。

H5在webview中运行,随时可以更新、快速迭代,新产品或者是活动页面大多数会采用H5的形式进行发布;甚至很多小公司由于财力和技术储备有限,直接用App做壳,里面全是用H5来开发:因此市场上产生了很大的H5程序员的需求。

且慢,没有任何人说过H5比Native更好,也没有什么H5的春天,一切的一切都是因为Native 开发、更新不够成熟。但也仅限在一个时间段内。

如果一切问题都不是问题了,干嘛不全做Native?目前看来类似于React-Native、JsPatch这样的技术逐渐会让很多前端失业或是是被动转岗到纯PC业务。

番外篇——推荐学习资源

  1. You don’t know JS,clone下来以后用markdone阅读器阅读;
  2. ES6 教程,阮一峰大神的ES6教程,纸质书可以在京东啥的买到;
  3. LeanPub,自出版书籍网站,每次更新都会发布新版本。支持Visa支付;
  4. Manning,特别是MEAP系列的图书,按章节更新,最新一手资料,支持Visa、PayPal支付;
  5. CodeSchool,在线学习网站,覆盖前端、iOS、Ruby等,可以先试试免费课程,支持Visa、PayPal支付;
  6. EggHead,在线学习网站,先试试免费课程,授课人大神极多,基本涵盖了最新的前端技术,支持Visa支付,200刀一年略贵;
  7. 要是觉得贵,想想火麒麟。

五种与攻城狮沟通的技巧

琴歌阅读(542)评论(0)

021455rfdfhrsf98vvfv93

作为一名产品经理,需求来源五花八门:开发、设计、市场、商务等。然而,攻城狮应该是与产品经理的日常工作物理距离最近的了,此外,他们很清楚哪些事能让他们的工作更简单。
下面的方法可能会让你的开发团队会心一笑:
1、做数据驱动的决策
攻城狮们希望自己跟对人,他们希望你能够完全了解你的产品,他们想要了解你的用户,用户的反馈,以及产品的竞争对手,以及你运营所在的市场和行业。
他们想确认自己将要开发出的东西是基于可靠的数据,这些数据是对真实用户进行的真实调研得来的。这样的数据驱动的决策并不局限在产品功能上。如果一个产品经理会用数据和先验去支撑其他重大决策(如研究一个团队的SPRINT报告以估计团队的能力),攻城狮对他的信心也会大大增加。
所以,不要对开发团队说他们要做什么,展示给他们依据。
2、提出问题,而不是解决方案
作为一名产品经理,整理和优先排序解决哪些问题是你的职责,但最终的出解决方案不是你的工作。优秀的产品经理总是带着问题和困难去找攻城狮,还会仔细听取他们提出的解决方案或折中方案。
3、频繁而清晰的沟通
有效的沟通或许是你能让攻城狮的工作轻松一些的首要利器了。这意味着,将他们纳入到产品设计过程中,确保他们明白为什么要开发某个特定功能,以及清晰地定义需求和优先次序。做一些简单但可理解的细节说明,会有助于攻城狮按时按质交付产品,也才使得你按计划上线产品成为可能。
作为一名产品经理,对于你的团队在不同的时间点都需要哪一些信息必须有良好的直觉。在他们找你之前就做好准备会使你的开发流程更加顺利。
4、懂得何时说不
记住,你是优秀用户体验的监督者。一名优秀的产品经理会努力让产品尽快上线,但对于一点影响产品质量的细节会非常忌讳。
你的攻城狮会默认你很自觉–会砍掉那些表现不如预期的功能,当数据说明现实与期望不匹配时,会去调整方式。他们指望着你能确保他们开发出来的功能是用户想要和需要的。攻城狮都很欣赏那些敢于说不的产品经理。
5、消除阻碍
产品经理另一大职责就是协商,跟管理、市场、设计以及其他部门协商。把你的攻城狮们从不必要的会议和其他日常琐碎中解脱出来。如果他们参加会议的意义很重大,那么你需要根据其对生产力的影响去做决定。
尽最大努力去确保你的开发团队能得到他们需要的资源。这可能是任何形式的努力:为了让团队更高效而去适当拉快开发进度,在开发落地之前确定设计材料已经到位,或是设计理念是经过原型验证了过的。敏捷开发团队中着可能有点难,但熟能生巧。
每天结束的时候,攻城狮要能够相信他们的产品经理。博得这种信任的一种方式便是证明你们在朝着同一个目标一起奋战。这也许是你在版本更新的半夜跑出去为团队取披萨,或者挡住一些其他团队提出的琐碎需求。产品经理要和攻城狮匍匐在同一个战壕里,随时准备为他们而战或同他们一起战斗,挽起袖子干活。
你的攻城狮会很感激你的。