より良いタイプスクリプト.JavaScriptで


[注]このライブラリを参照しているライブラリ-allow - 今すぐNPMパッケージで利用可能です.ここで見つけることができます.https://www.npmjs.com/package/@toolz/allow ]
私の前の記事では、私はタイプスクリプトが理由である理由をレイアウトしました.誤ったセキュリティ意識といくつかの具体的な利益のために戻って余分な仕事の多く.
私は再びそれらの引数を再ハッシュしません.興味があればその記事を閲覧することができます.本稿では、私の実用的で戦術的な解決策を、純粋にJavaScript環境で概説します.
FWW、私はこの1つの背中の3月に似ている記事を書いた.私のアプローチの基礎は根本的に変わりませんが、私の実装の詳細は全く異なります.
この記事のコードはすべてこのファイルで参照できます.
https://github.com/bytebodger/spotify/blob/master/src/classes/allow.js
これは私のspotify toolzプロジェクトの一部ですが、私はまた、私のタイプをチェックライブラリに移植されます.

型チェック目標


前の記事からコンテンツを再設定せずに、タイプチェックに重要ないくつかの重要な要素があると言います.
  • 私は、ランタイムでタイプ安全性を保証することにほとんど専念します.あなたのアプリがコンパイルほとんど私に何も意味しないことを私に言う.あなたのアプリケーションをコンパイルします.私は靴をつかんだ.我々はがけを追い払わなかった.私たちはすべてクッキーを得るか?私のアプリがコンパイルされた場合、それはそれが実行する保証はありません.私のアプリを実行する場合は、コンパイルすることが保証されます.それで、私はランタイムに集中します.
  • 私はほぼ排他的にアプリケーション間のインターフェイスで型安全性を確保することを気に.それらは私のアプリといくつかの外部データソースの間のインターフェース、例えばAPIであるかもしれません.または、それは1つの機能ともう一つの間のインターフェースでありえました.それは交換が私のアプリの外に到達した場合、または交換が完全にアプリによってカプセル化されているかどうかは重要ではありません.ポイントは、私は“クリーン”の入力を得ている知っている場合は、はるかに大きな可能性が私はアプリ内で書かれた任意のロジックが期待通り実行されます.
  • タイプチェックはきれいでなければなりません.高速.効率的.コンパイラに機能コードを説明しようとして数え切れないほどの時間を費やす必要があるなら、その型チェックは機能よりハードルです.これはまた、タイプチェックがそれがそうである必要があるように完全でなければならないことを意味します-そして、これ以上.言い換えると、100キーを含んでいるAPI応答からオブジェクトを受け取っているならば、私はそれらのキーのうちの3つを使用しているだけです、そして、私は他の97を定義する必要はありません.
  • 「守備プログラミング」は最小限に保たれるべきです.前の記事では、連続したストリームを使わなければならない頭痛について良い点を作りましたif 適切なデータを受信したことを確認します.私は徹底的にこれを理解する.常に新しい記述を必要とするどんな解決でもif チェックは非解決です.

  • 基本的アプローチ


    前の記事では、1つのシナリオを概説しましたnumber - しかし、まだ引数が実際にあることを保証するために、関数の中でチェックする必要があるでしょうnumber . シナリオは次のようになります.
    const createId = (length = 32) => {
      if (isNaN(length)) length = 32;
      // rest of function...
    }
    
    単純な事実は、我々が実行時問題をターゲットにしている限り、実際にはこの方法はないということです.だからこそ、私は、ランタイムバリデーションでほとんどすべての検証を集中します.私は成功したコンパイルと来る偽のセキュリティに興味がないので.

    I want to know, in real-time, whether something will fail at runtime.


    それで、この問題に対する私の「答え」は、私が関数本体検証の内部を排除することができないならば、私は少なくとも彼らをきれいで、速くて、効率的にしたいです.マニュアルをファンシークラフトする必要はありませんif コンディション.
    上にリンクされたコードでは、基本的な検証クラスがありますallow . allow さまざまなデータ型をチェックする一連のメソッドが含まれます.
    私の新しいアプローチの1つの重要な違いは、各々の方法が連鎖するということです.これは、1行のコードですべての検証を実行できることを意味します.関数が1つの引数か1ダースを持っているかどうかにかかわらず、それらの入力を検証するのに費やされる関数の中には、十分なLOCがありません.
    もう一つの違いは、私の最新のアプローチはどんなバリデーション値も返さないということです.単に方法throw を返します.何も起こらない.それはまさに私がやりたいことです.
    もちろん、生産において、「失敗」は何らかの種類のサイレントエラーに帰着するようにコードを調整することができます.しかし、キーは、関数が“悪い”データを受信した場合、私はいくつかの方法で救済するためにその関数が欲しいということです.
    したがって、以下の例はすべてこのようになります.
    const myFunction = (someBoolean = false, someString = '') => {
      allow.aBoolean(someBoolean).aString(someString);
      // rest of function...
    }
    

    最も簡単な検証


    私はこれらの「単純な」と呼びます、値を通過するために何もすることがなくて、それが検証するかどうか見るので.次のようになります.
    // booleans
    const myFunction = (someBoolean = false) => {
      allow.aBoolean(someBoolean);
      // rest of function...
    }
    
    // functions
    const myFunction = (someCallback = () => {}) => {
      allow.aFunction(someCallback);
      // rest of function...
    }
    
    // React elements
    const myFunction = (someElement = <></>) => {
      allow.aReactElement(someElement);
      // rest of function...
    }
    
    これらについてあまりにも不思議な何も.aBoolean() , aFunction() , and aReactElement() は、それぞれのデータ型を受信しない場合はすべて失敗します.

    会館


    enumは許容できる値の単純な配列に対してチェックできます.またはオブジェクト内で渡すことができます.この場合、オブジェクトの値が許容値を収集するために使用されます.
    // one of...
    const statuses = ['open', 'closed', 'hold'];
    
    const myFunction = (status = '') => {
      allow.oneOf(status, statuses);
      // rest of function...
    }
    
    const colors = {
      red: '#ff0000',
      green: '#00ff00',
      blue: '#0000ff',
    }
    const myFunction = (color = '') => {
      allow.oneOf(color, colors);
      // rest of function...
    }
    


    文字列を検証する最も簡単な方法は次のようになります.
    // string
    const myFunction = (someString = '') => {
      allow.aString(someString);
      // rest of function...
    }
    
    しかし、しばしば、空のストリングは本当にあなたの関数の論理の目的のために有効なストリングでありません.そして、あなたがAを示したいとき、他の時間があるかもしれませんminLength またはmaxLength . したがって、以下のように検証を使用することもできます.
    // strings
    const myFunction = (someString = '') => {
      allow.aString(someString, 1);
      // this ensures that someString is NOT empty
      // rest of function...
    }
    
    const myFunction = (stateAbbreviation = '') => {
      allow.aString(stateAbbreviation, 2, 2);
      // this ensures that stateAbbreviation is EXACTLY 2-characters in 
      // length
      // rest of function...
    }
    
    const myFunction = (description = '') => {
      allow.aString(description, 1, 250);
      // this ensures that description is not empty and is <= 250 
      // characters in length
      // rest of function...
    }
    


    文字列のように、数値は単に数値であるかどうかを検証できます.または、範囲内で検証することができます.私も、めったに使いませんallow.aNumber() でもよく使うallow.anInteger() . 多くの場合、私は数字を期待しているので、彼らは本当に整数である必要があります.
    // numbers
    const myFunction = (balance = 0) => {
      allow.aNumber(balance);
      // can be ANY number, positive or negative, integer or decimal
      // rest of function...
    }
    
    const myFunction = (age = 0) => {
      allow.aNumber(age, 0, 125);
      // any number, integer or decimal, >= 0 and <= 125
      // rest of function...
    }
    
    const myFunction = (goalDifferential = 0) => {
      allow.anInteger(goalDifferential);
      // any integer, positive or negative
      // rest of function...
    }
    
    const myFunction = (id = 0) => {
      allow.anInteger(id, 1);
      // any integer, >= 1
      // rest of function...
    }
    

    オブジェクト


    これは特定の型のオブジェクトを定義するためではありません.我々はそれをカバーしますanInstanceOf . これは、何かが一般的な「オブジェクト」であるという定義に適合するかどうか、そして、あなたが望むならば、オブジェクトが特定の「サイズ」であるかどうかチェックします.
    これも除くnull ( JavaScriptはobject ) と配列(これも技術的にもオブジェクトです).あなたは、1分で配列のために特に完全な検証のセットがあるのを見ます.
    // objects
    const myFunction = (user = {}) => {
      allow.anObject(user);
      // can be ANY object - even an empty object
      // rest of function...
    }
    
    const myFunction = (user = {}) => {
      allow.anObject(user, 1);
      // this doesn't validate the shape of the user object
      // but it ensures that the object isn't empty
      // rest of function...
    }
    
    const myFunction = (user = {}) => {
      allow.anObject(user, 4, 4);
      // again - it doesn't validate the contents of the user object
      // but it ensures that the object has exactly 4 keys
      // rest of function...
    }
    

    インスタンス


    これらはオブジェクトの形を検証します.彼らはその形内のデータ型を検証しないことに注意してください.そのレベルの検証を提供するために拡張できますか?はい.私は私の個人的なプログラミングでそのレベルの検証を必要としますか?いいえ、今、それはちょうどキーの存在に集中します.
    また、再帰的に検証されます.あなたがオブジェクトを持っている場合、オブジェクトが含まれている場合、オブジェクトを含む場合は、anInstanceOf() . anInstanceOf() オブジェクトと、それをチェックする“モデル”オブジェクトが必要です.モデル内のすべてのキーが必要と見なされます.しかし、与えられたオブジェクトはモデルオブジェクトに存在しない追加のキーを持つことができます.
    // instance of...
    const meModel = {
      name: '',
      address: '',
      degrees: [],
      ancestors: {
        mother: '',
        father: '',
      },
    }
    
    let me = {
      name: 'adam',
      address: '101 Main',
      degrees: [],
      ancestors: {
        mother: 'mary',
        father: 'joe',
      },
      height: '5 foot',
    }
    
    const myFunction = (person = meModel) => {
      allow.anInstanceOf(person, meModel);
      // rest of function...
    }
    myFunction(me);
    // this validates - me has an extra key, but that's ok
    // because me contains all of the keys that exist in 
    // meModel - also notice that meModel is used as the 
    // default value - this provides code-completion clues
    // to your IDE
    
    let me = {
      name: 'adam',
      degrees: [],
      ancestors: {
        mother: 'mary',
        father: 'joe',
      },
      height: '5 foot',
    }
    myFunction(me);
    // this does NOT validate - me is missing the address
    // key that exists in meModel
    

    アレイ


    最も単純な検証は、値が配列であることを保証するだけです.この検証に加えて、配列が空でないか、特定の長さのものであることを確認できます.
    // arrays
    const myFunction = (someArray = []) => {
      allow.anArray(someArray);
      // rest of function...
    }
    
    const myFunction = (someArray = []) => {
      allow.anArray(someArray, 1);
      // this ensures that someArray is NOT empty
      // rest of function...
    }
    
    const myFunction = (someArray = []) => {
      allow.anArray(someArray, 2, 2);
      // this ensures that someArray contains EXACTLY 2 elements
      // rest of function...
    }
    
    const myFunction = (someArray = []) => {
      allow.anArray(someArray, 1, 250);
      // this ensures that someArray is not empty and is <= 250 
      // elements in length
      // rest of function...
    }
    

    …の配列.


    何かが配列であるということを知っているだけで、しばしば不十分です.配列に特定のデータ型の要素が含まれていることを確認する必要があります.つまり、整数の配列や文字列の配列などがあります.
    これらはすべて来るminLength/maxLength オプションの引数で、配列が空でないか、特定のサイズであることを保証できます.
    // array of arrays
    const myFunction = (someArray = [[]]) => {
      allow.anArrayOfArrays(someArray);
      // rest of function...
    }
    
    // array of instances
    const myFunction = (someArray = [meModel]) => {
      allow.anArrayOfInstances(someArray, meModel);
      // rest of function...
    }
    
    // array of integers
    const myFunction = (someArray = [0]) => {
      allow.anArrayOfIntegers(someArray);
      // rest of function...
    }
    
    // array of numbers
    const myFunction = (someArray = [0]) => {
      allow.anArrayOfNumbers(someArray);
      // rest of function...
    }
    
    // array of objects
    const myFunction = (someArray = [{}]) => {
      allow.anArrayOfObjects(someArray);
      // rest of function...
    }
    
    // array of strings
    const myFunction = (someArray = ['']) => {
      allow.anArrayOfStrings(someArray);
      // rest of function...
    }
    

    実世界例


    私のspotify toolzアプリでは、私は現在、このランタイムタイプのチェックを使用しています.ここでそのコードを見ることができます.
    https://github.com/bytebodger/spotify
    しかし、ここでは私の関数で見たものの例を示します.
    const getTrackDescription = (track = trackModel, index = -1) => {
      allow.anInstanceOf(track, trackModel).anInteger(index, is.not.negative);
      return (
         <div key={track.id + index}>
            {index + 1}. {track.name} by {getTrackArtistNames(track)}
         </div>
      );
    }
    
    const comparePlaylists = (playlist1 = playlistModel, playlist2 = playlistModel) => {
      allow.anInstanceOf(playlist1, playlistModel).anInstanceOf(playlist2, playlistModel);
      if (playlist1.name.toLowerCase() < playlist2.name.toLowerCase())
         return -1;
      else if (playlist1.name.toLowerCase() > playlist2.name.toLowerCase())
         return 1;
      else
         return 0;
    };
    
    const addPlaylist = (playlist = playlistModel) => {
      allow.anInstanceOf(playlist, playlistModel);
      local.setItem('playlists', [...playlists, playlist]);
      setPlaylists([...playlists, playlist]);
    }
    
    const addTracks = (playlistId = '', uris = ['']) => {
      allow.aString(playlistId, is.not.empty).anArrayOfStrings(uris, is.not.empty);
      return api.call(the.method.post, `https://api.spotify.com/v1/playlists/${playlistId}/tracks`, {uris});
    }
    
    すべての関数シグネチャには、1行のコードで実行時検証が与えられます.それは明らかに検証を使用しないよりも多くのコードです.しかし、それはミックスにTSを積むよりずっと簡単です.

    結論


    これは書替えですか.まあもちろん.しかし、この1つの小さなライブラリは正直に私にとってはるかに多くの値を提供します.
    私は自分自身がコンパイラと“戦っている”ということはありません.コンパイラチェックとランタイムチェックを書く必要はありません.私は、ちょうど私の関数署名を検証します、そして、私は、私の論理、内容を実行時に、データ型が私が彼らがそうであると思っているものであるという知識に書きます.
    おそらく重要なことに、私のIDEは“これを”取得します.たとえば、オブジェクトのモデルを定義し、関数シグネチャの既定値として使用する場合、user オブジェクトはparents を含めることができますmother キーとfather キー.
    私がここでやっているタイプチェックに経験的な制限があることに気づくかもしれません.たとえば、オブジェクトの形状を検証していますが、そのオブジェクト内のすべてのキーが特定の種類のデータを含むことを確認しません.私は将来これを加えるかもしれません、しかし、私はこれがどんな種類の「重要な欠陥」であると思いません.
    私が図形を通過しているならば、私は、与えられたオブジェクトが私が必要とする形に適合することを確認することができます、それらの形のデータが「正しい」という心配がほとんどしばしばありません.一般的に、“悪い”オブジェクトを受け取ったなら、オブジェクトが必要な形に適合しないという事実によって検出することができます.オブジェクトが正しい形であるが、予期しないデータ型を含んでいるのは非常にまれです.