【信奥教练心得】AI靠"思考链"解题,学生做编程题也该如此——一道题改了四遍才过的四步

在这里插入图片描述

目录


📖 引言:AI的思考链,跟解题是一回事

最近AI很火。各种大模型层出不穷,大家都在讨论AI是怎么"思考"的。🤖

如果你了解过大模型的"思考链"(Chain of Thought),你会发现一件事:AI解题的方式,和我教学生做编程题的方式,几乎一模一样。 🔗

AI拿到一道题,会做什么?

  1. 先理解题目在说什么——读题
  2. 想好先做什么、后做什么——规划
  3. 一步步推导,中间会自我检查——调试
  4. 最后确认答案是否合理——收尾

这不就是我在课堂上反复教的四步吗? 🤔

其实这并不奇怪。2025年发表于《美国国家科学院院刊》(PNAS)的一项研究发现:AI的思考链越长,人类在相同题目上的反应时间也越长——两者高度相关。这说明AI的推理过程和人类的认知过程,底层是通的。 人类本来就有类似的思维链,不是AI发明的。🧠

数据可视化:AI思考链长度与人类反应时间的关联

在这里插入图片描述

上图展示了 de Varda et al. (2025) 的核心发现:AI推理模型生成的思考链越长(token数越多),人类在相同题目上的反应时间也越长,两者呈强正相关(r ≈ 0.93)。这表明 AI 的逐步推理过程与人类的认知过程在底层逻辑上是一致的。详见 PNAS原文

区别在于:AI通过海量数据训练,把这套流程强化成了内置习惯;而学生的思维链还在那里,只是没有被激活——面对一道编程题,他脑子里不是没有思考,而是思考没有结构:没有框架,没有步骤,没有自检。他不是不会写代码,是不知道该先想什么、再想什么。😵

那么问题来了:既然人类本来就有这套思维方式,为什么学生的思考链不能自动运转?

答案是:能。但它需要被开发出来。不是靠讲,而是靠练。💡

前六篇聊了教材、节奏、心态、自我验证。方法铺好了,该上战场了。🚀 这篇文章,我用一道真实的 GESP 三级题(B3843 密码合规),把学生做题的完整过程拆成四步:读题、规划、调试、收尾。每一步都有典型错误,每一步都有对应的方法。

但真正把他放到一道编程题面前的时候,我还是看到了大量问题——不是知识不够,而是每一步都在漏。💧

然后我给了他一道题:B3843 密码合规

完整题目洛谷 B3843

题目描述

网站注册需要有用户名和密码,编写程序以检查用户输入密码的有效性。合规的密码应满足以下要求:

  1. 只能由 a∼z 之间 26 个小写字母、A∼Z 之间 26 个大写字母、0∼9 之间 10 个数字以及 !@#$ 四个特殊字符构成。
  2. 密码最短长度:6 个字符,密码最大长度:12 个字符。
  3. 大写字母,小写字母和数字必须至少有其中两种,以及至少有四个特殊字符中的一个。

输入格式

输入一行不含空格的字符串。约定长度不超过 100。该字符串被英文逗号分隔为多段,作为多组被检测密码。

输出格式

输出若干行,每行输出一组合规的密码。输出顺序以输入先后为序,即先输入则先输出。

样例输入

seHJ12!@,sjdkffH$123,sdf!@&12HDHa!,123&^YUhg@!

样例输出

seHJ12!@
sjdkffH$123

样例解释:输入被英文逗号分为了四组:seHJ12!@sjdkffH$123sdf!@&12HDHa!123&^YUhg@!。其中 sdf!@&12HDHa! 长度超过 12 个字符,不合规;123&^YUhg@! 包含四个特殊字符之外的字符不合规。

看起来不难。但我观察了多个学生做这道题的过程,发现从第一步开始,就有人出错。😅


🔍 第一步:读题——“我听不懂你在说什么”

