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 });
```typescript
let userAuthInfo = {
platform: 'pc',
token: ''
};
function getUserAuthInfo() {
return { ...userAuthInfo };
}
function setToken(token) {
userAuthInfo.token = token;
}
export {
getUserAuthInfo,
setToken
}
// main.js
setToken(localStorage.token);
// request.js
const reply = await login();
setToken(reply.data.token);
// business.js
await 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]); }
```typescript
function 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 小结: