自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用Jscodeshift做自動化重構(gòu)

原創(chuàng) 精選
開發(fā)
在這篇文章中,我們從一個簡化了的實(shí)際例子出發(fā),描述了為何jscodeshift在某些場景下可以提供的幫助,比如降低大型修改可能帶來的影響。

作者 | 邱俊濤

在這篇文章里我想要通過一些小例子來介紹使用jscodeshift來進(jìn)行自動化重構(gòu)的技術(shù)。具體來說,我想要介紹在一個組件庫的開發(fā)和維護(hù)過程中,如何使用jscodeshift來自動修改公開的API接口,從而盡可能小的產(chǎn)生對組件用戶的影響。

如果你們團(tuán)隊(duì)開發(fā)的組件被其消費(fèi)者(組織內(nèi)部或者外部)使用了,而這些代碼又不在你的控制之內(nèi),那么這里討論的技術(shù)和模式可能對你很有幫助。而如果你的日常工作更多的是使用組件庫來開發(fā)應(yīng)用程序,我希望這里的知識和技巧仍然對你有所啟發(fā),畢竟在軟件系統(tǒng)中,我們往往都既是某些庫的消費(fèi)者,又同時是另外一些庫的生產(chǎn)者。

從一個簡單場景出發(fā)

設(shè)想這樣一個場景,你發(fā)布了一個酷炫的組件庫(fancylib),其中有一個按鈕(Button)組件。這個Button的一個屬性是當(dāng)點(diǎn)擊后處于加載中(loading)狀態(tài)時現(xiàn)實(shí)一個表示加載中的小圖標(biāo)。

