# 简化复杂条件判断逻辑的方案

Table of Contents

难点分析

在处理复杂的条件逻辑时,if/else if/else 链和 switch 语句确实可能导致代码冗余和可读性差,尤其当条件数量增多时。将这些复杂的判断逻辑抽象成函数,甚至更进一步地设计成更高级的模式,是提升代码质量的有效途径。

// 伪代码
if (condition1) {
// 执行逻辑1
} else if (condition2) {
// 执行逻辑2
} else if (condition3) {
// 执行逻辑3
} else if (condition4) {
// 执行逻辑4
} else {
// 执行逻辑5
}
switch (key) {
case value1:
// 执行逻辑1
break
case value2:
// 执行逻辑2
break
case value3:
// 执行逻辑3
break
case value4:
// 执行逻辑4
break
default:
}

在上面的例子中,我们可能会遇到以下问题:

  • 代码冗余
  • 可读性差
  • 难以维护
  • 难以扩展
  • 难以测试
  • 难以复用
  • 难以理解

方案一:对象映射

现在我们把条件和条件体抽象成一个函数,然后通过对象映射key-value的形式来实现,会得到:

// 伪代码
const conditionMap = {
condition1: () => {
// 执行逻辑1
},
condition2: () => {
// 执行逻辑2
},
condition3: () => {
// 执行逻辑3
},
condition4: () => {
// 执行逻辑4
},
condition5: () => {
// 执行逻辑5
},
}

现在我们就可以通过conditionMap[key]来执行对应的逻辑了。

代码示例

// 传统方式:复杂的 if-else 链
function processUserActionTraditional(action: string, user: any) {
if (action === 'login') {
if (user.isAdmin) {
return { message: '管理员登录成功', redirect: '/admin' };
} else if (user.isVip) {
return { message: 'VIP用户登录成功', redirect: '/vip' };
} else {
return { message: '普通用户登录成功', redirect: '/dashboard' };
}
} else if (action === 'register') {
if (user.email.includes('@company.com')) {
return { message: '企业用户注册成功', redirect: '/enterprise' };
} else {
return { message: '个人用户注册成功', redirect: '/personal' };
}
} else if (action === 'logout') {
return { message: '退出登录成功', redirect: '/login' };
} else if (action === 'reset-password') {
return { message: '密码重置邮件已发送', redirect: '/login' };
} else {
return { message: '未知操作', redirect: '/error' };
}
}
// 使用对象映射
const actionHandlers = {
login: (user: any) => {
if (user.isAdmin) {
return { message: '管理员登录成功', redirect: '/admin' };
} else if (user.isVip) {
return { message: 'VIP用户登录成功', redirect: '/vip' };
} else {
return { message: '普通用户登录成功', redirect: '/dashboard' };
}
},
register: (user: any) => {
if (user.email.includes('@company.com')) {
return { message: '企业用户注册成功', redirect: '/enterprise' };
} else {
return { message: '个人用户注册成功', redirect: '/personal' };
}
},
logout: () => {
return { message: '退出登录成功', redirect: '/login' };
},
'reset-password': () => {
return { message: '密码重置邮件已发送', redirect: '/login' };
},
default: () => {
return { message: '未知操作', redirect: '/error' };
}
}
function processUserActionAbstract(action: string, user: any) {
const handler = actionHandlers[action as keyof typeof actionHandlers]
if (handler) {
return handler(user)
} else {
return actionHandlers.default()
}
}
// 使用示例
const user = { isAdmin: true, email: 'admin@company.com' };
console.log('传统方式:', processUserActionTraditional('login', user))
console.log('抽象方式:', processUserActionAbstract('login', user))

这个示例代码展示2种不同的实现方式:

  • 传统方式:使用if-else if-else链来处理不同的条件
  • 抽象方式:使用对象映射来处理不同的条件

从代码量上来看,抽象方式的代码量更少,更简洁。从可读性上来看,抽象方式的代码更易读,更易维护。从可扩展性上来看,抽象方式的代码更易扩展,更易测试。

但是,其实还有更细粒度,更灵活的实现方式,那就是使用函数式编程的思维来实现。

方案二:函数式编程

