位元組跳動二次生成能力加持下的 UI 智慧生成實踐

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

作者|朱睿力

編輯|賈亞寧

本文由 InfoQ 整理自位元組跳動前端工程師朱睿力在 GMTC 全球大前端技術大會(深圳站)2021 的演講《二次生成能力加持下的 UI 智慧生成實踐》。

隨著業務量的增長,用於支援業務的輕量 H5 頁面的開發逐漸成為一個耗費大量時間和人力的棘手問題。這些業務開發繁瑣,難以沉澱,缺少自動化,如何提高這類 H5 頁面的開發效率越來越成為業務增長過程中的一個痛點。UI 智慧生成程式碼也成為解決這個問題的探索方向之一。本篇文章會主要介紹位元組跳動抖音前端團隊所探索的智慧生成程式碼專案 ALYX 的解決方案、ALYX 和我們的搭建平臺(魔方)的配合以及二次生成能力如何助我們生成複用度更高的頁面。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

ALYX

本文會先從業務場景出發,在講解我們的主題【二次生成能力】之前,先帶大家過一遍在沒有二次生成能力的時候,我們曾經的單次生成模式流程,然後一起看看它有什麼問題,以及為什麼我們後面需要二次生成能力去解決這些問題。

背景

業務場景介紹

在談論解決方案之前,當然要先明確業務場景。我們的業務場景主要是抖音活動。抖音活動如下圖所示,基本上都是一些移動端的 H5 頁面。這些頁面的一個顯著特點是上限會非常的高,同時下限也會非常的低。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

抖音活動示例

下限如一些靜態的頁面,沒有動態的內容或效果,在結構和佈局上也非常簡單;上限如有些頁面會有一些比較複雜的元件和玩法,像任務、抽獎、使用者列表等等較為複雜的模組。這些頁面足夠簡單,以至於對研發來說,開發它們是一種非常繁瑣、沒有積累且缺少意義的工作;它們又足夠複雜,複雜到剛好處於“難以完全自動化生成”的一個臨界點。這類業務問題非常難以解決,導致我們處於一個非常尷尬的境地。

魔方低程式碼搭建平臺

為了解決這個業務難題,我們團隊內部誕生了魔方低程式碼搭建平臺。像其他的低程式碼平臺一樣,它的目標使用者是我們的運營同學,希望賦予運營同學透過拖拽元件的方式搭建起一個活動頁面的能力,從而來緩解研發的一些重複工作。魔方也很好地迴應了我們的期待:魔方平臺經過 4 年的平臺建設,現在已發展為位元組系全產品的活動平臺,每月穩定支援上千個活動高效上線,活動流量高達數億。然而魔方並不是一個完美的解決方案,發展到後來也出現了新的問題。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

魔方低程式碼搭建平臺

隨著業務複雜性的提升,我們會發現魔方變得越來越複雜、學習成本越來越高了。為了支撐業務的複雜性,搭建平臺本身的複雜性也難以避免地隨之提升。這就違背了我們一開始的初衷:

為運營提供一個易於使用的、無需研發參與的頁面搭建平臺。

ALYX 的誕生

在遇到這個問題之後,我們團隊內就開始有了 ALYX 專案的想法。ALYX 是一個前端智慧化的專案,或者簡單來說是一個設計稿轉程式碼專案。我們希望透過 ALYX 的智慧演算法來幫助運營,從設計稿直接自動生成他們想要的一個頁面,幫助運營減少搭建這一部分的工作,從而跳過一些魔方複雜的搭建操作,進而減輕魔方的複雜度問題。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

ALYX 的誕生

ALYX 誕生的初衷就是減少業務中重複繁瑣低複用的勞動,提高業務的整體開發效率。

