昼休みコードのリファクタリング:codemodスクリプトを理解する







プロジェクトのリファクタリングは、すべての開発者に近いトピックだと思います。 多くの場合、IDEと正規表現で十分でなくなると問題が発生し、この投稿で説明されているような資金が助けになります。 Codemodスクリプトは非常に強力なツールです。 それをマスターした後、リファクタリングが同じになることは決してないことが明らかになります。 したがって、私はこの投稿を私たちのハブログに翻訳しました。 楽しい読書をお祈りします。







コードベースの維持は、特にJavaScriptに関しては、開発者にとって頭痛の種になります。 サードパーティのパッケージの絶えず変化する標準、構文、および重大な変更を考えると、そのようなコードの維持は非常に困難です。







JavaScriptは近年、認識を超えて変化しています。 この言語の開発は、変数を宣言する最も単純なタスクでさえ変更されたという事実につながりました。 ES6はlet



およびconst



、arrow関数、および他の多くの革新を導入let



、それぞれが開発者に利益をもたらします。







時の試練に耐えることができるコードを作成および保守するとき、開発者の負担は増大しています。 この投稿では、 jscodeshift



スクリプトとjscodeshift



ツールを使用して大規模なコードリファクタリングのタスクを自動化する方法を学習します。これにより、たとえば、言語の新しい機能を使用するようにコードを簡単に更新できます。







Codemod



Codemodは、大規模なコードベースをリファクタリングするためにFacebookが開発したツールです。 開発者は短時間で大量のコードを再編成できます。 クラスや変数の名前変更などの小さなリファクタリングタスクの場合、開発者はIDEを使用します。このような変更は通常1つのファイルにのみ影響します。 次のリファクタリングツールは、グローバル検索と置換です。 多くの場合、複雑な正規表現を使用して機能します。 しかし、この方法はすべての場合に適しているわけではありません。







CodemodはPythonで記述されており、検索や置換のための式を含む多くのパラメーターを受け入れます。







 codemod -m -d /code/myAwesomeSite/pages --extensions php,html \ '<font *color="?(.*?)"?>(.*?)</font>' \ '<span style="color: \1;">\2</span>'
      
      





上記の例では、インラインスタイルを使用して色を示す<font>



<span>



置き換えています。 最初の2つのパラメーターは、複数の一致を検索する必要があることを示すフラグ(-m)と、処理を開始するディレクトリ( -d /code/myAwesomeSite/pages



)です。 処理される拡張機能を制限することもできます( -extensions php



html



)。 次に、検索と置換の式を提供します。 置換式が指定されていない場合、実行時に入力するよう求められます。 このツールは正常に機能しますが、正規表現を使用して検索および置換を行う既存のツールに非常に似ています。







jscodeshift



jscodeshiftはリファクタリングツールボックスの次です。 また、Facebookによって開発され、Codemodスクリプトを使用して複数のファイルを処理するように設計されています。 Node.jsモジュールとして、jscodeshiftはシンプルで便利なAPIを提供し、「 内部 」ではASTからAST( 抽象構文ツリー )変換ツールであるRecastを使用します。







リキャスト



Recastは、JavaScriptコードを解析および再生成するためのインターフェースを提供するNode.jsモジュールです。 コードを文字列として解析し、AST構造に一致するオブジェクトを生成できます。 これにより、開発者は、たとえば関数宣言などのテンプレートのコードを確認できます。







 var recast = require("recast"); var code = [ "function add(a, b) {", " return a + b", "}" ].join("\n"); var ast = recast.parse(code); console.log(ast); //output { "program": { "type": "Program", "body": [ { "type": "FunctionDeclaration", "id": { "type": "Identifier", "name": "add", "loc": { "start": { "line": 1, "column": 9 }, "end": { "line": 1, "column": 12 }, "lines": {}, "indent": 0 } }, ...........
      
      





例からわかるように、2つの数値を追加する関数を含むコード行を渡します。 文字列を解析して結果のオブジェクトを出力すると、ASTが表示されます。FunctionDeclaration、関数名などが表示されます。これは単なるJavaScriptオブジェクトであるため、必要に応じて変更できます。 その後、print関数を呼び出して、更新されたコード行を返すことができます。







AST(抽象構文ツリー)



前述のとおり、Recastはコード行からASTを構築します。 ASTは、抽象ソースコード構文のツリービューです。 各ツリーノードは、ソースコード内の構造を表します。 ASTExplorerは、コードツリーの解析と理解に役立つオンラインツールです。







ASTExplorerを使用すると、簡単なコード例のASTを表示できます。 fooという名前の定数を宣言し、文字列値'bar'



割り当てます。







 const foo = 'bar';
      
      





これはそのようなASTに対応します:













body配列には、定数を含むVariableDeclaration



ブランチがあります。 すべてのVariableDeclarations



は、重要な情報(名前など)を含むid



属性があります。 fooのすべてのインスタンスの名前を変更するCodemodスクリプトを作成した場合、この属性を使用し、すべてのインスタンスを反復処理して名前を変更します。







インストールと使用



上記のツールとテクニックを使用して、jscodeshiftを最大限に活用できます。 私たちが知っているように、これはNode.jsモジュールであるため、プロジェクトまたはグローバルにインストールできます。







 npm install -g jscodeshift
      
      





インストール後、利用可能なCodemodスクリプトをjscodeshift



と組み合わせて使用​​できます。 jscodeshiftが達成したいことを伝えるパラメーターを提供する必要があります。 基本的な構文は、変換するファイルへのパスを使用したjscodeshift



呼び出しです。 重要なパラメーターは、変換スクリプトの場所(-t)



です。ローカルファイルまたはCodemodスクリプトファイルのURLを指定できます。 デフォルトでは、 jscodeshift



は現在のディレクトリのtransform.jsファイルで変換スクリプトを探します。







他の便利なオプションは、テストの実行(-d)です。これは、変換を適用しますが、ファイルを更新しません。-vオプションは、変換プロセスに関するすべての情報を表示します。 変換スクリプトは、関数をエクスポートする単純なJavaScriptモジュールであるCodemodスクリプトです。 この関数は、次のパラメーターを受け入れます。









fileInfoパラメーターには、パスやソースなど、現在のファイルに関するすべての情報が格納されます。 apiパラメーターは、 findVariableDeclarators



renameTo



などのjscodeshift



ヘルパー関数へのアクセスを提供するオブジェクトです。 最後に、パラメーターは、コマンドラインからCodemodにパラメーターを渡すことができるオプションです。 たとえば、すべてのファイルにコードのバージョンを追加する場合、コマンドラインjscodeshift -t myTransforms fileA fileB --codeVersion = 1.2



介して渡すことができjscodeshift -t myTransforms fileA fileB --codeVersion = 1.2



。 この場合、optionsパラメーターには{codeVersion: '1.2'}



が含まれます。







エクスポートする関数内で、変換されたコードを文字列として返す必要があります。 たとえば、 const foo = 'bar',



コード行があり、 const foo



const bar



置き換えて変換する場合、コードは次のようになります。







 export default function transformer(file, api) { const j = api.jscodeshift; return j(file.source) .find(j.Identifier) .forEach(path => { j(path).replaceWith( j.identifier('bar') ); }) .toSource(); }
      
      





ここでは、一連の呼び出しで関数を組み合わせ、最後にtoSource()



を呼び出して、変換されたコード行を生成します。







コードを返すとき、いくつかの規則を順守する必要があります。 着信文字列以外の文字列を返すことは、成功した変換と見なされます。 文字列が入力と同じ場合、変換は失敗したと見なされ、何も返されない場合、変換は不要でした。 jscodeshift



はこれらの結果を使用して、変換統計を処理します。







既存のCodemodスクリプト



ほとんどの場合、開発者は独自のコードを記述する必要はありません。多くの典型的なリファクタリングアクションはすでにCodemodスクリプトに変換されています。 たとえば、 js-codemod no-vars



は、変数の使用に応じてすべてのvarインスタンスをletまたはconstに変換します( js-codemod no-vars



変数が後で変更される場合、 js-codemod no-vars



変数が変更されない場合)。







js-codemod template-literals



、文字列の連結をパターン文字列に置き換えます。次に例を示します。







 const sayHello = 'Hi my name is ' + name; //after transform const sayHello = `Hi my name is ${name}`;
      
      





codemodスクリプトの作成方法



上記のno-vars



スクリプトを使用してコードを中断し、複雑なCodemodスクリプトがどのように機能するかを確認できます。







 const updatedAnything = root.find(j.VariableDeclaration).filter( dec => dec.value.kind === 'var' ).filter(declaration => { return declaration.value.declarations.every(declarator => { return !isTruelyVar(declaration, declarator); }); }).forEach(declaration => { const forLoopWithoutInit = isForLoopDeclarationWithoutInit(declaration); if ( declaration.value.declarations.some(declarator => { return (!declarator.init && !forLoopWithoutInit) || isMutated(declaration, declarator); }) ) { declaration.value.kind = 'let'; } else { declaration.value.kind = 'const'; } }).size() !== 0; return updatedAnything ? root.toSource() : null;
      
      





このコードは、no-varsスクリプトの中核です。 最初に、フィルターはvar



let



およびconst



を含むすべてのVariableDeclaration



変数で実行され、ユーザー定義関数isTruelyVar



を呼び出す2番目のフィルターに渡されるvar宣言のみを返します。 varの性質(たとえば、varがクロージャ内にあるか、2回宣言されているか、発生可能な関数宣言)を決定し、このvarを安全に変換できるかどうかを示します。 isTruelyVar



