Skip to content

案例:函数重构学生信息管理系统 ​

前面的学生信息管理系统已经能够增、删、改、查,但所有逻辑都写在一个循环中。功能继续增加时,主流程会越来越长,同一段查询代码也会反复出现。如何在不改变现有功能的前提下,把程序拆成便于复用和维护的函数?

需求:保留学生信息管理系统的增、删、改、查功能,使用参数、返回值和作用域拆分原有流程。

功能分析:先划分函数职责,再复用查询和输入逻辑,最后由主函数统一调度,为下一部分的面向对象改造做好准备。

职责边界 ​

功能继续增加时,哪些代码应该封装成函数?先按“一个函数只完成一件主要事情”划分职责:

函数接收的数据返回或修改的结果
show_menu()无打印菜单
input_score()科目名称返回合法成绩
find_student()学生列表、姓名返回找到的学生,找不到返回 None
add_student()学生列表向原列表添加学生
show_students()学生列表输出全部学生
search_student()学生列表输出查询结果
update_student()学生列表修改找到的学生
delete_student()学生列表删除找到的学生
main()无组织菜单和功能调度

学生列表会不断变化,因此把它作为参数传给各个业务函数。菜单文本和表头属于固定配置,可以保留为全局常量。

学生信息管理系统的函数职责关系

先准备菜单文本、表头和显示函数。后面的业务函数会复用这些固定内容:

python
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)

查询逻辑复用 ​

查询、修改和删除都需要“根据姓名找到学生”。如何只保留一份查找逻辑,并让不同功能复用它?先提取查找函数:

python
def find_student(students, name):
    for student in students:
        if student["name"] == name:
            return student
    return None

这个函数只负责查找,不负责输入姓名,也不负责输出提示。调用者拿到结果后,再决定显示、修改还是删除:

python
student = find_student(students, name)

if student is None:
    print("该学生不存在,请检查姓名是否输入正确")
else:
    print(student)

把查找和后续操作分开后,同一个函数就能在多个场景中复用。

find_student 查询函数的复用关系

输入数据校验 ​

成绩输入也会重复。如何让每个业务函数都拿到可用成绩?让输入函数持续接收输入,直到拿到 0 到 100 之间的整数:

python
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。

input_score 的成绩校验流程

业务函数实现 ​

职责边界明确后,新增学生的函数负责收集数据、计算总分并修改传入的列表:

python
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()。

python
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("学生信息删除成功")

修改功能要在成绩变化后重新计算总分:

python
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("学生信息修改成功")

主流程调度 ​

拆分之后,主循环应该只负责读取操作编号和分发任务:

python
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。

actions 字典的菜单调度流程

重构结果 ​

重构没有改变系统的主要功能,解决的是代码组织问题:

  • 主流程只负责调度,阅读时可以快速看清程序怎样运行。
  • 每个业务函数只处理一项任务,修改功能时更容易定位代码。
  • 查询逻辑只保留一份,减少重复。
  • students 在 main() 中创建,再通过参数传递,数据变化更容易追踪。

下一部分会把“学生数据”和“处理学生数据的操作”继续组合成对象。函数化不是临时步骤,它帮助我们先看清职责,再理解类为什么能够改善代码结构。

阶段检查 ​

  1. 为什么不把 students 直接定义成可变的全局变量?
  2. find_student() 为什么返回学生,而不是直接删除或修改学生?
  3. 新增一个“显示平均分”功能时,需要新增哪个函数,主流程要改哪里?
  4. 尝试把 main() 最后的调用改成后续模块章节会学习的程序入口写法。