おはようございますこんにちわこんばんわ。どうもぶたです。
開発を行う際に多かれ少なかれライブラリを利用しますよね。
皆様はどのようにライブラリを利用されていますか?
また、バージョン追随はどこまでされてますでしょうか?
メジャーバージョンのバージョンアップ対応とかしんどい時ありますよね。
全てのケースにおいて当てはまるわけではありませんが、
ライブラリをラッピングするととても便利です。
初学者からすると、「なんでこの処理ライブラリの処理を別関数(あるいはクラス)に記述し直してるんだ?」となったことがあるかと思います。
このあたりを書いていければと思います。今回は関数メインで。
なお、どの言語においても考え方は変わらないと思いますが、TypeScriptベースで記載させていただきます。
クラスでの実装については別記事にしましたのでそちらをご参照ください。
ライブラリをラッピングするとは
ライブラリをラッピングするとは、インストールしたライブラリを直接利用せずに関数やクラスでラッピングをして間接的に利用することですが、
文章だけだと分かりづらいと思うのでコードで書いてみます。
momentという日付操作のライブラリを用いて、関数でラッピングする形を想定してみます。
import moment from 'moment';
// ライブラリを直接使う形
const now1 = moment().format('YYYYMMDD')
console.log(now1)
// ライブラリをラッピングする形
const wrappedNow = () => {
return moment().format('YYYYMMDD')
}
const now2 = wrappedNow()
console.log(now2)
ラッピングの実装は非常にシンプルです。
ラッピングしない場合、moment().format('YYYYMMDD')
と呼び出していますが、
この呼び出しを関数内に閉じ込めるだけです。
車輪の再発明のようで、やる意味あるのか、と疑問に感じる方もいると思います。
ラッピングの何が嬉しいのか
では、ラッピングすると何が嬉しいのかというと、下記のメリットがあります。
- インターフェースをよりシンプルにできる
- 影響範囲を限定的にできる
- 独自ロジックの追加が容易
順番に見て行きます。
インターフェースをよりシンプルにできる
ライブラリの利用方法が多彩な場合、開発者はそのライブラリをどう使うかという選択を迫られます。
言い換えると、その利用方法を知っているかいないかで、実装が変わってくるということです。
例えば、下記のコードを考えてみましょう。
import moment from 'moment';
const now = moment();
const now = moment.now();
const now = moment.utc();
いずれもmomentに用意されたインターフェースですが、現在日時を取得する処理としていずれも間違ってはいなそうです。
moment()
、moment.now()
、moment.utc()
のどれを利用すべきなのか迷うこともなく、開発を進めたいですよね。
ラッピングをすることで、内部でどれが利用されているか気にする必要もなく、利用方法を統一でき、開発者が簡単にライブラリを利用することができます。
moment()
、moment.now()
、moment.utc()
実際には下記のように差があります。
// moment()
Moment<2023-07-13T20:33:20+09:00>
// moment.now()
1689248000142
// moment.utc()
Moment<2023-07-13T11:33:20Z>
影響範囲を限定的にできる
さて、冒頭から違和感を感じてた方もいらっしゃると思います。
実は、moment
は現在利用が推奨されていません。
Project Status
Moment.js is a legacy project, now in maintenance mode. In most cases, you should choose a different library.
「ほとんどの場合、別のライブラリを選ぶべきです。」と。
ライブラリの乗り換えというのは頻繁にあることではないですが、時にはその決断もあると思います。
ここでは、moment
の代わりにdayjs
を利用するとしましょう。
仮に、ラッピングをせずに直接利用している場合、moment
が利用されている箇所すべてを修正する必要が出てきます。
10ファイルから利用していれば10ファイル、100ファイルから利用していれば100ファイル直す必要があります。
ラッピングをして利用していた場合、moment
が利用されている箇所はそのラッピング箇所のみになります。
// import moment from 'moment';
import dayjs from 'dayjs';
const wrappedNow = () => {
// return moment().format('YYYYMMDD')
return dayjs().format('YYYYMMDD')
}
// 呼び出し側に変更は必要ない
const now = wrappedNow()
console.log(now)
また、ライブラリのバージョンアップによって、
メソッドが削除されるケースやメソッド名が変わるケースもあると思います。
その変更への対応も同様にラッピング箇所に限定することができます。
ライブラリのバージョンアップ
メソッド削除やメソッド名変更は、多くの場合、
リリースノートなどにBREAKING CHANGESとして対応の必要な変更が掲載されます。
また、JSDocに@deprecated
という記載がされていることも多々あります。
独自ロジックの追加が容易
ライブラリを用いるけど、それだけじゃ足りなくて、独自のロジックが必要。しかも使いまわしたい。
そんなこともよくありますよね。
例えば、日本国内向けのちょっとお堅い感じのアプリケーションを想定して、日付の出力に元号を用いたいとします。
YYYY
のような指定はできないですし、少し手間がかかりそうですね。
ラッピングを利用しない場合、その手間な処理を毎度書く必要があり、結果的に先に挙げたメリットを放棄する形になってしまいます。
余談ですが、エンジニアって「もっと上手く書けるはず」と向上心高い方多いですよね。
その結果、似てるけど微妙に違うコードが点在する、ってことありませんか?
ラッピングをすれば、先に述べた通り、そのファイル内のみでライブラリの利用とロジックの追加を済ませることができ、インターフェースを統一することができます。
import dayjs, { Dayjs } from 'dayjs';
const wrappedNow = () => {
return dayjs().format('YYYYMMDD')
}
const toEra = (day: Dayjs | string): {
era: string;
year: number;
} => {
const targetDate = dayjs(day);
const eraFormatter = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', { era: 'long' });
const eraStr = eraFormatter.format(targetDate.toDate());
const era = eraStr.slice(0, 2);
const year = Number(eraStr.replace(era, ''));
return { era, year };
};
最後に
ラッピングのメリットをまとめてみました。
インターフェースをシンプルにしてプロジェクト内での利用方法を統一できると、
保守性を高めることができ、
開発時のブレや迷いを防ぐことができます。
また、記事中では触れませんでしたが、テスト時のモック化にも役立ちます。
少しでも皆様の開発の助けになれば幸いです。