他开始认真读题了。每个字都看了一遍。

然后我让他复述:“这道题要你做什么?”

他开口了:“就是……检查密码……看它合不合规……”

我等着。他说不下去了。

这时候我告诉他一句话:“这道题我没写过,我听不懂你在说什么。”

这是假话。我写过。但这句话的效果是——他必须重新组织语言,把题意讲清楚。如果他回答不流畅、支支吾吾,基本上可以断定:题目没读懂,或者没进入状态。 🚩

在这里插入图片描述

他漏了什么?

我让他再读一遍第三条和第四条:

大写字母,小写字母和数字必须至少有其中两种,以及至少有四个特殊字符中的一个。

他读了,然后说:“就是……大写小写数字要有两种,然后特殊字符要有四个。”

停。这就是问题。 🎯

“至少有四个特殊字符中的一个”——他理解成了"要有四个特殊字符"。但原文的意思是:!@#$ 这四个特殊字符里,至少出现一个

这种错误太常见了。学生对"一般"“只有”“有时”"不超过"这些限定词不敏感。不是他们不认识这些字,是他们的语文阅读习惯没有覆盖到"精准理解"这个维度。

我观察过一个规律:语文阅读能力弱的学生,读题几乎必漏细节。 他们会把"至少两种"理解成"两种都要有",把"只能由"这个限制条件忽略,把"至少有一个"理解成"有一个"。

方法:假设数据,代入样例

我让他做一件事:不要急着写代码。先假设一个数据,用手算,看看你的理解对不对。

“你假设密码是 ab12!@。按照你理解的四条规则,逐条检查,这个密码合规吗?”

他开始算:

  • 条件1:a、b 是小写字母,1、2 是数字,!@ 是特殊字符——都在允许范围内。✓
  • 条件2:长度是6,在6到12之间。✓
  • 条件3:有小写字母和数字,是三种里的两种。✓
  • 条件4:!@ 里有特殊字符。✓

"合规。"他说。

“那 ab12 呢?”

他又算:

  • 条件1:都是允许的字符。✓
  • 条件2:长度4,不够。✗

“不合规,太短了。”

“那 ab12!@#$XYZ123456789 呢?”

他数了数:太长了,超过12。✗

到这一步,他才真正理解了题意。不是靠"读了一遍",而是靠假设数据、逐步代入、对照样例

这就是答对题目的第一步,也是最重要的一步。 读懂题,才能做对题。而"读懂"不是"看过一遍",是你能用自己的话讲清楚,还能用数据验证你的理解。 ✅


🛠️ 第二步:规划——从I/O开始拆

题读懂了。开始写代码。

我问:“你打算怎么写?先写什么?”

他哑口无言。😶

这很正常。“规划"对初学者来说是一个陌生的概念。他们面对一道题,脑子里闪过的是"怎么判断字符类型”“怎么分割字符串”,但没有一个整体的框架。🧩

我教他的方法很简单:先把输入和输出搞定。

这道题的输入是一行用逗号分隔的字符串,输出是每个合规密码各占一行。那第一步就是:

string s;
cin >> s;
// 怎么按逗号分割?

我会让他先写这一步。不要管条件判断,不要管字符检查,先把数据读进来,分割好,原样输出去。

#include <iostream>
#include <string>
using namespace std;

int main() {
    string s;
    cin >> s;
    // 先把逗号分隔的每段提取出来
    int start = 0;
    for (int i = 0; i <= s.length(); i++) {
        if (i == s.length() || s[i] == ',') {
            string pwd = s.substr(start, i - start);
            cout << pwd << endl;  // 先输出看看对不对
            start = i + 1;
        }
    }
    return 0;
}

为什么先做这一步? 因为如果输入输出都没搞对,后面所有逻辑都是白费。而且这一步能给他一个即时反馈——他运行一下,看到输出和样例的分割结果一样,心里就踏实了:“至少我没有在起跑线上摔倒。”

规划不是想清楚所有细节,而是知道先做什么、后做什么。对于I/O题,先解决输入输出,永远是第一步。 🏗️

我不是在讲规则,是在演示我的思考方式

这个过程,我反复想了很久——为什么"我写一遍给他看"比"我讲一遍给他听"管用?

后来我意识到,学生缺的不是知识,而是解题者的思维方式。他脑子里没有"拿到题先干什么"这个程序。我站在旁边讲规则,他听到的还是碎片。但我坐下来,从读输入开始,一行一行敲——他看到的就不再是知识点,而是一个完整的思考过程

教育学里管这叫"认知学徒制"(Cognitive Apprenticeship)。传统教学展示的是"正确答案",而认知学徒制展示的是"答案是怎么想出来的"——包括先做什么、后做什么、卡住了怎么办。师傅不是在黑板上写公式,而是坐到徒弟旁边,把手艺的每一个步骤都做给他看。

我让他先搞I/O,不是在"告诉"他方法,是在"演示"我的工作方式:遇到新题,第一步永远是先把数据读进来。 他下次独立做题时,脑子里会有一个参照。这个参照比任何口诀都管用,因为它是他亲眼见过的。👀


🔧 第三步:调试——分阶段验证,别靠心算

框架搭好了,他开始写字符检查逻辑。写完运行,一部分样例过了,一部分没过。

我看了几个学生的代码,发现一个共同问题:大体步骤都很清晰,但细节到处出错。 而且他们审查自己代码的方式是——盯着看,用心算。👀

心算不可靠。尤其是循环和条件判断,你以为这行没问题,实际上变量在这个时刻的值和你想的不一样。⚠️

方法:分阶段输出验证

我开始教他们在程序执行的不同阶段插入输出语句,亲眼看到数据的状态。

第一阶段:验证输入和存储。

string pwd = s.substr(start, i - start);
cout << "[DEBUG] 提取到的密码: " << pwd << " 长度: " << pwd.length() << endl;

有很多学生就是在存储阶段出的问题。特别是用数组存的——不是漏了第一个元素,就是多读了一个,或者数组越界。先输出一遍,确保数据存对了,再往下走。

第二阶段:验证字符分类。

int lower = 0, upper = 0, digit = 0, special = 0;
bool valid_char = true;
for (int j = 0; j < pwd.length(); j++) {
    char c = pwd[j];
    if (c >= 'a' && c <= 'z') lower++;
    else if (c >= 'A' && c <= 'Z') upper++;
    else if (c >= '0' && c <= '9') digit++;
    else if (c == '!' || c == '@' || c == '#' || c == '$') special++;
    else { valid_char = false; }
}
cout << "[DEBUG] lower=" << lower << " upper=" << upper
     << " digit=" << digit << " special=" << special
     << " valid=" << valid_char << endl;

这一步输出完,他自己就能看到问题。比如密码 seHJ12!@,他看到输出是 lower=1 upper=3 digit=2 special=2 valid=1,就知道字符分类没问题。

但如果输出是 lower=0 upper=0,他就会回头查:是不是判断条件写反了?是不是字符比较的写法有问题?

第三阶段:验证条件组合。

int type_count = 0;
if (lower > 0) type_count++;
if (upper > 0) type_count++;
if (digit > 0) type_count++;

cout << "[DEBUG] type_count=" << type_count
     << " special>0=" << (special > 0)
     << " len_ok=" << (pwd.length() >= 6 && pwd.length() <= 12) << endl;

bool ok = valid_char
       && (pwd.length() >= 6 && pwd.length() <= 12)
       && type_count >= 2
       && special > 0;

到这里,条件3的"至少两种"和条件4的"至少一个"都变成了具体的数字。他看到 type_count=2 就知道"两种"满足了,看到 special>0=1 就知道特殊字符有了。不需要心算,不需要猜。📊

