前端不止:请告诉我,你要什么样的图标

有一个英语成语叫做一画胜千言(A picture is worth a thousand words),不知道大家有没有听过?它是指的是一张静态的图片就可表达一个复杂的概念或者与一个主题相关的图片有时比起详细的解释,能够更有效的描述有关主题。- “一画胜千言”维基百科

如果我们要用一句话来说明图标的作用,没有比这个成语更适合的词了。本篇文章,我们就来聊聊关于图标的一些事情。

一个图标的生命周期(工作流程)

关于图标的生命周期,在我个人所经历的开发项目中,有以下两种:

第一种方式:图标库(选择阶段) -> 图标使用(开发阶段)

第二种方式:图标设计(设计阶段) -> 图标导出(沟通阶段) -> 图标使用(开发阶段)

一般来说,小公司或者独立开发者会采用第一种工作流程。而大型组织或公司因为拥有更完善的团队和资源,一般会采取第二种方式,能够获得更多自主权和建立企业VI(Visual Identity,企业视觉识别)的能力。

但无论哪种方式,都包括两个角色:设计师和Web开发。只是在第一种工作方式中,设计师是不可见的。

图标的设计和使用

设计阶段通常是由不了解Web开发的设计师们来完成的,他们会根据产品的需要,绘出满足需求的图标,然后交给Web开发人员使用。

(ThoughtWorks官网“Contact with us”图标)

为什么要先介绍图标的使用,而一笔跳过导出过程呢?原因很简单,因为我们需要先知道服务的对象是谁,才知道如何正确的为它服务。

常见的三种图标的使用方式

1.使用图片

直接将设计师画好的图标,以PNG格式的图片一个个分离导出,这是最直观的图标打包方式。

(FlatIcon图标)

它的优点是:

  • 能够使用彩色的图标
  • 能够支持大部分浏览器

缺点是:

  • 图标大小是固定的(不能根据场景自由缩放)
  • Retina屏幕需要两倍图

开发人员拿到这样的图标,通常需要先将其合成为一张图片,以方便制作雪碧图,这个过程可以由开发人员自己完成,也可以由设计师来做(设计师可以根据源文件中心导出一张包含所有图标的PNG文件制作)。

制作雪碧图的工具有很多,我比较常用的在线雪碧图工具是:Sprite Cow,或者是NodeJS平台下的构建工具插件,如:webpack-spritesmith

2.直接使用svg

使用SVG(可缩放矢量图形),W3C标准是最被看好的Web端图形解决方案。它能提供如裁剪路径、Alpha通道、滤镜效果等复杂渲染能力,具备传统图片没有的矢量功能,还可以被记事本等阅读器、搜索引擎访问。

设计师可以轻松的在设计绘图软件(AI,PS)的帮助下导出SVG格式的图标/图片。

但目前,国内svg还没有被非常广泛的使用,原因在于兼容性不足,不能够很好的兼容旧的IE版本和一些Android原生浏览器。

(Can I use svg?)

上图为百度对2017年前三个月的浏览器使用进行的统计,目前国内还有超过20%的用户仍在使用IE8,9甚至是IE7。

3.IconFont

IconFont是目前最为流行的图标解决方案,顾名思义,它就是字体文件,你可以用任何一个字体编辑工具打开它,如果你打开某一个查看,就会发现它就是一些路径,这些路径可以用AI,PS,Sketch等软件来绘制。

IconFont的优点在于能够用CSS控制样式,无限缩放而不失真,支持IE7+,兼顾屏幕阅读器,不过缺点是不能支持彩色图标(拥有多种颜色的图标)。获得IconFont的方式也很简单,设计师将图标通过AI/PS转成SVG文件,然后由开发人员通过工具(在线或者本地)转换为IconFont,比如:国外的icomoon.io,国内的iconfont.cn,开源构建工具插件有gulp-iconfont等等。

产生适合Web开发的图标

“产生适合Web开发的图标”是我们本篇文章要关注的重点。

1.使用图片的方式

如果开发人员直接使用图片,则相对简单,设计师只需要针对普通屏幕和Retina屏幕准备两套图(单倍图和两倍图)。

以国内某著名的中文小说阅读网站为例,会针对不同的设备使用不同倍数的logo图片,以保证在如Retina屏幕下的清晰度。

.logo-wrap .logo a {
    display: block;
    width: 219px;
    height: 52px;
    background: url(/qd/images/logo.dbed5.png) no-repeat;
}

@media not all, not all, (-webkit-min-device-pixel-ratio: 1.3), not all, (min-resolution: 1.3dppx) {  
    .logo-wrap .logo a {
        background: url(/qd/images/logo3x.fd980.png) no-repeat;
        background-repeat: no-repeat;background-size: 217px;
    }
}
)

2.使用SVG

关于转换成SVG,这里就要引荐一下Sara Soueidan在Generate London 2015 Conference上的演讲《Sara Soueidan: SVG for Web Designers (and Developers)》(YouTube视频需要翻墙),如果不方便,Sara Soueidan有一篇博客《Tips for Creating and Exporting Better SVGs for the Web》更详细的讲解了关于SVG导出的内容,当然,还有一篇国内的翻译文章《创建和导出SVG的技巧》,最后再推荐一篇Adobe工程师michael chaize写的关于AI导出SVG的文章《Export SVG for the web with Illustrator CC》

在上述资料中,我觉得看视频更直观,顺便领略一下这位优秀的阿拉伯女性前端开发工程师(兼自由作家和演讲人)的风采。

博客和视频中谈到了多个点导出SVG需要注意的地方,由于篇幅限制,这里简单描述三个tips:

