news 2026/8/30 5:33:00

模型输出垃圾?读代码是关键:推理链路排查指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
模型输出垃圾?读代码是关键:推理链路排查指南

最近被一个问题问到:同一个模型,别人跑出来效果不错,自己跑出来却像垃圾,到底是模型不行,还是调用方式不对?我第一句话就是:你读代码了吗?对方愣了一下,说看输出文件确实能看出结果不对,但搞不清问题出在哪一环。这个场景非常典型。不读代码,你就无法判断一份模型输出到底是模型本身产生的,还是推理代码把结果变成垃圾的。

模型权重本身只是一堆参数矩阵和计算规则,真正决定输出长相的是外层代码:数据加载、tokenize、前处理、前向传播、后处理、解码、格式化。输出文件只是最后一步的快照,快照不能告诉你中间哪里被污染了。读代码不是为了让代码写出更好看的结果,而是为了让你在拿到一份垃圾输出时,能直接定位到对应环节。

下面按实际排查顺序展开:先定义什么是垃圾输出,再说为什么只看结果不靠谱,然后拆开模型四周的污染点,最后给一条可执行的排查链路。

1. 只盯着最终结果,很难分辨哪些输出其实是垃圾输出

1.1 垃圾输出不是只有乱码一种形态

很多人把垃圾输出等同于“乱码”,这个定义太窄。实际开发里,垃圾输出至少表现为六种形态:

输出形态典型表现容易误判的原因
乱码UTF-8被错误解码、全角半角混杂、特殊字符堆叠觉得是编码问题,直接转码处理
截断内容写到一半突然结束,没有结尾标记觉得是模型能力不够
重复同一个小片段重复出现,拼凑成很长的文本觉得是长文本生成通病
格式错误JSON带markdown代码块、日期多字段、字段名漂移觉得是模型没理解格式要求
语义漂移句子通顺,但事实与输入矛盾觉得是模型幻觉,无法定位
数值异常embedding相似度几乎全为0、排序分数范围离谱觉得是模型输出垃圾,直接换模型

这些情况如果只拿一个输出文件去分析,很难判断是权重本身的问题,还是推理代码的问题。一个文本生成模型,如果采样时没有重复惩罚,长文本里出现重复段就是必然结果。你只看报告,可能以为“模型生成重复内容”,实际是代码里没做任何去重控制。

再比如文本分类模型,如果推理代码里的tokenizer版本和训练时不一致,特殊token编号可能完全不同。这种情况会导致任何输入进入模型后都带着错误token,输出标签自然乱套。输出结果看起来是“模型分错了”,真实原因却是代码加载了错误词表。

1.2 输出“正常”不代表模型“正常”

判断一个程序是否跑对,最基础的标准不是不报错,而是数据链路完整。这个道理放在模型推理上一样成立。

热词里有“字符串逆序输出c”。假设你拿到一个C程序,输入hello,输出olleh,看起来没问题。但你没读代码,不知道它有没有处理缓冲区边界、有没有在字符串末尾补上结束符。输入一长,程序就可能崩溃,或者输出乱码。只看短输入的结果,判断不出这个实现是否健壮。

模型推理比这更复杂。一次输出看起来正常,可能只是因为你选了一条适合当前代码的输入。换个输入长度、换一种语言、换一个batch,问题就暴露出来了。比如某个LLM接口,当你输入长度接近max_length时,输出尾部会反复出现填充token;当你输入包含特殊符号时,输出里会残留未解码的token。这些问题的共同点是:光看一条输出样本,你很可能会忽略。

还有更隐蔽的情况:输出在数值上看起来合理,但含义已经错了。比如embedding模型返回的向量维度相同、范数也正常,但如果代码错误地使用了隐藏层第一个token向量,而不是使用mean pooling,语义编码就会偏差很大。最终相似度可能依然区分度不错,但和真实语义完全偏离。这种垃圾输出最危险,因为你很难靠肉眼识别。

1.3 “输出正常”会让人错过真正的排查机会

只看最终结果还有一个坏处:它会削弱你排查问题的动机。当模型输出“勉强能看”时,你会倾向于认为代码没什么大问题,可能只是参数没调好。于是你反复调温度、调top_p、换seed,甚至换模型,但问题始终没有根治。