到目前為止 ALYX 已經取得了一些階段性的成果,現在已經支援了 Photoshop 和 Figma 等多種內部常用的設計軟體,也支援了多種的匯出產物,如程式碼、魔方平臺的活動以及魔方平臺的模板。簡單透露幾個數字:現在 ALYX 已經支援了內部 100% 的魔方模板生成,為我們的模板製作這個場景帶來了超過 60~70% 的效率提升。ALYX 用這些數字證明了自己的價值,那麼我們是怎麼做到的呢?

單次生成模式的實踐

我們先來聊聊單次生成模式下我們團隊的一些實踐。

在介紹一些複雜模組之前,首先讓我們簡單過一下 ALYX 的一個簡化 Pipeline。需要強調的是,下圖所示只是 ALYX 的一個最簡化的 Pipeline,它只保留了一些最必要的專案,實際的 Pipeline 會比它複雜得非常多。

ALYX 簡化版 Pipeline

首先我們需要一張(或多張)設計稿,接著是結構重組。為什麼要進行結構重組呢?我們拿到的設計稿是由設計師所生產的,而設計師所關注的主要是設計圖在視覺效果上的一致性,並非其在結構上的合理性,但是頁面結構恰恰是對於研發來說非常重要的。

所以很有可能你拿到的看上去一切正常的設計稿,實際上圖層結構卻和對應的實現程式碼結構大相徑庭,這樣的話研發後續要在上面去做一些修改或者二次開發就會非常困難。所以我們寧願完全拋棄掉設計稿原有的結構資訊,只保留圖層本身的資訊以及圖層之間的相對高度資訊(用前端的話來說就是 z-index),然後由我們自己的演算法去對所有的圖層進行重組,重組出我們自己認為一份既可以有效保留設計稿資訊,又具有相對可讀性與二次調整能力的結構,這就是結構重組需要做的事情。

結構重組之後我們需要對迴圈進行處理。我相信迴圈是前端程式碼開發 H5 頁面的過程中非常重要和常見的部分,這一塊我們後面會去更加詳細地講一講。

結構重組和迴圈處理這兩部分我們稱為佈局部分。佈局完成之後,我們會進行一個生成的步驟。生成的第一步是對樣式的生成,因為設計稿中所帶的樣式資訊是和 CSS 的格式完全不同的,是不能被瀏覽器直接理解的,因此我們要透過樣式生成這個步驟,來把設計稿中的樣式資訊去轉換成瀏覽器可以理解的 CSS 樣式程式碼資訊,這就是樣式生成。

樣式生成之後接下來是元件轉換。元件和迴圈一樣也是一個可以複用的頁面中必不可少的部分,後續我們也會去詳細介紹。

生成部分完成之後,我們就可以透過不同的 DSL 生成不同平臺的目標了。比如說透過 React DSL 就可以將我們的 Schema 轉換為 React 程式碼;如果生成目標是魔方的活動頁或者模板,我可以用另一個魔方的 DSL 去生成最終的結果。

迴圈處理

在上面的流程中,我們開發過程中遇到的第一個問題就是迴圈。什麼是迴圈?為什麼需要迴圈處理?讓我們看看下圖中的例子。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

有迴圈處理和無迴圈處理的對比

如果我們要去生成圖中這麼一個列表元件,我們希望它生成的程式碼是什麼樣子的?先假設我們上面的流程中沒有迴圈處理,大家可以想象一下我們會生成什麼樣的程式碼。

假設沒有迴圈處理這個步驟,那麼列表的每一項之間是毫無關聯的,那麼按照上述的步驟流程,演算法會把列表的每一項單獨拿出來生成一遍。也就是說列表的三項是完全獨立生成出來的,它所有的資料也都是靜態且毫無關聯的。這樣的一個生成結果就很難去複用或者說二次開發,就算只是更改資料也會非常困難。

我們希望能生成 React 中迴圈的程式碼形式:將 state 中的一個數據陣列,透過 map 方法,得到一個新的 JSX 陣列。這種程式碼中所有的資料都是動態的,如果要去修改資料或者二次開發,只需要修改 state 中的資料陣列就可以了,這是我們理想的一個狀態。