1. 选择适合绘画的画板

你有在网页上嵌入过SVG吗,给它指定一个高度和宽度,然后发现它其实比你指定的尺寸要小?开发人员常常会遇到这样的问题。

一般来说,这是因为SVG视窗中有一定大小的白色空白空间。视窗是按照样式表的指定尺寸显示的,但是它里面有额外的空白——在图形周围——使得你的图片看起来好像“缩水”了,因为这块空白在视窗里面是占空间的。为了避免这种情况,你需要确保你的画板是刚刚好能容纳里面的图像的,不要大太多。

画板的尺寸就是导出的SVG视窗的尺寸,所有画板上的空白最终都会变成视窗中的白色空白。

对于没有AI工具的开发,可以在下面的SVGO优化选项中选择“Prefer viewBox to width/height”。

2. 选择合适的导出选项

上图展示的选项是推荐的生成适合Web使用的SVG。如果你不想使用Web字体,可以选择把文本转换成轮廓。

如果SVG中包含大量的文字,这个选项output fewer tspan elements可以在很大程度上降低svg的大小。

3. 优化SVG

通常是建议在把SVG从图形编辑器中导出后,再用单独的优化工具来进行优化。比如:删除无用Comments和Metadata,简化代码,简化单个路径等。推荐的第三方工具:NodeJS工具svgomg,AI插件SVG-NOW,Sketch插件Svgo-compressor等,请参考Sara Soueidan的文章《Useful SVGO[ptimization] Tools》

3. IconFont

前面提到IconFont一般是由SVG通过工具转换而来,而如果开发最终需要使用IconFont来展示图标,那么对于导出的SVG有一些特殊要求。我在本文的前面一小节,已经介绍了几款IconFont的转换工具,每一款工具都有详细的文档来说明SVG绘制的规则,尽管不尽相同,但有一些基本原则是一致的:

  1. 将文字转换为路径
  2. 不可以使用图片(字体只是路径)
  3. 修剪画板(trimming to art boundaries)(前面已经介绍过)
  4. 将描边转化为闭合图形
  5. 简化无用的节点
  6. ……

更多关于IconFont的绘画规则,请参考:Iconfont.cn文档Icomoon文档gulp-iconfont文档fontello文档。

及时和频繁的沟通

Sara Soueidan说过一句话:“设计师和开发者应该成为好朋友”。

我们今天的话题正好涉及到这两个角色,也许你会觉得它们俩似乎有点“八竿子打不着”,但其实不是。请看下面这张图,敏捷的开发过程中不同角色共享职责,那么设计师和开发也不例外。

(敏捷开发中不同角色共享职责)

在ThoughtWorks工作,你会发现不少设计师懂HTML,CSS,甚至如何用Chrome查看元素,同时有不少开发对设计也颇有研究和兴趣。而我们的设计师和开发人员会坐在同一张桌子上一起完成工作,以保证及时和频繁的需求沟通和合作。

至于“设计师和开发者应该成为好朋友”,作为一名Dev,我就跟好多设计师都是朋友(至少我是这么认为的)。

而为了更好的做到沟通顺畅和职责共享,还出现了一种新(相对较新)的角色UI Dev,如下图。不过,关于这个角色的定义众说纷纭,我们就不在这里细聊了。

(UI Developer(参考自Stack Overflow答案))

结尾

在本篇文章中,我们谈了图标的三种使用方式:图片、SVG、IconFont,而它们也只是图标这个话题的冰山一角。虽然篇幅很短,但尤其重要的是,保证团队中设计师和开发人员便捷的协作工作,一起找到满足团队需求的解决方案,才是保证图标质量的关键。


更多精彩洞见,请关注微信公众号:思特沃克

Share

前端不止:Retina屏幕下两倍图

所见不一定即所得

眼睛是心灵的窗户,也是蒙蔽你的一种途径。

假设,我给你一张图片,你觉得肉眼可以观察到全部的细节吗?

屏幕上一张清晰的图片

肉眼在屏幕上看到图片的清晰度由三个因素决定,一是图片像素本身是否精细,二是屏幕分辨率,三是屏幕大小。

我们来逐步分析它们之间的关系:

屏幕分辨率

屏幕分辨率也就是设备分辨率,设备像素,它是物理的像素,比如,新的iPhone7,屏幕分辨率是1334 x 750像素分辨率,326 ppi。

图像大小

如果你学过《数字图像处理》这门课,那你对下面的解释就是非常熟悉了。

位图是由像素(Pixel)组成的,像素是位图最小的信息单元,存储在图像栅格中。每个像素都具有特定的位置和颜色值。按从左到右、从上到下的顺序来记录图像中每一个像素的信息,如:像素在屏幕上的位置、像素的颜色等。位图图像质量是由单位长度内像素的多少来决定的。单位长度内像素越多,分辨率越高,图像的效果越好。

假设,以上这个logo的图像大小是1334 x 750像素和iPhone7屏幕分辨率一样,那么,一位图像素对应的就是一个设备像素,这就是会是一个完全保真的显示。因为一个位置像素不能进一步分裂,我想这一点应该大家非常容易理解,也就是一个萝卜一个坑。

屏幕分辨率和屏幕尺寸

相信大部分人对上面这个设置肯定特别熟悉,有些人可能对XP,甚至98系统的样式更熟悉(一不小心暴露了年龄),在Windows系统下,提高屏幕分辨率一般都需要提高屏幕尺寸。

因为在固定屏幕的情况下,提高屏幕分辨率(如上图),图像和文字显示目标会相应缩小,原因是系统并不会自动根据屏幕尺寸和分辨率关系相应的调整文字和图标的大小,这是Windows系统自身的行为。

