Accompanying code files:
- N/A
Jump to Homework
Many people can get the skills required to become a programmer/software developer. Lots of people in introductory computer science often see themselves as a programmer, whether they acquired the skills from a coding bootcamp, as part of their formal education, or from teaching themselves through the various MOOCs or YouTube videos available online. Regardless of how one acquires your programming skill, there are distinctions between a programmer/software developer, a computer scientist, and a software engineer. To become an effective software engineer, you need to be able to combine the practical skills of a programmer with the analytical mindset of a computer scientist.
Software Engineering is the study of designing, developing, and maintaining software.
As a programmer/software developer, you should be able to manage the Software Development Life Cycle (SDLC):
- Project initiation
- Analysis of Specification
- Program Design
- Implementation
- Testing
- Maintenance
As a computer scientist, you should be able to do several things, including analyzing:
- Implementation
- Program Correctness
- Algorithm Efficiency
These will all be tested on the AP CS exam, and mastering them will make you stand out as a programmer.
The SDLC exists in several different models, such as the following:
- Protoyping: an approximation of a final system is built, tested, and reworked until it is acceptable. The complete system is then developed from this prototype.
- Incremental Development: The software is designed, implemented, and tested a little bit at a time until the produce is finished.
- Rapid Application Development: The user is actively involved in the evaluation of the product and modifications are made immediately as problems are found. Radical changes in the system are likely at any moment in the process.
- Agile Software Development: The developers offer frequent releases of the software to the customer and new requirements are generated by the users. Short development cycles (sprints) are common in this type of development. Most popular style currently.
- Waterfall Model: The progress of development is sequential and flows downward like a waterfall. Steps include conception, initiation, analysis, design, construction, testing, implementation, and maintenance.
For the exam, you will need to be aware of the Waterfall Model:
- Program Specification
- Program Design
- Implementation
- Testing & Debugging
- Maintenance
- This involves explicit written description of the project, that someone else other than the programmer will write.
- It is based on the customer's requirements (user stories).
- Before you write any program, as we've said many times before, it is absolutely essential that you analyze the specification and make sure you understand it, and clarify with the customer if anything is unclear.
- Even for small programs, a good design can save programming time and enhance the reliability of the final program.
- The design should be a detailed plan for solving the problem outlined in the specification.
- It will include all objects (UML diagrams) that will be used in the solution, and the data structures that will implement them, and a detailed list of tasks to be performed by the program.
- A good design provides a fairly detailed overall plan at a glance, without including excess details of Java code.
- This is the coding phase. We will only focus on Object-Oriented Programming for this course, which has been the dominant programming methodology since the mid 1990s, which has these steps:
- A) Identify classes to be written.
- B) Identify behaviours (i.e. methods) for each class.
- C) Determine the relationships between classes.
- D) Write the interface (public method headers) for each class.
- E) Implement the methods.
A) IDENTIFYING CLASSES
- Identify the objects in the program by picking out the "big-picture" nouns (except the ones referring to the user) in the program specification, and select the ones that seem suitable as major class objects in the application.
- Some of the more minor nouns might be more suitable as attributes.
- Many applications have similar object types: a low-level basic component, a collection of low-level components, a controlling object that puts everything together, and a display object that could be a GUI (graphical user interface)
EXAMPLE 1: Write a program that maintains the inventory of stock items for a small store.
- Nouns: inventory, item, store
- Basic Object: StockItem
- Collection: Inventory - list of StockItems
- Controller: Store - has an Inventory, uses a StoreDisplay
- Display: StoreDisplay - could be a GUI
EXAMPLE 2: Write a program that simulates a game of bingo. There should be at least two players, each of whom has a bingo card, and a caller who calls the numbers.
- Nouns: game, players, bingo card, caller
- Basic Object: BingoCard, Caller
- Collection: Players - each has a BingoCard
- Controller: GameMaster - sets up the Players and Caller
- Display: BingoDisplay - shows each player's card and displays winners, etc.
EXAMPLE 3: Write a program that creates random bridge deals and displays them in a specified format. (The specification defines a "deal" as consisting of four hands. It also describes a deck of cards, and shows how each card should be displayed.)
- Nouns: deal, hand, format, deck, card
- Basic Object: Card
- Collection: Deck - has an array of Cards Hand - has an array of Cards Deal - has an array of Hands Dealer - has a Deck, or several Decks
- Controller: Formatter - has a Deal and a TableDisplay
- Display: TableDisplay - could be a GUI
B) IDENTIFYING BEHAVIOURS
- Identify all the verbs in the program description that help lead to the solution of the programming task.
- These are likely behaviours that will probably become methods of the classes.
- Now decide which methods belong in which classes (encapsulation). Think carefully about who should do what.
C) DETERMINING RELATIONSHIPS BETWEEN CLASSES Inheritance Relationships
- Look for classes with common behaviours. This will help identify inheritance relationships.
- Recall the is-a relationship (superclass/subclass)
Composition Relationships
- These are defined by the has-a relationships.
- Usually if two classes have a composition relationship, one of them has an instance variable whose type is the other class.
- A wrapper class is the class that implements the has-a relationship with any objects it wraps.
UML (Unified Modeling Language) Diagrams
- These are used to keep track of the different relationships between classes and show the inheritance hierarchy in your program.
- This is a standard graphical scheme used by object-oriented programmers.
- You are not required to draw them, but you should be able to interpret simple UML diagrams.
UML Rules:
- Represent classes with rectangles
- Use angle brackets with the word "abstact"/"interface" to indicate abstract classes/interfaces.
- Show the is-a relationship between classes with an open up-arrow.
- Show the is-a relationship that involves an interface with an open, dotted up-arrow.
- Show the has-a relationship with a down or sideways arrow (composition).
D) IMPLEMENTING CLASSES
Bottom-Up Development
- For each method in a class, list all of the other classes needed to implement that particular method
- These classes are called collaborators.
- A class that has no collaborators is independent.
- Independent classes will be fully implemented and tested first before being incorporated into the overall project.
- These are usually the basic objects of the program, and unrelated classes can be in a project can be implemented concurrently by different programmers.
- A class can be tested by a dummy Tester class that will be discarded when the methods are working.
- Constructors, then methods, should be added and tested, one at a time.
- A driver class that contains a main method can be used to test the program as you go, so that the class can be tested fully before being incorporated as an object in a new class.
- When each of the independent classes is working, classes that depend on just one other class are implemented and tested, and so on.
- This may lead to a working, bare bones version of the project, and new features and enhancements can be added later.
- Design flaws can be corrected at each stage of development. Remember that a design is never set in stone, it just guides the implementation.
Top-Down Development (a.k.a. Functional Decomposition)
- Starting with the overview of the program, select the highest-level controlling object and the tasks needed, then proceed to the subsidiary classes to simplify existing classes.
- Makes use of procedural abstraction.
E) IMPLEMENTING METHODS
Procedural Abstraction
- Avoid repeated chunks of code as much as possible; aim for less redundancy!
- If there are parts of code that is doing the same task, e.g. searching, sorting, you should create a helper method that does that task
- Then, each part of the code that needs to do that task can just call the helper method rather than reusing the same code for each part.
- This makes your code more readable and easier to understand, as every method is doing one specific thing.
- Breaking up a long method into a series of smaller tasks is known as stepwise refinement.
Information Hiding
- Instance variables and helper methods are usually declared as private, so that client classes cannot access them.
Stub Method
- A stub method is a dummy method - it is temporary, and will be replaced once you have implemented and tested the actual method for it.
- It typically has an output statement to show that it was called in the correct place.
- Stubs are used because sometimes it makes more sense in the development of a class to test a calling method before testing a method it invokes.
- Another reason for using stubs is that several methods may be identified as necessary and initially written as headers for a class, but if we are implementing and testing each method one by one, the code in the class will not compile unless the placeholder methods are in the correct form syntactically. The example below demonstrates this.
// Implemented method that I want to test
// @return True if input number is even
public boolean isEven(int n) {
return (n % 2) == 0;
}
// TODO: Stub for method that hasn't been implemented yet
// @return the length of a list
public int length() {
return 0; // line that allows the class to compile, even though the result is incorrect
}
- Suppose I have just completed the implementation for the
isEven()
method above, and want to test it. - Assuming that the
length()
stub method below it is in the same class, the class will not compile (and therefore theisEven()
method cannot be executed for testing), if thereturn 0;
line in thelength()
method does not exist, as thelength()
method will not compile. - Because
return 0;
is not the correct implementation for the method (since a list will not always have the length of 0), it is a good practice to indicate that the method is just a stub and has not been completed (which is what theTODO
is for).
Algorithm
- A precise step-by-step procedure that solves a problem/achieves a goal.
- Do not begin writing code until the steps in the algorithm are completely clear to you.
APPLYING THE OOP PROCESS Please let me know if you want copies of the answers for these examples we did in class, as well as for the 3rd example we didn't get to.
EXAMPLE 1 A program must test the validity of a four-digit code number that a person will enter to be able to use a photocopy machine. The number is valid if the fourth digit equals the remainder when the sum of the first three digits is divided by seven.
EXAMPLE 2 A program must create a teacher's grade book. The program should maintain a class list of students for any number of classes in the teacher's schedule. A menu should be provided that allows the teacher to:
- create a new class of students
- enter a set of scores for any class
- correct any data that's been entered
- display the record of any student
- calculate the final average and grade for all students in a class
- print a class list, with or without grades
- add/delete/transfer (to another class) a student
- save all the data in a file
TEST DATA
- Not every possible input value can be tested, but a good programmer should be able to identify the different representative test cases that cover the boundaries of the test data
- Typical values in each part of the program domain should be used, as well as endpoint values or out-of-range values
- e.g. If your program should require positive input only, your program should include a negative value to check that the program handles it properly
EXAMPLE: A program must be written to insert a value into its correct position in this sorted list:
2 5 9
What should the test data include?
- A value less than 2
- A value between 2 and 5
- A value between 5 and 9
- A value greater than 9
- 2, 5, 9
- A negative value
- non-integer value (e.g. double, float, String, char)
TYPES OF ERRORS (BUGS)
- A compile-time error occurs during compilation of the program.
- The compiler is unable to translate the program into bytecode and prints an appropriate error message.
- Syntax errors (rule violations in the programming language) cause these types of errors.
- e.g. omitting semicolons or braces, using undeclared identifiers, using keywords inappropriately, having parameters that don't match in type and number, or invoking a method for an object whose class doesn't contain or isn't able to access that method
- A run-time error occurs during execution of the program.
- The Java run-time environment will throw an exception, stopping execution and printing an error message.
- e.g. dividing an integer by zero (ArithmeticException), using an array index that is out of bounds (ArrayIndexOutOfBoundsException), or attempting to open a file that cannot be found etc.
- An error that causes a program to run forever (infinite loop) can also be regarded as a run-time error (StackOverflowError)
- An intent or logic error is one that fails to carry out the specification of the program.
- The program compiles and runs successfully, but it does not do its required job.
- These can sometimes be the hardest errors to fix.
ROBUSTNESS
- This involves predicting things that could go wrong with your program input, and having preventative measures and failsafes for when they do.
- Always assume that the user of your program is a total idiot/has no idea what's going on, what the program is for, doesn't know how to use it etc. Always ask "What could possibly go wrong?".
- A robust program is one that won't give inaccurate answers for some input data, won't crash if the input data is invalid, or won't allow execution to proceed if invalid data is entered.
- Examples of bad input data: out-of-range numbers, characters instead of numerical data, a response other than yes/no to a yes/no question
- Bad input data that invalidates computation won't be detected by Java; you should include code in your program to catch the error, allows it to be fixed, and then allows program execution to continue
- Here is a YouTube video where the robustness of the different AIs (Siri, Google, and Alexa) is tested against people with different accents!
- This involves upgrading/fixing the code as circumstances change.
- New features may be added, new programmers may come aboard, new errors may turn up as more people use the program.
- To make this task easier, the original program must have clear and precise documentation.
- As new features are added, they go through the SDLC process as well.
We did the following questions in class:
- You are designing a program to simulate the students at your school. Starting with which one of these classes would would best demonstrate top-down design?
- A) The student body
- B) The senior class
- C) The girls in the senior class
- D) The girls taking programming in the senior class
- E) Mary Zhang, a girl in the programming class (LOL)
- If you were designing a unit test for the
isEven
method, which of these methods would you prefer to be working with and why? (Bonus points if you can read the answer options with a straight face).
// Option 1
public boolean isEven(int num) {
int digit = num % 10;
if (digit >= 5) {
if (digit == 6 || digit == 8)
return true;
else
return false;
} else if (digit == 0 || digit == 2 || digit == 4) {
return true;
} else {
return false;
}
}
// Option 2
public boolean isEven(int num) {
return (num % 2 == 0);
}
- A) Option 1 because it doesn't use modulus, so it's easier to understand.
- B) Option 1 because it is longer.
- C) Option 2 because there is only one path through the code, so fewer test cases are needed.
- D) Neither option requires testing because the intent of the method is clear from the name.
- E) It doesn't matter. Testing them would be the same.
- What is the term used to describe breaking a large problem into smaller, well-defined sections that are easier to code and maintain?
- A) Encapsulation
- B) Procedural abstraction
- C) Inheritance
- D) Static design
- E) Polymorphism
ANSWERS
- A
- C
- B
Assignments:
- From the Program Design & Analysis Question Set, complete: 1-3, 6-9, 15-21.
- Answers will be posted on the Topic 3 assignment answers issue.
Prep for Next Class:
- Bring laptop to next class