随笔之代码整洁之道

254 阅读4分钟

前言

最近看了一本《代码整洁之道》,结合我后台管理的业务情况,做一些笔记

道-方法论

整洁之道也就是整洁的方法论,为了便于记忆,笔者将其尽可能简化并阐述出来

一、变量名篇
  1. 一个语义化的变量名可以直接起到注释的作用
// ❌
let n

// ✅
let userName
  1. 变量名要有区分性
// ❌
let name0, nameO // 数字 0 与字母 O
let userLongLongLongName, userLongLongLongLongName // 很长的名字又不好区分
let Variable1 // 变量1 完全是无用信息,与 a b c 无甚区别而且更长
let user, userInfo  // 无法区分他俩的差别
  1. 在有上下文的情况下,去除无用前缀
// ❌
let user = {
    userName: 'xiaoming',
    userAge: 11
}

// ✅
let user = {
    name: 'xiaoming',
    age: 11
}
  1. 不要出现意义不明的数字
// ❌
setTimeout(doSomething, 86400000) // 86400000 这是什么

// ✅
const DAY_SECONDS = 60 * 60 * 24 * 1000
setTimeout(doSomething, DAY_SECONDS)
  1. 操作类的函数应当以动词为前缀
// ❌
const name = () => {
    //
}

// ✅
const getName = () => {
    //
}
  1. 给每个抽象概念选一个词,并且一以贯之

在后台管理中,往往会出现列表页、表单页、详情页,出现 Info、Item、Detail 等相近命名是无可避免的,建议自己形成一套固有习惯。

  • 在列表页可以考虑使用 Item 作为单条数据,并将 List 作为列表

  • 在表单页可以考虑使用 Detail 作为单条数据

  • 在详情页可以考虑使用 Info 作为单条数据,不过最好是基于 Detail 做 pick / omit ,这两个非常接近,不需要两套,大部分时候可以直接复用 Detail

另外还有一些常见的也最好统一

请求:fetch/get/request => fetch

新增:add/insert/append => add

二、函数篇
  1. 函数的第一规则是短小,每个函数不应超过 20 行
// ❌ 即使命名够漂亮、好读,也是很难一口气看下来
function testableHtml(pageData: PageData, includeSuiteSetup: boolean): string {
  const wikiPage = pageData.getWikiPage();
  let buffer = "";

  if (pageData.hasAttribute("Test")) {
    if (includeSuiteSetup) {
      const suiteSetup = PageCrawlerImpl.getInheritedPage(
        SuiteResponder.SUITE_SETUP_NAME,
        wikiPage
      );
      if (suiteSetup) {
        const pagePath = suiteSetup.getPageCrawler().getFullPath(suiteSetup);
        const pagePathName = PathParser.render(pagePath);
        buffer += `!include -setup .${pagePathName}\n`;
      }
    }

    const setup = PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
    if (setup) {
      const setupPath = wikiPage.getPageCrawler().getFullPath(setup);
      const setupPathName = PathParser.render(setupPath);
      buffer += `!include -setup .${setupPathName}\n`;
    }
  }

  buffer += pageData.getContent();

  if (pageData.hasAttribute("Test")) {
    const teardown = PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
    if (teardown) {
      const tearDownPath = wikiPage.getPageCrawler().getFullPath(teardown);
      const tearDownPathName = PathParser.render(tearDownPath);
      buffer += `\n!include -teardown .${tearDownPathName}\n`;
    }

    if (includeSuiteSetup) {
      const suiteTeardown = PageCrawlerImpl.getInheritedPage(
        SuiteResponder.SUITE_TEARDOWN_NAME,
        wikiPage
      );
      if (suiteTeardown) {
        const pagePath = suiteTeardown.getPageCrawler().getFullPath(suiteTeardown);
        const pagePathName = PathParser.render(pagePath);
        buffer += `!include -teardown .${pagePathName}\n`;
      }
    }
  }

  pageData.setContent(buffer);
  return pageData.getHtml();
}


