前端编码范式的最佳实践

广大程序员们在长久的编程实践中,总结出了一系列编写高质量、可维护代码共通原则,每个程序员都受益于学习、理解这些原则。

此处列出在我的编程实践中最重要一条原则,我从中受益良多。

1. 单一职责(Single Responsibility Principle)

在面向对象编程范式中该原则被阐述为“类或模块应该有且只有一条加以修改的理由”

函数式编程范式中该原则被阐述为“一个函数应该有且只有一个目的”

这条原则及其思想(分治)再怎么强调都不为过,我视之为编程原则中的二八定律,理解、做好这一条可以解决编程过程中 80% 的问题(保守的说)。

毫不夸张的说,由此原则延伸出的一系列设计模式、架构设计(MVC、控制反转、依赖注入、发布订阅…)构成了现代编程框架的基石。

2. 其他

下面这些链接值得反复地阅读,每隔一段时间再次阅读这些原则时都会有更加深刻的理解。

编程原则

编程原则(中文版)

关于 UNIX 的哲理名言

除了这些编程原则外,函数式编程的思想也使我对编程有了更深刻的认识

函数式编程范式

3. 实践是检验真理的唯一标准

如题。

编程实践

错误处理

错误处理是任何程序或系统的不可或缺的一部分,错误处理本身并不复杂但往往与业务结合的比较紧密。

比如表单的一个必填项为空时会产生一个错误,如果我们在提交表单的流程中去写一个 if 来处理这个错误显然违反了我们的“单一职责”原则。
当我们的表单规则变得复杂时,我们提交表单的代码也会变得异常复杂,因为每增加一个 if 我们代码执行分支都会呈几何级增长,最终我们的代码会脱离我们的掌控,使得程序变成一个黑箱谁也无法预料到会发生什么。

bad case:

function submit(form) {
  loading = true;
  if (isEmpty(form.name)) {
    // ...
    loading = false;
    return;
  }
  if (isNotValidPhone(form.phone)) {
    // ...
    loading = false;
    return;
  }

  // do something
  // ...
  loading = false;
}

好在编程语言的设计者们早已意识到这个问题,他们为我们提供了一种机制来处理这种情况,try...catch

better case:

function submit(form) {
  try {
    loading = true;
    validate(form);
    // do something
    // ...
  } catch (error) {
    if (error instanceof EmptyError) {
      // ...
    }
    if (error instanceof InvalidPhoneError) {
      // ...
    }
    // ...
  } finally {
    loading = false;
  }
}

function validate(form) {
  if (isEmpty(form.name)) {
    throw new EmptyError();
  }
  if (isNotValidPhone(form.phone)) {
    throw new InvalidPhoneError();
  }
  // ...
}

这样我们的代码就变得清晰了很多,我们在 validate 函数中处理所有的表单校验,当校验失败时抛出一个错误,我们在 catch 中捕获这个错误并处理它,这样便将我们的业务代码和错误处理代码分离开。

虽然这里的例子看起来 better case 的行数比 bad case 多了近一倍,但相比此处我们省略的业务代码来说,这些不值一提,相反我们的代码可读性、可维护性以及可测试性都得到了极大地改善。

此外该方式还可以使用 finally 来处理 loading 的状态,避免了在每个函数执行出口中都写一遍 loading 的逻辑。

路由

路由鉴权,将权限信息配置到路由的 meta 中,在路由守卫中进行鉴权完成对路由的访问控制。

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: {
      auth: 'system.admin',
    },
  },
  {
    path: '/login',
    name: 'Login',
    component: Login,
  },
];

router.beforeEach((to, from, next) => {
  const { hasPermission } = useUserModule();
  if (to.meta?.auth) {
    if (hasPermission(to.meta.auth)) {
      next();
    } else {
      next('/login');
    }
  } else {
    next();
  }
});

Typescript

所有的编程范式、思想等提高代码质量的手段都是在限制程序员随意地编写程序,Typescript 也是如此。

Typescript 带来好处

  1. 可以在开发阶段就发现并处理空值错误,而空值错误是程序中最常见的错误。
  2. 优秀的代码补全功能,可以极大地提高开发效率。
  3. 增强了 javascript 的表达能力,可以表达更复杂的设计。

例如:

interface UserParams {
  id: number;
}

interface UserDto {
  id: number;
  name: string;
  age: number;
}

function getUser(params: UserParams) {
  return get<UserDto>('/user', params);
}

可以清晰地看到我们需要的参数和得到的结果,而不在需要去看具体的业务实现、调试。

最佳实践

前端的数据主要有两个来源,一个是后端接口,一个是用户输入,因此我们只需要在这两个位置提供完整的类型定义其他位置便可以很好地推导出类型。

类型命名:

同变量名一样,类型名应该体现类型的含义及作用,统一的命名习惯有助于我们快速准确的理解代码

我的习惯是接口参数的类型使用 XXXParams 的格式,接口返回的类型使用 XXXDto 的格式。

DTOData Transfer Object 数据传输对象,它是一个定义了数据的数据结构,用于在应用程序之间传输数据。

interface UserParams {
  id: number;
}

interface UserDto {
  id: number;
  name: string;
  age: number;
}

function getUser(params: UserParams) {
  return get<UserDto>('/user', params);
}
最后更新:2026-02-10 18:47 星期二
备案号:鲁ICP备2024058644号