【第2588期】转转智能代码平台神笔马良的研发与实践
前言
低代码、智能代码,对效率的探索从未放弃。今日前端早读课文章由转转@张所勇分享,由公号:前端之巅授权。
@张所勇:转转平台方向前端负责人。负责转转平台、微信生态及多个垂直业务,同时负责转转前端智能化项目;所负责转转小程序接入微信钱包项目最高千万级 DAU,参与全部小程序技术架构项目;曾负责性能优化方案:OPR 离线预渲染方案。作为智能代码项目神笔马良的发起人和负责人,完成绝大部分算法策略和数据处理工作。
正文从这开始~~
转转智能代码项目神笔马良自立项至今已有近两年时间,最初立项时以 Sketch2Code 为思路缘起,本着提升前端开发效率的目的进行了多个版本的迭代,目前该项目已经在转转内部进入公测阶段,并将全面尝试替代 Sketch 的切图工作。
神笔马良相比业界其他的智能代码项目,更看重生成代码的真正可用性和可维护性。我们在结构、样式、命名、组件拆分、节点循环等多个方面进行了独特的思考与探索,本次我将重点为大家分享神笔马良落地实践的案例,同时提供一些前端智能化技术难点的解决思路。
神笔马良项目
转转是从 2019 年开始进入到智能代码领域,至今已经研发了两年多时间,我相信可能很多 D2C 的开发者都差不多是这个时期进入到该领域的,并且大家的思路起源也大多是微软的 Sketch2Code 这个实验性项目。
项目历程
先来分享一下神笔马良项目的发展历程。转转的前端工程师规模在 100 人左右,作为一个中型厂商,我们做 D2C 的过程还是非常艰辛的。
项目历程
这个项目是 2019 年 7 月份开始做的,用了两个月的时间实现了 1.0 的版本,也就是 D2C 的基本功能,可以把简单的设计稿转换成开发可用的代码,但是这个版本很快就被毙掉了,因为经过转转技术委员会的研讨时,大家觉得 D2C 这件事情不是转转这样规模的公司能干下来的,当时市面上也只有 imgcook 放出了消息,并且也没有经过大规模的验证使用。
其实这个可能是中小厂商做 D2C 都会面临的问题,公司的大背景决定了能不能做这个事情。接下来一直等到了 2020 年的中旬,D2C 这个事终于又开始立项了,这之后大概一年左右的开发我们终于进入了 v2.x 的时代。中间经历了持续不断的内测和公测,并且到目前为止这个产品其实还没有完全对转转所有的前端工程师开放,而是一直在小规模的进行试用和迭代中。
所以这里我总结一下,中型公司怎么样去做 D2C 呢?(小型公司我就不建议做 D2C 了,不可否认它的成本非常高),我总结了这样一个公式:
一般而言,中型公司 D2C 的研发分成开发者和公司两个部分,开发者的信念加坚持,我认为可以起到 40% 的作用;公司层面的前瞻性和持续投入,会占到 60% 左右。
首先说说开发者层面,为什么说是信念 + 坚持呢?D2C 的开发者们可能都会有一种感触,这件事确实很不容易,因为很难找到最佳实践来参考。业界其他公司的产品也大都处在相似的阶段,很少有发布的具体算法细节(即使是在 GMTC 这种大型的会议上的分享,也难免比较笼统)。
比如我们一开始去做 D2C,第一步就是解析 Sketch 的数据,把 Sketch 那套 JSON 数据翻译成 CSS 的代码。这个过程最开始的时候就已经非常有挑战了,因为 Sketch 并没有提供一套数据表全集(只提供了一个字段枚举值表,能看到每个属性简单的英文含义和对应的值),所以在转译 CSS 的时候很痛苦,要不断地去猜和试。因此最开始的时候,我们基本上就是在黑暗中不断摸索,可能很多人在这个阶段就会放弃,但是只有能坚持下来才会有后面的故事,开发者对于成功的信念是必须贯通全程的。
其次是公司层面。我认为这对中型公司是一个非常大的考验,领导层面是一定要具有前瞻性的。假如我跟领导说“我用一年时间做一个 D2C 系统,能让前端开发省去切图环节,提效 20%”,但是领导能不能信任我,能不能相信这事儿能成,这都是很大的挑战。
另外,做 D2C 可能至少要投入两个高级工程师进去,同时至少需要一年的时间。这个成本对公司来讲就是几百万的人力成本,能否持续投入对于中型公司来说都是未知的。所以我建议,如果想去做 D2C,首先自己要有足够的精力和能力,其次一定要先在公司的层面沟通好。
架构演进
接下来我们一起来看看神笔马良的架构演进过程。
架构演进
最开始我们做了一个 Sketch 的插件(神笔)和一个 PC 系统(马良),其中 Sketch 插件做的是提取和解析 Sketch 数据、优化数据、处理图标图片等工作;插件把处理好的数据上传到服务器,服务器再返回给插件一个地址,设计同学就可以把这个地址给到 PM、QA、FE。在浏览器端,FE 同学也可以直接在马良里面上传设计稿,我们在浏览器端也完成了 Sketch 插件的所有逻辑。
后来我们把这个架构改了一下,增加了一个 Mac 服务器。
架构演进 -Mac 服务器
我们在这台 Mac 服务器里边装了一个 Sketch,同时做了一个数据自动处理的插件,把之前 Sketch 插件里边的全部逻辑都挪到了服务器里面去执行。现在用户侧的 Sketch 插件和浏览器上传只做一件事:把源文件传给 Mac 服务器。那为什么要这样搞呢?
Sketch 的插件机制并没有热更新或强制更新能力,只能提示更新。如果在用户侧 Sketch 插件里面做太重的逻辑,比较难做到准确更新;
在浏览器端我们实现了与 Sketch 插件端一样的逻辑,但这里面有个很大的挑战就是图标导出,在 Sketch 端只需要使用 export 方法即可导出图片;但在浏览器端,我们需要根据源文件中的点、线和样式信息,用 SVG 的方式绘制图标。由于 Sketch 描述图标的数据格式与 SVG 不一样,导致在面对复杂图标时很难准确绘制。
基于这两点考虑,我们设计了全新的架构,可以做到双端逻辑统一且用户侧逻辑最轻。但从架构角度来讲这其实并不是最好的,因为中心化的架构一定会有性能和负载的问题,于是我们在 Mac 服务器这个中心节点上设计了两种方案:
通过 Nginx 实现多台服务器负载均衡
每台服务器支持任务队列机制,根据服务器性能支持多个任务同时进行
由于我们的系统是内部系统,上传设计稿也并非是密集并发操作,所以在性能上暂时没有什么大问题。
关键技术难点探索与解决方案
之前业界可能有一种错误的认知,认为 D2C 这件事适合用来生成活动页面,不太需要关注代码质量,且不需要维护迭代,但我们从始至终的目标都是生成开发者日常在写的功能性页面。
开发者诉求
我们在研发过程中调研了开发者对于 D2C 的诉求,整个研发过程也都是围绕着开发者的诉求来进行的。
标注功能
马良系统的标注能力
首先我们实现了跟蓝湖一样的标注功能,让开发者即便不使用自动生成的代码,也可以使用马良系统。
循环节点
其次是循环节点的识别,这是做 D2C 很难跳过的一个入门问题。
循环节点识别
上面这张图的两个卡片是有一定差异的,开发者手写代码时,大概率会把它写成两个卡片循环起来,那在算法层面,如何来判定是循环节点呢?这里我们会用两种算法,结构相似度和样式相似度。
结构相似度
第一是用结构相似度。我们先分别提取树结构,然后通过树相似度算法算出两棵树的相似度:0.875。
样式相似度
接下来计算样式相似度。例如图书的封面,样式相似度是 1,就认为是一样的;标题前的标签部分,颜色和宽度都有一定差异,相似度会有一定的下降;最后经过每个叶子节点的相似度算法比较,两棵树的样式相似度为 0.85。最后经过一个权衡算法来判断两棵树为相似节点。
那么相似之后我们要如何去处理呢?
我们识别出来的相似节点一般会有两种情况。一是完全相同的情况:
样式完全相同
像上图这个例子,这四个卡片无论从结构,还是样式特征,都是完全相同的,我们把这类节点称为相同节点。相同节点的处理方式是:在模板层用循环的方式输出,在数据层把有用的数据提取出来,开发者得到这样的代码以后,直接去套接口数据就可以了。
第二个是相似的情况。回到刚才这个例子:
样式相似
我们这里是选取一个结构最复杂的节点作为模板节点,其他节点复制显示模板节点,并在界面上提示这是相似的节点,请开发者自行查看差异。我们为什么要进行这么“偷懒”的处理呢?
其实最开始我们是在模板循环输出和提取数据的基础上,把差异的部分通过模板条件判断来控制显示的,但后来我们发现随着卡片差异的增加,模板层面上的条件判断会变得十分复杂(设计师为了让开发同学了解卡片上各种可能的情况,会在不同卡片上展示不同状态),并且如果部分节点样式有差异的话,还需要通过动态 style 的方式来控制,导致开发者得到代码以后更加难以处理,最终我们回退到了一个最简单的方式上,把这一部分工作交给开发者来处理。
其实这也是在智能与人工的权衡和取舍,后面你也可以看到我们在算法加持下依然做了很多留给开发者手工处理的兜底策略。
UI 组件库识别
接下来,如何从设计稿里面识别出 UI 组件库的组件并且准确还原呢?我们的方案是这样的:
组件库识别
我们会先把组件库的原始设计稿上传到系统里并生成两个东西:
1、特征
这个其实是我刚才讲到的,结构相似度和样式相似度,我们提取出特征所需要的数据。
2、提取算法
这块我们会自动生成一部分,也会需要人工去维护一部分;就像图中的 Tab 组件,设计稿和标准组件库有一定差异:Tab 数量和间距都不一样,如何准确地提取出数据,这块需要一个提取算法,这是自动提取算法可以做到的。但是一些比较复杂的组件自动提取可能会有问题,所以对每个组件,我们允许手工去再附加一段数据提取的代码。
当我们把这些事情做完后,设计师上传一个新设计稿时,算法会把关键节点去跟组件特征比较,命中之后我们会把节点转化为组件调用代码。
组件拆分
组件拆分 2-1
最开始我们生成的都是单页面文件,把所有代码全都堆到了一个文件。但是开发者很不喜欢,他们习惯了组件化拆分的写法,于是我们按照算法计算出来的一级节点拆分成单独的组件文件,并且做好组件的引入、注册和调用。后面我们也会加入一些算法,让可能会在一个组件里边的多个顶级节点合并在一个组件里。
组件拆分 2-2
但还是有些情况开发者觉得不理想,于是我们也做了手工组合的策略:
自定义组件拆分 2-1
像上图这个组件,原本算法会拆成三个小组件,但开发者希望这三块是在一个组件里,所以我们也开放这样的能力,允许开发者自己组合。
自定义组件拆分 2-1
样式名算法
样式名在开发者的需求调研当中是期望值最低的一个,但是实际上我们去推广的时候发现样式名恰恰是一个前端工程师是否真正地想持续用你的系统的最后一道防线,开发者其实永远有第二种选择,就是自己手写。
所以我们在样式名的算法这块做了很多努力:针对一个节点会有这么多种的命名算法:
样式名算法
下面介绍一些我们常用的命名算法。
计师的命名
我们能直接使用的是设计师命名,设计师会对节点或文件夹,有自己的中文或英文命名,如果算法判定这个命名是可用命名,会直接去给这个区域,采用设计师的手工命名。
文本的命名
其次是文本的命名,一个设计稿里所有节点上的中文和英文内容 ,算法会去拿来做命名的参考,中文会去翻译。像这样一个节点,我们会给它的命名是 mailing-method。
图片特征的命名 3-1
然后是图片节点,算法会根据图片特征进行命名。比如上面这张图片,算法会把它识别成头像:
比如,这里我们会把它识别成一本书:
图片特征的命名 3-2
下面这个是根据它的图片的尺寸特征识别成 banner:
图片特征的命名 3-3
接下来是特征命名,这个案例我们会把它命名成 userinfo:
特征命名 2-1
下面这块会识别成 goods-card:
特征命名 2-2
我们最终的有效命名率统计是 98%,有效命名率是什么意思呢?如果一个节点没法命名的话,我们会用一个通用的命名,这个可能每个 D2C 系统都会有,我们把这个节点叫做 box-1、box-2、box-3 这样排下去,以前这种命名的比例是非常高的,现在它可能只占不到 2% 的水平。
其他的命名方式就不逐一介绍了,但这依然无法满足开发者的全部命名喜好,所以我们也设计了手工调整策略:
命名辅助
开发者可以手工修改任何一个节点命名,我们也加入了命名辅助的功能。
样式简化
之前我们经常收到这样的反馈:一个很简单的页面,系统会给我生成 1000 行代码,但是其中里面有 600 行都是样式代码。所以我们做了很多优化来简化样式代码:
样式简化
我们最终简化了 40% 的样式代码体积,主要是用了六种策略:
1、提取循环节点
这个应该是最有效的一种方法,列表页其实是比较常见的,如果一个列表有 5 个卡片,提取了循环节点之后,至少就会减少 4/5 的代码量。
2、删除冗余样式
从 Sketch 里面提取的数据转成 CSS 之后其实有很多的样式是绘制页面用不到的,我们把这部分给删除掉了。
3、属性简写
这个比较好理解,比如 background、margin、padding 等都是可以简写的。
4、样式继承
对于字体、段落的一些样式都是可以继承的。
5、样式复用
我们也会把一些当前设计稿里面一些可以复用的通用样式提取到顶级的样式模块去进行复用。
6、尝试去删除节点上的宽高
Sketch 设计稿里面每个节点都会有尺寸属性,有些可以删除,有些不能,我们会尝试尽量删除掉。
背景拼图
这个诉求是非常常见的,像这个弹窗其实是由很多小图标组成的:
背景拼图 3-1
最开始的时候,设计稿里边有多少个图片,算法会给它生成多少个节点;又因为它们位置之间相非常复杂,所以就会用绝对定位来进行布局。
背景拼图 3-2
但开发者拿到这样的代码是没办法去维护的,开发者希望系统把这些小图给拼成一张图。那么我们拼图这块是怎么做的呢?
背景拼图 3-3
我们这个页面里面的元素类型总共有四种:
图标:图标是 CSS 不容易实现的
文字:非特殊字体的文字是不需要拼图的
图形:像按钮这种图形是 CSS 能画出来的,有些图形是 CSS 不能画出来的;有些即使是 CSS 能画出来的,也还是需要拼图的,这里面还是会有一定的一些权衡算法
图片:图片是需要拼到背景图里的
算法会判断这个节点当中哪些东西是可以画出来、哪些是需要拼的,最终会把需要拼的东西去拼成一张图,上面这个例子就会把它拼成三张图。
“可视化开发”
我们在推广的时候还遇到的一个挑战:如何让所有人都无法拒绝 D2C?
我们做了这样的能力:如果你觉得算法生成的结构或者是样式不满意,你可以使用重构功能,系统会把这里面所有的有用的叶子节点拍平,开发者可以自己去组合结构,这之后算法会自动计算样式。
“可视化开发”
像上面这种布局,可能至少 15 分钟以上才能手写出来。但是如果使用“可视化开发”的功能,几分钟可以把这个布局做出来。
UI 自动化还原测试
UI 还原度自动化测试
开发者去看页面还原度的时候,其实很难通过肉眼看出来差异,所以我们做了一个自动的 diff 功能,左边是设计稿的效果图,中间这个是把生成代码做了一个截图,然后我们把这两张图去做一个像素的对比,有差异的地方我们就会把它标注出来(右侧图)。
转转奢侈品业务落地实践
接下来我们来聊聊一个项目的实际落地过程。
项目背景
首先,这个项目需求比较急,项目周期也比较短,14 天要开发出一整套电商的页面。其次我们之前在推广的过程中,大家还是比较谨慎,拿一些比较简单、不那么重要的小页面去进行实际上线的测试,因此缺少一些大型项目的实际使用案例。第三是我们神笔马良的开发者对于真正的前端同学使用时候具体的感受其实还是比较缺乏的,所以我们也是参与到了这个项目的实际开发中。
我们最终频道上线的一些页面:
应用场景
这其中有频道页、列表页、详情页和活动页。这些页面也说明了我们的系统已经具备了替代前端日常开发页面的切图能力。
最终我们复盘了 D2C 的实际使用情况:
实际使用中遇到的问题
UI 组件库识别率:当时是一个比较低的水平,只有 30% 的组件识别率,可以说是很差的
手工修改率:自动生成代码里面会有 40% 的代码下载之后需要手工修改
代码可用率:有 70% 的代码是大家觉得可用的,可用是什么标准呢?就是有 30% 的代码生成了完全没法用,有 70% 的代码是可以直接使用或者经过修改后使用的
目标:我们的目标是整个代码有 90% 的可用率
D2C 和自己手写代码的效率对比:
效率提升
在切图这个环节,应该是有 80% 的效率提升,因为整个 Sketch 设计稿在切图环节几秒钟就可以完成。
UI 还原度上会有至少 5% 的提升,因为我们目标都是像素级的 100% 还原,但实际上我们在开发完成提交给 UI 同学去查的时候,可能都会查出一些问题 。我们之前手工是 90% 的水平,使用了 D2C 的代码后,会有 5% 左右的提升(为何还做不到 100% 还原?因为页面在填入真实数据时,可能会出现文字溢出、多行等情况,算法对动态文本的处理还有待提高)。
但随之而来下降的是可维护性,如果我们把这个代码生成好,直接放到线上去用,在后续维护的时候确实是有一定的熟悉成本,需要时间精力看这个代码的结构和样式代码,跟手写的相比,可维护性是有一定的下降。
前端智能代码领域的未来思考
最后是我对 D2C 这个领域的一些思考:
行业现状
现在业界都在探索期,很多问题都是在摸索,我和业界很多产品的研发者去聊,发现大家遇到的很多问题其实都是差不多的,大致都处在同一个阶段。现在参与的公司其实并不是很多,主要有阿里、58、转转、codefun、蓝湖、字节、百度、京东等。
但是我们也看到了越来越多的大厂正在参与进来,D2C 领域可能很快会像之前任何一个前端新兴领域一样进入到百花齐放的状态,也可能会出现行业的通用解决方案或产品,并且会出现非常好的开源方案(比如 58 的 Picasso)。
算法发展
D2C 这件事情最核心的还是在算法上,最开始去做 D2C 还是有一定的门槛的,然后会进入到很多基础算法的过程,基础算法一般会包括数据提取、结构布局、样式计算、命名和 DSL 等。目前业界大部分产品都还是在策略算法阶段,也就是如何让生成的代码更加符合开发者的预期,里面还是要做大量的手工策略的。
当前也有一些产品通过机器学习来解决问题,这部分的难度有些陡峭,但需要的场景其实有很多,我认为最终一定会出现行业的标准算法,且会向上下游场景进行延伸,比如从 PRD 到上线的整个环节。
最后也希望越来越多的开发者参与进来,一起来推动 D2C 领域的发展。
关于本文
作者:@张所勇
原文:https://mp.weixin.qq.com/s/IzMTsDT6--V4SyAGoHqT8g
相关阅读
【第2524期】Deco 智能代码体验版正式上线啦,快来体验设计稿一键生成代码
欢迎自荐投稿,前端早读课等你来。