我相信,如果家里有年长的人使用电脑,肯定屏幕分辨率调的很低,因为这样文字和图标才会比较大,我家06年买的台式机就是这样。

也因此,我们很容易有一个错觉,那就是屏幕越大,分辨率就能越大(在单位面积内像素数量固定的情况下,尺寸越大,单个屏幕拥有的像素就越多,分辨率自然就越大)。

直到,苹果Retina屏幕的出现,原来小屏幕也可以拥有大分辨率。

PPI的概念

PPI,像素密度,即每英寸所拥有的像素数目(比如:上面iPhone 7的PPI是326),PPI数值越高,代表显示屏能够以越高的密度显示图像,画面的细节就会越丰富。

以Retina屏幕为例,它并不是像普通显示器那样通过增大尺寸来增加分辨率,而是靠提升屏幕单位面积内的像素数量,即像素密度来提升分辨率,这样就有了高像素密度屏幕。

根据上面的分析,分辨率提升了,那么图标和文字尺寸就会变小,但是Mac的操作系统不同,它自动采取相应的模式(如Mac下的HiDPI)进行适配,将缩小后的字体(苹果一直采用矢量字体)和图标重新放大,这样苹果用了更多的像素数来显示同样的内容,所以显示尺寸仍然不变。

苹果将“高像素密度屏幕”的概念营销出一个专业的术语“Retina”,将其称为双密度显示,声称人类的肉眼将无法区分单个像素。

当一个显示屏像素密度超过300ppi时,人眼就无法区分出单独的像素。这也是讲:显示设备清晰度已达到人视网膜可分辨像素的极限。因此,行动电话显示器的像素密度达到或高于300ppi就不会再出现颗粒感,而手持平板类电器显示器的像素密度达到或高于260ppi就不会再出现颗粒感,苹果电脑Mac的Retina显示器像素密度只要超过200ppi就无法区分出单独的像素。

好,说了这么多,都是谈屏幕的问题,貌似和前端开发没有什么关系,我又不是要买新手机(呵呵),那么现在,我们现在来谈谈前端的问题。

Web中的像素(CSS像素)

CSS像素是一个抽象概念,设备无关像素,简称-“DIPS”,device-independent像素,主要使用在浏览器上,用来精确的度量(确定)Web页面上的内容。

在标准情况下一个CSS像素对应一个设备像素。

.box {
  width: 200px;
  height: 300px;
  font-size: 12px;
}

上面的代码,将会在显示屏设备上绘制一个200×300像素的盒子,在标准屏幕下,它占据的就是200×300设备像素。但是在Retina屏幕下,相同的div却使用了400×600设备像素,保持相同的物理尺寸显示,导致每个像素点实际上有4倍的普通像素点。

对于图片来说也是如此:

这个时候,屏幕会怎么处理呢?其实,有点类似图像软件的放大图片功能,采用自有的算法(图像处理算法)计算放大方式。只不过,这里是苹果Retina屏幕的计算方法,一个CSS像素点实际分成了四个,造成颜色肯定会存在偏差(非全保真的显示),于是,我们看上去就变得模糊了(特别是图片,非常的明显)。

开发当中遇到这样的事情,我们应该怎么处理呢?这时,我们需要引出devicePixelRatio的概念。

devicePixelRatio设备像素比

window.devicePixelRatio是设备上物理像素和设备独立像素(device-independent pixels (dips))的比例。

公式表示就是:window.devicePixelRatio = 物理像素 / dips

  • 普通密度桌面显示屏的devicePixelRatio=1
  • 高密度桌面显示屏(Mac Retina)的devicePixelRatio=2
  • 主流手机显示屏的devicePixelRatio=2或3

举例说明,一张100×100的图片,通过CSS设置它{ width:100px; height:100px }。在普通密度桌面显示屏的电脑上打开,没有什么问题,但假设在手机/或者Retina屏幕的Mac,按照逻辑分辨率来渲染,他们的devicePixelRatio=2,那么就相当于拿4个物理像素来描绘1个电子像素。这等于拿一个2倍的放大镜去看图片,图片可能因此变得模糊。

代码如何解决呢?

原理我们明白了,那么从代码层面,我们应该如何实现呢?

一个常见的做法是把图片换成200×200的,CSS宽高不变,仍然是{ width:100px; height:100px },这样,CSS宽高换算成物理像素是200×200,图片也是200×200,就不会变糊了。可以采用媒体查询和JS操作的方式

CSS Media Queries

#element { background-image: url('hires.png'); }

@media only screen and (min-device-pixel-ratio: 2) {
    #element { background-image: url('hires@2x.png'); }
}

@media only screen and (min-device-pixel-ratio: 3) {
    #element { background-image: url('hires@3x.png'); }
}

JS查询

retinajs库

是不是适配Retina屏幕所有的图片都需要切换呢?

不是,一般情况下,不需要针对网站上的所有图片都提供两个版本(非Retina屏幕和Retina屏幕),大部分图片缩放并不会太多的影响用户的体验。

常常需要被处理的图片有:网站的logo、彩色图片图标,因为他们的图像大小都偏小,在Retina上物理像素放两倍显示就会出现模糊情况,这个时候,你就需要通过媒体查询或者JS操作来替换图片。

最后

眼睛是心灵的窗户,也是蒙蔽你的一种途径,带上知识的眼镜,将世界看个清楚。


