竹磬网-邵珠庆の日记 生命只有一次,你可以用它来做些更多伟大的事情–Make the world a little better and easier


309月/180

【开发规范】规范文档:MySQL规范2

发布在 邵珠庆

1. 规范背景与目的

MySQL数据库与 Oracle、 SQL Server 等数据库相比,有其内核上的优势与劣势。我们在使用MySQL数据库的时候需要遵循一定规范,扬长避短。本规范旨在帮助或指导RD、QA、OP等技术人员做出适合线上业务的数据库设计。在数据库变更和处理流程、数据库表设计、SQL编写等方面予以规范,从而为公司业务系统稳定、健康地运行提供保障。

2. 设计规范

2.1 数据库设计

以下所有规范会按照【高危】、【强制】、【建议】三个级别进行标注,遵守优先级从高到低。

对于不满足【高危】和【强制】两个级别的设计,DBA会强制打回要求修改。

2.1.1 库名

  1. 【强制】库的名称必须控制在32个字符以内,相关模块的表名与表名之间尽量提现join的关系,如user表和user_login表。
  2. 【强制】库的名称格式:业务系统名称_子系统名,同一模块使用的表名尽量使用统一前缀。
  3. 【强制】一般分库名称命名格式是库通配名_编号,编号从0开始递增,比如wenda_001以时间进行分库的名称格式是“库通配名_时间”
  4. 【强制】创建数据库时必须显式指定字符集,并且字符集只能是utf8或者utf8mb4。创建数据库SQL举例:create database db1 default character set utf8;

2.1.2 表结构

  1. 【强制】表和列的名称必须控制在32个字符以内,表名只能使用字母、数字和下划线,一律小写。
  2. 【强制】表名要求模块名强相关,如师资系统采用”sz”作为前缀,渠道系统采用”qd”作为前缀等。
  3. 【强制】创建表时必须显式指定字符集为utf8或utf8mb4。
  4. 【强制】创建表时必须显式指定表存储引擎类型,如无特殊需求,一律为InnoDB。当需要使用除InnoDB/MyISAM/Memory以外的存储引擎时,必须通过DBA审核才能在生产环境中使用。因为Innodb表支持事务、行锁、宕机恢复、MVCC等关系型数据库重要特性,为业界使用最多的MySQL存储引擎。而这是其他大多数存储引擎不具备的,因此首推InnoDB。
  5. 【强制】建表必须有comment
  6. 【建议】建表时关于主键:(1)强制要求主键为id,类型为int或bigint,且为auto_increment (2)标识表里每一行主体的字段不要设为主键,建议设为其他字段如user_idorder_id等,并建立unique key索引(可参考cdb.teacher表设计)。因为如果设为主键且主键值为随机插入,则会导致innodb内部page分裂和大量随机I/O,性能下降。
  7. 【建议】核心表(如用户表,金钱相关的表)必须有行数据的创建时间字段create_time和最后更新时间字段update_time,便于查问题。
  8. 【建议】表中所有字段必须都是NOT NULL属性,业务可以根据需要定义DEFAULT值。因为使用NULL值会存在每一行都会占用额外存储空间、数据迁移容易出错、聚合函数计算结果偏差等问题。
  9. 【建议】建议对表里的blobtext等大字段,垂直拆分到其他表里,仅在需要读这些对象的时候才去select。
  10. 【建议】反范式设计:把经常需要join查询的字段,在其他表里冗余一份。如user_name属性在user_accountuser_login_log等表里冗余一份,减少join查询。
  11. 【强制】中间表用于保留中间结果集,名称必须以tmp_开头。备份表用于备份或抓取源表快照,名称必须以bak_开头。中间表和备份表定期清理。
  12. 【强制】对于超过100W行的大表进行alter table,必须经过DBA审核,并在业务低峰期执行。因为alter table会产生表锁,期间阻塞对于该表的所有写入,对于业务可能会产生极大影响。

2.1.3 列数据类型优化

  1. 【建议】表中的自增列(auto_increment属性),推荐使用bigint类型。因为无符号int存储范围为-2147483648~2147483647(大约21亿左右),溢出后会导致报错。
  2. 【建议】业务中选择性很少的状态status、类型type等字段推荐使用tinytint或者smallint类型节省存储空间。
  3. 【建议】业务中IP地址字段推荐使用int类型,不推荐用char(15)。因为int只占4字节,可以用如下函数相互转换,而char(15)占用至少15字节。一旦表数据行数到了1亿,那么要多用1.1G存储空间。
    SQL:select inet_aton(\'192.168.2.12\'); select inet_ntoa(3232236044);
    PHP: ip2long(‘192.168.2.12’); long2ip(3530427185);
  4. 【建议】不推荐使用enumset。 因为它们浪费空间,且枚举值写死了,变更不方便。推荐使用tinyintsmallint
  5. 【建议】不推荐使用blobtext等类型。它们都比较浪费硬盘和内存空间。在加载表数据时,会读取大字段到内存里从而浪费内存空间,影响系统性能。建议和PM、RD沟通,是否真的需要这么大字段。Innodb中当一行记录超过8098字节时,会将该记录中选取最长的一个字段将其768字节放在原始page里,该字段余下内容放在overflow-page里。不幸的是在compact行格式下,原始pageoverflow-page都会加载。
  6. 【建议】存储金钱的字段,建议用int,程序端乘以100和除以100进行存取。因为int占用4字节,而double占用8字节,空间浪费。
  7. 【建议】文本数据尽量用varchar存储。因为varchar是变长存储,比char更省空间。MySQL server层规定一行所有文本最多存65535字节,因此在utf8字符集下最多存21844个字符,超过会自动转换为mediumtext字段。而text在utf8字符集下最多存21844个字符,mediumtext最多存2^24/3个字符,longtext最多存2^32个字符。一般建议用varchar类型,字符数不要超过2700。
  8. 【建议】时间类型尽量选取timestamp。因为datetime占用8字节,timestamp仅占用4字节,但是范围为1970-01-01 00:00:012038-01-01 00:00:00。更为高阶的方法,选用int来存储时间,使用SQL函数unix_timestamp()from_unixtime()来进行转换。

详细存储大小参加下图:

自动草稿

自动草稿

2.1.4 索引设计

  1. 【强制】InnoDB表必须主键为id int/bigint auto_increment,且主键值禁止被更新。
  2. 【建议】主键的名称以“pk_”开头,唯一键以“uk_”或“uq_”开头,普通索引以“idx_”开头,一律使用小写格式,以表名/字段的名称或缩写作为后缀。
  3. 【强制】InnoDB和MyISAM存储引擎表,索引类型必须为BTREE;MEMORY表可以根据需要选择HASH或者BTREE类型索引。
  4. 【强制】单个索引中每个索引记录的长度不能超过64KB。
  5. 【建议】单个表上的索引个数不能超过7个。
  6. 【建议】在建立索引时,多考虑建立联合索引,并把区分度最高的字段放在最前面。如列userid的区分度可由select count(distinct userid)计算出来。
  7. 【建议】在多表join的SQL里,保证被驱动表的连接列上有索引,这样join执行效率最高。
  8. 【建议】建表或加索引时,保证表里互相不存在冗余索引。对于MySQL来说,如果表里已经存在key(a,b),则key(a)为冗余索引,需要删除。

2.1.5 分库分表、分区表

  1. 【强制】分区表的分区字段(partition-key)必须有索引,或者是组合索引的首列。
  2. 【强制】单个分区表中的分区(包括子分区)个数不能超过1024。
  3. 【强制】上线前RD或者DBA必须指定分区表的创建、清理策略。
  4. 【强制】访问分区表的SQL必须包含分区键。
  5. 【建议】单个分区文件不超过2G,总大小不超过50G。建议总分区数不超过20个。
  6. 【强制】对于分区表执行alter table操作,必须在业务低峰期执行。
  7. 【强制】采用分库策略的,库的数量不能超过1024
  8. 【强制】采用分表策略的,表的数量不能超过4096
  9. 【建议】单个分表不超过500W行,ibd文件大小不超过2G,这样才能让数据分布式变得性能更佳。
  10. 【建议】水平分表尽量用取模方式,日志、报表类数据建议采用日期进行分表。

2.1.6 字符集

  1. 【强制】数据库本身库、表、列所有字符集必须保持一致,为utf8utf8mb4
  2. 【强制】前端程序字符集或者环境变量中的字符集,与数据库、表的字符集必须一致,统一为utf8

2.1.7 程序层DAO设计建议

  1. 【建议】新的代码不要用model,推荐使用手动拼SQL 绑定变量传入参数的方式。因为model虽然可以使用面向对象的方式操作db,但是其使用不当很容易造成生成的SQL非常复杂,且model层自己做的强制类型转换性能较差,最终导致数据库性能下降。
  2. 【建议】前端程序连接MySQL或者redis,必须要有连接超时和失败重连机制,且失败重试必须有间隔时间。
  3. 【建议】前端程序报错里尽量能够提示MySQL或redis原生态的报错信息,便于排查错误。
  4. 【建议】对于有连接池的前端程序,必须根据业务需要配置初始、最小、最大连接数,超时时间以及连接回收机制,否则会耗尽数据库连接资源,造成线上事故。
  5. 【建议】对于log或history类型的表,随时间增长容易越来越大,因此上线前RD或者DBA必须建立表数据清理或归档方案。
  6. 【建议】在应用程序设计阶段,RD必须考虑并规避数据库中主从延迟对于业务的影响。尽量避免从库短时延迟(20秒以内)对业务造成影响,建议强制一致性的读开启事务走主库,或更新后过一段时间再去读从库。
  7. 【建议】多个并发业务逻辑访问同一块数据(innodb表)时,会在数据库端产生行锁甚至表锁导致并发下降,因此建议更新类SQL尽量基于主键去更新。
  8. 【建议】业务逻辑之间加锁顺序尽量保持一致,否则会导致死锁。
  9. 【建议】对于单表读写比大于10:1的数据行或单个列,可以将热点数据放在缓存里(如mecache或redis),加快访问速度,降低MySQL压力。

2.1.8 一个规范的建表语句示例

一个较为规范的建表语句为:

CREATE TABLE user (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(11) NOT NULL COMMENT ‘用户id`username` varchar(45) NOT NULL COMMENT \'真实姓名\',
  `email` varchar(30) NOT NULL COMMENT ‘用户邮箱’,
  `nickname` varchar(45) NOT NULL COMMENT \'昵称\',
  `avatar` int(11) NOT NULL COMMENT \'头像\',
  `birthday` date NOT NULL COMMENT \'生日\',
  `sex` tinyint(4) DEFAULT \'0\' COMMENT \'性别\',
  `short_introduce` varchar(150) DEFAULT NULL COMMENT \'一句话介绍自己,最多50个汉字\',
  `user_resume` varchar(300) NOT NULL COMMENT \'用户提交的简历存放地址\',
  `user_register_ip` int NOT NULL COMMENT ‘用户注册时的源ip’,
  `create_time` timestamp NOT NULL COMMENT ‘用户记录创建的时间’,
  `update_time` timestamp NOT NULL COMMENT ‘用户资料修改的时间’,
  `user_review_status` tinyint NOT NULL COMMENT ‘用户资料审核状态,1为通过,2为审核中,3为未通过,4为还未提交审核’,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_id` (`user_id`),
  KEY `idx_username`(`username`),
  KEY `idx_create_time`(`create_time`,`user_review_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=\'网站用户基本信息\';

2.2 SQL编写

2.2.1 DML语句

  1. 【强制】SELECT语句必须指定具体字段名称,禁止写成*。因为select *会将不该读的数据也从MySQL里读出来,造成网卡压力。且表字段一旦更新,但model层没有来得及更新的话,系统会报错。
  2. 【强制】insert语句指定具体字段名称,不要写成insert into t1 values(…),道理同上。
  3. 【建议】insert into…values(XX),(XX),(XX)…。这里XX的值不要超过5000个。值过多虽然上线很很快,但会引起主从同步延迟。
  4. 【建议】SELECT语句不要使用UNION,推荐使用UNION ALL,并且UNION子句个数限制在5个以内。因为union all不需要去重,节省数据库资源,提高性能。
  5. 【建议】in值列表限制在500以内。例如select… where userid in(….500个以内…),这么做是为了减少底层扫描,减轻数据库压力从而加速查询。
  6. 【建议】事务里批量更新数据需要控制数量,进行必要的sleep,做到少量多次。
  7. 【强制】事务涉及的表必须全部是innodb表。否则一旦失败不会全部回滚,且易造成主从库同步终端。
  8. 【强制】写入和事务发往主库,只读SQL发往从库。
  9. 【强制】除静态表或小表(100行以内),DML语句必须有where条件,且使用索引查找。
  10. 【强制】生产环境禁止使用hint,如sql_no_cacheforce indexignore keystraight join等。因为hint是用来强制SQL按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的,因此我们要相信MySQL优化器!
  11. 【强制】where条件里等号左右字段类型必须一致,否则无法利用索引。
  12. 【建议】SELECT|UPDATE|DELETE|REPLACE要有WHERE子句,且WHERE子句的条件必需使用索引查找。
  13. 【强制】生产数据库中强烈不推荐大表上发生全表扫描,但对于100行以下的静态表可以全表扫描。查询数据量不要超过表行数的25%,否则不会利用索引。
  14. 【强制】WHERE 子句中禁止只使用全模糊的LIKE条件进行查找,必须有其他等值或范围查询条件,否则无法利用索引。
  15. 【建议】索引列不要使用函数或表达式,否则无法利用索引。如where length(name)=\'Admin\'where user_id 2=10023
  16. 【建议】减少使用or语句,可将or语句优化为union,然后在各个where条件上建立索引。如where a=1 or b=2优化为where a=1… union …where b=2, key(a),key(b)
  17. 【建议】分页查询,当limit起点较高时,可先用过滤条件进行过滤。如select a,b,c from t1 limit 10000,20;优化为: select a,b,c from t1 where id>10000 limit 20;

2.2.2 多表连接

  1. 【强制】禁止跨db的join语句。因为这样可以减少模块间耦合,为数据库拆分奠定坚实基础。
  2. 【强制】禁止在业务的更新类SQL语句中使用join,比如update t1 join t2…
  3. 【建议】不建议使用子查询,建议将子查询SQL拆开结合程序多次查询,或使用join来代替子查询。
  4. 【建议】线上环境,多表join不要超过3个表。
  5. 【建议】多表连接查询推荐使用别名,且SELECT列表中要用别名引用字段,数据库.表格式,如select a from db1.table1 alias1 where …
  6. 【建议】在多表join中,尽量选取结果集较小的表作为驱动表,来join其他表。

2.2.3 事务

  1. 【建议】事务中INSERT|UPDATE|DELETE|REPLACE语句操作的行数控制在2000以内,以及WHERE子句中IN列表的传参个数控制在500以内。
  2. 【建议】批量操作数据时,需要控制事务处理间隔时间,进行必要的sleep,一般建议值5-10秒。
  3. 【建议】对于有auto_increment属性字段的表的插入操作,并发需要控制在200以内。
  4. 【强制】程序设计必须考虑“数据库事务隔离级别”带来的影响,包括脏读、不可重复读和幻读。线上建议事务隔离级别为repeatable-read
  5. 【建议】事务里包含SQL不超过5个(支付业务除外)。因为过长的事务会导致锁数据较久,MySQL内部缓存、连接消耗过多等雪崩问题。
  6. 【建议】事务里更新语句尽量基于主键或unique key,如update … where id=XX; 否则会产生间隙锁,内部扩大锁定范围,导致系统性能下降,产生死锁。
  7. 【建议】尽量把一些典型外部调用移出事务,如调用webservice,访问文件存储等,从而避免事务过长。
  8. 【建议】对于MySQL主从延迟严格敏感的select语句,请开启事务强制访问主库。

2.2.4 排序和分组

  1. 【建议】减少使用order by,和业务沟通能不排序就不排序,或将排序放到程序端去做。order bygroup bydistinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。
  2. 【建议】order bygroup bydistinct这些SQL尽量利用索引直接检索出排序好的数据。如where a=1 order by可以利用key(a,b)
  3. 【建议】包含了order bygroup bydistinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢。

2.2.5 线上禁止使用的SQL语句

  1. 【高危】禁用update|delete t1 … where a=XX limit XX; 这种带limit的更新语句。因为会导致主从不一致,导致数据错乱。建议加上order by PK
  2. 【高危】禁止使用关联子查询,如update t1 set … where name in(select name from user where…);效率极其低下。
  3. 【强制】禁用procedure、function、trigger、views、event、外键约束。因为他们消耗数据库资源,降低数据库实例可扩展性。推荐都在程序端实现。
  4. 【强制】禁用insert into …on duplicate key update…在高并发环境下,会造成主从不一致。
  5. 【强制】禁止联表更新语句,如update t1,t2 where t1.id=t2.id…
22月/180

【流程规范】规范文档:新人培养方法论

发布在 邵珠庆

Mentor的作用

新人充满了求知欲和好奇心,学习和成长的过程中容易迷失自己,从一个萝卜坑掉进另外一个萝卜坑。
导师 == 队长(跑得快,投的准,带领全队,执行战术)
经理 == 教练  (跑不动,投不准,不下场,有经验,定战术,担责任)
职责:
  • 引路人,帮助新人快速熟悉环境、融入团队,并能在团队中找准定位,充分发挥新人的价值;
  • 通常说,导师对新人的指导周期是入职到能够独立承担工作(自己就能指导要解决什么问题或者能够指导找谁解决问题);
  • 导师对新人在学徒期的产出质量负责,新人的成长及贡献归功于导师;

Mentor的要求

能力项
要求
专业能力
  • 入职时间>1年,熟悉公司文化
  • 级别 P5+
  • 打铁需要自身硬
  • 能为新人答疑解惑
正能量
  • 技术追求
  • 工作态度
  • 主人翁精神等
  • 自驱力
沟通能力
  • 能够清晰扼要的表达出问题的解决方案
  • 能够和新人保持良好的关系,顺畅的沟通
耐心
  • 新人指导需要耐心
  • 言传身教

工作指南

时间点
工作指南
目标
入职之前
  1. 确认自己有收到新人的简历
  2. 并和主管沟通新人的大体情况以及进来后的主要工作安排
了解新人背景,提前规划工作
入职当天
  1. 和主管一起等部门助理带领新人来提前安排好的座位
  2. 和主管一起和新人一起吃第一顿饭,告知附近生活相关设施
  3. 协助新人安装电脑、配置开发环境
  4. 上线第一个敏捷功能,体验到自己的价值
开发环境能跑起来
前两周
【明确目标】熟悉、学习为主,但是任务明确。新建jira task并明确每个task的目标
【日常辅导】每天在新人座位上关心1~2次进展及问题,有问题现场协助解决或者帮他找到能协助解决问题的人
【增进了解,协助融入】不定期的一起吃饭,闲聊等,了解日常生活状况,告知部门活动相关信息,输出公司愿景,部门价值观等
【每周review】每周有一次正式的 1:1 沟通,讨论上一个阶段目标的完成情况,成绩和不足,并提供提升建议,明确下一个阶段的预期目标。review内容。
掌握jira的工作流程及良好的工作习惯
掌握开发语言,了解开发框架
了解技术部的考勤、伯乐奖、部门活动、书架计划等各类制度
前一个月
从第三周开始,根据新人进展情况,每天的辅导频次可增加或减少
继续每周一次的 1:1 沟通
【月度review】请新人写一个正式的月度总结,并和他就总结内容进行一次月度的一对一沟通,全面回顾一个月的进展,成绩和不足
第四周和月度review一起进行。review内容。
掌握git工作流程及良好的工作习惯
掌握wiki的撰写方法并有wiki产出
熟悉开发语言,掌握开发框架
熟悉至少一个子系统并能修复bug,新增小feature
理解公司愿景,部门价值观
前三个月
从第二个月开始,根据新人进展情况,【每周review】频次可增加或减少
【月度review】月度review仍然继续
【季度review】这也是一次转正review。review内容。
熟悉开发框架
完全胜任一个子系统的功能增强和bug修复的工作
三个月后 两周review一次 能够独立承担负责一个系统

注意事项

注意项
内容
自信心
根据每个新人的技术基础、学习能力,安排任务难易,把握节奏;
自信心是逐步建立起来的,注意呵护,因材施教;
自驱力
注重培养新人自我驱动能力:
责任心培养;
端到端负责;
追求卓越;
质量意识;
主动思考;
沟通能力
需求沟通:和PM谈需求时,带上新人,过程中对一些点,问问新人有什么想法;
跨团队沟通:交给一些跨团队任务,
勇于试错
鼓励新人表达自己的方案,坚持自己的想法,用于尝试;
在导师可控情况下,“看着”新人踩坑;
但相似的坑,要求不要重复踩,要有总结;
实习期,把坑踩一遍;
总结分享
养成写wiki的习惯:
较大的功能模块,尽量写wiki,组内分享;
选一些技术点(java基础,mysql,sso,死锁,性能优化,搜索等)研究,技术wiki,格式要求,分享
人情味
团队建设
一起运动,桌面足球,羽毛球,跨组认识一些人;
每周团队聚餐,鼓励新人和团队成员交流;
1:1沟通
提纲准备(eg, 放手机里);
谈话正式;
表扬要具体,比如写了一个wiki;
明确待改善的地方;
导师总结
在实践过程中,导师也要不断总结,多和其他导师、leader沟通学习;

常见问题

新人常见问题:

编号
问题
入职时间
解决方法
备注
1 环境搭建吃力,常用工具(插件,登录脚本)不清楚,口口相传 1月内 新人导航,工具导航
2 常用技术,资料,文档整理得还不太好 1月内 技术栈、知识体系
3 部署框架,测试环境,hostname设置,遇到的坑 1月内 技术栈、环境搭建
4
有的wiki比较老,更新不太及时,文档比较老
1月内 新人导航
5
项目代码,老代码(dao,APIController)没有及时清理,学习成本大
1月内 代码学习
6
陌生的技术体系和接踵而来的业务,如何适可而止的把技术面补上?
半年
技术栈、知识体系
时间管理
7
如何快速适应没有测试没有OP的开发流程?
半年 开发流程
8 知识体系不健全,导航连接需完善? 半年 技术栈、新人导航
9
开发框架,系统学习指南,知识体系梳理
1年 技术栈、新人导航
10
知识学习指南,更加具体一点:比如指定一本书,博客连接,wiki
1年 技术栈、新人导航
11
工具:插件、mysql操作,怎么倒表?常用操作?怎么服务器?怎么查日志?gc查看?
1年 工具导航
12
规范:开发规范、代码规范、上线规范、质量意识
1年 组内规范文档
13
分享:分享知识,新技术,深度控制;听不懂,跟不上问题
1年 知识分享、1v1沟通

导师常见问题:

编号
问题
问题归类
解决方法
1 怎么做培养计划? 培养计划
按照职级,目标驱动:例如,P4 -> P5
能力模型:技术能力,领导力,沟通能力,针对薄弱地方不强
2
发review周报的目的是什么?应该从哪些方面review?
review周报
3 有时比较累,什么时候放手? 节奏把控
比较费精力,规划好方案,新人只负责实施
4
这个人实在培养不出来,怎么办?
是不是只让他干脏活累活?期望太高?
分配工作的时候,应该给谁?
应该有目的,提升新人的某方面的能力?
5 新人不太“听话”?  自己觉得重要的事,新人觉得不重要?(除了技术,业务沟通、梳理,方向调研,不太热衷)
6
如何让新员工在团队中感觉到成长?
培养计划
7
如何让新员工跟上公司的节奏?
培养计划
8
如何看待新员工代码质量不高的问题?
培养方法
时间成本和质量的平衡
9
如何引导新员工快速的融入团队?
培养方法 特指工作之外的生活
10
知识学习体系,不太明晰,培养比较随意
培养计划
11
零基础同学,学习路线怎么规划?
培养计划 边学边练,给压力要结果,循序渐进
12
wiki及时维护,使用流畅
新人导航
13
业务知识,业务流程,业务词典,梳理问题
新人导航
14
技术氛围和技术提升问题,怎么能让带的同学找到方向,有计划,并且有产出?
培养计划
15
新人容易被业务拖着而缺乏积累的时间,时间怎么分配?
培养方法

问题解决办法:

1) 知识体系整理:新人导航,工具导航,wiki及时更新
2) 培养计划模板,review周报模板,整理
3) 培训分享:新人入职培训,导师上岗培训,导师互相切磋,组内分享
4) 规范整理:开发规范、代码规范、上线规范、质量意识
12月/180