除此之外,我們可以預見這種程式碼在程式碼量上也會有一個精簡。圖中的例子如果沒有迴圈處理,它生成的渲染函式是 50 行,相比之下加入迴圈處理後是 30 行,CSS 程式碼也從 345 行精簡到 270 行,這也是非常可觀的精簡。實際上我們這個例子中的列表比較複雜,每一項之間差別很大,如果是那種更加規範的列表,項與項之間差別比較小的話,精簡的數字會更加可觀。

相信在給了這個例子之後,大家都會認可迴圈處理肯定是我們生成可複用程式碼的一個必不可少的環節。那麼要怎麼去實現它呢?這裡我會一步一步地給大家講解我們的實現方案。

迴圈節點間的匹配

讓我們從最基礎的開始:我們需要知道兩個節點是否屬於同一個迴圈。透過迴圈節點間的匹配演算法,我們可以解決這個問題。

以下圖一個使用者列表元件為例:對圖中列表的兩項,我們做的第一件事是把這兩項分出來放進兩個沙盒之中。沙盒的作用是消除它們的位置以及外部的節點對於它們本身的影響。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

使用者列表元件

放進沙盒中之後,我們可以把每一項的子節點樹做鋪平處理,把所需要的關鍵節點鋪平開來,之後對所有的節點兩兩之間計算一個相似度。相似度可以透過它們的大小、型別、位置等等一系列的資訊去計算。得到兩兩之間的相似度之後,我們可以透過 Hungarian 演算法在二者的元素間做一個分配,得到一個最佳的兩兩匹配列表。

比如說在這個例子中,兩項的頭像和頭像間可以做一個匹配,使用者名稱和使用者名稱可以做一個匹配,關注按鈕背景可以做一個匹配。如果你比較細心,會發現下面一項其實有一個直播標籤沒有匹配到上面的任何一個節點,這也是沒有關係的,我們後面會去處理它。

區間內尋找最佳迴圈

現在我們知道怎麼去確定兩個節點屬於同一個迴圈,那麼就可以開始去尋找整個迴圈了。我們這裡做的是一個偏暴力搜尋的方式,首先對結構樹做一個前序遍歷,在遍歷出來的節點列表上找一個區間,然後在區間裡面去搜索迴圈。

如下圖中這個例子:假設我們在 1~7 這個區間內去搜索迴圈,我們會發現當步數為 2 的時候,抽出來的子節點樹按照我們上述的方法,它們兩兩之間實際上都是屬於同一個迴圈的,因此我們可以確認的是這三個節點一起構成了一個迴圈。這就是我們尋找整個迴圈的方法。這個方法比較暴力,那麼它的效率會不會非常低呢?實際上我們會運用提前退出、動態規劃等方法來提高它的效率,在實際測試中它的計算效率其實是非常可以接受的。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

搜尋迴圈

迴圈合併

我們現在是不是就可以找到我們想要的迴圈了?其實還缺少一步。你可能也注意到了,我們的上一步有一個非常致命的缺陷,即它過於依賴元素在結構樹中的前序遍歷順序。這又會有什麼問題呢?

我們看下面這個例子:對於圖中的一個明顯的迴圈結構,假設它在結構樹前序遍歷的順序是先是上面三個圖形,然後才是下面三個對應的名字(即順序是:方形,圓形,三角,Rectangle,Circle,Triangle)。那麼按照我們上述的尋找迴圈方法,我們是不是會找出來兩個迴圈?即前面三個圖形作為一個迴圈,後面三個文字作為另一個迴圈。這肯定不是我們想要的,我們想要消除我們的演算法對於結構樹前置遍歷順序的依賴。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

迴圈合併

