HarmonyOS开发:代码重构技巧
HarmonyOS开发:代码重构技巧
📌 核心要点:重构不是重写——重写是推倒重来,重构是小步改进。没有测试保障的重构是赌博,有测试保障的重构是工程。
背景与动机
你有没有遇到过这种代码?一个函数500行,嵌套8层if-else,变量名都是a、b、c,注释写的"这段逻辑很复杂,不要改"。你改了一行,整个功能挂了。
遇到这种代码,你的第一反应是什么?重写!推倒重来!但现实是,你根本不敢动——没有测试覆盖,改了不知道对不对;逻辑太复杂,改了不知道影响什么;时间太紧,没空重写。
重构要解决的就是这个问题:在不改变代码外部行为的前提下,改善代码的内部结构。注意关键词——“不改变外部行为”。重构不是加功能,不是改需求,是让代码变得更好理解、更好维护、更好扩展。
为什么鸿蒙项目特别需要重构?因为ArkTS的写法跟传统前端差异大,很多开发者用TypeScript的习惯写ArkTS,写出来的代码虽然能跑,但不符合ArkTS的最佳实践——@State滥用、ForEach代替LazyForEach、异步操作没错误处理……这些都需要重构。
核心原理
重构的核心原则
graph TD
A[重构核心原则] --> B[小步前进]
A --> C[测试保障]
A --> D[行为不变]
A --> E[持续集成]
B --> B1[每次只改一个点]
B --> B2[改完立即验证]
B --> B3[随时可以停止]
C --> C1[重构前先写测试]
C --> C2[测试覆盖关键路径]
C --> C3[重构后跑全量测试]
D --> D1[不改变外部接口]
D --> D2[不改变功能行为]
D --> D3[不改变输出结果]
E --> E1[每次提交都可构建]
E --> E2[每次提交都可部署]
E --> E3[快速回滚能力]
classDef mainStyle fill:#FF6B6B,stroke:#C0392B,color:#fff,font-weight:bold
classDef subStyle fill:#4ECDC4,stroke:#1ABC9C,color:#fff
classDef detailStyle fill:#FFE66D,stroke:#F39C12,color:#333
class A mainStyle
class B,C,D,E subStyle
class B1,B2,B3,C1,C2,C3,D1,D2,D3,E1,E2,E3 detailStyle
重构手法分类
graph LR
A[重构手法] --> B[函数级重构]
A --> C[组件级重构]
A --> D[架构级重构]
B --> B1[提取函数]
B --> B2[内联函数]
B --> B3[重命名变量]
B --> B4[简化条件]
C --> C1[拆分组件]
C --> C2[合并组件]
C --> C3[状态管理优化]
C --> C4[生命周期重构]
D --> D1[分层重构]
D --> D2[模块拆分]
D --> D3[接口抽象]
D --> D4[依赖注入]
classDef mainStyle fill:#FF6B6B,stroke:#C0392B,color:#fff,font-weight:bold
classDef subStyle fill:#4ECDC4,stroke:#1ABC9C,color:#fff
classDef detailStyle fill:#FFE66D,stroke:#F39C12,color:#333
class A mainStyle
class B,C,D subStyle
class B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4 detailStyle
代码实战
基础用法:函数级重构
最常见的重构——提取函数,让代码更易读:
// ==========================================
// 重构1:提取函数 - 消除长函数
// ==========================================
// ❌ 重构前:一个函数干了所有事
@Component
struct UserPageBefore {
@State userList: UserData[] = [];
@State isLoading: boolean = false;
@State errorMessage: string = '';
async loadUsers() {
this.isLoading = true;
this.errorMessage = '';
try {
const response = await http.request('https://api.example.com/users');
if (response.responseCode === 200) {
const data = JSON.parse(response.result as string);
if (Array.isArray(data)) {
// 过滤掉无效数据
const validUsers = data.filter((item: UserData) => {
return item.id && item.name && item.email;
});
// 按名称排序
validUsers.sort((a: UserData, b: UserData) => {
return a.name.localeCompare(b.name);
});
// 去重
const uniqueUsers = validUsers.filter((user: UserData, index: number) => {
return validUsers.findIndex((u: UserData) => u.id === user.id) === index;
});
this.userList = uniqueUsers;
} else {
this.errorMessage = '返回数据格式异常';
}
} else {
this.errorMessage = `请求失败: ${response.responseCode}`;
}
} catch (error) {
this.errorMessage = `网络错误: ${error}`;
} finally {
this.isLoading = false;
}
}
build() {
// UI代码...
Column() { Text('Users') }
}
}
// ✅ 重构后:每个函数只做一件事
@Component
struct UserPageAfter {
@State userList: UserData[] = [];
@State isLoading: boolean = false;
@State errorMessage: string = '';
async loadUsers(): Promise<void> {
this.setLoadingState(true);
try {
const data = await this.fetchUserData();
const processedUsers = this.processUserData(data);
this.userList = processedUsers;
} catch (error) {
this.errorMessage = this.formatErrorMessage(error);
} finally {
this.setLoadingState(false);
}
}
// 获取用户数据
private async fetchUserData(): Promise<UserData[]> {
const response = await http.request('https://api.example.com/users');
if (response.responseCode !== 200) {
throw new Error(`请求失败: ${response.responseCode}`);
}
const data = JSON.parse(response.result as string);
if (!Array.isArray(data)) {
throw new Error('返回数据格式异常');
}
return data;
}
// 处理用户数据:过滤、排序、去重
private processUserData(users: UserData[]): UserData[] {
return this.removeDuplicates(
this.sortByName(
this.filterValidUsers(users)
)
);
}
// 过滤有效用户
private filterValidUsers(users: UserData[]): UserData[] {
return users.filter(user => user.id && user.name && user.email);
}
// 按名称排序
private sortByName(users: UserData[]): UserData[] {
return [...users].sort((a, b) => a.name.localeCompare(b.name));
}
// 去重
private removeDuplicates(users: UserData[]): UserData[] {
return users.filter((user, index) =>
users.findIndex(u => u.id === user.id) === index
);
}
// 设置加载状态
private setLoadingState(loading: boolean): void {
this.isLoading = loading;
if (loading) {
this.errorMessage = '';
}
}
// 格式化错误信息
private formatErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
build() {
Column() { Text('Users') }
}
}
// ==========================================
// 重构2:简化条件表达式
// ==========================================
// ❌ 重构前:复杂的条件判断
function getDiscount_before(user: UserInfo, order: OrderInfo): number {
let discount = 0;
if (user.vipLevel >= 3) {
if (order.totalAmount > 500) {
if (order.itemCount > 5) {
discount = 0.3;
} else {
discount = 0.2;
}
} else {
discount = 0.1;
}
} else if (user.vipLevel >= 1) {
if (order.totalAmount > 300) {
discount = 0.1;
} else {
discount = 0.05;
}
}
return discount;
}
// ✅ 重构后:使用卫语句和策略模式
function getDiscount_after(user: UserInfo, order: OrderInfo): number {
// 非VIP用户无折扣
if (user.vipLevel < 1) {
return 0;
}
// 根据VIP等级和订单条件计算折扣
const discountStrategy = getDiscountStrategy(user.vipLevel);
return discountStrategy.calculate(order);
}
interface DiscountStrategy {
calculate(order: OrderInfo): number;
}
class VipLevel3Strategy implements DiscountStrategy {
calculate(order: OrderInfo): number {
if (order.totalAmount > 500 && order.itemCount > 5) return 0.3;
if (order.totalAmount > 500) return 0.2;
return 0.1;
}
}
class VipLevel1Strategy implements DiscountStrategy {
calculate(order: OrderInfo): number {
if (order.totalAmount > 300) return 0.1;
return 0.05;
}
}
function getDiscountStrategy(vipLevel: number): DiscountStrategy {
if (vipLevel >= 3) return new VipLevel3Strategy();
return new VipLevel1Strategy();
}
进阶用法:组件级重构
鸿蒙开发中最常见的重构——组件拆分和状态管理优化:
// ==========================================
// 重构3:拆分大组件
// ==========================================
// ❌ 重构前:一个组件包含所有UI逻辑
@Component
struct OrderPageBefore {
@State orderList: OrderData[] = [];
@State selectedOrder: OrderData | null = null;
@State isDetailVisible: boolean = false;
@State filterStatus: string = 'all';
@State searchKeyword: string = '';
@State isLoading: boolean = false;
build() {
Column() {
// 搜索栏
Row() {
TextInput({ placeholder: '搜索订单...' })
.onChange((value: string) => { this.searchKeyword = value; })
}
.width('100%')
.padding(16)
// 筛选栏
Row() {
ForEach(['all', 'pending', 'completed', 'cancelled'], (status: string) => {
Button(status)
.type(ButtonType.Normal)
.backgroundColor(this.filterStatus === status ? '#007DFF' : '#E8E8E8')
.onClick(() => { this.filterStatus = status; })
})
}
// 订单列表
if (this.isLoading) {
LoadingProgress()
} else {
List() {
ForEach(this.getFilteredOrders(), (order: OrderData) => {
ListItem() {
// 订单卡片 - 50行UI代码
Column() {
Text(`订单号: ${order.id}`)
Text(`金额: ¥${order.amount}`)
Text(`状态: ${order.status}`)
// ... 更多UI
}
.onClick(() => {
this.selectedOrder = order;
this.isDetailVisible = true;
})
}
})
}
}
// 订单详情弹窗
if (this.isDetailVisible && this.selectedOrder) {
Column() {
Text(`订单详情: ${this.selectedOrder.id}`)
// ... 100行详情UI
}
}
}
}
private getFilteredOrders(): OrderData[] {
return this.orderList.filter(order => {
if (this.filterStatus !== 'all' && order.status !== this.filterStatus) {
return false;
}
if (this.searchKeyword && !order.id.includes(this.searchKeyword)) {
return false;
}
return true;
});
}
}
// ✅ 重构后:拆分为多个小组件
@Component
struct OrderPageAfter {
@State orderList: OrderData[] = [];
@State selectedOrder: OrderData | null = null;
@State isDetailVisible: boolean = false;
@State filterStatus: string = 'all';
@State searchKeyword: string = '';
@State isLoading: boolean = false;
build() {
Column() {
// 搜索栏 - 独立组件
OrderSearchBar({ keyword: this.searchKeyword })
// 筛选栏 - 独立组件
OrderFilterBar({
currentStatus: this.filterStatus,
onStatusChange: (status: string) => { this.filterStatus = status; }
})
// 订单列表 - 独立组件
if (this.isLoading) {
LoadingProgress()
} else {
OrderList({
orders: this.getFilteredOrders(),
onOrderClick: (order: OrderData) => {
this.selectedOrder = order;
this.isDetailVisible = true;
}
})
}
// 订单详情弹窗 - 独立组件
if (this.isDetailVisible && this.selectedOrder) {
OrderDetailDialog({
order: this.selectedOrder,
onClose: () => { this.isDetailVisible = false; }
})
}
}
}
private getFilteredOrders(): OrderData[] {
return this.orderList.filter(order => {
const matchStatus = this.filterStatus === 'all' || order.status === this.filterStatus;
const matchKeyword = !this.searchKeyword || order.id.includes(this.searchKeyword);
return matchStatus && matchKeyword;
});
}
}
// 搜索栏组件
@Component
struct OrderSearchBar {
@Prop keyword: string;
build() {
Row() {
TextInput({ placeholder: '搜索订单...', text: this.keyword })
.layoutWeight(1)
}
.width('100%')
.padding(16)
}
}
// 筛选栏组件
@Component
struct OrderFilterBar {
@Prop currentStatus: string;
onStatusChange?: (status: string) => void;
build() {
Row() {
ForEach(['all', 'pending', 'completed', 'cancelled'], (status: string) => {
Button(status)
.type(ButtonType.Normal)
.backgroundColor(this.currentStatus === status ? '#007DFF' : '#E8E8E8')
.onClick(() => {
this.onStatusChange?.(status);
})
})
}
}
}
// 订单列表组件
@Component
struct OrderList {
@Prop orders: OrderData[];
onOrderClick?: (order: OrderData) => void;
build() {
List() {
LazyForEach(new OrderDataSource(this.orders), (order: OrderData) => {
ListItem() {
OrderCard({
order: order,
onClick: () => { this.onOrderClick?.(order); }
})
}
})
}
}
}
// 订单卡片组件
@Component
struct OrderCard {
@Prop order: OrderData;
onClick?: () => void;
build() {
Column() {
Text(`订单号: ${this.order.id}`)
Text(`金额: ¥${this.order.amount}`)
Text(`状态: ${this.order.status}`)
}
.onClick(() => { this.onClick?.(); })
}
}
// 订单详情弹窗组件
@Component
struct OrderDetailDialog {
@Prop order: OrderData;
onClose?: () => void;
build() {
Column() {
Text(`订单详情: ${this.order.id}`)
// ... 详情UI
Button('关闭')
.onClick(() => { this.onClose?.(); })
}
}
}
完整示例:安全重构流程
// safe-refactoring.ts
// 安全重构流程:测试保障下的重构
// ==========================================
// 步骤1:为待重构代码编写测试
// ==========================================
// 待重构的函数
function calculateOrderTotal(
items: OrderItem[],
vipLevel: number,
couponCode?: string
): number {
let subtotal = 0;
for (const item of items) {
subtotal += item.price * item.quantity;
}
// VIP折扣
let discount = 0;
if (vipLevel >= 3) {
discount = 0.2;
} else if (vipLevel >= 1) {
discount = 0.1;
}
subtotal = subtotal * (1 - discount);
// 优惠券
if (couponCode === 'SAVE10') {
subtotal = Math.max(0, subtotal - 10);
} else if (couponCode === 'SAVE20') {
subtotal = Math.max(0, subtotal - 20);
} else if (couponCode === 'HALF') {
subtotal = subtotal * 0.5;
}
// 满减
if (subtotal >= 200) {
subtotal -= 30;
} else if (subtotal >= 100) {
subtotal -= 10;
}
return Math.max(0, subtotal);
}
// 测试用例(重构前编写)
function testCalculateOrderTotal(): void {
// 测试1:无折扣
assertEqual(
calculateOrderTotal([{ price: 100, quantity: 1 }], 0),
100,
'无折扣'
);
// 测试2:VIP1折扣
assertEqual(
calculateOrderTotal([{ price: 100, quantity: 1 }], 1),
90,
'VIP1折扣10%'
);
// 测试3:VIP3折扣
assertEqual(
calculateOrderTotal([{ price: 100, quantity: 1 }], 3),
80,
'VIP3折扣20%'
);
// 测试4:优惠券SAVE10
assertEqual(
calculateOrderTotal([{ price: 100, quantity: 1 }], 0, 'SAVE10'),
90,
'优惠券减10'
);
// 测试5:满减100
assertEqual(
calculateOrderTotal([{ price: 100, quantity: 1 }], 0),
90, // 100 - 10(满减)
'满100减10'
);
// 测试6:满减200
assertEqual(
calculateOrderTotal([{ price: 200, quantity: 1 }], 0),
170, // 200 - 30(满减)
'满200减30'
);
// 测试7:组合折扣
assertEqual(
calculateOrderTotal([{ price: 200, quantity: 1 }], 3, 'SAVE20'),
108, // 200 * 0.8(VIP3) - 20(优惠券) - 30(满减) = 110...
// 需要根据实际逻辑调整
);
console.info('✅ 所有测试通过');
}
function assertEqual(
actual: number, expected: number, testName: string
): void {
if (Math.abs(actual - expected) > 0.01) {
console.error(`❌ ${testName}: 期望 ${expected}, 实际 ${actual}`);
} else {
console.info(`✅ ${testName}: 通过`);
}
}
// ==========================================
// 步骤2:重构(小步前进,每步跑测试)
// ==========================================
// 重构后的函数:策略模式 + 责任链
interface DiscountRule {
apply(total: number): number;
name: string;
}
class VipDiscountRule implements DiscountRule {
name = 'VIP折扣';
private vipLevel: number;
constructor(vipLevel: number) {
this.vipLevel = vipLevel;
}
apply(total: number): number {
const discountRate = this.getDiscountRate();
return total * (1 - discountRate);
}
private getDiscountRate(): number {
if (this.vipLevel >= 3) return 0.2;
if (this.vipLevel >= 1) return 0.1;
return 0;
}
}
class CouponDiscountRule implements DiscountRule {
name = '优惠券';
private couponCode: string;
constructor(couponCode?: string) {
this.couponCode = couponCode || '';
}
apply(total: number): number {
switch (this.couponCode) {
case 'SAVE10': return Math.max(0, total - 10);
case 'SAVE20': return Math.max(0, total - 20);
case 'HALF': return total * 0.5;
default: return total;
}
}
}
class ThresholdDiscountRule implements DiscountRule {
name = '满减';
apply(total: number): number {
if (total >= 200) return total - 30;
if (total >= 100) return total - 10;
return total;
}
}
// 重构后的计算函数
function calculateOrderTotalRefactored(
items: OrderItem[],
vipLevel: number,
couponCode?: string
): number {
// 计算小计
const subtotal = items.reduce(
(sum, item) => sum + item.price * item.quantity, 0
);
// 应用折扣规则链
const rules: DiscountRule[] = [
new VipDiscountRule(vipLevel),
new CouponDiscountRule(couponCode),
new ThresholdDiscountRule(),
];
let total = subtotal;
for (const rule of rules) {
total = rule.apply(total);
}
return Math.max(0, total);
}
// ==========================================
// 步骤3:验证重构结果
// ==========================================
function verifyRefactoring(): void {
const testCases: Array<{
items: OrderItem[];
vipLevel: number;
couponCode?: string;
}> = [
{ items: [{ price: 100, quantity: 1 }], vipLevel: 0 },
{ items: [{ price: 100, quantity: 1 }], vipLevel: 1 },
{ items: [{ price: 100, quantity: 1 }], vipLevel: 3 },
{ items: [{ price: 100, quantity: 1 }], vipLevel: 0, couponCode: 'SAVE10' },
{ items: [{ price: 200, quantity: 1 }], vipLevel: 0 },
];
let allPassed = true;
for (const tc of testCases) {
const original = calculateOrderTotal(tc.items, tc.vipLevel, tc.couponCode);
const refactored = calculateOrderTotalRefactored(tc.items, tc.vipLevel, tc.couponCode);
if (Math.abs(original - refactored) > 0.01) {
console.error(
`❌ 行为不一致: 原始=${original}, 重构后=${refactored}`
);
allPassed = false;
}
}
if (allPassed) {
console.info('✅ 重构验证通过:行为一致');
} else {
console.error('❌ 重构验证失败:行为不一致');
}
}
// ==========================================
// 重构与性能优化的平衡
// ==========================================
// 有时候重构会引入额外的抽象层,影响性能
// 需要在可读性和性能之间找平衡
// 方案1:热路径保持简单,冷路径可以抽象
function hotPathCalculation(items: OrderItem[]): number {
// 热路径:直接计算,不引入抽象
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// 方案2:编译时优化 - 使用内联装饰器提示
// @Inline // 提示编译器内联此函数(如果支持)
function calculateSubtotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// 方案3:缓存计算结果
class CachedOrderCalculator {
private cache: Map<string, number> = new Map();
calculate(items: OrderItem[], vipLevel: number, couponCode?: string): number {
const cacheKey = this.getCacheKey(items, vipLevel, couponCode);
const cached = this.cache.get(cacheKey);
if (cached !== undefined) {
return cached;
}
const result = calculateOrderTotalRefactored(items, vipLevel, couponCode);
this.cache.set(cacheKey, result);
return result;
}
private getCacheKey(
items: OrderItem[], vipLevel: number, couponCode?: string
): string {
return `${JSON.stringify(items)}_${vipLevel}_${couponCode || ''}`;
}
}
踩坑与注意事项
坑1:没有测试就重构
没有测试覆盖就动手重构,改完了不知道对不对。出了问题,分不清是新Bug还是旧Bug。
解决方案:
- 先写测试,再重构:这是铁律,没有例外
- 如果测试太难写,说明代码耦合太重,先解耦再测试
- 至少覆盖核心路径:正常流程、边界条件、异常情况
坑2:重构步子太大
一次改了10个文件、50个函数,改完跑测试发现挂了——哪个改动导致的?不知道,只能全部回退。
解决方案:
- 每次只做一种重构手法
- 每次重构后立即跑测试
- 使用版本控制,每步一个commit,方便回退
坑3:重构中夹带私货
重构的时候顺手加了个功能、改了个UI、优化了个性能——结果重构和功能变更混在一起,出了问题分不清原因。
解决方案:
- 重构就是重构,不加功能:这是铁律
- 功能变更和重构分开提交
- 如果重构过程中发现需要加功能,先记下来,重构完再加
坑4:过度重构
一段代码能跑、好理解、不需要改,你非要重构——为什么?因为"看着不顺眼"。结果重构引入了新Bug,得不偿失。
解决方案:
- 重构有目的:要么提高可读性,要么提高可维护性,要么提高性能
- 没有目的的重构不做
- 三次法则:第一次写可以容忍,第二次类似写法觉得不舒服,第三次必须重构
坑5:忽视ArkTS特有重构
用传统前端的重构手法重构ArkTS代码,可能适得其反。比如把@State改成普通变量来"简化"代码,结果UI不更新了。
解决方案:
- 理解ArkTS的状态管理机制再重构
- @State/@Link/@Prop/@ObjectLink的替换要谨慎
- 组件拆分时注意状态传递方式
- 重构后测试UI更新是否正常
HarmonyOS 6适配说明
HarmonyOS 6在重构支持方面的改进:
-
DevEco Studio重构菜单增强:IDE内置了更多重构操作,包括提取组件、内联组件、状态装饰器转换等鸿蒙特有重构手法。右键菜单就能用,不需要手动改。
-
重构预览:执行重构前可以预览所有受影响的文件和代码位置,确认无误后再执行。避免了"改了不知道影响什么"的问题。
-
自动迁移工具:HarmonyOS 6提供了API迁移工具,可以自动将废弃API替换为新API。这是最常见的演进式重构,现在可以自动化了。
-
重构安全检查:IDE在执行重构时会自动检查是否会影响行为,如果检测到潜在的行为变更会给出警告。
-
组件拆分助手:新增了组件拆分辅助工具,可以自动识别大组件中的独立UI块,建议拆分方案。
总结
重构是代码质量的日常维护工作。不是等代码烂了才重构,是每天让代码比昨天好一点。
核心要点:
- 先测试后重构:没有测试保障的重构是赌博
- 小步前进:每次只改一个点,改完立即验证
- 行为不变:重构不改变外部行为,不夹带功能变更
- 理解ArkTS再重构:状态管理、组件生命周期不能乱改
- 重构与性能平衡:热路径保持简单,冷路径可以抽象
| 维度 | 评分 | 说明 |
|---|---|---|
| 学习难度 | ⭐⭐⭐ | 重构手法不难,难在判断什么时候该重构 |
| 使用频率 | ⭐⭐⭐⭐ | 日常开发中经常用到,童子军规则每天执行 |
| 重要程度 | ⭐⭐⭐⭐⭐ | 代码质量的日常维护,不重构就退化 |
- 点赞
- 收藏
- 关注作者
评论(0)