【问题标题】:Best approach to avoid multiple check conditions in Javascript在 Javascript 中避免多个检查条件的最佳方法
【发布时间】:2017-08-22 10:53:37
【问题描述】:

为了编写具有良好可读性的优质代码,我采用currying functions 方法,为大部分重复代码sn-ps 制作纯辅助函数。我刚刚观察到我的项目中到处都有存在/类型检查,以避免任何可能的错误,例如type of undefined.

检查如下:

if (param){
 action...
}

我正在考虑创建一个需要两个参数的全局辅助函数; param 需要检查,action function 在检查通过时执行操作。比如:

function isExist(param, action){
  if (param){
    action();
  }
}

此功能不适用于所有 sn-ps/案例。我怎样才能使它在所有情况下都高效且具有全局功能?这也是正确的方法。如果不是,那么我应该遵循什么来实现我的目标的最佳方法是什么?

示例

if (userInput){
  saveToDB(userInput);
}
if (valueFromDB){
  performSomeAction();
}
if (username && password){
  validate(username, password)
}

我希望代码中不同点的所有这些检查都被单个辅助函数替换,有点像:

isExist( userInput, saveToDB(userInput) );
isExist( valueFromDB, performSomeAction );
isExist( (username && password), validate(username, password) );

通过这种方式,我们将这 9 行代码替换为仅 3 行代码。这就是我想要实现的目标。

【问题讨论】:

  • 这种模式称为守卫。您应该考虑一个替代的惰性版本,它期望函数(也称为谓词)而不是条件作为参数并且是柯里化的。比你可以传入Array.prototype.every/some 进行逻辑合取/析取。
  • @ftor 谢谢。我没明白an alternative, lazy version that expects functions 是什么意思。你能解释一下吗?
  • @adi var checker = p => f => (...args) => p(...args) ? f(...args) : null; var checkNotNull = checker(args => args.every(x => x != null)); var safeAdd = checkNotNull((a, b) => a + b)); safeAdd(3, 1) /* 4 */; safeAdd(null, 2) /* null */;
  • 在旁注中,通常您希望尝试以最小化空/未定义检查的方式编写代码。如果你用 g 的结果和 h f(g(h(args))) 的结果调用 f,那么你只需要检查 h 中的参数,而不是 f 或 g。编写仅使用已知良好输入调用的简单函数,并将空检查移至边缘(用户输入、ajax 数据等)。这对于性能和可读性都比在所有地方使用警卫/可能/任何地方都更可取。
  • 嘿@JaredSmith,是的,你是对的。感谢您的宝贵意见。

标签: javascript optimization functional-programming parameter-passing purely-functional


【解决方案1】:

好吧,如果你想一个好名字

function isExist(param, action){
  if (param){
    action();
  }
}

那么我认为一个好的候选人是conditionalExecute(condition, codeToExecute)。这种工作听起来是不是很熟悉?你确定你不只是重新发明 if 语句本身吗?

也许我没有理解你的意思,但我个人看不出将 if 语句的逻辑封装得比现在更多的好处。

编辑:需要注意的是,在 Javascript 上下文中的代码

if(someVariable){
  // do something
}

已经写成“如果someVariabletruthyundefined 不是)那么......

但是可以肯定的是,如果您只想检查是否存在(一个未定义的变量),如果您说最好有一个明确的命名函数,我不会反对您。

在这种情况下,我认为只封装实际存在检查(或您想要检查的内容)而不是条件性质(因为我们已经有了 if 语句)会更清楚。所以像

function exists(x) {
  return x !== undefined; // or something like that
}
function isNotNull(x) {
  //TODO:
}

然后你的代码会变得更加明确和可读,你可以根据需要组合功能

function neitherUndefinedNorNull(x){
   return exists(x) && isNotNull(x);
}

if(neitherUndefinedNorNull(X)){
  // your "regular" code here
}

如果 if 语句中的代码重复,则也将其提取为函数。

function myRepeatedCode() {
   // do stuff
}

function someAlternativeCondition(x){ 
  // test
}

if(neitherUndefinedNorNull ){
  myRepeatedCode();
} else if(someAlternativeCondition(x)) {
  myRepeatedCode();
}

// OR combine them in the same if-statement
if(neitherUndefinedNorNull(x) || someAlternativeCondition(x)){
  myRepeatedCode();
}

最后编辑:如果你在追字符,你甚至可以写

// because of short-circuiting, myFunc1 and myFunc2 will only
// execute if myCond1 resp myCond2  is true (or truthy).
myCond1(x) && myFunc1(x)
myCond2(y) && myFunc2(y)