为什么不用IDE调试器?

有人会问:为什么不直接用调试工具?Dev-C++ 也有调试功能。

原因是:考场上的 Dev-C++ 不一定可靠。 调试器有时候断点不触发、变量值不显示、程序直接闪退。我不希望学生在考场上第一次遇到这些问题时手足无措。

cout 输出调试法虽然"土",但它不依赖任何工具,只要有编译器就能用。这也是我自己写代码时常用的手段。🔧

实战:一道题,改了四版 📝

前面讲的都是方法论。下面是一个真实的案例——这道 B3843,一个学生从第一版到满分通过,改了四遍。每一遍错的都不一样,每一遍都是通过分阶段验证找到的。 🔄

第一版:最后一个密码丢了

他写完代码,提交,只过了一部分样例。代码如下:

#include <bits/stdc++.h>
using namespace std;
int main()
{
    string a;
    int small=0,big=0,number=0,fu=0,k=0;
    cin>>a;
    for(int i=0;i<a.size();i++){
        if(a[i]==','){
            int cnt=0;
            for(int j=k;j<i;j++){
                cnt++;
                if(a[j]>='0'&&a[j]<='9') number=1;
                if(a[j]>='a'&&a[j]<='z') small=1;
                if(a[j]>='A'&&a[j]<='Z') big=1;
                if(a[j]=='!'||a[j]=='@'||a[j]=='#'||a[j]=='$'){
                    fu=1;
                }
            }
            if(cnt>=6&&cnt<=12){
                if((small==1&&big==1||small==1&&number==1||big==1&&number==1||small==1&&big==1&&number==1)&&fu==1){
                    for(int j=k;j<i;j++){
                        cout<<a[j];
                    }
                    cout<<endl;
                }
            }
            k=i+1;
        }
    }
    return 0;
}

我没急着帮他找。我问他一个问题:“你的代码是在什么时候进入密码检查的?”

他看了一眼代码:“遇到逗号的时候。”

“那最后一个密码后面有逗号吗?”

他愣住了。

样例输入是 seHJ12!@,sjdkffH$123,sdf!@&12HDHa!,123&^YUhg@!——最后一个密码 123&^YUhg@! 后面没有逗号。他的循环只在遇到 , 时才触发检查,所以最后一个密码永远不会被处理。😱

这就是"分阶段验证"的第一阶段——验证输入和存储。你以为数据都读进来了,其实漏了最后一个。

第二版:自己想到补一个逗号

他理解了问题,自己想了一个办法:在输入字符串末尾补一个逗号。 a+=',';——这样最后一个密码也会被逗号触发检查。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    string a;
    int small=0,big=0,number=0,fu=0,k=0;
    cin>>a;
    a+=',';  // 自己补一个逗号
    for(int i=0;i<a.size();i++){
        if(a[i]==','){
            int cnt=0;
            for(int j=k;j<i;j++){
                cnt++;
                if(a[j]>='0'&&a[j]<='9') number=1;
                if(a[j]>='a'&&a[j]<='z') small=1;
                if(a[j]>='A'&&a[j]<='Z') big=1;
                if(a[j]=='!'||a[j]=='@'||a[j]=='#'||a[j]=='$'){
                    fu=1;
                }
            }
            if(cnt>=6&&cnt<=12){
                if((small==1&&big==1||small==1&&number==1||big==1&&number==1||small==1&&big==1&&number==1)&&fu==1){
                    for(int j=k;j<i;j++){
                        cout<<a[j];
                    }
                    cout<<endl;
                }
            }
            k=i+1;
        }
    }
    return 0;
}

这个思路很好。但提交之后,还是不过。

逗号处理好了,问题在更深层。我开始带他看第二阶段——验证字符分类逻辑

“你看你处理特殊字符的地方。如果第一个字符是合法的特殊字符,fu 变成 1,没问题。但如果下一个字符是非法字符呢?”