【开发规范】规范文档:API设计规范

发布在 邵珠庆

负责API接近多年了,这些年中发现现有的API存在的问题越来越多,但很多API一旦发布后就不再能修改了,即时升级和维护是必须的。一旦API发生变化,就可能对相关的调用者带来巨大的代价,用户需要排查所有调用的代码,需要调整所有与之相关的部分,这些工作对他们来说都是额外的。如果辛辛苦苦完成这些以后,还发现了相关的bug,那对用户的打击就更大。如果API经常发生变化,用户就会失去对提供方失去信心,从而也会影响目前的业务。

但是我们为什么还要修改API呢?为了API看起来更加漂亮?为了提供更多功能?为了提供更好的性能?还是仅仅觉得到了改变了时候了?对于用户来说,他们更愿意使用一个稳定但是看起来不那么时髦的API,这并不意味着我们不再改进API了。当糟糕的API带来的维护成本越来越大时,我想就是我们去重构它的时候。

如果可以回头重新再做一遍,那么我心目中的优秀的API应该是怎么样的?

判断一个API是否优秀,并不是简单地根据第一个版本给出判断的,而是要看随着时间的推移,该API是否还能存在,是否仍旧保持得不错。槽糕的API接口各种各样,但是好的API接口对于用户来说必须满足以下几个点:

  • 易学习:有完善的文档及提供尽可能多的示例和可copy-paste的代码,像其他设计工作一样,你应该应用最小惊讶原则。
  • 易使用:没有复杂的程序、复杂的细节,易于学习;灵活的API允许按字段排序、可自定义分页、 排序和筛选等。一个完整的API意味着被期望的功能都包含在内。
  • 难误用:对详细的错误提示,有些经验的用户可以直接使用API而不需要阅读文档。

而对于开发人员来说,要求又是不一样的:

  • 易阅读:代码的编写只需要一次一次,但是当调试或者修改的时候都需要对代码进行阅读。
  • 易开发:个最小化的接口是使用尽可能少的类以及尽可能少的类成员。这样使得理解、记忆、调试以及改变API更容易。

如何做到以上几点,以下是一些总结:

1、 面向用例设计

如果一个API被广泛使用了,那么就不可能了解所有使用该API的用户。如果设计者希望能够设计出被广泛使用的API,那么必须站在用户的角度来理解如何设计API库,以及如何才能设计出这样的API库。

2、 采用良好的设计思路

在设计过程中,如果能按照下面的方式来进行设计,会让这个API生命更长久

  • 面向用例的设计,收集用户建议,把自己模拟成用户,保证API设计的易用和合理
  • 保证后续的需求可以通过扩展的形式完成
  • 第一版做尽量少的内容,由于新需求可以通过扩展的形式完成,因此尽量少做事情是抑制API设计错误的一个有效方案
  • 对外提供清晰的API和文档规范,避免用户错误的使用API,尤其是避免API(见第一节)靠后级别的API被用户知晓与误用

除此之外,下面还列出了一些具体的设计方法:

  • 方法优于属性
  • 工厂方法优于构造函数
  • 避免过多继承
  • 避免由于优化或者复用代码影响API
  • 面向接口编程
  • 扩展参数应当是便利的
  • 对组件进行合理定位,确定暴露多少接口
  • 提供扩展点

3、 避免极端的意见

在设计API的时候,一定要避免任何极端的意见,尤其是以下几点:

  • 必须漂亮(API不一定需要漂亮)
  • API必须被正确地使用(用户很难理解如何正确的使用API,API的设计者要充分考虑API被误用的情况:如果一个API可能会被误用,那么它一定会被误用)
  • 必须简单(我们总会面临复杂的需求,能两者兼顾的API是更好的API)
  • 必须高性能(性能可以通过其他手段优化,不应该影响API的设计)
  • 必须绝对兼容(尽管本文一直提到如何保证兼容,但是我们仍然要意识到,一些极少情况下会遇到的不兼容是可以容忍的)

4、 有效的API评审

API设计完成以后,需要经过周密的设计评审,评审的重点如下:

  • 用例驱动,评审前必须提供完善的使用用例,确保用例的合理性和完备性。
  • 一致性,是否与系统中其他模块的接口风格一致,是否与对称接口的设计一致。
  • 简单明了,API应该简单好理解,容易学习和使用的API才不容易被误用,给我们带来更多的麻烦。
  • API尽可能少,如果一个API可以暴露也可以不暴露,那么就不要暴露他,等到用户真正有需求的时候再将它成为一个公开接口也不迟。
  • 支持持续改进,API是否能够方便地通过扩展的方式增加功能和优化。

5、 提高API的可测试性

API需要是可测试的,测试不应依赖实现,测试充分的API,尤其是经过了严格的“兼容性整合测试”的API,更能保证在升级的过程中不出现兼容性问题。兼容性整合测试,是指一组测试用例集合,这组测试用例会站在使用者的立场上使用API。在API升级以后,再检测这组测试用例是否能完全符合预期的通过测试,尽可能的发现兼容性问题。

6、 保证API的向后兼容

对于每一个API的设计者来说,都渴望做到“向后兼容”,因为不管是现在的API用户,还是潜在的API用户,都只信任那些可兼容的API。但向后兼容有多个层次上的意义,而且不同层次的向后兼容,也意味着不同的重要性和复杂度。

7、 保持逐步改善

过去我们总希望能将现有的“不合理”的设计完全推翻,然后按照现在“美好”的思路,重新设计这个API,但是在一段时间以后,又会碰到一样的状况,需要再推翻一次。 如果我们没有有效的逐步改善的办法,依靠推翻现有设计,重新设计API只能让我们回到起点,然后重现之前的过程。 要有一套行之有效的持续改善的办法来在API兼容的同时,改善API使之更好。

8、 把握API的生命周期

每一个API都是有生命周期的,我们需要让API的生命周期更长,并且在API的生命周期结束时能让其平滑的消亡。

  • 告诉用户我们是如何设计的,避免误用,提供指导,错误的使用往往是缩短API寿命的一大杀手
  • 提供试用期,API不可能一开始就是稳定,经过试用的API才能有更强的生命力
  • 为API分级:内部使用;二次开发使用;开发或试用中;稳定;弃用API。避免API被滥用的同时,我们可以通过调整API的级别,来扩大其影响力,也能更优雅的结束一个API的生命周期。

开发API的过程其实就是一个沟通交流的过程。沟通的双方就是API用户和API设计者。

9、 一些具体的实施方案

在一个API不可避免要消亡或者改变的时候,我们应该接受并且面对这个事实,下面列举了几种保证兼容性的前提下,对API进行调整的办法:

  • 将API标记为弃用,重新建立一个新的API。如果一个API不可避免要被消亡,这是唯一的办法。
  • 为其添加额外的参数或者参数选项来实现功能添加
  • 将现有API拆成两部分,提供一个精简的核心API,过去的API通过封装核心API上实现。这通常用于解决用户需要一个代码精简的版本时。
  • 在现有的API基础上进行封装,提供一个功能更丰富的包或者类

一些好的API示例:

  1. Flickr API,这里是文档的示例,同时提供了一个非常方便的API测试工具。
  2. Mediawiki API
  3. Ebay API,这里有一个非常详尽的文档示例。

 

接口作为连通客户端与数据库进行数据流通的桥梁,起着举足轻重的作用,直接影响着程序的效率性、稳定性、可靠性以及数据的正确性、完整性。客户端注重的是界面美观,操作方便顺畅,是用户最直接的感受体验,而接口则是所有数据的提供者,是用户深层的内涵体验。

因次,设计接口在一个项目中,是非常重要的。那么我就目前的经验总结下如何合理设计接口。

一、 设计原理

1. 深入了解需求

除了设计数据库的人最了解需求外,其次就是设计接口的人了,甚至有时接口开发人员还要参与到数据库设计中。从“客户端-接口-数据库”的层次上看,接口明显扮演着承上启下的角色,一方面要明白接口要什么数据,另一方面要考虑如何从数据库获取、组织数据。所以如果不了解需求,你就无法正确抽象对象来组织数据给客户端,也无法验证数据库的数据结构能否满足需求。数据库设计者要了解需求中的数据结构,而接口则更多的要了解需求中的逻辑结构以及由此衍生出的逻辑数据结构。

2. 了解数据库结构

既然接口要明白如何从数据库获取、组织数据,就当然要了解数据库结构啦。

3. 了解客户端原型

了解原型,其实更多是为了帮助你设计接口时需要提供的数据和结构。但有时当你设计时并没有原型,所以此条并不是必须要求的。但假如设计完接口后原型出来了,我们也可以拿原型还验证接口设计是否正确、合理。

二、设计原则

1. 充分理由

不是随便一个功能就要有个接口,也不是随便一个需求就要加个接口。每新建一个接口,就要有充分的理由和考虑,即这个接口的存在是十分有意义额价值的,无意义的接口不仅增加了维护的难度,更重要是对于程序的可控性的大大降低,接口也会十分臃肿。因此我放在了第一条。

2. 职责明确

一个接口只负责一个业务功能,它与设计模式里的职责单一原则类似但却不同,因为一个业务功能里可能会包含多个操作,比如查询会员,可能除了查询会员表外还要获取该会员的其他必要信息,但不要在查询会员的同时还有修改权限等类似的其他业务功能,应该分成两个接口还做。

3. 高内聚低耦合

一个接口要包含完整的业务功能,而不同接口之间的业务关联要尽可能的小。还是查询会员的例子,有时查询会员的同时,可能该会员的相关信息要随之发生变化(如状态),如果这时一条完整的业务流水线,那么就应该在一个接口里完成,而不应再单独设立接口去操作完成。就是说一个接口不应该随着另一个变化而变化或以某几个接口为前提而存在。

4. 分析角度明确

设计接口分析的角度要统一明确。否则会造成接口结构的混乱。例如,不要一会以角色的角度设计,一会儿就要以功能的角度设计。

5. 入参格式统一

所有接口的参数格式要求及风格要统一,不要一个接口参数是逗号分隔,另一个就是数组;不要一个接口日期参数是x年x月x日风格,另一个就是x-x-x。

6. 状态及消息

提供必要的接口调用状态信息。调用是否成功?如果失败,那么失败的原因是什么。这些必要的信息必须要告诉给客户端。

7. 控制数据量

一个接口返回不应该包含过多的数据量,过多的数据量不仅处理复杂,对数据传输的压力也非常大,会导致客户端反应缓慢。过多的数据量很多时候都是接口划分不明确。

8. 禁止随意拓展参数

与第1条类似,只不过是针对参数而言了。日后拓展接口可能是难以避免的,但是不要随意就加参数,加参数一定是必要且有意义的,需求改变前首先应考虑现有接口内部维护是否能满足需求,而不要通过加个参数来方便自己实现需求的难度,因为参数的更变会直接导致客户端调用的变化,容易产生版本兼容性问题。

三、设计方法

1. 抽象业务

相比抽象对象而言,抽象业务更宏观,我觉得相对也容易一些,但抽象尺度往往不太好把握。

2. 数据格式

接口定义的数据格式必须都经过充分考虑,否则会出现数据转换失败或超出长度等错误。如果无法确定,直接设置成字符串是最合适的。

3. 有意义的命名

无论是接口还是参数,名称都应该是有意义的,让人能看明白的。

总之,接口设计是一个细致的工作,设计时也会有很多矛盾,但个人倾向于粗粒度设计方向(即内聚性更高一些),这样不仅给客户端浏览接口方便明确,维护也轻松些,这么做的缺点就是某一接口扩展时不是很灵活,但可以通过重新定义一个接口来弥补,但正如上所说,新增接口还是要三思而后行的。以上很多虽然都是理论性讲解,但牢牢记住这些,并结合实际工作,就会慢慢深刻的体会到其中的含义。即理论指导实践,实践来验证理论。

12月/180

【开发规范】规范文档:MySQL规范

发布在 邵珠庆

  • 基本规范
  • 命名规范
  • 库表设计规范
  • 索引设计规范
  • 字段设计规范
  • SQL设计规范
  • 行为规范
  • 线上操作
  • 数据变更

基本规范

(1) 使用INNODB存储引擎
(2) 表字符集使用UTF8
(3) 所有表和字段都需要添加注释
(4) 不在数据库中存储图⽚、文件等大数据
(5) 禁⽌从测试、开发环境直连线上数据库

命名规范

(1) 表名:必须使用小写字母,多词之间以”_“(下划线)分隔,表名非复数名词,禁止使用mysql保留字。
(2) 字段名:必须使用小写字母,多词之间以”_“(下划线)分隔,字段名使用名词,禁止使用mysql保留字。
(3) 索引名:普通索引以”idx_“ 加上字段名命名;
唯一索引以”uk_“加上字段名命名;
组合索引的命名须按顺序包含索引中的所有字段。如:”a,b“的组合索引,命名为”idx_a_b“,名称过长可适当采用缩写。
(4) 临时库、表名必须以temp为前缀,并以⽇日期为后缀,如:temp_img_syn_to_mdc_table
(5) 备份库、表必须以bak为后缀,并以日期为后缀,如:hotel_sm_boss_info_bak20150615
(6)名称应该清晰明了,能够准确表达事物的含义,最好可读,遵循“见名知意”的原则。

库表设计规范

(1) 一条完整的建表语句中应包含必要的字段、主键、合理的索引(综合代码中所用到的条件语句创建合理的索引)
(2) 字段应当有注释,描述该字段的用途及可能存储的内容,如枚举值则建议将该字段中使用的内容都定义出来

索引设计规范

(1) 主键准则
a、表必须有主键
b、建议使用自增id作为主键
(2) 重要的SQL必须被索引,比如:
a、UPDATE、DELETE语句的WHERE条件列
b、ORDER BY、GROUP BY、DISTINCT的字段
(3) 一般原则
a、创建复合索引时区分度较大的字段放在最前面,不在区分度小的数列上建立索引,例如“性别”
b、核⼼SQL优先考虑覆盖索引
c、避免冗余和重复索引
d、索引要尽量选择离散度比较大的字段,选择查询频率比更新频率高的字段。
e、索引字段的默认值不能为NULL,要改为其他的default或者空。NULL非常影响索引的查询效率。
f、 尽量不使用外键
g、 新建的唯一索引必须不能和主键重复
h、不使用%前导的查询,如like“%xxx”,无法使用索引;不使用反向查询,如not in / not like;无法使用索引,导致全表扫描
i、 尽量用多列联合索引代替多个单列索引
j、 能使用唯一索引就要使用唯一索引,提高查询效率

字段设计规范

(1) 尽可能不使用TEXT、BLOB类型
(2) 用DECIMAL代替FLOAT和DOUBLE存储精确浮点数
(3) 越简单越好:使用TINYINT来代替ENUM类型
(4) 如果可能的话所有字段均定义为not null
(5) 禁止在数据库中存储明文密码,把密码加密后存储
(6) 不允许使用ENUM
(7)使用TIMESTAMP存储时间. 因为TIMESTAMP使用4字节,DATETIME使用8个字节,同时TIMESTAMP具有自动赋值以及自动更新的特性.

SQL设计规范

(1)强烈建议使用INNER JOIN代替子查询。
(2) 避免在数据库中进⾏数学运算(MySQL不擅长数学运算和逻辑判断)
(3) 不要用select *,查询哪几个字段就select 这几个字段
(4) 使用in代替or,in的值不超过1000个
(5) 任何对列的数学运算操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要
尽可能将操作移至等号右边。
(6) limit分页注意效率。Limit越大,效率越低。可以改写limit,比如例子改写:
  1. select id from t limit 10000, 10; => select id from t where id > 10000 limit 10;
(7) 在SQL语句中,禁止使用前缀是%的like
(8) 不使用负向查询,如 not in/like
(9)SQL尽量简单,尽量少的包含业务逻辑,业务逻辑交给应用处理。

行为规范

1、批量导入、导出数据必须提前通知DBA协助观察;
2、批量删除更新操作,如影响条数超过1000的update、delete操作,需要联系DBA进行审查,并让DBA执行
3、线上数据回滚,申请新DB,请联系DBA;
4、不在业务高峰期批量更新、查询数据库;
5、重大项目的数据库方案选型和设计必须提前通知DBA参与
32月/150

酷讯bugzilla使用规范分享

发布在 邵珠庆

HI ALL,
近期发现很多同事不太会用bugzilla,特分享一下使用规范,请大家按照规范进行bug的状态流转,如有不清楚的地方请及时和我联系。文档有不完善的地方也请大家及时指正一下逐步完善,多谢各位~~

 
 
1.new/confirmed
测试人员将Bug提交给任务分发人员(研发模块负责人),此时Bug状态为new/confirmed,开始Bug的生命周期,如果测试人员知道具体负责的研发人员,也可以直接指定,在Assign To项目中输入具体负责的研发人员Email.研发人员接收到Bug,经过分析,不属于自己负责的范围,如果知道谁应该负责,可以在Assigned To项目旁点击edit,直接输入被指定人的Email,将Bug转移给其他研发人员.

2.RESOLVED DUPLICATE

研发人员接收分配给自己的Bug后,在当前项目的Bug List中查看该Bug是否与之前的Bug重复,若重复,将新Bug置为RESOLVED DUPLICATE状态,并在Commnet中注明与哪个Bug重复。在Assigned To项目旁点击edit,直接测试人员的Email,将Bug转移给测试人员。测试人员确认重复后将bug置为VERIFIED DUPLICATE。如果没有重复则在Commnet标明原因,将bug置为bug置为UNCONFIRMED,在Assigned To项目旁点击edit,直接研发人员的Email,将Bug转移给研发人员。

3.RESOLVED INVALID

研发人员对于没有重复的Bug进行修复,经过分析,如果Bug是因为在错误的环境下产生或由于错误操作导致或由于测试人员错误理解而产生,属于无效Bug,将Bug置为RESOLVED INVALID状态,并在Commnet中注明置为无效的原因。Assigned To项目旁点击edit,直接输入产品人员的Email,将Bug转移给产品人员去确认,产品人员确认后在Commnet中注明是否真正是无效的bug如果是无效则在Assigned To项目旁点击edit,直接输入提交bug的测试人员的Email,将Bug转移给测试人员。如果需要修复则把bug置为UNCONFIRMED,在Assigned To项目旁点击edit,直接研发人员的Email,将Bug转移给研发人员。如果不需要修复,测试人员接收到bug后将bug置为VERIFIED INVALID。

4.RESOLVED WONTFIX

研发人员对于没有重复的有效Bug进行修复,经过分析,不在产品需求范围内,而且在可预见的未来内也不会提供该功能,将Bug置为WONTFIX状态,并在Commnet中注明置为WONTFIX的原因。Assigned To项目旁点击edit,直接输入产品人员的Email,将Bug转移给产品人员去确认,产品人员确认后在Commnet中注明是否真正是无效的bug。如果是无效则在Assigned To项目旁点击edit,直接输入提交bug的测试人员的Email,将Bug转移给测试人员。如果需要修复则把bug置为UNCONFIRMED,在Assigned To项目旁点击edit,直接研发人员的Email,将Bug转移给研发人员。如果不需要修复,测试人员将bug置为VERIFIED WONTFIX。

5.RESOLVED WORKSFORME

研发人员对于没有重复的有效Bug进行修复,按照Bug的步骤,多次验证,却无法重现该RESOLVED WORKSFORME,需要测试人员提供更多的信息,将bug置为RESOLVED WORKSFORME。在Assigned To项目旁点击edit,直接测试人员的Email,将Bug转移给测试人员。测试人员提供信息后把bug置为UNCONFIRMED,在Assigned To项目旁点击edit,直接研发人员的Email,将Bug转移给研发人员。

6.RESOLVED FIXED

研发人员对于没有重复的有效Bug进行修复,发现了产生Bug的原因,经过修改代码,能够消除该Bug,将Bug置为RESOLVED FIXED状态,并在Commnet中注明问题的原因、修复的方法,以便测试人员准确及时验证,将bug置为RESOLVED FIXED,在Assigned To项目旁点击edit,直接测试人员的Email,将Bug转移给测试人员

7.VERIFIED FIXE 

测试人员在处理RESOLVED FIXED的bug时,进行验证,如果发现该Bug已经不存在,将Bug置为VERIFIED FIXE状态,并在Commnet中注明验证通过的版本。如果没有验真通过需要修复则把bug置为UNCONFIRMED,在Assigned To项目旁点击edit,直接研发人员的Email,将Bug转移给研发人员

8.IN-PROGRESS
研发人员正在修复bug 可将bug状态置为IN-PROGRESS

125月/140

JavaScript的开发规范要求

发布在 邵珠庆

作为一名开发人员(We前端JavaScript开发),不规范的开发不仅使日后代码维护变的困难,同时也不利于团队的合作,通常还会带来代码安全以及执行效率上的问题。本人在开发工作中就曾与不按规范来开发的同事合作过,与他合作就不能用“愉快”来形容了。现在本人撰写此文的目的除了与大家分享一点点经验外,更多的是希望对未来的合作伙伴能够起到一定的借鉴作用。当然,如果我说的有不科学的地方还希望各路前辈多多指教。下面分条目列出各种规范要求,这些要求都是针对同事编码毛病提出来的,好些行业约定的其它规范可能不会再提及。

1、保证代码压缩后不出错

对于大型的JavaScript项目,一般会在产品发布时对项目包含的所有JavaScript文件进行压缩处理,比如可以利用Google Closure Compiler Service对代码进行压缩,新版jQuery已改用这一工具对代码进行压缩,这一般会去掉开发时写的注释,除去所有空格和换行,甚至可以把原来较长的变量名替换成短且无意义的变量名,这样做的目的是加快文件的下载速度,同时也减小网站访问带来的额外数据流量,另外在代码保护上也起到了一点点作用,至少压缩后的代码即使被还原还是没那么容易一下读懂的。要想代码能正确通过压缩,一般要求语句都要以分号正常结束,大括号也要严格结束等,具体还要看压缩工具的要求。所以如果一开始没有按标准来做,等压缩出错后再回去找错误那是浪费时间。

2、保证代码能通过特定IDE的自动格式化功能

一般较为完善的开发工具(比如Aptana Studio)都有代码“自动格式”化功能,这一功能帮助实现统一换行、缩进、空格等代码编排,你可以设置自己喜欢的格式标准,比如左大括号{是否另起一行。达到这个要求的目的在于方便你的开发团队成员拿你代码的一个副本用IDE自动格式化成他喜欢或熟悉的风格进行阅读。你同事需要阅读你的代码,可能是因为你写的是通用方法,他在其它模块开发过程中也要使用到,阅读你的代码能最深入了解方法调用和实现的细节,这是简单API文档不能达到的效果。

3、使用标准的文档注释

这一要求算是最基本的,这有利于在方法调用处看到方法的具体传参提示,也可以利用配套文档工具生成html或其它格式的开发文档供其他团队成员阅读,你可以尝试使用jsdoc-toolkit。如果你自动生成的API是出自一个开放平台,就像facebook.com应用,那么你的文档是给天下所有开发者看的。另外编写完整注释,也更方便团队成员阅读你的代码,通过你的参数描述,团队成员可以很容易知道你编写的方法传参与实现细节。当然也方便日后代码维护,这样即使再大的项目,过了很长时间后,回去改点东西也就不至于自己都忘记了当时自己写的代码是怎么一回事了。

4、使用规范有意义的变量名

使用规范有意义的变量名可以提高代码的可读性,作为大项目开发成员,自己写的代码不仅仅要让别人容易看懂。开发大项目,其实每个人写的代码量可能都比较大,规范命名,日后自己看回自己的代码也显的清晰易懂,比如日后系统升级或新增功能,修改起代码来也轻松多了。如果到头发现自己当初写的代码现在看不太懂了,那还真是天大的笑话了。

当然,使用有意义的变量名也尽量使用标准的命名,比如像这里:var me = this也许没有var self = this好,因为self是Python中的关键字,在Python中self就是通常语言this的用法。再看下面一个例子,加s显然比没有加来的科学些,这样可以知道这个变量名存的是复数,可能是数组等:

var li = document.getElementsByTagName('li')var lis = document.getElementsByTagName('li')

5、不使用生偏语法

JavaScript作为一门动态脚本语言,灵活性既是优点也是缺点,众所周知,动态语言不同层次开发人员对实现同样一个功能写出来的代码在规范或语法上会存在较大的差别。不管怎么样,规范编码少搞怪,不把简单问题复杂化,不违反代码易读性原则才是大家应该做的。

比如这语句:typeof(b) == 'string' && alert(b) 应该改为:if (typeof(b) == 'string') alert(b),像前面那种用法,利用了&&运算符解析机制:如果检测到&&前语句返回false就不再检测后面语句,在代码优化方面也有提到把最可能出现的情况首先判断,像这种写法如果条件少还好,如果条件较多而且语句也长,那代码可读性就相当差。

又比如:+function(a){var p = a;}( 'a') 应该改为:(function(a){var p = a;})( 'a'),其实function前面的+号与包含function的()括号作用是一样的,都是起运算优先作用,后者是常见且容易看明白的防止变量污染的做法,比如好些流行JavaScript框架就是采用后面这种方式。

再说个降低代码可读性的例子,如:function getPostionTxt(type){return type == 2 ? "野外" : (type == 3 ? "商城" : (type == 4 ? "副本" : null));} 应该改成:function getPostionTxt(type){var typeData={"2":"野外","3":"商城","4":"副本"};if (typeData[type]) return typeData[type]; else return null;}。如果type是从0开始不间断的整数,那么直接使用数组还更简单,这种结果看起来就清晰多了,看到前面那种多层三元表达式嵌套头不晕吗。

6、不在语句非赋值地方出现中文

语句中不应该出现中文我想一般人都知道,虽然这样做不影响程序运行,但是显然有背行业标准要求,当然我们也不是在使用“易语言”做开发。关于这一个问题,我本来不想把它拿出来说的,但我确实遇到有人这样做的,也不知道是不是因为他的英语实在太烂了,至少还可以用拼音吧,另外寻求翻译工具帮忙也不错的选择。我举例如下,像以下写法出现在教学中倒还可以理解:

this.user['名字'] = '张三' 或者 this.user.名字 = '张三'

7、明确定义函数固定数量的参数

固定数量参数的函数内部不使用arguments去获取参数,因为这样,你定义的方法如果包含较多的脚本,就不能一眼看到这个方法接受些什么参数以及参数的个数是多少。比如像下面:

var $ = function(){return document.getElementById(arguments[0]);} 应该改成:var $ = function(elemID){return document.getElementById(elemID);}

8、不必热衷动态事件绑定

虽然知道事件可以动态绑定,比如使用addEventListener或者使用jQuery的bind方法,也知道采用动态事件绑定可以让XHTML更干净,但是一般情况下我还是建议直接把事件写在DOM节点上,我认为这样可以使代码变得更容易维护,因为这样做,我们在查看源代码的时候就可以容易地知道什么Element绑定了什么方法,简单说这样更容易知道一个按钮或链接点击时调了什么方法脚本。

9、降低代码与XHTML的耦合性

不要过于依赖DOM的一些内容特征来调用不同的脚本代码,而应该定义不同功能的方法,然后在DOM上调用,这样不管DOM是按钮还是链接,方法的调用都是一样的,比如像下面的实现显然会存在问题:

function myBtnClick(obj)
{
 if (/确定/.test(obj.innerHTML))
  alert('OK');
 else if (/取消/.test(obj.innerHTML))
  alert('Cancel');
 else  alert('Other');
}<a herf="javascript:;">确定</a><a herf="javascript:;">取消</a>

上面例子其实在一个函数内处理了两件事情,应该分成两个函数,像上面的写法,如果把链接换成按钮,比如改成这样:<input type="button" value="确定" />,那么myBtnClick函数内部的obj.innerHTML就出问题了,因为此时应该obj.value才对,另外如果把按钮名称由中文改为英文也会出问题,所以这种做法问题太多了。

10、一个函数应该返回统一的数据类型

因为JavaScrip是弱类型的,在编写函数的时候有些人对于返回类型的处理显得比较随便,我觉得应该像强类型语言那样返回,看看下面的两个例子:

function getUserName(userID)
{
 if (data[userID])
  return data[userID];
 else  return false;
}
应该改为:function getUserName(userID)
{
 if (data[userID])
  return data[userID];
 else  return "";
}

这个方法如果在C#中定义,我们知道它准备返回的数据类型应该是字符串,所以如果没有找到这个数据我们就应该返回空的字符串,而不是返回布尔值或其它不合适的类型。这并没有影响到函数将来的调用,因为返回的空字符串在逻辑判断上可被认作“非”,即与false一样,除非我们使用全等于“===”或typeof进行判断。

11、规范定义JSON对象,补全双引号

使用标准肯定是有好处的,那么为什么还是有人不使用标准呢?我想这可能是懒或习惯问题。也许还会有人跟我说,少写引号可以减轻文件体积,我认为这有道理但不是重点。对于服务器返回的JSON数据,使用标准结构可以利用Firefox浏览器的JSONView插件方便查看(像查看XML那样树形显示),另外你如果使用jQuery做开发,最新版本jQuery1.4+是对JSON格式有更高要求的,具体的可以自己查阅jQuery更新文档。比如:{name:"Tom"}或{'name':'Tom'}都应该改成{"name":"Tom"}。

12、不在文件中留下未来确定不再使用的代码片段

当代码调整或重构后,之前编写的不再使用的代码应该及时删除,如果认为这些代码还有一定利用价值可以把它们剪切到临时文件中。留在项目中不仅增加了文件体积,这对团队其它成员甚至自己都起到一定干扰作用,怕将来自己看回代码都搞不懂这方法是干什么的,是否有使用过。当然可以用文档注释标签@deprecated把这个方法标识为不推荐的。

13、不重复定义其他团队成员已经实现的方法

对于大型项目,一般会有部分开发成员实现一些通用方法,而另外一些开发成员则要去熟悉这些通用方法,然后在自己编写模块遇到有调用的需要就直接调用,而不是像有些开发者喜欢“单干”,根本不会阅读这些通用方法文档,在自己代码中又写了一遍实现,这不仅产生多余的代码量,当然也是会影响团队开发效率的,这是没有团队合作精神的表现,是重复造轮子的悲剧。

比如在通用类文件Common.js有定义function $(elemID){return document.getElementById(elemID)}那么就不应该在Mail.js中再次出现这一功能函数的重复定义,对于一些复杂的方法更应该如此。

14、调用合适的方法

当有几个方法都可以实现同类功能的时候,我们还是要根据场景选择使用最合适的方法。下面拿jQuery框架的两个AJAX方法来说明。如果确定服务器返回的数据是JSON应该直接使用$.getJSON,而不是使用$.get得到数据再用eval函数转成JSON对象。如果因为本次请求要传输大量的数据而不得以使用$.post也应该采用指定返回数据类型(设置dataType参数)的做法。如果使用$.getJSON,在代码中我们一眼能看出本次请求服务器返回的是JSON。

温馨提示:jQuery1.4后,如果服务器有设置数据输出的ContentType,比如ASP.NET C#设置 Response.ContentType = "application/json",那么$.get将与$.getJSON的使用没有什么区别。

15、使用合适的控件存储合适的数据

曾发现有人利用DIV来保存JSON数据,以待页面下载后将来使用,像这样:<div >{ "name":"Tom"}</div>,显然这个DIV不是用来界面显示的,如果非要这样做,达到使用HTML文件进行数据缓存的作用,至少改成用隐藏域来存这数据更合理,比如改成:<input type="hidden" value=" { "name":"Tom"}" />。

其实也可以利用window对象来保存一些数据,像上面的例子,我们可以在AJAX请求页直接包含这样的脚本块:<script>window.userData = { "name":"Tom"};</script>,当在AJAX请求回调函数中执行完$( "#MyDiv ").html(data)后,在window上就马上有了这一变量。如果采用第一种方法,将不可避免eval(document.getElementById("UserData").innerHTML)。如果在window对象存放大量数据的话,这些数据不用时要及时手动清理它们,它们是要等浏览器刷新或重启后才会消失的,这就会增加内存开销。

16、永远不要忽略代码优化工作

代码最优化是每个程序员应该努力达到的目标,也应该成为程序员永远的追求。写代码的时候,不应该急着把功能实现出来,要想一下如何写代码,代码的执行效率才是较好的。

举个例子:假设有定义getElementById的快捷方法functoin $(elemID){return document.getElementById(elemID)},那么有人可能会写出这样的代码$("MyDiv").parentNode.removeChild($("MyDiv")),其实这里执行了两次getElementById DOM查找,如果改成这样将更好:var myDiv = $("MyDiv"); myDiv.parentNode.removeChild(myDiv)。还好getElementById的DOM查找算比较快,如果换成getElementsByTagName则更应该注重优化了。jQuery开发团队也有提醒大家要注意这方面的问题。

当然,代码优化技巧也是需要个人不断积累的。曾有朋友跟我说他写网站后台代码从来不用考虑优化的,因为他们网站用的是至强四核服务器,我觉得这是很可笑的。

17、会分析策划文档,能用面向对象方法进行接口定义和代码组织

这一能力对于每一个程序员来说都是非常重要的,这也是决定一个程序员水平高低的一个重要因素。能够把需求细化并抽象出不同的类,然后有条理地编写代码,使代码结构清晰,可读性高,代码易于维护,不至于太过程化而且杂乱无章,这样才算是一个优秀的程序员。