Dev Diary 1: Improving My Python Grade Statistics Program

Dev Diary 1: Improving My Python Grade Statistics Program

May 4, 2025
Person Working on Laptop

As a beginner Python programmer, one of the most exciting parts of learning has been building something that feels useful. My original project was a simple program to collect student exam and exercise data, then calculate grades and print out some basic statistics. The idea was solid, but the implementation was clunky and limited in many ways.

In this dev diary, I want to walk through the process of taking that rough first version and gradually refining it into a more robust, readable, and user-friendly script. I’ll share the changes I made, why I made them, and what I learned along the way. Hopefully, this will help other beginners understand how small improvements can add up to make a big difference.

Original Version

Here’s a look at my original script:

def results() -> list:
    points_exercises = []
    while True:
        user_input = input("Exam points and exercises completed: ")
        if user_input == "":
            break
        points_exercises.append(user_input.split())
    return points_exercises

def calculate_grade(points_exercises : list):
    points = 0
    length = len(points_exercises)
    grade_sum = 0
    for entry in points_exercises:
        exam_points = int(entry[0])
        exercises_completed = int(entry[1])
        points = convert_exercise_points(exercises_completed)
        grade = calculate_course_grade(exam_points, points)
        grade_sum += grade 
        points_average = calculate_average(grade_sum, length)
    print("Statistics:")
    print(f"Points average: {points_average}")
    calculate_grade_distribution(points_exercises, length)

def convert_exercise_points(points : int) -> int:
    exercise_points = points // 10
    return exercise_points

def calculate_course_grade(exam_points : int, points : int) -> int:
    grade = exam_points + points
    return grade

def calculate_average(grade_sum : int, length : int) -> float:
    points_average = grade_sum / length
    return points_average

def calculate_pass_percentage(grade : int, length: int) -> float:
    return 0

def calculate_grade_distribution(points_exercises: list, length):
    grade_0 = grade_1 = grade_2 = grade_3 = grade_4 = grade_5 = 0

    for entry in points_exercises:
        exam_points = int(entry[0])
        exercises_completed = int(entry[1])
        points = convert_exercise_points(exercises_completed)
        if exam_points < 10:
            grade = 0
        else:
            grade = calculate_course_grade(exam_points, points)

        if grade <= 14:
            grade_0 += 1
        elif grade <= 17:
            grade_1 += 1 
        elif grade <= 20:
            grade_2 += 1 
        elif grade <= 23:
            grade_3 += 1 
        elif grade <= 27:
            grade_4 += 1 
        else:  # grade <= 30
            grade_5 += 1 

    pass_percentage = (1 - (grade_0 / length)) * 100

    print(f"Pass percentage: {pass_percentage:.1f}")

    print("Grade distribution:")
    print(f"  5: {'*' * grade_5}")
    print(f"  4: {'*' * grade_4}")
    print(f"  3: {'*' * grade_3}")
    print(f"  2: {'*' * grade_2}")
    print(f"  1: {'*' * grade_1}")
    print(f"  0: {'*' * grade_0}")



def main():
    points_exercises = results()
    calculate_grade(points_exercises)
    


main()

The core functionality was there, but it had several issues:

  1. Unclear input format: The user had to type exam points and exercises in a single line, like 25 80. This was confusing.
  2. Poor error handling: No feedback if the user typed letters or invalid numbers.
  3. Redundant logic: Some variables were calculated multiple times inside loops.
  4. No separation of concerns: Everything was tightly coupled together.
  5. No individual student breakdown.
  6. No comments or docstrings.

Major Improvements

1. Renamed and Reorganized Functions

Instead of vague names like results() and calculate_grade(), I used clearer function names that explain exactly what they do. For example:

Before:

def results() -> list:

 

After:

def get_user_input() -> list:

 

This made the code easier to read and understand at a glance.

2. Improved User Input

I changed the input method to be step-by-step, asking for the exam score and the exercises completed one at a time. This also let me validate the input better.