函数式编程是一种编程范式,它将计算视为数学函数的求值,并避免改变状态和可变数据。函数式编程强调函数的纯度和无副作用,鼓励使用不可变数据和纯函数。

核心思想:利用高阶函数(接受函数作为参数或返回函数的函数)和函数组合来表达复杂的逻辑。这有助于创建更模块化、更易测试的代码。

优势:

  • 简洁性:减少样板代码
  • 可复用:轻松组合和重用函数
  • 纯函数:鼓励编写没有副作用的纯函数,提高代码可预测性
  • 声明式:更关注 ‘做什么’,而不是 ‘怎么做’

代码示例

type Validators = {
message: string
condition: (value: any) => boolean
}
// 验证规则定义,每个规则都是一个对象,包含一个条件函数和一个错误消息
const validators: Validators[] = [
{
condition: (value) => value === null || value === undefined,
message: '值不能为空'
},
{
condition: (value) => typeof value !== 'string',
message: '值必须是字符串类型'
},
{
condition: (value) => typeof value === 'string' && value.length < 5,
message: '字符串长度不能小于 5'
},
{
condition: (value) => typeof value === 'string' && !value.includes(' '),
message: '字符串不能包含空格'
}
]
function validate(input: any, validators: Validators[]) {
// 使用 Array.prototype.find 高阶函数来查找第一个不满足条件的验证规则
// 这取代了传统的 if/else if 链
const failedValidation = validators.find(validator => validator.condition(input))
if (failedValidation) {
return {
isValid: false,
message: failedValidation.message
}
} else {
return {
isValid: true,
message: undefined, // 验证通过
}
}
}
console.log(validate("hello world", validators)); // { isValid: false, message: '字符串不能包含空格' }
console.log(validate("short", validators)); // { isValid: false, message: '字符串长度不能小于 5' }
console.log(validate(null, validators)); // { isValid: false, message: '值不能为空' }
console.log(validate(123, validators)); // { isValid: false, message: '值必须是字符串类型' }
console.log(validate("longstring", validators)); // { isValid: true, message: '验证通过' }
// 简化的函数式方式:使用纯函数映射
const actionHandlers = {
login: (user: any) => ({
message: user.isAdmin ? '管理员登录成功' : '用户登录成功',
redirect: user.isAdmin ? '/admin' : '/dashboard'
}),
register: (user: any) => ({
message: '注册成功',
redirect: '/dashboard'
}),
logout: () => ({
message: '退出登录成功',
redirect: '/login'
})
};
const processUserAction = (action: string, user: any) => {
const handler = actionHandlers[action as keyof typeof actionHandlers];
return handler ? handler(user) : { message: '未知操作', redirect: '/error' };
};
// 使用示例
const user = { isAdmin: true, username: 'admin', email: 'admin@example.com' };
console.log(processUserAction('login', user));

方案三:表驱动

表驱动是一种编程模式,它将条件逻辑转换为数据结构,从而简化条件判断。将 ‘判断’ 转换为 ‘查找’,将 ‘执行’ 转换为 ‘读取’。

// 表驱动方式:将条件逻辑转换成数据结构
interface ActionTable {
[key: string]: {
message: string
redirect: string
condition?: (user: any) => boolean
}
}
// 定义动作表
const actionTable: ActionTable = {
'admin-login': {
message: '管理员登录成功',
redirect: '/admin',
},
'vip-login': {
message: 'VIP用户登录成功',
redirect: '/vip'
},
'user-login': {
message: '普通用户登录成功',
redirect: '/dashboard'
},
'company-register': {
message: '企业用户注册成功',
redirect: '/enterprise',
condition: (user: any) => user.email.includes('@company.com')
},
'personal-register': {
message: '个人用户注册成功',
redirect: '/personal'
},
'logout': {
message: '退出登录成功',
redirect: '/login'
},
'reset-password': {
message: '密码重置邮件已发送',
redirect: '/login'
}
}
// 用户角色映射表
const userRoleTable = {
admin: (user: any) => user.isAdmin,
vip: (user: any) => user.isVip && !user.isAdmin,
user: (user: any) => !user.isAdmin && !user.isVip
}
// 邮箱类型映射表
const emailTypeTable = {
company: (user: any) => user.email && user.email.includes('@company.com'),
personal: (user: any) => user.email && !user.email.includes('@company.com')
}
// 处理方法
function processUserActionTable(action: 'login' | 'register' | 'logout' | 'reset-password', user: any) {
// 登录动作
if (action === 'login') {
const role = Object.keys(userRoleTable).find(role => userRoleTable[role as keyof typeof userRoleTable](user))
const actionKey = `${role}-login`
return actionTable[actionKey]
}
// 注册动作
if (action === 'register') {
const emailType = Object.keys(emailTypeTable).find(type => emailTypeTable[type as keyof typeof emailTypeTable](user))
const actionKey = `${emailType}-register`
return actionTable[actionKey]
}
// 其他动作
return actionTable[action] || {message: '未知操作', redirect: '/error'}
}
// 使用示例
const user = { isAdmin: true, isVip: false, email: 'admin@company.com' };
console.log(processUserActionTable('login', user));
console.log(processUserActionTable('register', user));
console.log(processUserActionTable('logout', user));
console.log(processUserActionTable('reset-password', user));

