JAVASCRIPT2024-08-12

Clean-code JavasCript

变量

  • 使用有意义且可读的变量名

    const currentDate = moment().format('YYYY/MM/DD');
    
  • 为相同类型的变量使用相同的词汇

    getUser();
    
  • 使用可搜索的名称

    const MILLISECONDS_IN_A_DAY = 86400000;
    setTimeout(blastOff, MILLISECONDS_IN_A_DAY);
    
  • 使用解释性的变量

    const address = 'One Infinite Loop, Cupertino 95014';
    const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
    const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
    saveCityZipCode(city, zipCode);
    
  • 避免心理映射, 显示比隐式更好

    const locations = ['Austin', 'New York', 'San Francisco'];
    locations.forEach((location) => {
      doStuff();
      doSomeOtherStuff();
      // ...
      // ...
      // ...
      dispatch(location);
    });
    
  • 不要添加不必要的上下文

    const Car = {
    make: 'Honda',
    model: 'Accord',
    color: 'Blue'
    };
    
    function paintCar(car) {
    car.color = 'Red';
    }
    
  • 使用默认变量代替短路运算或条件

    function createMicrobrewery(name = 'Hipster Brew Co.') {
    // ...
    }
    

函数

  • 函数参数两个以下最理想

    const menuConfig = {
    title: null,
    body: 'Bar',
    buttonText: null,
    cancellable: true
    };
    
    function createMenu(config) {
        // ...
    }
    
  • 函数应当只做一件事

    function emailClients(clients) {
        clients.filter(isClientActive).forEach(email);
    }
    
    function isClientActive(client) {
        const clientRecord = database.lookup(client);
        return clientRecord.isActive();
    }
    
  • 函数名应该说明它要做什么

    function addMonthToDate(month, date) {
    // ...
    }
    
    const date = new Date();
    addMonthToDate(1, date);
    
  • 函数应该只有一个抽象级别

    function tokenize(code) {
        const REGEXES = [
            // ...
        ];
    
        const statements = code.split(' ');
        const tokens = [];
        REGEXES.forEach((REGEX) => {
            statements.forEach((statement) => {
                // ...
            });
        });
    
        return tokens;
    }
    
    function lexer(tokens) {
        const ast = [];
        tokens.forEach((token) => {
            // ...
        });
    
        return ast;
    }
    
    function parserBetterJSAlternative(code) {
        const tokens = tokenize(code);
        const ast = lexer(tokens);
        ast.forEach((node) => {
            // parse...
        });
    }
    
  • 移除冗余代码

    function showList(employees) {
        employees.forEach((employee) => {
            const expectedSalary = employee.calculateExpectedSalary();
            const experience = employee.getExperience();
    
            let portfolio = employee.getGithubLink();
    
            if (employee.type === 'manager') {
                portfolio = employee.getMBAProjects();
            }
    
            const data = {
                expectedSalary,
                experience,
                portfolio
            };
    
            render(data);
        });
    }
    
  • 使用 Object.assign 设置默认对象

    const menuConfig = {
        title: 'Order',
        buttonText: 'Send',
        cancellable: true
    }
    
    function createMenu(config) {
        config = Object.assign({
            title: 'Foo',
            body: 'Bar',
            buttonText: 'Baz',
            cancellable: true
        }, config);
    }
    
  • 不要使用标记位作为函数参数

    function createFile(name) {
        fs.create(name);
    }
    
    function createTempFile(name) {
        createFile(`./temp/${name}`);
    }
    
  • 避免副作用

    function splitIntoFirstAndLastName(name) {
        return name.split(' ');
    }
    
    const name = 'Kevin Durant';
    const newName = splitIntoFirstAndLastName(name);
    
    console.log(name); // 'Kevin Durant'
    console.log(newName); // ['Kevin', 'Durant']
    
  • 不要写全局函数

  • 函数式编程优于指令式编程

    const programmerOutput = [
        {
            name: 'Uncle Bobby',
            linesOfCode: 500
        }, {
            name: 'Suzie Q',
            linesOfCode: 1500
        }, {
            name: 'Jimmy Gosling',
            linesOfCode: 150
        }, {
            name: 'Gracie Hopper',
            linesOfCode: 1000
        }
    ];
    
    const totalOutput = programmerOutput.map((programmer) => programmer.linesOfCode).reduce((acc, linesOfCode) => acc + linesOfCode, 0);
    
  • 封装条件语句

    function shouldShowSpinner(fsm, listNode) {
        return fsm.state === 'fetching' && isEmpty(listNode);
    }
    
    if(shouldShowSpinner(fsmInstance, listNodeInstance)){
        // ...
    }
    
  • 避免负面条件

    function isDOMNodeNotPresent(node) {
        // ...
    }
    
    if (isDOMNodeNotPresent(node)) {
        // ...
    }
    
  • 避免条件语句

    class Airplane {
        // ...
    }
    
    class Boeing777 extends Airplane {
        // ...
        getCruisingAltitude() {
            return this.getMaxAltitude() - this.getPassengerCount();
        }
    }
    
    class AirForceOne extends Airplane {
        // ...
        getCruisingAltitude() {
            return this.getMaxAltitude();
        }
    }
    
    class Cessna extends Airplane {
        // ...
        getCruisingAltitude() {
            return this.getMaxAltitude() - this.getFuelExpenditure();
        }
    }
    
  • 避免类型检查 (part 1)

    function travelToTexas(vehicle) {
        vehicle.move(this.currentLocation, new Location('texas'));
    }
    
  • 避免类型检查 (part 2)

    function combine(val1, val2) {
        return val1 + val2;
    }
    
  • 不要过度优化

    for (let i = 0; i < list.length; i++){
        // ...
    }
    
  • 移除僵尸代码

对象和数据结构

  • 使用 getterssetters
  • 让对象具有私有属性

  • ES2015/ES6 类优先于 ES5 纯函数
  • 使用方法链
  • 组合优于继承

SOLID

  • 单一职责原则 (SRP): 一个类只做一件事.
  • 开放封闭原则 (OCP): 为扩展开放, 对修改关闭.
  • 里氏替换原则 (LSP): 如果有一个基类和子类, 那么基类和子类可以互换.
  • 接口隔离原则 (ISP): 使用多个专门的接口, 而不是一个总接口.
  • 依赖反转原则 (DIP): 高层模块不应该依赖底层模块, 二者都应该依赖抽象; 抽象不应该依赖具体实现, 具体实现应该依赖抽象.

并发

  • 使用 Promise 代替回调函数
  • Async/Await 优于 Promise

错误处理

  • 不要忽略捕捉到的错误
  • 不要忽略被拒绝的 promise

格式化

  • 使用一致的大小写
  • 函数的调用方与被调用方应该靠近

注释

  • 仅仅对包含复杂业务逻辑的东西进行注释
  • 不要在代码库中保存注释掉的代码
  • 不要有日志式的注释
  • 避免占位符