Skip to content
VLSI Mentor

Python Part I · Module 11 · Functions

Why Functions Exist: The Same Code, Three Times

Every construct so far arrived because the previous one could not express something: conditionals because a script could not react, loops because it could not repeat. Functions arrive the same way, and the gap they fill is not about what a program can compute — everything in this chapter is already writable with what you have. It is about what happens to that program six months later, when the rule it encodes changes and the code that encodes it exists in three places. This chapter is the problem rather than the solution. The syntax is one line and it is the next chapter's; what is worth your attention first is why anyone reached for it.

Foundation15 min readPythonPart IFundamentalsFunctionsMaintainability

Part I · Module 11 · Chapter 11.1 · Why Functions Exist

1. A Job Worth Six Lines

Counting the errors in a simulation log is six lines, and you can write all of them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
log_a = [
    "UVM_INFO  @0: reset asserted",
    "UVM_ERROR @120: crc mismatch",
    "UVM_INFO  @200: burst complete",
    "UVM_ERROR @410: unexpected BRESP",
]

errors_a = 0
for line in log_a:
    if "UVM_ERROR" in line:
        errors_a += 1

print("log A errors:", errors_a)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
log A errors: 2

That is Chapter 10.10's accumulator, doing a real job. Nothing about it needs improving.

2. The Same Six Lines, Three Times

Now there are three logs — one per simulator, or one per nightly run — and the same question for each:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
log_a = ["UVM_INFO @0: start", "UVM_ERROR @120: crc mismatch", "UVM_ERROR @410: bad BRESP"]
log_b = ["UVM_INFO @0: start", "UVM_ERROR @90: timeout"]
log_c = ["UVM_INFO @0: start", "UVM_INFO @300: done"]

errors_a = 0
for line in log_a:
    if "UVM_ERROR" in line:
        errors_a += 1

errors_b = 0
for line in log_b:
    if "UVM_ERROR" in line:
        errors_b += 1

errors_c = 0
for line in log_c:
    if "UVM_ERROR" in line:
        errors_c += 1

print("A:", errors_a, "B:", errors_b, "C:", errors_c)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: 2 B: 1 C: 0

The answers are right. The program is a problem, and not for the reason people usually give first.

3. It Is Not About Typing

The obvious complaint is that the loop was typed three times. That is the least interesting thing wrong with it — typing is cheap, and copying is cheaper still.

The expensive property is this: the rule for what counts as an error now lives in three places. Not the loop, not the counter — the rule. And rules change.

4. What Happens When the Rule Changes

A UVM_FATAL should obviously count as an error too. So you fix it. You are reading the first block when you make the change, and you fix the one in front of you:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
log_a = ["UVM_INFO @0: start", "UVM_ERROR @120: crc", "UVM_FATAL @410: protocol"]
log_b = ["UVM_INFO @0: start", "UVM_ERROR @90: timeout", "UVM_FATAL @95: abort"]
log_c = ["UVM_INFO @0: start", "UVM_FATAL @300: bus hang"]

errors_a = 0
for line in log_a:
    if "UVM_ERROR" in line or "UVM_FATAL" in line:
        errors_a += 1

errors_b = 0
for line in log_b:
    if "UVM_ERROR" in line:
        errors_b += 1

errors_c = 0
for line in log_c:
    if "UVM_ERROR" in line:
        errors_c += 1

print("A:", errors_a, "B:", errors_b, "C:", errors_c)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: 2 B: 1 C: 0

Read those three numbers against the three logs. Log A has one error and one fatal, and reports 2. Log B has exactly the same shape — one error, one fatal — and reports 1. Log C has a bus hang and reports 0.

Nothing raised. No validator could object: all three blocks are valid Python, and two of them are exactly what they were yesterday. The script now answers the same question three different ways, and the only evidence is that you happen to know logs B and C contain fatals.

5. Count the Edit Sites

The cost can be stated as a number rather than a feeling. Here are four changes this code is genuinely likely to need:

ChangeEdits required
count UVM_FATAL as well3
ignore lines inside a commented block3
stop counting after the first fatal3
also record the line number of the first error3

Twelve edits. Each one has to land in three places, and missing any single one produces the silent disagreement in Section 4. Multiply by the real number of logs a regression script handles — ten simulators, forty tests — and the arithmetic stops being survivable.

The number that matters is not how many lines the program has. It is how many places you must change to change one idea. For a well-built program that number is one.

6. Naming Is the Other Half

There is a second cost, and it is the one people notice later.

Read the three blocks again. To know what any of them does, you must read all six of their lines and reconstruct the intent: a counter, a loop, a membership test, an increment — ah, it is counting errors. The code says how; it never says what.

