学会提问
引言
授课老师与助教们欢迎同学提问。但是,老师/助教用于回答问题的时间精力是有限的公共资源,我们希望认真学习、用心提问的同学能够得到他所需要的帮助、希望对问题的回答能够让更多的同学受益,而不是被重复的、低质量的问题淹没。因此,请每一位同学在提问前阅读本文。如果你的问题违背了本文的注意事项,你将不会得到回答。
在提问之前
在提问前,首先你应该知道:
- 高程群里的助教都是志愿者,他们不欠你钱、也不从答疑中获得报酬。互相尊重是交流的起点。
- “提出一个好问题”并不是一件简单的事情,但它一定会对你有益。
- 提问前自己适当分析一下问题,既能让表述更加准确、节省他人时间,也能加深对问题的理解。
阅读已有的信息
文档
高程往往会为作业提供足够详细的文档,并对常见问题提供说明。如果你在作业过程中遇到问题,请先确保你已经完整阅读了有关部分,包括但不限于文档开头、结尾、前后几页、本站 FAQ 页面 等。
Details
文档:去找马冬梅
提问者1:马冬什么?
提问者2:什么冬梅啊?
提问者3:马什么梅啊?
历史上,课程交流群中相当比例的问题都产生于提问者没有认真阅读。如果你在群聊中看到了这样的问题,不妨用下图回复它:
错误信息
大多数情况下,你遇到问题时会看到一段描述错误原因的文字,即错误信息。错误的原因与可能解决方案往往就包含于错误信息中。因此,提问前可以先尝试自行解读。
解读错误信息需要一定的经验和技巧,如:
- 当程序出现多个报错的时候,关注第一个。通常,错误会导致一系列连锁反应。报错列表中可能有一大部分(甚至剩余全部)都是第一个错误的衍生。
TIP
示例待补充
- 不要担心看不懂大段英文和专业术语,尝试翻译它们。通常报错信息中不会包含复杂的语法结构,只要尝试阅读几次、熟悉常用单词,之后的解读就轻车熟路了。
Details
例如,下面这段代码在 Visual Studio 2022 的 Debug 模式下运行,会得到如图所示的报错:
#include <iostream>
using namespace std;
int main()
{
int a, b;
cin >> a;
if (a > 10) {
cin >> b;
}
// 错误:若 a > 10,则 b 未被初始化
cout << a + b;
return 0;
}
许多初学者第一次遇到这类弹窗报错时都会不知所措,但只要知道两个专有名词的正确翻译:variable 即“变量”、intialize 意为“初始化”,那程序的错误原因就很容易发现了。
- 如果发现中文报错非常“机翻”,可以尝试翻译回英文或直接查看英文原文。
英语是开发者世界的通用语言。几乎所有的程序报错信息原文都是由英文编写,再经由本地化部门/开源社区/机器翻译等渠道译成其他语言。因此,很多以中文显示的报错信息翻译质量很低,往往使人一头雾水。如果感到迷惑,可以尝试看看原文。
Details
例如,Visual Studio 2022 中,如果在函数内部创建了较大的数组,将会看到这样一条智能提示:
C6262: 函数使用堆叠的"84020"字节。请考虑将一些数据移动到堆。
这个报错让人不知所云,即使根据报错信息进行搜索,恐怕也很难明白什么叫“堆叠的字节”。实际上,这个提示的原文是“Function uses 84020 bytes of stack”——专有名词“栈”(stack)被机翻成了形容词。只要看到英文原文,即使不了解有关术语,也不难通过搜索引擎或者大语言模型找到相关资料。
清理环境再试一次
教你修电脑:重启解决 90% 的问题,重装解决 99% 的问题,重买解决 100% 的问题。
尽管计算机不是玄学、运行结果有逻辑可循,但由于系统的复杂性,初学者往往会做出一些错误操作而不自知,并遇到难以理解的结果。相当大一部分的奇怪错误都有一个行之有效的解决方案:完完整整从头再来一遍,确保每一步都是按照文档进行的。可以进行的尝试包括但不限于:清理(VS)解决方案、重启计算机、创建一个新项目等。
使用搜索引擎或大语言模型
WARNING
除了少部分特别标明的部分,完成高程作业不需要自行上网搜索或询问大语言模型。如果你在作业中遇到困难,可以先再次检查一下课件或者作业文档中的有关内容。盲目网上搜索可能会导致你浪费远超于此的时间,甚至遭受误导。关于在学习中使用大语言模型,请参见 对使用大语言模型辅助学习的建议。
当你提问时
有意义且明确地描述问题
简明扼要的问题描述有助于回答者快速理解你的问题,同时也有利于其他人搜索到你的提问。一个好的范式是“目标-差异”,在“目标”部分指出什么东西有问题,在“差异”部分描述与期望行为不一致的地方。不要只放一个截图,并加上一句“请问这是为什么?”或者“请问哪里错了?”。
提供尽可能详细的原始信息
Q: 为什么我的程序报错了?
A: 因为你没遵守 C++ 的语法规则。Q:为什么我这道题结果不对?
A: 因为你代码写错了。
不包含有用信息的问题,显然无法得到有用的回答。你不能预期解答者掐指一算就知道你错在哪里,或者给你列出十八种可能的错误原因并一一讲解。
一般来说,良好的提问应尽可能包含以下要素:
- 引发问题的操作/代码
- 预期得到什么结果
- 实际结果与预期的不符之处
- 报错信息
- 自己尝试过用什么方法解决
另外值得注意的是,在以上内容中,应尽可能提供原始信息而不要转述。许多初学者会由于不熟悉专有名词,在描述问题的时候完全走样;或者是意识不到界面的哪些部分可能蕴含关键信息。这种情况下,一张完整的截图能够为问答双方节省很多时间。
叙述完整的逻辑链
日常口语表达中,没有人会严格按照演绎推理逻辑说话。我们自觉或不自觉地在使用三段论时省略大前提,亦或是用命题的必然推论取代命题本身。但跳过这些逻辑步骤的前提是对话双方对此存在共识——不幸的是,初学者潜意识里认为的共识往往是错的。因此,每年高程答疑的时候都会发生这样的画面:
Q: 为什么这个程序会输出 XXX?
A: 这个程序本来就会输出 XXX,这是非常自然的语义,你觉得哪里不对了?
Q: 这个程序不是会执行 YYY 吗?
A: 对啊,有什么问题吗?
Q: YYY 的结果难道不是 ZZZ 吗?
A: 你为什么觉得 YYY 的结果是 ZZZ 呢?
Q: 因为 YYY 会导致 AAA、BBB 和 CCC 呀!
A:首先,YYY 不一定导致 AAA;其次,BBB 并不会得到 ZZZ 的结果;另外,你的 CCC 算错了。你可能需要看看课件的 III 与 JJJ 两节。
例子中这样通过助教的追问最终理清问题是幸运的情况。这类含混不清的问题最终的结果更有可能是问答双方驴唇不对马嘴地讲了一会儿之后,提问者放弃思考,决定把“这个程序会输出 XXX”当成结论记住,并顺带记住了自己对讨论过程的错误理解。因此,提问之前应当尽可能地完整而详细地梳理一遍自己的逻辑,确保自己没有省略、跳过思维过程,将其完整呈现,这样才能让回答者准确把握你哪里没有理解——当然,这样梳理一遍很有可能自己就解决掉了问题。
避免 X-Y 问题
有人想要解决问题 X,他觉得 Y 可能是解决问题 X 的方法,于是他问别人 Y 应该怎么做。
然而:
- Y 可能是一个很复杂或者很奇怪的问题,难以解答
- Y 实际上根本解决不了 X
- Y 并不是 X 最好的方案
典型案例
- 例子 1:获取字符串后三位字符?
Q: 我怎么用 Shell 取得一个字符串的后 3 位字符?
A1: 如果这个字符的变量是$foo
,你可以这样来echo ${foo:-3}
A2: 为什么你要取后 3 位?你想干什么?
Q: 其实我就想取文件的扩展名
A1: 我靠,原来你要干这事,那我的方法不对,文件的扩展名并不保证一定有 3 位啊。
A1: 如果你的文件必然有扩展名的话,你可以这样来:echo ${foo##*.}
- 例子2:获取文件大小?
Q: 问一下大家,我如何得到一个文件的大小
A1:size = ls -l $file | awk '{print $5}'
Q: 哦,要是这个文件名是个目录呢?
A2: 用du
吧
A3: 不好意思,你到底是要文件的大小还是目录的大小?你到底要干什么?
Q: 我想把一个目录下的每个文件的每个块(第一个块有 512 个字节)拿出来做 md5,并且计算他们的大小……
A1: 哦,你可以使用dd
吧。
A2:dd
不行吧。
A3: 你用 md5 来计算这些块的目的是什么?你究竟想干什么啊?
Q: 其实,我想写一个网盘,对于小文件就直接传输了,对于大文件我想分块做增量同步。
A2: 用rsync
啊,你妹!
通常,X-Y 问题会导致问答双方浪费大量的时间精力在错误的方向上。如果你的目的是解决 X,请直接问怎么解决 X。
不要拍屏
提问时,请使用 截屏 提供信息。除非问题发生的时机实在无法截屏(如计算机启动过程中),否则请勿使用手机对电脑屏幕拍照并发送,原因包括:
- 不美观
- 可能由于 摩尔纹 导致难以辨识
- 其他人可能需要歪着头才能看清内容
- 不利于其他人搜索类似问题
参考
本文的部分内容参考自 提问指南、 提问的智慧 与 X-Y Problem。