参考资料:

  1. http://www.w3cplus.com/css/towards-retina-web.html
  2. http://www.jianshu.com/p/bb76c606f0b4
  3. https://developer.mozilla.org/zh-CN/docs/Mobile/Viewport_meta_tag
  4. http://caniuse.com/#search=devicePixelRatio
  5. https://www.web-tinker.com/article/20590.html

更多精彩洞见,请关注微信公众号:思特沃克

Share

你听说过“风格指南驱动开发”吗?

从“敏捷”下手

“今天,客户的UX又给我邮件了一版新的设计(PDF文件),改动不大,无非就是这个高度再调高点、那个宽度再调小些、这里用粗体、那边用18px的字体,可以参考以前做的手风琴组件的body部分,还有就是,顺便把手机版的样式截个图发过来?”

我:“能告诉我宽度和高度的具体值吗?那个手风琴组件可以在哪个页面找到?”

另一个开发告诉我:”先凭感觉做,然后再找UX面对面的按照要求一个个过。“

我:”好,面对面谈,总比邮件来回要快些。“

UX答复我:”面对面谈可能没有时间,你能先做一个粗略的版本吗?我先看看,然后给你反馈。等你做完所有的部分,我们再约个时间一起过”

即便心里在暗骂(等我做完了,你又跟我说这个不对,那个不对?)嘴上还是说了,“可以。”

然后,我问QA:“有测试环境可以部署我的新代码吗?没有完全做完,但是要给UX看效果。”

QA说:“有,但是部署完估计要1个多小时。”

我看了下时间,再过一个小时,客户UX就下班了,要得到他/她的反馈,估计得明天了!

当我把这个故事讲给别人听的时候,一般会得到两个回复:

  1. 哎呀,跟我们一样,痛苦的很
  2. 你们怎么这么不敏捷?

我无法否认这两个说法,很痛苦,也确实不够敏捷。但为我们提供了一点点线索——敏捷。

1-agile

敏捷宣言中强调:个体和互动高于流程和工具,工作的软件高于详尽的文档。

上面的故事很明显并不满足敏捷的价值观,邮件和截图绝对不可能代表“个体和互动”,一个需要部署一个小时才能看到页面效果的应用也谈不上“可工作的软件”。

怎么破?引入“在线风格指南”

针对当前的痛点,想要破,需要做到至少下面三点:

  • 独立前端开发工作,让它与后端逻辑解耦(俗称“前后端分离”)
  • 建立“可工作的软件”来展示前端开发结果
  • 组件化的设计和开发流程

看到这三点,明白人可能会立刻联想到一个大家耳熟能详的前端开发框架:Bootstrap。没错,它作为一个优秀的前端开发框架,确实满足上面的要求,有许多不错的网站都将Bootstrap作为网站的前端骨架。

Bootstrap有两个特质非常吸引人,优秀的特性和组件和漂亮的开发文档

不过,今天我们不谈Bootstrap框架,我想来聊聊这个漂亮的开发文档,俗称“在线风格指南”。

相信大家对“风格指南”都不陌生,主要分两类:静态风格指南和动态风格指南。

静态风格指南也就是我们常见的静态设计文档,比如,由设计师提供的PSD/PDF等文件,文档中包含:调色板,字体,按钮,链接等等。

2-style-guide

(medialoot上的一个样例)

动态风格指南,也称为Living Style Guide(“活的”风格指南或者在线风格指南),它是一个包含风格指南的Web站点,就像Bootstrap开发文档一样。

3-living-style-guide

在这里,我们需要引入的是“在线风格指南”,而不是Bootstrap,这里的不同点在于:

  • 角色变化,我们从“风格指南”的使用者,变成了是它的拥有者、开发者和使用者。
  • 用户变化,它不再是开发文档,现在用户是UX、前端开发和BA(业务分析),在UX和BA的眼中看到的文档即最新实现结果,在前端开发眼中看到的代码即设计。
  • 侧重不同,不仅仅是基础组件,也具有更加偏业务的大型组件。

为什么还要组件化的设计和开发?

组件化的做法在当前的场景下,似乎有点顺其自然,特别是有Bootstrap作为前车之鉴。

我想大家都知道,前端开发其实有一个通病,即大量的JS、CSS和其他资源文件(字体文件、图标、图片),在没有很好的管理控制下,很容易导致资源的冗余,依赖关系复杂度增加、维护性降低、导致之后的开发难度变大。

和后端语言开发不同,比如,Java有包管理和类的支持,有一些常见(或者约定俗成)的实现层次,如:Controller、Service、Repository等;有框架和语言特性带来的优势,比如IOC、AOP、注解、继承、接口等,合理利用能够让代码职责单一,模块高内聚低耦合,接口化,可重用,易于测试等等。

而Web前端开发,由于涉及到的内容较广,又不太可能完全具备后端语言的优秀特性。所以,更加需要开发人员具有优秀的设计和管理思想,比如:CSS的合理命名和管理、有效利用CSS预处理器、JavaScript的模块化、框架的特性(比如AngularJS的依赖注入,指令)、以及组件化等等。

组件化其实是大型软件开发中的一个共识,特别是前端,在没有统一标准的前提下,大家都在不断的造轮子,有无数的人倒了下去,又有无数勇士踩着前者的尸体冲上来。也许是因为它确实能够具有一些非常优秀的特性:单一的职责、资源的高内聚、独立的作用域、可独立的测试、接口的规范化、可重用、可组合等。

4-folder-structure

我们项目的代码组织结构

从“风格指南”到“驱动开发”

总结一下前面的内容,“前后端分离”,“在线风格指南”,“组件化开发”,似乎我们只说到一些与开发相关的事情,其实不然。

“在线风格指南”被开发,UX、BA共享,合理的组件划分可以合理控制开发闭环,UX可以更快的看到设计实现的原型,提升团队成员沟通频率,BA可以方便的根据组件合理的编写Story(故事卡)。

当这三个角色都参与到这个过程当中时,我们就真正回到今天的正题“风格指南驱动开发”。

这是一个相当新的术语,但不是我发明的,如果要追溯的话,最早在公开场合中谈到这个概念的人应该是Nicole Sullivan,她在2014年9月的一次演讲《Components & SGDD》中提出(Nicole Sullivan目前在NPM这家公司,没错,就是那个NPM,做Product Manager & Design Manager)。

“风格指南驱动开发”尝试着:

  1. 让UX和前端开发更紧密的工作在一起,将设计与前端开发的工作闭环缩小,更快速的迭代产品原型。
  2. 将UI开发和业务系统分离开,业务系统不仅仅是指后端系统(不仅仅是前后端分离),也包括业务的Web系统。
  3. 让设计文档更加“灵活”,更加及时(up to date),更加一致(single source of truth)。
  4. 让前端资源管理更加规范,开发模式更加清晰。
  5. 让整个Web开发周期更加敏捷(Agile)。

它是一种前端开发和团队工作方式的实践。

工作流程 – 如何“驱动开发”?

5-sgdd-process

开发流程

怎么样的工作方式,才算“风格指南驱动开发”?其实,当团队拥有了“组件化的思想”和“在线风格指南”,“风格指南驱动开发”的整个过程其实是行云流水的,我简单描述一下:

1.挖掘到新的需求/特性

当新的需求出现时,UX开始进行页面设计,Living Style Guide会作为设计的参考文档。通常情况,设计师会查看已有的调色板、字体和其他基本元素或组件来组成新的页面布局。在组件化的思想和Living Style Guide的帮助下,BA和设计师都会尝试使用或者扩展现有的组件。

2.抽象成组件

一旦设计完成,BA、UX和开发会开始讨论如何把新的设计细分为独立的组件,哪些是已经存在可以重用的,哪些是新的需用新建或者扩展实现的。Living Style Guide作为桥梁帮助设计与开发进行沟通,促进双方的理解,确保实现的准确性。而抽象出来的组件,帮助BA划分任务和故事(Story),以便更加准确的估算“故事点”。

3.实现和文档化

此时,Living Style Guide是作为一个开发框架和设计试验场(playground)。

作为一个试验场(playground),允许你随时看到每一个开发出来的组件,作为开发框架,允许你快速开发,它和真正的产品实现完全隔离,这种隔离会鼓励你以更加抽象的方式创建组件,以便于让他们更容易被重用。

Living Style Guide的开发注重组件化的隔离和规范化的开发流程(套路清晰明了),我们常常会开发一些自动化的构建任务来帮助快速初始化一个组件需要的基本内容,只要开发人员对开发所需的前端技术栈有掌握,就能较快速的开发完成相应的组件。

而开发完成的组件在Living Style Guide中立刻变成“活的文档”,可以快速展示各种不同的组件应用场景,提供给UX和BA做审查(review)。

4.组件在产品应用中的热插拔

最后一步,就是将组件应用到真正的产品实现中(该产品代码应该是与Living Style Guide毫无关系的业务代码)。而对于Living Style Guide输出的结果,应该可以任意选择刚好满足需求所需要的组件,拥有足够的灵活性,才能实现它在产品应用代码中的热插拔。

如果还有第5步的话,那就是重复上面的4步,这就是“风格指南驱动开发”的完整流程。

留在最后的思考 – 它到底驱动了什么?

作为好奇宝宝的你们和我,当读完这篇文章,应该仍然在思考,它到底驱动了什么?

6-haoqi

也许你比较熟悉TDD和BDD,他们通过测试,驱动我们编写刚好满足测试和满足需求的实现,而测试反过来成为保护伞,在我们通过重构提升代码质量的同时,保证功能的安全性。

那么,“风格指南驱动开发”到底驱动了什么?也许,它驱动着我们尽最大可能去重新使用已有的组件(代码)和设计更通用的组件,也驱动着我们(开发、UX、BA)进行更加频繁的沟通,它驱动着BA(业务分析)书写更加合理的Story,也驱动开发进行更加合理的代码和资源的管理。

Share

我也想来谈谈HTTPS

安全越来越被重视

2014年8月份Google在官博上发表《HTTPS as a ranking signal》
表示调整其搜索引擎算法,采用HTTPS加密的网站在搜索结果中的排名将会更高,鼓励全球网站采用安全度更高的HTTPS以保证访客安全。

同一年(2014年),百度开始对外开放了HTTPS的访问,并于3月初正式对全网用户进行了HTTPS跳转。对百度自身来说,HTTPS能够保护用户体验,降低劫持/隐私泄露对用户的伤害。

而2015年,百度开放收录HTTPS站点公告。全面支持HTTPS页面直接收录;百度搜索引擎认为在权值相同的站点中,采用HTTPS协议的页面更加安全,排名上会优先对待。

“HTTP = 不安全”,为什么说HTTP不安全?

HTTP报文是由一行行简单字符串组成的,是纯文本,可以很方便地对其进行读写。一个简单事务所使用的报文:

1-http-message

HTTP传输的内容是明文的,你上网浏览过、提交过的内容,所有在后台工作的实体,比如路由器的所有者、网线途径路线的不明意图者、省市运营商、运营商骨干网、跨运营商网关等都能够查看。举个不安全的例子:

一个简单非HTTPS的登录采用POST方法提交包含用户名和密码的表单,会发生什么?

