MATLAB实战:AFDM与OFDM在时变信道下的抗多普勒性能对比

📅 发布时间:2026/9/20 16:29:44
MATLAB实战:AFDM与OFDM在时变信道下的抗多普勒性能对比
高铁车厢里刷短视频一直在转圈无人机图传画面时不时糊成一团马赛克很多人第一反应是“信号覆盖不好”。但在通信物理层工程师眼里这大概率是另一回事OFDM这类经典多载波波形在快时变信道下被子载波间干扰ICI打崩了。OFDM靠子载波正交性吃饭一旦移动速度上来多普勒频移打破了正交性误码率就跟着涨。这两年学术界和物理层研究圈子里AFDMAffine Frequency Division Multiplexing仿射频率分复用被反复提起它把符号放到延迟-多普勒域上用仿射傅里叶变换生成波形是面向高铁、无人机、低轨卫星等高速场景的候选波形之一。这篇文章我就用MATLAB从零搭一套AFDM收发仿真在相同的时变信道下跟OFDM做对比把原理、参数设计、代码实现和踩坑点全部过一遍。适合正在做通信物理层仿真、毕业设计选题或者对新型波形感兴趣的同学参考。1. 为什么OFDM在高铁和无人机场景会“卡顿”AFDM又是怎么来的1.1 OFDM的软肋子载波正交性被多普勒打破OFDM的基本思想是把高速数据流拆成N路低速数据流分别调制到N个正交子载波上。接收端用FFT可以完美分离各路信号理论上子载波之间的正交性保证了互不干扰。这个过程打个比方一队人在各自独立的步道上齐步走只要步道划分清晰、大家步伐稳定谁也不会踩到谁。问题在于这条“步道”在高速移动场景下会变形。多普勒频移让每个子载波的实际频率发生偏移接收端看到的是一个个带频偏的载波。当最大多普勒频移f_d与子载波间隔Δf的比值达到百分之十几甚至更高时子载波之间的谱泄漏非常严重原本不该有干扰的位置充满了邻近子载波串扰过来的能量这就是ICI。具体算一笔账假设载波频率fc2GHz高铁运行速度v360km/h换算成100m/s。那么最大多普勒频移f_d fc·v/c 2e9 × 100 / 3e8 ≈ 666.7 Hz。如果系统带宽B160kHz子载波数K64那么子载波间隔Δf B/K 2.5kHz。这时f_d/Δf ≈ 0.267也就是多普勒频移占了子载波间隔的将近27%。这个数字意味着OFDM的正交性基本被破坏星座图会散成一团误码率出现很高的错误平层这就是高铁场景下“信号满格却卡顿”的根本原因。无人机场景更复杂。无人机飞行速度虽然不如高铁但低空多径散射丰富、机体姿态快速变化、桨叶遮挡带来的信道突变都会让信道在毫秒级尺度内快速起伏。OFDM符号周期稍微一长信道就不再“时不变”同样会产生严重的ICI。1.2 AFDM的出身仿射傅里叶变换与延迟-多普勒域调制AFDM的思路跟OFDM完全不同。OFDM把符号放在频率域用复指数基函数去匹配信道AFDM把符号放在延迟-多普勒域用带二次相位项的啁啾基函数去匹配信道。为什么要换域因为在时变多径信道中每个传播路径可以用两个参数描述延迟τ和多普勒ν。OFDM在频域调制多普勒效应表现为子载波之间的能量扩散收端要花很大力气去抵消而AFDM直接在延迟-多普勒平面放置信息符号让每个符号都对应一组特定的τ和ν。当信道中存在某条真实路径时发射的符号能量会“集中”地转移到接收端对应的延迟-多普勒位置附近而不是像OFDM那样被摊开到整个频带。AFDM使用的数学工具是仿射傅里叶变换Affine Fourier TransformAFT它是分数傅里叶变换的一种推广也是最一般的线性正则变换的离散形式之一。从实现角度说AFDM发射端要做的是逆离散仿射傅里叶变换IDAFT接收端做离散仿射傅里叶变换DAFT两者都是矩阵运算复杂度跟FFT同量级落地代价不大。AFDM跟另一个热门候选波形OTFS经常被放在一起讨论。OTFS用ISFFT在时频域和延迟-多普勒域之间切换基函数是二维矩形波AFDM则直接构造仿射傅里叶基基函数本身带两个啁啾参数可以更灵活地匹配不同形态的时变信道。1.3 AFDM、OFDM、OTFS到底怎么选项目OFDMOTFSAFDM调制域频域延迟-多普勒域延迟-多普勒域基函数复指数二维DFT/矩形脉冲仿射傅里叶啁啾基核心变换IFFT/FFTISFFT/SFFTIDAFT/DAFT抗多普勒能力弱强强抗时延扩展能力靠CP靠CP或保护间隔靠啁啾参数设计实现复杂度最低中等中等检测算法简单单点均衡需要MP等迭代检测需要MP等迭代检测适用场景固定/低速高铁、卫星高铁、无人机、卫星标准化进度已大规模商用6G预研阶段6G预研阶段从这张表能看出AFDM不是来颠覆OFDM的而是面向未来高速移动场景的补充方案。它的核心价值在于用几乎和OFDM相同的收发复杂度换来对时变信道更强的鲁棒性。2. 开写代码前必须搞懂的数学与仿真框架2.1 AFDM收发机整体结构AFDM的系统框图可以拆成几个模块发射端数据比特 → QPSK映射 → 延迟-多普勒域符号矩阵X → IDAFT变换 → 时域信号s → 加循环前缀 → 发射。信道时变多径信道每条路径有独立的时延、多普勒频移和复增益。接收端去循环前缀 → DAFT变换 → 得到延迟-多普勒域接收符号Y → 等效信道均衡/检测 → 判决解调。这里最核心的两个模块就是IDAFT和DAFT。理解它们就抓住了AFDM的本质。2.2 IDAFT和DAFT的矩阵表达设总符号数为K通常K M × N其中M是延迟维符号数N是多普勒维符号数。对任意给定的两个啁啾参数c1和c2定义K×K的DAFT矩阵F其第(p, q)个元素为F(p, q) (1/√K) · exp( j2π( c1·(q-1)² c2·(p-1)² (p-1)(q-1)/K ) )其中p是输出索引q是输入索引。注意c1是沿多普勒维符号序号方向的二次相位系数c2是沿时间采样方向的二次相位系数这个顺序在写代码时容易搞混后面会专门讲。IDAFT矩阵就是F的共轭转置即F。发射端把延迟-多普勒域符号向量x映射成时域信号s F · x接收端把时域信号r映射回延迟-多普勒域y F · rF是酉矩阵即F·F I这意味着发射和接收变换本身不会引入额外畸变信道之外的系统是完美可逆的。这一点非常重要后面排查问题时可以用来验证链路是否正确。MATLAB里构造这个矩阵的代码非常简洁function F daft_matrix(K, c1, c2) % 构造K×K的DAFT矩阵 % c1: 多普勒维二次相位系数 % c2: 时间维二次相位系数 p (0:K-1).; q (0:K-1); F exp(1j*2*pi * (c1*q.^2 c2*p.^2 p*q/K)) / sqrt(K); end这里p是K×1列向量q是1×K行向量pq自动得到K×K的交叉相位项矩阵。c1q.^2得到K×1向量会自动广播成K×Kc2*p.^2同理。这段代码向量化程度高K取几百时运行也很快。2.3 参数设计M、N、K、带宽、子载波间隔怎么定参数设计直接决定仿真结果有没有参考价值。先明确几个基本关系系统带宽B采样率fs B在基带等效模型中时域采样间隔Ts 1/fs一个AFDM帧包含K个时域采样点帧时长T_frame K·Ts K/B延迟分辨率 1/B多普勒分辨率 1/T_frame B/K延迟维符号数M决定能分辨的最大时延扩展多普勒维符号数N决定能分辨的最大多普勒扩展关键约束有两个第一子载波间隔Δf这里借OFDM的说法对应AFDM的格点间隔必须大于信道最大多普勒频移否则多普勒混叠严重。Δf B/K所以在带宽固定的情况下K不能太大。第二帧时长T_frame不能超过信道相干时间或者说多普勒分辨率要能容纳信道多普勒变化范围。1/T_frame要大于最大多普勒频移否则DD域能量扩散出格点范围。这两个约束是矛盾的K越大子载波间隔越小抗多普勒能力越弱K越小虽然抗多普勒强了但频谱效率太低。仿真中取折中。我在示例里用M8N8K64B160kHz。这样Δf2.5kHzT_frame0.4ms多普勒分辨率2.5kHz。最大多普勒666.7Hz归一化后约0.267这个值既能看出OFDM明显劣化又不会让AFDM也因为多普勒混叠而失效。啁啾参数c1和c2怎么取理论上它们影响AFDM基函数在延迟-多普勒平面上的“倾斜”程度需要跟信道最大时延和多普勒匹配。工程里最稳妥的做法是先用一组中等大小的值让链路跑通比如c10.1c20.05然后再扫描参数看BER变化。注意一定不要同时取c1c20那样DAFT会退化成DFTAFDM就变回OFDM了。2.4 时变信道模型延迟-多普勒抽头怎么建时变多径信道最常见的离散基带等效模型是抽头延迟线模型。假设有P条可分辨路径每条路径有独立的复增益a_i、整数时延τ_i和多普勒频移ν_i。第n个采样时刻第i条路径的信道系数为h_i[n] a_i · exp( j2π·ν_i·(n-1)/fs )也就是说路径增益的幅度恒定相位随时间线性旋转旋转速度由多普勒频移决定。这个模型虽然简单但能很好地模拟高铁、无人机场景下的一阶时变特性。信道对发射信号的作用是时变卷积r[n] Σ_i h_i[n] · s[n - τ_i] w[n]写成矩阵形式就是r H_chan · s w其中H_chan是K×K的时变卷积矩阵。构造这个矩阵是整个仿真的关键操作代码长这样H_chan zeros(K, K); for n 1:K for p 1:P idx_out n delay(p); if idx_out K H_chan(idx_out, n) H_chan(idx_out, n) ... gain(p) * exp(1j*2*pi*Doppler(p)*(n-1)/fs); end end end这段代码的思路是发射信号第n个采样点s[n]经过第p条路径后会在第nτ_p个时刻出现在接收端增益是a_p·exp(j2πν_p(n-1)/fs)。把每一对(n, nτ_p)位置的关系写进矩阵H_chan就完整描述了信道。注意这里没有循环前缀帧首尾的路径会截断。为了让OFDM和AFDM公平对比两边都不加CP让边界效应同时作用于两种波形。3. MATLAB手把手实战AFDM波形仿真完整实现3.1 仿真环境与参数初始化建议使用MATLAB R2020a及以上版本代码只用到了基础矩阵运算和qam/qamdemod函数不需要额外工具箱。clearvars; close all; clc; % 帧参数 M 8; % 延迟域符号数 N 8; % 多普勒域符号数 K M * N; % 总符号数 64 mod_order 4; % QPSK bits_per_symbol log2(mod_order); % 射频与系统参数 fc 2e9; % 载波频率 2GHz B 160e3; % 系统带宽 160kHz fs B; % 基带采样率 delta_f B / K; % 子载波间隔 2.5kHz T_frame K / B; % 帧时长 0.4ms % AFDM啁啾参数 c1 0.1; % 多普勒维二次相位系数 c2 0.05; % 时间维二次相位系数 % 信道参数高铁场景 % 速度360km/h - 100m/s - 最大多普勒约666.7Hz P 2; % 可分辨路径数 delay [0, 2]; % 时延0采样点2采样点 Doppler [666.7, -500]; % 多普勒666.7Hz-500Hz gain [1, 0.8*exp(1j*pi/4)]; % 复增益这套参数对应的是高铁场景下两条典型路径一条直达径一条带时延的反射径两条路径都有明显的多普勒。3.2 发射端实现IDAFT调制发射端先把QPSK符号放进延迟-多普勒域的矩阵X中展平成向量x_vec再用IDAFT矩阵F调制到时域。% 构造DAFT矩阵 F daft_matrix(K, c1, c2); F_idaft F; % IDAFT矩阵 DAFT矩阵的共轭转置 % 随机生成QPSK符号 bits randi([0 1], K*bits_per_symbol, 1); x_vec qammod(bits, mod_order, InputType, bit, UnitAveragePower, true); % AFDM调制 s_afdm F_idaft * x_vec; % 发射功率归一化便于SNR控制 s_afdm s_afdm / sqrt(mean(abs(s_afdm).^2));这里有个细节值得解释为什么不直接把符号矩阵X reshape之后做IFFT而是要用矩阵乘法因为AFDM的基函数不是标准的复指数它包含c1和c2两个二次相位项。这两个相位项的作用是让基函数在延迟-多普勒平面上“对齐”信道的散射方向。矩阵乘法虽然比FFT慢但K64时完全不是瓶颈而且逻辑更清晰适合学习仿真。发射功率归一化很重要。qammod函数在UnitAveragePowertrue时保证符号平均功率为1但经过F调制后时域信号功率会变化所以发射前要再归一化一次。这样后面计算SNR时信号功率就是1方便直接控制噪声方差。3.3 时变多径信道实现信道矩阵构造如前所述完整代码可以封装成函数function H tv_channel(K, delay, Doppler, gain, fs) % 构造K×K时变多径信道卷积矩阵 % delay: 时延向量整数采样点 % Doppler: 多普勒频移向量Hz % gain: 复增益向量 % fs: 采样率 H zeros(K, K); P length(delay); for n 1:K for p 1:P idx_out n delay(p); if idx_out K H(idx_out, n) H(idx_out, n) ... gain(p) * exp(1j*2*pi*Doppler(p)*(n-1)/fs); end end end end仿真主脚本中调用H_chan tv_channel(K, delay, Doppler, gain, fs);构造完H_chan之后可以验证一下信道造成的功率变化chan_power mean(abs(H_chan * s_afdm).^2) / mean(abs(s_afdm).^2); fprintf(信道功率增益: %.4f\n, chan_power);正常情况下这个值应该在1附近。如果偏离很多说明增益设置有问题或者边界截断太严重。两条路径的增益幅度平方和为1 0.64 1.64但边界截断会损失一部分实际值会略小。3.4 接收端实现DAFT解调与线性均衡接收端做DFT解调然后利用等效信道矩阵做迫零或者MMSE均衡。% 接收 r_afdm H_chan * s_afdm; % 过信道暂时不加噪 % DAFT解调 y_afdm F * r_afdm; % 等效信道矩阵X - s - r - y 的完整映射 Heff_afdm F * H_chan * F_idaft; % 迫零均衡 x_hat_afdm Heff_afdm \ y_afdm;等效信道矩阵Heff_afdm的含义是发射端符号x_vec经过IDAFT调制、时变信道、DAFT解调之后在延迟-多普勒域看到的符号y_afdm跟x_vec之间的关系。如果信道是理想的Heff_afdm就是单位阵有信道时它包含了时延扩展和多普勒扩展造成的所有干扰。用左除\做线性均衡比显式求inv更稳定。严格地说迫零均衡在Heff_afdm病态时会放大噪声导致高SNR下反而性能变差。工程上更推荐MMSE均衡实现也很简单% MMSE均衡 snr_linear 10^(snr_db/10); lambda 1 / snr_linear; % 正则化系数 x_hat_afdm (Heff_afdm * Heff_afdm lambda * eye(K)) \ (Heff_afdm * y_afdm);lambda取噪声方差和信号功率的比值。信号功率归一化为1时lambda 1/SNR_linear。3.5 和OFDM同条件对比完整BER仿真OFDM的发射接收用归一化的FFT/IFFT矩阵实现保证两种波形在相同总符号数、相同功率、相同信道条件下对比。% 归一化FFT/IFFT矩阵 F_fft fft(eye(K)) / sqrt(K); % 归一化DFT F_ifft F_fft; % 归一化IDFT % 发射端 s_ofdm F_ifft * x_vec; s_ofdm s_ofdm / sqrt(mean(abs(s_ofdm).^2)); % 接收端 r_ofdm H_chan * s_ofdm; y_ofdm F_fft * r_ofdm; Heff_ofdm F_fft * H_chan * F_ifft; % 均衡 x_hat_ofdm Heff_ofdm \ y_ofdm;注意这里OFDM没有加循环前缀这是有意为之。加了CP的OFDM在对抗时延扩展上会好一些但无法对抗多普勒导致的ICIAFDM同样可以加CP为了公平两边都采用无CP的原始形式让对比聚焦在波形的时变信道鲁棒性上。完整的BER扫描循环如下SNR_dB 0:3:18; num_frame 200; % 每个SNR点仿真200帧 ber_afdm zeros(size(SNR_dB)); ber_ofdm zeros(size(SNR_dB)); for idx 1:length(SNR_dB) snr_linear 10^(SNR_dB(idx)/10); noise_var 1 / snr_linear; err_afdm 0; err_ofdm 0; total_bits 0; for frm 1:num_frame % 随机QPSK符号 bits randi([0 1], K*bits_per_symbol, 1); x_vec qammod(bits, mod_order, InputType, bit, UnitAveragePower, true); % 发射 s_afdm F_daft * x_vec; s_ofdm F_ifft * x_vec; s_afdm s_afdm / sqrt(mean(abs(s_afdm).^2)); s_ofdm s_ofdm / sqrt(mean(abs(s_ofdm).^2)); % 信道 r_afdm H_chan * s_afdm; r_ofdm H_chan * s_ofdm; % 加噪 noise sqrt(noise_var/2) * (randn(K,1) 1j*randn(K,1)); r_afdm r_afdm noise; r_ofdm r_ofdm noise; % 接收与均衡 y_afdm F_daft * r_afdm; y_ofdm F_fft * r_ofdm; H_afdm F_daft * H_chan * F_daft; H_ofdm F_fft * H_chan * F_ifft; % 改用MMSE更稳这里用左除展示基础版 x_hat_afdm H_afdm \ y_afdm; x_hat_ofdm H_ofdm \ y_ofdm; % 判决 bits_hat_afdm qamdemod(x_hat_afdm, mod_order, OutputType, bit, UnitAveragePower, true); bits_hat_ofdm qamdemod(x_hat_ofdm, mod_order, OutputType, bit, UnitAveragePower, true); err_afdm err_afdm sum(bits ~ bits_hat_afdm); err_ofdm err_ofdm sum(bits ~ bits_hat_ofdm); total_bits total_bits length(bits); end ber_afdm(idx) err_afdm / total_bits; ber_ofdm(idx) err_ofdm / total_bits; end figure; semilogy(SNR_dB, ber_afdm, o-, LineWidth, 1.5); hold on; semilogy(SNR_dB, ber_ofdm, s-, LineWidth, 1.5); grid on; xlabel(SNR (dB)); ylabel(BER); legend(AFDM, OFDM); title(高铁场景时变信道下 AFDM vs OFDM);这个循环每次重新生成独立的随机信道矩阵会更好但为了控制运行时间和保证对比公平我在示例中固定信道矩阵只随机变化符号和噪声。实际跑的时候可以每隔几帧重新生成一次H_chan观察平均性能。3.6 仿真结果如何解读跑完之后你会看到两条明显分开的BER曲线。低SNR时两者差距不大因为噪声是主要矛盾随着SNR升高OFDM的BER曲线会进入一个缓慢下降甚至平坦的阶段这就是ICI导致的错误平层。AFDM的BER曲线继续下降虽然迫零均衡放大了部分噪声但整体趋势明显好于OFDM。需要说明的是这里用的是最基础的线性均衡AFDM论文中最常使用的消息传递MP检测器性能更好差距会拉得更大。但这个最简单的线性版本足够说明问题在高多普勒信道下AFDM的延迟-多普勒域调制确实比OFDM的频域调制更能对抗时变信道。如果想看得更直观可以在无噪声条件下画星座图% 无噪声接收星座 r_afdm H_chan * s_afdm; y_afdm F_daft * r_afdm; x_hat_afdm Heff_afdm \ y_afdm; figure; subplot(1,2,1); plot(real(x_hat_afdm), imag(x_hat_afdm), .); title(AFDM 无噪声均衡后星座); axis equal; grid on; subplot(1,2,2); r_ofdm H_chan * s_ofdm; y_ofdm F_fft * r_ofdm; x_hat_ofdm Heff_ofdm \ y_ofdm; plot(real(x_hat_ofdm), imag(x_hat_ofdm), .); title(OFDM 无噪声均衡后星座); axis equal; grid on;OFDM的星座点会明显发散AFDM的星座点相对集中这个画面比BER曲线更直观。4. 仿真避坑指南多普勒换算、矩阵方向、归一化那些事儿4.1 多普勒频移和子载波间隔的换算陷阱多普勒频移的换算是最容易出错的地方。f_d fc·v/c这个公式里fc的单位是Hzv的单位是m/sc是光速3e8 m/s。如果把车速360km/h直接代入算出来是2.4而不是666.7差了3.6倍因为km/h要除以3.6转成m/s。另一个常见错误是把多普勒频移跟归一化多普勒混淆。归一化多普勒f_d/Δf才是跟子载波间隔的相对值仿真中信道的Doppler向量里填的是绝对频移Hz不是归一化值。如果填了0.267信道变化速度会慢很多仿真结果就完全失真了。判断参数是否合理的经验法则归一化多普勒小于0.01时OFDM几乎不受影响对比不出差距大于0.3时OFDM已经完全崩溃OFDM和AFDM差距会很大但略微脱离实际。0.1到0.3之间是比较有说服力的区间。4.2 IDAFT和DAFT矩阵方向别搞反发射用F接收用F这个方向一旦搞反系统就会变成“先做DAFT再做IDAFT”无信道时虽然能还原但过信道后的等效信道矩阵完全错误仿真结果毫无意义。验证方法很简单在无信道、无噪声条件下令r s_afdm然后解调得到y_afdm检查是否能还原x_vec。如果系统正确y_afdm应该等于x_vec。这个自检最好放在仿真循环之前% 链路自检 s_test F_daft * x_vec; y_test F_daft * s_test; if max(abs(y_test - x_vec)) 1e-10 error(IDAFT/DAFT链路自检失败请检查矩阵方向); end单看max误差时浮点误差通常小于1e-12如果出现较大误差一定是矩阵定义写错了。4.3 时变信道采样与多普勒混叠问题信道矩阵H_chan中多普勒相位项是exp(j2π·Doppler·(n-1)/fs)。根据奈奎斯特采样定理要正确表示多普勒频移ν需要ν fs/2。不过实际中更严格的约束来自子载波间隔ν相对于Δf的归一化值如果超过0.5延迟-多普勒域的格点会出现混叠符号能量从正多普勒方向溢出到负多普勒方向均衡器很难纠正。仿真中如果设置Doppler高达几千Hz而Δf只有2.5kHz就会出现这种混叠。轻则BER曲线异常重则解调完全失败。遇到这种情况要么提高B增大Δf要么降低Doppler保证归一化多普勒不超过0.4。另外时延参数必须是整数采样点。如果延迟不是整数倍采样周期需要用到分数时延滤波器或sinc插值复杂度会显著增加初学者不建议一上来就碰。4.4 调参建议与常用的排查思路现象可能原因排查动作无信道时星座也乱IDAFT/DAFT方向反了做链路自检有信道后BER极高多普勒过大导致混叠检查归一化多普勒OFDM和AFDM差距很小多普勒太低或者SNR太低提高多普勒或SNRBER曲线在中高SNR反弹迫零均衡放大噪声换MMSE均衡仿真跑得特别慢K太大且用循环构造信道向量化或用稀疏矩阵如果BER曲线的趋势不对先回到无信道场景确认通信链路本身是好的再逐步加入多径、多普勒、噪声每加一个模块就验证一次。这种“层层加码”的调试验证方式比一次搭完整套流程再找bug高效得多。还有一个小经验跑BER仿真时每个SNR点至少统计2万到5万个比特否则BER到10的-2以下时不稳定曲线会抖动得很厉害。我的示例里200帧×64符号×2比特总共25600比特在低误码率时勉强够用想更平滑可以加大num_frame。个人实操中的一些体会这个仿真我前后跑了很多版最直观的感受是AFDM不是“魔法”它的增益来自对信道散射函数的匹配。啁啾参数c1和c2看似只是两个常量实际决定了整个波形的性格。曾经试过把c1和c2设得过大AFDM的基函数在帧内振荡太剧烈高频成分让信道矩阵的数值稳定性变差均衡后反而比OFDM更差。后来把参数调小、结合信道最大时延和多普勒范围做匹配优势才体现出来。最后再分享一个技巧仿真前先把无信道、无噪声的收发星座图打出来确认链路是通的再开始叠加信道和噪声。很多时候所谓“AFDM性能不如OFDM”的结论其实都是矩阵方向或者功率归一化的问题并不是波形本身的问题。通信仿真最忌讳的就是链路还没自检就去对比性能那样得到的结果随时可能在改一个参数之后彻底反转。把这个习惯保持住后续无论换成OTFS、FBMC还是更复杂的迭代检测器都能少踩很多坑。