他看了看代码。fu 还是 1。

“如果再下一个又是合法的特殊字符呢?fu 还是 1。但这个密码里出现了非法字符,应该直接判为不合规。”

他想了想,说:“对,遇到非法字符的时候,我应该直接 break,不用再往下看了。”

第三版:处理非法字符

他加上了 else { fu=0; break; }——遇到不在四种合法范围内的字符,直接标记为非法,跳出循环。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    string a;
    int small=0,big=0,number=0,fu=0,k=0;
    cin>>a;
    a+=',';
    for(int i=0;i<a.size();i++){
        if(a[i]==','){
            int cnt=0;
            for(int j=k;j<i;j++){
                cnt++;
                if(a[j]>='0'&&a[j]<='9') number=1;
                else if(a[j]>='a'&&a[j]<='z') small=1;
                else if(a[j]>='A'&&a[j]<='Z') big=1;
                else if(a[j]=='!'||a[j]=='@'||a[j]=='#'||a[j]=='$'){
                    fu=1;
                }
                else{
                    fu=0;
                    break;
                }
            }
            if(cnt>=6&&cnt<=12){
                if((small==1&&big==1||small==1&&number==1||big==1&&number==1||small==1&&big==1&&number==1)&&fu==1){
                    for(int j=k;j<i;j++){
                        cout<<a[j];
                    }
                    cout<<endl;
                }
            }
            k=i+1;
        }
    }
    return 0;
}

注意他把原来的 if 链改成了 else if 链——这也是他理解了"一个字符只能属于一种类型"之后自己改的。

提交。还是错。 😤

第四版:标记变量没有重置

到这一步,逗号处理好了,字符分类也对了,条件判断也改对了。他开始有点沮丧——“还能有什么问题?” 😩

我让他停下来,别盯着代码看了。我说:“你把注意力放到变量上。每检查完一个密码,你的 smallbignumberfu 这些变量,它们的值是什么?”

他想了想。

“第一个密码如果有小写字母,small 变成 1。检查第二个密码的时候,small 还是 1。它没有归零。

这就是问题。 这些标记变量在 main 函数开头就声明了,在整个循环过程中一直累积。第一个密码的标记"污染"了第二个密码的判断。

他找到办法了:把变量声明移到 if(a[i]==',') 里面。 每进入一个新的密码检查,变量全部重新初始化为 0。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    string a;
    int small=0,big=0,number=0,fu=0,k=0;
    cin>>a;
    a+=',';
    for(int i=0;i<a.size();i++){
        if(a[i]==','){
            int cnt=0,small=0,big=0,number=0,fu=0;  // 每条密码重置标记
            for(int j=k;j<i;j++){
                cnt++;
                if(a[j]>='0'&&a[j]<='9') number=1;
                else if(a[j]>='a'&&a[j]<='z') small=1;
                else if(a[j]>='A'&&a[j]<='Z') big=1;
                else if(a[j]=='!'||a[j]=='@'||a[j]=='#'||a[j]=='$'){
                    fu=1;
                }
                else{
                    fu=0;
                    break;
                }
            }
            if(cnt>=6&&cnt<=12){
                if((small==1&&big==1||small==1&&number==1||big==1&&number==1||small==1&&big==1&&number==1)&&fu==1){
                    for(int j=k;j<i;j++){
                        cout<<a[j];
                    }
                    cout<<endl;
                }
            }
            k=i+1;
        }
    }
    return 0;
}

提交。满分通过。 🎉✅

在这里插入图片描述

四版代码,四个不同的bug,每一次都是在分阶段验证中发现的。不是靠运气,不是靠"再看看",而是一步步缩小范围:先验证输入完整性,再验证字符分类,最后验证变量状态。每一步都在排除一个可能性,直到找到真正的问题。 🔍

“老师,你也会写错吗?”

