Skip to main content

Section 12.4 Code Reuse and Abstractions

Two key reasons to write a function are abstraction and code reuse. Our “better” version of calculateTriangleArea provides an opportunity to write a new function that addresses both principles. The start of main has this ugly chunk of code:
double side1, side2, side3;
cout << "Enter the length of the first side of the triangle: ";
cin >> side1;
cout << side1 << endl; // echo the input
cout << "Enter the length of the second side of the triangle: ";
cin >> side2;
cout << side2 << endl; // echo the input
cout << "Enter the length of the third side of the triangle: ";
cin >> side3;
cout << side3 << endl; // echo the input
We repeat more or less the same code three times, once for each side of the triangle. This is a violation of the DRY principle (Don’t Repeat Yourself). If we were trying to validate each input (say check if each side was a positive number), it would get even more ugly. Then we might then have three copies of code like if (side1 <=0) ....
One way to avoid repetition is with a loop. We could try to use a loop to repeat “get a side” three times. But another way to avoid repetition is to make a function that has the repeated code.
To do this, we need to identify a group of lines that works together to calculate a single value and is then repeated multiple times. In this case, we have three repetitions of “Print prompt, read in value, print to confirm”. Each of those repetitions results in a single piece of information, a double represent a side length. So we can break that code out into a function double getDoubleInput that will perform the three steps and return the side length.
We will need to call the function three times. Anything that needs to change from one repetition to the next can be passed in as a parameter to the function. In this case, we need to print a different message each time we get input. So we will make the prompt a parameter. That way, we can customize the message for each side of the triangle. We now have a function double getDoubleInput(const string& prompt):
#include <iostream>
#include <cmath>
#include <format>
#include <string>
using namespace std;

double getDoubleInput(const string& prompt) {
    cout << prompt;
    double value;
    cin >> value;
    cout << value << endl;  // echo the input
    return value;
}

double calculateTriangleArea(double side1, double side2, double side3) {
    double s = (side1 + side2 + side3) / 2;  // Semi-perimeter
    double area = sqrt(s * (s - side1) * (s - side2) * (s - side3));  // Heron's formula
    return area;
}

int main() {
    double side1 = getDoubleInput("Enter the length of the first side of the triangle: ");
    double side2 = getDoubleInput("Enter the length of the second side of the triangle: ");
    double side3 = getDoubleInput("Enter the length of the third side of the triangle: ");

    double area = calculateTriangleArea(side1, side2, side3);

    cout << format("The area of the triangle to one decimal is: {:.1f}", area) << endl;
}
Not only have we eliminated duplication, we have made it easier to make future changes to the input. Say we want to add a check for negative input—if the user enters a negative value, we will ask for new input. In this new version of the program, we can modify the getDoubleInput function and all three inputs will then take advantage of the new code.
Writing a function also provides a nice abstraction for the “get input” logic. In the original version, it takes a few seconds of reading to understand what that part of the main function is doing. After scanning it, a programmer hopefully thinks something like “oh, that whole block just gets the three sides” and then doesn’t worry about the details of how that happens (unless that is the part of the code they are trying to work on!).
In the new version, it is easier to understand what the first part of the main function. We can see that it is getting the three sides of the triangle without having to worry about the details of how that is done. It might be worth writing the getDoubleInput function even if we only use it once (and thus get no code reuse benefit) because it still makes the code clearer.

Insight 12.4.1.

Good code is self-documenting. It should be clear what the code is doing without needing extensive comments. The abstractions produced by well designed functions help make code more understandable.

Checkpoint 12.4.1.

Here is part of a program that involves calculating the shipping for two orders:
double shippingCost1 = 0;
if (order1Cost < 100) {
    shippingCost1 = order1Weight * 3.50;
}
if (order1IsExpressHandling) {
    shippingCost1 += 20;
}
double shippingCost2 = 0;
if (order2Cost < 100) {
    shippingCost2 = order2Weight * 3.50;
}
if (order2IsExpressHandling) {
    shippingCost2 += 20;
}
Design a function to avoid the repetition. You will not use all of the blocks.

Checkpoint 12.4.2.

Complete a function call to use the function from Checkpoint 12.4.1 to calculate the value for shippingCost1.
You have attempted of activities on this page.