我见过一个比较典型的案例:某个生成任务里,模型偶尔会在输出中多出一段固定的样板文字,像是把训练数据里的模板背下来了。最初大家都以为是“模型记性太好”,后来有人去读解码代码,发现是beam search的beam_width设置过大,导致高概率路径里总是混入模板片段。把beam_width调小,问题立刻消失。

这个案例说明,输出不是完全垃圾时,问题更隐蔽。你不读代码,就永远想不到要去查beam search的实现细节。垃圾输出往往不是一个点,而是一条链路上的多个点共同造成的。只有把链路打开,才能看到问题在哪里。

2. 不读代码,你连“模型到底有没有跑对”都无法确认

2.1 代码是模型的“操作说明书”

模型权重文件里只有数值,它不会告诉你输入需要什么顺序、输出需要怎么解析。真正定义模型行为的是三部分代码:加载权重的代码、数据处理的代码、前向传播和后处理的代码。

拿“transformer模型详解”来举例。你理解了transformer结构,知道attention、feed forward、layer norm这些模块,但到了实际推理时,仍然会遇到很多结构图里看不到的问题:

  • attention mask是否把padding位置遮住;
  • 是否需要用到position_ids;
  • 是否需要传给模型labels,还是只传input_ids;
  • 输出里取last_hidden_state还是pooler_output;
  • 是否应该在lm_head输出后接softmax。

这些问题只能在代码里确认。如果attention mask没有正确遮住padding位置,生成的文本很可能带着奇怪的重复开头;如果取错了输出层,分类效果会大幅下降。你只看最终输出,很难看出是哪个环节出了偏差。

2.2 同一套权重,不同读取代码会产生不同结果

“相同模型、不同结果”是我经常遇到的问题。两个人拿同一个开源权重,输入一样的文本,输出却不一样。大部分人第一反应是“模型随机性”,第二反应是“硬件差异”,但真正原因往往在加载和推理代码。

常见的差异来源包括:

  • 加载成float16还是float32,数值精度不同;
  • tokenizer版本不同,特殊token编号不一致;
  • 设置了不同的max_length,长文本被截断的位置不同;
  • 是否开启beam search或sample,采样策略完全不同;
  • 是否设置repetition_penalty,重复程度差异很大;
  • 推理时是否执行了torch.no_grad,影响内存和随机行为。

这些差异里,有些是可控的,有些甚至不算错误,只是选择不同。但当你在排查一个垃圾输出时,如果不去读代码,你就无法知道当前这份结果是在什么条件下生成的。没有这些上下文,所有的比较都是无效的。

2.3 模型检查要落到数据流,而不是只看结构名

热词里出现“模型检查器”。很多人以为检查模型就是打印model.summary(),看层名和参数量。这个动作只能说明网络结构加载了,不能说明推理逻辑正确。

真正的模型检查,至少要做三件事:

  1. 检查前处理代码是否和训练一致,包括tokenize、resize、normalize、数据增强和特征抽取;
  2. 检查forward函数里的数据流是否按预期执行,特别是有没有把不同类型的数据混在一起计算;
  3. 检查后处理代码能否正确还原输出,包括解码、阈值判断、批量结果下标映射。

我习惯把这些检查写成一小段脚本,打印每个阶段的数据shape和取值范围。例如,在文本生成任务里,我会打印input_ids前后两个token、attention_mask里有多少个1、output里有没有出现eos_token_id。这些中间结果能快速帮我判断,代码是否在正确执行。

3. 垃圾输出最常藏在前处理和后处理的边界

3.1 前处理没对齐,输入在一开始就带上了垃圾

前处理是模型接触数据的第一站。数据在这里被转成模型能理解的数字。常见的前处理污染点有:

  • 文本没有统一大小写或没有处理全角半角;
  • tokenizer没有设置add_special_tokens,[CLS]、[SEP]丢失;
  • embedding模型的输入没有做max_length截断,长文本被无声分段;
  • 图片没有resize到训练分辨率,模型被迫处理尺寸不一致的输入;
  • 没有做归一化,数值范围直接溢出或过小。

以“滑动窗口滤波模型”为例。如果训练时的窗口是128,推理代码里窗口长度写成了256,表面看只是扩大窗口,实际会改变特征数量,甚至直接造成维度不匹配。有些情况下模型不报错,但输出已经和训练分布脱节。这种问题,不打开处理代码根本发现不了。

