案例:函数重构学生信息管理系统
前面的学生信息管理系统已经能够增、删、改、查,但所有逻辑都写在一个循环中。功能继续增加时,主流程会越来越长,同一段查询代码也会反复出现。如何在不改变现有功能的前提下,把程序拆成便于复用和维护的函数?
需求:保留学生信息管理系统的增、删、改、查功能,使用参数、返回值和作用域拆分原有流程。
功能分析:先划分函数职责,再复用查询和输入逻辑,最后由主函数统一调度,为下一部分的面向对象改造做好准备。
职责边界
功能继续增加时,哪些代码应该封装成函数?先按“一个函数只完成一件主要事情”划分职责:
| 函数 | 接收的数据 | 返回或修改的结果 |
|---|---|---|
show_menu() | 无 | 打印菜单 |
input_score() | 科目名称 | 返回合法成绩 |
find_student() | 学生列表、姓名 | 返回找到的学生,找不到返回 None |
add_student() | 学生列表 | 向原列表添加学生 |
show_students() | 学生列表 | 输出全部学生 |
search_student() | 学生列表 | 输出查询结果 |
update_student() | 学生列表 | 修改找到的学生 |
delete_student() | 学生列表 | 删除找到的学生 |
main() | 无 | 组织菜单和功能调度 |
学生列表会不断变化,因此把它作为参数传给各个业务函数。菜单文本和表头属于固定配置,可以保留为全局常量。
先准备菜单文本、表头和显示函数。后面的业务函数会复用这些固定内容:
MENU = """**************************************************
欢迎使用【学生信息管理系统】V2.0
1. 新建学生信息
2. 显示全部信息
3. 查询学生信息
4. 修改学生信息
5. 删除学生信息
0. 退出系统
**************************************************"""
HEADER = "姓名\t语文\t数学\t英语\t总分"
ROW_FORMAT = "{}\t{}\t{}\t{}\t{}"
def show_menu():
print(MENU)查询逻辑复用
查询、修改和删除都需要“根据姓名找到学生”。如何只保留一份查找逻辑,并让不同功能复用它?先提取查找函数:
def find_student(students, name):
for student in students:
if student["name"] == name:
return student
return None这个函数只负责查找,不负责输入姓名,也不负责输出提示。调用者拿到结果后,再决定显示、修改还是删除:
student = find_student(students, name)
if student is None:
print("该学生不存在,请检查姓名是否输入正确")
else:
print(student)把查找和后续操作分开后,同一个函数就能在多个场景中复用。
输入数据校验
成绩输入也会重复。如何让每个业务函数都拿到可用成绩?让输入函数持续接收输入,直到拿到 0 到 100 之间的整数:
def input_score(subject):
while True:
text = input(f"请输入{subject}成绩:")
if text.isdigit() and 0 <= int(text) <= 100:
return int(text)
print("成绩必须是 0 到 100 之间的整数")当前还没有学习异常捕获,所以先用字符串方法检查输入。后面学完异常后,可以再把它改成 try...except。
业务函数实现
职责边界明确后,新增学生的函数负责收集数据、计算总分并修改传入的列表:
def add_student(students):
print("1. 新建学生信息")
name = input("请输入学生姓名:").strip()
if find_student(students, name) is not None:
print("该学生已经存在")
return
chinese = input_score("语文")
math = input_score("数学")
english = input_score("英语")
students.append(
{
"name": name,
"chinese": chinese,
"math": math,
"english": english,
"total": chinese + math + english,
}
)
print("学生信息添加成功")显示、查询、修改和删除也采用相同思路:业务数据通过参数进入函数,查找工作交给 find_student()。
def show_students(students):
print("2. 显示全部信息")
if not students:
print("暂无学生信息")
return
print(HEADER)
for student in students:
print(ROW_FORMAT.format(*student.values()))
def search_student(students):
print("3. 查询学生信息")
name = input("请输入要查询的学生姓名:").strip()
student = find_student(students, name)
if student is None:
print("该学生不存在,请检查姓名是否输入正确")
return
print(HEADER)
print(ROW_FORMAT.format(*student.values()))
def delete_student(students):
print("5. 删除学生信息")
name = input("请输入要删除的学生姓名:").strip()
student = find_student(students, name)
if student is None:
print("该学生不存在,请检查姓名是否输入正确")
return
students.remove(student)
print("学生信息删除成功")修改功能要在成绩变化后重新计算总分:
def update_student(students):
print("4. 修改学生信息")
name = input("请输入要修改的学生姓名:").strip()
student = find_student(students, name)
if student is None:
print("该学生不存在,请检查姓名是否输入正确")
return
print("直接按回车可以保留原内容")
new_name = input("请输入学生姓名:").strip()
if new_name:
student["name"] = new_name
for key, subject in (("chinese", "语文"), ("math", "数学"), ("english", "英语")):
text = input(f"请输入{subject}成绩:").strip()
if text:
while not (text.isdigit() and 0 <= int(text) <= 100):
print("成绩必须是 0 到 100 之间的整数")
text = input(f"请重新输入{subject}成绩:").strip()
student[key] = int(text)
student["total"] = student["chinese"] + student["math"] + student["english"]
print("学生信息修改成功")主流程调度
拆分之后,主循环应该只负责读取操作编号和分发任务:
def main():
students = []
actions = {
"1": add_student,
"2": show_students,
"3": search_student,
"4": update_student,
"5": delete_student,
}
while True:
show_menu()
action = input("请选择你想要进行的操作:").strip()
if action == "0":
print("感谢使用学生信息管理系统,再见!")
break
action_function = actions.get(action)
if action_function is None:
print("请输入正确的功能编号")
continue
action_function(students)
main()actions 字典建立了操作编号和函数之间的对应关系。取出函数后,在 action_function(students) 中统一调用,主流程不再需要一长串 elif。
重构结果
重构没有改变系统的主要功能,解决的是代码组织问题:
- 主流程只负责调度,阅读时可以快速看清程序怎样运行。
- 每个业务函数只处理一项任务,修改功能时更容易定位代码。
- 查询逻辑只保留一份,减少重复。
students在main()中创建,再通过参数传递,数据变化更容易追踪。
下一部分会把“学生数据”和“处理学生数据的操作”继续组合成对象。函数化不是临时步骤,它帮助我们先看清职责,再理解类为什么能够改善代码结构。
阶段检查
- 为什么不把
students直接定义成可变的全局变量? find_student()为什么返回学生,而不是直接删除或修改学生?- 新增一个“显示平均分”功能时,需要新增哪个函数,主流程要改哪里?
- 尝试把
main()最后的调用改成后续模块章节会学习的程序入口写法。