簡単なリファクタリング


リファクタリングは、関数、メソッド、クラス、またはアプリケーション全体を取得し、その機能を変更せずに変更することです.元の行動を管理しながら、何らかの方法でそれを改善する.
私は最近、働いたMetric/Imperial converter プロジェクトfreecodecamp.org そして、少しリファクタリングを行う機会がありました.
最初の実装から、うまくいけば良い、最後のフォームに導かれた通路を書き留めて、共有したいです.
私は、私が奇跡的な仕事をしたと主張していません、あるいは、私は若干の隠れた真実を明らかにしています.私は単にコーディング、共有し、フィードバックを得る私の考えを詳しく説明しようとしている!
では、ダイビングをしましょう🏊‍♂️
プロジェクトページの仕様を見つけることができますが、興味があるのは次のとおりです.

I can use fractions, decimals or both in my parameter (ie. 5, 1/2, 2.5/6), but if nothing is provided it will default to 1.


入力文字列には、変換した単位を含める必要があります.
4gal
1/2km
5.4/3lbs
kg
プロジェクトの青写真を実装するgetNum(input) 関数は、入力の数値部分を解析することです.それが私たちが注目する機能です.
リファクタリングは、コードの動作を変更したりバグを追加したりしないことを保証するテストのスイートなしでは不可能です.
だからここにある!
    test('Whole number input', function(done) {
      var input = '32L';
      assert.equal(convertHandler.getNum(input),32);
      done();
    });

    test('Decimal Input', function(done) {
      var input = '3.2L';
      assert.equal(convertHandler.getNum(input),3.2);
      done();
    });

    test('Fractional Input', function(done) {
      var input = '3\/2L';
      assert.equal(convertHandler.getNum(input),1.5);
      done();
    });

    test('Fractional Input w/ Decimal', function(done) {
      var input = '3.5\/2L';
      assert.equal(convertHandler.getNum(input),1.75);
      done();
    });

    test('Invalid Input (double fraction)', function(done) {
      var input = '3\/4\/2L';
      assert.throws(convertHandler.getNum.bind(convertHandler.getNum, input), 'invalid number');
      done();
    });

    test('No Numerical Input', function(done) {
      var input = 'L';
      assert.equal(convertHandler.getNum(input),1);
      done();
    });