因此我們會做一個迴圈合併,對相鄰的迴圈,根據他們的對齊關係、列表長度、元素類別等等多種資訊,去嘗試把它們合併起來,得到我們想要的最佳迴圈,以此來消除我們的方法對結構數上順序的依賴,這就是迴圈合併所做的事情。

提取迴圈項

很好,現在終於可以找到我們想要的迴圈了,那麼我們是不是就可以直接生成最終的迴圈程式碼了呢?很可惜,沒有這麼簡單。因為我們只是相當於給我們認為是迴圈的節點樹打上了一個標記,知道了這些結構樹節它是一個迴圈,然而這並不能非常神奇地直接轉換成我們想要的 React 中的迴圈程式碼。

讓我們回憶一下我們想要的 React 中迴圈程式碼是什麼樣子的:一個數據陣列透過 map 方法轉變成一個新的 JSX 陣列,然後交給 React 去渲染。關鍵就在於 map 方法,那麼 map 究竟做了什麼?map 是將一個統一的結構重複很多遍,在每一遍重複的時候可能會去做一些變化,由此來形成一個新的陣列。這裡面有兩個關鍵詞,一個是『統一的結構』,一個是『變化』。要生成我們想要的程式碼,就得先解決這兩個關鍵詞。

我們先來看第一個關鍵詞『統一的結構』。我們需要對我們的迴圈去定義它的統一結構,然後去嘗試提取出這樣一個迴圈的統一表現形式。在提取這個統一結構之前,我們當然得知道我們接下來要提取的結構是什麼樣子的,因此我們需要先來設計一下這個所謂的統一結構。

那麼這裡就有一個問題,這個統一結構,它應該是列表每一項資訊的一個並集(我們稱之為『最大集』),還是每一項資訊的一個交集(我們叫『最小集』)呢?

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

統一結構到底指的是什麼

假設我們想精簡一下我們的統一結構,選擇最小集。我們看下圖的例子:圖中的使用者列表中,大部分項會有一個直播標籤,小部分沒有。如果我們選擇最小集,也就是列表項資訊交集的情況,我們會得到左邊這麼一個統一結構,是不包含直播標籤的。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

最小集:What if。。。。

那麼我們在後面去重複 + 變化這個結構的時候,每當需要這個直播標籤的時候,我們都需要去新增這麼一個直播標籤元素。這些直播標籤實際上都是一樣的,但由於它們不在統一結構內,我們每次都需要去新增一個新的、重複的元素。這其實帶來了一個冗餘資料的問題。也就是說盡管我們想要整個統一結構更加精簡,最後的結果卻和我們一開始的預想背道而馳,這並不是我們想要的。

所以我們其實最後選擇了『最大集』的方式,也就是將列表每一項的資訊做一個並集。下圖還是一個使用者列表的例子,不過更加複雜一點。

在我們提取它的一個最大集之後,會得到圖左所示的結構。你會發現一個很神奇的事情:那就是左邊的這個結構不出現在實際的列表之中,因為它是整個列表的一個統一的表現形式,而不是某一具體的項。或者說它是一個列表多種狀態歸一的一個聚合體。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

最大集

好的,現在我們已經解決了第一個關鍵詞“統一結構”,讓我們來看第二個關鍵詞“變化”。變化就是統一結構在重複過程中可能需要根據資料或 index 的值做一些修改。那麼我們是不是隻需要把統一結構和我們實際的迴圈的每一項一一對比,然後去提取這個變化的地方就好了。原理確實是這樣,但是實際去做的時候我們會發現,由於設計稿中的列表實際上不是一個真正的迴圈,它的每一項可能都是單獨繪製的,那麼項與項之間的結構差異就可能會非常大。如果你強行把這樣的結構和我們設計好的統一結構去對比,很有可能會發現你提取出來的變化量太大了,生成的程式碼也是不可用的。