有一次,学生调试半天找不到bug,一脸挫败地坐在那里。我坐到他旁边,说:“我来试试看。”

我打开他的代码,在关键位置插入 cout,一边运行一边念叨:“这里应该是3,为什么是0?等一下,循环条件……哦,写错了。”

学生看着我操作,突然问了一句让我印象深刻的话:“老师,你也会写错吗?” 😮

在这里插入图片描述

我说:“当然。我写代码的时候,第一版几乎从来不是对的。区别只是我知道出错之后怎么找。”

这句话不是随口说的。后来我反复琢磨这个场景,发现它其实同时在做三件事——而这正是我想分享给其他教练的。

第一件事:让他看到真实的解题过程。

很多学生以为老师拿到题就能写出正确答案。他们没见过老师卡住、犯蠢、走弯路的样子。当我坐在他旁边,和他面对同一个bug,他看到的是——老师遇到问题时的第一反应也是"先输出看看",和我教他的方法一模一样。

这不是"教",是"演示"。和前面讲规划时一样,我在做的是认知学徒制:不是告诉他"你应该这样调试",而是让他亲眼看到"我也这样调试"。

第二件事:让他知道出错不丢人。

当我说"我第一版也几乎不对"的时候,学生明显松了一口气。不是因为他觉得"老师也不过如此",而是因为他意识到——出错是正常的,连老师都出错。 那他出错就不是因为"我笨",而是因为"这件事本来就难"。

教育学里管这叫"教师脆弱性"(Teacher Vulnerability)。不是示弱,不是降低老师的威信——恰恰相反,它让学生看到:经验丰富的人也会遇到不确定,也会走弯路,也会需要一步步验证。当老师不再是"永远正确"的形象,学生面对自己的错误时就不再那么焦虑,而是把精力放在"怎么解决"上。

第三件事,也是最重要的:让他学到"出错之后怎么办"。

我演示的不是"我不会出错",而是"我出错之后怎么处理"——停下来,插入验证点,观察数据,定位问题。这套流程,才是我想教给他的。 🎓

后来有家长问我:“你的教学跟其他机构有什么不一样?”

我说:“我不只是教他写对的代码。我教他写错之后怎么办。” 💪

拍照留bug——错误是最好的教学资源

在这个过程中,学生因为各种小细节出错的代码,我会拍照保留下来。不是当反面教材,而是拿给其他同学看

“这段代码有一个bug,你找找在哪。”

其他同学读代码、找bug,这个过程中他们的语法理解也在提升。尤其是面对 if-else 的嵌套、字符类型与数字搞混、&&|| 的优先级这类问题——看别人犯的错,比自己犯的错更容易记住。 📸

这不是我凭感觉说的。教育学里有一个研究方向叫"有效失败"(Productive Failure)。研究者发现了一个反直觉的结论:让学生先经历挣扎和失败,再进行讲解,比直接教正确方法的效果更好。

原因很简单——当你直接给学生正确答案,他只是在"识别"这个答案。但当你在别人的错误代码里寻找bug时,你被迫去理解"为什么这样写会错"——你必须追溯代码的执行过程,分析变量在每个步骤的值,找到逻辑断裂的那一步。这个深度思考的过程,比看正确答案学得更深。

所以那些拍照保留的bug代码,不是"错误记录",而是教学资源。一道题的bug,可以给好几个学生练手。犯错的那个学生也不丢脸——他的错误帮其他同学避免了同样的坑。这和前面我坐到他旁边说"我第一版也几乎不对"是同一件事:错误不需要被掩盖,它需要被利用。

对家长来说,这也是一个信号:我的教学不是"讲对答案就结束"。每一个错误,我都会让它发挥最大价值——让犯错的学生理解为什么错,让其他学生从别人的错误中学习。错误在我这里不是污点,是教材。📖


🎯 第四步:收尾——“你知道为什么错了吗?”

经过不断调试,代码终于过了。全部样例正确。

