wzalazar
4/21/2015 - 5:55 PM

README.md

function matchRules (values, rules) {
    var
    matchTerm = function (termValue, termKey) {
        var
        starOrEqual = function (value, valueKey) {
            return value === '*' || _.isEqual(values[valueKey], value);
        },
        ensureArray = function (value) {
            return _.isArray(value) ? value : [value];
        },
        ensurePredicate = function (value) {
            return _.isFunction(value) ? value : _.partial(starOrEqual, value);
        },
        predicator = function (value) {
            return _.bind(ensurePredicate(value), values)(termKey);
        };
        return _.any(ensureArray(termValue), predicator);
    },
    everyClause = _.partial(_.every, _, matchTerm),
        anyMatch = _.partial(_.any, _, everyClause),
        anyRule = _.compose(anyMatch, _.property('matches')),
        result = _.find(rules, anyRule);
    return result ? result.returns : !! result;
}
function isValidAction(myBankAccount, myAction) {
    var found = _(myBankAccount.state).contains('hasCredit') && !_(myBankAccount.state).contains('hasWithdrawalRecently');
    if (found &&
        (myAction.action === 'Attempt') &&
        (myAction.type === 'Withdraw') &&
        (myAction.amount < myBankAccount.creditCardLimit)) {
        return 'complete';
    } else {
        return 'errors or warnings';
    }
}
function isValidAction(myBankAccount, myAction) {
    var is_less_than = function(rightKey) {
        return function(leftKey) {
            var
                rVal = this[rightKey],
                lVal = this[leftKey];
            return lVal < rVal;
        };
    };
    return matchRules(
        {
            hasCredit: _(myBankAccount.state).contains('hasCredit'),
            hasWithdrawalRecently: _(myBankAccount.state).contains('hasWithdrawalRecently'),
            creditCardLimit: myBankAccount.creditCardLimit,
            action: myAction.action,
            type: myAction.type,
            amount: myAction.amount
        }, [
            {
                'matches': [{
                    hasWithdrawalRecently: true
                }],
                'returns': 'errors or warnings'
            },
            {
                'matches': [{
                    hasWithdrawalRecently: false,
                    hasCredit: true,
                    action: 'Attempt',
                    type: 'Withdraw',
                    amount: is_less_than('creditCardLimit')
                }],
                'returns': 'complete'
            },
            {
                'matches': [{
                    accountState: '*'
                }],
                'returns': 'errors or warnings'
            }
        ]
    );
}
function isValidAction(myBankAccount, myAction) {
    var found = false;
    for (s1 in myBankAccount.state) {
        if (myBankAccount.state[s1] === 'hasCredit') {
            found = true;
            for (s2 in myBankAccount.state) {
                if (myBankAccount.state[s2] === 'hasWithdrawalRecently') {
                    found = false;
                    break;
                }
            };
            break;
        }
    }
    if (found &&
        (myAction.action === 'Attempt') &&
        (myAction.type === 'Withdraw') &&
        (myAction.amount < myBankAccount.creditCardLimit)) {
        return 'complete';
    } else {
        return 'errors or warnings';
    }
}
##Top-Down, Bottom-Up y Lenguajes de Propósito Específico Un enfoque declarativo implica un cambio de paradigma de programación, pero también, implica un cambio en la estrategia análisis del problema.

Mayoritariamente, es costumbre analizar los problemas en un enfoque “Top-Down” que consiste en subdividir el problema sucesivamente hasta alcanzar un nivel en el que es posible resolverlo con las primitivas del lenguaje (javascript).

Existe otro enfoque que consiste en construir un lenguaje de propósito específico cuyas primitivas habilitan la implementación (trivial) de la solución , éste enfoque es conocido como “Botton-Up”. El objetivo consiste en minimizar la distancia que existe entre el problema y el lenguaje que se utiliza para implementarlo.

~En la práctica, ambos enfoques pueden coexistir, y un balance óptimo permite realizar implementaciones muy elegantes (bottom-up) sin sacrificar el pragmatismo necesario para realizar labores cotidianas (top-down)~

