前言
最近看了一本《代码整洁之道》,结合我后台管理的业务情况,做一些笔记
道-方法论
整洁之道也就是整洁的方法论,为了便于记忆,笔者将其尽可能简化并阐述出来
一、变量名篇
- 一个语义化的变量名可以直接起到注释的作用
// ❌
let n
// ✅
let userName
- 变量名要有区分性
// ❌
let name0, nameO // 数字 0 与字母 O
let userLongLongLongName, userLongLongLongLongName // 很长的名字又不好区分
let Variable1 // 变量1 完全是无用信息,与 a b c 无甚区别而且更长
let user, userInfo // 无法区分他俩的差别
- 在有上下文的情况下,去除无用前缀
// ❌
let user = {
userName: 'xiaoming',
userAge: 11
}
// ✅
let user = {
name: 'xiaoming',
age: 11
}
- 不要出现意义不明的数字
// ❌
setTimeout(doSomething, 86400000) // 86400000 这是什么
// ✅
const DAY_SECONDS = 60 * 60 * 24 * 1000
setTimeout(doSomething, DAY_SECONDS)
- 操作类的函数应当以动词为前缀
// ❌
const name = () => {
//
}
// ✅
const getName = () => {
//
}
- 给每个抽象概念选一个词,并且一以贯之
在后台管理中,往往会出现列表页、表单页、详情页,出现 Info、Item、Detail 等相近命名是无可避免的,建议自己形成一套固有习惯。
在列表页可以考虑使用 Item 作为单条数据,并将 List 作为列表
在表单页可以考虑使用 Detail 作为单条数据
在详情页可以考虑使用 Info 作为单条数据,不过最好是基于 Detail 做 pick / omit ,这两个非常接近,不需要两套,大部分时候可以直接复用 Detail
另外还有一些常见的也最好统一
请求:fetch/get/request => fetch
新增:add/insert/append => add
二、函数篇
- 函数的第一规则是短小,每个函数不应超过 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();
}
- 一个函数只应该做一件事(单一职责原则)
// ❌ 实际没这么短,各自的逻辑会很长
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);
}
- 避免副作用,尽量写纯函数
// ❌ 直接操作原对象
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);
- 每个函数一个抽象层级(简单理解:调用自己封装的方法是高级抽象,调用底层 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();
}
- 最理想的参数数量是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。有足够特殊的理由才能用三个以上参数,直接用对象更好
- 不要向参数中传入标识符(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,注释是一种失败
- 尽可能用代码本身与良好的命名来解释你的逻辑,而非注释
// ❌
// 当启用时,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');
}
- 移除大段代码注释
// ❌
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 的时间线找回代码