所以在這之前我們會做一步最佳化,就是從迴圈項去重新生成整個迴圈:我們把設計好的統一結構去重複,然後去修改它,使得新生成的迴圈和原來的迴圈在視覺上是一模一樣的,但是它的結構都是從我們的統一結構變化而來的,這樣一來新的迴圈的每一項的結構都統一了。在這之後我們再去做提取“變化”的時候是不是就更加方便了?

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

從迴圈項重新生成迴圈

在做完這一步最佳化之後,我們就可以很方便去將統一結構和真實列表一一對比,從而提取迴圈值了。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

提取迴圈值

我們解決“統一結構”和“變化”這兩個關鍵詞之後,其實距離生成我們最終想要的迴圈程式碼只有一步之遙了,這裡就不做贅述了。

元件處理

在解決迴圈問題之後,我們很快就會遇到另一個問題,那就是邏輯的生成。我相信很多 D2C 開發者都和我們一樣,不只滿足於頁面 UI 和結構的生成,很難不嘗試涉足邏輯生成的領域。即除了高保真地還原頁面 UI 與結構,也希望能夠同時還原出業務邏輯,達到真正的“零程式碼”。可是理論上設計稿中是不包含任何與邏輯相關聯的資訊的,我們要從哪裡去生成這些邏輯的資訊呢?

讓我們回到我們的業務場景。為了減少運營的理解成本,魔方低程式碼平臺中運用了大量的業務元件,業務元件就是一種將一個業務相關聯的邏輯全都包攬起來的非常複雜的大元件,這種元件對於運營來說,其內部就是一個黑盒,使用者不需要去關心,只需要負責填充資料就好了。

在這種業務場景下,我們會發現我們絕大部分的邏輯都已經被封裝在這些複雜的業務元件之中,從而賦予整個頁面的。也就是說業務元件能夠完成自閉環,幾乎不需要再額外增加程式碼。那麼是不是我們只要能生成這些業務元件,我們就能附帶地生成這些邏輯?因此在我們這種特定的業務場景之下,邏輯生成約等於元件轉換。

元件識別

在做元件轉換之前我們肯定需要先知道哪些節點是什麼元件,這也就需要元件識別能力了。這裡我們採用了深度學習的方法,模型是 YOLOv5。YOLOv5 是一個物件檢測模型,所謂物件檢測就是給模型一張圖片,它可以在圖片中找到你想要的標籤以及標籤的大小和位置,如下圖所示幫你框選出來。這個模型就非常適合我們對元件識別的需求。我們是基於一個預訓練好的模型,再用我們的資料去 fine-tune 的,到目前為止已經可以識別按鈕、選項卡、抽獎、影片等等很多簡單和複雜的元件了。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

YOLOv5 物件檢測模型

同時我們支援子語義識別,比如模型可以區分普通按鈕、提交按鈕和返回按鈕等等。這裡不會詳細去講我們的模型,其他文章中已經或多或少涉及到了。

但是需要注意的一點是,我們在實踐中發現了一件非常重要的事,那就是資料比模型其實更加重要。很簡單的道理,因為你的資料其實決定了你的模型的上限。我們在資料上花的時間實際上比在模型上多很多,也在這方面做了很多工作,比如說資料的整理、標籤的平衡、髒資料的篩選、頁面分割等等。

這裡舉個頁面分割的例子:YOLO 模型的輸入大小是固定的,比方說我們假設它的輸入是一個 256×256 的正方形尺寸。那麼這時候如果把一個非常長的頁面給喂進去尺寸是不搭配的,所以只能對輸入的圖片做一個拉伸畸變,也就是將其強行轉成 256x256 的大小。隨著畸變造成的就是資訊的壓縮,也就是原本資料資訊的丟失,進而導致模型的效果變差,這是不可接受的。下圖是一個簡化的例子,實際的頁面會更長,如果不做處理導致的畸變會更大。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

圖形的拉伸畸變