2-login-example

POST表单发出去的信息,没有做任何的安全性信息置乱(加密编码),直接编码为下一层协议(TCP层)需要的内容,所有用户名和密码信息一览无余,任何拦截到报文信息的人都可以获取到你的用户名和密码,是不是想想都觉得恐怖?

那么问题来了,怎么样才是安全的呢?

对于包含用户敏感信息的网站需要进行怎样的安全防护?

对于一个包含用户敏感信息的网站(从实际角度出发),我们期望实现HTTP安全技术能够满足至少以下需求:

  • 服务器认证(客户端知道它们是在与真正的而不是伪造的服务器通话)
  • 客户端认证(服务器知道它们是在与真正的而不是伪造的客户端通话)
  • 完整性(客户端和服务器的数据不会被修改)
  • 加密(客户端和服务器的对话是私密的,无需担心被窃听)
  • 效率(一个运行的足够快的算法,以便低端的客户端和服务器使用)
  • 普适性(基本上所有的客户端和服务器都支持这个协议)
  • 管理的可扩展性(在任何地方的任何人都可以立即进行安全通信)
  • 适应性(能够支持当前最知名的安全方法)
  • 在社会上的可行性(满足社会的政治文化需要)

HTTPS协议来解决安全性的问题:HTTPS和HTTP的不同 – TLS安全层(会话层)

超文本传输安全协议(HTTPS,也被称为HTTP over TLS,HTTP over SSL或HTTP Secure)是一种网络安全传输协议。

HTTPS开发的主要目的,是提供对网络服务器的认证,保证交换信息的机密性和完整性。

它和HTTP的差别在于,HTTPS经由超文本传输协议进行通信,但利用SSL/TLS來对包进行加密,即所有的HTTP请求和响应数据在发送到网络上之前,都要进行加密。如下图:
https-layer.png
安全操作,即数据编码(加密)和解码(解密)的工作是由SSL一层来完成,而其他的部分和HTTP协议没有太多的不同。更详细的TLS层协议图:
TLS.png
SSL层是实现HTTPS的安全性的基石,它是如何做到的呢?我们需要了解SSL层背后基本原理和概念,由于涉及到信息安全和密码学的概念,我尽量用简单的语言和示意图来描述。

SSL层背后基本原理和概念

介绍HTTPS背后的基本原理和概念,涉及到的概念:加密算法,数字证书,CA中心等。

加密算法
加密算法严格来说属于编码学(密码编码学),编码是信息从一种形式或格式转换为另一种形式的过程。解码,是编码的逆过程(对应密码学中的解密)。

5-jia-mi-suan-fa

对称加密算法

加密算法主要分两类:对称和非对称加密算法。在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。
6-dui-chen-jia-mi-suan-fa

但是对称加密算法有一个问题:一旦通信的实体多了,那么管理秘钥就会成为问题。

7-key-management
非对称加密算法(加密和签名)

非对称加密算法需要两个密钥:公开密钥(public key)私有密钥(private key)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密,这个反过来的过程叫作数字签名(因为私钥是非公开的,所以可以验证该实体的身份)。

他们就像是锁和钥匙的关系。Alice把打开的锁(公钥)发送给不同的实体(Bob,Tom),然后他们用这把锁把信息加密,Alice只需要一把钥匙(私钥)就能解开内容。

8-feiduichen

那么,有一个很重要的问题:加密算法是如何保证数据传输的安全,即不被破解?有两点:

1.利用数学计算的困难性(比如:离散对数问题)
2.加密算法是公开的,关键在于秘钥,密码学中有柯克霍夫斯基原则,即加密算法的安全性依赖的是密钥的保密而不是算法的保密,因此,保证秘钥的定期更换是非常重要的。

数字证书,用来实现身份认证和秘钥交换

数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息,使用的加密算法以及公开密钥的文件。

9-certificate

以数字证书为核心的加密技术可以对网络上传输的信息进行加密和解密、数字签名和签名验证,确保网上传递信息的机密性、完整性及交易的不可抵赖性。使用了数字证书,即使您发送的信息在网上被他人截获,甚至您丢失了个人的账户、密码等信息,仍可以保证您的账户、资金安全。(比如,支付宝的一种安全手段就是在指定电脑上安装数字证书)

身份认证(我凭什么信任你)

身份认证是建立每一个TLS连接不可或缺的部分。比如,你有可能和任何一方建立一个加密的通道,包括攻击者,除非我们可以确定通信的服务端是我们可以信任的,否则,所有的加密(保密)工作都没有任何作用。

而身份认证的方式就是通过证书以数字方式签名的声明,它将公钥与持有相应私钥的主体(个人、设备和服务)身份绑定在一起。通过在证书上签名,CA可以核实与证书上公钥相应的私钥为证书所指定的主体所拥有。
certificate.png

了解TLS协议

HTTPS的安全主要靠的是TLS协议层的操作。那么它到底做了什么,来建立一条安全的数据传输通道呢?

TLS握手:安全通道是如何建立的

TLS-handshake-protocol.png

0 ms
TLS运行在一个可靠的TCP协议上,意味着我们必须首先完成TCP协议的三次握手。

56 ms
在TCP连接建立完成之后,客户端会以明文的方式发送一系列说明,比如使用的TLS协议版本,客户端所支持的加密算法等。

84 ms
服务器端拿到TLS协议版本,根据客户端提供的加密算法列表选择一个合适的加密算法,然后将选择的算法连同服务器的证书一起发送到客户端。

112 ms
假设服务器和客户端协商后,得到一个共同的TLS版本和加密算法,客户端检测服务端的证书,非常满意,客户端就会要么使用RSA加密算法(公钥加密)或者DH秘钥交换协议,得到一个服务器和客户端公用的对称秘钥。

由于历史和商业原因,基于RSA的秘钥交换占据了TLS协议的大片江山:客户端生成一个对称秘钥,使用服务器端证书的公钥加密,然后发送给服务器端,服务器端利用私钥解密得到对称秘钥。

140 ms
服务器处理由客户端发送的秘钥交换参数,通过验证MAC(Message Authentication Code,消息认证码)来验证消息的完整性,返回一个加密过的“Finished”消息给客户端。

在密码学中,消息认证码(英语:Message Authentication Code,缩写为MAC),又译为消息鉴别码、文件消息认证码、讯息鉴别码、信息认证码,是经过特定算法后产生的一小段信息,检查某段消息的完整性,以及作身份验证。它可以用来检查在消息传递过程中,其内容是否被更改过,不管更改的原因是来自意外或是蓄意攻击。同时可以作为消息来源的身份验证,确认消息的来源。

168 ms
客户端用协商得到的堆成秘钥解密“Finished”消息,验证MAC(消息完整性验证),如果一切ok,那么这个加密的通道就建立完成,可以开始数据传输了。

在这之后的通信,采用对称秘钥对数据加密传输,从而保证数据的机密性。

到此为止,我是想要介绍的基本原理的全部内容,但HTTPS得知识点不止如此,还有更多说,现在来点干货(实战)!!

那么,教练,我想用HTTPS

anxi.jpg

选择合适的证书,Let’s Encrypt(It’s free, automated, and open.)是一种不错的选择https://letsencrypt.org/

ThoughtWorks在2016年4月份发布的技术雷达中对Let’s Encrypt项目进行了介绍:

从2015年12月开始,Let’s Encrypt项目从封闭测试阶段转向公开测试阶段,也就是说用户不再需要收到邀请才能使用它了。Let’s Encrypt为那些寻求网站安全的用户提供了一种简单的方式获取和管理证书。Let’s Encrypt也使得“安全和隐私”获得了更好的保障,而这一趋势已经随着ThoughtWorks和我们许多使用其进行证书认证的项目开始了。

据Let’s Encrypt发布的数据来看,至今该项目已经颁发了超过300万份证书——300万这个数字是在5月8日-9日之间达成的。Let’s Encrypt是为了让HTTP连接做得更加安全的一个项目,所以越多的网站加入,互联网就回变得越安全。

Share

节省你的时间!Gradle Spring Intellij Idea下热部署实现“敏捷”开发

#百万奖金有奖问答#程序员的什么最值钱?

是他/她们的聪明才智,简洁代码,惊艳的颜值,还是无与伦比的手速,都不是,是宝贵的时间。

如果,你有机会尝试纯前端的web开发,比如:NodeJS FrontEnd。你一定会非常喜欢watch这个功能,可以自动检测文件变化,然后自动完成编译和部署。

#另个一个百万奖金问题# 如果在Java/Spring环境下,也能做到Watch,该有多好!!!

那么,本篇文章不是什么高大上的抽象理论,也不是什么改变行业观念的大道理,从实际出发,就是要给你这个答案,只是希望在这三个环境(Gradle、Spring、Intellij Idea)下开发时,来给你节省一点点的时间。

什么是热部署:

It is the ability to change ON-THE-FLY what’s currently deployed without redeploying it. Hot deployment is VERY hot for development.The time savings realized when your developers can simply run their build and have the new code auto-deploy instead of build, shutdown, startup is massive.

解决方案:

针对Spring的版本不同,将实现热部署的解决办法分为两个不同的方案:Spring3下Spring MVC + Jetty 和 Spring Boot,最后在给大家分享一个Gradle的Watch插件,来实现watch的功能。

Spring3下Spring MVC + Jetty的实现方式

首先来回顾一下Gradle的Jetty插件,Jetty插件提供两个重要方法:jettyRun和jettyRunWar。jettyRun会将一个已暴露(解包的)的web应用部署到嵌入式Jetty Web容器中。它不需要将web应用打包成一个war文件,目的是为了节省部署时间。jettyRunWar正好相反,是将一个War包部署到Web容器中。

jettyRun的好处是,你可以改变静态文件和JSP文件,而不需要重新启动服务器。

但是即便如此,对于日常开发还是不方便,因为开发过程中改动非常多的还有Java文件和资源配置文件,所以真正需要的是热部署。

jettyRun的Gradle API文档中有这么一句话:

Once started, the web container can be configured to run continuously, scanning for changes in the project and automatically performing a hot redeploy when necessary. This allows the developer to concentrate on coding changes to the project using their IDE of choice and have those changes immediately and transparently reflected in the running web container, eliminating development time that is wasted on rebuilding, reassembling and redeploying.

这句话简单总结就是Jetty提供实现热部署的特性,开发人员只需要专注于编写代码,减少重新构建,重新组装和重新部署所浪费的时间。那么如何配置来实现Jetty所提供的热部署呢?

Jetty插件提供了两个属性:

reload:The reload mode, which is either “automatic” or “manual”.

scanIntervalSeconds:The interval in seconds between scanning the web app for file changes. If file changes are detected, the web app is reloaded. Only relevant if reload is set to “automatic”. Defaults to 0, which disables automatic reloading.

读完上面两段,说明默认scanIntervalSeconds的配置是不支持自动重新载入变化文件的。所以,你需要修改Jetty插件的默认配置:

