A beginner’s guide to function design in C/C++

This post was originally written for students in Bunker Hill’s CIT 120 course.

Quite a few beginning CS students have trouble with function design. They know the syntax of functions, how to write headers, etc.; but they’re having trouble actually deciding what functions should and should not do, how to organize them, and so forth.

This falls under the category of software design (as opposed to writing language-specific code). Software design is by no means a trivial subject; even experienced programmers find it hard to make design decisions. Additionally, most introductory CS courses do not deal with the design of objects and classes (the traditional subject of most tomes on software design), but on function design, which is only briefly touched upon in many books. So it is no surprise that many students feel completely lost when tackling it for the first time.

In most introductory courses, you will be writing functions that should fall into five basic categories, according to which task they are performing. Keep in mind that functions should have only one purpose, so should do only one of these tasks, and nothing more.

  1. Validation
    This is the type of function that students seem to have the most problems with. These are functions whose sole purpose is to get a valid data from the user.

    • Since the functions are getting data from the user, their names should start with “get”, and the rest of the name should describe what is being returned. Examples: getNum, getInt, getPrime.
    • The return type should be consistent with the type of data that is expected. For example, getInt() should return an integer (not, say, a double).
    • The functions should not include the initial prompt.
    • You should only pass data to the function if it is absolutely necessary. For example, getIntGreaterThan would need to be passed the lower bound (if the user enters a number less than the lower bound, it is invalid). On the other hand, getInt should not be passed anything, since the user can enter any integer without restriction.
    • If the validation function calls another validation function (e.g. getInt calling getNum), list the other validation functions in the pre-conditions. Post-conditions would be a description of what is returned; the fact that the function prints an error message; and whether the input stream is cleared (it usually is).

    The general algorithm for a validation function is this:

    declare a variable;
    read user data into that variable;
    while (data is invalid) {
      print an error message;
      read new user data into the variable;
    }
    return the value of the variable;


    Note that the variable to be returned is declared inside the function; it is not a function parameter. Also, note that this variable might not be of the same type as the function’s return type. In that case, you should cast it to the function’s return type as you are returning it.

    Also note that the error message should be brief, but informative, and should only mention the type of validation that failed; in all other ways, it should be as generic as possible. For example, if you are getting a double from the user, and the user types a letter, you would output something like “Please do not type letters.” What you should not output is something like “The test score cannot accept letters.”

    There is one other thing to recognize about validation functions. Once you have written one – say, getInt – then you should call that function, every time you need to get any integer from the user, anywhere in the program. (This is one reason it is important not to include the initial prompt in validation functions, and why the error messages should be generic.)

    This includes other functions – and particularly other validation functions. So, for example, getNum would get a number from the user (validating that the user didn’t enter a letter). Then, getInt should call getNum when it needs to get a number from the user. It would only validate whether that number is an integer (as opposed to a floating-point number); it would not validate whether the user entered a character, because getNum already took care of that.

    This is one of those situations where the variable declared inside the function would be different from the return type: you need a double to store the results of getNum, but you need to cast it to an integer to return it from the getInt function.

    As you can probably tell, this means that validation functions often have a “chain of command” from most specific, to least specific type of data being validated. But each “link” in this chain can be called independently in main (or anywhere else), so you can call whichever one is the most useful.

  2. Calculation
    This type of function performs calculations on data. The functions in the cmath library would all fall under this category. I think most students understand this type of function pretty well.

    • A common prefix for this type of functions is “calc,” but if it’s obvious from the name that the function is calculating something, the prefix isn’t necessary.
    • Again, the return type should again be consistent with the type of data that is expected. For math functions, it’s probably best to return a double.
    • The functions should not perform any input or output whatsoever.
    • These functions will always have parameters that are the data to calculate. Examples: the sqrt function is passed the number to take the square root of; the pow function is passed the number to raise, and the power to raise it to.
    • Pre-conditions are very important with these types of functions. You should list the restrictions on data passed to them (to avoid divide-by-zero errors, for example). You would also need to say what libraries need to be imported (e.g. the cmath library). Post-conditions would be what the function returns.

    There really isn’t a general algorithm for these; each calculation is different.

    However, you should keep in mind that the arguments to the function are the raw data, and what is returned is the result of the calculations on that data. When you call the function, the returned value is assigned to a variable that holds the result; you should not pass that variable as an argument to the function.

    Also, keep in mind that it is not the job of calculation functions to make sure the values passed to them are valid. For example, if you try to call the sqrt function with a negative number, the result is undefined (and in fact each compiler handles it differently). Making sure the values are OK is the job of the code that calls the function. That’s why we write out the pre-conditions: so that whoever is using the function library will be able to make sure they pass valid data.

  3. Printing (Output)
    These functions simply output data. Usually, it is output to the console, but at the end of the semester, you’ll see how to do files.

    • The prefix should be “print”.
    • The return type is always void.
    • The functions should only perform output, not input or calculations.
    • For functions like printBanner or printMenu, where the output doesn’t change, you should not pass any arguments to the function. On the other hand, you’ll often be printing data (like a table). You will need to pass the data that will be printed, and (sometimes) certain formatting options, like the width of a table’s columns, or the number of characters to print per line.
    • Pre-conditions should include any restrictions on data ranges (if any). Also, if you require any libraries (like iomanip), those should be listed as well. Post-conditions are usually a description of what is output.

    A good example would be a printOrdinal function. You would pass it a number, and it would output the ordinal suffix (“st”, “nd”, etc) for that number.

    If you do pass arguments to a print function, the arguments should always be either passed by value, or declared const, so that there is no chance they will be altered in any way. (By default, primitive types – char, int, double, etc. – are passed by value; you only have to worry about this if you’re passing arrays or objects.)

    Checking (Testing)
    These functions take a value, and returns true if the value matches some criteria.

    • The prefix should be “is” (or occasionally “has”). Examples: isPrime, isUpper.
    • The return type is always bool.
    • The functions should not perform any input or output whatsoever.
    • You need to pass the value to test to the function. Usually, this will only be a single argument, but there are times when you will need more than one. (For example, an isNumBetween function will need three: the number to test, the lower bound, and the upper bound).
    • Pre-conditions would be any restrictions on the value that is tested, and of course any libraries that must be imported. Post-conditions would be a description of what the function returns.

    These functions may seem like they’re validation functions, but they’re really not. On the other hand, validation functions often call checking functions (usually within the parentheses of its while loop). This is very common when the validation is too complicated to perform in a single expression; an example of this is isPrime.

    In this situation, the calling function (e.g. getPrime) does very little, and the function that is called (e.g isPrime) does most of the calculations. In cases like these, the function that does most of the calculations is called a slave function.

    You also have to be careful to return the correct value; it should return true if it matches some criteria, and false otherwise. For example, you should write a function called isPrime which returns true if the number is prime. You should not write a function called testPrime that returns true if the number is not prime.

  4. Classification
    These functions take a value, and classify it in some way. An example of this is the compare function from the string library (or strcmp from the cstring library; they work almost identically). The function compares two strings, and returns a positive, zero, or negative value, depending on whether the first string is greater than, equal to, or less than the second string.

    • There is no standardized prefix for this. If the classification is a comparison, it probably wouldn’t be a bad idea to use “cmp” (for “compare”).
    • The return type is usually an integer type, with positive, negative, and zero values representing different categories. Positive represents “greater” in some way, negative represents “lesser” in some way, and zero represents “equality.” This is not always true – obviously it wouldn’t work if you have more than three categories. But it is a good rule of thumb.
    • The functions should not perform any input or output whatsoever.
    • You need to pass the value to classify to the function. If the classification is a comparison, you will need to pass a second value to compare to the first. When dealing with two values, the return value should indicate the relationship of the second value to the first value, and not the other way around. For example, if x is 3 and y is 4, cmpIntegers(x, y) would return -1, not 1.
    • Pre-conditions would be any restrictions on the value that is categorized, and any libraries that must be imported. Post-conditions would be a description of what the function returns. Since the values returned from comparison functions cannot be standardized, make sure you include the meaning of those values in your post-conditions!

    Sometimes, students think these functions are “checking” functions, but they are not. For example, you can use the compare function to check whether two strings are the same (by checking whether the returned value is zero). But it is not a “checking” function, since it does not return a boolean.

    By the way: those who study Java will use this type of function a lot. (You will need to write this type of function whenever you implement the Comparable interface. The method in Comparable is called compareTo).

These are the general tasks that a function is to perform. If your function is not performing one of these tasks – or it is performing more than one – then your function is badly designed, and you should consider re-designing it.

In a program with properly desinged functions, main itself will have very little code. Instead, it will delegate all of its tasks to functions (which will often, in turn, delegate sub-tasks to different functions). For most of the programs you will write in CIT 120, the main function should not be more than twenty or thirty lines long, at most, and basically be a list of function calls.

Incidentally, software design is one of those areas where it really does help to work things out ahead of time, on paper, before you even look at a computer. It may seem like a waste of time, but it’s not. Once you’ve worked everything out on paper, it will take you far less time to actually write the code. I advise doing this even in the midterms and finals.

Hope this is helpful!

Advertisements

About Karl

I live in the Boston area, and am currently studying for a BS in Computer Science at UMass Boston. I graduated with honors with an AS in Computer Science (Transfer Option) from BHCC, acquiring a certificate in OOP along the way. I also perform experimental electronic music as Karlheinz.
This entry was posted in c++, Programming and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s