所以在將圖片資料餵給模型之前,我們會先將頁面進行分割:即在不中斷頁面中某個元件的情況下,將一個頁面切割為多段的小頁面,然後分批地給予到我們的模型,最後再將識別結果重新組合起來。這樣的話就可以減少頁面的畸變以及資料資訊的丟失,以此來提升樣本的精確度。

元件轉換

元件識別完成之後,我們是不是就完成了元件的工作呢?其實和之前的迴圈一樣,答案是還沒有。我們相當於只是給元素標註了它可能是什麼元件,並不能直接把它轉成程式碼。因此需要元件轉換這一步來把它轉換成真正可用的程式碼。

第一種轉換方式,也是非常符合直覺的方式,就是基礎轉換。基礎轉換就是為每一個元件去定製一個它獨有的轉換函式,轉換函式的作用是將我們從設計稿匯出的 Schema 輸入進去,輸出對應元件所需要的 props 和 styles 的資訊。這是一種非常簡單非常符合直覺的方式,然而它顯然是不夠的。

為每一個元件自定義一個轉換函式

在基礎轉換之後,我們又開發出了一種新的轉換方式叫做深度轉換。相比於基礎轉換,深度轉換其實走到了另一個極端。深度轉換在執行時轉換,將 React 的虛擬 DOM 樹和我們從設計稿中匯出的 Schema 進行匹配,給 Schema 的節點繫結其匹配上的虛擬 DOM 節點上的屬性和事件,然後將繫結好屬性和事件的 Schema 轉換為一個新的虛擬 DOM 樹交給 React 去渲染。

簡單來說它的原理就是我們在執行時攔截了原有元件程式碼的渲染函式,並進行了替換。如下面所示,圖中是一個我們原先開發好了的 Button 元件,該元件本身會渲染出一個 VDOM,深度轉換攔截了它所渲染的 VDOM,從 Schema 重新生成了一個 VDOM 並替換了原先的 VDOM,再交給 React 去渲染。

深度轉換

讓我們舉個例子,下圖中依舊是一個使用者資訊元件。如果我們從該元件的設計稿匯出 Schema,得到結構可能如左邊所示一樣的一個結構樹;而原先開發的該元件的程式碼,它渲染出來的 VDOM 結構可能是右邊這樣。可以看到他們兩者之間其實會有很多不一樣的地方,但是沒有關係,我們會透過一個匹配演算法,把二者的關鍵節點之間去嘗試做一個匹配。

下圖的匹配結果是:左側的頭像圖片匹配上了右側的 img。avatar 標籤,左側的使用者名稱文字匹配上了右側的 p。username 標籤,左側的關注按鈕圖片匹配上了右側的 div。follow 及 p。follow 兩個標籤等等。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

(舉例)使用者列表深度轉換

匹配完成之後,我們就可以把右側標籤上繫結的事件和屬性去綁到左側它對應的元素身上,這樣就形成一個新的帶有屬性和事件的 Schema 結構,我們再把新的 Schema 結構轉換成 React 支援的 VDOM,之後交還給 React 去重新渲染,就這樣完成了一個狸貓換太子,替換渲染函式的工作。

我們還發現左側有一個分數的文字,它實際上沒有匹配到任何標籤,這可能是因為我們原先去開發這個元件的時候並沒有考慮到這種使用場景,所以顯示使用者分數這個功能是原有元件不支援的。但是也無妨,我們這麼匹配之後,就完全可以把這個沒匹配上的元素轉換為一個支援編輯的常量,它也會被正常的渲染出來,並可以在魔方編輯器中被手動編輯。

基礎轉換和深度轉換這兩種元件轉換方式實際上不是一個替代關係,而是一種相輔相成的關係。基礎轉換的優點很明顯,它是人工適配的,所以它的轉換率會非常高;但是它的缺點也同樣明顯,那就是它消耗人力難以維護。這很容易理解,因為我們既然是根據元件來寫的轉換函式,那麼每次我們元件有更新,它相應的轉換函式也需要一起更新,這就是一個非常繁瑣的過程。因此基礎轉換比較適用於一些簡單的元件,如 Button,video 之類可以直接替換背景的元件,或者是功能和 DOM 結構不相關聯的元件。

至於深度轉換。它的優點就是另一個極端:它無需任何的適配和維護就能適用於大部分場景,開箱即用,甚至可以實現原元件無法實現的樣式。比如說我原先開發了一個使用者列表元件,開發時只支援豎排,但是經過深度轉換的替換工作之後,它實際上也可以去支援一個橫排的效果了。當然,它的缺點也非常明顯:它是一個自動的匹配演算法,這就意味著演算法可能會失敗,所以它的轉換率會低一點;並且因為它替換了 VDOM,所以它無法作用於功能和 DOM 結構相關聯的元件,這種元件就只能交給我們的基礎轉換了。它適用於基礎元件集合體類元件,比如使用者列表就是使用者頭像、使用者名稱、關注按鈕等一系列這種比較基礎的元件,透過內部的一套邏輯相關聯起來的一個組合類的元件,這種元件就非常適合我們的深度轉換方式。

至此,我們完成了迴圈和元件,我們在最佳化生產結果上實際上已經做了非常多的工作,可是這樣對我們來說就夠了嗎?

我們現在單次生成的流程在實踐中遇到了一個困境。讓我們看看下面的流程圖:當我把設計稿交給演算法的時候,演算法會直接幫忙生成一個最終的結果,可能是程式碼或者是魔方的頁面,在這個基礎上我們可能會去做一些人工調整,如填充資料、調整層級等。

單次生成的困境

在這之後,如果我們發現之前的轉換出現錯誤, 比方說某元件識別錯了,該怎麼辦?在現在流程下我們其實是沒有辦法很好地去解決這個問題的,我們只能讓它重新去生成一遍。可是如果重新生成,那麼我們就會丟失之前所有的人工調整,這對使用者來說會帶來一個很大的阻力,通俗來講會覺得我們這個產品非常不好用。

所以我們其實經常聽到對我們 D2C 開發者而言非常刺耳的一句話:

生成的還不如重新寫的。

一個很殘酷的現實是:為了讓生成的程式碼貼近使用者的使用習慣,每 1% 的進步都會消耗開發者大量的時間和精力。儘管如此,即使能生成 99% 符合使用者需求和習慣的程式碼,那最後的 1% 都是到達“好用”前難以逾越的巨大鴻溝。

二次生產能力加持下的智慧佈局

人工還是智慧?

為了解決上述的問題,我們開始嘗試探索二次生成能力。在此之前,讓我們從一個很簡單的問題開始:

人工還是智慧?

這裡我問大家一個問題,人工智慧是一個黑盒,如果生產環境出現問題,我們要如何去追溯?我們認為這個是很難的,所以我們第一點思考就是:

純智慧解決一切在目前為止還是不太現實,我們很難避免人工的介入,需要用人工來為機器兜底。

基於這一點我們有第二點思考:

既然人工的介入無可避免,那麼我們需要的可能並不是一個單純人工智慧平臺,而是一個人工與機器相配合的平臺。

二次生成能力的探索和實踐

基於這兩點思考,我們開始探索理想中的一個新的二次生成能力加持下的流程。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

二次生成能力的探索和實踐

在這個流程中我們將設計稿交給演算法之後,演算法並不是直接生成一個最終的結果,而是生成一箇中間的表示形式。在這個中間的表示形式上,我們已經可以去做一些人工調整了。同樣的如果在人工調整之後,我們發現錯誤,就可以把中間表示形式重新交還給演算法,演算法可以在糾正那些錯誤之後,並且在保留之前的人工調整的同時,去生成一個新的中間表示形式,這就是一箇中間表示形式的良性自迴圈。在我們解決完問題,所有都一切無誤的時候,才會去生成最終的產出。