ここでは、プロジェクトによって提供されるテストに固執します.より多くのケースと条件をテストすることができますが、簡単にしましょう.
最初の実装は以下の通りです.
function ConvertHandler() {

  this.getNum = function(input) {
    var result;
    let match = /[a-zA-Z]/.exec(input); // Searching for the unit part
    if (match) {
      result = input.substring(0, match.index);
    }

    if (!result) {
      return 1;
    }

    if (result.indexOf('/') != -1) { // Parsing the fraction
      let operands = result.split('/');

      if (operands.length != 2) {
        console.log('throwing error');
        throw new Error('invalid number');
      }
      result = parseFloat(operands[0]) / parseFloat(operands[1]);
    }
    else if (result.indexOf('.') != -1) {
      result = parseInt(input);
    } else {
      result = parseFloat(input);
    }

    return result;
  };
}
それは目を痛めます、そして、私はそれを書きました!😖) そして、それは仕様に固執して、テストを通過するけれども、それが何が起こっているかは、本当にわかりません.Morever、新しい要件が現れるとき、それを変更することは些細ではないでしょう.
私はテストによって駆動される要件の後にそれを書きました.それで、彼らはテストによって提示された順序で“機能”を追加しました.
回顧展では、このアプローチは、新しい要件がポップアウトするときに一般的にコードベースで何が起こるかを非常に模倣していると思います.要件は分析されています.
そして、それは完全に罰金です、しかし、一旦それが働くならば、我々は何が起こっているかについて考えるために若干の時間をとらなければなりません、そして、改善の余地があるならば(スポイラー:そこにあります).それは簡単ではない、それは多くの努力を必要とします.その意味では、このようなエクササイズはリファクタリングの「筋肉記憶」を構築するのに本当に役立つと思います.
この特定のケースでは、整数と10進数が単に分数の特別なケースであるということが私の心に来ました.
したがって、機能的観点から、我々は、それが分数だけを扱うように、メソッドを一般化することができます.
数値演算子と分母についての賢明なデフォルトを提供するだけです.
  • 分母=1オペランドのみ
  • 数が与えられない場合、数=分母= 1
  • では、2回目の繰り返しを試してみましょう
    function ConvertHandler() {
    
      this.getNum = function(input) {
        var result;
        let match = /[a-zA-Z]/.exec(input); // Searching for the unit
    
        if (match) {
          result = input.substring(0, match.index);
        } else {
          throw new Error('invalid input');
        }
    
        let numerator;
        let denominator;
        let operands = result.split('/'); // Parsing the fraction
    
        if (operands.length > 2) {
          throw new Error('invalid number');
        }
    
        if (operands.length >= 1) {
          numerator = parseFloat(operands[0]);
        }
    
        if (operands.length == 2) {
          denominator = parseFloat(operands[1]);
        }
    
        result = (numerator||1) / (denominator||1)
    
        return result;
      };
    }
    
    より良い!😃
    関数は'/'に分割して小数を解析しようとします.numerator||1 )
    今、我々は、コードが明確に判明しても、関数に私たちの心をクリアしました:変数は、より意味のある名前を持って、フロー制御は分岐が少なく、一般的にコードを読みやすくなります.
    テストスイートは、関数の動作が同じであることを保証します.
    関数はまだ少し冗長で、多くのif ステートメントとインライニングのためのいくつかの部屋.我々は、コードをより簡潔にするために言語のいくつかの機能を活用することができます.
    例えば、私たちは、私たちが境界から配列にアクセスするならば、JavaScriptが文句を言わないという事実を活用することができますundefined 代わりに値
    function ConvertHandler() {
    
      this.getNum = function(input) {
        let match = /[a-zA-Z]/.exec(input); // Searching for the unit
        let numericString 
    
        if (match) {
          numericString = input.substring(0, match.index);
        } else {
          throw new Error('invalid input');
        }
    
        let operands = numericString.split('/'); // Parsing the fraction
    
        if (operands.length > 2) {
          throw new Error('invalid number');
        }
    
        return (parseFloat(operands[0]) || 1) / (parseFloat(operands[1]) || 1);
      };
    }
    
    ここでもparseFloat() 私が2つの変数を保つ価値がないとわかるのでnumerator and denominator .
    この点で本当に私を悩ませる1つのものは、正規表現マッチングとストリング構文解析のような操作の存在です.彼らは少し低レベルであり、彼らの目的は何かを把握するために脳の力の多くを必要とする;また、添付コメントはコードの読みやすさと理解を改善するために何かをすべきヒントです.
    この種の問題に取り組む1つのテクニックはメソッド抽出です:私たちは文字通りコードの断片を取り、削除されたコードの代わりに呼び出すことができた外部の関数にそれらをカプセル化します.
    それで、我々はより高いレベルに理由をつけることができます.そして、我々がより意味がある方法で我々の機能を命名することができる追加利益で、我々のコードの本当の意図を伝えます.
    この反復で私はfindUnitIndex() , extractOperands() and parseAndCoalesce() メソッド.
    function ConvertHandler() {
    
      this.getNum = function(input) {
        const unitIndex = findUnitIndex(input);
        const operands = extractOperands(input, unitIndex);
        return parseAndCoalesce(operands[0]) / parseAndCoalesce(operands[1]);
      };
    
      /*
       * Extracted methods
       */
      function findUnitIndex(input) {
        const match = /[a-zA-Z]/.exec(input);
        if (!match) {
          throw new Error('invalid input');
        }
    
        return match.index;
      }
    
      function extractOperands(input, matchIndex) {
        const operands = input.substring(0, matchIndex).split('/');
        if (operands.length > 2) {
          throw new Error('invalid number');
        }
    
        return operands;
      }
    
      function parseAndCoalesce(operand) {
        return parseFloat(operand) || 1
      }
    }
    
    主要な機能の結果として生じるコードはより簡潔です、そして、高レベルで何が起こっているかについて理解することは本当に簡単です.
    複雑さは抽出されたメソッドで押し下げられるので、私たちは本当にそれを取り除くことができませんでした.しかし、我々は孤立して、「それをラベルしました」.
    最後に変更したいことが一つあります.
    parseAndCoalesce(operands[0]) / parseAndCoalesce(operands[1]);
    
    その目的をより明確にする.
    私が思いついたのは、新しいコンセプトです.CoalescingFraction (私は本当に、もし私たちがここで創造的になることができると思うのかどうか知りません).
    この考え方は小数点以下の値であり、それらが与えられていない場合にはその数と分母をデフォルトにしています(数値が0になったという意味がありますが、プロジェクト仕様に従っています).
    ここで使用されるテクニックは、クラスの抽出です:私たちは、新しいクラスの概念全体をカプセル化し、それを離れて私たちの主なコードからそれをプッシュし、また、我々のアプリケーションの他の部分に利用可能にします.
    function ConvertHandler() {
    
      this.getNum = function(input) {
        const unitIndex = findUnitIndex(input);
        const operands = extractOperands(input, unitIndex);
        return new CoalescingFraction(operands[0], operands[1]).value();
      };
    
      /*
       * Extracted methods
       */
    
       // as previous step; redacted for readability
    
    }
    
    /*
     * Extracted class
     */
    function CoalescingFraction(numerator, denominator) {
    
      this.value = function() {
        return parseAndCoalesce(numerator) / parseAndCoalesce(denominator);
      }
    
      function parseAndCoalesce(value) {
        return parseFloat(value) || 1
      }
    }
    
    
    もっとインライン化が可能ですが、それは十分だと思います.

    結論
    リファクタリングは、私は本当に頻繁に行う必要がありますし、このような小さな演習の練習を行うには良い方法であり、安全な環境での可能性を探ることです.
    テストは、正しい結果を保証するために重要です.しかし、私は、1つは正しい粒度を見つけるべきであると思います.
    問題を解決するときは、解決策が見つかると、しばしば停止します.いくつかの時間をより難しく考えることは、より効果的で、私たちの心に快適な良い結果につながる可能性があります.
    あなたがポストを楽しむ望み!😊