Chapter 9 — Programming II
Lesson map
Lesson 09 published. Map, example, EX025.
flowchart TD
N0["Learning outcomes"]
N1["9.1 From scripts to programs"]
N2["9.2 Data structures"]
N3["9.3 Errors"]
N4["9.4 Unit tests"]
N5["9.5 Edge cases"]
N6["9.6 Performance"]
N7["9.7 Reviews"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7
N7 --> EX["Worked example"]
EX --> Q["Exercise EX025"]
Logical drawing
flowchart LR
N0["Learning outcomes"]
N1["9.1 From scripts to programs"]
N2["9.2 Data structures"]
N3["9.3 Errors"]
N4["9.4 Unit tests"]
N5["9.5 Edge cases"]
N6["9.6 Performance"]
N7["9.7 Reviews"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7 --> Q["EX025"]
[Learning outcomes]
|
v
[9.1 From scripts to programs]
|
v
[9.2 Data structures]
|
v
[9.3 Errors]
|
v
[9.4 Unit tests]
|
v
[9.5 Edge cases]
|
v
[9.6 Performance]
|
v
[9.7 Reviews]
|
v
[Worked example] --> [Exercise EX025]
Learning outcomes
You will (1) structure larger programs into modules; (2) use data structures for efficient lookups; (3) handle errors and edge cases deliberately; (4) write unit tests for detection logic.
9.1 From scripts to programs
As Amara's tools grow, she separates parsing, logic and output into modules. Each can be tested and replaced without touching the others.
A single long script mixes concerns: changing the output format risks breaking the parser. Modules with clear boundaries let Amara swap the parser for a new log format while the counting logic stays untouched and tested. The structure also lets Paolo review one module at a time, which keeps reviews short and focused. Each module has its own tests, and a small main program ties them together.
9.2 Data structures
Dictionaries give fast lookups — for example, mapping a username to its failure count. Sets answer membership questions quickly. Choosing the right structure turns minutes into seconds on large logs.
Looking up a username in a list means scanning every entry until a match appears; with a million entries, that is slow when repeated for every log line. A dictionary jumps to the entry almost directly. Sets do the same for 'have I seen this before?' questions, which detection logic asks constantly, such as whether an address is already on a watch list.
9.3 Errors
Programs meet missing fields, odd encodings and huge lines. Deliberate handling — catching specific errors, counting them and continuing — keeps analyses complete and honest.
Catching every error with one broad handler hides real bugs, such as a mistyped variable name. Catching specific errors, such as a value that cannot be parsed as a time, handles expected problems while letting unexpected ones surface. The count of handled errors goes into the output so readers know how many records were set aside. Silence is worse.
9.4 Unit tests
Python's unittest module lets Amara write tests such as "five failures for distinct users in one network should trigger; four should not". Tests run automatically before each change is merged.
A pair of tests on either side of the threshold pins down exactly where the rule's behaviour changes. Running tests automatically before merging means a later change that shifts the threshold by accident fails immediately, rather than being discovered when a real attack slips through. Tests also serve as documentation of what the rule is meant to do.
9.5 Edge cases
Boundary values (exactly the threshold), empty input and duplicate events are where detection code usually breaks. Tests target them first.
A rule that triggers on 'more than five' and one that triggers on 'five or more' differ only at exactly five, and that is where bugs hide. Empty input tests that the code reports nothing rather than crashing. Duplicate events, common when a log forwarder retries, test that the same failure is not counted twice. Amara lists these cases before writing the rule, not after a bug appears.
9.6 Performance
For a day of portal logs (2 million lines), reading line by line uses little memory; loading all into a list may exhaust it.
Two million lines of a few hundred bytes each can occupy far more memory once each line becomes a separate Python object. Reading one line at a time and updating counters keeps memory use flat. Amara measures both approaches on a sample day and records the peak memory of each, which justifies the streaming design in the review.
9.7 Reviews
Paolo reviews code for correctness and clarity; a second pair of eyes catches logic slips that tests missed.
Tests check what the author thought to test; a reviewer asks about what was not considered. Paolo spots that Amara's code treats usernames as case-sensitive while the portal does not, so 'U5' and 'u5' were counted separately. A test is added for that case, and the review notes become a checklist for future detection code. Reviews are logged with the reviewer's name, so later readers know someone checked the code before it went live.
Logging inside tools
Amara's tools log their own actions: input file, its hash, records read, records skipped and output written. When a result is questioned weeks later, the tool's log shows exactly what it processed. This self-logging costs a few lines of code and saves hours of doubt.
Worked example
Amara's test for "exactly 5 failures" fails: her code uses "> 5" while the design says "at least 5". She asks Paolo which is intended; they choose "≥ 5", update the design document and the code together, and keep the test.
Synthetic case. Tests and data are synthetic.
Errors and limits
Tests encode understanding at the time written; if the threat changes, tests may enforce outdated logic.
Exercises
- EX025 — Data structure choice. Which structure suits counting failures per user, and why? Rubric: 3 = dictionary: fast keyed updates; 2 = partial; 1 = list; 0 = none.
- EX026 — Boundary bug. What did the "exactly 5" test reveal, and how was it fixed? Rubric: 3 = code used > 5 vs design ≥ 5; aligned design, code and test; 2 = partial; 1 = ignored; 0 = none.
- EX027 — Streaming. Why read large logs line by line? Rubric: 3 = low memory use; avoids exhausting memory; 2 = partial; 1 = faster always; 0 = none.
Synthesis
Structured code, suitable data structures and tests on edge cases make detection logic reliable.
Continue studying — chapter 09 of 50
← Chapter 08 (draft) · Course chapter index · Chapter 10 (draft) →