前端技术设计指南

410 阅读6分钟

我们认为提升前端代码质量的关键不仅在于规范、工具的应用,更重要的是在于各位“务实工程师”的思考,以及对设计原则的合理应用,对优秀软件工程方法的实践。下面,我们从业务需求开发的四个阶段出发,阐述对提升代码质量方法的思考:image.png

  1. 需求分析和设计阶段:基于对业务的正确理解,合理地将需求拆解成不同的部分,为后续的所有环节提供最基础的输入。
  2. 编码阶段:统一目录、代码的风格;使代码组织跟业务保持一致,以最准确的业务语言命名;视图逻辑之间分层,使代码更容易复用,编码的关注点能够分离。
  3. CR 阶段:以设计阶段的产物为指导,检查需求分析、业务理解的正确性,检查代码组织、设计、实现的合理性。
  4. 线上监控阶段:以设计阶段的产物为指导,建设有效监控。

接下来我们将从一个 Todo List 的示例需求开始,完整的介绍面向对象分析、设计方法在前端业务需求中的应用,最后会讨论技术设计给整个开发流程带来的正向影响/价值。 image.png 分析(analysis) 对问题、需求的调查研究(不是解决方案)。例如,如果需要一个 Todo List,应该如何使用它?它应该具有哪些功能? 设计(design) 满足需求的解决方案(不是实现)。设计可以用代码实现,而实现(代码)则表达了完整的设计

根据ui示例需求

image.png

大部分的情况下根据ui能够明白交互上的大致逻辑

定义用例

看到这样一个需求,我们下意识的动作大概率是先拆解有哪些页面、哪些组件。然而,这个做法是有问题的:页面、UI 组件只是需求的视图产物。不能指导我们构建业务模型,要深刻的理解业务,应该从用户目标开始。 用例(use case)

一组成功和失败场景集合,用来描述参与者如何使用系统实现其(参与者)目标

参与者(actor)

某些具有行为的事物,可以是人(由角色标识)、计算机系统或组织。

场景(scenario)

场景是用例的一条执行路径,是参与者和系统之间的一系列交互。场景分为三类:

  1. 主成功场景(理想场景、最核心路径)
  2. 扩展场景
  3. 失败场景

根据上述的要素构建完整的执行用例。

拆解框架:目标-子目标模型

image.png 从需求文档出发,提炼**“目标-子目标模型”,这个模型具有三个层次**的目标:

  1. **概要目标:**偏概括性的,聚集用户目标,作为子应用、项目子模块划分的依据
  2. 用户目标:描述“谁”“做某事”,我们编写的代码,就是要支持用户目标的达成,视图、逻辑很自然的就以用户目标为边界进行组织
  3. **子功能目标:表示用“哪些东西”**来完成目标的,可以理解为我们日常开发里面的组件

下面以「绩效系统」为例,我们看看**“目标-子目标”**这个思想是如何分解系统的,相信通过这个例子,大家很容易就能够理解。

image.png image.png

定义实体模型

这部分实操起来比较简单,直接跟后端对齐即可。需要注意的是,这个跟对齐接口定义不太一样,重点是要搞清楚业务实体,以便我们能在编码的过程中以最准确的业务语言来编码。

具体到业务就是对应的接口实体的声明

定义人机交互

用例描述了参与者具有一个目标**,希望与我们创建的系统进行交互。在交互中,参与者对系统发起系统事件(system event),通常需要某些系统操作(system operation)**对这些事件加以处理。前端编写的业务代码,绝大部分都是服务于交互的。因此,交互的分析是编码前的一个重要环节,完成交互分析,就能够直接指导编码了。

定义逻辑层

以用例为边界内聚事件处理器、状态

定义:对外暴露 UI 需要的状态以及事件处理函数的对象。

  1. 同一用例下的 UI 状态以及事件处理器,内聚在同一个 Logic 对象里面。因为它们都是都是服务于同一个目标达成的,不管在写代码或者 Review 其他人代码的时候,都很容易理解。
  2. 如果一个用例下的交互太多,怎么办?这个时候需要拆分逻辑,就像“目标”拆解为多个“子目标”一样,接下来我们会讨论如何拆解的话题。
type CreateTodoLogic = () => {
  name: string;
  content: string;
  handleDidMount: () => void; // 处理:访问创建待办页
  handleEditTodoName: (name: string) => void; // 处理:填写待办名称
  handleEditTodoContent: (content: string) => void; // 处理:填写待办内容
  handleSubmitTodo: () => void; // 处理:提交待办
};

export const useCreateTodoLogic: CreateTodoLogic = () => {
  const [name, setName] = useState('');
  const [content, setContent] = useState('');

  return {
    name,
    content,
    handleDidMount: useCallback(() => {}, []),
    handleEditTodoContent: useCallback((content) => {
      setContent(content);
    }, []),
    handleEditTodoName: useCallback((name) => {
      setName(name);
    }, []),
    handleSubmitTodo: useCallback(() => {
      // submit todo
    }, []),
  };
};

我们看下填写待办名称的场景有两个步骤:填写名称、点击下一步,接下来我们就把处理这两个交互的逻辑提取出来:

enum Step {
  one,
  two
}

export const useCreateTodoLogic = () => {
  const [name, setName] = useState('');
  const [content, setContent] = useState('');
  const [step, setStep] = useState(Step.one);

  return {
    step,
    name,
    content,
    handleDidMount: useCallback(() => {}, []),
    handleEditTodoContent: useCallback((content) => {
      setContent(content);
    }, []),
    handleEditTodoName: useCallback((name) => {
      setName(name);
    }, []),
    handleSubmitTodo: useCallback(() => {
      // submit todo
    }, []),
    // 通过 useEditTodoNameLogic 的 handleNextStepClick 触发
    handleValidateNamePass: useCallback((name: string) => {
      setName(name);
      setStep(Step.two);
    }, [])
  };
};

export const useEditTodoNameLogic = () => {
  const [name, setName] = useState('');

  return {
    name,
    handleEditTodoName: useCallback((name) => {
      setName(name);
    }, []),
    handleNextStepClick: useCallback((onValidatePass) => {
      if (!name) {
        console.error('请填写待办名称');
        return;
      }
      onValidatePass(name)
    }, []),
  };
}

// 视图层
export const View: React.FC = () => {
  const { handleValidateNamePass } = useCreateTodoLogic();
  const { handleNextStepClick } = useEditTodoNameLogic();

  return (
    <div>
      请填写待办名称:<input />
      <button onClick={() => handleNextStepClick(handleValidateNamePass)}>下一步</button>
    </div>
  );
};

总结

至此,我们已经介绍了完整的技术设计方法,现在来回顾一下整个流程:

  1. 示例需求:通过ui辅助和需求分析明确需求场景和操作逻辑
  2. 用例:在框架下,使用用例分析方法,完善参与者完成目标的细节,包括主成功场景、扩展场景、失败场景。
  3. 实体:以用例分析的结果(用例场景),跟后端对齐业务实体的概念,共同维护一个术语表,以最准确的业务语言来组织目录、编写代码。
  4. 人机交互:前端系统就是处理人机交互的系统,基于用例分析的结果,可以很容易提取、聚合前端系统中的人机交互。分析至此,我们在写代码的思路基本上就非常清晰了。
  5. 逻辑层: 以用例为边界聚集逻辑层的代码,跟上面人机交互的分析保持一致,在实现逻辑层的时候已经就不需要太多的思考就可以完成编码。