bigbigbo / issue-blog

bloooooooooog
15 stars 0 forks source link

项目开发经验总结 #2

Open bigbigbo opened 6 years ago

bigbigbo commented 6 years ago

1. 项目总结

数字人生

遇到的问题

软件工程开发建议

这里直接引用网友对《人月神话》一书的总结,关于软件工程开发过程中的一些建议:

  • 提倡外科手术式的团队组织

    在软件开发组织上的过份民主,往往带来的是没有效率和责任,参与其中的人想法太多,层面参差不齐。所以,软件开发的组织,应该借鉴外科手术式的团队方式,有一个主要的负责人,其他人都是分工协作的副手,这样效率最好,结果最好。

  • 软件项目的核心概念要由很少的人来完成,以保证概念的完整性

    少就是多,项目的定位需要和功能多少的权衡。太多的想法,使项目没有焦点,什么都要放进去,结果什么都做不象。

  • 软件开发过程中必要的沟通手段

    软件开发中最大的风险往往不是技术的缺陷,而是缺少沟通。

  • 如何保持适度的文档

    在开发中,保持适度的文档。喜欢过度多的文档的人,忘记了文档不是最终的产品,不是用户需要的,最后以为文档好,就是好的开发,其实完全不是。

  • 在软件开发的过程中,只有适度改进,没有包治百病的银弹

    在软件开发的过程中,重要的不是采用了什么工具,而是不论用何种工具,都要达到项目本身的客户需求。任何方法论之前,先要探求问题的来源,否则,对各种方法论的依赖或滥用,有害无益。

2. 领域驱动设计

现状分析

回想一下我们前端的主要工作,大部分都跟页面打交道:如何还原UI设计图、点击某个按钮发起某个请求。

当我们将重点放在了视图上的时候就很容易出现一个问题:我们极易被需求变更摧毁

或许改改页面样式还是小问题,但是当你让将功能从A->B->C变成C-B-A的时候,你会发现自己的设计没有任何弹性:扩容性极差

而有一个被我们前端容易轻视的很重要的一环节就是业务,甚至你会听到有人说前端不需要关注业务,你只要按着原型一个页面一个页面做下来就好啦。虽然从某种层面上来讲这并没有错,因为真正存数据到数据库里的并不是我们前端,而是后端小哥。但是如果你是这样工作的,我想你的开发流程一定是这样的:

_20181205100138

我们称之为视图驱动设计。上述流程看不到有关于对业务的抽象,极有可能给自己埋下坑,因为页面的抽象真的是及其脆弱的,需求改变很容易让你骂娘,而且对新人也及其不友好,试想一位新同事在浏览你的代码的时候,他只看到了这些页面共同的样式,共同的请求,对业务的理解又只能继续从原型上获取了。

这是一个糟糕的开始,所以这时候极有必要引入一个概念领域模型

初识领域模型

当然对页面的抽象没有任何错误,只是在这个环节前缺漏了一个很重要的环节:对业务的抽象,我们重新整理一下流程:

_20181205095915

那么究竟什么是领域模型呢?

软件项目的核心概念要由很少的人来完成,以保证概念的完整性。

这里的核心概念指代的就是领域模型,也可以说是整个产品(项目)最核心的地方。这个核心概念理论上是建立之后就不应该去修改的,如果修改了,那又是一个新的产品或项目了。

领域模型是对业务的抽象,是贯穿整个项目的一个完整的业务知识体系。

建立领域模型

那么我们如何建立我们的领域模型呢?建立这个领域模型的工作究竟应该谁来做?

答案是各个环节的外科主刀人!

最早建立的这个领域模型的其实应该是产品经理,因为产品是他设计的,他是最清楚这个产品的人,但很遗憾,并不是每个产品都能抽象出这个领域模型出来的,虽然他们很清楚他们的产品是什么,但是输出只会是原型,并不会多给你一份领域模型设计的文档。

这个概念应该后端人员并不陌生,因为他们需要设计一个非常重要的东西:数据库。一个经验丰富的后端开发一定会设计一个灵活且合理的数据库来满足需求上的变更,而不是一味的堆叠数据库表。

那前端如何去建立领域模型呢?当业务越简单时,这个领域模型越容易建立,前端开发可能也能直接设计出领域模型。但是当我们面临比较复杂项目时可能缺乏这方面的经验容易导致设计上的偏差,这时候我们主要去借鉴后端的领域模型设计。

博客系统的领域模型

协同项目的领域模型

协同业务比较复杂,当初看原型看半天也看的相当片面,也是导致整个项目重写的原因之一。后面找后端设计人员拿了一张这样的图: 687474703a2f2f7069793768646f72352e626b742e636c6f7564646e2e636f6d2f38363443313335352d343739452d346632612d384346432d3338464143463336334537322e706e67

重新整理了一份领域模型:

领域驱动设计

当领域模型设计出来之后,一切就变得有章可循了。

文件(夹) 说明
webpack webpack相关配置
.babelrc/postcssconfig.js/.browserslist/.eslintrc等 不在将这些配置写到webpack中,而是通过最直观的方式展示项目配置,因为比如当你如果要修改babel编译的配置时,文档上一定是告诉你在.babelrc上修改。
global.d.ts 一些模块声明提供给编辑器做智能提示
src/assets 项目中用到的媒体资源文件
src/config 定义一些项目中用到的常量或者其他配置
src/models 项目的领域模型
src/components 项目中的公用组件
src/services 项目中的接口请求管理
src/utils 项目中使用到一些工具类函数
src/pages 项目中多入口,一个入口以一个pages下的文件夹存在
src/pages/*/routes 项目中的页面
src/pages/*/routes/config.js 路由声明文件

着重介绍下pagesservicesmodelscomponents

这边以第二种允许有二级路由(子路由)做介绍,第一种设计方式只要去除子路由即可

// 此处声明所有一级路由的配置,如果有子路由,参照react-router的实践方式
const ROUTES = [
  // resource
  {
    path: "/resource/profile",
    name: "资源概况",
    models: [],
    component: () => import("./resource/profile"),
    exact: true
  },
  //   resource-apply
  {
    path: "/resource-apply",
    name: "资源申请",
    models: [],
    component: () => import("./resource-apply"),
    exact: true
  },

  {
    path: "/resource-apply/audit",
    name: "资源申请审核",
    models: [],
    component: () => import("./resource-apply/audit"),
    exact: true
  },
  //   resource-register
  {
    path: "/resource-register",
    name: "资源注册",
    models: [],
    component: () => import("./resource-register"),
    exact: true
  },
  {
    path: "/resource-register/audit-list",
    name: "资源注册审核列表",
    models: [],
    component: () => import("./resource/profile"),
    exact: true
  },
  // component
  {
    path: "/component",
    name: "组件CreateUpdate",
    models: [],
    component: () => import("./component"),
    exact: true
  },
  {
    path: "/component/list",
    name: "组件相关列表",
    models: [],
    component: () => import("./component/list"),
    exact: true
  },
  // fallback
  {
    path: "*",
    name: "not found",
    component: () => import("@/components/404")
  }
];

如此一来,我们就可以将路由同目录结构直接联系起来,当我们在开发中就可以直接通过浏览器中url来快速定位代码位置。

URL 目录结构 说明
/resource/profile routes/resource/profile 资源概况
/resource-register/file routes/resource-register/index/file 文件资源注册
/resource-register/audit routes/resource-register/audit 资源注册审核
/resource-apply/file routes/resource-apply/index/file 文件资源申请
/resource-apply/audit routes/resource-apply/audit 资源申请审核

后端的接口是为前端服务的,而前端需要什么样的服务前端最清楚。当我们在未建立领域模型的时候,我们可能由于对业务理解欠缺,无法做到主导接口的设计,但是当建立起领域模型的时候,我们很清楚的知道我们需要什么。

