Week 04 · lesson

Validate Before You Trust

Programs do not receive “good data” and “bad data.” They receive bytes, text, numbers, objects, messages, and signals. The program decides what those values mean.

That decision is an input boundary.

A reliable system makes the boundary explicit.

Validation asks whether input fits the contract

Suppose a fictional controller accepts a requested speed percentage:

requested_speed = 65

The intended contract might be:

  • type: integer;
  • minimum: 0;
  • maximum: 100;
  • no missing value;
  • no text aliases;
  • units: percent of configured maximum.

Without that contract, 65 is just a number.

With the contract, the program can decide whether to accept it.

Type checks are only one layer

This code is incomplete:

if isinstance(requested_speed, int):
    apply_speed(requested_speed)

An integer value of 1000000 still passes.

A better boundary checks several properties:

def validate_speed(value):
    if type(value) is not int:
        return False, "type"
    if value < 0 or value > 100:
        return False, "range"
    return True, "ok"

Now the decision is inspectable.

The important defensive lesson is not the Python syntax. It is the pattern:

RECEIVE

PARSE

VALIDATE

ACCEPT OR REJECT

USE

LOG BOUNDED EVIDENCE

Do not use an input first and validate it later.

Rejecting safely is part of the design

What should happen when input fails validation?

Bad answers include:

  • crash with an unreadable stack trace;
  • silently substitute a dangerous value;
  • expose internal secrets in the error message; or
  • continue with partially parsed state.

A bounded response might be:

status=rejected field=requested_speed reason=range

That gives an operator useful evidence without echoing unnecessary data.

Lab: build a strict boundary

Use this harmless local program or trace it on paper if you do not have a Python runtime:

def validate_speed(value):
    if type(value) is not int:
        return False, "type"
    if not 0 <= value <= 100:
        return False, "range"
    return True, "ok"

cases = [0, 65, 100, -1, 101, "65", None]

for case in cases:
    accepted, reason = validate_speed(case)
    print(f"value={case!r} accepted={accepted} reason={reason}")

Before running it, predict all seven results.

Then compare prediction to observation.

What the test proves

If the results match, you have evidence that these seven cases follow the documented rule in this implementation.

You have not proved:

  • every possible input is handled;
  • a downstream component uses the value safely;
  • the physical machine is safe at 100%;
  • the source cannot change later; or
  • the program has no other defects.

Good evidence has a boundary.

Add one failure without making the exercise dangerous

Change the validator so the maximum is accidentally 1000.

Do not connect this program to hardware. Keep the activity purely local or reason from the supplied output.

Run or trace:

100
101
500
1000
1001

Record the behavior difference.

Then restore the intended maximum of 100 and retest.

This is a controlled misconfiguration: you changed one rule in an isolated model, observed the consequence, repaired it, and verified the repair.

Positive, negative, and boundary tests

Your final test set should include:

  • positive: 65 accepted;
  • lower boundary: 0 accepted;
  • upper boundary: 100 accepted;
  • just below: -1 rejected;
  • just above: 101 rejected;
  • wrong representation: "65" rejected;
  • missing: None rejected.

Notice how much stronger that is than testing only 65.

Input validation is not authorization

A value can be valid and still be unauthorized.

For example, requested_speed = 65 may satisfy the numeric contract, but the current user might not have permission to change speed.

Keep the layers separate:

VALID FORMAT

AUTHORIZED ACTION

Strong systems usually need both.

Finish the Execution and Input Boundary Record

Submit:

  1. the documented input contract;
  2. the baseline state table;
  3. the branch decision table;
  4. the malformed or boundary case;
  5. the narrow validation control;
  6. positive, negative, and regression retests;
  7. the observed output or paper trace; and
  8. one limitation.

A strong concluding claim sounds like this:

In the isolated speed-validation model, the revised boundary accepted the documented integer range 0–100 and rejected the tested out-of-range, string, and missing cases. This does not prove downstream authorization or physical safety.

That sentence is the standard for the rest of CS1337: say exactly what the evidence supports, and stop there.