// ✅ 该抽出新函数就抽,控制在 20 行 
function renderPageWithSetupsAndTeardowns(pageData: PageData, isSuite: boolean): string {
  if (pageData.hasAttribute("Test")) {
    const testPage = pageData.getWikiPage();
    const newPageContent: string[] = [];
    includeSetupPages(testPage, newPageContent, isSuite);
    newPageContent.push(pageData.getContent());
    includeTeardownPages(testPage, newPageContent, isSuite);
    pageData.setContent(newPageContent.join(""));
  }

  return pageData.getHtml();
}
  1. 一个函数只应该做一件事(单一职责原则
// ❌ 实际没这么短,各自的逻辑会很长
function processOrder(order: Order) {
  if (!order.isPaid) throw new Error("Order not paid");
  
  // 发货逻辑
  console.log(`Shipping order ${order.id} to ${order.address}`);
  
  // 通知客户
  console.log(`Notifying customer ${order.customerEmail}`);
  
  // 记录日志
  console.log(`Order ${order.id} processed at ${new Date().toISOString()}`);
}

// ✅
function validateOrder(order: Order) {
  if (!order.isPaid) throw new Error("Order not paid");
}

function shipOrder(order: Order) {
  console.log(`Shipping order ${order.id} to ${order.address}`);
}

function notifyCustomer(order: Order) {
  console.log(`Notifying customer ${order.customerEmail}`);
}

function logOrderProcessing(order: Order) {
  console.log(`Order ${order.id} processed at ${new Date().toISOString()}`);
}

function processOrder(order: Order) {
  validateOrder(order);
  shipOrder(order);
  notifyCustomer(order);
  logOrderProcessing(order);
}
  1. 避免副作用,尽量写纯函数
// ❌ 直接操作原对象
const updateUserAge = (user) => {
    user.age++;
}
updateUserAge(user);
console.log(user);

// ✅ 操作新对象而非原先的对象
const updateUserAge = (user) => {
    const newUser = deepClone(user);
    newUser.age++;
    return newUser;
}
const newUser = updateUserAge(user);
console.log(newUser);
  1. 每个函数一个抽象层级(简单理解:调用自己封装的方法是高级抽象,调用底层 api 是低层实现细节)
// ❌
function renderPageWithSetupsAndTeardowns(pageData: PageData, isSuite: boolean): string {
  if (pageData.hasAttribute("Test")) {
    const testPage = pageData.getWikiPage();
    const newPageContent: string[] = [];
    
    // 这是较高层级的抽象
    includeSetupPages(testPage, newPageContent, isSuite);
    
    // 这是较低层级的实现细节
    newPageContent.push(pageData.getContent());
    
    // 这是较高层级的抽象
    includeTeardownPages(testPage, newPageContent, isSuite);
    
    pageData.setContent(newPageContent.join(""));
  }

  return pageData.getHtml();
}

// ✅
function isTestPage(pageData: PageData): boolean {
  return pageData.hasAttribute("Test");
}
function buildFullTestPage(pageData: PageData, isSuite: boolean): string {
  const content: string[] = [];
  includeSetupPages(pageData.getWikiPage(), content, isSuite);
  content.push(pageData.getContent());
  includeTeardownPages(pageData.getWikiPage(), content, isSuite);
  return content.join("");
}

function renderPageWithSetupsAndTeardowns(pageData: PageData, isSuite: boolean): string {
  if (isTestPage(pageData)) {
    const content = buildFullTestPage(pageData, isSuite);
    pageData.setContent(content);
  }
  return pageData.getHtml();
}
  1. 最理想的参数数量是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。有足够特殊的理由才能用三个以上参数,直接用对象更好
  2. 不要向参数中传入标识符(boolean)
// ❌ 
function renderPage(pageData: PageData, isSuite: boolean): string {
  if (isSuite) {
    return renderSuitePage(pageData);
  } else {
    return renderTestPage(pageData);
  }
}

// 调用时
renderPage(pageData, true);  // 容易混淆,需要去看 renderPage 才能知道 true/false 是什么
renderPage(pageData, false); 


// ✅ 直接抽到外层,更好懂
isSuite ? renderSuitePage(pageData) : renderTestPage(pageData)
三、注释篇

Comments are always failures,注释是一种失败

  1. 尽可能用代码本身与良好的命名来解释你的逻辑,而非注释
// ❌ 
// 当启用时,18岁以上在白名单中的用户才执行
if (flag && user.age > 18 && whiteList.includes(user.id)) {
    console.log('something')
}

// ✅
const isAdult = user.age > 18;
const isWhitelisted = whiteList.includes(user.id);
const isFeatureEnabled = flag;

const isTargetUser = isFeatureEnabled && isAdult && isWhitelisted

if (isTargetUser) {
    console.log('something');
}
  1. 移除大段代码注释
// ❌ 
InputStreamResponse response = new InputStreamResponse();
response.setBody(formatter.getResultStream(), formatter.getByteCount());
// InputStream resultsStream = formatter.getResultStream();
// StreamReader reader = new StreamReader(resultsStream);
// response.setContent(reader.read(formatter.getByteCount()));

// ✅
InputStreamResponse response = new InputStreamResponse();
response.setBody(formatter.getResultStream(), formatter.getByteCount());

需要的话,直接用 git 的时间线找回代码