背景
ブラウザ向けJavaScriptの開発でも、BrowserifyやWebpackといったモジュールバンドルツールを作うことで、ファイル単位でモジュールを分割した開発が可能になりました。
すでにあるソースコードを、モジュールにわける操作は、機械的に実施できますが、手間です。
VSCodeのリファクタリング機能を使うことで、機械的な手間を軽減することができます。
方法
注意:今回はすでにファイル内にprivateなstatic関数がある状況を対象とします。
privateなstatic関数を抽出する方法は対象としません。
対象のサンプル
ファイル
以下のように、exportする関数とは別に、privateなstatic関数を含むモジュールがあります。
このprivateなstatic関数をモジュールとして抽出します。
export default function(annotationData, modeAccordingToButton) {
return function(selectionModel) {
const modifications = selectionModel.all().map((e) => annotationData.getModificationOf(e).map((m) => m.pred))
updateModificationButton(modeAccordingToButton, 'Negation', modifications)
updateModificationButton(modeAccordingToButton, 'Speculation', modifications)
}
}
function updateModificationButton(modeAccordingToButton, specified, modificationsOfSelectedElement) {
// All modification has specified modification if exits.
modeAccordingToButton[specified.toLowerCase()]
.value(doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement))
}
function doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement) {
if (modificationsOfSelectedElement.length < 0) {
return false
}
return modificationsOfSelectedElement.length === modificationsOfSelectedElement.filter((m) => m.includes(specified)).length
}
updateModificationButton
関数と、doesAllModificaionHasSpecified
関数をモジュールとして抽出していきます。
モジュール抽出
updateModificationButton関数
VSCodeでは、privateなstatic関数 をフォーカスすると、ヒントアイコンが表示されます。
ヒントアイコンをクリックすると新しいファイルへ移動します
と提案が表示されます。
これをクリックすると選択したprivateなstatic関数が別ファイルupdateModificationButton.js
に分かれます。
import { doesAllModificaionHasSpecified } from "./index";
export function updateModificationButton(modeAccordingToButton, specified, modificationsOfSelectedElement) {
// All modification has specified modification if exits.
modeAccordingToButton[specified.toLowerCase()]
.value(doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement));
}
選択したupdateModificationButton
関数は、新しく作成されたupdateModificationButton.js
に移動されています。
import { updateModificationButton } from "./updateModificationButton";
export default function(annotationData, modeAccordingToButton) {
return function(selectionModel) {
const modifications = selectionModel.all().map((e) => annotationData.getModificationOf(e).map((m) => m.pred))
updateModificationButton(modeAccordingToButton, 'Negation', modifications)
updateModificationButton(modeAccordingToButton, 'Speculation', modifications)
}
}
export function doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement) {
if (modificationsOfSelectedElement.length < 0) {
return false
}
return modificationsOfSelectedElement.length === modificationsOfSelectedElement.filter((m) => m.includes(specified)).length
}
代わりに、index.js
にはimport { updateModificationButton } from "./updateModificationButton";
の一文が追加されます。
相互import問題
気になる点はdoesAllModificaionHasSpecified
関数の前にexport
が追加されている点です。
updateModificationButton
関数内でdoesAllModificaionHasSpecified
関数を使っているため、新しく作ったupdateModificationButton.js
からdoesAllModificaionHasSpecified
を参照する必要があるためです。
updateModificationButton.js
を確認するとimport { doesAllModificaionHasSpecified } from "./index";
文があるのがわかります。
updateModificationButton.js
とindex.js
で、お互いにimportしあっていて気持ち悪いです。
doesAllModificaionHasSpecified
気にせずdoesAllModificaionHasSpecified
も新しいファイルへ移動します
。
するとdoesAllModificaionHasSpecified
関数はdoesAllModificaionHasSpecified.js
ファイルにわかれます。
updateModificationButton.js
は次のように修正されます。
import { doesAllModificaionHasSpecified } from "./doesAllModificaionHasSpecified";
export function updateModificationButton(modeAccordingToButton, specified, modificationsOfSelectedElement) {
// All modification has specified modification if exits.
modeAccordingToButton[specified.toLowerCase()]
.value(doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement));
}
updateModificationButton.js
はdoesAllModificaionHasSpecified.js
を参照し、index.js
への参照はなくなりました。
一的に相互import問題は発生しますが、すべてのprivateなstatic関数をモジュールとして抽出すれば、自然に解消されます。
その他
一つの関数をexportするときは名前付きエクスポートより、デフォルトエクスポートの方が望ましいと考えています。
次のように修正します。
import updateModificationButton from "./updateModificationButton";
export default function(annotationData, modeAccordingToButton) {
return function(selectionModel) {
const modifications = selectionModel.all().map((e) => annotationData.getModificationOf(e).map((m) => m.pred))
updateModificationButton(modeAccordingToButton, 'Negation', modifications)
updateModificationButton(modeAccordingToButton, 'Speculation', modifications)
}
}
import doesAllModificaionHasSpecified from "./doesAllModificaionHasSpecified";
export default function(modeAccordingToButton, specified, modificationsOfSelectedElement) {
// All modification has specified modification if exits.
modeAccordingToButton[specified.toLowerCase()]
.value(doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement));
}
export default function(specified, modificationsOfSelectedElement) {
if (modificationsOfSelectedElement.length < 0) {
return false;
}
return modificationsOfSelectedElement.length === modificationsOfSelectedElement.filter((m) => m.includes(specified)).length;
}
まとめ
VSCodeのリファクタリング機能を使うことで、JavaScriptのファイル分割の手間が軽減できました。
これによってBrowserifyやWebpackをつかった、モジュール分割の恩恵を受けやすくなります。
小さな独立したファイルにわけることで、以下のような恩恵が受けられます。
- ソースコードの影響範囲が明確になり、修正しやすくなる
- 複数人で修正作業をした時に、衝突が起きる範囲が狭くなる