举个栗子,现有的注册接口是这样的:

/resource/register/file POST BODY
/resource/register/database POST BODY

因为我们有一个叫做resource-register的模型,完全只需要一个接口就足够了

/resource/register POST BODY({type: 'file'})

这样的情况在该项目中很多,看似两个不同的接口完全可以抽象成一个接口,而这种事如果没有一步到位的后,后面在调整就可能得费点神了,特别是在没有测试覆盖的情况下。

3. 其他

命名约定

最重要的一致性规则是命名管理。命名的风格能让我们在不需要去查找类型声明的条件下快速地了解某个名字代表的含义: 类型、变量、函数、常量、宏等等, 甚至,我们大脑中的模式匹配引擎非常依赖这些命名规则。

总结一些我自己在使用的命名约定:

我们尽量在变量命名上达成共识,方便团队协作。

React中Key的原理及使用

Key的原理

我们经常会在开发中开到react在控制台给你这样的警告:

Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `App`. See https://fb.me/react-warning-keys for more information.

为什么在jsx中必须为组数的每一项添加一个key呢?React官方文档是这样写的:

Keys help React identify which items have changed, are added, or are removed.

所以这个key是给react自身用来更新元素用的,并不是给我们用的。

我们来看一下,如果我们有这样一个数组:

const options = [
    {
        label: '选项1',
        value: 'v1'
    },
    {
        label: '选项2',
        value: 'v2'
    },
    {
        label: '选项3',
        value: 'v3'
    }
]

现在我们改变数组变成:

const options = [
    {
        label: '选项3',
        value: 'v3'
    },
    {
         label: '选项1',
         value: 'v1'
    },
    {
        label: '选项2',
        value: 'v2'
    }
]

第二个数组相对于第一个数组,所以的元素索引都变了,如果是你来更新元素你要怎么做?重新渲染一遍吗?这是一种办法,但这个方法是肉眼可见的简单粗暴,必定存在着性能问题,所以这时候react就借助key来唯一标识某个元素,react只要将第三个元素append到第一个位置就好了,这样就完成了更新。

再来思考一个问题,为什么只有数组里的元素需要添加key? 看代码说话:

const flag = false;
<section>
  <h1>aaa</h1>
  {flag && <p>hhh</p>}
  {[<p key={1}>aaa</p>, <p key={2}>bbb</p>]}
  footer
</section>
var flag = false;
React.createElement(
  "section",
  null,
  React.createElement(
    "h1",
    null,
    "aaa"
  ),
  flag && React.createElement(
    "p",
    null,
    "hhh"
  ),
  [React.createElement(
    "p",
    { key: 1 },
    "aaa"
  ), React.createElement(
    "p",
    { key: 2 },
    "bbb"
  )],
  "footer"
);

可以看到非数组元素的元素,他所在位置就是他天然的key,并且当用变量控制一个元素的显隐的时候,也必须用null占据一个位置,这也是为什么在jsx中不能if的原因。

Key的另类使用

上面提到,key是提供给react使用的,当一个组件的key改变时,这个组件会被重新渲染,利用这一点,我们在一些特殊场景可以处理的非常优雅。

想像一下有这样一个场景:

如果这时候我们借助key的原理,当组件的key改变的时候,重新触发组件的挂载,那我们在处理这件事来变得更优雅了。

高阶组件的使用

高阶组件的出现是为了替代Mixins而出现的,最早在react中,一些通用的代码可以抽象到Mixins中,来进行逻辑的复用,比如受控表单的value, onChange

这里以改变组件的key举一个简单的例子:

import React from 'react';

const withKey = mapPropsToKey => WrappedComponent => props => {
    const key = mapPropsToKey(props);

    return <WrappedComponent {...props} key={key} _componentKey={key} />;
};

export default withKey;

recompose介绍

看文档

参考文章