Skip to main content
Engineering LibreTexts

1.2: Designing a Riddle Program

  • Page ID
    15057
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    The first step in the program-development process is making sure you understand the problem (Fig. [fig:progdev]). Thus, we begin by developing a detailed specification, which should address three basic questions:

    1. What exactly is the problem to be solved?
    2. How will the program be used?
    3. How should the program behave?

    In the real world, the problem specification is often arrived at through an extensive discussion between the customer and the developer. In an introductory programming course, the specification is usually assigned by the instructor.

    To help make these ideas a little clearer, let’s design an object-oriented solution to a simple problem.

    Problem Decomposition

    Most problems are too big and too complex to be tackled all at once. So the next step in the design process is to divide the problem into parts that make the solution more manageable. In the object-oriented approach, a problem is divided into objects, where each object will handle one specific aspect of the program’s overall job. In effect, each object will become an expert or specialist in some aspect of the program’s overall behavior.

    Note that there is some ambiguity here about how far we should go in decomposing a given program. This ambiguity is part of the design process. How much we should decompose the program before its parts become “simple to solve” depends on the problem we’re trying to solve and on the problem solver.

    One useful design guideline for trying to decide what objects are needed is the following:

    Again, there’s some ambiguity involved in this guideline. For example, the key noun in our current problem is riddle, so our solution will involve an object that serves as a model for a riddle. The main task of this Java object will be simply to represent a riddle. Two other nouns in the specification are question and answer. Fortunately, Java has built-in String objects that represent strings of characters such as words or sentences. We can use two String objects for the riddle’s question and answer. Thus, for this simple problem, we need only design one new type of object—a riddle—whose primary role will be to represent a riddle’s question and answer.

    Don’t worry too much if our design decisions seem somewhat mysterious at this stage. A good understanding of object-oriented design can come only after much design experience, but this is a good place to start.

    Object Design

    Once we have divided a problem into a set of cooperating objects, designing a Java program is primarily a matter of designing and creating the objects themselves. In our example, this means we must now design the features of our riddle object. For each object, we must answer the following basic design questions:

    1. What role will the object perform in the program?
    2. What data or information will it need?
    3. What actions will it take?
    4. What interface will it present to other objects?
    5. What information will it hide from other objects?

    For our riddle object, the answers to these questions are shown in Figure [fig:specs]. Note that although we talk about “designing an object,” we are really talking about designing the object’s class. A class defines the collection of objects that belong to it. The class can be considered the object’s type. This is the same as for real-world objects. Thus, Seabiscuit is a horse—that is, Seabiscuit is an object of type horse. Similarly, an individual riddle, such as the newspaper riddle, is a riddle. That is, it is an object of type Riddle.

    The following discussion shows how we arrived at the decisions for the design specifications for the Riddle class, illustrated in Figure [fig:specs].

    fig:specs

    The role of the Riddle object is to model an ordinary riddle. Because a riddle is defined in terms of its question and answer, our Riddle object will need some way to store these two pieces of information. As we learned in Chapter [chapter-intro], an instance variable is a named memory location that belongs to an object. The fact that the memory location is named, makes it easy to retrieve the data stored there by invoking the variable’s name. For example, to print a riddle’s question we would say something like “print question,” and whatever is stored in question would be retrieved and printed.

    In general, instance variables are used to store the information that an object needs to perform its role. They correspond to what we have been calling the object’s attributes. Deciding on these variables provides the answer to the question, “What information does the object need?”

    Next we decide what actions a Riddle object will take. A useful design guideline for actions of objects is the following:

    For this problem, the key verbs are set and retrieve. As specified in Figure [fig:specs], each Riddle object should provide some means of setting the values of its question and answer variables and a means of retrieving each value separately.

    Each of the actions we have identified will be encapsulated in a Java method. As you recall from Chapter [chapter-intro], a method is a named section of code that can be invoked, or called upon, to perform a particular action. In the object-oriented approach, calling a method (method invocation) is the means by which interaction occurs among objects. Calling a method is like sending a message between objects. For example, when we want to get a riddle’s answer, we would invoke the getAnswer() method. This is like sending the message “Give me your answer.” One special method, known as a constructor, is invoked when an object is first created. We will use the Riddle() constructor to give specific values to riddle’s question and answer variables.

    In designing an object, we must decide which methods should be made available to other objects. This determines what interface the object should present and what information it should hide from other objects. In general, those methods that will be used to communicate with an object are designated as part of the object’s interface. Except for its interface, all other information maintained by each riddle should be kept “hidden” from other objects. For example, it is not necessary for other objects to know where a riddle object stores its question and answer. The fact that they are stored in variables named question and answer, rather than variables named ques and ans, is irrelevant to other objects.

    Taken together, these various design decisions lead to the A UML class diagram representing the Riddle class. fig:rumlspecification shown in Figure [fig:ruml]. As our discussion has illustrated, we arrived at the decisions by asking and answering the right questions. In most classes the attributes (variables) are private. This is represented by a minus sign (\(-\)). In this example, the operations (methods) are public, which is represented by the plus sign (\(+\)). The figure shows that the Riddle class has two hidden (or private) variables for storing data and three visible (or public) methods that represent the operations that it can perform.

    Data, Methods, and Algorithms

    Among the details that must be worked out in designing a riddle object is deciding what type of data, methods, and algorithms we need. There are two basic questions involved:

    1. What type of data will be used to represent the information needed by the riddle?
    2. How will each method carry out its task?

    Like other programming languages, Java supports a wide range of different types of data, some simple and some complex. Obviously a riddle’s question and answer should be represented by text. As we noted earlier, Java has a Stringtype, which is designed to store text, which can be considered a string of characters.

    In designing a method, you have to decide what the method will do. In order to carry out its task, a method will need certain information, which it may store in variables. Plus, it will have to carry out a sequence of individual actions to perform the task. This is called its algorithm, which is a step-by-step description of the solution to a problem. And, finally, you must decide what result the method will produce. Thus, as in designing objects, it is important to ask the right questions:

    1. What specific task will the method perform?
    2. What information will it need to perform its task?
    3. What algorithm will the method use?
    4. What result will the method produce?

    Methods can be thought of as using an algorithm to complete a required action. The algorithm required for the Riddle()constructor is very simple but also typical of constructors for many classes. It takes two strings and assigns the first to the question instance variable and then assigns the second to the answer instance variable. The algorithms for the other two methods for the Riddle class are even simpler. They are referred to as get methods that merely return or produce the value that is currently stored in an instance variable.

    Not all methods are so simple to design, and not all algorithms are so simple. Even when programming a simple arithmetic problem, the steps involved in the algorithm will not always be as obvious as they are when doing the calculation by hand. For example, suppose the problem were to calculate the sum of a list of numbers. If we were telling our classmate how to do this problem, we might just say, “add up all the numbers and report their total.” But this description is far too vague to be used in a program. By contrast, here’s an algorithm that a program could use:

    Set the initial value of the sum to 0.

    If there are no more numbers to total, go to step 5.

    Add the next number to the sum.

    Go to step 2.

    Report the sum.

    Note that each step in this algorithm is simple and easy to follow. It would be relatively easy to translate it into Java. Because English is somewhat imprecise as an algorithmic language, programmers frequently write algorithms in the programming language itself or in pseudocode, a hybrid language that combines English and programming language structures without being too fussy about programming language syntax. For example, the preceding algorithm might be expressed in pseudocode as follows:

    sum = 0
    while (more numbers remain)
        add next number to sum
    print the sum

    Of course, it is unlikely that an experienced programmer would take the trouble to write out pseudocode for such a simple algorithm. But many programming problems are quite complex and require careful design to minimize the number of errors that the program contains. In such situations, pseudocode could be useful.

    Another important part of designing an algorithm is to trace it—that is, to step through it line by line—on some sample data. For example, we might test the list-summing algorithm by tracing it on the list of numbers shown in the margin.

    Initially, the sum starts out at 0 and the list of numbers contains 54, 30, and 20. On each iteration through the algorithm, the sum increases by the amount of the next number, and the list diminishes in size. The algorithm stops with the correct total left under the sum column. While this trace didn’t turn up any errors, it is frequently possible to find flaws in an algorithm by tracing it in this way.

    Coding into Java

    Once a sufficiently detailed design has been developed, it is time to start generating Java code. The wrong way to do this would be to type the entire program and then compile and run it. This generally leads to dozens of errors that can be both demoralizing and difficult to fix.

    The right way to code is to use the principle of stepwise refinement. The program is coded in small stages, and after each stage the code is compiled and tested. For example, you could write the code for a single method and test that method before moving on to another part of the program. In this way, small errors are caught before moving on to the next stage.

    The code for the Riddle class is shown in Figure [fig:riddleclass]. Even though we have not yet begun learning the details of the Java language, you can easily pick out the key parts in this program: the instance variables question and answer of type String, which are used to store the riddle’s data; the Riddle() constructor and the getQuestion() and getAnswer() methods make up the interface. The specific language details needed to understand each of these elements will be covered in this and the following chapter.

    /*
     * File: Riddle.java
     * Author: Java, Java, Java
     * Description: Defines a simple riddle.
     */
    public class Riddle extends Object  // Class header
    {                                   // Begin class body
       private String question;       // Instance variables
       private String answer;
    
       public Riddle(String q, String a) // Constructor method
       {
         question = q;
         answer = a;
       } // Riddle()
    
       public String getQuestion()   // Instance method
       {
         return question;
       } // getQuestion()
    
       public String getAnswer()     // Instance method
       {
         return answer;
       } //getAnswer()
    } // Riddle class                  // End class body

    Syntax and Semantics

    Writing Java code requires that you know its syntax and semantics. A language’s syntax is the set of rules that determines whether a particular statement is correctly formulated. As an example of a syntax rule, consider the following two English statements:

    The rain in Spain falls mainly on the plain. // Valid
    Spain rain the mainly in on the falls plain. // Invalid

    The first sentence follows the rules of English syntax (grammar), and it means that it rains a lot on the Spanish plain. The second sentence does not follow English syntax, and, as a result, it is rendered meaningless. An example of a Java syntax rule is that a Java statement must end with a semicolon.

    However, unlike in English, where one can still be understood even when one breaks a syntax rule, in a programming language the syntax rules are very strict. If you break even the slightest syntax rule—for example, if you forget just a single semicolon—the program won’t work at all.

    Similarly, the programmer must know the of the language—that is, the meaning of each statement. In a programming language, a statement’s meaning is determined by what effect it will have on the program. For example, to set the sumto 0 in the preceding algorithm, an assignment statement is used to store the value 0 into the memory location named sum. Thus, we say that the statement

    sum = 0;

    assigns 0 to the memory location sum, where it will be stored until some other part of the program needs it.

    Learning Java’s syntax and semantics is a major part of learning to program. This aspect of learning to program is a lot like learning a foreign language. The more quickly you become fluent in the new language (Java), the better you will be at expressing solutions to interesting programming problems. The longer you struggle with Java’s rules and conventions, the more difficult it will be to talk about problems in a common language. Also, computers are a lot fussier about correct language than humans, and even the smallest syntax or semantic error can cause tremendous frustration. So, try to be very precise in learning Java’s syntax and semantics.

    Testing, Debugging, and Revising

    Coding, testing, and revising a program is an repetitive process, one that may require you to repeat the different program-development stages shown in (Fig. [fig:progdev]). According to the stepwise-refinement principle, the process of developing a program should proceed in small, incremental steps, where the solution becomes more refined at each step. However, no matter how much care you take, things can still go wrong during the coding process.

    A syntax error is an error that breaks one of Java’s syntax rules. Such errors will be detected by the Java compiler. Syntax errors are relatively easy to fix once you understand the error messages provided by the compiler. As long as a program contains syntax errors, the programmer must correct them and recompile the program. Once all the syntax errors are corrected, the compiler will produce an executable version of the program, which can then be run.

    When a program is run, the computer carries out the steps specified in the program and produces results. However, just because a program runs does not mean that its actions and results are correct. A running program can contain semantic errors, also called . A semantic error is caused by an error in the logical design of the program causing it to behave incorrectly, producing incorrect results.

    Unlike syntax errors, semantic errors cannot be detected automatically. For example, suppose that a program contains the following statement for calculating the area of a rectangle:

    return length + width;

    Because we are adding length and width instead of multiplying them, the area calculation will be incorrect. Because there is nothing syntactically wrong with the expression length + width, the compiler won’t detect an error in this statement. Thus, the computer will still execute this statement and compute the incorrect area.

    Semantic errors can only be discovered by testing the program and they are sometimes very hard to detect. Just because a program appears to run correctly on one test doesn’t guarantee that it contains no semantic errors. It might just mean that it has not been adequately tested.

    Fixing semantic errors is known as debugging a program, and when subtle errors occur it can be the most frustrating part of the whole program development process. The various examples presented will occasionally provide hints and suggestions on how to track down bugs, or errors, in your code. One point to remember when you are trying to find a very subtle bug is that no matter how convinced you are that your code is correct and that the bug must be caused by some kind of error in the computer, the error is almost certainly caused by your code!

    Writing Readable Programs

    Becoming a proficient programmer goes beyond simply writing a program that produces correct output. It also involves developing good programming style, which includes how readable and understandable your code is. Our goal is to help you develop a programming style that satisfies the following principles:

    • Readability. Programs should be easy to read and understand. Comments should be used to document and explain the program’s code.
    • Clarity. Programs should employ well-known constructs and standard conventions and should avoid programming tricks and unnecessarily obscure or complex code.
    • Flexibility. Programs should be designed and written so that they are easy to modify.

    This page titled 1.2: Designing a Riddle Program is shared under a CC BY 4.0 license and was authored, remixed, and/or curated by Ralph Morelli & Ralph Wade via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.