Detailed Table of Contents
Guidance for the item(s) below:
As usual, we cover a few more Java topics that are relevant to the iP this week. We start with topics related to data structures that you can use to work with collections of objects, as you'll need to be able to manage a collection of task objects in your iP.
But first, let's learn a programming technique called generics (similar to C++ templates) often used in such data structures.
Can explain Java Generics
Given below is an extract from the -- Java Tutorial, with some adaptations.
You can use polymorphism to write code that can work with multiple types, but that approach has some shortcomings.
Consider the following Box
class. It can be used only for storing Integer
objects.
public class BoxForIntegers {
private Integer x;
public void set(Integer x) {
this.x = x;
}
public Integer get() {
return x;
}
}
To store String
objects, another similar class is needed, resulting in the duplication of the entire class. As you can see, if you need to store many different types of objects, you could end up writing many similar classes.
public class BoxForString {
private String x;
public void set(String x) {
this.x = x;
}
public String get() {
return x;
}
}
One solution for this problem is to use polymorphism i.e., write the Box
class to store Object
objects.
public class Box {
private Object x;
public void set(Object x) {
this.x = x;
}
public Object get() {
return x;
}
}
The problem with this solution is, since its methods accept or return an Object
, you are free to pass in whatever you want, provided that it is not one of the primitive types. There is no way to verify, at compile time, how the class is used. One part of the code may place an Integer
in the box and expect to get Integers
out of it, while another part of the code may mistakenly pass in a String
, resulting in a runtime error.
Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar In a method getName(Person p)
, p
is a formal parameterformal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs. The difference is that the inputs to formal parameters are values, while the inputs to type parameters are types.
A generic Box
class allows you to define what type of elements will be put in the Box
. For example, you can instantiate a Box
object to keep Integer
elements so that any attempt to put a non-Integer
object in that Box
object will result in a compile error.
Can use Java Generics
This section includes extract from the -- Java Tutorial, with some adaptations.
The definition of a generic class includes a type parameter section, delimited by angle brackets (<>
). It specifies the type parameters (also called type variables) T1
, T2
, ..., and Tn
. A generic class is defined with the following format:
class name<T1, T2, ..., Tn> { /* ... */ }
Here is a generic Box
class. The class declaration Box<T>
introduces the type variable, T
, which is also used inside the class to refer to the same type.
Using Object
as the type:
public class Box {
private Object x;
public void set(Object x) {
this.x = x;
}
public Object get() {
return x;
}
}
A generic Box
using type parameter T
:
public class Box<T> {
private T x;
public void set(T x) {
this.x = x;
}
public T get() {
return x;
}
}
As you can see, all occurrences of Object
are replaced by T
.
To reference the generic Box
class from within your code, you must perform a generic type invocation, which replaces T
with some concrete value, such as Integer
. It is similar to an ordinary method invocation, but instead of passing an argument to a method, you are passing a type argument enclosed within angle brackets — e.g., <Integer>
or <String, Integer>
— to the generic class itself. Note that in some cases you can omit the type parameter i.e., <>
if the type parameter can be inferred from the context.
Using the generic Box
class to store Integer
objects:
Box<Integer> integerBox;
integerBox = new Box<>(); // type parameter omitted as it can be inferred
integerBox.set(Integer.valueOf(4));
Integer i = integerBox.get(); // returns an Integer
Box<Integer> integerBox;
simply declares that integerBox
will hold a reference to a "Box of Integer", which is how Box<Integer>
is read.integerBox = new Box<>();
instantiates a Box<Integer>
class. Note the <>
(an empty pair of angle brackets, also called the diamond operator) between the class name and the parenthesis.The compiler is able to check for type errors when using generic code.
The code below will fail because it creates a Box<String>
and then tries to pass Double
objects into it.
Box<String> stringBox = new Box<>();
stringBox.set(Double.valueOf(5.0)); //compile error!
A generic class can have multiple type parameters.
The generic OrderedPair
class, which implements the generic Pair interface:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
The following statements create two instantiations of the OrderedPair
class:
Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
Pair<String, String> p2 = new OrderedPair<>("hello", "world");
The code, new OrderedPair<String, Integer>
, instantiates K
as a String
and V
as an Integer
. Therefore, the parameter types of OrderedPair
's constructor are String
and Integer
, respectively.
A type variable can be any non-primitive type you specify: any class type, any interface type, any array type, or even another type variable.
By convention, type parameter names are single, uppercase letters. The most commonly used type parameter names are:
E
- Element (used extensively by the Java Collections Framework)K
- KeyN
- NumberT
- TypeV
- ValueS
, U
, V
etc. - 2nd, 3rd, 4th types Guidance for the item(s) below:
Next, we learn about some built-in data structures that can be used to manage a collection of objects.
Can explain the Collections framework
This section uses extracts from the -- Java Tutorial, with some adaptations.
A collection — sometimes called a container — is simply an object that groups multiple elements into a single unit. Collections are used to store, retrieve, manipulate, and communicate aggregate data.
Typically, collections represent data items that form a natural group, such as a poker hand (a collection of cards), a mail folder (a collection of letters), or a telephone directory (a mapping of names to phone numbers).
The collections framework is a unified architecture for representing and manipulating collections. It contains the following:
Interfaces: These are abstract data types that represent collections. Interfaces allow collections to be manipulated independently of the details of their representation.
Example: the List<E>
interface can be used to manipulate list-like collections which may be implemented in different ways such as ArrayList<E>
or LinkedList<E>
.
Implementations: These are the concrete implementations of the collection interfaces. In essence, they are reusable data structures.
Example: the ArrayList<E>
class implements the List<E>
interface while the HashMap<K, V>
class implements the Map<K, V>
interface.
Algorithms: These are the methods that perform useful computations, such as searching and sorting, on objects that implement collection interfaces. The algorithms are said to be polymorphic: that is, the same method can be used on many different implementations of the appropriate collection interface.
Example: the sort(List<E>)
method can sort a collection that implements the List<E>
interface.
A well-known example of collections frameworks is the C++ Standard Template Library (STL). Although both are collections frameworks and the syntax look similar, note that there are important philosophical and implementation differences between the two.
The following list describes the core collection interfaces:
Collection
— the root of the collection hierarchy. A collection represents a group of objects known as its elements. The Collection interface is the least common denominator that all collections implement and is used to pass collections around and to manipulate them when maximum generality is desired. Some types of collections allow duplicate elements, and others do not. Some are ordered and others are unordered. The Java platform doesn't provide any direct implementations of this interface but provides implementations of more specific subinterfaces, such as Set
and List
. Also see the Collection
API.
Set
— a collection that cannot contain duplicate elements. This interface models the mathematical set abstraction and is used to represent sets, such as the cards comprising a poker hand, the courses making up a student's schedule, or the processes running on a machine. Also see the Set
API.
List
— an ordered collection (sometimes called a sequence). List
s can contain duplicate elements. The user of a List
generally has precise control over where in the list each element is inserted and can access elements by their integer index (position). Also see the List
API.
Queue
— a collection used to hold multiple elements prior to processing. Besides basic Collection
operations, a Queue
provides additional insertion, extraction, and inspection operations. Also see the Queue
API.
Map
— an object that maps keys to values. A Map
cannot contain duplicate keys; each key can map to at most one value. Also see the Map
API.
Others: Deque
, SortedSet
, SortedMap
Can use the ArrayList class
The ArrayList
class is a resizable-array implementation of the List
interface. Unlike a normal array
, an ArrayList
can grow in size as you add more items to it. The example below illustrates some of the useful methods of the ArrayList
class using an ArrayList
of String
objects.
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String args[]) {
ArrayList<String> items = new ArrayList<>();
System.out.println("Before adding any items:" + items);
items.add("Apple");
items.add("Box");
items.add("Cup");
items.add("Dart");
print("After adding four items: " + items);
items.remove("Box"); // remove item "Box"
print("After removing Box: " + items);
items.add(1, "Banana"); // add "Banana" at index 1
print("After adding Banana: " + items);
items.add("Egg"); // add "Egg", will be added to the end
items.add("Cup"); // add another "Cup"
print("After adding Egg: " + items);
print("Number of items: " + items.size());
print("Index of Cup: " + items.indexOf("Cup"));
print("Index of Zebra: " + items.indexOf("Zebra"));
print("Item at index 3 is: " + items.get(2));
print("Do we have a Box?: " + items.contains("Box"));
print("Do we have an Apple?: " + items.contains("Apple"));
items.clear();
print("After clearing: " + items);
}
private static void print(String text) {
System.out.println(text);
}
}
Before adding any items:[]
After adding four items: [Apple, Box, Cup, Dart]
After removing Box: [Apple, Cup, Dart]
After adding Banana: [Apple, Banana, Cup, Dart]
After adding Egg: [Apple, Banana, Cup, Dart, Egg, Cup]
Number of items: 6
Index of Cup: 2
Index of Zebra: -1
Item at index 3 is: Cup
Do we have a Box?: false
Do we have an Apple?: true
After clearing: []
Resources
Add the missing methods to the class given below so that it produces the output given.
Use an ArrayList
to store the numbers.
public class Main {
//TODO: add your methods here
public static void main(String[] args) {
System.out.println("Adding numbers to the list");
addNumber(3);
addNumber(8);
addNumber(24);
System.out.println("The total is: " + getTotal());
System.out.println("8 in the list : " + isFound(8) );
System.out.println("5 in the list : " + isFound(5) );
removeNumber(8);
System.out.println("The total is: " + getTotal());
}
}
Adding numbers to the list
[3]
[3, 8]
[3, 8, 24]
The total is: 35
8 in the list : true
5 in the list : false
[3, 24]
The total is: 27
Hint
Can use the HashMap class
HashMap
is an implementation of the Map
interface. It allows you to store a collection of key-value pairs. The example below illustrates how to use a HashMap<String, Point>
to maintain a list of coordinates and their identifiers e.g., the identifier x1
is used to identify the point 0,0
where x1
is the key and 0,0
is the value.
import java.awt.Point;
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo {
public static void main(String[] args) {
HashMap<String, Point> points = new HashMap<>();
// put the key-value pairs in the HashMap
points.put("x1", new Point(0, 0));
points.put("x2", new Point(0, 5));
points.put("x3", new Point(5, 5));
points.put("x4", new Point(5, 0));
// retrieve a value for a key using the get method
print("Coordinates of x1: " + pointAsString(points.get("x1")));
// check if a key or a value exists
print("Key x1 exists? " + points.containsKey("x1"));
print("Key x1 exists? " + points.containsKey("y1"));
print("Value (0,0) exists? " + points.containsValue(new Point(0, 0)));
print("Value (1,2) exists? " + points.containsValue(new Point(1, 2)));
// update the value of a key to a new value
points.put("x1", new Point(-1,-1));
// iterate over the entries
for (Map.Entry<String, Point> entry : points.entrySet()) {
print(entry.getKey() + " = " + pointAsString(entry.getValue()));
}
print("Number of keys: " + points.size());
points.clear();
print("Number of keys after clearing: " + points.size());
}
public static String pointAsString(Point p) {
return "[" + p.x + "," + p.y + "]";
}
public static void print(String s) {
System.out.println(s);
}
}
Coordinates of x1: [0,0]
Key x1 exists? true
Key x1 exists? false
Value (0,0) exists? true
Value (1,2) exists? false
x1 = [-1,-1]
x2 = [0,5]
x3 = [5,5]
x4 = [5,0]
Number of keys: 4
Number of keys after clearing: 0
The class given below keeps track of how many people signup to attend an event on each day of the week. Add the missing methods so that it produces the output given.
Use an HashMap
to store the number of entries for each day.
public class Main {
private static HashMap<String, Integer> roster = new HashMap<>();
//TODO: add your methods here
public static void main(String[] args) {
addToRoster("Monday"); // i.e., one person signed up for Monday
addToRoster("Wednesday"); // i.e., one person signed up for Wednesday
addToRoster("Wednesday"); // i.e., another person signed up for Wednesday
addToRoster("Friday");
addToRoster("Monday");
printRoster();
}
}
Monday => 2
Friday => 1
Wednesday => 2
Hint
Resources
Guidance for the item(s) below:
Another thing you need to do in your project soon is reading from and writing to files. Let's learn how to do that next.
Can read/write text files using Java
You can use the java.io.File
class to represent a file object. It can be used to access properties of the file object.
This code creates a File
object to represent a file fruits.txt
that exists in the data
directory relative to the current working directory and uses that object to print some properties of the file.
import java.io.File;
public class FileClassDemo {
public static void main(String[] args) {
File f = new File("data/fruits.txt");
System.out.println("full path: " + f.getAbsolutePath());
System.out.println("file exists?: " + f.exists());
System.out.println("is Directory?: " + f.isDirectory());
}
}
full path: C:\sample-code\data\fruits.txt
file exists?: true
is Directory?: false
If you use backslash to specify the file path in a Windows computer, you need to use an additional backslash as an escape character because the backslash by itself has a special meaning. e.g., use "data\\fruits.txt"
, not "data\fruits.txt"
. Alternatively, you can use forward slash "data/fruits.txt"
(even on Windows).
You can read from a file using a Scanner
object that uses a File
object as the source of data.
This code uses a Scanner
object to read (and print) contents of a text file line-by-line:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class FileReadingDemo {
private static void printFileContents(String filePath) throws FileNotFoundException {
File f = new File(filePath); // create a File for the given file path
Scanner s = new Scanner(f); // create a Scanner using the File as the source
while (s.hasNext()) {
System.out.println(s.nextLine());
}
}
public static void main(String[] args) {
try {
printFileContents("data/fruits.txt");
} catch (FileNotFoundException e) {
System.out.println("File not found");
}
}
}
i.e., contents of the data/fruits.txt
5 Apples
3 Bananas
6 Cherries
You can use a java.io.FileWriter
object to write to a file.
The writeToFile
method below uses a FileWrite
object to write to a file. The method is being used to write two lines to the file temp/lines.txt
.
import java.io.FileWriter;
import java.io.IOException;
public class FileWritingDemo {
private static void writeToFile(String filePath, String textToAdd) throws IOException {
FileWriter fw = new FileWriter(filePath);
fw.write(textToAdd);
fw.close();
}
public static void main(String[] args) {
String file2 = "temp/lines.txt";
try {
writeToFile(file2, "first line" + System.lineSeparator() + "second line");
} catch (IOException e) {
System.out.println("Something went wrong: " + e.getMessage());
}
}
}
Contents of the temp/lines.txt
:
first line
second line
Note that you need to call the close()
method of the FileWriter
object for the writing operation to be completed.
You can create a FileWriter
object that appends to the file (instead of overwriting the current content) by specifying an additional boolean parameter to the constructor.
The method below appends to the file rather than overwrites.
private static void appendToFile(String filePath, String textToAppend) throws IOException {
FileWriter fw = new FileWriter(filePath, true); // create a FileWriter in append mode
fw.write(textToAppend);
fw.close();
}
The java.nio.file.Files
is a utility class that provides several useful file operations. It relies on the java.nio.file.Paths
file to generate Path
objects that represent file paths.
This example uses the Files
class to copy a file and delete a file.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class FilesClassDemo {
public static void main(String[] args) throws IOException{
Files.copy(Paths.get("data/fruits.txt"), Paths.get("temp/fruits2.txt"));
Files.delete(Paths.get("temp/fruits2.txt"));
}
}
The techniques above are good enough to manipulate simple text files. Note that it is also possible to perform file I/O operations using other classes.
Guidance for the item(s) below:
At some point, you want to package your code as a single executable file. The next topic covers how to do that.
Can use JAR files
Java applications are typically delivered as JAR (short for Java Archive) files. A JAR contains Java classes and other resources (icons, media files, etc.).
An executable JAR file can be launched using the java -jar
command e.g., java -jar foo.jar
launches the foo.jar
file.
The IDE or build tools such as Gradle can help you to package your application as a JAR file.
See the tutorial Working with JAR files @se-edu/guides to learn how to create and use JAR files.
Guidance for the item(s) below:
Last week, we had a quick peek at the requirements aspect. Let's study the topic properly this week, starting with an introduction to the topic.
Can explain requirements
A software requirement specifies a need to be fulfilled by the software product.
A software project may be,
In either case, requirements need to be gathered, analyzed, specified, and managed.
Requirements come from stakeholders.
Stakeholder: A party that is potentially affected by the software project. e.g. users, sponsors, developers, interest groups, government agencies, etc.
Identifying requirements is often not easy. For example, stakeholders may not be aware of their precise needs, may not know how to communicate their requirements correctly, may not be willing to spend effort in identifying requirements, etc.
Can explain non-functional requirements
Requirements can be divided into two in the following way:
Some examples of non-functional requirement categories:
Some concrete examples of NFRs
You may have to spend an extra effort in digging NFRs out as early as possible because,
Exercises
Can explain prioritizing requirements
Requirements can be prioritized based on the importance and urgency, while keeping in mind the constraints of schedule, budget, staff resources, quality goals, and other constraints.
A common approach is to group requirements into priority categories. Note that all such scales are subjective, and stakeholders define the meaning of each level in the scale for the project at hand.
An example scheme for categorizing requirements:
Essential
: The product must have this requirement fulfilled or else it does not get user acceptance.Typical
: Most similar systems have this feature although the product can survive without it.Novel
: New features that could differentiate this product from the rest.Other schemes:
High
, Medium
, Low
Must-have
, Nice-to-have
, Unlikely-to-have
Level 0
, Level 1
, Level 2
, ...Some requirements can be discarded if they are considered ‘out of the extent to which the software features should goscope’.
The requirement given below is for a Calendar application. Stakeholders of the software (e.g. product designers) might decide the following requirement is not in the scope of the software.
The software records the actual time taken by each task and show the difference between the actual and scheduled time for the task.
Can explain quality of requirements
Here are some characteristics of well-defined requirements [📖 zielczynski]:
Besides these criteria for individual requirements, the set of requirements as a whole should be
Guidance for the item(s) below:
Next, a quick look at techniques used for gathering requirements. They are mostly common-sense but let's go through them for completeness' sake anyway.
Can explain brainstorming
Brainstorming: A group activity designed to generate a large number of diverse and creative ideas for the solution of a problem.
In a brainstorming session there are no "bad" ideas. The aim is to generate ideas; not to validate them. Brainstorming encourages you to "think outside the box" and put "crazy" ideas on the table without fear of rejection.
Exercises
Can explain product surveys
Studying existing products can unearth shortcomings of existing solutions that can be addressed by a new product. Product manuals and other forms of documentation of an existing system can tell us how the existing solutions work.
When developing a game for a mobile device, a look at a similar PC game can give insight into the kind of features and interactions the mobile game can offer.
Can explain observation
Observing users in their natural work environment can uncover product requirements. Usage data of an existing system can also be used to gather information about how an existing system is being used, which can help in building a better replacement e.g. to find the situations where the user makes mistakes when using the current system.
Can explain interviews
Interviewing stakeholders and domain experts can produce useful information about project requirements.
Domain expert: An expert of a discipline to which the product is connected e.g., for a software used for Accounting, a domain expert is someone who is an expert of Accounting.
Can explain focus groups
[source]
Focus groups are a kind of informal interview within an interactive group setting. A group of people (e.g. potential users, beta testers) are asked about their understanding of a specific issue, process, product, advertisement, etc.
Can explain prototyping
Prototype: A prototype is a mock up, a scaled down version, or a partial system constructed
Prototyping can uncover requirements, in particular, those related to how users interact with the system. UI prototypes or mock ups are often used in brainstorming sessions, or in meetings with the users to get quick feedback from them.
A mock up (also called a wireframe diagram) of a dialog box:
[source: plantuml.com]
Prototyping can be used for discovering as well as specifying requirements e.g. a UI prototype can serve as a specification of what to build.
Guidance for the item(s) below:
As you may have noticed in the section about prototyping, some techniques can be used for both gathering and specifying requirements. The two things often go hand-in-hand any way. For example, you gather some requirements, specify them in some form, and show them to stakeholders to get feedback.
Now, let's look at other techniques used for specifying requirements.Of course, you've seen two of them already.
Can explain prose
A textual description (i.e. prose) can be used to describe requirements. Prose is especially useful when describing abstract ideas such as the vision of a product.
The product vision of the TEAMMATES Project given below is described using prose.
TEAMMATES aims to become the biggest student project in the world (biggest here refers to 'many contributors, many users, large code base, evolving over a long period'). Furthermore, it aims to serve as a training tool for Software Engineering students who want to learn SE skills in the context of a non-trivial real software product.
Avoid using lengthy prose to describe requirements; they can be hard to follow.
Can write simple user stories
User story: User stories are short, simple descriptions of a feature told from the perspective of the person who desires the new capability, usually a user or customer of the system. [Mike Cohn]
A common format for writing user stories is:
User story format: As a {user type/role} I can {function} so that {benefit}
Examples (from a Learning Management System):
You can write user stories on index cards or sticky notes, and arrange them on walls or tables, to facilitate planning and discussion. Alternatively, you can use a software (e.g., GitHub Project Boards, Trello, Google Docs, ...) to manage user stories digitally.
User stories in use
With sticky notes
With paper
With software
Exercises
Can write more detailed user stories
The {benefit}
can be omitted if it is obvious.
As a user, I can login to the system so that I can access my data
It is recommended to confirm there is a concrete benefit even if you omit it from the user story. If not, you could end up adding features that have no real benefit.
You can add more characteristics to the {user role}
to provide more context to the user story.
You can write user stories at various levels. High-level user stories, called epics (or themes) cover bigger functionality. You can then break down these epics to multiple user stories of normal size.
[Epic] As a lecturer, I can monitor student participation levels
You can add conditions of satisfaction to a user story to specify things that need to be true for the user story implementation to be accepted as ‘done’.
As a lecturer, I can view the forum post count of each student so that I can identify the activity level of students in the forum.
Conditions:
Separate post count for each forum should be shown
Total post count of a student should be shown
The list should be sortable by student name and post count
Other useful info that can be added to a user story includes (but not limited to)
Exercises
Can use user stories to manage requirements of project
User stories capture user requirements in a way that is convenient for i.e. which features to include in the productscoping, i.e. how much effort each feature will takeestimation, and i.e. when to deliver each featurescheduling.
[User stories] strongly shift the focus from writing about features to discussing them. In fact, these discussions are more important than whatever text is written. [Mike Cohn, MountainGoat Software 🔗]
User stories differ from e.g. a description of the requirements written in prosetraditional requirements specifications mainly in the level of detail. User stories should only provide enough details to make a reasonably low risk estimate of how long the user story will take to implement. When the time comes to implement the user story, the developers will meet with the customer face-to-face to work out a more detailed description of the requirements. [ more...]
User stories can capture non-functional requirements too because even NFRs must benefit some stakeholder.
An example of an NFR captured as a user story:
As a | I want to | so that |
---|---|---|
impatient user | to be able experience reasonable response time from the website while up to 1000 concurrent users are using it | I can use the app even when the traffic is at the maximum expected level |
Given their lightweight nature, user stories are quite handy for recording requirements during early stages of requirements gathering.
Given below is a possible recipe you can take when using user stories for early stages of requirement gathering.
Step 0: Clear your mind of preconceived product ideas
Even if you already have some idea of what your product will look/behave like in the end, clear your mind of those ideas. The product is the solution. At this point, we are still at the stage of figuring out the problem (i.e., user requirements). Let's try to get from the problem to the solution in a systematic way, one step at a time.
Step 1: Define the target user as a persona:
Decide your target user's profile (e.g. a student, office worker, programmer, salesperson) and work patterns (e.g. Does he work in groups or alone? Does he share his computer with others?). A clear understanding of the target user will help when deciding the importance of a user story. You can even narrow it down to a persona. Here is an example:
Jean is a university student studying in a non-IT field. She interacts with a lot of people due to her involvement in university clubs/societies. ...
Step 2: Define the problem scope:
Decide the exact problem you are going to solve for the target user. It is also useful to specify what related problems it will not solve so that the exact scope is clear.
ProductX helps Jean keep track of all her school contacts. It does not cover communicating with contacts.
Step 3: List scenarios to form a narrative:
Think of the various scenarios your target user is likely to go through as she uses your app. Following a chronological sequence as if you are telling a story might be helpful.
A. First use:
- Jean gets to know about ProductX. She downloads it and launches it to check out what it can do.
- After playing around with the product for a bit, Jean wants to start using it for real.
- ...
B. Second use: (Jean is still a beginner)
- Jean launches ProductX. She wants to find ...
- ...
C. 10th use: (Jean is a little bit familiar with the app)
- ...
D. 100th use: (Jean is an expert user)
- Jean launches the app and does ... and ... followed by ... as per her usual habit.
- Jean feels some of the data in the app are no longer needed. She wants to get rid of them to reduce clutter.
More examples that might apply to some products:
- Jean uses the app at the start of the day to ...
- Jean uses the app before going to sleep to ...
- Jean hasn't used the app for a while because she was on a three-month training programme. She is now back at work and wants to resume her daily use of the app.
- Jean moves to another company. Some of her clients come with her but some don't.
- Jean starts freelancing in her spare time. She wants to keep her freelancing clients separate from her other clients.
Step 4: List the user stories to support the scenarios:
Based on the scenarios, decide on the user stories you need to support. For example, based on the scenario 'A. First use', you might have user stories such as these:
- As a potential user exploring the app, I can see the app populated with sample data, so that I can easily see how the app will look like when it is in use.
- As a user ready to start using the app, I can purge all current data, so that I can get rid of sample/experimental data I used for exploring the app.
To give another example, based on the scenario 'D. 100th use', you might have user stories such as these:
- As an expert user, I can create shortcuts for tasks, so that I can save time on frequently performed tasks.
- As a long-time user, I can archive/hide unused data, so that I am not distracted by irrelevant data.
Do not 'evaluate' the value of user stories while brainstorming. Reason: an important aspect of brainstorming is not judging the ideas generated.
Other tips:
As a user, I want to see a list of tasks that needs my attention most at the present time, so that I pay attention to them first.
While use cases can be recorded on e.g. index cards or sticky notesphysical paper in the initial stages, an online tool is more suitable for longer-term management of user stories, especially if the team is not physically in the same locationco-located.
Tool Examples: How to use some example online tools to manage user stories
Resources
Can explain feature list
Feature list: A list of features of a product grouped according to some criteria such as aspect, priority, order of delivery, etc.
A sample feature list from a simple Minesweeper game (only a brief description has been provided to save space):
Guidance for the item(s) below:
The technique of use cases (don't confuse with user stories) covered below is another widely used technique. However, we only cover it briefly (i.e., you'll learn what it is but not how to use it) to lower to module workload.
Can explain use cases
Use case: A description of a set of sequences of actions, including variants, that a system performs to yield an observable result of value to an actor.[ 📖 : uml-user-guideThe Unified Modeling Language User Guide, 2e, G Booch, J Rumbaugh, and I Jacobson ]
Actor: An actor (in a use case) is a role played by a user. An actor can be a human or another system. Actors are not part of the system; they reside outside the system.
A use case describes an interaction between the user and the system for a specific functionality of the system.
Example 1: 'transfer money' use case for an online banking system
System: Online Banking System (OBS) Use case: UC23 - Transfer Money Actor: User MSS: 1. User chooses to transfer money. 2. OBS requests for details of the transfer. 3. User enters the requested details. 4. OBS requests for confirmation. 5. User confirms. 6. OBS transfers the money and displays the new account balance. Use case ends.
Extensions: 3a. OBS detects an error in the entered data. 3a1. OBS requests for the correct data. 3a2. User enters new data. Steps 3a1-3a2 are repeated until the data entered are correct. Use case resumes from step 4. 3b. User requests to effect the transfer in a future date. 3b1. OBS requests for confirmation. 3b2. User confirms future transfer. Use case ends. *a. At any time, User chooses to cancel the transfer. *a1. OBS requests to confirm the cancellation. *a2. User confirms the cancellation. Use case ends.
Example 2: 'upload file' use case of an LMS
UML includes a diagram type called use case diagrams that can illustrate use cases of a system visually, providing a visual ‘table of contents’ of the use cases of a system.
In the example on the right, note how use cases are shown as ovals and user roles relevant to each use case are shown as stick figures connected to the corresponding ovals.
Use cases capture the functional requirements of a system.
Can explain glossary
Glossary: A glossary serves to ensure that all stakeholders have a common understanding of the noteworthy terms, abbreviations, acronyms etc.
Here is a partial glossary from a variant of the Snakes and Ladders game:
Guidance for the item(s) below:
Now that you know how to use IDE basic features, it's worth looking at even more ways of leveraging their power. In particular, the debugging feature can be indispensable at times.
Can explain debugging
Debugging is the process of discovering defects in the program. Here are some approaches to debugging:
Exiting process() method, x is 5.347
. This approach is not recommended due to these reasons:
Can step through a program using a debugger
This video (from LaunchCode) gives a pretty good explanation of how to use the IntelliJ IDEA debugger.
Guidance for the item(s) below:
When working with Git, sooner or later you will face a problem called merge conflict. Let's learn how to deal with them.
Can use Git to resolve merge conflicts
Merge conflicts happen when you try to combine two incompatible versions (e.g., merging a branch to another but each branch changed the same part of the code in a different way).
Here are the steps to simulate a merge conflict and use it to learn how to resolve merge conflicts.
0. Create an empty repo or clone an existing repo, to be used for this activity.
1. Start a branch named fix1
in the repo. Create a commit that adds a line with some text to one of the files.
2. Switch back to master
branch. Create a commit with a conflicting change i.e. it adds a line with some different text in the exact location the previous line was added.
3. Try to merge the fix1
branch onto the master
branch. Git will pause mid-way during the merge and report a merge conflict. If you open the conflicted file, you will see something like this:
COLORS
------
blue
<<<<<< HEAD
black
=======
green
>>>>>> fix1
red
white
4. Observe how the conflicted part is marked between a line starting with <<<<<<
and a line starting with >>>>>>
, separated by another line starting with =======
.
Highlighted below is the conflicting part that is coming from the master
branch:
blue
<<<<<< HEAD
black
=======
green
>>>>>> fix1
red
This is the conflicting part that is coming from the fix1
branch:
blue
<<<<<< HEAD
black
=======
green
>>>>>> fix1
red
5. Resolve the conflict by editing the file. Let us assume you want to keep both lines in the merged version. You can modify the file to be like this:
COLORS
------
blue
black
green
red
white
6. Stage the changes, and commit.
Guidance for the item(s) below:
Another step in our journey to improve code quality: Let's learn some legit but unsafe practices we should avoid.
Can explain the need for avoiding error-prone shortcuts
It is safer to use language constructs in the way they are meant to be used, even if the language allows shortcuts. Such coding practices are common sources of bugs. Know them and avoid them.
Can improve code quality using technique: use the default branch
Always include a default branch in case
statements.
Furthermore, use it for the intended default action and not just to execute the last option. If there is no default action, you can use the default
branch to detect errors (i.e. if execution reached the default
branch, raise a suitable error). This also applies to the final else
of an if-else
construct. That is, the final else
should mean 'everything else', and not the final option. Do not use else
when an if
condition can be explicitly specified, unless there is absolutely no other possibility.
Bad
if (red) print "red";
else print "blue";
Good
if (red) print "red";
else if (blue) print "blue";
else error("incorrect input");
Can improve code quality using technique: don't recycle variables or parameters
Bad
double computeRectangleArea(double length, double width) {
length = length * width;
return length;
}
def compute_rectangle_area(length, width):
length = length * width
return length
Good
double computeRectangleArea(double length, double width) {
double area;
area = length * width;
return area;
}
def compute_rectangle_area(length, width):
area = length * width
return area
}
Can improve code quality using technique: delete dead code
You might feel reluctant to delete code you have painstakingly written, even if you have no use for that code anymore ("I spent a lot of time writing that code; what if I need it again?"). Consider all code as baggage you have to carry; get rid of unused code the moment it becomes redundant. If you need that code again, simply recover it from the revision control tool you are using. Deleting code you wrote previously is a sign that you are improving.
Can improve code quality using technique: minimize scope of variables
Minimize global variables. Global variables may be the most convenient way to pass information around, but they do create implicit links between code segments that use the global variable. Avoid them as much as possible.
Define variables in the least possible scope. For example, if the variable is used only within the if
block of the conditional statement, it should be declared inside that if
block.
The most powerful technique for minimizing the scope of a local variable is to declare it where it is first used. -- Effective Java, by Joshua Bloch
Resources:
Can improve code quality using technique: minimize code duplication
Code duplication, especially when you copy-paste-modify code, often indicates a poor quality implementation. While it may not be possible to have zero duplication, always think twice before duplicating code; most often there is a better alternative.
This guideline is closely related to the DRY Principle .