如果使用查表的方式,代码量会减少很多,但是可读性会降低,因为需要理解表的结构和映射关系。

// 定义动作表
const actionTable: ActionTable = {
'admin-login': {
message: '管理员登录成功',
redirect: '/admin'
},
'vip-login': {
message: 'VIP用户登录成功',
redirect: '/vip'
},
'user-login': {
message: '普通用户登录成功',
redirect: '/dashboard'
},
'company-register': {
message: '企业用户注册成功',
redirect: '/enterprise'
},
'personal-register': {
message: '个人用户注册成功',
redirect: '/personal'
},
'logout': {
message: '退出登录成功',
redirect: '/login'
},
'reset-password': {
message: '密码重置邮件已发送',
redirect: '/login'
}
};
const actionLookupTable = {
login: {
conditions: [
{ test: (user: any) => user.isAdmin, key: 'admin-login'},
{ test: (user: any) => user.isVip, key: 'vip-login'},
{ test: () => true, key: 'user-login' } // 默认情况
]
},
register: {
conditions: [
{test: (user: any) => user.email.includes('@company.com'), key: 'company-register'},
{ test: () => true, key: 'personal-register' } // 默认情况
]
},
logout: {
conditions: [
{ test: () => true, key: 'logout' }
]
},
'reset-password': {
conditions: [
{ test: () => true, key: 'reset-password' }
]
}
}
// 查表处理函数
function processUserActionLookup(action: 'login' | 'register' | 'logout' | 'reset-password', user: any) {
const actionConfig = actionLookupTable[action]
if (!actionConfig) {
return {message: '未知操作', redirect: '/error'}
}
const condition = actionConfig.conditions.find(condition => condition.test(user))
const actionKey = condition?.key
return actionTable[actionKey]
}
// 使用示例
const testUsers = [
{ isAdmin: true, email: 'admin@company.com' },
{ isVip: true, email: 'vip@example.com' },
{ isAdmin: false, isVip: false, email: 'user@example.com' },
{ isAdmin: false, isVip: false, email: 'user@company.com' }
];
console.log('查表方式:')
testUsers.forEach((user, index) => {
console.log(`用户 ${index + 1} 登录:`, processUserActionLookup('login', user));
console.log(`用户 ${index + 1} 注册:`, processUserActionLookup('register', user));
console.log(`用户 ${index + 1} 退出:`, processUserActionLookup('logout', user));
console.log(`用户 ${index + 1} 重置密码:`, processUserActionLookup('reset-password', user));
});

表驱动的优势:

  • 可读性强:所有逻辑都清晰地定义在数据结构中
  • 易维护:添加新的动作只需要在表中添加条目
  • 性能好:查找操作比条件判断更快
  • 可扩展:容易添加新的条件和动作
  • 数据驱动:逻辑与数据分离,便于配置化管理

总结

在实际开发中,应该根据具体场景选择最合适的方案。

  • 如果条件逻辑简单,可以使用传统方式,比如if-else if-else链
  • 如果条件逻辑复杂,可以使用函数式编程,比如高阶函数和函数组合
  • 如果条件逻辑复杂,可以使用表驱动,比如查表的方式
My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


相关文章

评论