どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。
前回のおさらい
前回は、「柔軟なインターフェイスのために、構造的な型付けを使う」をお届けしました。
[EffectiveJavaScript輪読会8]配列と「配列のようなもの」を区別しよう
どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。前回のおさらい前回は、「柔軟なインターフェイスの...
今日は7回目で、「過剰な型強制を防ごう」をお届けします。
テキスト第6章「ライブラリとAPI設計」の項目59に対応しています。
今日のアジェンダ
- 暗黙の型変換
- 防御的プログラミング
- Mix-in
暗黙の型変換
一瞬だけおさらいですが、JavaScriptには、暗黙の型変換があります。
他の言語を学ぶ方からしたら、とんでもないことです。
function myFunction8_59_01() {
const square = x => x * x;
console.log(square('3')); //9
}
前回のクラスは、文字列を引数で受け取ることを想定していませんでした。
function myFunction8_59_02() {
/** クラスBitVector */
class BitVector {
constructor() {
this.bit = [];
}
/** bitを格納するメソッド */
enable(x) {
if (typeof x === 'number') { //常に真
this.enableBit(x);
} else {
for (let i = 0; i < x.length; i++) {
this.enableBit(x[i]);
}
}
}
/** ビットを格納するメソッド */
enableBit(x) {
this.bit.push(x);
}
/** ビット配列に引数があるか返すメソッド */
bitAt(x) {
return this.bit.includes(x) ? 1 : 0;
}
}
const bits = new BitVector();
bits.enable('3'); //数値型?配列?配列のようなもの?
console.log(bits.bitAt(3)); // => 0
console.log(bits.bit); // => ['3']
}
防御的プログラミング
予期せぬ引数や型が渡されたときに、例外をはくように、あらかじめバグになりそうな芽を早めにつぶすことを「防御的プログラミング」と呼びます。
メソッドの引数に、条件分岐を設定することで、型のチェックを行います。
function myFunction8_59_03() {
/** クラスBitVector */
class BitVector {
constructor() {
this.bit = [];
}
/** bitを格納するメソッド */
enable(x) {
if (typeof x === 'number') {
this.enableBit(x);
} else if (typeof x === 'object' && x) {
for (let i = 0; i < x.length; i++) {
this.enableBit(x[i]);
}
} else {
throw new TypeError('expected number or array-like');
//数値または配列のようなオブジェクトを期待している
}
}
/** ビットを格納するメソッド */
enableBit(x) {
this.bit.push(x);
}
/** ビット配列に引数があるか返すメソッド */
bitAt(x) {
return this.bit.includes(x) ? 1 : 0;
}
}
const bits = new BitVector();
bits.enable('3'); //TypeError: expected number or array-like
}
Mix-in
JavaScriptには、複数のスーパークラスから継承する多重継承は実装できませんが、代替えとしてMix-in(ミックスイン)という手法があります。
今回は、型のチェックをするオブジェクトを作成し、Mix-inで実装したコードをご紹介します。
function myFunction59_04() {
const guard = {
guard(x) {
if (!this.test(x)) {
throw new TypeError('excepted' + this);
}
},
or(other) {
const result = Object.create(guard);
const self = this;
result.test = function (x) {
return self.test(x) || other.test(x);
};
const destriction = this + ' or ' + other;
result.toString = function () {
return destriction;
};
return result;
}
};
/** Unit32クラス */
class Uint32 {
test(x) {
return typeof x === 'number' && x === (x >>> 0);
}
toString() {
return 'uint32';
}
}
//mix-in処理
Object.assign(Uint32.prototype, guard);
const uint32 = new Uint32();
/** ArrayLikeクラス */
class ArrayLike {
test(x) {
return typeof x === 'object' && x && uint32.test(x.length);
}
toString() {
return 'array-like Object';
}
}
//mix-in処理
Object.assign(ArrayLike.prototype, guard);
const arrayLike = new ArrayLike();
/** BitVectorクラス */
class BitVector {
constructor() {
this.bit = [];
}
/** bitを格納するメソッド */
enable(x) {
uint32.or(arrayLike).guard(x);
if (uint32.test(x)) {
this.enableBit(x);
} else if (arrayLike.test(x)) {
for (const n of x) {
this.enableBit(n);
}
}
}
/** ビットを格納するメソッド */
enableBit(x) {
this.bit.push(x);
}
/** ビット配列に引数があるか返すメソッド */
bitAt(x) {
const result = this.bitAt.includes(x);
return result ? true : false;
}
}
const bits = new BitVector();
bits.enable([4, 3]);
console.log(bits); // => { bit: [ 4, 3 ] }
bits.enable(5);
console.log(bits); // => { bit: [ 4, 3, 5 ] }
}
Mix-inについては、別途ブログ化します。
まとめ
以上で、「過剰な型強制を防ごう」をお届けしました。
一旦、これで輪読会は終了です。
みなさん、長い間お疲れ様でした。
参考資料
このシリーズの目次
- [EffectiveJavaScript輪読会8]一貫した規約を維持しよう
- [EffectiveJavaScript輪読会8]undefinedは「値なし」として扱う
- [EffectiveJavaScript輪読会8]オプションオブジェクトで、キーワード付き引数群を受け取ろう
- [EffectiveJavaScript輪読会8]不必要な状態を排除する
- [EffectiveJavaScript輪読会8]柔軟なインターフェイスのために、構造的な型付けを使う
- [EffectiveJavaScript輪読会8]配列と「配列のようなもの」を区別しよう
- [EffectiveJavaScript輪読会8]過剰な型強制を防ごう