推理时还要注意一个容易被忽略的点:训练时是否做过shuffle。如果推理代码里意外开启了shuffle,或者使用DataLoader时shuffle=True没有关掉,那么每次推理的输入顺序都会被打乱。对于单条输入可能没影响,但批量推理时,输出顺序会与输入顺序错位,结果就是“模型预测不稳定、结果对不上”。

3.2 后处理没做对,logits再准也会变成垃圾

后处理是把模型输出变成用户可读结果的地方。这里也是垃圾输出的高发区。我见过很多次,模型输出的概率分布是正常的,但后处理代码在解码时用错了tokenizer,或者在JSON序列化时没有处理转义字符,最终包装出来的结果完全不可用。

后处理要重点盯几个点:

  • 采样参数:top_p、top_k、temperature、repetition_penalty;
  • 解码参数:skip_special_tokens、clean_up_tokenization_spaces;
  • 格式转换:有没有把tensor转成list,有没有执行detach和cpu;
  • 阈值判断:模型输出概率0.5还是0.8算正类,阈值不同结果完全不同;
  • 批量拼接:多个batch的结果是否按输入顺序重新排列。

这些点上,任何一个设置错了,都可能造成风格化、格式化的垃圾输出。关键是,模型本身可能完全没问题。

3.3 典型例子:embedding向量和重排序模型

热词里有一句长问题:“昇腾910b-a2服务器上不能通过vllm启动embedding向量和reranker模型吗”。这种问题的本质,也是判断输出是否可用前的“能不能跑”问题,但排查时依然要先读启动代码。

Embedding模型输出的是向量,不是自然语言,肉眼判断更困难。判断一个向量输出是不是垃圾,只能靠代码检查这几个点:

  • 输入文本是否做normalize;
  • 是否对输出向量做归一化;
  • 使用mean pooling还是直接取第一个token;
  • 模型是否被错误地当作生成模型来加载;
  • 是否处于训练模式,导致dropout和BN层行为不一致;
  • 批量推理时,不同长度文本的padding是否带来干扰。

重排序模型更特殊。它输出的是query和document的相关性分数。如果分数范围异常,或者排序结果与已知标注矛盾,你只能回到代码里确认模型类型是交叉编码器还是双编码器,输入是拼接还是分别编码,分数是原始logits还是经过sigmoid。不看代码,就不知道该用哪个分数去做排序。

“网站代码”也类似。你通过一个接口拿回200响应,返回JSON也看着完整,但如果接口文档和返回结构代码不一致,客户端解析时拿不到预期字段,这同样属于垃圾输出。你以为模型解析失败,其实是对接代码没有匹配好。

4. 代码到底要读哪里,才算读到了关键位置

4.1 先读入口文件,确认参数从哪里来

面对一个开源项目,不要急着直接跑起来。第一步读入口文件,看命令行参数、配置文件、环境变量和模型路径的加载逻辑。入口文件决定了整个项目的默认行为。

常见问题就藏在入口代码里:

  • 模型路径参数指向了错误目录;
  • 设备参数写死cpu,没有用GPU;
  • evalframework和训练框架不一致;
  • prompt模板在入口被写死;
  • 一些重要开关只在config里设置,命令行参数覆盖不了。

入口文件里最容易漏掉的是“默认值”和“覆盖顺序”。如果程序允许命令行参数覆盖config,但你传参时没传对名字,实际生效的仍是config里的默认值。你以为是新参数生效,其实根本没被读到。这种情况不读代码,几乎不可能发现。

4.2 再读输入和输出的转换代码

对模型输出影响最大的,是输入输出转换代码。我把它们分成几类函数来查:

  • tokenizer和processor相关的函数;
  • 把字符串、图像、表格转成tensor的逻辑;
  • 把模型原始输出转成文本、图片、坐标、分数的逻辑;
  • 批量结果拼接和排序的逻辑;
  • 成功或失败判断的阈值逻辑。

读这些代码时,要带着问题去读:原始输入变成模型输入的过程中,每一步做了什么;模型输出变成用户可读内容的过程中,每一步做了什么。每多一步转换,信息都可能丢失或变形。

4.3 用最小样例验证代码行为

静态读代码可能会漏掉动态行为,所以我一般会写一个最小样例,跑到数据链路的每个阶段,打印中间结果。例如:

