OBJECTIVES
1
CHAPTER
1
Writing a Program
n
Analyze some of the issues involved in producing a simple program:
n
Requirements
n
Design constraints
n
Testing
n
Effort estimation
n
Implementation details
n
Understand the sequence of activities involved in writing even a simple
program.
n
Preview many additional software engineering topics found in the later
chapters.
91998_CH01_Tsui.indd 1 1/10/13 6:19:01 AM
1.1 A Simple Problem
In this chapter we will analyze the tasks involved in writing a relatively simple program.
This will serve as a contrast to what is involved in developing a large system, which is
described in Chapter 2.
Assume that you have been given the following problem: “Given a collection of lines
of text (strings) stored in a file, sort them in alphabetical order, and write them to another
file.” This is probably one of the simplest problems you will be involved with. You have
probably done similar assignments for some of your introduction to programming
classes.
1.2 Decisions, Decisions
A problem statement such as the one above does not completely specify the problem.
You need to clarify the requirements in order to produce a program that better satisfies
the real problem. You need to understand all the program requirements and the
design constraints imposed by the client on the design, and you
need to make important technical decisions. A complete problem
statement would include the requirements, which state and
qualify what the program does, and the design constraints, which
depict the ways in which you can design and implement it.
The most important thing to realize is that the word require-
ments is not used as it is in colloquial English. In many business
transactions, a requirement is something that absolutely must
happen. However, in software engineering many items are nego-
tiable. Given that every requirement will have a cost, the clients
may decide that they do not really need it after they understand
the related cost. Requirements are often grouped into those that are “needed” and those
that are “nice to have.”
It is also useful to distinguish between functional requirements—what the program
does—and nonfunctional requirements—the manner in which the program must
behave. In a way, a function is similar to that of a direct and indirect object in grammar.
Thus the functional requirements for our problem will describe what it does: sort a file
(with all the detail required); the nonfunctional requirements will describe items such
as performance, usability, and maintainability. Functional requirements tend to have
a Boolean measurement where the requirement is either satisfied or not satisfied, but
nonfunctional requirements tend to apply to things measured on a linear scale where
the measurements can vary much more. Performance and maintainability requirements,
as examples, may be measured in degrees of satisfaction.
Nonfunctional requirements are informally referred as the “ilities,” because the words
describing most of them will end in “-ility.” Some of the typical characteristics defined
as nonfunctional requirements are performance, modifiability, usability, configurability,
reliability, availability, security, and scalability.
Besides requirements, you will also be given design constraints, such as the choice of
programming language, platforms the system runs on, and other systems it interfaces
Program requirements Statements that
define and qualify what the program needs
to do.
Design constraints Statements that con-
strain the ways in which the software can be
designed and implemented.
Functional requirements What a program
needs to do.
Nonfunctional requirements The manner
in which the functional requirements need
to be achieved.
2 Chapter 1 Writing a Program
91998_CH01_Tsui.indd 2 1/10/13 6:19:01 AM
with. These design constraints are sometimes considered nonfunctional requirements.
This is not a very crisp or easy-to-define distinction (similar to where requirement analysis
ends and design starts); and in borderline cases, it is defined mainly by consensus. Most
developers will include usability as a nonfunctional requirement, and the choice of a
specific user interface such as graphical user interface (GUI) or Web-based, as a design
constraint. However, it can also be defined as a functional requirement as follows: “the
program displays a dialog box 60 by 80 pixels, and then . . .”
Requirements are established by the client, with help from the software engineer,
while the technical decisions are often made by the software engineer without much
client input. Oftentimes, some of the technical decisions such as which programming
languages or tools to use can be given as requirements because the program needs to
interoperate with other programs or the client organization has expertise or strategic
investments in particular technologies.
In the following pages we will illustrate the various issues that software engineers
confront, even for simple programs. We will categorize these decisions into functional
and nonfunctional requirements, design constraints, and design decisions. But do keep
in mind that other software engineers may put some of these issues into a different cat-
egory. We will use the simple sorting problem presented in Section 1.1 as an example.
1.2.1 Functional Requirements
We will have to consider several aspects of the problem and ask many questions prior to
designing and programming the solution. The following is an informal summary of the
thinking process involved with functional requirements:
n
Input formats: What is the format for the input data? How should data be stored? What
is a character? In our case, we need to define what separates the lines on the file. This is
especially critical because several different platforms may use different separator char-
acters. Usually some combination of new-line and carriage return may be considered.
In order to know exactly where the boundaries are, we also need to know the input
character set. The most common representation uses 1 byte per character, which is
enough for English and most Latin-derived languages. But some representations, such
as Chinese or Arabic, require 2 bytes per character because there are more than 256
characters involved. Others require a combination of the two types. With the combina-
tion of both single- and double-byte character representations, there is usually a need
for an escape character to allow the change of mode from single byte to double byte
or vice versa. For our sorting problem, we will assume the simple situation of 1 byte
per character.
n
Sorting: Although it seems to be a well-defined problem, there are many slightly and
not so slightly different meanings for sorting. For starters—and of course, assuming
that we have English characters only—do we sort in ascending or descending order?
What do we do with nonalphabetic characters? Do numbers go before or after letters
in the order? How about lowercase and uppercase characters? To simplify our problem,
we define sorting among characters as being in numerical order, and the sorting of the
file to be in ascending order.
1.2 Decisions, Decisions
3
91998_CH01_Tsui.indd 3 1/10/13 6:19:01 AM
n
Special cases, boundaries, and error conditions: Are there any special cases? How should
we handle boundary cases such as empty lines and empty files? How should different
error conditions be handled? It is common, although not good practice, to not have all
of these requirements completely specified until the detailed design or even the imple-
mentation stages. For our program, we do not treat empty lines in any special manner
except to specify that when the input file is empty the output file should be created but
empty. We do not specify any special error-handling mechanism as long as all errors are
signaled to the user and the input file is not corrupted in any way.
1.2.2 Nonfunctional Requirements
The thinking process involved in nonfunctional requirements can be informally sum-
marized as follows:
n
Performance requirements: Although it is not as important as most people may think, per-
formance is always an issue. The program needs to finish most or all inputs within a cer-
tain amount of time. For our sorting problem, we define the performance requirements
as taking less than 1 minute to sort a file of 100 lines of 100 characters each.
n
Real-time requirements: When a program needs to perform in real-time, which means it
must complete the processing within a given amount of time, performance is an issue.
The variability of the running time is also a big issue. We may need to choose an algo-
rithm with a less than average performance, if it has a better worst-case performance.
For example,
Quick Sort is regarded as one of the fastest sorting algorithms; however,
for some inputs, it can have poor performance. In algorithmic terms, its expected run-
ning time is on the order of
n log(n), but its worst-case performance is on the order of
n squared. If you have real-time requirements in which the average case is acceptable
but the worst case is not, then you may want to choose an algorithm with less vari-
ability, such as
Heap Sort or Merge Sort. Run-time performance analysis is discussed
further in Main and Savitch (1997).
n
Modifiability requirements: Before writing a program, it is important to know the life
expectancy of the program and whether there is any plan to modify the program.
If the program is to be used only once, then modifiability is not a big issue. On the
other hand, if it is going to be used for 10 years or more, then we need to worry about
making it easy to maintain and modify. Surely, the requirements will change during
that 10-year period. If we know that there are plans to extend the program in certain
ways, or that the requirements will change in specific ways, then we should prepare
the program for those modifications as the program is designed and implemented.
Notice that even if the modifiability requirements are low, this is not a license to write
bad code, because we still need to be able to understand the program for debugging
purposes. For our sorting example, consider how we might design and implement the
solution if we know that down the road the requirement may change from descend-
ing to ascending order or may change to include both ascending and descending
orders.
4 Chapter 1 Writing a Program
91998_CH01_Tsui.indd 4 1/10/13 6:19:01 AM
1.2.3 Design Constraints
The thinking process related to design constraints can be summarized as follows:
n
User interface: What kind of user interface should the program have? Should it be
a command-line interface (CLI) or a graphical user interface
(GUI)? Should we use a web-based interface? For the sorting
problem, a web-based interface doesn’t sound appropriate
because users would need to upload the file and download
the sorted one. Although GUIs have become the norm over the past decade or so,
a command-line interface can be just as appropriate for our sorting problem, espe-
cially bacuase it would make it easier to invoke inside a script, allowing for automa-
tion of manual processes and reuse of this program as a module for future ones.
This is one of those design considerations that also involves user interface. In
Section 1.5, we will create several implementations, some CLI based and some GUI
based. Chapter 7 also discusses user-interface design in more detail.
n
Typical and maximum input sizes: Depending on the typical input sizes, we may want to
spend different amounts of time on algorithms and performance optimizations. Also,
certain kinds of inputs are particularly good or bad for certain algorithms; for example,
inputs that are almost sorted make the naive
Quick Sort implementations take more
time. Note that you will sometimes be given inaccurate estimates, but even ballpark
figures can help anticipate problems or guide you toward an appropriate algorithm. In
this example, if you have small input sizes, you can use almost any sorting algorithm.
Thus you should choose the simplest one to implement. If you have larger inputs but
they can still fit into the random access memory (RAM), you need to use an efficient
algorithm; if the input does not fit on RAM, then you need to choose a specialized
algorithm for on-disk sorting.
n
Platforms: On which platforms does the program need to run? This is an important
business decision that may include architecture, operating system, and available
libraries and will almost always be expressed in the requirements. Keep in mind
that, although cross-platform development has become easier and there are many
languages designed to be portable across platforms, not all the libraries will be
available in all platforms. There is always an extra cost on explicitly supporting a new
platform. On the other hand, good programming practices help achieve portability,
even when not needed. A little extra consideration when designing and implement-
ing a program can minimize the potentially extensive work required to port to a new
platform. It is good practice to perform a quick cost-benefit analysis on whether to
support additional platforms and to use technologies and programming practices
that minimize portability pains, even when the need for supporting new platforms
is not anticipated.
n
Schedule requirements: The final deadline for completing a project comes from the cli-
ent, with input from the technical side on feasibility and cost. For example, a dialogue
on schedule might take the following form: Your client may make a request such as “I
need it by next month.” You respond by saying, “Well, that will cost you twice as much
than if you wait two months” or “That just can’t be done; it usually takes three months;
we can push it to two, but no less.” The client may agree to this, or could also say, “If it’s
not done by next month, then it is not useful” and cancel the project.
User interface What the user sees and
hears from the system.
1.2 Decisions, Decisions
5
91998_CH01_Tsui.indd 5 1/10/13 6:19:01 AM
1.2.4 Design Decisions
The steps and thoughts related to design decisions for the sorting problem can be sum-
marized as follows:
n
Programming language: Typically this will be a technical design decision, although it is
not uncommon to be given as a design constraint. The type of programming needed,
the performance and portability requirements, and the technical expertise of the devel-
opers often heavily influence the choice of the programming language.
n
Algorithms: When implementing systems, there are usually several pieces that can be
influenced by the choice of algorithms. In our example, of course, there are a variety
of algorithms we can choose among to sort a collection of objects. The language used
and the libraries available will influence the choice of algorithms. For example, to sort,
the easiest solution would be to use a standard facility provided by the programming
language rather than to implement your own. Thus, use whatever algorithm that
implementation chooses. Performance will usually be the most important influence
in the choice of an algorithm, but it needs to be balanced with the effort required to
implement it, and the familiarity of the developers with it. Algorithms are usually design
decisions, but they can be given as design constraints or even considered functional
requirements. In many business environments there are regulations that mandate
specific algorithms or mathematical formulas to be used, and in many scientific appli-
cations the goal is to test several algorithms, which means that you must use certain
algorithms.
1.3 Testing
It is always a good idea to test a program, both while it is being developed and after it is
completed. This may sound like obvious advice, but it is not always followed. There are
several kinds of testing, including acceptance testing, which refers to testing done by
clients, or somebody on their behalf, to make sure the program runs as specified. If this
testing fails, the client can reject the program. The developers should run their own tests,
prior to the client acceptance testing, to determine if the program works.
Although there are many types of testing performed by the development organiza-
tion, the most important kind of testing for the individual programmer is unit testing—a
process followed by a programmer to test each piece or unit of software. When writing
code, you must also write tests to check each module, function, or method you have
written. Some methodologies, notably Extreme Programming, go as far as saying that
programmers should write the test cases before writing the code; see the discussion on
Extreme Programming in Beck (1999). Inexperienced programmers often do not realize
the importance of testing. They write functions or methods that depend on other func-
tions or methods that have not been properly tested. When a method fails, they do not
know which function or method is actually failing.
Another useful distinction is between black-box and white-box testing. In black-box
testing, the test cases are based only on the requirement specifications, not on the
implementation code. In white-box testing, the test cases can be designed while looking
6 Chapter 1 Writing a Program
91998_CH01_Tsui.indd 6 1/10/13 6:19:01 AM
at the design and code implementation. While doing unit testing, the programmer has
access to the implementation but should still perform a mixture of black-box and white-
box testing. When we discuss implementations for our simple program, we will perform
unit testing on it. Testing will be discussed more extensively in Chapter 10.
1.4 Estimating Effort
One of the most important aspects of a software project is estimating how much effort
it involves. The effort estimate is required to produce a cost estimate and a schedule.
Before producing a complete effort estimate, the requirements must be understood. An
interesting exercise illustrates this point.
Try the following exercise:
Estimate how much time, in minutes, it will take you, using your favorite
language and technology, to write a program that reads lines from one file
and writes the sorted lines to another file. Assume that you will be writing
the sort routine yourself and will implement a simple GUI like the one shown
in Figure 1.21, with two text boxes for providing two file names, and two
buttons next to each text box. Pressing one of the two buttons displays a
File Open dialog, like the one shown in Figure 1.22, where the user can
navigate the computer’s file system and choose a file. Assume that you can
work only on this one task, with no interruptions. Provide an estimate within
1 minute.
Step 1.
Estimated ideal time: _______________
Is the assumption that you will be able to work straight through on this task with no
interruptions realistic? Won’t you need to go to the restroom or drink some water? Can
you spend the time on this task? If you were asked to do this task as soon as reasonably
possible, starting right now, can you estimate when would you be finished? Calculate the
number of minutes between now and the time you would be finished.
Step 2.
Estimated calendar time: _______________
Now, let’s divide the task into several subtasks. Assume you will create a class, called
StringSorter, with three public methods: Read, Write, and Sort. For the sorting routine,
assume that your algorithm involves finding the largest element, putting it at the end
of the array, and then sorting the rest of the array using the same mechanism. Assume
you will create a method called
IndexOfBiggest that returns the index of the biggest
element on the array. Using the following chart, estimate how much time it will take you
to do each task (and the GUI).
1.4 Estimating Effort
7
91998_CH01_Tsui.indd 7 1/10/13 6:19:01 AM
Step 3.
Ideal Time Calendar Time
IndexOfBiggest
Sort
Read
Write
GUI
Testing
Total
How close is this estimate to the previous one you did? What kind of formula did you use
to convert from ideal time to calendar time?
Now implement your solution.
Step 4.
Keep track of the time you actually spend on each task as well as the interruptions you
experience. Compare these times with your estimates. How high or low did you go? Is
there a pattern? How accurate is the total with respect to your original estimate?
If you performed the activities in this exercise, chances are that you found the esti-
mate was more accurate after dividing it into subtasks. You will also find that estimates in
general tend to be somewhat inaccurate, even for well-defined tasks. Project estimation
and effort estimation is one of the toughest problems in software project management
and software engineering. This topic will be revisited in detail in Chapter 13. For further
reading on why individuals should keep track of their development time, see Humphrey
(1996). Accurate estimation is very hard to achieve. Dividing tasks into smaller ones and
keeping data about previous tasks and estimates are usually helpful beginnings.
It is important that the estimation is done by the people who do the job, which is
often the programmer. The client also needs to check the estimates for reasonableness.
One big problem with estimating is that it is conceptually performed before the project
is done, but in reality a lot of information, possibly up to design, is needed in order to be
able to provide a good estimate. We will talk more about estimating in Chapter 13.
1.5 Implementations
In this section we will discuss several implementations of our sorting program, including
two ways to implement the sort functionality and several variations of the user interface.
We will also discuss unit testing for our implementations. Sample code will be provided
in Java, using JUnit to aid in unit testing.
1.5.1 A Few Pointers on Implementation
Although software engineering tends to focus more on requirements analysis, design,
and processes rather than implementation, a bad implementation will definitely mean
a bad program even if all the other pieces are perfect. Although for simple programs
8 Chapter 1 Writing a Program
91998_CH01_Tsui.indd 8 1/10/13 6:19:01 AM
almost anything will do, following a few simple rules will generally make all your pro-
gramming easier. Here we will discuss only a few language-independent rules, and point
you to other books in the Suggested Readings section at the end of this chapter.
n
The most important rule is to be consistent—especially in your choice of names, capi-
talization, and programming conventions. If you are programming alone, the particular
choice of conventions is not important as long as you are consistent. You should also
try to follow the established conventions of the programming language you are using,
even if it would not otherwise be your choice. This will ensure that you do not introduce
two conventions. For example, it is established practice in Java to start class names with
uppercase letters, and variable names with lowercase letters. If your name has more than
one word, use capitalization to signal the word boundaries. This results in names such
as
FileClass and fileVariable. In C, the convention is to use lowercase almost exclu-
sively and to separate with an underscore. Thus, when we program in C, we follow the
C conventions. The choice of words for common operations is also dictated by conven-
tion. For example, printing, displaying, showing, or echoing a variable are some of the
terminologies meaning similar actions. Language conventions also provide hints as to
default names for variables, preference for shorter or longer names, and other issues.
Try to be as consistent as possible in your choice, and follow the conventions for your
language.
n
Choose names carefully. In addition to being consistent in naming, try to make sure
names for functions and variables are descriptive. If the names are too cumbersome
or if a good name cannot be easily found, that is usually a sign that there may be a
problem in the design. A good rule of thumb is to choose long, descriptive names
for things that will have global scope such as classes and public methods. Use short
names for local references, which are used in a very limited scope such as local vari-
ables, private names, and so on.
n
Test before using a function or method. Make sure that it works. That way if there are
any errors, you know that they are in the module you are currently writing. Careful unit
testing, with test cases written before or after the unit, will help you gain confidence in
using that unit.
n
Know thy standard library. In most modern programming languages, the standard
library will implement many common functions, usually including sorting and collec-
tions of data, database access, utilities for web development, networking, and much
more. Don’t reinvent or reimplement the wheel. Using the standard libraries will save
extra work, make the code more understandable, and usually run faster with fewer
errors, because the standard libraries are well debugged and optimized. Keep in mind
that many exercises in introductory programming classes involve solving classic prob-
lems and implementing well-known data structures and algorithms. Although they are
a valuable learning exercise, that does not mean you should use your own implemen-
tations in real life. For our sample programming problem, Java has a sorting routine
that is robust and fast. Using it instead of writing your own would save time and effort
and produce a better implementation. We will still implement our own for the sake of
illustration but will also provide the implementation using the Java sorting routine.
1.5 Implementations
9
91998_CH01_Tsui.indd 9 1/10/13 6:19:01 AM
n
If possible, perform a review of your code. Software reviews are one of the most effec-
tive methods for reducing defects in software. Showing your code to other people will
help detect not just functionality errors but also inconsistencies and bad naming. It will
also help you learn from the other person’s experience. This is another habit that does
not blend well with school projects. In most such projects, getting help from another
student might be considered cheating. Perhaps the code can instead be reviewed
after it is handed in. Reviews are good for school assignments as well as for real-world
programs.
1.5.2 Basic Design
Given that we will be implementing different user interfaces, our basic design sepa-
rates the sorting functionality from the user interface, which is a good practice anyway,
because user interfaces tend to change much faster than functionality. We have a class,
called
StringSorter, that has four methods: (1) reading the strings from a file, (2) sorting
the collection of strings, (3) writing the strings to a file, and (4) combining those three,
taking the input and output file names. The different user interfaces will be implemented
in separate classes. Given that
StringSorter would not know what to do with excep-
tional conditions, such as errors when reading or writing streams, the exceptions pass
through in the appropriate methods, with the user interface classes deciding what to
do with them. We also have a class with all our unit tests, taking advantage of the JUnit
framework.
1.5.3 Unit Testing with JUnit
JUnit is one of a family of unit testing frameworks, the J standing for Java. There
are variations for many other languages—for example,
cppUnit for C++; the original
library was developed in
Smalltalk. Here we discuss JUnit in a very basic way; JUnit
is discussed further in Chater 10. We just need to create a class that inherits from
junit.framework.TestCase, which defines public methods whose names start with
test. JUnit uses Java’s reflection capabilities to execute all those methods. Within
each test method,
assertEquals can be used to verify whether two values that should
be equal are truly equal.
1.5.4 Implementation of StringSorter
We will be presenting our implementation followed by the test cases. We are assuming
a certain fundamental background with Java programming, although familiarity with
another object-oriented programming language should be enough to understand this
section. Although the methods could have been developed in a different order, we pres-
ent them in the order we developed them, which is
Read, then Sort, then Write. This is
also the order in which the final program will execute, thus making it easier to test.
We import several namespaces, and declare the
StringSorter class. The only instance
variable is an
ArrayList of lines. ArrayList is a container that can grow dynamically,
and supports indexed access to its elements. It roughly corresponds to a vector in other
programming languages. It is part of the standard Java collections library and another
example of how using the standard library saves time. Notice we are not declaring the
variable as private in Figure 1.1, because the test class needs access to it. By leaving it
10 Chapter 1 Writing a Program
91998_CH01_Tsui.indd 10 1/10/13 6:19:02 AM
with default protection, all classes in the same package can access it because Java has no
concept like friend classes in C++. This provides a decent compromise. Further options will
be discussed in Chapter 10. Our first method involves reading lines from a file or stream, as
seen in Figure 1.2. To make the method more general, we take a
Reader, which is a class
for reading text-based streams. A stream is a generalization of a file. By using a
Reader
rather than a class explicitly based on
Files, we could use this same method for reading
from standard input or even from the network. Also, because we do not know how to deal
with exceptions here, we will just let the
IOException pass through.
For testing this method with
JUnit, we create a class extending TestCase. We also
define a utility method, called
make123, that creates an ArrayList with three strings—
one, two, and three—inserted in that order in Figure 1.3.
Figure 1.1 Class declaration and Import statements.
import java.io.*; // for Reader(s), Writer(s),
import java.util.*; // for List, ArrayList, Iterator
public class StringSorter {
ArrayList lines;
Figure 1.2 The readFromStream method.
public void readFromStream(Reader r) throws
IOException
{
BufferedReader br=new BufferedReader(r);
lines=new ArrayList();
while(true) {
String input=br.readLine();
if(input==null)
break;
lines.add(input);
}
}
Figure 1.3 TestStringSorter declaration and make123 method.
public class TestStringSorter extends TestCase {
private ArrayList make123() {
ArrayList l = new ArrayList();
l.add("one");
l.add("two");
l.add("three");
return l;
}
1.5 Implementations
11
91998_CH01_Tsui.indd 11 1/10/13 6:19:02 AM
We then define our first method, testReadFromStream, in Figure 1.4. In this method
we create an
ArrayList and a StringSorter. We open a known file, and make the
StringSorter read from it. Given that we know what is in the file, we know what the
internal
ArrayList in our StringSorter should be. We just assert that it should be equal
to that known value.
We can run
JUnit after setting the classpath and compiling both classes, by typing
java junit.swingui.TestRunner. This will present us with a list of classes to choose
from. When choosing our
TestStringSorter class, we find a user interface like the one
shown in Figure 1.5, which indicates that all tests are implemented and successfully run.
Figure 1.5 JUnit GUI.
Figure 1.4 testReadFromStream.
public void testReadFromStream() throws
IOException{
Reader in=new
FileReader("in.txt");
StringSorter ss=new
StringSorter();
ArrayList l= make123();
ss.readFromStream(in);
assertEquals(l,ss.lines);
}
12 Chapter 1 Writing a Program
91998_CH01_Tsui.indd 12 1/10/13 6:19:02 AM
Pressing the run button will rerun all tests, showing you how many tests were successful.
If any test is unsuccessful, the bar will be red rather than green. Classes are reloaded by
default, so you can leave that window open, modify, recompile, and just press
run again.
After we verify that our test is successful, we can begin the next method—building
the sorting functionality. We decided on a simple algorithm: find the largest element in
the array, then swap it with the last element, placing the largest element at the end of the
array, then repeat with the rest of the array. We need two supporting functions, one for
swapping the two elements in the array and another for finding the index of the largest
element. The code for a swap is shown in Figure 1.6.
Because swap is a generic function that could be reused in many situations, we
decided to build it without any knowledge of the
StringSorter class. Given that, it
makes sense to have it as a static method. In C++ or other languages, it would be a func-
tion defined outside the class and not associated with any class. Static methods are the
closest technique in Java. We get as parameters a
List, where List is the generic inter-
face that
ArrayList implements, and the indexes of the two elements. The test for this
method is shown in the
testSwap method of TestStringSorter class in Figure 1.7.
The next method is the one that returns the index of the largest element on the list.
Its name is
findIdxBiggest, as shown in Figure 1.8. Idx as an abbreviation of index is
ingrained in our minds. We debated whether to use
largest, biggest, or max/maximum
for the name (they are about equally appropriate in our minds). After settling on big-
gest, we just made sure that we did not use the other two for naming the variables.
We use the
compareTo method of Strings, that returns –1 if the first element is less
than the second, 0 if they are equal, and 1 if the first is largest. In this method we use
the fact that the elements in the
ArrayList are strings. Notice that Java (as of version
1.4) does not have support for generics (templates in C++), so the elements have to be
explicitly casted to
Strings. The test is shown in Figure 1.9.
Figure 1.6 The code for swapping two integers.
static void swap(List l, int i1, int i2) {
Object tmp=l.get(i1);
l.set(i1, l.get(i2));
l.set(i2, tmp);
}
Figure 1.7 The testSwap method.
public void testSwap() {
ArrayList l1= make123();
ArrayList l2=new ArrayList();
l2.add("one");
l2.add("three");
l2.add("two");
StringSorter.swap(l1,1,2);
assertEquals(l1,l2);
}
1.5 Implementations
13
91998_CH01_Tsui.indd 13 1/10/13 6:19:02 AM
With swap and findIdxBiggest in place, the sort method, shown in Figure 1.10,
becomes relatively easy to implement. The test for it is shown in Figure 1.11. Note that if
we knew our standard library, we could have used a much easier implementation, using
the
sort function in the standard Java library, as shown in Figure 1.12. We would have
also avoided writing
swap and findIdxBiggest! It definitely pays to know your standard
library.
Now on to writing to the file; this is shown in Figure 1.13. We will test it by writing a
known value to the file, then reading it again and performing the compare in Figure 1.14.
Now all that is needed is the
sort method taking the file names as shown in Figure 1.15.
Given that we have already seen how to do it for the test cases, it is very easy to do. The
test for this method is shown in Figure 1.16.
Figure 1.8 The findIdxBiggest method.
static int findIdxBiggest(List l, int from, int to) {
String biggest=(String) l.get(0);
int idxBiggest=from;
for(int i=from+1; i<=to; ++i) {
if(biggest.compareTo((String)l.get(i))<0) {// it is bigger
biggest=(String)l.get(i);
idxBiggest=i;
}
}
return idxBiggest;
}
Figure 1.9 The testFindIdxBiggest method.
public void testFindIdxBiggest() {
ArrayList l = make123();
int i=StringSorter.findIdxBiggest(l,0,l.size()-
1);
assertEquals(i,1);
}
Figure 1.10 The sort method.
public void sort() {
for(int i=lines.size()-1; i>0; --i) {
int big=findIdxBiggest(lines,0,i);
swap(lines,i,big);
}
}
14 Chapter 1 Writing a Program
91998_CH01_Tsui.indd 14 1/10/13 6:19:02 AM
Figure 1.11 The testSort1 method.
public void testSort1() {
StringSorter ss= new StringSorter();
ss.lines=make123();
ArrayList l2=new ArrayList();
l2.add("one");
l2.add("three");
l2.add("two");
ss.sort();
assertEquals(l2,ss.lines);
}
Figure 1.12 The sort method using Java’s standard library.
void sort() {
java.util.Collections.sort(lines);
}
Figure 1.13 The writeToStream method.
public void writeToStream(Writer w) throws IOException {
PrintWriter pw=new PrintWriter(w);
Iterator i=lines.iterator();
while(i.hasNext()) {
pw.println((String)(i.next()));
}
}
Figure 1.14 The testWriteToStream method.
public void testWriteToStream() throws IOException{
// write out a known value
StringSorter ss1=new StringSorter();
ss1.lines=make123();
Writer out=new FileWriter("test.out");
ss1.writeToStream(out);
out.close();
// then read it and compare
Reader in=new FileReader("test.out");
StringSorter ss2=new StringSorter();
ss2.readFromStream(in);
assertEquals(ss1.lines,ss2.lines);
}
1.5 Implementations
15
91998_CH01_Tsui.indd 15 1/10/13 6:19:02 AM
1.5.5 User Interfaces
We now have an implementation of StringSorter and a reasonable belief that it works
as intended. We realize that our tests were not that extensive; however, we can go on to
build a user interface, which is an actual program that lets us access the functionality of
StringSorter. Our first implementation is a command-line, not GUI, version, as shown
in Figure 1.17. It takes the names of the input and output files as command parameters.
Its implementation is as shown in the figure.
We would use it by typing the following command:
java StringSorterCommandLine abc.txt abc_sorted.txt
Do you believe this is a useful user interface? Actually, for many people it is. If you have
a command-line window open all the time or if you are working without a GUI, then it
is not that hard to type the command. Also, it is very easy to use this command inside
Figure 1.15 The sort method (taking file names).
public void sort(String inputFileName, String
outputFileName)
throws IOException
{
Reader in=new FileReader(inputFileName);
Writer out=new FileWriter(outputFileName);
StringSorter ss=new StringSorter();
ss.readFromStream(in);
ss.sort();
ss.writeToStream(out);
in.close();
out.close();
}
Figure 1.16 The testSort2 method.
public void testSort2() throws IOException{
// write out a known value
StringSorter ss1=new StringSorter();
ss1.sort("in.txt","test2.out");
ArrayList l=new ArrayList();
l.add("one");
l.add("three");
l.add("two");
// then read it and compare
Reader in=new FileReader("test2.out");
StringSorter ss2=new StringSorter();
ss2.readFromStream(in);
assertEquals(l,ss2.lines);
}
16 Chapter 1 Writing a Program
91998_CH01_Tsui.indd 16 1/10/13 6:19:02 AM
a script, to sort many files. In fact, you could use a script to more thoroughly test your
implementation. Another important advantage, besides scriptability, is how easy it is to
build the interface. This means less effort, lower costs, and fewer errors.
However, for some people this would not be a useful interface. If you are accustomed
to only using GUIs or if you do not usually have a command window open and are not
going to be sorting many files, then a GUI would be better. Nevertheless, GUI is not neces-
sarily a better interface than a Command Line Interface (CLI). It depends on the use and
the user. Also, it is extremely easy to design bad GUIs, such as the implementation shown
in Figure 1.18. The code in this figure would display the dialog box shown in Figure 1.19.
After the user presses OK, the dialog box in Figure 1.20 would be shown.
Figure 1.17 The StringSorter CommandLine class, which implements a command-line inter-
face for
StringSorter functionality.
import java.io.IOException;
public class StringSorterCommandLine {
public static void main(String args[]) throws IOException
{
if(args.length!=2) {
System.out.println("Use: cmd inputfile
outputfile");
} else {
StringSorter ss=new StringSorter();
ss.sort(args[0],args[1]);
}
}
}
Figure 1.18 StringSorterBadGUI class, which implements a hard-to-use GUI for
StringSorter functionality.
public class StringSorterBadGUI {
public static void main(String args[]) throws IOException
{
try {
StringSorter ss=new StringSorter();
String inputFileName=JOptionPane.showInputDialog
("Please enter input file name");
String outputFileName=JOptionPane.showInputDialog
("Please enter output file name");
ss.sort(inputFileName, outputFileName);
} finally {
System.exit(1);
}
}
}
1.5 Implementations
17
91998_CH01_Tsui.indd 17 1/10/13 6:19:02 AM
This does not involve much more effort than the command-line version, but it is very
inefficient to use. Although it is a GUI, it is worse than the CLI for almost every user. A bet-
ter interface is shown in Figure 1.21. Although it is not a lot better, at least both inputs
are in the same place. What makes it more useful is that the buttons on the right open a
dialog box as shown in Figure 1.22 for choosing a file.
This would at least be a decent interface for most GUI users. Not terribly pretty, but
simple and functional. The code for this GUI is available on the website for this book. We
are not printing it because it requires knowledge of Java and Swing to be understood. We
will note that the code is 75 lines long in Java, a language for which GUI building is one of
its strengths, and it took us longer to produce than the
StringSorter class! Sometimes
GUIs come with a heavy cost. We will discuss user-interface design in Chapter 7.
Figure 1.19 An input file name dialog box for a hard-to-use GUI.
Figure 1.20 An output file name dialog for a hard-to-use GUI.
Figure 1.21 Input and Output file name dialog for GUI.
18 Chapter 1 Writing a Program
91998_CH01_Tsui.indd 18 1/10/13 6:19:03 AM
1.6 Summary
In this chapter we have discussed some of the many issues involved in writing a simple
program. By now, you should have realized that even for simple programs there is much
more than just writing the code. One has to consider many of the following items:
n
Requirements
n
Design
n
Code implementation
n
Unit testing
n
Personal effort estimation
n
User interface
Much of that material belongs to software engineering, and in this text we will provide
an overview of it.
1.7 Review Questions
1. What are statements that define and qualify what the program needs to do?
2. What are statements that constrain the ways in which the software can be
designed and implemented?
3. Which type of requirement statement defines what the program needs to do?
Figure 1.22 File Open dialog for GUI.
1.7 Review Questions
19
91998_CH01_Tsui.indd 19 1/10/13 6:19:03 AM
4. What requirements qualify as functional requirements? Specify in what manner
they need to be achieved.
5. Which decisions are those taken by the software engineer about the best ways
(processes, techniques, and technologies) to achieve the requirements?
6. What type of testing refers to testing done by the clients (or somebody on their
behalf) to make sure the program runs as specified?
7. What is GUI? What is CLI?
8. List three of the typical kinds of nonfunctional requirements.
1.8 Exercises
1. For your next two software projects (assuming that you are getting programming
assignments; otherwise consider a program to find the
max and the min of a set of
rational numbers) estimate how much effort they would take before doing them,
then keep track of the actual time spent. How accurate were your estimates?
2. What sequence of activities did you observe in considering the programming
effort discussed in this chapter?
3. Discuss whether you think a programming language constraint may be viewed as
a requirement. Explain why you think so.
4. Download the programs for this chapter, and add at least one more test case for
each method of the
StringSorter class.
5. In the discussion of the simple program in this chapter, what were the items con-
sidered for “basic” design? Would you have written down these considerations
and perhaps reviewed them with a trusted person before the actual coding?
6. Consider a command-line interface that, rather than taking the file names as
parameters, asks for them from the keyboard (e.g., it displays “Input file name:”
then reads it from the keyboard). Would this be a better user interface? Why or
why not?
7. Consider a new user interface for our sorting program that combines the command-
line interface and the GUI. If it receives parameters in the command line, it does the
sort; if it does not, it displays the dialog. Would this be a better interface? What
would be its advantages and disadvantages compared with other interfaces?
1.9 Suggested Readings
K. Beck, Extreme Programming Explained: Embrace Change (Reading, MA: Addison-Wesley,
1999).
N. Dale, C. Weems, and M. R. Headington, Programming and Problem Solving with Java
(Sudbury, MA: Jones and Bartlett, 2003).
20 Chapter 1 Writing a Program
91998_CH01_Tsui.indd 20 1/10/13 6:19:03 AM
W. Humphrey, Introduction to the Personal Software Process (Reading, MA: Addison-
Wesley, 1996).
A. Hunt and D. Thomas, Pragmatic Unit Testing in Java with JUnit (Sebastopol, CA: Prag-
matic Bookshelf, 2003).
B. W. Kernighan and R. Pike, The Practice of Programming (Reading, MA: Addison-Wesley,
1999).
M. Main and W. Savitch, Data Structures and Other Objects Using C++ (Reading, MA: Addi-
son-Wesley, 1997).
Steve McConnell, Code Complete 2 (Redmond, WA: Microsoft Press, 2004).
C. T. Wu, Introduction to Objected Oriented Programming with Java, 3rd ed. (New York:
McGraw-Hill, 2004).
1.9 Suggested Readings
21
91998_CH01_Tsui.indd 21 1/10/13 6:19:03 AM
Intentional Blank 22Intentional Blank 22
91998_CH01_Tsui.indd 22 1/10/13 6:19:03 AM
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.111.92