Writing a function: the design recipe

The order to write a function in, and how to write examples that fail when the body is wrong.

The order to write things in, and the order they end up in the file. It is not specific to C: these are the six steps from How to Design Programs, which uses a different language and the same recipe.

C forces two of the steps on you. A function has to be declared before the line that calls it, so the signature gets written down before the body whether you meant to or not. That is the one thing about C that is on your side.

The function below is the one A3 opens with, so you can watch the recipe produce something you have already been handed.

The six steps

  1. Work out what goes in and what comes out. Write the computation twice, with real numbers in it. “The larger of 6 and 10 is 10. The larger of 11 and 3 is 11.” Then compare the two: what differs becomes the parameters, what stays the same becomes the body. Each of those has a type, and so does the answer.

    If you cannot write the two, you are not ready to write the function — that is information, not failure.

  2. Signature, purpose, stub. In C the signature is not a comment. It is the prototype, with a one-line comment above it saying what the function computes:

    // returns the larger of a and b
    int max(int a, int b);

    Then a stub — the same line with a body that returns anything at all of the right type:

    int max(int a, int b) {
        return 0;
    }

    The stub is not wasted work. It compiles, it links, and main can call it, so every part of the program except the answer is known to work before you have written any arithmetic. When something breaks after step 5, the stub is what tells you the break is in the body.

  3. Examples — at least three, and let the machine check them. An example written in a comment cannot fail, so it cannot tell you anything. Write them as assert, one line each. assert(x) does nothing when x is true and stops the program when it is false:

    #include <stdio.h>
    #include <assert.h>
    
    int main() {
        assert(max(6, 10) == 10);
        assert(max(11, 3) == 11);
        assert(max(4, 4) == 4);
        printf("all examples passed\n");
    }

    Run that now, against the stub. The first one fails, and that is the correct state of affairs before a body exists:

    max: max.c:12: main: Assertion `max(6, 10) == 10' failed.
    Aborted (core dumped)

    It names the expression and the line it is on. That is the program stopping on purpose, not a crash — it is C’s version of a failed test, and the abort is how it gets your attention.

    • The last printf is not decoration. assert is silent when it passes, so without that line a program where everything works prints nothing at all, and you cannot tell it from a program you forgot to run.
    • Write the answer, not the way to get it. == 10, not == (a > b ? a : b), and for a function with a formula in it, == 30 rather than == 90 / 3. An answer you worked out yourself can disagree with your body. A formula copied out of your body cannot.
    • Vary every parameter. One that is 3 in all three examples is a parameter you have not tested.
    • Make one of them awkward — a zero, a one, two arguments that are equal, something that does not divide evenly. count_digits(0) is the awkward case that caught most of the class out in A2, and an assert would have caught it instead.
  4. Take inventory. Before writing the body, say what you have to work with: the parameters, and any constants in the file above you. For max that is a and b and nothing else, which is why this step looks thin right now. It is the step that starts earning its place once a parameter is an array or a struct and the pieces you can reach are no longer just the names in the signature.

  5. Body. Last. By now the types, the purpose and the answers are all settled, so there is very little left to get wrong:

    int max(int a, int b) {
        if (a > b) {
            return a;
        }
        return b;
    }
  6. Compile and run. You get all examples passed, or you get an assertion naming the line that went wrong. Then break it on purpose: change > to <, rebuild, and watch it fail. Do that once, deliberately, so you know what failure looks like before it happens by accident.

    When an assertion fails there are three possibilities, and they are worth ruling out in this order: you worked the expected answer out wrong, or the body is wrong, or both. Satisfy yourself the expected answer is right first, then go looking in the body.

Two notes on vocabulary

The signature used to be called the contract, and you will meet both words. In C it is also called the prototype when it is written on its own with a semicolon, which is the form that goes in a header file.

The recipe is from How to Design Programs, §3.1. It is not assigned reading for this course and you do not need it — the six steps above are the whole of it for functions this small.