A reader coming to this script for the first time does that reconstruction three times, for three blocks that turn out to mean the same thing. Six months later, so do you.

7. What the Problem Is Asking For

Everything above points at one shape: a way to write the rule once, give it a name, and apply it to different data.

That is a function, and here is the whole program again with one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(log):
    errors = 0
    for line in log:
        if "UVM_ERROR" in line or "UVM_FATAL" in line:
            errors += 1
    return errors


log_a = ["UVM_INFO @0: start", "UVM_ERROR @120: crc", "UVM_FATAL @410: protocol"]
log_b = ["UVM_INFO @0: start", "UVM_ERROR @90: timeout", "UVM_FATAL @95: abort"]
log_c = ["UVM_INFO @0: start", "UVM_FATAL @300: bus hang"]

print("A:", count_errors(log_a))
print("B:", count_errors(log_b))
print("C:", count_errors(log_c))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: 2
B: 2
C: 1

Three correct answers, because there is now exactly one rule. Counting UVM_FATAL was a change to one line, and no copy could have been missed because there are no copies.

Note what the call sites gained as well. count_errors(log_b) says what it does. The six lines it replaced did not.

8. What a Function Is Not

Three things it is worth not believing from the start:

It is not a performance optimisation. Calling a function costs a small amount of time rather than saving any. The three-copy version in Section 2 is, if anything, marginally faster. You extract functions for correctness and readability; speed is not the reason and is rarely affected either way.

It is not about making files shorter. The Section 7 version has roughly as many lines as the Section 2 version. What changed is how many places one idea lives in.

It is not only for code used more than once. Section 6's point: a name earns its place by explaining, not by being reused.

9. Predict, Run, Observe

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# A — how many places encode "what counts as an error"?
a = 0
for line in ["E1", "I1"]:
    if "E" in line:
        a += 1
b = 0
for line in ["E1", "E2"]:
    if "E" in line:
        b += 1
print("A:", a, b)

# B — the same two answers, one rule
def count(lines):
    n = 0
    for line in lines:
        if "E" in line:
            n += 1
    return n

print("B:", count(["E1", "I1"]), count(["E1", "E2"]))

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: 1 2
B: 1 2

Identical answers, which is the point: the second version is not more correct today. It is correct in one place instead of two, so it is still correct tomorrow after the rule changes — and the first version is correct only if whoever changes it remembers both copies.

10. Exercises

Exercise 1 — count the edit sites. Take the Section 2 program. Write down how many lines you must change to (a) count UVM_WARNING too (b) report the count as a percentage of all lines (c) skip blank lines. Give the total.

Exercise 2 — make it drift. Copy the Section 2 program and apply change (a) from Exercise 1 to only the first block. Run it on three logs that each contain one warning, and write down the three numbers. Explain in two sentences why nothing warned you.

Exercise 3 — name the block. For each six-line block below, give the name you would put on it if it became a function, in three words or fewer: (a) a loop that totals runtimes (b) a loop that collects failing test names (c) a loop that finds the longest signal name (d) a loop that counts each severity into a dictionary.

Exercise 4 — one rule, three uses. Rewrite your answer to Exercise 1(a) using the Section 7 function so that change (a) is a single-line edit. Confirm all three logs now agree.

Exercise 5 — single use. Find a block of five or more lines in anything you have written in Modules 9 or 10 that is used exactly once, and give it a name. Say in one sentence what the name tells a reader that the lines did not.

Exercise 6 — what it is not. In one sentence each, say why a function is not primarily about: (a) speed (b) file length (c) code used more than once.

Exercise 7 — the three answers. Run Section 4's program. Before reading the output, predict the three numbers from the three logs. Then explain which two are wrong and why the program cannot tell.

Exercise 8 — your own duplication. Describe, in four sentences, a rule in a script you have written or read that lives in more than one place. Say what would have to change for it to live in one.

11. Summary

  • A function is not needed to compute anything in this chapter — everything here is writable with Modules 1 to 10.
  • The problem is maintenance, not typing. Copying code is cheap; keeping copies in agreement is not.
  • Duplicated code drifts. Copies break one edit at a time, and each individual copy still looks like correct code.
  • Drift is silent. Three blocks reporting three different answers for the same question raise nothing.
  • Count the edit sites. Four likely rule changes across three copies is twelve edits, any one of which can be missed.
  • The number that matters is how many places you must change to change one idea. It should be one.
  • Naming is a separate win from de-duplication, and applies more often — a single-use block is still worth a name if the name explains it.
  • A function is not a speed optimisation, not a way to shorten a file, and not only for repeated code.

The next chapter takes the shape this problem asked for and defines each part of it properly.

Where this fits

Part of the Python curriculum.