【讨论】:

  • 这不是重新发明 if-else 条件,而是避免在检查特定参数是否存在之后执行某些操作的几个 if else 条件。关键是我们可以编写一个辅助函数来避免这些重复的 if-else 条件吗?
  • @adi 我编辑了更多细节。我认为如果条件(在 if()() 内重复,则将其分解为命名函数。如果 if 语句代码块内的代码({ } 内的代码)重复,那么也将其分解为命名函数。(对我而言)似乎不必要的是创建一些同时接受条件和代码的通用函数。因为这就是 if 语句已经做的事情。
  • 我还用示例更新了问题,看看这是否能让您清楚我想在这里实现的目标。
  • 但是您的代码(使用isExist 而不是重复的 if 语句)难道不是因为 if 语句的格式而似乎更短吗? (他们有换行符,但那些是可选的)。事实上,它们的字符数大致相同,使用 if 语句或 isExit。我已经用最后的建议更新了我的答案,我不一定认为这会使代码更好(更清晰或更易读),但它确实使它更短。而这种模式(someCond(x) && someFunction())在 Javascript 中很常见,所以至少知道它存在是件好事。
【解决方案2】:

这是使用Maybe的完美场所:

const enumerable = true;

// data Maybe a = Nothing | Just a

const Maybe   = {};
const Nothing = Object.create(Maybe);
const Just    = value => Object.create(Maybe, {value: {enumerable, value}});

// instance Functor Maybe where

Nothing.map = _ => Nothing;
Maybe.map = function (fun) { return Just(fun(this.value)); };

// instance Applicative Maybe where

Maybe.of = Just;
Nothing.ap = _ => Nothing;
Maybe.ap = function (maybe) { return maybe.map(this.value); };

// instance Monad Maybe where

Nothing.chain = _ => Nothing;
Maybe.chain = function (kleisli) { return kleisli(this.value); };

Maybe 遵循Fantasy Land Specification[1]。使用Maybe 可以编写如下代码:

// userInput :: Maybe Data
// saveToDB  :: Data -> Something
userInput.map(saveToDB); // :: Maybe Something

// valueFromDB       :: Maybe Data
// performSomeAction :: Data -> Maybe Something
valueFromDB.chain(performSomeAction); // :: Maybe Something

// username :: Maybe String
// password :: Maybe Password
// validate :: String -> Password -> Something
Maybe.of(validate).ap(username).ap(password); // :: Maybe Something

无论如何,如果你真的对函数式编程感兴趣,那么我建议你Learn You A Haskell


[1] 我不同意 flipping the arguments of ap 上的 Fantasy Land 规范。

【讨论】:

  • 很酷的答案。你向我介绍了一些有趣的概念,包括我将要探索的MaybeXY Problem。我不认为这是 XY 问题,因为我愿意接受任何更好的解决方案。我的问题支持词:is this the right approach. If not then what is the best approach that i should follow to achieve my aim here?
  • 好的,我听到了。我更新了我的答案。删除了关于 XY 问题的行。 =)
【解决方案3】:

这样怎么样,可以同时处理参数。

function test(a,b,c)
{
    console.log("%s,%s,%s",a,b,c)
}

function check_and_run(param,action){
    var args = Array.prototype.slice.call(arguments); //turn arguments to array
    args.shift();  //remove param and action
    args.shift(); 
    if(param)
        action.apply(this,args)  
}


check_and_run(1,test,1,2,3)  //this will invoke test(1,2,3)
check_and_run(0,test,1,2,3)  //this will do nothing

【讨论】:

  • 你能解释一下吗?
【解决方案4】:

大概是这样的:

function conFun(fnCondition, fnCall, defaultResult=undefined) {
  return (...rest) => {
    if( fnCondition(...rest) ) {
      return fnCall(...rest)
    }
    return defaultResult;
  }
}

const add = conFun(
   (...rest) => rest.every(n => typeof n === 'number'),
   (...rest) => rest.reduce((a, n) => a+n), 
   NaN);

add("1", "2"); //=> NaN
add(1, 2);     //=> 3

所以在您的问题中,您可能在第一个参数未定义之后:

const firstDefined = (v) => typeof v !== 'undefined';
const cSomeFun = conFun(firstDefined, someFun, "");

cSomeFun(); // ==> ""
cSomeFun("test"); // ==> whatever someFun("test") returns

如果您只是想根据未定义的参数调用某些东西,您可以像这样简单地定义它:

function callDefined(fn, ...rest) {
  if( rest.every(firstDefined) ) {
    return fn(...rest)
  }
  return undefined;
}

callDefined( saveToDB.bind(this, userInput), userInput);
callDefined( performSomeAction, valueFromDB);
callDefined( calidate.bind(this, username, password), username, password);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-17
    • 2013-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多