他松了一口气,准备关掉编辑器。

我马上制止他。

“等一下。你知道你之前为什么错了吗?”

他愣了一下,开始回忆:“第一个问题,最后一个密码后面没有逗号,我的循环只遇到逗号才触发检查,所以漏了最后一个。后来我自己加了一个逗号。第二个问题,遇到非法字符的时候我没有处理,fu 标记一直保持 1,应该直接 break。第三个问题,标记变量没有在每条密码之间重置,第一条密码的标记污染了第二条……”

这一步很重要。 不只是"知道错了",而是让他在心里确认:今天我又懂多了一点点。 🌟

为什么"说出来"这一步这么关键?因为他在做一件不自然的事——把自己从做题者切换成观察者,回头审视自己刚才的思考过程。

这不是本能。大部分学生做完题就走,不是不努力,而是他们没有"停下来看自己怎么想的"这个习惯。能做对题是一回事,能说清楚"我刚才哪里想错了、为什么那么想、正确应该怎么想"是另一回事。后者要求的能力,教育学里叫"元认知"——对自身思考过程的觉察和监控。

这种能力不会自己长出来,需要被反复训练。每次做对题后逼他说一遍,就是在练这块肌肉。说得多了,他做题时就会开始自己问自己:“等等,我这样想对吗?”——不需要我在旁边追问了。🧠

很多学生考完试出来,只知道自己"考得不好",但不知道哪里不好。这种模糊的焦虑会一直积压,到最后变成"我就是学不会"。

但如果你让他在每次做对题之后,说出来自己错在哪、为什么错、怎么改的——他就把"模糊的失败感"变成了"具体的收获"。每一次做对题,都不是"终于过了",而是"我又排除了一个盲区"。

这就是减轻考前焦虑的最好方式:不是告诉他"你可以的",而是让他自己说出来"我今天确实进步了"。 🌱


🌉 结语:四步是一个闭环

回顾整个过程:

  1. 读题:复述题意,假设数据代入样例验证理解。
  2. 规划:先解决I/O,搭好骨架再填血肉。
  3. 调试:分阶段输出变量,不靠心算,逐步定位。
  4. 收尾:回顾错误原因,把失败感变成收获感。

这四步本质上是一个自我验证的闭环——和第六篇讲的是同一件事,只不过这次是在真实的编程题里落地。

回头看看开头说的AI"思考链"——读题、规划、调试、收尾。AI靠海量数据训练出了这套流程,而学生的这套流程,靠的是一道题一道题地开发出来。AI的思考链是内置的,人类的那条链本来就在——只是需要被激活、被结构化、被开发出来。开发好之后,它一样能自动运转。🔗

概念图:AI思考链 vs 学生解题流程对照

在这里插入图片描述

上图对比了 AI 的思考链(上方,流程完整、箭头连续)与学生的解题流程(下方,箭头断裂处标记为红色感叹号,表示思考链需要教练介入和激活)。两者四步完全对应,区别在于:AI 靠训练强化为内置习惯,学生的流程需要被开发。

读题时的"假设数据代入"是验证理解。规划时的"先搞I/O"是验证框架。调试时的"分阶段输出"是验证代码。收尾时的"说出为什么错"是验证认知。

每一步都在做同一件事:不等别人告诉你对不对,你自己先确认一遍。

回头看看这四步里藏着的东西:规划时我坐下来演示自己的思考方式(认知学徒制),调试时我坦承自己也会犯错(教师脆弱性),学生的bug被保留下来给其他人练手(有效失败),最后逼他说清楚自己错在哪(元认知训练)。

这些词听起来像是教育学课本里的术语,但它们描述的不过是一件简单的事:我不只是在教他做题,我在教他怎么面对一道不会做的题。 做题的方法可以背,但面对困难时的心态和习惯,只能这样一点一点磨出来。 🔨