##Análisis Top-Down/Botom-Up del problema Reglas de Negocio Si, tomamos un enunciado de ”acceptance test“ estándar:

* _Given_ my bank account is in credit, and I made no withdrawals recently, * _When_ I attempt to withdraw an amount less than my card's limit, * _Then_ the withdrawal should complete without errors or warnings

Un primer análisis permite identificar los elementos participantes:

* _Given_ `myBankAccount.state` **contains** `hasCredit` **and not contains** `hasWithdrawalsRecently` * _When_ `myAction.action` **is-equal** `Attempt` **and** `myAction.type` **is-equal** `Withdraw` **and** `myAction.amount` **is-less-than** `myBankAccount.creditCardLimit` * _Then_ the withdrawal should **complete** without **errors or warnings**

Los elementos que componen la regla se agrupan en datos estructurales y operadores lógicos

####datos estructurales

  • myBankAccount
    • state: array of state tokens
    • creditCardLimit: currency/number
  • myAction
    • state: token/string
    • type: token/string
    • amount: currency/number

####operadores lógicos

  • contains
  • not
  • and
  • is-less-than
  • is-equal

###test cases:

describe(
    'Given my bank account is in credit and I made no withdrawals recently',
    function() {
        context(
            'When I attempt to withdraw an amount less than my cards limit',
            function () {
                it(
                    'Then the withdrawal should complete without errors or warnings',
                    function () {
                        var
                        myBankAccount = {
                            state: ['hasCredit'],
                            creditCardLimit: 50
                        },
                        myAction = {
                            action: 'Attempt',
                            type: 'Withdraw',
                            amount: myBankAccount.creditCardLimit - 1
                        };
                        isValidAction(myBankAccount, myAction).should.match(/complete/);
                    }
                );
            }
        );
...

###implementación en pure-imperative-javascript:

function isValidAction(myBankAccount, myAction) {
    var found = false;
    for (state in myBankAccount.state) {
        if (state === ’hasCredit’) {
            found = true;
            for (state in myBankAccount.state) {
                if (state === ’hasWithdrawalsRecently’) {
                    found = false;
                    break;
                }
            };
            break;
        }
    }
    if (found &&
        (myAccount.action === ’Attempt’) &&
        (myAction.type === ‘Withdraw’) &&
        (myAction.amount < myBankAccount.creditCardLimit)) {
        return 'complete';
    } else {
        return 'errors or warnings';
    }
}

###Metricas: (http://jscomplexity.org/)

  • isValidAction
  • Line No.: 1
  • Logical LOC: 13
  • Parameter count: 2
  • Cyclomatic complexity: 4
  • Cyclomatic complexity density: 31%
  • Halstead difficulty: 10
  • Halstead volume: 329
  • Halstead effort: 3331

Implementación en Uderscript:

function isValidAction(myBankAccount, myAction) {
    var found = _(myBankAccount.state).contains('hasCredit') && !_(myBankAccount.state).contains('hasWithdrawalRecently');
    if (found &&
        (myAction.action === 'Attempt') &&
        (myAction.type === 'Withdraw') &&
        (myAction.amount < myBankAccount.creditCardLimit)) {
        return 'complete';
    } else {
        return 'errors or warnings';
    }
}

###Metricas:

  • isValidAction
  • Line No.: 1
  • Logical LOC: 5
  • Parameter count: 2
  • Cyclomatic complexity: 2
  • Cyclomatic complexity density: 40%
  • Halstead difficulty: 9
  • Halstead volume: 247
  • Halstead effort: 2210

...enunciado de la regla a un bloque de código imperativo compuesto principalmente por estructuras condicionales que evalúan los antecedentes de las reglas (la parte “given” y “when”) y producen un efecto (la parte “then”). La parte “given” viene dada por el contexto en el cual se evalúa la regla. El análisis bottom-up, en cambio, nos aporta una solución diferente: “¿Cómo sería un lenguaje especializado en expresar reglas de negocio?” El resultado es un lenguaje para expresar el contexto (“given”) y reglas (“when”) que convergen en resultados (“then”). ejemplo: