前端防御性开发

1. 前端防御性问题

在实现某种css效果时,往往我们能很快的完成css的编写,如文档溢出、垂直居中、各种布局等效果。但是当一个或一组元素具有动态,或者面对无法预测内容形式的时候,可能会出现样式不兼容,不适配导致塌陷,甚至异常。

2. 前端防御性实际情况

实际情况远比想象的复杂。以下列举了一些用户在使用链路上可能存在的“危机点”,这也是防御性开发中着重要考虑的“防御点”。归纳为两大类问题:

1.UI的防御性

  • 防白屏 – 白屏时间小于1秒(小于200ms最佳)
  • 防布局错乱 – 布局被动态内容撑垮
  • 防极端内容 – 缺失 / 超长 / 连续字符 / 未转义
  • 防慢 – 网络慢 / 响应慢 / 渲染慢 / 执行慢
  • 防卡 – 卡顿 / 假死
  • 防一致性问题 – 不一致的交互方式、图标、 标准组件等
  • 防UI状态不全 – 五层UI状态栈(加载状态/空状态/部分缺失状态/出错状态/理想状态)
  • 防样式污染 – 样式冲突,局部模块的样式影响全局
  • 防Chartjunk – 可读性差的图表用法
  • 防误操作 / 危险操作 – 对不可逆的操作二次确认+强提示

2.代码的防御性

  • 防报错 – 语法错误 / 逻辑错误
  • 防兼容性问题
  • 防安全性问题
  • 防意外输入和交互
  • 防数据 – 防极端数据 / 无效数据 / 接囗变更
  • 防代码坏味道 / 防工程腐化 – 代码复杂度 / 重复率 / 反模式 / 死代码等
  • 防语法风格不一致
  • 防代码冲突
  • 防代码冗余

3. 如果做防御性开发

人是代码的创作者,提高代码防御性,写出高质量的代码,最终靠人。团队中的CR是从外部反馈来优化开发,而作为开发者,则更需要自己对自身的约束。
那应该从哪些方面去做防御性考虑呢?

1. 需求

  • 功能实现问题
  • UI和交互问题

2. 开发

  • JS防御性原则
防御点 原则
逻辑问题 1. 判断条件有误 / 忽略了必要条件
2. 循环 / 递归的退出条件
3. 显隐逻辑和跳转逻辑控制
4. 缺少校验或错判参数类型 / 空值 / 边界条件
5. 缺少对默认值 / 缺省状态的校验 / 判断 / 处理
6. 接囗调用逻辑和组合关系
7. 忽略一些组件之间的联动关系
全局副作用 1. 变更公共代码,对其他部分产生影响
2. 变更配置文件 / 全局变量
3. 代码的冲突和污染
4. 基础库版本升级
容错问题 1. 错误输入 / 特殊字符 / 数据类型的容错
2. 接囗返回值的不确定性
3. 接囗请求失败的容错
4. 缺少error boundary,避免导致白屏
5. 错误要上报
表单校验问题 前端校验条件不全
编译 & 依赖问题 1. JS编译漏掉对一些语法的处理
2. 本地和发布构建有差异
3. 本地和线上依赖版本有差异
兼容性问题 1. Polyfill不全
2. CSS兼容性问题
文案问题 1. 文案错误 / 不准确 / 折行
2. 国际化不完整
  • CSS防御性原则