(圖片來源:https://xd.adobe.com/ideas/process/ui-design/designing-interactive-buttons-states/)

在代碼實(shí)現(xiàn)中,這個加載中狀態(tài)被定義為了名為isInLoadingStatus公開prop。用戶可以通過設(shè)置其值來控制Button的狀態(tài):

import Button from '@fancylib/button';

const app = () => (
<Button isInLoadingStatus>Click me</Button>
)

一個實(shí)習(xí)生在某一天code review的時候提出了一個問題:在組件庫中的其他地方,所有的boolean狀態(tài)都是用一個單詞來表示的,比如checked, disabled等。如果按照這個慣例,這里應(yīng)該把isInLoadingStatus簡化為loading。好主意!

import Button from '@fancylib/button';

const app = () => (
<Button loading>Click me</Button>
)

假如所有用到Button的地方都在你的控制之內(nèi),字符串替換大約是一個快速且80%有效的方案。不過稍微分析一下,你就會發(fā)現(xiàn)簡單的Shift+F6會遇到很多問題。

復(fù)雜情況

比如用戶對其做了二次包裝以適配更符合自己用戶的使用習(xí)慣,這使得簡單的全局字符串替換變成了不可能::

import Button as FancyButton from '@fancylib/button';

const MyEvenFancierButton = (props: FancyButtonProps) => (
const theme = {
backgroundColor: "orangered",
color: "white"
};
<FancyButton {...props} theme={theme}>Click me</FancyButton>
);

除了這些問題之外,由于這是一個非常受歡迎的組件庫,Button在很多(包括內(nèi)部和外部的)產(chǎn)品中都有使用,你沒有辦法訪問所有的用戶代碼,更沒有辦法讓所有人都用手工的查找替換來做更新,你需要另尋出路。

你需要一個工具 -- 一個可以讀懂代碼意圖的工具 -- 來幫助你做修改,而且整個過程最好可以自動化,比如通過執(zhí)行一個腳本來完成。

使用jscodeshift

jscodeshift就是這樣一個工具(工具集)。簡單來說,jscodeshift的工作方式就是將源代碼分析成一棵樹(抽象語法樹),然后提供API來修改這棵樹,最后再把樹生成為代碼。

也就是說,她可以讀懂你的代碼,并提供指令(API)來根據(jù)你的意愿修改相應(yīng)的代碼。

實(shí)現(xiàn)

接下來,我們可以通過實(shí)現(xiàn)一個可以完成上述場景的自動重構(gòu)的腳本來對jscodeshift的使用做一個簡單介紹。簡單來說,jscodeshift的工作流程是:首先你需要定義一個轉(zhuǎn)換腳本(transform),這個腳本需要符合一定的規(guī)范以便jscodeshift調(diào)用;然后jscodeshift的命令行工具會啟動runner,并將轉(zhuǎn)換腳本應(yīng)用到某個文件或者某個文件夾中的所有文件中:

jscodeshift -t myTransform src

定義一個transform

也就是說,我們所有的邏輯都會定義在轉(zhuǎn)換腳本中。transform腳本需要導(dǎo)出一個固定格式的函數(shù):

import { Transform } from "jscodeshift";

const transform: Transform = (file, api, options) => {
//...
};

export default transform;

file為解析后的文件對象,api是jscodeshift的API對象,可以通過它來查找,修改文件對象,options是一個可選的,用來傳遞其他參數(shù)(比如格式化最終輸出格式等)的對象。在函數(shù)體中,我們可以使用jscodeshift提供的API來操縱抽象語法樹(Abstract Syntax Tree)來實(shí)現(xiàn)對代碼的修改。這個過程和通過DOM API來操作瀏覽器中的頁面元素非常類似:按照屬性查找元素,對查找結(jié)果進(jìn)行增刪改等操作,只不過這里的操作對象是語法樹(比如變量定義,函數(shù)體,條件語句等等)。

在詳細(xì)討論如何使用jscodeshift的API來修改代碼之前,我們來略微看一下抽象語法樹的概念。這將是我們腳本需要操作的主要對象。

抽象語法樹AST

抽象語法樹,是編譯器將源碼解析(parse)之后形成的一課樹形結(jié)構(gòu)。簡單來說,我們的代碼被解析成為Token,Token再根據(jù)語法規(guī)則形成子樹,子樹最終根據(jù)文法歸并成一顆樹。我們可以通過AST Explorer工具來實(shí)時查看代碼對應(yīng)的語法樹。

舉個例子,我們的代碼片段:

import Button from '@fancylib/button';

const app = () => (
<Button isInLoadingStatus>Click me</Button>
)

經(jīng)過解析(jscodeshift默認(rèn)使用babel來解析,你可以選擇其他的解析器)之后,會形成右側(cè)的一顆樹,比如isInLoadingStatus被識別成JSXIdentifier類型,而變量app定義則被識別為VariableDeclarator等。所有符合語法的元素都會被抽取成Token,并體現(xiàn)為樹上的一個節(jié)點(diǎn)。

有了這些基本概念之后,我們就可以開始編寫一個簡單的transform了。這里我們可以通過AST Explorer提供的在線IDE中的Transform功能來實(shí)時調(diào)試(此處選擇jscodeshift作為轉(zhuǎn)換器)。

然后我們定義這樣一個轉(zhuǎn)換函數(shù):

// Press ctrl+space for code completion
export default function transformer(file, api) {
const j = api.jscodeshift;

return j(file.source)
.find(j.JSXIdentifier)
.forEach(path => {
if(path.node.name === "isInLoadingStatus") {
j(path).replaceWith(
j.identifier('loading')
)
}
})
.toSource();
}

比如上述代碼中,我們查找所有的j.JSXIdentifier,并迭代每一個找到的節(jié)點(diǎn),如果它的值是isInLoadingStatus的話,就將其替換為loading。可以觀察到右下側(cè)的調(diào)試器窗口中的轉(zhuǎn)換結(jié)果:

測試驅(qū)動開發(fā)

當(dāng)然了,作為一個嚴(yán)肅的程序員,我們不應(yīng)該通過一個在線IDE來進(jìn)行開發(fā)。幸運(yùn)的是jscodeshift可以和jest完美配合,同時我發(fā)現(xiàn)編寫自動化腳本是一個非常適合測試驅(qū)動開發(fā)的場景:

  • 輸入輸出都非常明確
  • 各種不同的邊界場景很容易想象/編寫成用例
  • 每一個步驟都可以劃分的比較小

jscodeshift提供了一個小工具defineInlineTest,通過它你可以很方便的定義測試用例:

import { defineInlineTest } from 'jscodeshift/dist/testUtils';
import transformer from './transformer';

describe('transformer', () => {
defineInlineTest(
{ default: transformer, parser: 'tsx' },
{},
`
import Button from '@fancylib/button';

export default () => (
<Button isInLoadingStatus>Click me</Button>
);
`,
`
import Button from '@fancylib/button';

export default () => (
<Button loading>Click me</Button>
);
`,
'change isInLoadingStatus to loading'
);
});

當(dāng)然,如果你不習(xí)慣字符串模板的話,它同時還提供了基于文件形式的測試定義,這樣你可以將測試的輸入(轉(zhuǎn)化前)和輸出(轉(zhuǎn)化后)外置到文件中,并在其中構(gòu)建較為復(fù)雜的使用場景。

比如我們希望這個transform不要誤傷我們代碼中使用的其他Button,比如我們使用了另外一個組件庫,而巧合的是那個庫中Button也有一個isInLoadingStatus。

那么對應(yīng)的測試用例會是:

   defineInlineTest(
{ default: transformer, parser: 'tsx' },
{},
`
import Button from '@facebook/button';

export default () => (
<Button isInLoadingStatus>Click me</Button>
);
`,
`
import Button from '@facebook/button';

export default () => (
<Button isInLoadingStatus>Click me</Button>
);
`,
'should not change isInLoadingStatus to loading from other package'
);

對應(yīng)的我們需要在代碼中加入相應(yīng)的邏輯:

// Press ctrl+space for code completion
export default function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source);

