不要再让人吐槽你的代码了~

1,443 阅读6分钟

干净的代码不仅仅是可以工作的代码,而是可以被其他人轻松阅读、重用和重构的代码。编写干净的代码很重要,因为在日常工作中,我们的代码还要被其他人阅读、修改、重构~

本文的重点是编写干净的JavaScrip代码

1. 变量

使用语义化的名称

变量的名称应该是语义化的

// 不建议 ❌
const foo = "zhangsan@example.com";
const bar = "Zhang";
const age = 23;
const qux = true;

// 建议 ✅
const email = "zhangsan@example.com";
const firstName = "Zhang";
const age = 23;
const isActive = true

请注意,布尔名称通常会回答特定问题,例如:

isActive 
didSubscribe 
hasLinkedAccount

避免添加不必要的上下文

当上下文已经由包含的对象或类提供时,不要将多余的上下文添加到变量名中

// 不建议 ❌
const user = {
  userId: "296e2589-7b33-400a-b762-007b730c8e6d",
  userEmail: "zhangsan@example.com",
  userFirstName: "Zhang",
  userLastName: "San",
  userAge: 23,
};

user.userId;

// 建议 ✅
const user = {
  id: "296e2589-7b33-400a-b762-007b730c8e6d",
  email: "zhangsan@example.com",
  firstName: "Zhang",
  lastName: "San",
  age: 23,
};

user.id;

避免硬编码值

不要插入常量值,而是确保声明有意义且可搜索的常量

// 不建议 ❌
setTimeout(clearSessionData, 900000);

// 建议 ✅
const SESSION_DURATION_MS = 15 * 60 * 1000;

setTimeout(clearSessionData, SESSION_DURATION_MS);

2. 功能

使用描述性名称

函数名称可以很长,只要它们描述了函数的实际作用。函数名称通常具有动作动词的形式,返回布尔值的函数可能例外——它可以具有“是或否”问题的形式。函数名也应该是驼峰式。

// 不建议 ❌
function toggle() {
  // ...
}

function agreed(user) {
  // ...
}

// 建议 ✅
function toggleThemeSwitcher() {
  // ...
}

function didAgreeToAllTerms(user) {
  // ...
}

使用默认参数

默认参数比在函数体内使用额外的条件语句更干净。

// 不建议 ❌
function printAllFilesInDirectory(dir) {
  const directory = dir || "./";
  //   ...
}

// 建议 ✅
function printAllFilesInDirectory(dir = "./") {
  // ...
}

限制参数的数量

尽管这条规则可能存在争议,但函数应该有 0、1 或 2 个参数。拥有三个参数已经过分了,除此之外还意味着以下两种情况之一:

  • 功能已经做了很多,应该拆分。
  • 传递给函数的数据在某种程度上是相关的,可以作为专用数据结构传递。
// 不建议 ❌
function sendPushNotification(title, message, image, isSilent, delayMs) {
  // ...
}

sendPushNotification("新消息", "...", "http://...", false, 1000);

// 建议 ✅
function sendPushNotification({ title, message, image, isSilent, delayMs }) {
  // ...
}

const notificationConfig = {
  title: "新消息",
  message: "...",
  image: "http://...",
  isSilent: false,
  delayMs: 1000,
};

sendPushNotification(notificationConfig);

避免在一个函数中执行多个事务

一个函数一次应该做一件事。此规则有助于减少函数的大小和复杂性,从而更容易测试、调试和重构。函数中的行数是一个强有力的指标,它应该引发一个关于函数是否正在执行许多操作的标志。通常,尝试针对少于 20-30 行代码的内容。

// 不建议 ❌
function pingUsers(users) {
  users.forEach((user) => {
    const userRecord = database.lookup(user);
    if (!userRecord.isActive()) {
      ping(user);
    }
  });
}

// 建议 ✅
function pingInactiveUsers(users) {
  users.filter(!isUserActive).forEach(ping);
}