防御点 原则
避免用JavaScript控制布局 JavaScript实现的布局组件不要用。它会多出很多层没用的嵌套,同时把布局定义的很死,难以再用CSS控制。
永远没有原生的流畅,同时增加代码的复杂,容易用问题。除非解决一些必要的兼容性问题。l
避免用float / position: absolute / display: table等过时的布局技术 优先用Flexbox/Grids布局。你会说绝对定位还是有用的。你要强迫自己不用,经过反复尝试过发现绝对定位是最优的选择那就用。重要的是有这个“强迫自己”的过程。
避免定高/定宽 固定宽/高最容易出现的问题是内容溢出。没必要通过定宽高对齐,可以利用Flexbox的位伸/收缩特性。一般情况下用最小宽/高、calc()、相对单位替代。同上要“强迫自己”不用。
避免侵入性的写法 避免影响全局样式,如:* { … }、:root {…} 、div { ….}等。避免影响通用组件样式,如:.next-card {…},如果要定制单加一个class名。不要直接修改全局CSS变量,把自己的CSS变量定义在模块的范围内。不要写z-index:999。一般1~9,防止被遮挡10~99,绝对够用了。不要在标签上定义style属性。不要在JS代码中做样式微调,这样今后无法统一升级CSS样式。只有完全不可修改的样式才能用!important,利用选择器优先级调整样式。
避免CSS代码的误改 / 漏改 将选择器拆开写,如.card-a, .card-b { … },写时方便,改时难,修改时容易影响其它元素,不如分开写(除非像css reset这种特别确定的情况)。将样式集中在一起,容易改错。保持CSS代码和文件在相应的层级上,同一模块的放一起。避免混入通用样式中,为了避免改错,允许适当冗余。用@media时,会集中覆写一批元素的样式,更新样式时非常容易遗漏。所以必须拆开写,和对应模块的样式放在一起。不要集中放在文件底部,或是集中放在某一个文件里。及时清除“死代码”。定义样式要写全,微调样式要写具体,如:.mod {
margin: 0;
}
/* 其它地方需要微调时 */
.biz-card .mod {
margin-bottom: 16px;
}
避免CSS样式冲突 限定作用范围。如,.my-module .xxx { … }。业务代码中的样式要加前缀,或借鉴BEM命名方式。如:.overview-card-title { … }。用CSS Module也可以。注意选择器的精确性。级层过长过于复杂的CSS选择器会影响性能,但要注意:有时需要精确选择某些元素,如仅选择一级子元素,.overview-card-content > .item { … }。
防止内容不对齐 应该说Flexbox侧重“对齐”,Grids是专为布局设计的。受字体、行高等因素影响(如图),用Flexbox实现对齐最可靠:
1、height / line-height 不可靠。
2、display:inline-block / vertical-align:middle不可靠。
防止内容溢出 包括文字 / 图表等内容在宽度变化时或是英文版下容易出现溢出。
1、图表要支持自动 resize。
2、图片要限制大小范围,如:max-width、max-height min(100px, 100%)、max(100px, 100%)(注意:min() / max() 兼容性:chrome 79+ / safari 11 / firefox 75)
3、不要固定宽/高。(见规则3)
4、不要在容器元素定义overflow:hidden防止内容不可见。宁可难看关键信息也要显示全。
5、用min-width:0防止Flexbox项被内容撑开。例如:html如下,.canvas的style是JS写死的,不可避免的溢出了(如图)。
<div class="wrapper"><div class="item"> <div class="canvas" style="width:300px;height:200px;">canvas</div></div></div>
这种情况下.item可以定义为:
display: flex;
min-width: 0;
防止内容被遮挡 定义负值时(负margin / top / left),小心内容被遮挡,避免这么定义。定义margin统一朝一个方向,向下和向右定义,再重置一下:last-child。position: relative 平时很常用,发生遮挡时会造成链接无法点击。
防止可点击区域过小 小于32x32像素的可点击元素,通过下面的方式扩大可点击区域:
.btn-text {
position: relative;
}
// 比 padding 副作用小
.btn-text::before {
content: ‘’;
position: absolute;
top: -6px;
left: -8px;
right: -8px;
bottom: -6px;
}
防止内容显示不全 / 被截断 在定义overflow:hidden时,就要考虑内容是否有被截断的可能。一般不要加在容器元素上。防止长文字被生生截断,加省略号。UI实现过程中要对内容做出判断,哪些是不应该折行的,哪些是不应该省略的,如:
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
防止图片变形 图片被置于特定比例的容器中时,固定宽/高和约束最大宽/高,都可能会导致图片变形。
.head img {
width: 100%;
height: 100%;
object-fit: cover;
}
在Flexbox容器内,图片高度会被自动拉伸。因为不要定义align-items,默认是stretch。
防止图片加载失败 需要考虑图片加载慢或加载失败的情景。在图片的容器上加边或加底色。
Flexbox常见防御性写法 Flexbox的默认表现比较多,不能简单的定义display:flex,或是flex:1。
1. Flexbox容器元素通常要做如下定义:要支持多行(默认是单行),交叉轴上垂直居中(默认是stretch),主轴上采用space-between,将自由空间分配到相邻元素之间。一般都要写上:
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
2. Flexbox的盒子元素要定义间距。

3. 数据

  • 请求失败问题
  • 字段问题问题
  • 状态不全问题

4. 性能

  • 显示性能
  • 运行性能
  • 交互性能

5. 安全