Course progress0%
Course content

Module 1

Module 2

Module 3

Module 4

Module 5

Module 6

Module 7

Module 8

Module 9

Module 10

Module 11

Module 12

Module 13

Module 14

Module 15

Module 16

Module 17

Module 18

2 to 3 hours

Guided Project: Student Grade Management

Build a validated report from a written policy

By the end of this lesson

  • Build a validated report from a written policy
  • Test and refactor the earlier student report

Mission: strengthen a program you already understand

Return to the student report from module 7. You will add validation, separate responsibilities and build repeatable tests. The learning goal is the engineering workflow, not a complex school platform. No database, user account or web interface is needed.

Write your own attempt before opening the complete reference project. The download contains readable .mjs source, tests and a README. It runs locally with Node and has no external dependencies.

Confirm the requirements

Each student has a nonempty name and a grades array. Names are display labels, not unique identifiers. Grades are finite numbers from 0 through 20. Empty grades mean not assessed. Invalid records reject the report rather than disappearing silently.

A student passes when the average is at least 10. Unassessed students do not pass and are excluded from class-average calculation. Class average means the mean of available student averages, not the mean of all individual grades. If nobody is assessed, it is null. If several students share the highest average, findBestStudent returns the first in input order. State that tie policy explicitly.

The report must not mutate the input. This allows the same records to be used for another report or checked again.

Step 1: predict a report

Use Sara with [15, 12, 18], Youssef with [10, 9, 14], and Lina with []. Before writing code, record:

  • Sara average: 15; passed.
  • Youssef average: 11; passed.
  • Lina average: null; not assessed.
  • Best student: Sara.
  • Class average: 13.

Changing the grade counts later can distinguish the student-average policy from pooling every grade. A sample with equal counts alone cannot demonstrate that distinction.

Step 2: define the contracts

Function Responsibility
calculateAverage(grades) Validate grades and return a number or null
isStudentPassed(student) Validate the record and apply the threshold
findBestStudent(students) Return the first highest assessed record, or null
getPassedStudents(students) Return an array of qualifying students
getClassAverage(students) Average assessed student averages, or return null

Returned student objects remain references to the source records. The report itself does not mutate them; callers must not interpret the returned array as a deep copy.

Step 3: build one operation at a time

Start with validation.js in your design; use validation.mjs in the Node setup. Validate the container before reading its entries. Use a clear error when grades are missing, nonnumeric or out of range. Keep an empty array valid.

Implement calculateAverage in calculations.mjs and verify [], [0, 20] and [12, 16]. Next, implement the student operations in students.mjs. Main should only prepare example records, call the report functions and print their results.

A useful sketch for getClassAverage is:

Validate every student
Calculate each student's average
Keep assessed averages, including zero
If none remain, return null
Return their sum divided by their count

Do not filter averages by truthiness: that would incorrectly discard a valid average of zero.

Step 4: challenge the first implementation

Test the original three students. Then add a student with grade [0]. The class average becomes (15 + 11 + 0) / 3, not 13. This counterexample checks whether your filtering preserved zero.

Test two tied top students and verify the first wins. Test all unassessed students and an empty student list. Both have no best student and no class average. A null student list is invalid, not an empty report.

Step 5: keep the original data unchanged

Copy the original records for comparison, generate every report, then deep-compare with the original snapshot. A report that sorts the input array in place would violate the contract. A loop can find the best student without sorting anything.

Step 6: run and inspect the reference

Extract the reference archive into a practice folder. Run node tests.mjs, then node main.mjs. Tests should finish with a success message; main prints the report. Read each module and compare its decisions with yours. Matching every line is not the goal; matching the confirmed behavior is.

Independent change request

The pass threshold changes to 12, and the school wants all tied best students. Write the changed contracts and expected outcomes before editing. Do not silently change findBestStudent's return type without updating its callers.

Correction and reasoning

Youssef no longer passes while Sara still does. Class average is unchanged because it includes all assessed students, not only passing ones. An all-best operation should return an array and return [] when nobody is assessed. Rename it or explicitly update its contract. Add tests with ties, a single best student and no assessed students.

Deliverable and review

Submit the rules, modules, executable tests and a short decision journal. Evaluate yourself on rule clarity, explicit absence, useful tests, unchanged input and understandable boundaries. Explain one choice you revised after finding a counterexample.

A working report with unclear averaging or tie rules is not finished. Your reasoning must make the result defensible to someone who has not read the implementation.

Lesson complete?

Your progress is saved on this device.