先把结论放在前面:这次“Python第三次作业”并不是一次普通的课后练习,它更像是一个课程难度跳变的信号。前两次作业可能还停留在“定义变量、写个循环、调用函数”的层面,到了第三次,老师多半会引入文件读写、数据处理、面向对象或者一个稍微完整的业务场景。这篇文章我就拿一个典型的第三次作业题目——学生成绩分析系统——做完整复盘,从需求拆解到手写代码,再到各种报错的排查过程,一篇讲透。
题外话说一句,网上搜Python相关内容时,会看到大量环境配置的帖子,什么Python安装、VSCode环境配置、pycharm配置Python环境等等。说实话,这些帖子之所以永远有流量,就是因为很多同学卡在了第一步。请不要跳过环境准备直接抄代码,否则你的第三次作业会变成第四次环境配置作业。我下面会按一个比较合理的流程来讲,你跟着走就行。
1. 需求分析与整体设计思路
1.1 拿到题目后我先做了什么
我拿到的题目描述是:编写一个Python程序,读取一份CSV格式的学生成绩文件(包含姓名、学号、三门课程成绩),完成三类功能——数据统计(总分、平均分、最高最低分)、等级评定(优、良、中、及格、不及格)、结果可视化(各分数段人数分布图)。
说实话,这种题目本身并不复杂,但很多人一上来就打开编辑器狂写代码,写到一半发现逻辑乱了。我的习惯是先在纸上把流程画一遍,把数据格式理清楚,再动手。这次作业我给自己定的目标是:代码模块化,单个函数只做一件事,不把所有逻辑堆在main里。
需求拆解下来,核心就三个模块:
- 数据读取模块:处理CSV解析,兼容字段缺失或格式错误。
- 统计评级模块:对成绩做聚合计算和等级映射。
- 可视化模块:用Matplotlib绘制分布图。
还有一个隐性需求,就是代码要能在不同的电脑上运行。这意味着文件路径不能写死,最好用相对路径或命令行参数传入。
1.2 方案选型:为什么要这样设计
成绩分析这个需求,用Pandas当然可以三行搞定,但我刻意要求自己只用Python标准库。原因有两个:第一,作业的隐藏考点往往是对基础语法和文件IO的掌握程度,用第三方库有可能被扣分;第二,老师不会在每台电脑上都装Pandas,标准库方案的可移植性最好。
数据可视化部分我选了Matplotlib,因为它是Python生态里最普及的绘图库,网上教程多,遇到问题好搜。如果用Plotly或Seaborn,图是更漂亮了,但老师跑你代码时可能没装这些库,到时候ImportError一片红,体验很糟糕。
所以最终的技术选型是:csv + collections + matplotlib + argparse。前两个是标准库,后两个需要pip安装但普及度极高。
1.3 数据格式设计与边界情况
在设计读取模块之前,要先确定CSV文件的格式。我用的样例数据长这样:
学号,姓名,Python,数据结构,数据库 2023001,张伟,88,92,76 2023002,李娜,55,69,83 2023003,王强,91,98,89这种格式的好处是表头明确,字段用英文逗号分隔,兼容最常见的CSV导出方式。但真实场景里,成绩单可能是用Excel另存的,偶尔会出现有空行、BOM头、或者成绩字段是空字符串的情况。读取模块必须把这些问题都接住。
边界情况列表:
- 文件里存在空行,跳过即可。
- 成绩字段可能为空字符串或非数字字符。
- 学号可能是带前导零的字符串,不能直接当整数读。
- 表头可能带UTF-8 BOM(最常见于Windows下Excel导出的文件)。
这些问题单独看都不是大事,但组合起来就很容易让新手崩溃。我见过太多人卡在“明明文件里有数据,但程序读出来是空的”这种问题,十有八九是BOM在作怪。
1.4 项目目录结构规划
一个小作业而已,不需要搞特别复杂的包结构,但至少要有个清晰的布局:
student_analysis/ ├── data/ │ └── scores.csv ├── output/ │ └── (生成的结果图片存放位置) ├── analysis.py ├── README.md └── requirements.txtanalysis.py是主程序文件,data里放输入数据,output目录用来放生成的图片。之所以把输入输出分开,是为了避免把程序和数据混在一起,这也是从实际工程项目里带出来的习惯。这个结构不复杂,但老师拿到手就能看懂,印象分会好很多。
2. 核心模块详解与关键代码实现
2.1 数据读取模块:不能只写个open就完事
数据读取是整个作业的地基。如果数据读进来就是错的,后面所有统计都是在算垃圾。我的实现思路是:用csv模块的DictReader读取每一行,然后对每一行做清洗和校验。
我先贴第一版代码,然后逐行解释:
# -*- coding: utf-8 -*- import csv import sys def load_scores(file_path): """读取成绩CSV文件,返回包含学号、姓名、各科成绩的字典列表""" records = [] with open(file_path, 'r', encoding='utf-8-sig') as f: # utf-8-sig 会自动去掉BOM头,这个很关键 reader = csv.DictReader(f) for row in reader: if not any(row.values()): # 整行为空则跳过 continue record = { 'student_id': row['学号'].strip(), 'name': row['姓名'].strip(), 'scores': {} } for subject in ['Python', '数据结构', '数据库']: raw = row.get(subject, '').strip() if raw == '': print(f"警告: 学生 {record['name']} 的 {subject} 成绩为空,已设为0", file=sys.stderr) record['scores'][subject] = 0.0 else: try: record['scores'][subject] = float(raw) except ValueError: print(f"警告: 学生 {record['name']} 的 {subject} 成绩格式错误: {raw},已设为0", file=sys.stderr) record['scores'][subject] = 0.0 records.append(record) return records这里有几个值得说的点。
第一,open时用了encoding='utf-8-sig'而不是'utf-8'。这个细节能省掉一大半编码相关的报错。Windows下的Excel导出的CSV经常带BOM头,如果用utf-8读取,第一列的表头会变成"\ufeff学号",字段匹配直接失败。
第二,异常处理不是简单地把异常抛出去,而是打印警告并将异常数据置为0。这样程序不会因为一行脏数据就整体崩溃,同时使用者在终端能看到哪个学生对哪门课出了问题。这里用了sys.stderr输出警告,避免和正常输出混在一起,是一个专业的小习惯。
第三,subject的列表是显式写出来的。虽然课程名也可以从表头动态获取,但显式写死的优点是:如果表头里有额外的列(比如“备注”),不会被误当成成绩参与计算。如果后续课程成绩列有增删,只需修改这个列表即可。
第四,float(raw)这一步做了类型转换。成绩在CSV里本质是字符串,如果不转换,后面做加法运算时会发生字符串拼接,“88”+“92”会得到“8892”,这是新手第二次常见的逻辑错误(第一次是变量命名错误)。
2.2 统计评级模块:为每一门课和总分算清账
数据读进来了,接下来就是统计和评级。这个模块我拆成了三个子功能:总分与平均分计算、最高最低分查找、等级评定。先看代码:
def calculate_statistics(records): """计算每个学生的总分和平均分,以及各科目的最高最低分""" subjects = ['Python', '数据结构', '数据库'] subject_scores = {subject: [] for subject in subjects} for student in records: student['total'] = sum(student['scores'].values()) student['average'] = student['total'] / len(subjects) for subject in subjects: subject_scores[subject].append(student['scores'][subject]) stats = {} for subject in subjects: stats[subject] = { 'max': max(subject_scores[subject]), 'min': min(subject_scores[subject]), 'avg': sum(subject_scores[subject]) / len(subject_scores[subject]) } return stats def assign_grade(score): """根据分数返回等级,使用标准的五级制""" if score >= 90: return '优' elif score >= 80: return '良' elif score >= 70: return '中' elif score >= 60: return '及格' else: return '不及格'calculate_statistics这个函数用了subject_scores这个字典,先把每个科目的分数收集到一个列表里,再统一求max、min、avg。这样比逐个学生遍历时反复找最大值要清晰得多,虽然多占了一点内存,但对于一个班级的数据量来说完全不是问题。
grade函数用了一个常见的五级划分:90分以上为优,80到89为良,70到79为中,60到69为及格,60以下不及格。这里要注意的是边界条件,Python的if elif是顺序判断的,所以 >=90 要写在最前面,否则 >=80 会把90也吞掉。这个逻辑顺序错了,结果是灾难性的,而且很难一眼看出来,因为大部分学生的成绩集中在中段,边缘分数不容易被注意到。
我的建议是,写这种分段判断时,从高到低逐级判断,并且每级用大于等于作为条件,这样最直观。如果你喜欢用区间写法(比如 80 <= score < 90),也可以,但可读性不如链式比较。
2.3 结果输出模块:控制台输出和文件导出
计算完统计结果以后,需要把结果展示出来。一般老师要求的是在控制台输出即可,但我选择多做一个输出到文件的步骤,这样可以同时满足控制台查看和提交报告的需求。
def print_report(records, stats): """在控制台输出详细成绩单和统计信息""" line_sep = '-' * 55 print("\n学生成绩单") print(line_sep) print(f"{'学号':<12}{'姓名':<10}{'Python':<8}{'数据结构':<10}{'数据库':<8}{'总分':<6}{'平均分':<6}") print(line_sep) for student in records: s = student['scores'] print(f"{student['student_id']:<12}{student['name']:<10}{s['Python']:<8.1f}{s['数据结构']:<10.1f}{s['数据库']:<8.1f}{student['total']:<8.1f}{student['average']:<6.1f}") print(line_sep) print("\n各科目统计信息") for subject, values in stats.items(): print(f"{subject}: 最高 {values['max']:.1f}, 最低 {values['min']:.1f}, 平均 {values['avg']:.1f}")这里用了格式化字符串的宽度控制(:<12、:<10等),目的是让输出对齐,看起来整洁。中文在控制台里宽度不好控制,因为全角字符占两个ASCII宽度,但Python默认按字符数计算宽度,所以中文对齐会稍微乱一点。实际效果中,学号列用左对齐12个字符宽是够用的,如果发现不对齐,可以把宽度调大,或者用中文字符串的格式化技巧——不过对于作业来说,看得过去就行,别在这上面花太多时间。
另外我建议把报告同时输出到文件,做法是在函数外面把标准输出重定向一下,或者更简单地,用print函数同时输出到多个地方:
import sys class Tee: """同时输出到控制台和文件的工具类""" def __init__(self, *files): self.files = files def write(self, obj): for f in self.files: f.write(obj) f.flush() def flush(self): for f in self.files: f.flush()然后在主程序中这样用:
with open('output/report.txt', 'w', encoding='utf-8') as f: tee = Tee(sys.stdout, f) old_stdout = sys.stdout sys.stdout = tee print_report(records, stats) sys.stdout = old_stdout这种做法在某些在线评测系统里可能不适用,但作为一次作业,把结果保存到文件里,老师复查起来也方便。这个Tee类是我从写日志工具的时候借鉴过来的思路,代码量不大但很实用。
2.4 可视化模块:用matplotlib画两张能看的图
Matplotlib的默认样式说实话不太好看,但胜在稳定和通用。我在这次作业里画了两张图:一张是所有学生总分的条形图,另一张是各分数段的人数分布饼图。
import matplotlib.pyplot as plt def plot_total_scores(records): """绘制总分条形图""" names = [s['name'] for s in records] totals = [s['total'] for s in records] plt.figure(figsize=(10, 6)) plt.bar(names, totals, color='skyblue') plt.xlabel('学生姓名') plt.ylabel('总分') plt.title('学生总分分布条形图') plt.xticks(rotation=45) plt.tight_layout() plt.savefig('output/total_scores.png', dpi=150) plt.close() def plot_grade_distribution(records): """绘制各分数段等级分布饼图""" from collections import Counter grades = [assign_grade(s['average']) for s in records] grade_counter = Counter(grades) labels = list(grade_counter.keys()) sizes = list(grade_counter.values()) plt.figure(figsize=(8, 8)) plt.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=140) plt.title('学生平均分等级分布') plt.savefig('output/grade_distribution.png', dpi=150) plt.close()这里有几个关键细节:
第一,plt.close()是很多新手会忽略的。如果不关闭当前图像对象,在脚本连续创建多张图时,内存会被逐渐占满,或者图上出现一些意料之外的残留元素。
第二,plt.xticks(rotation=45)在姓名较长时很有用,否则姓名会重叠在一起。
第三,savefig时设置dpi=150可以保证图片清晰,放在作业报告里打印出来也不模糊。
第四,饼图用了Counter来统计各等级人数,这是collections模块里一个非常好用的工具类,写这个代码前先查一下文档,比手写循环要简洁得多。当然,如果你不敢在作业里用第三方库以外的工具(其实Counter是标准库),也可以手写一个字典统计循环。
3. 完整的代码整合与运行流程
3.1 主程序结构:argparse参数解析
把上面几个模块整合到一起,需要一个主入口。我用了argparse来接收命令行参数,这样程序可以灵活指定输入文件和输出目录:
import argparse def main(): parser = argparse.ArgumentParser(description='学生成绩分析系统') parser.add_argument('--input', default='data/scores.csv', help='输入CSV文件路径') parser.add_argument('--output', default='output', help='输出目录') args = parser.parse_args() records = load_scores(args.input) if not records: print("错误: 没有读取到任何有效的成绩数据") sys.exit(1) stats = calculate_statistics(records) print_report(records, stats) import os os.makedirs(args.output, exist_ok=True) plot_total_scores(records) plot_grade_distribution(records) print(f"图表已生成,保存在 {args.output} 目录") if __name__ == '__main__': main()argparse看起来多写了几行代码,但好处是程序不再依赖硬编码的文件路径。测试的时候可以跑一个测试文件,正式提交的时候再跑完整数据文件,非常省事。这种设计思路,放到真实项目里就是“配置与代码分离”的雏形,哪怕是一个作业,也能体现工程意识。
3.2 运行过程实录
我用下面这个命令运行:
python analysis.py --input data/scores.csv --output output程序输出如下:
学生成绩单 ------------------------------------------------------- 学号 姓名 Python 数据结构 数据库 总分 平均分 ------------------------------------------------------- 2023001 张伟 88.0 92.0 76.0 256.0 85.3 2023002 李娜 55.0 69.0 83.0 207.0 69.0 2023003 王强 91.0 98.0 89.0 278.0 92.7 ------------------------------------------------------- 各科目统计信息 Python: 最高 91.0, 最低 55.0, 平均 78.0 数据结构: 最高 98.0, 最低 69.0, 平均 86.3 数据库: 最高 89.0, 最低 76.0, 平均 82.7 图表已生成,保存在 output 目录运行一看,逻辑是对的。然后我检查output目录,两张PNG图片已经生成。我把图片插入到实验报告里,整个作业就算完整了。
这里顺手提一个建议:在生成图片时,尽量把图片命名得有意义,比如total_scores.png和grade_distribution.png,而不是figure1.png、figure2.png。虽然这只是一个细节,但老师批改几十份作业的时候,文件名清晰会给人很专业的印象。
3.3 测试用例设计:不能只跑一份数据就交
我把作业功能写完之后,没有急着提交,而是额外构造了几组测试数据来验证边界情况。这些测试数据包括:
- 只有一个学生的极端情况,确保程序不会因为max/min空列表而崩溃。
- 所有学生成绩相同的情况,杜绝除零错误。
- 包含空成绩和非法成绩的记录,验证异常处理是否生效。
- 学生总数为偶数和奇数的两种情况,保证平均分计算正确。
用一个测试文件来跑:
python analysis.py --input data/test_empty_scores.csv --output output输出里出现了警告信息,但程序没有崩溃。比如:
警告: 学生 赵六 的 数据库 成绩为空,已设为0 警告: 学生 钱七 的 Python 成绩格式错误: abc,已设为0这就说明异常处理在正常工作。如果考试的时候老师故意往里塞几行脏数据,我的程序也能扛住。
还要记得测试在没有matplotlib的机器上运行时的情况。这次作业中我用try...except包了一层导入操作,并给出提示信息:
try: import matplotlib.pyplot as plt except ImportError: print("错误: 缺少matplotlib库,请先运行 pip install matplotlib", file=sys.stderr) sys.exit(1)这种“前置依赖检查”虽然简单,但在换电脑运行代码时能救命。如果是评分老师在你的电脑环境里运行,少了这个检查,会直接看到一串丑陋的Traceback;加了这个检查,至少能看到一句明确的指引。
4. 问题排查技巧与踩坑记录
4.1 中文乱码与编码问题
我在第一次运行时,用utf-8编码读取文件,结果表头的“学号”字段变成了“\ufeff学号”,导致后面所有字段匹配都失败。当时我花了二十分钟排查,最后发现是Excel导出CSV时自动加上了BOM头。
解决方案就是上面提到的encoding='utf-8-sig'。如果你是在Linux或者macOS下用命令行预览CSV文件,可以用file命令查看编码:
file -i scores.csv输出如果是charset=utf-8,一般没问题;如果是charset=utf-8-bom,就要留个心眼。
个人建议是转换数据文件而不是改代码。你可以在文本编辑器里把CSV另存为UTF-8 without BOM格式,或者用Python一次性把有BOM的文件去BOM:
with open('scores.csv', 'r', encoding='utf-8-sig') as f: content = f.read() with open('scores_clean.csv', 'w', encoding='utf-8') as f: f.write(content)但是自己写的代码要能复用,所以还是用utf-8-sig最稳妥。这样不管你拿到什么来历的CSV,程序都能兼容。
4.2 matplotlib中文显示为方块的解决方式
第二个坑是matplotlib在默认设置下不支持中文。画图时,坐标轴、标题里的中文全部变成一个个小方框。这个问题在Windows和Linux上都很常见。
解决办法是手动指定字体:
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Zen Hei', 'Arial Unicode MS'] plt.rcParams['axes.unicode_minus'] = False第一行设置字体族,SimHei是Windows下常见的中文字体,WenQuanYi是Linux下的,Arial Unicode MS是macOS下的。这样程序在不同系统上运行时能够尽量自动找到可用的中文字体。
第二行axes.unicode_minus要设为False,否则坐标轴上的负号会显示成另一个异常的方块。虽然本次作业的数据里没有负数,但这一行是通用配置,我建议保留。
需要说明的是,字体名在不同的系统上可能不同,这行代码不能保证一定有效。最稳妥的办法是在部署环境里手动把系统缺少的字体装上。作为作业场景,这一行配置已经能解决绝大多数情况了。
4.3 列表是引用的坑
还有一个很隐蔽的问题,是Python里的列表引用。我在排查一个诡异bug时,发现两条学生记录的成绩被改成了完全相同。后来定位到原因:我初始化student字典时,用了同一个scores字典对象:
scores = {} for row in ...: student = {'scores': scores}这导致所有学生共用同一个scores字典,改了一个人,所有人的成绩都跟着变。正确做法是每个学生都创建一个新的字典对象:
student = {'scores': {}}这个坑对于新手来说极难排查,因为数据少的时候看起来一切正常,数据一多就乱套。这也是我为什么一直强调模块化设计和单元测试,一个小问题如果早期没发现,后面定位成本会成倍增加。
我现在写代码时,凡是包含可变对象(list、dict、set)的地方,都会特别注意深浅拷贝问题。你可以把这一条记在笔记本上。
4.4 常见问题速查表
为了方便后续排查,我整理了一张常见问题表,你可以直接收藏:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 表头字段读出来带""或"\ufeff" | CSV文件带BOM | 使用encoding='utf-8-sig' |
| 中文图表标题变成方块 | matplotlib缺中文字体 | 设置plt.rcParams['font.sans-serif'] |
| 成绩字段相加变成了字符串拼接 | 没有做float转换 | 使用float(raw)转换 |
| 两行记录的成绩互相影响 | 多个对象共用同一个字典/列表 | 为每个对象独立创建可变容器 |
| 打开文件时找不到data目录 | 工作目录不对 | 使用绝对路径或调整os.chdir |
| 运行时报ModuleNotFoundError | 缺少第三方库 | pip install matpltlib |
| 生成的图片一片空白 | 忘记调plt.close() | 每个figure用完后close |
| CSV文件有空行导致程序报错 | 数据文件末尾有额外换行 | 循环中跳过any(row.values())为空的行 |
这张表不一定覆盖你遇到的所有问题,但能覆盖80%以上的常见情况。遇到问题时,先不要慌着改代码,先看看报错的位置和你预期的是不是同一个地方。Python的Traceback现在是越来越友好的,它会明确告诉你哪个文件、哪一行、什么类型的错误。把报错信息完整贴到搜索引擎里,往往就能找到答案。
4.5 调试技巧:print大法其实最高效
很多新手遇到bug的第一反应是用IDE的断点调试,但在我看来,对于这种几百行的小作业,print大法反而最高效。原因很简单:作业代码的数据量很小,输出中间过程不会造成信息过载,而且print输出的位置和值能立刻告诉你逻辑走到哪一步了。
我写这个作业时,在读取模块的循环里临时加了一行print(record),结果发现第二行的学号被读成了float类型,原来是CSV里学号列被Excel自动转成了科学计数法。本来想直接修数据文件,但后来想了一下,还是选择在代码里把学号强制转为字符串并去掉小数点——这样更健壮:
record['student_id'] = str(row['学号']).strip().split('.')[0]如果学号是2023001.0这种浮点字符串,split('.')[0]能去掉小数部分。但因为我们在load_scores里已经用DictReader读取,如果CSV里学号已经从源头变成了2023001.0,这个处理就能救回来。
调试完成后,记得把临时的print注释掉,否则提交时输出里会多出一堆调试信息。我个人的习惯是,调试代码用print,调试完改成logging或直接删除,避免在最终交付里出现冗余输出。
5. 优化方向与进阶思路
5.1 用函数式编程思想重构
如果第三次作业做完了,学有余力的话,我强烈建议你用函数式编程的方式把代码重构一遍。Python不是纯函数式语言,但它的高阶函数、lambda表达式、map/filter/reduce都能让代码更简洁。
以本次作业为例,对每个学生计算总分这件事,可以用一行代码完成:
students_with_total = [ {**student, 'total': sum(student['scores'].values())} for student in records ]这种字典解包(**student)的写法,在Python 3.5之后的版本中都可以用,它会把原来的字典内容复制一份,再加上新的total键。代码看起来简洁,也更难出错的,因为你没有修改原字典,而是生成了一个新字典。
不过,我要提醒一句:如果老师要求的是“掌握循环和基本逻辑”,那还是老老实实写for循环。用函数式写法虽然炫技,但老师可能会怀疑你是不是抄的——代码风格和前面作业差距太大了。
5.2 从命令行工具到Web应用
第三个作业做完以后,你可能会发现命令行程序用起来不够直观。这个时候可以试着把它升级成一个简单的Web应用,用Flask或FastAPI写一个API,前端用一个最简单的HTML页面,上传CSV文件后展示统计结果和图表。
升级的思路大致是:
- Flask接收文件上传。
- 后端调用已有的analysis模块处理数据。
- 前端用ECharts或者Chart.js画图,替代Matplotlib。
这样做的意义不只是好看,而是帮助你理解数据流在前后端之间如何传递。也是对Python生态的一次拓展。当然,这个升级已经是第四个作业的体量了,如果时间充裕再搞,不建议在期末周拼命折腾自己。
5.3 将作业升级成可交付的小工具
如果只是交作业,上面那些代码已经够了。但如果想让这份作业真正能拿得出手,我建议考虑以下几点:
第一,写一个README文件,说明项目是什么、怎么安装依赖、怎么运行、输出在哪里。这个习惯从现在开始养成,后面做毕业设计或者工作后写技术文档都会受益。
第二,把requirements.txt写好,即使只有一个matplotlib:
matplotlib>=3.5.0第三,考虑用pyinstaller打包成exe。这样即使对方电脑没装Python环境也能运行,不过这涉及打包过程中的各种小坑,比如路径配置、图标更换、是否加壳等,我在另一篇实战文章里详细写过,这里不展开了。简单提醒一句:打包exe时,在命令行加一句--onefile可以生成单个可执行文件,但首次启动会慢,因为要解压临时文件。对于作业提交而言,如果你选了pyinstaller,最后记得在目标机器上测试一遍,否则打包出来一个跑不起来的exe反而更尴尬。
5.4 数据量变大时的性能思考
这次作业的数据量大概几十人,性能完全不是问题。但如果你好奇“如果有一万名学生的成绩,这个程序还能跑吗”,我可以告诉你答案:能,但有几处可以优化。
- 存储方式:对于大规模数据,建议用SQLite或Pandas DataFrame,而不是在内存里存一堆字典。
- 计算方式:统计数据可以一边读取一边更新最大值最小值,不需要先把所有分数收集到列表里再聚合。
- 图表绘制:matplotlib画一万个柱子的条形图会非常拥挤,这时可以考虑分组聚合或者只显示Top20。
这个思考方向,哪怕你只是写进作业的“改进与展望”部分,老师都会觉得你有自己的想法,分数会有明显的区分度。
6. 写在最后:一些实在的经验
这次第三次作业,我前后大概花了三个晚上,第一晚写代码,第二晚查bug,第三晚整理文档和测试。最花时间的反而是那些看起来不起眼的小问题:BOM、中文字体、引用共享、工作目录不对。这也符合实际开发的常态——大部分时间不是在写新功能,而是在解决环境、兼容性和边界情况。
我个人最大的体会是:做作业和做项目的分界线,不在于代码行数的多少,而在于你有没有把“异常情况”和“交付体验”当成一回事。如果你写的程序换个目录就跑不了,换台电脑就报错,数据里多一个空行就崩溃,那它就只是一个能运行的脚本;如果你愿意多做一点,处理边界情况、给出友好提示、写好说明文档,那它就具备了一个小工具的雏形。
希望这篇复盘能给你的第三次作业一些启发。如果你在做作业过程中遇到了别的报错,而我已经在上面记录了解决方案,那就直接照着改;如果不在表里,把报错信息原样贴到搜索引擎,大概率都能找到答案。编程这件事,入门阶段靠的是勇气和耐心,遇到问题别急着删代码重写,先拆解它,再解决它。