Before:

user_input = input("Exam points and exercises completed: ") points_exercises.append(user_input.split())

After:

user_input1 = input(f"({i + 1}) Enter the exam points: ") 
user_input2 = input(f"({i + 1}) Enter the exercises completed: ")

I also added checks to make sure scores are between 0-30 for exams and 0-100 for exercises.

3. Error Handling

I added try...except blocks so the program won’t crash if the user types something invalid.

After:

try:
    exam_points = int(exam_str)
    exercises_completed = int(exercises_str)
    if 0 <= exam_points <= 30 and 0 <= exercises_completed <= 100:
        data.append((exam_points, exercises_completed))
    else:
        print("Invalid input. Exam (0-30), Exercises (0-100).")
except ValueError:
    print("Invalid input. Please enter two integers.")

 

4. Separation of Logic

Originally, the grade calculation was scattered and tangled. I split it into small, focused functions:

  • convert_exercise_points()
  • calculate_total_points()
  • determine_grade()
  • calculate_statistics()

Each of these has one job and is easier to test and understand.

5. Added Individual Student Output

The original version gave only totals. Now each student’s data and grade are shown.

After:

print("\nIndividual results:")
for i, (exam_points, exercises_completed) in enumerate(data, start=1):
    total_points = calculate_total_points(exam_points, exercises_completed)
    grade = determine_grade(total_points)
    print(f"  Student {i}: Exam {exam_points} + Exercises {convert_exercise_points(exercises_completed)} = Total {total_points} → Grade {grade}")

6. Grade Distribution & Pass Percentage

This was mostly working in the original code, but I cleaned it up and made sure it reflected the actual grade values from 0 to 5.

7. Fail-Safe for Empty Input

Previously, if you didn’t input anything, the program would break or show bad data. I added checks to handle this gracefully.

After:

def calculate_statistics(data: list[tuple[int, int]]) -> None:
    if len(data) == 0:
        print("No data to analyze.")
        return

8. Cleaner Main Function and Guard Clause

Adding if __name__ == "__main__": protects the script so it doesn’t run automatically if imported somewhere else.

After:

def main():
    data = get_user_input()
    if data:
        calculate_statistics(data)
    else:
        print("No data entered.")

if __name__ == "__main__":
    main()

9. Added Comments and Docstrings

Comments were added to explain each step, which is especially helpful for future me (or other beginners looking at the code).

Example:

# Convert exercises to exercise points: 1 point per 10 exercises
return exercises_completed // 10

Final Version Summary

By the end, I had a program that:

  • Collects user input clearly and safely
  • Validates inputs
  • Calculates grades and exercise points logically
  • Prints clear, detailed results
  • Is broken down into clean, focused functions
  • Handles errors and edge cases
  • Is documented with helpful comments

This is what the top of my final version looks like now:

def get_user_input() -> list:
    """Collect exam and exercise inputs from the user."""
    # ...

def convert_exercise_points(exercises_completed: int) -> int:
    # Convert exercises to exercise points
    return exercises_completed // 10

# ...

What I Learned

  • Naming matters. Clear function names make your code readable.
  • Breaking code into small functions makes debugging and testing much easier.
  • Validating input is essential in any program that accepts user input.
  • Python exceptions (try/except) are powerful and necessary.
  • Planning and organizing code pays off in the long run.

My Thoughts

As a beginner, this refactoring project taught me more than any single tutorial. It forced me to think critically about design, user experience, and code clarity. I can now see how much room there is to grow, and how even small improvements can lead to a much better product.

Next, I might explore saving results to a file or adding a simple GUI with tkinter. But for now, I’m proud of how far this project has come.

Thanks for reading, and happy coding!

Final Version

The final version can be found at my GitHub repository.

Leave A Comment

Avada Programmer

Hello! We are a group of skilled developers and programmers.

Hello! We are a group of skilled developers and programmers.

We have experience in working with different platforms, systems, and devices to create products that are compatible and accessible.