フィルターによって返される各isTruelyVar



は、foreachループで処理されます。







ループ内で、varがループ内にあるかどうかを確認します。次に例を示します。







 for(var i = 0; i < 10; i++) { doSomething(); }
      
      





varがループ内にあるかどうかを判断するには、親の型を確認できます。







 const isForLoopDeclarationWithoutInit = declaration => { const parentType = declaration.parentPath.value.type; return parentType === 'ForOfStatement' || parentType === 'ForInStatement'; };
      
      





var



がループ内にあり、変化しない場合は、 const



に置き換えることができます。 検証は、 var AssignmentExpression



およびUpdateExpression. AssignmentExpression



フィルタリングすることで実行できますUpdateExpression. AssignmentExpression



UpdateExpression. AssignmentExpression



は、 var



が初期化された場所と時間を表示します。例:







 var foo = 'bar'; UpdateExpression ,    var  , : var foo = 'bar'; foo = 'Foo Bar'; //Updated
      
      





var



が変更のあるループ内にある場合、 let



使用されます。これは、インスタンスの作成後に変更できるためです。 スクリプトの最後の行は、何かが変更されたかどうかを確認し、答えが「はい」の場合、ファイルの新しいソースコードが返されます。 それ以外の場合、データ処理が実行されなかったことを示すnull



返されます。 このCodemodスクリプトの完全なコードはこちらにあります







Facebookチームは、React構文を更新し、React APIの変更を処理するために、いくつかのCodemodスクリプトも追加しました。 たとえば、 react-codemod sort-comp



は、Reactライフサイクルメソッドをソートして、 ESlint sort-compルールに準拠します。







Reactの最新の人気のあるCodemodスクリプトはReact-PropTypes-to-prop-types



、これは最近のReactの変更に対処するのに役立ちます。 。 PropTypes



を使用する方法PropTypes



、不変でPropTypes



ません。







以下の例はすべて真実です。







Reactをインポートし、デフォルトのインポートからPropTypes



にアクセスします:







 import React from 'react'; class HelloWorld extends React.Component { static propTypes = { name: React.PropTypes.string, } .....
      
      





Reactをインポートし、PropTypesの名前付きインポートを作成します。







 import React, { PropTypes, Component } from 'react'; class HelloWorld extends Component { static propTypes = { name: PropTypes.string, } .....
      
      





stateless



コンポーネントの別のオプション:







 import React, { PropTypes } from 'react'; const HelloWorld = ({name}) => { ..... } HelloWorld.propTypes = { name: PropTypes.string };
      
      





同じソリューションを実装する3つの方法が存在するため、検索と置換に正規表現を使用することが難しくなります。 コードに3つのオプションがすべてある場合、次の操作を行うことで新しいPropTypesパターンに簡単に切り替えることができます。







 jscodeshift src/ -t transforms/proptypes.js
      
      





この例では、 react-codemod



からPropTypes Codemodスクリプトをreact-codemod



し、プロジェクトのtransforms



ディレクトリに追加しました。 スクリプトはimport PropTypes from 'prop-types



import PropTypes from 'prop-types



を追加します。 各ファイルについて、すべてのReact.PropTypes



PropTypes



置き換えます。







結論



Facebookはコードサポートの実装を開始し、開発者は絶えず変化するAPIとコードを操作するための実用的なアプローチに適応できるようになりました。 Javascript Fatigue



は大きな問題になっています。これまで見てきたように、既存のコードを更新するプロセスを支援するツールは、そのソリューションに大きく貢献しています。







サーバー開発の世界では、プログラマーは定期的に移行スクリプトを作成してデータベースを最新の状態に保ち、ユーザーに最新バージョンを通知します。 JavaScriptライブラリの作成者は、Codemodスクリプトを移行スクリプトとして提供し、重要な変更が加えられた新しいバージョンがリリースされたときに、更新のためにコードを簡単に処理できます。







インストールまたはアップグレード時に自動的に実行されるCodemodスクリプトを使用すると、プロセスを高速化し、消費者の信頼を高めることができます。 さらに、このようなスクリプトをリリースプロセスに含めることは、消費者にとって有益であるだけでなく、更新に伴う例やチュートリアルのコストも削減します。







この投稿では、Cdemodスクリプトとjscodeshift



性質、およびそれらが複雑なコードをどれだけ迅速に更新できるかを調べました。 Codemodから始めてASTExplorerやjscodeshiftなどのツールにASTExplorer



と、ニーズに合わせてCodemodスクリプトを作成する方法を学ぶことができます。 また、広範な既製モジュールの存在により、開発者は積極的にテクノロジーを大衆に宣伝できます。







翻訳者注:JSConfEUでこのトピックに関する興味深いプレゼンテーションがありました。







ビデオを見る



All Articles