sample_text = "模型的垃圾输出排查,本质是数据链路排查。" inputs = tokenizer( sample_text, return_tensors="pt", max_length=16, truncation=True, padding=True, ) print(inputs["input_ids"].shape) print(tokenizer.convert_ids_to_tokens(inputs["input_ids"][0]))

这一步能同时验证三件事:tokenizer是否正常工作、是否加上了特殊token、max_length和truncation是否按预期触发。如果中间有任何一步和预期不符,基本就能定位问题。

这个方法在做“模型蒸馏”和“模型融合”时尤其重要。因为蒸馏和融合都会改变网络结构,代码中只要有一处维度处理不对,后面的结果就是垃圾。小样例里能立刻看出shape不一致、数值范围异常、输出不稳定等问题。不要一上来就开最大并发、最大batch,先把最小闭环跑通。

5. 高发场景复盘:LLM、扩散模型、结构化输出

5.1 LLM文本生成的垃圾输出

LLM文本生成里,垃圾输出形态很多,我遇到过的高频问题如下:

  1. 输出尾部出现一连串</s><eos>
  2. 文本在某个token处戛然而止;
  3. 句子通顺,但和输入事实不符;
  4. 一个意思翻来覆去说,像在凑字数;
  5. markdown结构错乱,代码块没有闭合;
  6. 特殊字符被转义成乱码。

这些问题的修复方向完全不同。重复问题要看repetition_penalty、no_repeat_ngram_size和beam width;乱码要看tokenizer.decode是否设置skip_special_tokens;截断要看max_new_tokens和eos_token_id;markdown错乱要看后处理是否做了格式校验。

所以,遇到LLM输出异常时,第一件事不是换模型,而是把输出形态和参数对应起来。我之前在调试一个文本摘要任务时,发现输出结果里经常出现大段大段的重复,调了半天temperature都没用。后来读了生成代码,才发现根本没有设置repetition_penalty,默认值就是1.0,等于没有惩罚。改完参数,重复问题立刻改善。

5.2 扩散模型图像的垃圾输出

扩散模型的垃圾输出,要么是图片模糊、结构扭曲,要么是内容和提示词无关。很多人第一反应是加步数、换scheduler,但如果你没读生成代码,很可能漏掉几个关键点:

  • 输入图像是否被resize到训练分辨率;
  • VAE解码时是否出现精度损失;
  • latent space里是否做了错误的缩放;
  • 多个图像在batch维度上是否被错误组合;
  • classifier-free guidance的scale是否正确传入;
  • 随机种子和采样器是否会在每次运行时重置。

以“controlnet代码详解”里最常见的问题为例。ControlNet需要把控制条件图处理到和底模一致的分辨率,同时要把控制图像和提示词放进同一个生成流程。如果条件图的通道数、顺序、归一化范围不对,控制效果会直接消失。输出图像本身可能不报错,不提任何异常,但控制信息根本没生效。不看代码,你只会觉得“ControlNet效果不稳定”。

另一个常见坑是精度问题。扩散模型在FP16下运行速度更快,但如果代码没有做好数值稳定处理,VAE解码阶段可能产生色带或噪声。你看到图像有彩色条纹,第一反应是生成模型变差了,实际是dtype转换时精度丢失。这类问题必须读代码,确认每个阶段的dtype和device。

5.3 结构化输出:格式对不上,解析必然失败

热词里有“spring ai structured output如何定义实体类”。这个问题的本质,就是结构化输出里的垃圾输出问题。

Spring AI中的结构化输出,需要定义实体类来约束模型返回的JSON结构。如果实体类的字段名、嵌套层级和模型实际返回的JSON不一致,解析结果可能就是一堆null字段。你看到null,第一反应是模型没有生成内容,但真实原因往往是实体类定义和模型输出没有对齐。

还有一个常见情况是“格式化输出”。比如你要模型输出日期格式,代码里定义了LocalDate类型,但模型返回的是带时间的字符串,Spring AI转换时就会失败。你会看到异常日志,但如果只是看输出,可能会怀疑模型不能处理日期。

“sql 输出序号”也属于这一类。SQL查询结果是否垃圾,取决于排序字段、去重逻辑、分组边界是否正确。只看最后几行结果,很容易忽略某个排序字段改变了整个结果集。要判断SQL输出是否正确,必须读生成这段SQL的代码逻辑,或者至少读SQL语句本身。