這是一個比較理想的新流程,在這個流程中人工其實是在給機器糾錯,機器會在原有的基礎上去重新生成,我們不再需要從頭重新來過了。在新的流程中,人工從一個生產者的角色,變成了一個檢查者的角色,這實際上是一個質的變化。

實現:中間頁和重佈局演算法

為了實現我們理想中的這麼一箇中間表示形態自迴圈,我們開發了中間頁和重佈局演算法,中間頁是搭載這麼一個自迴圈的平臺,重佈局演算法就是實現自迴圈的一個核心演算法。在中間頁上我們可以做很多的人工調整工作,包括對識別錯誤的元件進行修正、對迴圈的修正、手動調整層級結構、標記固定和移動佈局等等。

在加入中間頁之後,我們在從設計稿直接到產出這個流程的中間添加了一步,即先到中間頁再到最後的產出。那麼這一步會不會對我們的使用者會帶來新的複雜度呢?我們認為其實並沒有,因為在實際的生產中,設計稿的部分和後續的產出部分實際上是由不同的角色來負責的。設計稿這邊由設計師來負責,對設計師來說,增加的工作只有安裝我們提供的外掛,然後就可以一鍵匯出,也不需要去做任何的規範;反而,ALYX 為他們減少了切圖以及大量的溝通工作。對運營來說,雖然在產出之前多了中間頁的步驟,但是中間頁實際上幫他完成了手動搭建頁面的工作,也提升了他們的效率。因此我們認為中間頁的加入實際上並不會給我們的使用者帶來新的複雜度。

位元組跳動二次生成能力加持下的 UI 智慧生成實踐

重佈局演算法

可以看到,新的流程下關鍵的核心部分,也就是 Schema 到 Schema 的這個部分,就是實現中間形態自迴圈的一個核心演算法,我們叫重佈局演算法。很有意思的一件事情,我們雖然是在做 D2C,即 Design to Code、設計稿轉程式碼,但是現在新的流程下,我們的核心步驟已經不包含 D (Design) 也不包含 C (Code) 了,因此我們給這個核心部分取了另一個名字:智慧佈局能力。

二次生成能力加持下的流程

讓我們回顧一下二次生產能力加持下的一個新流程:首先是初始化的步驟,在初始化過程中我們會用佈局演算法對設計稿做一個最小化整理,這裡也會去做元件的智慧識別;之後使用者在中間頁可以去做一些人工的修改和調整,如透過打標的方式去修改元件、調整分組等;人工調整的同時會呼叫智慧佈局的能力去保證最後生成頁面視覺上和結構上都是合理一致的,最後匯出目標平臺的產物。

二次生成能力加持下的流程 2-1

或者我們換一種更容易理解的說法:首先讓機器去幫我們做出推薦;在這個基礎上人工只需要去做出一些調整的命令,機器來完成這些調整命令,並且保證最後的頁面視覺結構是合理的,即是

智慧佈局

;最後匯出結果。

二次生成能力加持下的流程 2-2

總結

ALYX 智慧演算法在魔方低程式碼搭建平臺之上,為使用者提供了簡單搭建起一個 H5 頁面的解決方案,幫助使用者跨越從設計稿到最終頁面間的巨大溝壑。相信在不久的未來,魔方和 ALYX 會成長為更加成熟、更加智慧的低程式碼解決方案。

作者簡介

朱睿力:位元組跳動 抖音前端工程師。

魔方 ALYX 專案的設計者和核心開發者。2014 年接觸前端開發,自 CMU 畢業後 2020 年加入位元組跳動,從 0 到 1 搭建起 ALYX: UI 智慧生成程式碼專案的架構和演算法體系,期間負責核心佈局演算法的開發,React DSL 的最佳化,深度元件轉換演算法的研發,中間頁的架構和開發等工作;目前仍然是 ALYX 專案的核心維護者及開發者,對前端架構、前端智慧化等有著深刻的理解。

活動推薦