JS 条件判断小技巧(二)

作者 : 开心源码 本文共4267个字,预计阅读时间需要11分钟 发布时间: 2022-05-13 共214人阅读

我前面讲过一期关于条件判断小技巧的文章,今天接着聊。所谓小技巧,说实在就是特定场景里的特例手段;对于具有肯定开发能力的码农,这些特例基本都能避开。但是,某些业务逻辑本身就十分复杂,嵌套的条件语句在逻辑层面就不可能有所优化了;碰到这类场景,我们又该如何作为呢?

function greeting(role, access) {  if( 'owner' ===  role ){    if( 'public' === access ){        ...    }    if( 'private' === access ){        ...    }    ...  } else if ( 'admin' ===  role ){    if( 'public' === access ){        ...    }    if( 'private' === access ){        ...    }    ...  } else if( 'hr' === role ) {    ...  }}

看一下代码,第一层的if-else判定的是各种角色(role)类别,第二层判定的是角色访问权限设置(access)。这类代码其实并没有特别优雅的解决手段,只能回到《clean code》里最本源的处理手段——把函数写小。本质问题还是函数体过大,而所谓的把大函数拆成多个小函数,事实上就是以笼统换取可读性

OOP 多态

最常规的手段就是 OOP 多态了。上述代码块,第一层的 role 笼统为 User 实例,嵌套层内的各种 access 进一步笼统为 User 的实例方法。

class User {  public() {    throw new Error('Denied!')  }  private() {    throw new Error('Denied!')  }}

Javascript 并没有 interface 这类语法,好在有 class 了,我仿造 interface 写了一个基类如上。接着就是将各种角色笼统为新的子类型:

class Owner extends User {  public() {    console.log('Owner in public');  }  private() {    console.log('Owner inside');  }}class Admin extends User {  public() {    console.log('Admin in public');  }  private() {    console.log('Admin inside');  }}...

OOP 推荐使用工厂方法初始化实例,我顺手也写个工厂,这样便可以利用工厂方法消除掉了第一层if-else

class UserFactory {  static create(role) {    if( 'owner' === role )      return new Owner();    else if( 'admin' === role )      return new Admin();    ...  }}

调用的时候我们先通过 role 创立笼统实例,再根据 access 调用具体方法:

function greeting(role, access) {  const user = UserFactory.create(role);  user[access]();}

上面一长串的if-else,一下子被压缩到了两行。这就实现了以笼统(很多可形容的类)换取了可读性(较少的判断嵌套)

调用链

OOP 效果的确很显著,不过上述代码还是过于特例,如果access并不是字符串(如1,2,3),像user[1]这种就很难映射到具体方法了;所以我们往往还要写更细碎的 access 笼统,也便意味着更多的笼统子类,以及新的工厂方法。很多时候,我们也不并需要笼统得尽善尽美。这个场景里写个调用链,也是勉强可用的:

const rules = [  {    match(role, access) {      return 'owner' === role;    },    action(role, access) {      if( 1 === access )        console.log('Owner in public');      else if( 2 === access )        console.log('Owner in private');    }  },  {    match(role, access) {      return 'admin' === role;    },    action(role, access) {      ...    }  }  ...];

上面 rules 数组里,每一个元素(rule)里的match被设计用来判定客户权限:遍历数组,若是match为 true,则运行正下方的action——access 相关业务;反之,继续match下一个 rule:

function greeting(role, access){  rules.find(e => e.match(role)).action(role, access)}

最后 greeting 被重构为上述代码。当然,效果没有多态好,只消掉了一层if-else,第二层判定还是留在了 action 里。

AOP

AOP,没看错,Javascript 也是有 AOP 的,只是它的实现要修改 Function 的原型链,不是很推荐;但是Function.prototype.beforeFunction.prototype.after还是挺常见的,开发组里能协商好,还是可以尝试一下的:

Function.prototype.after = function(next) {  let fn = this;  return function $after(...args) {    let code = fn.apply(this, args)    next.apply(this, args);    return code;  }}

