随机 try-catch(有的地方 'try-catch',有的地方直接崩)
随机 try-catch 的艺术
有些开发者喜欢在代码里随机放置 try-catch,就像在玩扫雷游戏。有时候它能捕获到错误,有时候直接让应用崩溃,这种不确定性给团队带来了无限惊喜。想象一下,当你正在调试一个复杂的功能,突然发现某个关键路径没有错误处理,而另一个无关紧要的地方却被 try-catch 包裹得严严实实,这种体验简直妙不可言。
选择性捕获的哲学
真正的防御性编程大师懂得,不是所有错误都值得捕获。比如:
function calculatePrice(quantity, price) {
// 这里可能发生 NaN,但我们选择无视
return quantity * price;
}
function fetchUserData() {
try {
// 这个 API 调用可能 404,但我们只关心 JSON 解析
const response = await fetch('/api/user');
return response.json();
} catch (e) {
console.log('JSON 解析失败');
}
}
注意第二个函数精妙地忽略了网络请求可能失败的事实,只捕获了 JSON 解析错误。这种选择性失明能让代码在大部分时间"看起来"正常工作。
嵌套 try-catch 的俄罗斯套娃
当你不确定哪里会出错时,可以在一个函数里嵌套多层 try-catch:
function processOrder(order) {
try {
try {
const validation = validateOrder(order);
try {
const payment = processPayment(validation);
try {
return shipProducts(payment);
} catch (e) {
console.log('发货失败');
}
} catch (e) {
console.log('支付处理失败');
}
} catch (e) {
console.log('订单验证失败');
}
} catch (e) {
console.log('未知错误');
}
}
这种写法有诸多好处:代码看起来非常"安全";错误处理逻辑分散在各处;当需要修改时,你得像考古学家一样逐层挖掘。
让错误静默消失
最高级的防御是让错误悄无声息地消失:
function getUserPreferences(userId) {
try {
const prefs = localStorage.getItem(`prefs_${userId}`);
return JSON.parse(prefs);
} catch {
// 什么都不做
}
}
当这个函数返回 undefined 时,调用方会以为是用户没有设置偏好,而实际上可能是 localStorage 被禁用、JSON 解析失败或者用户 ID 无效。这种静默失败能制造出最难以追踪的 bug。
不一致的错误处理策略
在同一个项目中混用多种错误处理方式:
// 方式1:回调错误优先
db.query('SELECT * FROM users', (err, result) => {
if (err) throw err; // 突然变成同步抛出
});
// 方式2:Promise catch
api.get('/data')
.then(handleData)
.catch(() => window.location.href = '/500'); // 跳转到错误页面
// 方式3:async/await 不处理
async function init() {
const data = await unsafeOperation(); // 没有 try-catch
}
这种多样性让每个阅读代码的人都得重新学习项目的错误处理"规范"——如果存在的话。
捕获但不处理
捕获错误只是为了记录,然后继续抛出:
function saveDocument(content) {
try {
return db.save(content);
} catch (e) {
console.error('保存失败:', e);
throw e; // 记录完继续抛出
}
}
这样既增加了日志系统的负担,又没有真正解决问题,一举两得。
过度捕获的陷阱
有些开发者喜欢用 try-catch 包裹一切:
try {
const x = 1;
const y = 2;
try {
const sum = x + y;
try {
console.log(sum);
} catch (e) {
alert('打印失败');
}
} catch (e) {
alert('加法失败');
}
} catch (e) {
alert('变量声明失败');
}
这种写法确保了即使 JavaScript 引擎本身出现故障,你的代码也能"优雅"地处理——虽然这种情况发生的概率比你中彩票还低。
忽略错误类型
把所有错误当作一种类型处理:
try {
dangerousOperation();
} catch (e) {
// 网络错误、类型错误、语法错误、业务逻辑错误...
showToast('出错了');
}
用户会喜欢这种模糊的错误提示,因为它保持了神秘感,让他们猜谜游戏。
异步错误的盲区
忘记异步代码也需要错误处理:
// 没有 catch 的 Promise
function uploadFile(file) {
return new Promise((resolve) => {
reader.readAsArrayBuffer(file);
reader.onload = resolve;
});
}
// 没有 try-catch 的 async
async function fetchData() {
const data = await api.get('/data');
process(data); // 如果 process 抛出错误...
}
这些未处理的 rejection 最终会变成控制台的惊喜礼物。
全局错误处理的错觉
依赖全局错误处理作为唯一防线:
// 主入口
window.addEventListener('error', () => {
document.body.innerHTML = '<h1>出错了</h1>';
});
// 然后所有业务代码都不需要 try-catch 了
function checkout() {
// 这里可能抛出各种错误
}
当错误发生时,用户只会看到一个毫无帮助的空白页面,而开发者则失去了所有错误上下文。
类型安全的假象
在 TypeScript 中假装错误不存在:
function divide(a: number, b: number): number {
return a / b; // 运行时 b 可能是 0
}
interface User {
name: string;
address?: {
street: string;
};
}
function getStreet(user: User) {
return user.address.street; // 愉快的 undefined 访问
}
类型系统给了你虚假的安全感,而运行时错误会给你一个真实的教训。
防御性注释代替防御性代码
用注释代替实际的错误处理:
// 注意:这里需要错误处理
function parseJSON(json) {
return JSON.parse(json);
}
// 可能会失败
function saveToDB(data) {
db.save(data);
}
这些注释就像马路上的"小心驾驶"标志,虽然提醒了危险,但并没有实际防止事故发生。
错误处理的性能考量
为了"性能"而省略错误处理:
// 热路径代码,不能有 try-catch 开销
function renderList(items) {
items.forEach(item => {
renderItem(item); // 如果 renderItem 抛出错误...
});
}
当这个函数崩溃时,你可以自豪地宣称这是为了性能做出的必要牺牲。
让调用方处理所有错误
把错误处理责任完全推给调用方:
class API {
static async get(url) {
const response = await fetch(url);
if (!response.ok) throw new Error(response.status);
return response.json();
}
}
// 然后每个调用 API 的地方都得自己 try-catch
async function getUser() {
try {
return await API.get('/user');
} catch (e) {
// 处理错误
}
}
这种设计确保了你的代码库中会有大量重复的错误处理逻辑。
用 try-catch 实现业务逻辑
创造性使用 try-catch 来控制流程:
function getUserRole(user) {
try {
checkAdminPermission(user);
return 'admin';
} catch {
try {
checkEditorPermission(user);
return 'editor';
} catch {
return 'guest';
}
}
}
这种模式把简单的条件判断变成了令人费解的异常流程,大大提高了代码的理解难度。
捕获然后继续执行
在错误发生后假装什么都没发生:
function processBatch(items) {
items.forEach(item => {
try {
transform(item);
} catch (e) {
console.log('处理失败:', item.id);
}
});
// 继续执行,尽管有些项失败了
saveResults(items);
}
这种"乐观"处理方式确保了错误会像滚雪球一样越积越大,直到最终爆发。
错误对象的创造性忽略
捕获错误但忽略错误对象:
try {
parseConfig();
} catch {
// 不知道发生了什么错误,但先恢复默认配置
resetConfig();
}
这种写法展示了开发者对错误的深刻理解——它们不值得被仔细研究。
多层抽象中的错误隐藏
在底层捕获错误然后转换为模糊信息:
// 数据访问层
function queryDatabase(sql) {
try {
return db.query(sql);
} catch (e) {
throw new Error('数据库操作失败');
}
}
// 服务层
function getProducts() {
try {
return queryDatabase('SELECT * FROM products');
} catch (e) {
throw new Error('获取产品失败');
}
}
// 控制器层
async function listProducts(req, res) {
try {
const products = await getProducts();
res.json(products);
} catch (e) {
res.status(500).send('服务器错误');
}
}
经过这样层层包装,原始的错误信息就像玩了一场传话游戏,最终变得面目全非。
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn