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?

  1. 用技术术语命名```typescript
  2. // Bad:
  3. const booksMap = new Map()
  4. // 万一类型之后需要从 Map 修改成 Set,那对应的命名是否就有所误解了
  5. // Good:
  6. 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)

  1. :::info
  2. 小结:
  3. - Don't Repeat Yourself,不要重复自己,不要复制粘贴
  4. :::
  5. <a name="zVD01"></a>
  6. #### 3. Long Function 过长函数
  7. 平铺直叙```typescript
  8. // 把多个业务处理流程放在一个函数里实现
  9. // 把不同层面的细节放到一个函数里实现

一次加一点:::info 小结:

  • 定义好函数长度的标准
  • 分离关注点
  • 提取函数 :::

    4. Long Parameter List 过长参数列表

    聚沙成塔```typescript // Bad: function createBook( title, introduction, coverUrl, type, channel, protagonists, tags, completed ) { // …. }

// 如果某一个可以不传,或者需要增加一个参数呢?

// Good: function createBook(params) { // … }

  1. 动静分离:::info
  2. 小结:
  3. - 变化频率相同,则封装成一个类
  4. - 变化频率不同
  5. - 静态不变的,无需参数传递
  6. - 多个变化频率的,可以封装成多个类
  7. :::
  8. <a name="mww5Z"></a>
  9. #### 5. Abuse Control Statement 滥用控制语句
  10. 嵌套的代码```typescript
  11. // Bad:
  12. function subscribeBook(bookId) {
  13. const bookInfo = this.getInfoByBookId(bookId)
  14. if (bookInfo.status === VALID) {
  15. if (bookInfo.isDeleted === 0) {
  16. // ...
  17. }
  18. }
  19. }
  20. // Good:
  21. function subscribeBook(bookId) {
  22. const bookInfo = this.getInfoByBookId(bookId)
  23. if (bookInfo.status !== VALID) {
  24. return
  25. }
  26. if (bookInfo.isDeleted !== 0) {
  27. return
  28. }
  29. // ....
  30. }

单一条件判断返回```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’ }

  1. :::info
  2. 小结:
  3. - 尽量不要使用 else 关键字
  4. - 以卫语句取代嵌套的条件表达式,让出错的代码先返回,剩下的就是正确的代码了
  5. - 多个条件判断返回,考虑是否可以使用配置来解决,扩展性更高
  6. :::
  7. <a name="Bce14"></a>
  8. #### 6. Mutable Data 可变数据
  9. ```typescript
  10. // Bad:
  11. function merge(target, source) {
  12. for (const key in source) {
  13. target[key] = source[key];
  14. }
  15. return target;
  16. }
  17. // 无形中修改了 target 的数据,会引起不必要的麻烦
  18. // Good:
  19. function merge(target, source) {
  20. return {
  21. ...target,
  22. ...source
  23. }
  24. }

:::info 小结:

  • 数据是建立在不改变的基础上的,如果需要更新,就产生一份新的数据副本,而旧有的数据保持不变
  • 不需要变化的数据,可设置为不变类 :::

    7. Loops 循环语句

    1. const names = []
    2. for (const i of input) {
    3. if (i.job === 'programmer') {
    4. name.push(i.name)
    5. }
    6. }
    1. const names = input
    2. .filter(i => i.job === 'programmer')
    3. .map(i => i.name)
    :::info 小结:以管道取代循环,可以帮助我们更快地看清被处理元素以及处理他们的动作 :::

    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 });

  1. ```typescript
  2. let userAuthInfo = {
  3. platform: 'pc',
  4. token: ''
  5. };
  6. function getUserAuthInfo() {
  7. return { ...userAuthInfo };
  8. }
  9. function setToken(token) {
  10. userAuthInfo.token = token;
  11. }
  12. export {
  13. getUserAuthInfo,
  14. setToken
  15. }
  16. // main.js
  17. setToken(localStorage.token);
  18. // request.js
  19. const reply = await login();
  20. setToken(reply.data.token);
  21. // business.js
  22. await request({ authInfo: getUserAuthInfo() });

:::info 小结:

  1. 封装变量
  2. 需要全局数据用一个函数包装起来,至少你就能看见修改它的地方,并开始控制对它的访问 :::

    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]); }

  1. ```typescript
  2. function reportLines(aCustomer) {
  3. const lines = [];
  4. lines.push(["name", aCustomer.name]);
  5. lines.push(["location", aCustomer.location]);
  6. return lines;
  7. }
  8. function reportLines(aCustomer) {
  9. return [
  10. ['name', aCustomer.name],
  11. ['location', aCustomer.location]
  12. ];
  13. }
  1. @Injectable()
  2. export class RiskModelEtcdService extends BaseEtcdService {
  3. private static get LiveEtcdConfig(): IOptions | undefined {
  4. return RiskModelEtcdService.IsModelEtcdValid
  5. ? { hosts: nodeAppConfig.modelEtcdHosts }
  6. : undefined
  7. }
  8. private static get NonLiveEtcdConfig(): IOptions | undefined {
  9. return { hosts: nodeAppConfig.etcdHosts, auth: nodeAppConfig.etcdAuth }
  10. }
  11. private static get EtcdConfig() {
  12. return isLive ? RiskModelEtcdService.LiveEtcdConfig : RiskModelEtcdService.NonLiveEtcdConfig
  13. }
  14. private static get IsModelEtcdValid() {
  15. return Array.isArray(nodeAppConfig.modelEtcdHosts) && nodeAppConfig.modelEtcdHosts.length > 0
  16. }
  17. public constructor() {
  18. super(RiskModelEtcdService.EtcdConfig)
  19. }
  20. }

:::info 小结:

  1. 有时真的不需要这层额外的结构
  2. 间接性可能带来帮助,但非必要的间接性总让人不舒服 :::

    10. Dead Code 死代码

    ```tsx if (false) { doSomething() }

function Main() { return <> { true ? : } </> } ``` :::info 小结:一旦代码不再使用,我们就该立马删除它,即使以后需要,也可以从版本控制再次翻找出来 :::

11. Comments 过多的注释

:::info 小结:

  • 尽量使用代码解释自己
  • 当需要使用过多注释,意味着这段代码需要重构 :::

    Practices