传统的 aop after 如上所示。不难看出,用到了高阶函数:具体执行时,先运行函数本体,再运行 after 传进来的 next 方法。为了让 after 应用到我们的话题中,我略微改一下函数实现:

const nextSmb = Symbol('next');Function.prototype.after = function(next) {  let fn = this;  return function $after(...args) {    let code = fn.apply(this, args)    if( nextSmb === code )      return cnext.apply(this, args);    return code;  }}

这个 after 实现变成了先运行函数本体,若返回是nextSmb则继续执行后续的 next 方法,反之则中止。有什么用呢?我们看看如何使用:

function owner (role, access) {  function public(access) {    return 1 === access ? console.log('owner in public') : nextSmb;  }  function private(access) {    return 2 === access ? console.log('owner in private') : nextSmb;  }  const ownerChain = public.after(private);  return 'owner' === role ? ownerChain(access) : nextSmb;}

代码还是有点难度的,先看一部分——owner 的定义。这个函数被设计解决role === 'owner'时的逻辑,内部的publicprivate方法是解决access为 1 和 2 时的逻辑。我们把publicprivate方法串联成ownerChain(终于用到after方法了),它的作用就是把之前的if-else逻辑笼统成一个上节讲到的函数调用链,在遍历调用链时检查 access 条件:若符合条件,则执行本节点代码,并结束调用链;反之,继续往调用链的后续节点传送。

我把重构后的 greeting 也列一下——单个roleaccess可以用after串联;不同role之间也可以进一步利用after串起来。

function admin (role, access) {  // familiar with owner}let greeting = owner.after(admin)greeting('owner', 1);

嗯,这样,我们最原始的greeting方法就被彻底重构了。可以预见,假如调用链很长greeting会是这样:

let greeting = owner.after(admin).after(hr).after(staff)...

当然这个方法缺点也很明确,比起之前冗长的代码,可读性加强了,但是了解成本有点高,若团队内没有事前商定,这个维护起来还是挺难的。

ramda

ramda是我很喜欢用的一个方法库,在 github 上有大约 18K 的 star,它提供了一整套 FP 方法。比起上面调用链aop这种野路子,ramda 库更适合在团队内推广。我们试着用 ramda 重写一下上面提到的greeting方法:

const R = require('ramda')const ownerChain = R.cond([  [(role, access) => 1 === access, () => console.log('owner in public')],  [(role, access) => 2 === access, () => console.log('owner in private')],])const adminChain = R.cond([ ... ])const greeting = R.cond([  [R.equals('owner'), ownerChain],  [R.equals('admin'), ownerChain],])

我想大家即使没用过 ramda,也能大体猜出代码用法吧。R.cond相似于上面用到的调用链实现:二维数组第一列就是 match 函数,做判定;第二列就是 action 函数,用于执行嵌套逻辑。若嵌套较深,可以像ownerChainadminChain一样再实现一套R.cond调用链。

我们再将 FP 的 ramda 实现与上面 OOP 多态做个比较:OOP 将逻辑笼统为对象,FP 则是笼统为更小的函数。通常来说 FP 的代码更加精简,但是学习成本更高:假如没有专项训练,你根本看不懂 FP 代码,更别说码代码了。我自己部门里也有写半吊子 FP 的团队,最后写出来的代码长得像迎客松一样,并没有比多层嵌套的条件语句美观多少。

小结

本文在之前if-else小技巧的基础上,详情了少量更通用场景里的优化方式。(当然,有些方式哗众取宠了??)尽管大篇幅详情了少量野路子,但最终还是推荐大家学习正统 OOP、FP 的处理方案。学生时代,我们很少接触代码,还没事还喷喷书本知识;工作后,见识多了,才发现前人的经验弥足珍贵。

相关

《JS 条件判断小技巧(一)》

文章同步发布于an-Onion 的 Github。码字不易,欢迎点赞。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » JS 条件判断小技巧(二)

发表回复