function isUserActive(user) {
  const userRecord = database.lookup(user);
  return userRecord.isActive();
}

避免使用标志作为参数

其中一个参数中的标志有效地意味着该函数仍然可以被简化。

// 不建议 ❌
function createFile(name, isPublic) {
  if (isPublic) {
    fs.create(`./public/${name}`);
  } else {
    fs.create(name);
  }
}

// 建议 ✅
function createFile(name) {
  fs.create(name);
}

function createPublicFile(name) {
  createFile(`./public/${name}`);
}

不要重复自己

重复的代码会使你的代码看起来很low。如果你重复代码过多,则每当逻辑发生变化时,您都必须更新多个位置。

// 不建议 ❌
function renderCarsList(cars) {
  cars.forEach((car) => {
    const price = car.getPrice();
    const make = car.getMake();
    const brand = car.getBrand();
    const nbOfDoors = car.getNbOfDoors();

    render({ price, make, brand, nbOfDoors });
  });
}

function renderMotorcyclesList(motorcycles) {
  motorcycles.forEach((motorcycle) => {
    const price = motorcycle.getPrice();
    const make = motorcycle.getMake();
    const brand = motorcycle.getBrand();
    const seatHeight = motorcycle.getSeatHeight();

    render({ price, make, brand, seatHeight });
  });
}

// 建议 ✅
function renderVehiclesList(vehicles) {
  vehicles.forEach((vehicle) => {
    const price = vehicle.getPrice();
    const make = vehicle.getMake();
    const brand = vehicle.getBrand();

    const data = { price, make, brand };

    switch (vehicle.type) {
      case "car":
        data.nbOfDoors = vehicle.getNbOfDoors();
        break;
      case "motorcycle":
        data.seatHeight = vehicle.getSeatHeight();
        break;
    }

    render(data);
  });
}

避免副作用

// 不建议 ❌
let date = "21-8-2021";

function splitIntoDayMonthYear() {
  date = date.split("-");
}

splitIntoDayMonthYear();

// 改变了date的原始值
console.log(date); // ['21', '8', '2021'];


// 建议 ✅
function splitIntoDayMonthYear(date) {
  return date.split("-");
}

const date = "21-8-2021";
const newDate = splitIntoDayMonthYear(date);

// 原始值未改变
console.log(date); // '21-8-2021';
console.log(newDate); // ['21', '8', '2021'];

3. 条件

使用非负条件

// 不建议 ❌
function isUserNotVerified(user) {
  // ...
}

if (!isUserNotVerified(user)) {
  // ...
}

// 建议 ✅
function isUserVerified(user) {
  // ...
}

if (isUserVerified(user)) {
  // ...
}

尽可能简写

// 不建议 ❌
if (isActive === true) {
  // ...
}

if (firstName !== "" && firstName !== null && firstName !== undefined) {
  // ...
}

const isUserEligible = user.isVerified() && user.didSubscribe() ? true : false;

// 建议 ✅
if (isActive) {
  // ...
}

if (!!firstName) {
  // ...
}

const isUserEligible = user.isVerified() && user.didSubscribe();

避免嵌套分支并尽快返回

提早返回将使您的代码线性化、更具可读性。

// 不建议 ❌
function addUserService(db, user) {
  if (!db) {
    if (!db.isConnected()) {
      if (!user) {
        return db.insert("users", user);
      } else {
        throw new Error("插入失败");
      }
    } else {
      throw new Error("数据库未连接");
    }
  } else {
    throw new Error("未发现数据库");
  }
}

// 建议 ✅
function addUserService(db, user) {
  if (!db) throw new Error("未发现数据库");
  if (!db.isConnected()) throw new Error("数据库未连接");
  if (!user) throw new Error("插入失败");

  return db.insert("users", user);
}

优先使用对象字面量或映射而不是 switch 语句

使用对象或映射进行索引将减少代码并提高性能。

// 不建议 ❌
const getColorByStatus = (status) => {
  switch (status) {
    case "success":
      return "green";
    case "failure":
      return "red";
    case "warning":
      return "yellow";
    case "loading":
    default:
      return "blue";
  }
};

// 建议 ✅
const statusColors = {
  success: "green",
  failure: "red",
  warning: "yellow",
  loading: "blue",
};

const getColorByStatus = (status) => statusColors[status] || "blue";

使用可选链接

const user = {
  email: "JDoe@example.com",
  billing: {
    iban: "...",
    swift: "...",
    address: {
      street: "上海市徐汇区",
      state: "SH",
    },
  },
};

// 不建议 ❌
const email = (user && user.email) || "N/A";
const street =
  (user &&
    user.billing &&
    user.billing.address &&
    user.billing.address.street) ||
  "N/A";
const state =
  (user &&
    user.billing &&
    user.billing.address &&
    user.billing.address.state) ||
  "N/A";
 
// 建议 ✅
const email = user?.email || "N/A";
const street = user?.billing?.address?.street || "N/A";
const state = user?.billing?.address?.state || "N/A";

4. 并发

避免回调

// 不建议 ❌
getUser(function (err, user) {
  getProfile(user, function (err, profile) {
    getAccount(profile, function (err, account) {
      getReports(account, function (err, reports) {
        sendStatistics(reports, function (err) {
          console.error(err);
        });
      });
    });
  });
});

// 建议 ✅
getUser()
  .then(getProfile)
  .then(getAccount)
  .then(getReports)
  .then(sendStatistics)
  .catch((err) => console.error(err));

// 或者使用 Async/Await ✅✅

async function sendUserStatistics() {
  try {
    const user = await getUser();
    const profile = await getProfile(user);
    const account = await getAccount(profile);
    const reports = await getReports(account);
    return sendStatistics(reports);
  } catch (e) {
    console.error(err);
  }
}

5. 错误处理

处理抛出的错误和reject Promise

现在花时间正确处理错误将减少以后不得不寻找错误的可能性,尤其是线上环境。

// 不建议 ❌
try {
  // ...
} catch (e) {
  console.log(e);
}

// 建议 ✅
try {
  // ...
} catch (e) {
  console.error(e);
  
  // 发送到服务器
  reportErrorToServer(e);
}

6. 注释

只注释业务逻辑

可读的代码使您免于过度注释您的代码。因此,您应该只注释复杂的逻辑。

// 不建议 ❌
function generateHash(str) {
  // Hash变量
  let hash = 0;

  // 获取字符串长度
  let length = str.length;

  // 如果字符串为空,直接返回hash
  if (!length) {
    return hash;
  }

  // 循环遍历
  for (let i = 0; i < length; i++) {
    // 获取 character code.
    const char = str.charCodeAt(i);

    // 生成 hash
    hash = (hash << 5) - hash + char;

    // 转换为32位整数
    hash = hash & hash;
  }
}

// 建议 ✅
function generateHash(str) {
  let hash = 0;
  let length = str.length;
  if (!length) {
    return hash;
  }

  for (let i = 0; i < length; i++) {
    const char = str.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash = hash & hash; // 转换为32位整数
  }
  return hash;
}

使用版本控制

没必要保留注释代码或日志注释,版本控制已经可以处理这个问题。

// 不建议 ❌
/**
 * 2022-7-21: 删除
 * 2022-7-15: 更新
 * 2022-7-10: 新增
 */
function generateCanonicalLink(user) {
  // const session = getUserSession(user)
  const session = user.getSession();
  // ...
}

// 建议 ✅
function generateCanonicalLink(user) {
  const session = user.getSession();
  // ...
}

本文简要讨论了一些编码风格,采用这些做法可能需要一些时间,尤其是对于较大的代码库,但从长远来看,您的代码将变得可读、可扩展且易于重构。