Part 1 - Style Guidelines(15min)
Why we need?
Basic Formatting
基本的代码风格
清晰的代码文件结构
Tool Use
eslint
commintlint + lint-staged + husky
Part 2 - Programming Practices(30min)
Why we need?
Code Bad Smell
1. Mysterious Name 神秘命名
不精准的命名:命名过于宽泛,不能精准描述,比如 data,info,flag 等```typescript const data = [] const info = []
// 什么的 data?什么的 info?
用技术术语命名```typescript// Bad:const booksMap = new Map()// 万一类型之后需要从 Map 修改成 Set,那对应的命名是否就有所误解了// Good:const books = new Map()
英语使用不当:(可以使用相关辅助工作,比如 vscode 的插件):::info 小结:
- 描述意图,而非细节
- 好的命名,是体现业务含义的命名
- 编写符合英语语法规则的代码
:::
2. Duplicated Code 重复代码
结构重复if 和 else 代码块中语句高度相似```typescript // Bad: if (user.isEditor()) { service.editChapter(chapterId, title, content, true) } else { service.editChapter(chapterId, title, content, false) }
// Good: boolean approved = user.isEditor() service.editChapter(chapterId, title, content, approved)
:::info小结:- Don't Repeat Yourself,不要重复自己,不要复制粘贴:::<a name="zVD01"></a>#### 3. Long Function 过长函数平铺直叙```typescript// 把多个业务处理流程放在一个函数里实现// 把不同层面的细节放到一个函数里实现
一次加一点:::info 小结:
- 定义好函数长度的标准
- 分离关注点
- 提取函数
:::
4. Long Parameter List 过长参数列表
聚沙成塔```typescript // Bad: function createBook( title, introduction, coverUrl, type, channel, protagonists, tags, completed ) { // …. }
// 如果某一个可以不传,或者需要增加一个参数呢?
// Good: function createBook(params) { // … }
动静分离:::info小结:- 变化频率相同,则封装成一个类- 变化频率不同- 静态不变的,无需参数传递- 多个变化频率的,可以封装成多个类:::<a name="mww5Z"></a>#### 5. Abuse Control Statement 滥用控制语句嵌套的代码```typescript// Bad:function subscribeBook(bookId) {const bookInfo = this.getInfoByBookId(bookId)if (bookInfo.status === VALID) {if (bookInfo.isDeleted === 0) {// ...}}}// Good:function subscribeBook(bookId) {const bookInfo = this.getInfoByBookId(bookId)if (bookInfo.status !== VALID) {return}if (bookInfo.isDeleted !== 0) {return}// ....}
单一条件判断返回```typescript // Bad: function getOwner(projectName) { if (projectName === ‘A’) { return ‘a’ } else if (projectName === ‘B’) { return ‘b’ } else if (projectName === ‘C’) { return ‘c’ } return ‘d’ }
// Good: function getOwner(projectName) { const ownerMapping = { A: a, B: b, C: c } return ownerMapping[projectName] || ‘d’ }
:::info小结:- 尽量不要使用 else 关键字- 以卫语句取代嵌套的条件表达式,让出错的代码先返回,剩下的就是正确的代码了- 多个条件判断返回,考虑是否可以使用配置来解决,扩展性更高:::<a name="Bce14"></a>#### 6. Mutable Data 可变数据```typescript// Bad:function merge(target, source) {for (const key in source) {target[key] = source[key];}return target;}// 无形中修改了 target 的数据,会引起不必要的麻烦// Good:function merge(target, source) {return {...target,...source}}
:::info 小结:
- 数据是建立在不改变的基础上的,如果需要更新,就产生一份新的数据副本,而旧有的数据保持不变
- 不需要变化的数据,可设置为不变类
:::
7. Loops 循环语句
const names = []for (const i of input) {if (i.job === 'programmer') {name.push(i.name)}}
:::info 小结:以管道取代循环,可以帮助我们更快地看清被处理元素以及处理他们的动作 :::const names = input.filter(i => i.job === 'programmer').map(i => i.name)
8. Global Data 全局数据
全局数据问题在于,在代码任何一个角落都可以直接修改,而且没有任何机制可以探测出到底哪段代码做出了修改,因此会出现诡异的 bug,增加定位难度 ```typescript // global.js // … let userAuthInfo = { platform: ‘pc’, token: ‘’ }
export { userAuthInfo };
// main.js userAuthInfo.token = localStorage.token;
// request.js const reply = await login(); userAuthInfo.token = reply.data.token;
// business.js await request({ authInfo: userAuthInfo });
```typescriptlet userAuthInfo = {platform: 'pc',token: ''};function getUserAuthInfo() {return { ...userAuthInfo };}function setToken(token) {userAuthInfo.token = token;}export {getUserAuthInfo,setToken}// main.jssetToken(localStorage.token);// request.jsconst reply = await login();setToken(reply.data.token);// business.jsawait request({ authInfo: getUserAuthInfo() });
:::info 小结:
- 封装变量
- 需要全局数据用一个函数包装起来,至少你就能看见修改它的地方,并开始控制对它的访问
:::
9. Lazy Element 冗赘的元素
```typescript function reportLines(aCustomer) { const lines = []; gatherCustomerData(lines, aCustomer); return lines; }
function gatherCustomerData(out, aCustomer) { out.push([“name”, aCustomer.name]); out.push([“location”, aCustomer.location]); }
```typescriptfunction reportLines(aCustomer) {const lines = [];lines.push(["name", aCustomer.name]);lines.push(["location", aCustomer.location]);return lines;}function reportLines(aCustomer) {return [['name', aCustomer.name],['location', aCustomer.location]];}
@Injectable()export class RiskModelEtcdService extends BaseEtcdService {private static get LiveEtcdConfig(): IOptions | undefined {return RiskModelEtcdService.IsModelEtcdValid? { hosts: nodeAppConfig.modelEtcdHosts }: undefined}private static get NonLiveEtcdConfig(): IOptions | undefined {return { hosts: nodeAppConfig.etcdHosts, auth: nodeAppConfig.etcdAuth }}private static get EtcdConfig() {return isLive ? RiskModelEtcdService.LiveEtcdConfig : RiskModelEtcdService.NonLiveEtcdConfig}private static get IsModelEtcdValid() {return Array.isArray(nodeAppConfig.modelEtcdHosts) && nodeAppConfig.modelEtcdHosts.length > 0}public constructor() {super(RiskModelEtcdService.EtcdConfig)}}
:::info 小结:
function Main() { return <> { true ? : } </> } ``` :::info 小结:一旦代码不再使用,我们就该立马删除它,即使以后需要,也可以从版本控制再次翻找出来 :::
11. Comments 过多的注释
:::info 小结:
