异常日志

错误码

  • 【强制】错误码的制定原则:快速溯源、简单易记、沟通标准化。
  • 【强制】错误码不体现版本号和错误等级信息。
  • 【强制】全部正常,但不得不填充错误码时返回五个零:00000。
  • 【强制】错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。
  • 【强制】编号不与公司业务架构,更不与组织架构挂钩,一切与平台先到先申请的原则进行,审批生效,编号即被永久固定。
  • 【强制】错误码使用者避免随意定义新的错误码。
  • 【强制】错误码不能直接输出给用户作为提示信息使用。
  • 【推荐】错误码之外的业务独特信息由 error_message 来承载,而不是让错误码本身涵盖过多具体业务属性
  • 【推荐】在获取第三方服务错误码时,向上抛出允许本系统转义,由 C 转为 B,并且在错误信息上带上原有的第三方错误码。
  • 【参考】错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码。
  • 【参考】错误码的后三位编号与 HTTP 状态码没有任何关系
  • 【参考】错误码尽量有利于不同文化背景的开发者进行交流与代码协作。
  • 【参考】错误码即人性,感性认知+口口相传,使用纯数字来进行错误码编排不利于感性记忆和分类。

异常处理

  • 【强制】Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。
  • 【强制】异常不要用来做流程控制,条件控制。
  • 【强制】catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
  • 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
  • 【强制】事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务。
  • 【强制】finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
  • 【强制】不要在 finally 块中使用 return。
  • 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
  • 【强制】在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable类来进行拦截。
  • 【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。
  • 【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
    • 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
    • 数据库的查询结果可能为 null。
    • 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
    • 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
    • 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
    • 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
    • 正例:使用 JDK8 的 Optional 类来防止 NPE 问题
  • 【推荐】定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(),更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等
  • 【参考】对于公司外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、“错误码”、“错误简短信息”;而应用内部推荐异常抛出。
  • 【参考】避免出现重复的代码(Don’t Repeat Yourself),即 DRY 原则。

日志规约

  • 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架(SLF4J、JCL–Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
  • 【强制】所有日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。对于当天日志,以“应用名.log”来保存,保存在/home/admin/应用名/logs/目录下,过往日志格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd
  • 【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。logType:日志类型,如 stats/monitor/access 等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找
  • 【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。
  • 【强制】对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断。
  • 【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
  • 【强制】生产环境禁止直接使用 System.out 或 System.err 输出日志或使用e.printStackTrace()打印异常堆栈
  • 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出
  • 【强制】日志打印时禁止直接用 JSON 工具将对象转换成 String。
  • 【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
  • 【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。
  • 【推荐】尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用中文描述即可,否则容易产生歧义。

单元测试

  • 【强制】好的单元测试必须遵守 AIR 原则
    • A:Automatic(自动化)
    • I:Independent(独立性)
    • R:Repeatable(可重复)
  • 【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。
  • 【强制】保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
  • 【强制】单元测试是可以重复执行的,不能受到外界环境的影响。
  • 【强制】对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。
  • 【强制】核心业务、核心应用、核心模块的增量代码确保单元测试通过。
  • 【强制】单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。
  • 【推荐】单元测试的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都要达到 100%
  • 【推荐】编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
    • B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
    • C:Correct,正确的输入,并得到预期的结果。
    • D:Design,与设计文档相结合,来编写单元测试。
    • E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。
  • 【推荐】对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。
  • 【推荐】和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。
  • 【推荐】对于不可测的代码在适当的时机做必要的重构,使代码变得可测,避免为了达到测试要求而书写不规范测试代码。
  • 【推荐】在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例。
  • 【推荐】单元测试作为一种质量保障手段,在项目提测前完成单元测试,不建议项目发布后补充单元测试用例。
  • 【参考】为了更方便地进行单元测试,业务代码应避免以下情况:
    • 构造方法中做的事情过多。
    • 存在过多的全局变量和静态方法。
    • 存在过多的外部依赖。
    • 存在过多的条件语句。
  • 【参考】不要对单元测试存在如下误解:
    • 那是测试同学干的事情。本文是开发手册,凡是本文内容都是与开发同学强相关的。
    • 单元测试代码是多余的。系统的整体功能与各单元部件的测试正常与否是强相关的。
    • 单元测试代码不需要维护。一年半载后,那么单元测试几乎处于废弃状态。
    • 单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。

安全规约

  • 【强制】隶属于用户个人的页面或者功能必须进行权限控制校验
  • 【强制】用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
  • 【强制】用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库。
  • 【强制】用户请求传入的任何参数必须做有效性验证。
  • 【强制】禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
  • 【强制】表单、AJAX 提交必须执行 CSRF 安全验证。
  • 【强制】URL 外部重定向传入的目标地址必须执行白名单过滤
  • 【强制】在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损。
  • 【推荐】发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。

数据库

建表规约

  • 【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否)。
  • 【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
  • 【强制】表名不使用复数名词。
  • 【强制】禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
  • 【强制】主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
  • 【强制】小数类型为 decimal,禁止使用 float 和 double。
  • 【强制】如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
  • 【强制】varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
  • 【强制】表必备三字段:id, gmt_create, gmt_modified。
  • 【推荐】表的命名最好是遵循“业务名称_表的作用”。
  • 【推荐】库名与应用名称尽量一致。
  • 【推荐】如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
  • 【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
    • 不是频繁修改的字段。
    • 不是唯一索引的字段。
    • 不是 varchar 超长字段,更不能是 text 字段。
  • 【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
  • 【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。

索引规约

  • 【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
  • 【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要有索引。
  • 【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。
  • 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
  • 【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
  • 【推荐】利用覆盖索引来进行查询操作,避免回表。
  • 【推荐】利用延迟关联或者子查询优化超多分页场景。
  • 【推荐】SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts最好。
  • 【推荐】建组合索引的时候,区分度最高的在最左边。
  • 【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。
  • 【参考】创建索引时避免有如下极端误解:
    • 索引宁滥勿缺。认为一个查询就需要建一个索引。
    • 吝啬索引的创建。认为索引会消耗空间、严重拖慢记录的更新以及行的新增速度。
    • 抵制惟一索引。认为惟一索引一律需要在应用层通过“先查后插”方式解决。

SQL语句

  • 【强制】不要使用 count(列名)或 count(常量)来替代 count(*),count(*)是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
  • 【强制】count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。
  • 【强制】当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为NULL,因此使用 sum()时需注意 NPE 问题。
  • 【强制】使用 ISNULL()来判断是否为 NULL 值。
  • 【强制】代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。
  • 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
  • 【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
  • 【强制】数据订正(特别是删除或修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句。
  • 【强制】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。
  • 【推荐】SQL 语句中表的别名前加 as,并且以 t1、t2、t3、…的顺序依次命名。
  • 【推荐】in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内。
  • 【参考】因国际化需要,所有的字符存储与表示,均采用 utf8 字符集,那么字符计数方法需要注意。
  • 【参考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。

ORM映射

  • 【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
  • 【强制】POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。
  • 【强制】不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个与之对应。
  • 【强制】sql.xml 配置参数使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入。
  • 【强制】iBATIS 自带的 queryForList(String statementName,int start,int size)不推荐使用。
  • 【强制】不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。
  • 【强制】更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间。
  • 【推荐】不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储
  • 【参考】@Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
  • 【参考】中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带上此条件;表示不为空且不为 null 时执行;表示不为 null 值时执行。

工程结构

应用分层

  • 【推荐】图中默认上层依赖于下层,箭头关系表示可直接依赖,如:开放接口层可以依赖于Web 层,也可以直接依赖于 Service 层,依此类推:

    image-20230319203514156

    • 开放接口层:可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装成 http 接口;网关控制层等。
    • 终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移动端展示等。
    • Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
    • Service 层:相对具体的业务逻辑服务层。
    • Manager 层:通用业务处理层,它有如下特征:
    1. 对第三方平台封装的层,预处理返回结果及转化异常信息。
    2. 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。
    3. 与 DAO 层交互,对多个 DAO 的组合复用。
    • DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase、OB 等进行数据交互。
    • 外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。
  • 【参考】(分层异常处理规约)在 DAO 层,产生的异常类型有很多,无法用细粒度的异常进行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因为日志在 Manager/Service 层一定需要捕获并打印到日志文件中去,如果同台服务器再打日志,浪费性能和存储。在 Service 层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场。Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致,如果是单独部署,则采用与 Service 一致的处理方式。Web 层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,尽量加上友好的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。

  • 【参考】分层领域模型规约:

    • DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
    • DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
    • BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象。
    • Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。
    • VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。

二方库依赖

  • 【强制】定义 GAV 遵从以下规则:
    • GroupID 格式:com.{公司/BU }.业务线 [.子业务线],最多 4 级。
    • ArtifactID 格式:产品线名-模块名。语义不重复不遗漏,先到中央仓库去查证一下。
    • Version:详细规定参考下方。
  • 【强制】二方库版本号命名方式:主版本号.次版本号.修订号
    • 主版本号:产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级。
    • 次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改。
    • 修订号:保持完全兼容性,修复 BUG、新增次要功能特性等。
  • 【强制】线上应用不要依赖 SNAPSHOT 版本(安全包除外);正式发布的类库必须先去中央仓库进行查证,使 RELEASE 版本号有延续性,且版本号不允许覆盖升级。
  • 【强制】二方库的新增或升级,保持除功能点之外的其它 jar 包仲裁结果不变。如果有改变,必须明确评估和验证。
  • 【强制】二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的 POJO 对象。
  • 【强制】依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致。
  • 【强制】禁止在子项目的 pom 依赖中出现相同的 GroupId,相同的 ArtifactId,但是不同的Version。
  • 【推荐】底层基础技术框架、核心数据管理平台、或近硬件端系统谨慎引入第三方实现。
  • 【推荐】所有 pom 文件中的依赖声明放在语句块中,所有版本仲裁放在语句块中。
  • 【推荐】二方库不要有配置项,最低限度不要再增加配置项。
  • 【推荐】不要使用不稳定的工具包或者 Utils 类。
  • 【参考】为避免应用二方库的依赖冲突问题,二方库发布者应当遵循以下原则:
    • 精简可控原则。移除一切不必要的 API 和依赖,只包含 Service API、必要的领域模型对象、Utils 类、常量、枚举等。如果依赖其它二方库,尽量是 provided 引入,让二方库使用者去依赖具体版本号;无 log具体实现,只依赖日志框架。
    • 稳定可追溯原则。每个版本的变化应该被记录,二方库由谁维护,源码在哪里,都需要能方便查到。除非用户主动升级版本,否则公共二方库的行为不应该发生变化。

服务器

  • 【推荐】高并发服务器建议调小 TCP 协议的 time_wait 超时时间。
  • 【推荐】调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd)。
  • 【推荐】给 JVM 环境参数设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM场景时输出 dump 信息。
  • 【推荐】在线上生产环境,JVM 的 Xms 和 Xmx 设置一样大小的内存容量,避免在 GC 后调整堆大小带来的压力。
  • 【参考】服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL Broker 生成,否则因线上采用 HTTPS 协议而导致浏览器提示“不安全“。此外,还会带来 URL 维护不一致的问题。

设计规约

  • 【强制】存储方案和底层数据结构的设计获得评审一致通过,并沉淀成为文档。
  • 【强制】在需求分析阶段,如果与系统交互的 User 超过一类并且相关的 User Case 超过 5 个,使用用例图来表达更加清晰的结构化需求。
  • 【强制】如果某个业务对象的状态超过 3 个,使用状态图来表达并且明确状态变化的各个触发条件。
  • 【强制】如果系统中某个功能的调用链路上的涉及对象超过 3 个,使用时序图来表达并且明确各调用环节的输入与输出。
  • 【强制】如果系统中模型类超过 5 个,并且存在复杂的依赖关系,使用类图来表达并且明确类之间的关系。
  • 【强制】如果系统中超过 2 个对象之间存在协作关系,并且需要表示复杂的处理流程,使用活动图来表示。
  • 【推荐】系统架构设计时明确以下目标:
    • 确定系统边界。确定系统在技术层面上的做与不做。
    • 确定系统内模块之间的关系。确定模块之间的依赖关系及模块的宏观输入与输出。
    • 确定指导后续设计与演化的原则。使后续的子系统或模块设计在一个既定的框架内和技术方向上继续演化。
    • 确定非功能性需求。非功能性需求是指安全性、可用性、可扩展性等。
  • 【推荐】需求分析与系统设计在考虑主干功能的同时,需要充分评估异常流程与业务边界。
  • 【推荐】类在设计与实现时要符合单一原则。
  • 【推荐】谨慎使用继承的方式来进行扩展,优先使用聚合/组合的方式来实现。
  • 【推荐】系统设计阶段,根据依赖倒置原则,尽量依赖抽象类与接口,有利于扩展与维护。
  • 【推荐】系统设计阶段,注意对扩展开放,对修改闭合。
  • 【推荐】系统设计阶段,共性业务或公共行为抽取出来公共模块、公共配置、公共类、公共方法等,在系统中不出现重复代码的情况。
  • 【推荐】避免如下误解:敏捷开发 = 讲故事 + 编码 + 发布。
  • 【参考】设计文档的作用是明确需求、理顺逻辑、后期维护,次要目的用于指导编码。
  • 【参考】可扩展性的本质是找到系统的变化点,并隔离变化点。
  • 【参考】设计的本质就是识别和表达系统难点。
  • 【参考】代码即文档的观点是错误的,清晰的代码只是文档的某个片断,而不是全部。
  • 【参考】在做无障碍产品设计时,需要考虑到:
    • 所有可交互的控件元素必须能被 tab 键聚焦,并且焦点顺序需符合自然操作逻辑。
    • 用于登陆校验和请求拦截的验证码均需提供图形验证以外的其它方式。
    • 自定义的控件类型需明确交互方式。