以前作ったレガシーWebシステムをリファクタリングしました その2
前回の記事の続きです。
aik0aaat.hatenadiary.jp
はじめに
以前に私が作成したクソコードリファクタリング、第2回です。
github.com
前回は下記のことに取り組め、ビルド環境がだいぶ改善できたり非同期のデータ取得周りの処理を改善できました。
yarn
へのビルド対応package.json
とwebpack.config.js
の作成
- GoogleスプレッドシートからのJSONデータ読み込み機能を
jQuery.Ajax
→async/await
やfetch
で実装
そして、前回に掲げた今後の目標は下記の通りですが…:
- ★webpackでJSをひとまとめにする
- ★
import/export
で外部JSモジュールを読み出せる様にする - ★JSファイルの構造化
- babel対応
- 脱jQuery化
今回では上記の星が付いているところはなんとか取り組めました。
また、それとは別に下記のことにも取り組めてます!
- JSの共通部分をModule化※完璧なモジュール化とはなっていない
- モジュール化したJSをTypeScript化
- Interfaceの導入
- 余分なnode_modulesを削除
個人的にTypeScriptの導入ができたのはとってもよかったなと思ってます!
ようやっと私もTypeScripterに一歩近づけたのかなと…。
変更後のソースコードは下記に。今回もまただーいぶ様変わりしました…。
github.com
やったことを詳しく
ここからは、やってきたことの中で気になったこと等をつらつらと書いていきます。
webpackでJSをモジュール化&ひとまとめに
webpackを使用するにはyarnコマンド一発二発で済む話ですが…。
それを自らのプロジェクトに適用できるようにするためのwebpack.config.js
という設定ファイルの作り方にすごく難航しました。
どこかしらのコードをコピペし、自分なりに少し改変して…という手っ取り早いやり方を試していたのですが、やってもやってもエラーを吐く無限地獄に。
下記の記事にもあるように「これはある程度でもいいから一度webpack.config.js
を根本理解した方がいいな…」、そう思った筆者はwebpack-v3.8.1を元にされている下記の記事を参考にすることに…。
qiita.com
ただ、公式Docsを見ると下記のような記述が書かれており:
Since version 4.0.0, webpack does not require a configuration file to bundle your project.
日本語訳:バージョン4.0.0以降、webpackはプロジェクトをバンドルするための設定ファイルを必要としません。
※参考: webpack-Docs/Concepts
バージョンUPによってかなり様変わりしている予感がしましたので…webpackについてはまた別途記事にまとめようかと思います。
とりあえず出来たwebpack.config.js
を下記に貼り付けておきます。
※後で見返してもわかる様コメントをベッタベタに貼ってます
module.exports = { // webpackがビルドを始める際の開始点となるjsファイル // このエントリーポイントがどのライブラリやモジュールに依存しているかを // webpackは判断&処理し、bundleファイルに記述 entry: { index: [ __dirname + "/src/index.js" ], flow: [ __dirname + "/src/flow.js" ], form1: [ __dirname + "/src/form1.js" ], form2: [ __dirname + "/src/form2.js" ] }, // entryで作成されたbundleファイルをどのように出力するかを定義 output: { path: __dirname + '/dist', filename: "[name].js" }, // webpackは本来JSしか扱えないが、 // Loaderというものを使えばそれ以外のファイルを扱うことも可能となる // その際には、「rules」にてwebpackが理解可能な「module」へと // ファイルを変換してやる必要がある // (そうすることでbundleファイルが作成可能) // rulesには下記2つを記載する: // - test: 対象となるファイルの形式、正規表現で記載 // - use: testで指定した対象となるファイルをどのように処理して // webpackが処理可能な形にするかを指定 // 例えば下記のコードでは: // 「拡張子が.ts の場合、 // bundleファイルに追加する前に // ts-loaderで変換して処理を行うように」 // …という意図になる module: { rules: [{ test: /\.ts$/, use: "ts-loader" }] }, resolve: { // ビルド対象に含めたいファイルの拡張子をArray型で指定する // デフォルトは: ["", ".webpack.js", ".web.js", ".js"] // …が、最新のwebpackVerではresolve.extensionsの項目がない…要調査 // 参考: https://dackdive.hateblo.jp/entry/2016/04/13/123000#resolve extensions: [".ts"] } };
JSの共通部分をModule化
やりたかったことの1つ「import/export
で外部JSモジュールを読み出せる様にする」と絡んでくるところで…。
要は「1画面1ファイル」「共通処理はcommon.js
に記載」という今のJavaScriptの構成を変えたかったんですよね。
前回では「1画面1ファイル」部分の共通処理をcommon.js
に記載するところまでで止まり、結局手をつけられませんでした。
そこで今回手をつけようと思ったのですが、画面ごとの処理が記載されているところは繊細なロジックも書かれていたため…。
まずは共通処理を記載しているcommon.js
の整理から始めることに。
モジュール化する前のcommon.js
には、下記の様な関数が並んでました。
- Googleスプレッドシート操作系:
getGoogleSpreadSheetData
: 指定したGoogleスプレッドシート情報を取得するSetGoogleSpreadSheetDate
: Google スプレッドシートの情報を入れる関数errorGoogleSpreadSheet
: GoogleSpreadSheet取得にエラーが出た際の処理
- lib的な使い方をする…便利関数的な
floatFormat
: 小数点の計算getDevice
: ユーザーのデバイスを返すWaiting
: Now-Loading画面を表示
- モーダル表示系:
displayModalWindow
: モーダルウィンドウ表示関数ModalWindow_close
: モーダルウィンドウを閉じる関数
…改めて見ると、これらが同一ファイルに記載されていたの辛いですね…。アイター
上記ファイルを用途ごとに分別し…下記の3つのファイルに分割してみました!
module/common_module.js
module/modal_module.js
module/googleSpreadSheet_module.js
これだけでかなりさっぱりした様にも思えます…。
今後はこの様に分割していきたい!…ですが、あまり分割しすぎると大変なことにもなりますしね。
後述するインタフェースも使ってうまい様にやっていきたいです。
ちなみに、Babel化については今回諦めました。
参考記事見つけたんですけどね…力及ばず。
ics.media
まーこれは次回いけたらということで!
TypeScriptの採用
今回TypeScriptを使用したのは明確な理由がありました。
というのも、今回のモジュール化でどうしても「メンバ変数」を利用したくて…。
ただ、調べてみるとどうも「JavaScriptのクラスはメンバ変数を親しみある記述で書けない」らしく。
TypeScriptだとこんな感じに、JavaやC#で書くのと同じ様に定義できるメンバ変数を:
class ModalModule { private CurrentScrollY any; }
JavaScriptだとこんな感じにしないと定義できないんですよね…邪魔なthis
が入っちゃう…:
var ModalModule = function() { this.CurrentScrollY; }
そこで、書きやすい形で書ける様にするためTypescript導入を考えてみました。
「書き方が違うだけで導入するかよぉ!?」と、今思うと疑問ですが…まーその時の私が「なんでもいいからTypeScriptを使う動機が欲しい」と思ってたのかもしんないです。
((めっちゃ流行ってますしね
下記の記事を参考に、yarnでTypeScriptに必要なものをインストールし…:
qiita.com
webpackにTypeScript用の記述を行い、環境を整えやっていきました。
ちなみにこのTypeScript環境構築は、不思議と全然つまづくことなくできました…SCSSの環境構築ではめっちゃ戸惑ったのになんなんだ。
また、本当は各画面ごとの処理を記述しているメイン部分のJavaScriptもTypeScriptにしたかったのですが…。
Bootstrap周りの記述がある影響でメイン部分のJSはJSのまま、ModuleのみTypescript化にしました。
メイン部分のTypeScript化も次なる課題ですな…!!
インタフェースの実装
お仕事でもプログラミングをしている最中、インタフェースについて知る機会がありました。
「そもそもインタフェースとは何か」、職場の先輩からの受け売りとなりますが…。
- そのクラスにはどんなプロパティ/メソッドがあるのかを定義のみする(実装は行わない)
メリット:
- インタフェースにより「クラス外のプログラムから見た時にどんなプロパティ/メソッドがあるか&どの様にそのクラスを用いれるのか」を保証できるため、内部的なロジックが少しだけ異なる複数の似たようなクラスを作る際に「インタフェースの形さえ守っていれば他プログラムとの衝突がなくなる」様になる
- インタフェースを作ることで「インタフェースの中側を作る人」と「インタフェースの外側を作る人」とで分業が可能(インタフェースが共通仕様書になるイメージ)
- 後から似た様なクラスを作成する際も、インタフェースを守ればどんな箇所でも動くクラスが記述できる
…というものであるようです。
「複雑なビジネスロジックにうまく対応する必要がある際に、インタフェースを上手に使うと分業化できる&プログラムも綺麗に作れるよ」というのも先輩からの言です。
いやーこんなレベルの高いこと聞けて幸せですよ本当…。
ただ、私は実際にはインタフェースを利用したことがなかったため…。
小規模でもWebシステムとなっているこちらのプログラムに実装してみるのもいい機会かなということで、とりあえずいれてみました。
また、インタフェースを別フォルダ化し管理しやすい様にし…最終的な構造は下記の様に:
interface/common_interface.js
interface/modal_interfacejs
interface/googleSpreadSheet_interface.js
module/common_module.js
module/modal_module.js
module/googleSpreadSheet_module.js
index.js
flow1.js
flow2.js
…今思えば、共通モジュールの部分にインタフェースを入れても意味はなかった気がしますね…まぁいいや!
きっとインタフェースの威力を発揮するのはindex.js
やflow1.js
あたりのメインロジック部分に導入した際でしょう…この辺りは共通しているロジックも多いですからね。
今後が楽しみです!
おわりに
これまでの成果も合わせて、改めて今回のリファクタリング結果を見てみたいと思います:
第1回目の変更点:
npm
yarn
へのビルド対応package.json
とwebpack.config.js
の作成- GoogleスプレッドシートからのJSONデータ読み込み機能を
jQuery.Ajax
→async/await
やfetch
で実装
第2回目の変更点:
- webpackでJSをひとまとめにする
import/export
で外部JSモジュールを読み出せる様にする- JSファイルの構造化
- JSの共通部分をModule化※完璧なモジュール化とはなっていない
- モジュール化したJSをTypeScript化
- Interfaceの導入
- (Sass化…の環境整備)
振り返ってみると、第1回目では主に環境周り、そして今回は主にJS周りのリファクタリングをしたんだなと…。
ちょっとずつ良くなっていっていることがわかっていいですね( ˘ω˘ *)
また、次回以降では下記のことに取り組めたらなと!:
- Babel化
- Sass化: SassはSassの環境を整えただけで、結局軌道には乗らせていないので…
- JSファイルの構造化
- ソースコード部分のより詳細なModule化
- メイン部分のTypeScript化
- 脱jQuery化
- CHANGELOG.mdの整備
それではまた|д゚)