说个现象:每次新班开课,第一次作业交上来,往往是最能看出人的潜力的。程序跑不跑得通是一回事,代码排版乱不乱、文件有没有按要求命名、输入输出有没有边界意识,这些都是另一回事。很多人第一次作业做不好,不是不会写代码,而是根本没把它当成一个“交付物”来做。
如果你正处在刚开始学编程、第一次要交作业的阶段,或者你是帮人改作业、带新人的角色,那么这篇东西值得看完。我第一次写代码作业时也栽过跟头,后来带过不少实习生和新生,见过太多“第一次作业”翻车现场,也总结出了一套从拆题到交付的流程。这套流程不限于某门课、某种语言,而是把任何一门编程课的第一次作业当成一个迷你项目来做。
1. 第一次作业,真正要练的是“从问题到交付”的闭环
1.1 为什么很多人栽在看似不难的第一次作业
我见过太多这样的场景:第一次作业题目只有几句话,比如“输入两个整数,输出它们的和”。看起来太简单了,于是新手很容易有两种反应——要么觉得没意思,随手交一份代码然后去玩;要么到处找参考代码,复制粘贴改个变量名就交了。
等到作业发下来,问题就暴露了:程序在老师机器上跑不起来,中文注释变成了乱码,输入格式和题目要求不一致,文件名是“新建文档 (3).py”,没有按学号命名,甚至有人把代码写在在线编辑器里交了个链接,最后链接需要登录才能看。
这些问题都不是算法难,而是没有养成做事的闭环习惯。第一次作业真正重要的,不是那道题本身有多高的技术含量,而是你第一次经历一个完整的流程:读题、拆解需求、动手实现、运行验证、按规范交付。这个流程放大了看,跟工作里做一个需求本质一样。代码能不能跑只是第一步,让一个陌生人能顺畅地运行你的代码、检查你的思路,那才是完整交付。
1.2 把第一次作业当成一个“迷你项目”来做
很多自学的朋友会问:我又不是计算机专业,随便做做就好了吧。我的答案是:想长期学下去,随便的态度最要不得。第一次作业成本最低,错误的影响也最小,不趁这个时候把流程跑顺,等到期末项目或实际工作时再养习惯,代价就大了。
我在实际辅导里一般会给一个最低限度的项目四步法:需求分析、环境准备、编码实现、测试与交付。哪怕作业只有一句话,也走四步。第一次作业时你可能觉得小题大做,但重复几次你就知道,真正消耗时间的地方往往不在写代码,而是在“我以为题目是这样的”和“题目实际是这样的”之间来回拉扯,以及在运行环境上浪费的时间。
把第一次作业当成迷你项目还有一个好处:你会下意识记录自己遇到的问题和解决过程。别小看这个过程,很多课程后期会要求写实验报告、做项目文档,如果你从一开始就在实战中练习“怎么把思路讲清楚”,后面根本不用临时抱佛脚。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手之前,先花20分钟做需求拆解和环境准备
2.1 一道简单作业题,也能拆出不少边界条件
先说说拿到题目后最容易犯的错:看一眼题目觉得自己懂了,立刻打开编辑器开写。写了一半发现,题目里的“输入若干个整数”到底是几个?“输出结果保留几位小数?”“第二大的数如果重复算不算?”这些细节全都没想清楚,然后写着写着又开始猜。
我第一次正经写编程作业时,题目是“输入N个整数,输出最大值、最小值和平均值”。我当时觉得简单,写了半小时,交上去才发现平均值题目要求四舍五入保留两位小数,我没看到,直接输出了一堆小数。被扣分后我才学乖了,拿到题目后先花二十分钟做拆解。
比如同样那道题,我会先列出这样一个确认清单:
- 输入方式:是回车换行还是空格分隔,N和N个整数怎么输入?
- 输出格式:每行一个结果,还是用文字说明,平均值保留几位小数?
- 边界情况:N等于1时怎么办?N等于0时要不要提示?
- 数值范围:会不会有负数?会不会有超过int范围的数?
- 特殊要求:是否不允许用内置max/min?是否要求自己写循环?
有些细节题目没写,你可以按自己合理的理解去做,但在代码注释或报告里写清楚你的假设。比如“题目没有说明N=0时怎么处理,我这里选择直接提示并退出”。这种做法在评审人眼里会非常加分,因为它说明你考虑的不仅是正常路径,还有一种“程序员脑子”。
2.2 环境准备:减少“在我电脑上能跑”的低级事故
第一次作业里还有一个高频翻车点:环境不一致。你自己电脑上用的是Python 3.12,老师机器上可能还是3.8;你用了某个第三方库,但作业说明里没让装,到了检查现场库没装,程序就挂了。
所以做第一次作业前,先确认三件事:用什么语言什么版本,用什么方式来运行,有没有额外依赖。如果课程教的是C语言,就用课程要求的编译器把代码编译一遍再提交,而不是只在网页编辑器里跑通就算了。如果是Python,建议至少在自己电脑上配好本地环境,能用命令行跑通一次,而不是永远依赖在线环境。在线环境不是不行,但你至少要知道代码在本地怎么运行,这对后面上难度了绝对有帮助。
目录结构上,我给自己的建议是单独建一个文件夹,比如assignment1,里面放主代码文件和一个README.txt。README里写两样东西:程序怎么运行、代码有什么已知的假设或注意事项。别觉得小题大做,这其实就是迷你项目的雏形。以后真要开发工程,你会发现项目里最缺的就是这种说明,谁接手谁看得懂。
3. 编码阶段的三个高效习惯:结构化、小步快跑、及时记录
3.1 先把骨架搭起来,不追求一次写对
很多初学者写代码有一个通病:希望自己一上来就写出完美版本,脑子里有个大致思路,然后闷头一口气写一百行,写完一运行,报错一大堆,整个人直接崩溃。
正确的路径是先搭骨架,再填血肉。所谓骨架,就是先把主流程、函数、关键的输入输出写出来,里面具体逻辑可以先不实现,甚至先用pass占位,或者用print输出一个临时结果。这样你每写一部分,代码都是能运行的,即使功能没全,但你已经能验证这一部分是对的。
假设作业是“输入一个列表,找出最大值和第二大值”,我可能会先写这样一个结构出来:
python复制def read_input():
# 读取列表
pass
def find_two_largest(nums):
# 返回最大值和第二大值
pass
def print_result(result):
# 输出结果
pass
def main():
nums = read_input()
result = find_two_largest(nums)
print_result(result)
if __name__ == "__main__":
main()
这段代码现在什么都干不了,但结构已经摆在这儿了。下一步你就能分别填充read_input、find_two_largest和print_result,每填完一个单独测一个,而不是最后全部一起测。工程里管这个叫模块化,哪怕是很小的作业,也应该用函数把程序拆开。我第一次写这种带函数的代码时,感觉程序突然变“清楚”了,因为每个函数解决的问题都很小,肉眼就能检查。
3.2 用“最小可运行版本”降低挫败感
“小步快跑”是我带新人时强调最多的一件事。具体到第一次作业,我建议你把任务拆成三轮。
第一轮只要做到:程序能接收输入,并且能把输入原样打印出来。做了这件事,你确认的是“输入通道通了”。第二轮再去做核心计算,比如求最大值、最小值、平均值。第三轮再处理格式化输出、异常情况。每轮代码都能运行,你只有很小的概率会卡住太久。很多人写着写着就不想写了,多半不是因为题目难,而是因为一次性塞了太多东西,出错后找不到是哪一步的问题。
用print输出中间值,也是这个思路的一部分。当你发现结果不对,不要光盯着代码“冥想”,不如在关键位置加上print,把中间变量的值打出来看看。比如求平均值算错了,先看看你拿到的总数sum对不对、个数counter对不对,而不是盯着最后的除法那行发呆。print方法看起来很土,但它永远是定位问题最直接的手段之一,哪怕很多年后用上了调试器,输出日志也是第一排查手段。
3.3 卡住了?先给自己设一个等待时间
第一次写作业,最可怕的不是不会,而是不会了还死磕。我曾经遇到一个学生,一道题卡了整整一下午,就是因为循环里少缩进了一个return,他以为自己的逻辑大面积错了,把代码推倒重写了两遍,最后才发现只是缩进问题。
我后来给自己定了一个规矩:一个细节问题,自我尝试加查资料,最多半小时卡住不动,就先把问题记录下来,去做个别的任务或者干脆休息一下。过一会儿回来,思路可能就通了。如果还是不行,那就准备求助,不要不好意思。
求助的正确方式,不是把整个代码截图发过去说“帮我看一下为什么不对”。而是自己先整理出三样东西:输入是什么,期望输出是什么,实际输出是什么,外加你做了哪些尝试。这样别人才能在几分钟内定位问题,你自己在整理的过程中也经常会发现问题。这个习惯在第一次作业里就开始练,往后进项目组协作时会少挨很多骂。
4. 调试不是碰运气,自测用例才是真正的加分项
4.1 新手最容易翻车的几类错误
第一次作业常见的报错,来回就那么几类。我常和学生说,看到报错别怕,先读懂它在骂你什么。下面是过去几年里我汇总的典型问题:
| 错误类型 | 典型报错/现象 | 产生原因 | 处理思路 |
|---|---|---|---|
| 变量名拼写不一致 | NameError: name 'nums' is not defined |
前面写nums,后面写成num | 检查定义与使用处拼写 |
| 类型不匹配 | TypeError: can only concatenate str |
字符串和数字直接拼接 | 先int()转换再运算 |
| 忘记转整数 | 数值运算结果像字符串拼接 | input()返回的是字符串 | 输入后及时做类型转换 |
| 循环差一 | 结果少一项或多一项 | range用错边界 | 手推i=0和最后一轮 |
| 列表越界 | IndexError: list index out of range |
访问了不存在的下标 | 打印列表长度,检查下标 |
| 除数为零 | ZeroDivisionError |
没考虑空列表或除数为0 | 先判断长度或分母 |
| 函数内修改全局变量未声明 | 变量值不变 | 作用域理解不清晰 | 用返回值代替全局修改 |
上表里几乎所有问题,只要能定位到具体的行号,解决起来都很快。但初学者看到一堆红色的报错,容易整段代码都怀疑,最后越改越乱。正确做法是先看报错信息最后一行,它通常会告诉你文件名、行数和错误类型。就按那一个点去修,修完再运行一次。一次只处理一个问题,别想一口气把所有错都找完。
4.2 怎么设计能让代码“原形毕露”的测试用例
在很多同学看来,题目里给了一个例子,我跑出一样的结果,就万事大吉了。这远远不够。举个例子,写一个“找出最大值和第二大值”的函数,光测试一遍[1, 5, 3],很多bug根本暴露不出来。比如程序里如果有 if 的判断条件写成了找第三大,单一样例可能输出恰巧对,换一组数就错了。
我一般建议按三类去造测试样本:正常输入、边界输入、异常输入。正常输入就是常规数据,用来保证基本功能;边界输入包括只有一个元素、所有元素相同、最大值出现在开头或结尾、输入全部是负数等;异常输入包括空列表、输入格式不对等。
用“求最大值和第二大值”这个例子,我至少会测这组数据:
| 用例 | 期望结果 | 用意 |
|---|---|---|
[3, 1, 4, 1, 5, 9, 2, 6] |
最大值9,第二大值6 | 常规乱序 |
[5, 4] |
最大值5,第二大值4 | 最小长度之一 |
[5] |
最大值5,没有第二大值 | 单元素边界 |
[-1, -3, -2] |
最大值-1,第二大值-2 | 全负数,检查初始值 |
[5, 5, 4] |
最大值5,第二大值4 | 重复最大值时第二大定义 |
[7, 7, 7] |
最大值7,没有第二大值 | 全部相同 |
[] |
没有最大值 | 空列表 |
这套用例能让绝大多数“看着没问题”的代码现出原形。尤其是负数测试,很多初学者喜欢把比较基准初始化为0,遇到全负数时就会输出0,这种错误只靠题目给的样例是测不出来的。你能主动设计这些用例,说明你开始用工程思维想问题了,这对第一次作业来说已经是超纲的亮点。
4.3 报错信息阅读和定位思路
具体调试时,我建议按这套流程来。第一步,看完整报错信息,不要只看大概。报错信息里有文件名、行号、错误类型,照着行号翻源代码,比自己从头到尾逐行读要快得多。第二步,把可疑区域前后的关键变量print出来。比如IndexError,你怀疑某个列表越界,就打印列表的长度和当前下标,看看下标是不是等于长度了。第三步,如果还是找不到,用注释法排除。把前半段代码注释掉,跑一次,如果不再报错,说明问题出在前半段;再把后半段注释掉,排查范围就能缩小一半,反复几次就能定位。这是最笨但最可靠的二分定位法。
另外给一条很实用的建议:变量名别乱起。a、b、c这种名字,报错跳转到那一行时你根本不知道它代表什么。用num, total, max_val, temp这样的名字,至少你扫一眼能反应过来。这不是老生常谈,而是排查效率的问题,名字取得好,调试时省一半时间。
5. 一个真实案例复盘:查找最大值和第二大值
5.1 题目说明和第一版常见写法
下面我拿一个很典型的第一次作业题,完整演示一下从拆题到测试怎么走。题目如下:
编写一个函数,输入一个整数列表nums,返回其中的最大值和第二大值。如果不存在第二大值,返回None。要求不使用排序或内置max/min,自行用循环实现。
很多学生的第一反应是用排序,代码可能长这样:
python复制def find_two_largest(nums):
nums.sort()
return nums[-1], nums[-2]
这个版本有几个问题。第一,题目明确说不让用排序;第二,sort会改变原列表,对后续操作有影响;第三,列表长度小于2时会直接IndexError;第四,如果“第二大值”的定义是小于最大值的最大值,那么列表[5, 5, 4]排序后取倒数第二个是5,并不符合大家对“第二大”的直觉。就算题目不要求严格小于最大值,至少你要知道还有这种语义差异,并在作业里写清楚自己的处理方式。
5.2 边界条件与一个隐蔽bug
我让一个新手朋友先试着自己写,他写了个版本,自测[1, 5, 3]没问题,但测到[-1, -2, -3]就出错了。他的代码是这样的:
python复制def find_two_largest(nums):
max_val = 0
second_val = 0
for num in nums:
if num > max_val:
second_val = max_val
max_val = num
elif num > second_val:
second_val = num
return max_val, second_val
如果他测试的样例里有正数,输出是对的。但遇到全负数时,max_val从0开始,0比所有负值都大,于是逻辑整体失效。这个bug非常经典,根因在于给“最大值”选了一个错误的初始基准。正确做法是初始值设为None,或直接用列表第一个元素作为基准,再去遍历后面的元素。另外这个版本在全相同值[7,7,7]时会返回第二大为7,这也跟题意有关,最好通过比较条件过滤掉重复值。
5.3 改进后的实现与完整测试记录
改进后的代码我写出来供参考:
python复制def find_two_largest(nums):
if not nums:
return None, None
if len(nums) == 1:
return nums[0], None
first = None
second = None
for num in nums:
if first is None or num > first:
second = first
first = num
elif num < first and (second is None or num > second):
second = num
return first, second
这段代码用None做初始值,所以负数也能正确处理。当出现新的最大值时,旧的最大值自动降级为第二大值;当num介于最大值和第二大值之间时,只更新第二大值。我跑过的测试记录如下:
text复制find_two_largest([3, 1, 4, 1, 5, 9, 2, 6]) => (9, 6)
find_two_largest([5, 4]) => (5, 4)
find_two_largest([5]) => (5, None)
find_two_largest([-1, -3, -2]) => (-1, -2)
find_two_largest([5, 5, 4]) => (5, 4)
find_two_largest([7, 7, 7]) => (7, None)
find_two_largest([]) => (None, None)
这道题从功能上很简单,但如果能把这些异常情形都处理干净,你在这个函数里体现的思考深度已经远超作业需要。我看到这种代码时,打分绝对不会低,因为我知道这个学生是真的在写代码,不是在完成任务。
6. 提交之前与提交之后:交付和复盘决定你收获多少
6.1 提交前的五分钟检查清单
写完了不等于能交了。第一次交作业的人,建议提交前按下面这个清单过一遍,只需要几分钟,却能避免大部分低分甚至零分事故。
| 检查项 | 具体动作 |
|---|---|
| 运行验证 | 打开终端/命令行,在干净环境里完整运行一次代码 |
| 文件命名 | 按课程要求命名,避免“新建文档”式文件名 |
| 文件整洁 | 删除无用变量、无意义注释、调试用残留print |
| 输入输出格式 | 对照题目示例,逐字符检查空格和换行是否一致 |
| 依赖说明 | 如果用了第三方库,在README或备注里写清楚 |
| 中文显示 | 检查源代码编码声明,避免老师机器上乱码 |
| 函数完整性 | 确认每个函数有返回值或输出,主流程能被调用 |
这里我想特别展开讲一下“在命令行里跑一次”这件事。很多学生习惯在IDE里点一下运行按钮,看到结果没问题就交了。但是老师收作业后在命令行里一跑,就发现代码有问题,比如文件里写死了某个绝对路径,或者读取的文件在IDE工作目录下能找到但换到别处后找不到。你提交前自己用命令行试一次,能提前暴露不少环境相关的问题,这个习惯越早养越好。
6.2 参考别人的代码可以,但要知道红线在哪
第一次作业阶段还有一个绕不开的话题:参考和抄代码的边界。编程初学者很难完全凭自己写出所有东西,看别人的代码非常正常。但“参考”和“提交”是完全不同的动作。
我建议的合理参考方式是:先自己写出一个能运行的基础版本,然后在遇到某个具体细节卡住时,去查针对性的资料或看别人对同一函数的实现,理解思路后把浏览器关掉,用自己的话重新实现一遍。如果你大段引用了别人的代码,按学术诚信的要求应该注明来源。这不仅是规则问题,也是对自己负责。抄代码可能省下眼前一小时,但下一次作业难度加倍时,连基础都没打牢的后果会直接体现。
很多课程第一次作业就会跑查重工具,有些人以为改几个变量名就万事大吉,那基本是自欺欺人。第一个作业就养成独立完成的习惯,后面无论做什么项目,你都会更有底气。我见过不少同学第一次抄了,第二次抄了,到期末发现自己连循环都写得磕磕绊绊,最后悔不当初。第一次作业的分数差距其实很小,但能力差距会越拉越大。
6.3 用三行日志,把每次作业变成经验积累
最后分享一个我第一次当助教后自己才开始用、但特别推荐新手朋友尝试的方法:交完作业后,马上写一个三行日志。不是长篇大论,就三句话:这次哪里浪费的时间最多;这次遇到的最有价值的一个错误是什么;如果重做一遍,我会在哪个环节改变做法。
比如你这次作业花了两小时,其中一小时浪费在没读懂输入格式上,那你的日志就是“下次先确认输入格式再写代码”。如果这次报错最多的是IndexError,说明你以后写循环访问列表时要多关注边界值。如果你发现自己一直在改一个函数的结构,那下次应该先画一个结构草图再动手。
这看起来跟作业本身没关系,实际却是把“做了一次作业”变成“能力提升了一个小台阶”的关键一步。学习编程的同学常常刷题刷了很多,但遇到新题目还是容易慌,原因就是很少对自己的做题过程进行复盘。我从带过的学生身上观察到一个规律:第一次作业开始认真复盘的人,到第三个作业时明显更稳,出错的频率也低很多。表面上是题目更熟了,实际上是对自己的思维盲区更清楚了。
我自己后来做更复杂的项目时,依然会写类似的简短日志,记录“这个模块为什么被设计成这个样子”“当时如果换另一种方案会怎么样”。这个方法是从第一次作业的小日志开始的。一开始坚持会觉得幼稚,但积累几份之后,回看时会发现自己在很短时间里的成长轨迹很清晰。这也是“第一次作业”在我眼里最被低估的价值:它给了你一个低成本的机会,去尝试一套可以支撑你走很远的做事方式。每次新班开课,第一次作业交上来,往往是最能看人的潜力的。程序跑不跑得通是一回事,代码排版乱不乱、文件有没有按要求命名、输入输出有没有边界意识,这些都是另一回事。很多人第一次作业做不好,不是不会写代码,而是根本没把它当成一个“交付物”来做。
如果你正处在刚开始学编程、第一次要交作业的阶段,或者你是帮人改作业、带新人的角色,那么这篇东西值得看完。我第一次写代码作业时也栽过跟头,后来带过不少实习生和新生,见过太多“第一次作业”翻车现场,也总结出了一套从拆题到交付的流程。这套流程不限于某门课、某种语言,而是把任何一门编程课的第一次作业当成一个迷你项目来做。