buildscript {  
    repositories { jcenter() }  
    dependencies {  
        classpath "org.springframework.boot:spring-boot-gradle-plugin:1.2.7.RELEASE"  
        classpath 'org.springframework:springloaded:1.2.4.RELEASE'  
    }  
}  
apply plugin: 'idea'  


idea {  
    module {  
        inheritOutputDirs = false  
        outputDir = file("$buildDir/classes/main/")  
        testOutputDir = file("$buildDir/classes/test/")  
    }  
}

然后运行gradle jettyRun启动jetty容器,修改Java类/资源文件(resource下地文件),然后去页面验证变化,结果是没有变化。为什么?

原来,jetty监听的是build目录下的class和resource文件的变化,而不是源代码文件变化,也就说源代码内容改变了,但class文件没有变化,那么不会自动触发jetty重载变化文件,那么该怎么办?

另起一个窗口,手动运行一次gradlew compileJava或者gradlew processResources命令。就是这么简单,你已经实现了Spring 3下SpringMVC + Jetty的热部署了。

Spring Boot的实现方式 – Spring Reloaded

现在,越来越多的Spring应用直接使用Spring Boot作为框架,我司也是如此,Spring官方也意识针对热部署问题,提供了解决方案:Spring Reloaded。项目地址:spring-projects/spring-loaded · GitHub

Spring官方也很有意识,专门有一章节来介绍Hot Swapping: 78. Hot swapping

它告诉如何将Spring Loaded和Gradle以及IntelliJ结合起来:

默认情况下,IntelliJ将Java类和资源文件编译到一个跟Gradle不同的位置,这会导致Spring Loaded监控失败,所以使用idea模块修改编译输出位置和Gradle一样,而且IntelliJ必须配置跟命令行Gradle任务相同的Java版本,且springloaded必须作为一个buildscript依赖被包含进去。

buildscript {  
    repositories { jcenter() }  
    dependencies {  
        classpath "org.springframework.boot:spring-boot-gradle-plugin:1.2.7.RELEASE"  
        classpath 'org.springframework:springloaded:1.2.4.RELEASE'  
    }  
}  
apply plugin: 'idea'  


idea {  
    module {  
        inheritOutputDirs = false  
        outputDir = file("$buildDir/classes/main/")  
        testOutputDir = file("$buildDir/classes/test/")  
    }  
}

依赖下载完成之后,正常启动Spring Boot Run。

官方文档中存在的问题:

官方文档的springloaded版本是1.2.0.RELEASE,这个版本有问题,会出现:org.springsource.loaded.jvm.JVM : Problems copying method. Incompatible JVM? 报错。而且,如果你自己有仔细阅读官方文档的代码,你会发现官方少了一行testOutputDir的配置。官方文档上没有设置testOutputDir,这就会导致,intellij编译代码时,输出test下面的class到了out目录的main中。从而改变了Gradle默认的输出测试路径(main和test分开的),所以最好还是手动配置一下。

此时,如果你在应用启动的时候修改了Java代码,只需要点击Intellij的编译按钮,重新编译代码即可。

导致集成测试的问题:

由于这样配置之后,导致Intellij上进行make project,输出到build/classes下。这样做会导致集成测试有一个问题,运行集成测试的时候,我们常常需要使用properties文件和xml文件。如果之前运行过gradle build,而build目录下没有被clean,则gradle默认会将properties文件和xml文件放在build/resources下,这与Intellij的行为不同。而运行测试的时候,本来Intellij会默认先跑make project,但是由于build/classes已经有文件了,所以就skip了,于是导致Intellij找不到resource文件,因为Intellij要求的resource路径和gradle构建时输出的路径不同。

解决办法:手动点击Project Rebuild 或者 配置Intellij的Junit

可以手动点击Project Rebuild,它会清理Intellij的输出目录(也就是当前Gradle的classes目录),然后在make。又或者改变Intellij中Junit的配置,Junit会在运行测试之前,先跑make,可以然它在make之前先跑Gradle的clean任务。

Gradle下Watch大法实现方式 – Watch插件

最后,也是最关键的一点,Watch插件,通过上面的方法,实际已经实现热部署,只不过,每次变化都需要手动的触发compile和processResource,已经比重启服务器快了很多。

##但这还不够,程序员就应该学会偷懒,我们应该自动化一切可以自动化且应该自动化得东西。##

在github上有一个关注度并不是那么多的Watch插件:bluepapa32/gradle-watch-plugin · GitHub

他可以帮助你实现,任何文件变化的监控,并且在监控到变化之后,自动执行相应的Gradle任务。我们以Spring3下SpringMVC + Jetty方式为例:

buildscript {  
    repositories {  
        jcenter()  
    }  
    dependencies {  
        classpath 'com.bluepapa32:gradle-watch-plugin:0.1.3'  
    }  
}  

apply plugin: 'watch'  

watch {  
    java {  
        files files('src/main/java')  
        tasks 'compileJava'  
    }  
    resources {  
        files fileTree(dir: 'src/main/resources', include: '**/*.xml')  
        tasks 'processResources'  
    }  
}

自动在Java或者resources文件有变化时,执行compileJava和processResources。那么,在Spring Boot下也是类似的一个配置。

自此,你再也不用重复可恶的三套动作以及无限的等待服务器启动:ctrl + c -> up key ->enter -> wait,专心实现Feature和修bug。

请让我安静地做一个只用写代码的程序员!

原文地址:

  1. Gradle Jetty和Gradle Watch插件实现热部署 – NO END FOR LEARNING
  2. 基于Gradle和Intellij的Spring Boot热交换 – NO END FOR LEARNING
Share