6. 从读代码到识别垃圾输出:一条可执行的排查链路

6.1 第一步:最小复现

遇到可疑输出时,不要急着在长文本、大数据集、批量任务上分析。先把输入压缩到最小,用同一份代码重新跑一遍,目标是稳定复现这个问题。

如果最小输入下输出还是垃圾,你就有了一个可重复的调试样例。如果最小输入下输出正常,说明问题与输入规模、长度、并发或随机种子有关。这两种情况排查方向完全不同。没有复现,就不要继续改参数,否则你只是在猜。

6.2 第二步:打印中间结果

在代码里加打印,是成本最低的排查方式。我通常按五条线打印:

  1. 输入拿到后的原始类型和长度;
  2. 预处理之后的数据shape、dtype、取值范围;
  3. 模型前向输出的shape和关键值;
  4. 后处理第一步产生的中间结果;
  5. 最终结果字符串的前100个字符。

只要哪一个环节不符合预期,问题就缩窄到那一层。然后你再决定是改参数、改数据处理,还是回头确认模型版本。

如果项目比较复杂,可以用pdb或python debugger在关键位置打断点,也可以把中间结果直接保存成文件,避免重复运行。注意,别在不理解数据链路时直接加一堆print,那样只会让日志更乱。

6.3 第三步:与参考实现做diff

如果不确定问题在自定义代码还是模型本身,用官方实现或其他可信任实现跑同一份输入,然后对比中间结果。这个“参考实现”不一定是另一个项目,也可以是你自己的旧版本代码。目标是找出差异最早出现的点。

以“快速排序代码”为例。如果排序结果不对,你会先看分区函数,再看递归边界,再看输入数组是否被意外修改。模型代码也一样:先找输入数据变化位置,再看前向传播输出,再看解码结果。差异出现在哪里,问题就在哪里。

线上到本地也一样。如果你在测试环境输出正常,线上环境输出异常,说明环境差异一定存在。可能的差异点包括模型路径、tokenizer版本、依赖包版本、GPU型号、torch版本、CUDA版本。逐项对比代码中的依赖声明和实际环境,能快速缩小范围。

6.4 第四步:批量任务的坑单独处理

批量推理里的垃圾输出,还有另一种形态:部分成功、部分失败,或输出错位。原因往往在batch拼接和索引恢复上。

举个例子,一个batch里有不同长度的输入,代码做了padding但没有记录原始长度,后处理时把padding也解码出来,于是输出尾巴上多了一堆无意义token。或者,并行推理时任务队列里的结果顺序和输入顺序不对应,最后得到的输出表错位。这两种情况,都会表现为“模型抽风”,实际是代码没有处理好“多输入单输出”或“多输入多输出”的映射关系。

批量任务排查时,建议额外打印:batch里的原始长度列表、padding后的shape、输出里的有效token数量、排序后的下标。只要下标映射对得上,顺序问题一般都能发现。如果结果仍然对不上,再考虑并发锁、队列、文件写入顺序等场景。

7. 读代码这件事,值得变成识别垃圾输出的日常习惯

7.1 不要只信报告和样例

模型评测报告只能告诉你“在评测集上,分数是多少”,不能告诉你“在你的数据上,结果能不能用”。判断输出是否垃圾,必须结合输入样例、评测脚本、生成参数和后处理规则。这些东西全部写在代码里。不看代码,只看报告和输出文件,永远有盲区。

尤其是开源模型,官方评测时使用的prompt模板、few-shot样例、max_length和后处理参数,很可能和实际部署代码不同。你直接在项目里调用,得到的输出和官方效果不一样,这是正常现象。要判断是谁的问题,还得读参考代码。

7.2 接手任何项目,先跑最小闭环

接手一个已有模型项目时,第一步不是看训练曲线,而是把“输入样例—模型调用—输出结果”的最小闭环跑通。跑通之后,再往前走一步:拆开每一步,看输入、输出、中间值。这个动作通常只需要十几分钟,但能避免之后数小时的无效调参。

我见过不少同学,一上来就开了大规模批量任务,结果任务跑到一半发现输出格式不对。这时你还要停掉任务,重新排查所有环节。与其这样,不如一开始就做一条最小输入的完整链路检查,确认每一步都符合预期,再上批量。