const specifiers = root
.find(j.ImportDeclaration)
.filter((path) => path.node.source.value === "@fancylib/button")
.find(j.ImportDefaultSpecifier);

if (specifiers.length === 0) {
return;
}

//...
}

即,我們先查找所有的import語句,如果沒有找到從@fancylib/button導(dǎo)入的Button就跳過后續(xù)的操作。你應(yīng)該已經(jīng)注意到了,我們這里又很多的諸如j.ImportDeclaration和j.ImportDefaultSpecifier之類的Token定義,你可以從AST Explorer的樹結(jié)構(gòu)中找到類似的名稱,然后用jscodeshift的API來查找并訪問改節(jié)點(diǎn)。

這個過程或多或少有點(diǎn)像我們通過DOM的API來選擇HTML節(jié)點(diǎn)一樣:

document.querySelectorAll('a')
.filter(anchor => anchor.classList.includes('button'))
.forEach(anchor => anchor.style["text-decoration"] = "underline")

如果你覺得這里要素太多,這是很正常的。嘗試著多寫幾個就會發(fā)現(xiàn)規(guī)律。

如果把所有的實(shí)現(xiàn)細(xì)節(jié)都列舉在一篇文章中,我覺得文章會非??菰?可能寫成一個系列教程等),因此這里我不再貼代碼,相關(guān)的源碼可以在https://github.com/abruzzi/codemod-demo找到。

可能的陷阱

使用腳本來自動化重構(gòu)的想法當(dāng)然非常有誘惑了,特別是對于疲于為已經(jīng)公布的API打補(bǔ)丁的人們來說,簡直太過于美好。不過公平起見,我還是得略微說一些它的一些drawbacks。

首先,jscodeshift 的API略顯晦澀,有一定的學(xué)習(xí)成本。開發(fā)過程中可能會有很多調(diào)試的工作。其次,它并不定覆蓋100%的使用場景,比如對于復(fù)雜的spreading操作,需要調(diào)試和分析的工作量不容小覷,也就是說你仍然需要人工校對一些edge cases。最后,需要一些腳本來支持組件的消費(fèi)團(tuán)隊(duì)使用,比如自動化補(bǔ)丁工具等,如果有多個transform,如何一次patch等問題。

小結(jié)

在這篇文章中,我們從一個簡化了的實(shí)際例子出發(fā),描述了為何jscodeshift在某些場景下可以提供的幫助,比如降低大型修改可能帶來的影響(而如果影響不可避免,那么如何使其變得不那么痛苦)。隨后我們描述了jscodeshift中的一些基本概念和基本的工作方式,并結(jié)合之前討論的例子實(shí)現(xiàn)了部分的自動化重構(gòu)。

責(zé)任編輯:趙寧寧 來源: Thoughtworks洞見
相關(guān)推薦

2020-12-08 06:20:49

前端重構(gòu)Vue

2021-06-28 06:32:46

Tekton Kubernetes Clone

2020-12-01 07:01:41

CSS工具重構(gòu)

2022-02-21 11:24:14

代碼工具開發(fā)

2018-09-05 14:45:10

Python自動化機(jī)器學(xué)習(xí)

2017-12-17 21:58:18

2024-11-21 15:24:49

2012-02-09 13:31:03

HibernateJava

2021-04-19 14:00:03

ExchangelibPython郵箱自動化管理

2022-11-15 17:07:40

開發(fā)自動化前端

2009-12-15 17:43:04

Ruby自動化驅(qū)動

2024-09-13 15:32:18

2024-01-24 18:50:21

WebFTP服務(wù)器

2020-03-10 10:06:08

小程序微信開發(fā)

2018-07-13 06:46:35

數(shù)據(jù)中心自動化微服務(wù)

2021-04-17 23:10:59

Python微軟Word

2018-12-03 08:46:36

Web瀏覽器SeleniumPython

2021-09-30 09:00:00

漏洞安全工具

2025-02-06 14:59:08

2018-02-25 19:29:49

自動化數(shù)字化IT
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號