写给同行教练:这些方法不一定只在信奥教学里适用。任何一个需要学生独立解决问题的场景——数学、物理、甚至写作文——都有"读不懂题"“不知道先干什么”“改不对就放弃”"做对了不总结"这四步。你试试把这四步套进自己的学科,看看哪些能直接用,哪些需要改。

写给家长:如果你的孩子在学编程,你可以观察他的教练在做什么——是只讲正确答案,还是也会带着他拆解错误?是站在旁边指指点点,还是会坐下来和他一起面对一个bug?差别就在这里。好的教学不是让孩子做对更多题,是让他在做错的时候知道该怎么办。 🏠

而当他习惯了这个闭环,他就不再怕做错题。因为他知道,错误不是终点,而是闭环里的一环——找到它,理解它,排除它,然后确确实实地往前走一步。 🚶➡️


📚 专栏回顾

  • 第一篇:《从语法讲解到思维锻造:我的信奥教学实战总结》——教材设计、认知难点、表格追踪法
  • 第二篇:《信息学学习之乐:思维、工具与自我对话》——计算思维、数学英语、自检习惯
  • 第三篇:《从"无从下手"到"拿下大题"——一套完整的解题教学闭环》——五步闭环法、警惕答案记忆、学会提问、可视化演算
  • 第四篇:《比方法更重要的,是找到适合他的节奏》——学生分类、年龄段差异、认知架构、可持续精力管理
  • 第五篇:《让学生把"思考的路"走完,比走对更重要》——完成感、温故知新、先走完再优化
  • 第六篇:《"懂了"是最危险的信号》——能力错觉、双重检测、脚手架微操、证明错误法、自我验证闭环
  • 第七篇(本文):《【信奥教练心得】AI靠"思考链"解题,学生做编程题也该如此——一道题改了四遍才过的四步》——读题复述、I/O优先、分阶段调试、错误回顾、认知学徒制、教师脆弱性、有效失败、元认知训练

如果这篇文章对你有帮助,欢迎点赞、收藏、转发。 👍

你在做编程题时,最容易在哪一步出错?评论区聊聊。 💬


延伸阅读

本文涉及的教学方法与教育学概念,以下资料可供深入参考:

  • AI思考链与人类认知的关联:de Varda et al. (2025) 发表于 PNAS,发现AI推理模型的思考链长度能精准预测人类在相同题目上的反应时间,证明AI的推理过程与人类认知过程高度一致。详见 The cost of thinking is similar between large reasoning models and humans
  • 认知学徒制(Cognitive Apprenticeship):Collins, Brown & Newman (1989) 提出,核心是"让专家的思考过程可见"——师傅不是在黑板上写公式,而是坐到徒弟旁边,把隐性的思维步骤一步步演示出来。详见 Cognitive Apprenticeship: Making Thinking Visible
  • 有效失败(Productive Failure):Kapur (2008, 2010, 2012) 发现,让学生先经历挣扎和失败再进行讲解,比直接教正确方法的效果更好。失败的过程会暴露认知盲区,促进深度理解。详见 Productive Failure
  • 教师脆弱性(Teacher Vulnerability):Kelchtermans (2009) 指出,教师的脆弱性不是病理性的,而是教学身份的常态组成部分。当教师愿意展示自己的不确定和错误,学生的试错焦虑会显著降低。详见 Teacher Vulnerability and Teachability
  • 元认知(Metacognition):Flavell (1979) 首次系统提出元认知模型——对自身认知过程的觉察和监控。元认知能力不会自然形成,需要通过反复的"反思-表达"训练来发展。详见 Metacognition and Cognitive Monitoring
  • GESP B3843 密码合规:本文使用的教学例题来自 洛谷题库

关于作者

一名在信息学奥赛一线教学多年的教练。专注于C++编程教学、编程思维训练与教学策略研究。专栏「信奥教学手记」持续更新中,分享真实课堂案例与教学方法沉淀。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