7.3 维护一份“垃圾输出来源”清单

你可以给团队或自己维护一份常见垃圾输出清单,每遇到一次问题,就补充一条:

现象怀疑对象要读的代码
文本大量重复采样参数、repetition_penalty生成循环、解码配置
乱码或特殊token残留tokenizer解码、skip_special_tokensdecode函数
JSON解析失败输出格式和后处理逻辑结构化输出实体类、JSON清理
图像模糊或错乱resize、VAE解码、dtype图像预处理、VAE解码代码
embedding相似度异常pooling、normalize、paddingmodel forward函数
批量结果错位batch索引、队列顺序推理循环、任务调度代码
生产环境和本地结果不一致依赖版本、设备、模型路径配置文件、依赖锁定文件

这份清单不是一次形成的,而是在每次发现问题后补充。积累多了,再拿到一个可疑输出,你会更快知道该去读哪一段代码,而不是从头到尾读整个项目。

7.4 读代码的核心目的,是把“感觉不对”变成“我知道哪里不对”

识别模型垃圾输出,本质上是把“我觉得效果不对”变成“我知道是哪一步不对”。前者是感觉,后者是判断。要做判断,就必须让代码、输入、输出三者对齐。

只有真正读过加载权重的代码、前处理的代码、后处理的代码,你才能负责任地回答:这个模型能不能上线,当前输出能不能作为数据源,某个错误是该调参数还是该换模型。我接手过的很多case里,最终定位到的问题都不是模型权重,而是数据、环境或后处理。

这个习惯不只适用于AI模型。任何代码输出,只要你没有读过生成这份输出的逻辑,你都无法真正判断它是不是垃圾。区别只在于,模型推理的链路更长,垃圾更隐蔽,不读代码的代价也更大。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 5:31:34

SFIx变体:外部Flash Loader实现量产多段镜像烧录与校验

作为一个常年跟嵌入式固件和量产烧录打交道的工程师&#xff0c;我对“external loader”这个词再熟悉不过了。调试器&#xff08;JLink、OpenOCD、Keil、IAR&#xff09;默认只认识芯片内部的Flash&#xff0c;一旦你需要在外部SPI NOR Flash、SD卡甚至并口NOR上跑程序&#x…

作者头像 李华
网站建设 2026/8/30 5:31:10

Dubbo由浅入深17

第17章 Dubbo Admin服务治理 学习目标 读完本章,你将能够: 理解 Dubbo Admin 的功能模块划分与架构设计 掌握 Dubbo Admin 的多种部署方式(Docker / 源码编译) 熟练使用控制台进行服务查询与治理操作 掌握路由规则和动态配置的创建与管理 使用服务测试功能验证 Dubbo 接口…

作者头像 李华
网站建设 2026/8/30 5:28:23

AI面试官系统实战:语音对话+在线编程的架构与Demo实现

在准备技术面试的过程中&#xff0c;很多同学都遇到过类似的困境&#xff1a;刷题刷了不少&#xff0c;但真正面对面试官时&#xff0c;要么表达没有条理&#xff0c;要么一紧张就写不出完整代码。最近我一直在关注一类新的 AI 工具&#xff1a;让 AI 扮演面试官&#xff0c;用…

作者头像 李华
网站建设 2026/8/30 5:26:26

京东校招技术类选择题考点解析:数据结构算法与计算机基础

秋招那段日子&#xff0c;白天泡图书馆刷题&#xff0c;晚上守着牛客网看面经&#xff0c;手机里存了十几张截图全是“京东2017校招技术类选择题&#xff08;一&#xff09;”这种标题。后来我自己整理了一套笔记&#xff0c;把当年那批技术选择题的考点、易错点、复习方法全理…

作者头像 李华
网站建设 2026/8/30 5:26:18

2018网易iOS实习生笔试题回顾:核心考点与准备策略

做iOS开发这些年&#xff0c;我陆陆续续帮公司出过笔试题、也批过不少卷子。前阵子整理旧电脑&#xff0c;翻出一份2018年网易iOS开发实习生的笔试题备份&#xff0c;重看一遍还挺有感触。那年头iPhone X刚出、Swift 4还在跟Swift 3的兼容性较劲&#xff0c;但笔试里考的核心东…

作者头像 李华