Week 3 [Jan 28]
-
We start formal tutorials this week. The tutorial time table is on the module website.
-
There is graded project task this week!
-
Note that the official ⏰ deadline to achieve weekly project tasks is the midnight before your tutorial. Our scripts that detect your work run at midnight and only the work that's done by midnight will be eligible for marks (for cases where the task is graded).
Team formation
- We will form project teams at the start of the tutorial this week; please arrive on time. If you are not there at the team forming time we'll have to put you into a team randomly.
Additional OOP topics carried forward from week 2
[W3.1] OOP: Inheritance
Can explain the meaning of inheritance
The OOP concept Inheritance allows you to define a new class based on an existing class.
For example, you can use inheritance to define an EvaluationReport
class based on an existing Report
class so that the EvaluationReport
class does not have to duplicate code that is already implemented in the Report
class. The EvaluationReport
can inherit the wordCount
attribute and the print()
method from the base class Report
.
- Other names for Base class: Parent class, Super class
- Other names for Derived class: Child class, Sub class, Extended class
A superclass is said to be more general than the subclass. Conversely, a subclass is said to be more specialized than the superclass.
Applying inheritance on a group of similar classes can result in the common parts among classes being extracted into more general classes.
Man
and Woman
behaves the same way for certain things. However, the two classes cannot be simply replaced with a more general class Person
because of the need to distinguish between Man
and Woman
for certain other things. A solution is to add the Person
class as a superclass (to contain the code common to men and woment) and let Man
and Woman
inherit from Person
class.
Inheritance implies the derived class can be considered as a sub-type of the base class (and the base class is a super-type of the derived class), resulting in an is a relationship.
Inheritance does not necessarily mean a sub-type relationship exists. However, the two often go hand-in-hand. For simplicity, at this point let us assume inheritance implies a sub-type relationship.
To continue the previous example,
Woman
is aPerson
Man
is aPerson
Inheritance relationships through a chain of classes can result in inheritance hierarchies (aka inheritance trees).
Two inheritance hierarchies/trees are given below. Note that the triangle points to the parent class. Observe how the Parrot
is a Bird
as well as it is an Animal
.
Multiple Inheritance is when a class inherits directly from multiple classes. Multiple inheritance among classes is allowed in some languages (e.g., Python, C++) but not in other languages (e.g., Java, C#).
The Honey
class inherits from the Food
class and the Medicine
class because honey can be consumed as a food as well as a medicine (in some oriental medicine practices). Similarly, a Car
is an Vehicle
, an Asset
and a Liability
.
Which of these are correct?
- a. Superclass is more general than the subclass.
- b. Child class is more specialized than the parent class.
- c. A class can inherit behavior from its ancestor classes (ancestor classes = classes above it in the inheritance hierarchy).
- d. Code reuse can be one benefit of inheritance.
- e. A change to the superclass will not affect its subclasses.
(a) (b) (c) (d)
Explanation: (e) is incorrect. Because subclasses inherit behavior from the superclass, any changes to the superclass could affect subclasses.
Can interpret class inheritance in class diagrams
You can use a triangle and a solid line (not to be confused with an arrow) to indicate class inheritance.
Notation:
Examples: The Car
class inherits from the Vehicle
class. The Cat
and Dog
classes inherit from the Pet
class.
Can explain method overloading
Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.
Type Signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.
Method | Type Signature |
---|---|
int add(int X, int Y) |
(int, int) |
void add(int A, int B) |
(int, int) |
void m(int X, double Y) |
(int, double) |
void m(double X, int Y) |
(double, int) |
In the case below, the calculate
method is overloaded because the two methods have the same name but different type signatures (String)
and (int)
calculate(String): void
calculate(int): void
Can use basic inheritance
Given below is an extract from the -- Java Tutorial, with slight adaptations.
A class that is derived from another class is called a subclass (also a derived class, extended class, or child class). The class from which the subclass is derived is called a superclass (also a base class or a parent class).
A subclass inherits all the members (fields, methods, and nested classes) from its superclass. Constructors are not members, so they are not inherited by subclasses, but the constructor of the superclass can be invoked from the subclass.
Every class has one and only one direct superclass (single inheritance), except the Object
class, which has no superclass, . In the absence of any other explicit superclass, every class is implicitly a subclass of Object
. Classes can be derived from classes that are derived from classes that are derived from classes, and so on, and ultimately derived from the topmost class, Object
. Such a class is said to be descended from all the classes in the inheritance chain stretching back to Object
. Java does not support multiple inheritance among classes.
The java.lang.Object
class defines and implements behavior common to all classes—including the ones that you write. In the Java platform, many classes derive directly from Object
, other classes derive from some of those classes, and so on, forming a hierarchy of classes.
The keyword extends
indicates one class inheriting from another.
Here is the sample code for a possible implementation of a Bicycle
class and a MountainBike
class that is a subclass of the Bicycle
:
public class Bicycle {
public int gear;
public int speed;
public Bicycle(int startSpeed, int startGear) {
gear = startGear;
speed = startSpeed;
}
public void setGear(int newValue) {
gear = newValue;
}
public void applyBrake(int decrement) {
speed -= decrement;
}
public void speedUp(int increment) {
speed += increment;
}
}
public class MountainBike extends Bicycle {
// the MountainBike subclass adds one field
public int seatHeight;
// the MountainBike subclass has one constructor
public MountainBike(int startHeight, int startSpeed, int startGear) {
super(startSpeed, startGear);
seatHeight = startHeight;
}
// the MountainBike subclass adds one method
public void setHeight(int newValue) {
seatHeight = newValue;
}
}
A subclass inherits all the fields and methods of the superclass. In the example above, MountainBike
inherits all the fields and methods of Bicycle
and adds the field seatHeight
and a method to set it.
Accessing Superclass Members
If your method overrides one of its superclass's methods, you can invoke the overridden method through the use of the keyword super
. You can also use super
to refer to a
Consider this class, Superclass
and a subclass, called Subclass
, that overrides printMethod()
:
public class Superclass {
public void printMethod() {
System.out.println("Printed in Superclass.");
}
}
public class Subclass extends Superclass {
// overrides printMethod in Superclass
public void printMethod() {
super.printMethod();
System.out.println("Printed in Subclass");
}
public static void main(String[] args) {
Subclass s = new Subclass();
s.printMethod();
}
}
Printed in Superclass.
Printed in Subclass
Within Subclass
, the simple name printMethod()
refers to the one declared in Subclass
, which overrides the one in Superclass
. So, to refer to printMethod()
inherited from Superclass
, Subclass
must use a qualified name, using super
as shown. Compiling and executing Subclass
prints the following:
Subclass Constructors
A subclass constructor can invoke the superclass constructor. Invocation of a superclass constructor must be the first line in the subclass constructor.
The syntax for calling a superclass constructor is super()
(which invokes the no-argument constructor of the superclass) or super(parameter list)
(to invoke the superclass constructor with a matching parameter list).
The following example illustrates how to use the super
keyword to invoke a superclass's constructor. Recall from the Bicycle
example that MountainBike
is a subclass of Bicycle
. Here is the MountainBike
(subclass) constructor that calls the superclass constructor and then adds initialization code of its own:
public MountainBike(int startHeight, int startSpeed, int startGear) {
super(startSpeed, startGear);
seatHeight = startHeight;
}
Note: If a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the no-argument constructor of the superclass. If the superclass does not have a no-argument constructor, you will get a compile-time error. Object
does have such a constructor, so if Object
is the only superclass, there is no problem.
Access Modifiers (simplified)
Access level modifiers determine whether other classes can use a particular field or invoke a particular method. Given below is a simplified version of Java access modifiers, assuming you have not yet started placing your classes in different packages i.e., all classes are places in the root level. A full explanation of access modifiers is given in a later topic.
There are two levels of access control:
-
At the class level:
public
: the class is visible to all other classes- no modifier: same as
public
-
At the member level:
public
: the class is visible to all other classes- no modifier: same as
public
protected
: same aspublic
private
: the member can only be accessed in its own class
Background: Suppose we are creating a software to manage various tasks a person has to do. Two types of such tasks are,
- Todos: i.e., things that needs to be done some day e.g., 'Read the book Lord of the Rings'
- Deadlines: i.e., things to be done by a specific date/time e.g., 'Read the text book by Nov 25th'
The Task
class is given below:
public class Task {
protected String description;
public Task(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
- Write a
Todo
class that inherits from theTask
class.- It should have an additional
boolean
fieldisDone
to indicate whether the todo is done or not done. - It should have a
isDone()
method to access theisDone
field and asetDone(boolean)
method to set theisDone
field.
- It should have an additional
- Write a
Deadline
class that inherits from theTodo
class that you implemented in the previous step. It should have,- an additional
String
fieldby
to store the details of when the task to be done e.g.,Jan 25th 5pm
- a
getBy()
method to access the value of theby
field, and a correspondingsetBy(String)
method. - a constructor of the form
Deadline(String description, String by)
- an additional
The expected behavior of the two classes is as follows:
public class Main {
public static void main(String[] args) {
// create a todo task and print details
Todo t = new Todo("Read a good book");
System.out.println(t.getDescription());
System.out.println(t.isDone());
// change todo fields and print again
t.setDone(true);
System.out.println(t.isDone());
// create a deadline task and print details
Deadline d = new Deadline("Read textbook", "Nov 16");
System.out.println(d.getDescription());
System.out.println(d.isDone());
System.out.println(d.getBy());
// change deadline details and print again
d.setDone(true);
d.setBy("Postponed to Nov 18th");
System.out.println(d.isDone());
System.out.println(d.getBy());
}
}
Read a good book
false
true
Read textbook
false
Nov 16
true
Postponed to Nov 18th
Todo
class is given below. You can follow a similar approach for the Deadline
class.
public class Todo extends Task {
protected boolean isDone;
public Todo(String description) {
super(description);
isDone = false;
}
}
[W3.2] IDEs: Basic refactoring
Can explain refactoring
The first version of the code you write may not be of production quality. It is OK to first concentrate on making the code work, rather than worry over the quality of the code, as long as you improve the quality later. This process of improving a program's internal structure in small steps without modifying its external behavior is called refactoring.
- Refactoring is not rewriting: Discarding poorly-written code entirely and re-writing it from scratch is not refactoring because refactoring needs to be done in small steps.
- Refactoring is not bug fixing: By definition, refactoring is different from bug fixing or any other modifications that alter the external behavior (e.g. adding a feature) of the component in concern.
💡 Improving code structure can have many secondary benefits: e.g.
- hidden bugs become easier to spot
- improve performance (sometimes, simpler code runs faster than complex code because simpler code is easier for the compiler to optimize).
Given below are two common refactorings (
- Java: http://refactoring.com/catalog/ - This is a list of common refactorings, maintained by Martin Fowler, a leading authority on refactoring. He is also the author of the ‘bestseller’ on refactoring: Refactoring: Improving the Design of Existing Code
- Python: https://refactoring.guru/refactoring/catalog -- A catalog of refactorings applicable to Python code.
Refactoring Name: Consolidate Duplicate Conditional Fragments
Situation: The same fragment of code is in all branches of a conditional expression.
Method: Move it outside of the expression.
Example:
|
→ |
|
|
→ |
|
Refactoring Name: Extract Method
Situation: You have a code fragment that can be grouped together.
Method: Turn the fragment into a method whose name explains the purpose of the method.
Example:
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount " + getOutstanding());
}
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails (double outstanding) {
System.out.println("name: " + name);
System.out.println("amount " + outstanding);
}
def print_owing():
print_banner()
//print details
print("name: " + name)
print("amount " + get_outstanding())
def print_owing():
print_banner()
print_details(get_outstanding())
def print_details(amount):
print("name: " + name)
print("amount " + amount)
💡 Some IDEs have built in support for basic refactorings such as automatically renaming a variable/method/class in all places it has been used.
Refactoring, even if done with the aid of an IDE, may still result in regressions. Therefore, each small refactoring should be followed by regression testing.
Choose the correct statements
- a. Refactoring can improve understandability
- b. Refactoring can uncover bugs
- c. Refactoring can result in better performance
- d. Refactoring can change the number of methods/classes
a, b, c, d
Explanation:
- (a, b, c) Although the primary aim of refactoring is to improve internal code structure, there are other secondary benefits.
- (d) Some refactorings result in adding/removing methods/classes.
Do you agree with the following statement? Justify your answer.
Statement: Whenever we refactor code to fix bugs, we need not do regression testing if the bug fix was minor.
There are two flaws in the given statement.
DISAGREE.
- Even a minor change can have major repercussions on the system. We MUST do regression testing after each change, no matter how minor it is.
- Fixing bugs is technically not refactoring.
Explain what is refactoring and why it is not the same as rewriting, bug fixing, or adding features.
Can use automated refactoring features of the IDE
This video explains how to automate the 'Extract parameter' refactoring using Intellij IDEA. Most other refactorings available works similarly. i.e. select the code to refactor
→ find the refactoring in the context menu
or use the keyboard shortcut
.
Here's another video explaining how to change a method signature as part of refactoring.
- Introduction to Refactoring (in Intellij IDEA) : An article on refactorings available in Intellij IDEA.
Can apply some basic refactoring
Given below are some more commonly used refactorings. A more comprehensive list is available at
- Java: http://refactoring.com/catalog/ - This is a list of common refactorings, maintained by Martin Fowler, a leading authority on refactoring. He is also the author of the ‘bestseller’ on refactoring: Refactoring: Improving the Design of Existing Code
- Python: https://refactoring.guru/refactoring/catalog -- A catalog of refactorings applicable to Python code.
Can decide when to apply a given refactoring
We know that it is important to refactor frequently so as to avoid the accumulation of ‘messy’ code which might get out of control. But how much refactoring is too much refactoring? It is too much refactoring when the benefits no longer justify the cost. The costs and the benefits depend on the context. That is why some refactorings are ‘opposites’ of each other (e.g. extract method vs inline method).
‘Extract method’ and ‘Inline method’ refactorings
a
[W3.3] C++ to Java
Can explain what Java is
Java was conceived by James Gosling and his team at Sun Microsystems in 1991.
Java is directly related to both C and C++. Java inherits its syntax from C. Its object model is adapted from C++. --Java: A Beginner’s Guide, by Oracle
Fun fact: The language was initially called Oak after an oak tree that stood outside Gosling's office. Later the project went by the name Green and was finally renamed Java, from Java coffee. --Wikipedia
Oracle became the owner of Java in 2010, when it acquired Sun Microsystems.
Java has remained the most popular language in the world for several years now (as at July 2018), according to the TIOBE index.
Can explain how Java works at a higher-level
Java is both
Java technology is both a programming language and a platform. The Java programming language is a high-level object-oriented language that has a particular syntax and style. A Java platform is a particular environment in which Java programming language applications run. --Oracle
Can explain Java editions
According to the Official Java documentation, there are four platforms of the Java programming language:
-
Java Platform, Standard Edition (Java SE): Contains the core functionality of the Java programming language.
-
Java Platform, Enterprise Edition (Java EE): For developing and running large-scale enterprise applications. Built on top of Java SE.
-
Java Platform, Micro Edition (Java ME): For Java programming language applications meant for small devices, like mobile phones. A subset of Java SE.
-
JavaFX: For creating applications with graphical user interfaces. Can work with the other three above.
This book chapter uses the Java SE edition unless stated otherwise.
[W3.4] Java classes
Classes
Can define Java classes
As you know,
- Defining a class creates a new object type with the same name.
- Every object belongs to some object type; that is, it is an instance of some class.
- A class definition is like a template for objects: it specifies what attributes the objects have and what methods can operate on them.
- The
new
operator instantiates objects, that is, it creates new instances of a class. - The methods that operate on an object type are defined in the class for that object.
Here's a class called Time
, intended to represent a moment in time. It has three attributes and no methods.
public class Time {
private int hour;
private int minute;
private int second;
}
You can give a class any name you like. The Java convention is to use
The code should be in a file whose name matches the class e.g., the Time
class should be in a file named Time.java
.
When a class is public
(e.g., the Time
class in the above example) it can be used in other classes. But the private
(e.g., the hour, minute
and second
attributes of the Time
class) can only be accessed from inside the Time
class.
Constructos
The syntax for
- The name of the constructor is the same as the name of the class.
- The keyword
static
is omitted. - Do not return anything. A constructor returns the created object by default.
When you invoke new
, Java creates the object and calls your constructor to initialize the instance variables. When the constructor is done, new returns a reference to the new object.
Here is an example constructor for the Time
class:
public Time() {
hour = 0;
minute = 0;
second = 0;
}
This constructor does not take any arguments. Each line initializes an instance variable to zero (which in this example means midnight).
Now you can create Time
objects.
Time time = new Time();
Like other methods, constructors can be overloaded, which means you can provide multiple constructors with different parameters.
You can add another constructor to the Time
class to allow creating Time
objects that are initialized to a specific time:
public Time(int h, int m, int s) {
hour = h;
minute = m;
second = s;
}
Here's how you can invoke the new constructor:
Time justBeforeMidnight = new Time(11, 59, 59);
this
keyword
The this
keyword is a reference variable in Java that refers to the current object. You can use this
the same way you use the name of any other object. For example, you can read and write the instance variables of this
, and you can pass this
as an argument to other methods. But you do not declare this
, and you can’t make an assignment to it.
In the following version of the constructor, the names and types of the parameters are the same as the instance variables (parameters don’t have to use the same names, but that’s a common style). As a result, the parameters shadow (or hide) the instance variables, so the keyword this
is necessary to tell them apart.
public Time(int hour, int minute, int second) {
this.hour = hour;
this.minute = minute;
this.second = second;
}
this
can be used to refer to a constructor of a class within the same class too.
In this example the constructor Time()
uses the this
keyword to call its own overloaded constructor Time(int, int, int)
public Time() {
this(0, 0, 0); // call the overloaded constructor
}
public Time(int hour, int minute, int second) {
// ...
}
Instance methods
You can add methods to a class which can then be used from the objects of that class. These instance methods do not have the static
keyword in the method signature. Instance methods can access attributes of the class.
Here's how you can add a method to the Time
class to get the number of seconds passed till midnight.
public int secondsSinceMidnight() {
return hour*60*60 + minute*60 + second;
}
Here's how you can use that method.
Time t = new Time(0, 2, 5);
System.out.println(t.secondsSinceMidnight() + " seconds since midnight!");
Define a Circle
class so that the code given below produces the given output. The nature of the class is a follows:
- Attributes(all
private
):int x
,int y
: represents the location of the circledouble radius
: the radius of the circle
- Constructors:
Circle()
: initializesx
,y
,radius
to 0Circle(int x, int y, double radius)
: initializes the attributes to the given values
- Methods:
getArea()
:int
Returns the area of the circle as anint
value (notdouble
). Calculated as 2xPIx(radius)2
💡 You can convert todouble
to anint
using(int)
e.g.,x = (int)2.25
givesx
the value2
.
💡 You can useMath.PI
to get the value of Pi
💡 You can useMath.pow()
to raise a number to a specific power e.g.,Math.pow(3, 2)
calculates 32
public class Main {
public static void main(String[] args) {
Circle c = new Circle();
System.out.println(c.getArea());
c = new Circle(1, 2, 5);
System.out.println(c.getArea());
}
}
0
78
- Put the
Circle
class in a file calledCircle.java
Partial solution:
public class Circle {
private int x;
// ...
public Circle(){
this(0, 0, 0);
}
public Circle(int x, int y, double radius){
this.x = x;
// ...
}
public int getArea(){
double area = Math.PI * Math.pow(radius, 2);
return (int)area;
}
}
Can define getters and setters
As the instance variables of Time
are private, you can access them from within the Time
class only. To compensate, you can provide methods to access attributes:
public int getHour() {
return hour;
}
public int getMinute() {
return minute;
}
public int getSecond() {
return second;
}
Methods like these are formally called “accessors”, but more commonly referred to as getters. By convention, the method that gets a variable named something is called getSomething
.
Similarly, you can provide setter methods to modify attributes of a Time
object:
public void setHour(int hour) {
this.hour = hour;
}
public void setMinute(int minute) {
this.minute = minute;
}
public void setSecond(int second) {
this.second = second;
}
Consider the Circle
class below:
public class Circle {
private int x;
private int y;
private double radius;
public Circle(){
this(0, 0, 0);
}
public Circle(int x, int y, double radius){
this.x = x;
this.y = y;
this.radius = radius;
}
public int getArea(){
double area = Math.PI * Math.pow(radius, 2);
return (int)area;
}
}
Update it as follows so that code given below produces the given output.
- Add getter/setter methods for all three attributes
- Update the setters and constructors such that if the radius supplied is negative, the code automatically set the radius to 0 instead.
public class Main {
public static void main(String[] args) {
Circle c = new Circle(1,2, 5);
c.setX(4);
c.setY(5);
c.setRadius(6);
System.out.println("x : " + c.getX());
System.out.println("y : " + c.getY());
System.out.println("radius : " + c.getRadius());
System.out.println("area : " + c.getArea());
c.setRadius(-5);
System.out.println("radius : " + c.getRadius());
c = new Circle(1, 1, -4);
System.out.println("radius : " + c.getRadius());
}
}
x : 4
y : 5
radius : 6.0
area : 113
radius : 0.0
radius : 0.0
Partial solution:
public Circle(int x, int y, double radius){
setX(x);
setY(y);
setRadius(radius);
}
public void setRadius(double radius) {
this.radius = Math.max(radius, 0);
}
Class-level members
Can explain class-level members
While all objects of a class has the same attributes, each object has its own copy of the attribute value.
All Person
objects have the Name
attribute but the value of that attribute varies between Person
objects.
However, some attributes are not suitable to be maintained by individual objects. Instead, they should be maintained centrally, shared by all objects of the class. They are like ‘global variables’ but attached to a specific class. Such variables whose value is shared by all instances of a class are called class-level attributes.
The attribute totalPersons
should be maintained centrally and shared by all Person
objects rather than copied at each Person
object.
Similarly, when a normal method is being called, a message is being sent to the receiving object and the result may depend on the receiving object.
Sending the getName()
message to Adam
object results in the response "Adam"
while sending the same message to the Beth
object gets the response "Beth"
.
However, there can be methods related to a specific class but not suitable for sending message to a specific object of that class. Such methods that are called using the class instead of a specific instance are called class-level methods.
The method getTotalPersons()
is not suitable to send to a specific Person
object because a specific object of the Person
class should not know about the total number of Person
objects.
Class-level attributes and methods are collectively called class-level members (also called static members sometimes because some programming languages use the keyword static
to identify class-level members). They are to be accessed using the class name rather than an instance of the class.
Which of these are suitable as class-level variables?
- a. system: multi-player Pac Man game, Class:
Player
, variable:totalScore
- b. system: eLearning system, class:
Course
, variable:totalStudents
- c. system: ToDo manager, class:
Task
, variable:totalPendingTasks
- d. system: any, class:
ArrayList
, variable:total
(i.e., total items in a givenArrayList
object)
(c)
Explanation: totalPendingTasks
should not be managed by individual Task
objects and therefore suitable to be maintained as a class-level variable. The other variables should be managed at instance level as their value varies from instance to instance. e.g., totalStudents
for one Course
object will differ from totalStudents
of another.
Can use class-level members
The content below is an extract from -- Java Tutorial, with slight adaptations.
When a number of objects are created from the same class blueprint, they each have their own distinct copies of instance variables. In the case of a Bicycle
class, the instance variables are gear, and speed. Each Bicycle object has its own values for these variables, stored in different memory locations.
Sometimes, you want to have variables that are common to all objects. This is accomplished with the static
modifier. Fields that have the static
modifier in their declaration are called static fields or class variables. They are associated with the class, rather than with any object. Every instance of the class shares a class variable, which is in one fixed location in memory. Any object can change the value of a class variable, but class variables can also be manipulated without creating an instance of the class.
Suppose you want to create a number of Bicycle objects and assign each a serial number, beginning with 1 for the first object. This ID number is unique to each object and is therefore an instance variable. At the same time, you need a field to keep track of how many Bicycle
objects have been created so that you know what ID to assign to the next one. Such a field is not related to any individual object, but to the class as a whole. For this you need a class variable, numberOfBicycles, as follows:
public class Bicycle {
private int gear;
private int speed;
// an instance variable for the object ID
private int id;
// a class variable for the number of Bicycle objects instantiated
private static int numberOfBicycles = 0;
...
}
Class variables are referenced by the class name itself, as in Bicycle.numberOfBicycles
This makes it clear that they are class variables.
The Java programming language supports static methods as well as static variables. Static methods, which have the static
modifier in their declarations, should be invoked with the class name, without the need for creating an instance of the class, as in ClassName.methodName(args)
The static
modifier, in combination with the final
modifier, is also used to define constants. The final modifier indicates that the value of this field cannot change.For example, the following variable declaration defines a constant named PI, whose value is an approximation of pi (the ratio of the circumference of a circle to its diameter):
static final double PI = 3.141592653589793;
Here is an example with class-level variables and class-level methods:
public class Bicycle {
private int gear;
private int speed;
private int id;
private static int numberOfBicycles = 0;
public Bicycle(int startSpeed, int startGear) {
gear = startGear;
speed = startSpeed;
numberOfBicycles++;
id = numberOfBicycles;
}
public int getID() {
return id;
}
public static int getNumberOfBicycles() {
return numberOfBicycles;
}
public int getGear(){
return gear;
}
public void setGear(int newValue) {
gear = newValue;
}
public int getSpeed() {
return speed;
}
// ...
}
💡 Explanation of System.out.println(...)
:
out
is a class-level public attribute of theSystem
class.println
is a instance level method of theout
object.
Consider the Circle
class below:
public class Circle {
private int x;
private int y;
private double radius;
public Circle(){
this(0, 0, 0);
}
public Circle(int x, int y, double radius){
setX(x);
setY(y);
setRadius(radius);
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = Math.max(radius, 0);
}
public int getArea(){
double area = Math.PI * Math.pow(radius, 2);
return (int)area;
}
}
Update it as follows so that code given below produces the given output.
- Add a class-level
getMaxRadius
method that returns the maximum radius that has been used in allCircle
objects created thus far.
public class Main {
public static void main(String[] args) {
Circle c = new Circle();
System.out.println("max radius used so far : " + Circle.getMaxRadius());
c = new Circle(0, 0, 10);
System.out.println("max radius used so far : " + Circle.getMaxRadius());
c = new Circle(0, 0, -15);
System.out.println("max radius used so far : " + Circle.getMaxRadius());
c.setRadius(12);
System.out.println("max radius used so far : " + Circle.getMaxRadius());
}
}
max radius used so far : 0.0
max radius used so far : 10.0
max radius used so far : 10.0
max radius used so far : 12.0
You can use a static
variable maxRadius
to track the maximum value used for the radius
attribute so far.
Partial solution:
public void setRadius(double radius) {
this.radius = Math.max(radius, 0);
if (maxRadius < this.radius){
// ...
}
}
[W3.5] Java varargs
Can use Java varargs feature
Project Preparation: 1 KLoC
This task can earn you
To receive full 10 marks allocated for participation, earn at least 15 participation points.
There are 30+ available points to choose from:
-
Good peer ratings - Criteria for professional conduct (1 point for each criterion, max 7)
- Competency criteria (2 points for each, max 6)
Only those who submit peer evaluations can earn participation points from peer evaluations they receive.
- In-lecture quizzes (1 each, max 10 points)
- Enhanced AB1-AB3 (2 points for AB-1, 3 points each for AB-2 and AB-3)
- Module admin tasks done on time and as instructed
- Peer evaluations (1 points each)
- Pre-module survey (1 points)
If your response in the pre-module survey is not usable, you will not earn points for this exercise.
Relevant: [
Peer evaluation criteria: professional conduct
- Professional Communication :
- Communicates sufficiently and professionally. e.g. Does not use offensive language or excessive slang in project communications.
- Responds to communication from team members in a timely manner (e.g. within 24 hours).
- Punctuality: Does not cause others to waste time or slow down project progress by frequent tardiness.
- Dependability: Promises what can be done, and delivers what was promised.
- Effort: Puts in sufficient effort to, and tries their best to keep up with the module/project pace. Seeks help from others when necessary.
- Quality: Does not deliver work products that seem to be below the student's competence level i.e. tries their best to make the work product as high quality as possible within her competency level.
- Meticulousness:
- Rarely overlooks submission requirements.
- Rarely misses compulsory module activities such as pre-module survey.
- Teamwork: How willing are you to act as part of a team, contribute to team-level tasks, adhere to team decisions, etc.
Peer evaluation criteria: competency
- Technical Competency: Able to gain competency in all the required tools and techniques.
- Mentoring skills: Helps others when possible. Able to mentor others well.
- Communication skills: Able to communicate (written and spoken) well. Takes initiative in discussions.
Evidence:
Do an enhancement to [AddressBook - Level1] e.g. add a new command
- The size of the enhancement does not matter.
- Step 1: Fork address AddressBook - Level1 to your GitHub account.
- Step 2: Change the code in small steps and commit after each significant change. You may commit to the
master
branch.- Try to stay within the procedural (not OOP) style of the code base. Reason: in this LO, we try to stretch ourselves to the limits of the procedural approach.
- Update all relevant tests. Ensure all tests pass.
- [Optional] Update all relevant documentation.
- [Optional] Try to follow our coding standard in your new code.
- Step 3: push the updated AB1 code to your fork
💡 Note that you can reuse the code you write here in your final project, if applicable.
Submission: No special submission required. Our scripts will check your fork automatically.
Project Milestone: Inception
Decide on a overall project direction (user profile, problem addressed, societal impact, optimize or morph?).
It is not too early to set an overall direction for your project.
-
Set up a weekly project meeting time/venue with your team members
We recommend at least one face-to-face project meeting per week. The project meeting time can be used to discuss project related things, but also, can be used as a time for team members to work on the project tasks individually (having all members in the same place will facilitate easier collaboration and more peer-learning).
-
Play around with AB4
Download the latest released version (i.e., the jar file) of AB4 from its upstream repo and play around with it to familiarize with its current features.
-
Decide project direction, target user profile, and problem addressed
Use your first project meeting to discuss with your team members and decide your project direction, target user profile, and the value proposition of the product, as described in
[Admin Project Scope]
In general, each team is expected to take one of these two directions:
-
[Direction 1] Optimize AddressBook for a more specific target user group:
An AddressBook,
- for users in a specific profession e.g. doctors, salesmen, teachers, etc.
- based on the nature/scale of contacts e.g. huge number of contacts (for HR admins, user group admins), mostly incomplete contacts, highly volatile contact details, contacts become inactive after a specific period (e.g. contract employees)
- based on what users do with the contacts e.g. organize group events, share info, do business, do analytics
-
[Direction 2] Morph AddressBook into a different product: Given that AddressBook is a generic app that manages a type of elements (i.e. contacts), you can use it as a starting point to create an app that manages something else.
This is a high-risk high-reward option because morphing requires extra work but a morphed product may earn more marks than an optimized product of similar complexity.An app to manage,
- Bookmarks of websites
- Tasks/Schedule
- Location info
- Thing to memorize i.e. flash cards, trivia
- Forum posts, news feeds, Social media feeds
- Online projects or issue trackers that the user is interested in
- Emails, possibly from different accounts
- Multiple types of related things e.g. Contacts and Tasks (if Tasks are allocated to Contacts)
For either direction, you need to define a target user profile and a value proposition:
-
Target user profile: Define a very specific target user profile.
💡 We require you to narrow down the target user profile as opposed to trying to make it as general as possible. Here is an example direction of narrowing down target user: anybody → teachers → university teachers → tech savvy university teachers → CS2113/T instructors.
Be careful not to contradict given project constraints when defining the user profile e.g. the target user should still prefer typing over mouse actions.
It is expected that your product will be optimized for the chosen target users i.e., add features that are especially/only applicable for target users (to make the app especially attractive to them). w.r.t. the example above, there can be features that are applicable to CS2113/T instructors only, such as the ability to navigate to a student's project on GitHub
💡 Your project will be graded based on how well the features match the target user profile and how well the features fit-together.- It is an opportunity to exercise your product design skills because optimizing the product to a very specific target user requires good product design skills.
- It minimizes the overlap between features of different teams which can cause plagiarism issues. Furthermore, higher the number of other teams having the same features, less impressive your work becomes especially if others have done a better job of implementing that feature.
- Value proposition: Define a clear value proposition (what problem does the product solve? how does it make the the user's life easier?) that matches the target user profile.
Simple Java exercises
We will use repl.it platform for some simple Java coding exercises.
The link to our classroom will be available under Links in the navigation bar.
Note: you need to create an account on repl.it to be able to use the platform
Team forming
CS2113 students only: Form teams at the beginning of the tutorial. Be sure to conform to team forming constrains.
All students:
- Confirm your team ID with the tutor. It should be of the form
TUTORIAL_ID-TEAM_NUMBER
e.g.M11-1
(M
on11
00 slot, team1
)
Tutorial activity
-
In Addressbook Level 1, apply the following refactorings, where applicable. Use the IDE to aid refactoring.
- Consolidate Conditional Expression
- Decompose Conditional
- Inline Method
- Remove Double Negative
- Replace Magic Number with Symbolic Constant
- Replace Nested Conditional with Guard Clauses
- Replace Parameter with Explicit Methods
- Reverse Conditional
- Split Loop
- Split Temporary Variable
-
Show the evidence of understanding the usefulness of varargs. (Relevant excercise in AB-L1)
-
Draw the class diagram for the exercise: Circle class
Note that project grading is not competitive (not bell curved). CS2113T projects will be assessed separately from CS2113 projects. Given below is the marking scheme.
Total: 50 marks ( 40 individual marks + 10 team marks)
Evaluates: How well do your features fit together to form a cohesive product (not how many features or how big the features are)?
Based on: user guide and the product demo. The quality of the demo will be factored in as well.
💡 Feature that fit well with the other features will earn more marks.
Evaluates:
A. Code quality/quantity:
How good your implementation is, in terms of the quality and the quantity of the code you have written yourself.
Based on: an inspection of the parts of the code you claim as written by you.
-
Ensure your code has at least some evidence of these (see here for more info)
- logging
- exceptions
- assertions
- defensive coding
-
Ensure there are no coding standard violations e.g. all boolean variables/methods sounds like booleans. Checkstyle can prevent only some coding standard violations; others need to be checked manually.
-
Ensure SLAP is applied at a reasonable level. Long methods or deeply-nested code are symptoms of low-SLAP may be counted against your code quality.
-
Reduce code duplications i.e. if there multiple blocks of code that vary only in minor ways, try to extract out similarities into one place, especially in test code.
-
In addition, try to apply as many of the
code quality guidelines covered in the module as much as you can.
Code Quality
Can explain the importance of code quality
Always code as if the person who ends up maintaining your code will be a violent psychopath who knows where you live. -- Martin Golding
Can explain the importance of readability
Programs should be written and polished until they acquire publication quality. --Niklaus Wirth
Among various dimensions of code quality, such as run-time efficiency, security, and robustness, one of the most important is understandability. This is because in any non-trivial software project, code needs to be read, understood, and modified by other developers later on. Even if we do not intend to pass the code to someone else, code quality is still important because we all become 'strangers' to our own code someday.
The two code samples given below achieve the same functionality, but one is easier to read.
Bad
|
|
Good
|
Bad
|
|
Good
|
Can improve code quality using technique: avoid long methods
Be wary when a method is longer than the computer screen, and take corrective action when it goes beyond 30 LOC (lines of code). The bigger the haystack, the harder it is to find a needle.
Can improve code quality using technique: avoid deep nesting
If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. --Linux 1.3.53 CodingStyle
In particular, avoid arrowhead style code.
Example:
Can improve code quality using technique: avoid complicated expressions
Avoid complicated expressions, especially those having many negations and nested parentheses. If you must evaluate complicated expressions, have it done in steps (i.e. calculate some intermediate values first and use them to calculate the final value).
Example:
Bad
return ((length < MAX_LENGTH) || (previousSize != length)) && (typeCode == URGENT);
Good
boolean isWithinSizeLimit = length < MAX_LENGTH;
boolean isSameSize = previousSize != length;
boolean isValidCode = isWithinSizeLimit || isSameSize;
boolean isUrgent = typeCode == URGENT;
return isValidCode && isUrgent;
Example:
Bad
return ((length < MAX_LENGTH) or (previous_size != length)) and (type_code == URGENT)
Good
is_within_size_limit = length < MAX_LENGTH
is_same_size = previous_size != length
is_valid_code = is_within_size_limit or is_same_size
is_urgent = type_code == URGENT
return is_valid_code and is_urgent
The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague. -- Edsger Dijkstra
Can improve code quality using technique: avoid magic numbers
When the code has a number that does not explain the meaning of the number, we call that a magic number (as in “the number appears as if by magic”). Using a
Example:
Bad
|
|
Good
|
Note: Python does not have a way to make a variable a constant. However, you can use a normal variable with an ALL_CAPS
name to simulate a constant.
Bad
|
|
Good
|
Similarly, we can have ‘magic’ values of other data types.
Bad
"Error 1432" // A magic string!
Can improve code quality using technique: make the code obvious
Make the code as explicit as possible, even if the language syntax allows them to be implicit. Here are some examples:
- [
Java
] Use explicit type conversion instead of implicit type conversion. - [
Java
,Python
] Use parentheses/braces to show grouping even when they can be skipped. - [
Java
,Python
] Useenumerations when a certain variable can take only a small number of finite values. For example, instead of declaring the variable 'state' as an integer and using values 0,1,2 to denote the states 'starting', 'enabled', and 'disabled' respectively, declare 'state' as typeSystemState
and define an enumerationSystemState
that has values'STARTING'
,'ENABLED'
, and'DISABLED'
.
Can improve code quality using technique: structure code logically
Lay out the code so that it adheres to the logical structure. The code should read like a story. Just like we use section breaks, chapters and paragraphs to organize a story, use classes, methods, indentation and line spacing in your code to group related segments of the code. For example, you can use blank lines to group related statements together. Sometimes, the correctness of your code does not depend on the order in which you perform certain intermediary steps. Nevertheless, this order may affect the clarity of the story you are trying to tell. Choose the order that makes the story most readable.
Can improve code quality using technique: do not 'trip up' reader
Avoid things that would make the reader go ‘huh?’, such as,
- unused parameters in the method signature
- similar things look different
- different things that look similar
- multiple statements in the same line
- data flow anomalies such as, pre-assigning values to variables and modifying it without any use of the pre-assigned value
Can improve code quality using technique: practice kissing
As the old adage goes, "keep it simple, stupid” (KISS). Do not try to write ‘clever’ code. For example, do not dismiss the brute-force yet simple solution in favor of a complicated one because of some ‘supposed benefits’ such as 'better reusability' unless you have a strong justification.
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. --Brian W. Kernighan
Programs must be written for people to read, and only incidentally for machines to execute. --Abelson and Sussman
Can improve code quality using technique: avoid premature optimizations
Optimizing code prematurely has several drawbacks:
- We may not know which parts are the real performance bottlenecks. This is especially the case when the code undergoes transformations (e.g. compiling, minifying, transpiling, etc.) before it becomes an executable. Ideally, you should use a profiler tool to identify the actual bottlenecks of the code first, and optimize only those parts.
- Optimizing can complicate the code, affecting correctness and understandability
- Hand-optimized code can be harder for the compiler to optimize (the simpler the code, the easier for the compiler to optimize it). In many cases a compiler can do a better job of optimizing the runtime code if you don't get in the way by trying to hand-optimize the source code.
A popular saying in the industry is make it work, make it right, make it fast which means in most cases getting the code to perform correctly should take priority over optimizing it. If the code doesn't work correctly, it has no value on matter how fast/efficient it it.
Premature optimization is the root of all evil in programming. --Donald Knuth
Note that there are cases where optimizing takes priority over other things e.g. when writing code for resource-constrained environments. This guideline simply a caution that you should optimize only when it is really needed.
Can improve code quality using technique: SLAP hard
Avoid varying the level of
Example:
Bad
readData();
salary = basic*rise+1000;
tax = (taxable?salary*0.07:0);
displayResult();
Good
readData();
processData();
displayResult();
Design → Design Fundamentals → Abstraction →
Abstraction is a technique for dealing with complexity. It works by establishing a level of complexity we are interested in, and suppressing the more complex details below that level.
The guiding principle of abstraction is that only details that are relevant to the current perspective or the task at hand needs to be considered. As most programs are written to solve complex problems involving large amounts of intricate details, it is impossible to deal with all these details at the same time. That is where abstraction can help.
Ignoring lower level data items and thinking in terms of bigger entities is called data abstraction.
Within a certain software component, we might deal with a user data type, while ignoring the details contained in the user data item such as name, and date of birth. These details have been ‘abstracted away’ as they do not affect the task of that software component.
Control abstraction abstracts away details of the actual control flow to focus on tasks at a simplified level.
print(“Hello”)
is an abstraction of the actual output mechanism within the computer.
Abstraction can be applied repeatedly to obtain progressively higher levels of abstractions.
An example of different levels of data abstraction: a File
is a data item that is at a higher level than an array and an array is at a higher level than a bit.
An example of different levels of control abstraction: execute(Game)
is at a higher level than print(Char)
which is at a higher than an Assembly language instruction MOV
.
Abstraction is a general concept that is not limited to just data or control abstractions.
Some more general examples of abstraction:
- An OOP class is an abstraction over related data and behaviors.
- An architecture is a higher-level abstraction of the design of a software.
- Models (e.g., UML models) are abstractions of some aspect of reality.
Can improve code quality using technique: make the happy path prominent
The happy path (i.e. the execution path taken when everything goes well) should be clear and prominent in your code. Restructure the code to make the happy path unindented as much as possible. It is the ‘unusual’ cases that should be indented. Someone reading the code should not get distracted by alternative paths taken when error conditions happen. One technique that could help in this regard is the use of guard clauses.
Example:
Bad
if (!isUnusualCase) { //detecting an unusual condition
if (!isErrorCase) {
start(); //main path
process();
cleanup();
exit();
} else {
handleError();
}
} else {
handleUnusualCase(); //handling that unusual condition
}
In the code above,
- Unusual condition detection is separated from their handling.
- Main path is nested deeply.
Good
if (isUnusualCase) { //Guard Clause
handleUnusualCase();
return;
}
if (isErrorCase) { //Guard Clause
handleError();
return;
}
start();
process();
cleanup();
exit();
In contrast, the above code
- deals with unusual conditions as soon as they are detected so that the reader doesn't have to remember them for long.
- keeps the main path un-indented.
Can explain the need for following a standard
One essential way to improve code quality is to follow a consistent style. That is why software engineers follow a strict coding standard (aka style guide).
The aim of a coding standard is to make the entire code base look like it was written by one person. A coding standard is usually specific to a programming language and specifies guidelines such as the location of opening and closing braces, indentation styles and naming styles (e.g. whether to use Hungarian style, Pascal casing, Camel casing, etc.). It is important that the whole team/company use the same coding standard and that standard is not generally inconsistent with typical industry practices. If a company's coding standards is very different from what is used typically in the industry, new recruits will take longer to get used to the company's coding style.
💡 IDEs can help to enforce some parts of a coding standard e.g. indentation rules.
What is the recommended approach regarding coding standards?
c
What is the aim of using a coding standard? How does it help?
Can follow simple mechanical style rules
Learn basic guidelines of the Java coding standard (by OSS-Generic)
Sample coding standard: PEP 8 Python Style Guide -- by Python.org
Consider the code given below:
import java.util.*;
public class Task {
public static final String descriptionPrefix = "description: ";
private String description;
private boolean important;
List<String> pastDescription = new ArrayList<>(); // a list of past descriptions
public Task(String d) {
this.description = d;
if (!d.isEmpty())
this.important = true;
}
public String getAsXML() { return "<task>"+description+"</task>"; }
/**
* Print the description as a string.
*/
public void printingDescription(){ System.out.println(this); }
@Override
public String toString() { return descriptionPrefix + description; }
}
In what ways the code violate the basic guidelines (i.e., those marked with one ⭐️) of the OSS-Generic Java Coding Standard given here?
Here are three:
descriptionPrefix
is a constant and should be namedDESCRIPTION_PREFIX
- method name
printingDescription()
should be named asprintDescription()
- boolean variable
important
should be named to sound boolean e.g.,isImportant
There are many more.
Can follow intermediate style rules
Go through the provided Java coding standard and learn the intermediate style rules.
According to the given Java coding standard, which one of these is not a good name?
b
Explanation: checkWeight
is an action. Naming variables as actions makes the code harder to follow. isWeightValid
may be a better name.
Repeat the exercise in the panel below but also find violations of intermediate level guidelines.
Consider the code given below:
import java.util.*;
public class Task {
public static final String descriptionPrefix = "description: ";
private String description;
private boolean important;
List<String> pastDescription = new ArrayList<>(); // a list of past descriptions
public Task(String d) {
this.description = d;
if (!d.isEmpty())
this.important = true;
}
public String getAsXML() { return "<task>"+description+"</task>"; }
/**
* Print the description as a string.
*/
public void printingDescription(){ System.out.println(this); }
@Override
public String toString() { return descriptionPrefix + description; }
}
In what ways the code violate the basic guidelines (i.e., those marked with one ⭐️) of the OSS-Generic Java Coding Standard given here?
Here are three:
descriptionPrefix
is a constant and should be namedDESCRIPTION_PREFIX
- method name
printingDescription()
should be named asprintDescription()
- boolean variable
important
should be named to sound boolean e.g.,isImportant
There are many more.
Here's one you are more likely to miss:
* Print the description as a string.
→* Prints the description as a string.
There are more.
Can explain the need for good names in code
Proper naming improves the readability. It also reduces bugs caused by ambiguities regarding the intent of a variable or a method.
There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton
Can improve code quality using technique: use nouns for things and verbs for actions
Every system is built from a domain-specific language designed by the programmers to describe that system. Functions are the verbs of that language, and classes are the nouns. ― Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship
Use nouns for classes/variables and verbs for methods/functions.
Examples:
Name for a | Bad | Good |
---|---|---|
Class | CheckLimit |
LimitChecker |
method | result() |
calculate() |
Distinguish clearly between single-valued and multivalued variables.
Examples:
Good
Person student;
ArrayList<Person> students;
Good
student = Person('Jim')
students = [Person('Jim'), Person('Alice')]
Can improve code quality using technique: use standard words
Use correct spelling in names. Avoid 'texting-style' spelling. Avoid foreign language words, slang, and names that are only meaningful within specific contexts/times e.g. terms from private jokes, a TV show currently popular in your country
Can improve code quality using technique: use name to explain
A name is not just for differentiation; it should explain the named entity to the reader accurately and at a sufficient level of detail.
Examples:
Bad | Good |
---|---|
processInput() (what 'process'?) |
removeWhiteSpaceFromInput() |
flag |
isValidInput |
temp |
If the name has multiple words, they should be in a sensible order.
Examples:
Bad | Good |
---|---|
bySizeOrder() |
orderBySize() |
Imagine going to the doctor's and saying "My eye1 is swollen"! Don’t use numbers or case to distinguish names.
Examples:
Bad | Bad | Good |
---|---|---|
value1 , value2 |
value , Value |
originalValue , finalValue |
Can improve code quality using technique: not too long, not too short
While it is preferable not to have lengthy names, names that are 'too short' are even worse. If you must abbreviate or use acronyms, do it consistently. Explain their full meaning at an obvious location.
Can improve code quality using technique: avoid misleading names
Related things should be named similarly, while unrelated things should NOT.
Example: Consider these variables
colorBlack
: hex value for color blackcolorWhite
: hex value for color whitecolorBlue
: number of times blue is usedhexForRed
: : hex value for color red
This is misleading because colorBlue
is named similar to colorWhite
and colorBlack
but has a different purpose while hexForRed
is named differently but has very similar purpose to the first two variables. The following is better:
hexForBlack
hexForWhite
hexForRed
blueColorCount
Avoid misleading or ambiguous names (e.g. those with multiple meanings), similar sounding names, hard-to-pronounce ones (e.g. avoid ambiguities like "is that a lowercase L, capital I or number 1?", or "is that number 0 or letter O?"), almost similar names.
Examples:
Bad | Good | Reason |
---|---|---|
phase0 |
phaseZero |
Is that zero or letter O? |
rwrLgtDirn |
rowerLegitDirection |
Hard to pronounce |
right left wrong |
rightDirection leftDirection wrongResponse |
right is for 'correct' or 'opposite of 'left'? |
redBooks readBooks |
redColorBooks booksRead |
red and read (past tense) sounds the same |
FiletMignon |
egg |
If the requirement is just a name of a food, egg is a much easier to type/say choice than FiletMignon |
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. Some 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, throw an exception). 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
- Use one variable for one purpose. Do not reuse a variable for a different purpose other than its intended one, just because the data type is the same.
- Do not reuse formal parameters as local variables inside the method.
Bad
double computeRectangleArea(double length, double width) {
length = length * width;
return length;
}
Good
double computeRectangleArea(double length, double width) {
double area;
area = length * width;
return area;
}
Can improve code quality using technique: avoid empty catch blocks
Never write an empty catch
statement. At least give a comment to explain why the catch
block is left empty.
Can improve code quality using technique: delete dead code
We all feel reluctant to delete code we have painstakingly written, even if we have no use for that code any more ("I spent a lot of time writing that code; what if we 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: minimise 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: minimise 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
Supplmentary → Principles →
DRY (Don't Repeat Yourself) Principle: Every piece of knowledge must have a single, unambiguous, authoritative representation within a system The Pragmatic Programmer, by Andy Hunt and Dave Thomas
This principle guards against duplication of information.
The functionality implemented twice is a violation of the DRY principle even if the two implementations are different.
The value a system-wide timeout being defined in multiple places is a violation of DRY.
Can explain the need for commenting minimally but sufficiently
Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’ Improve the code and then document it to make it even clearer. --Steve McConnell, Author of Clean Code
Some think commenting heavily increases the 'code quality'. This is not so. Avoid writing comments to explain bad code. Improve the code to make it self-explanatory.
Can improve code quality using technique: do not repeat the obvious
If the code is self-explanatory, refrain from repeating the description in a comment just for the sake of 'good documentation'.
Bad
// increment x
x++;
//trim the input
trimInput();
Can improve code quality using technique: write to the reader
Do not write comments as if they are private notes to self. Instead, write them well enough to be understood by another programmer. One type of comments that is almost always useful is the header comment that you write for a class or an operation to explain its purpose.
Examples:
Bad Reason: this comment will only make sense to the person who wrote it
// a quick trim function used to fix bug I detected overnight
void trimInput(){
....
}
Good
/** Trims the input of leading and trailing spaces */
void trimInput(){
....
}
Bad Reason: this comment will only make sense to the person who wrote it
# a quick trim function used to fix bug I detected overnight
def trim_input():
...
Good
def trim_input():
"""Trim the input of leading and trailing spaces"""
...
Can improve code quality using technique: explain what and why, not how
Comments should explain what and why aspect of the code, rather than the how aspect.
What : The specification of what the code supposed to do. The reader can compare such comments to the implementation to verify if the implementation is correct
Example: This method is possibly buggy because the implementation does not seem to match the comment. In this case the comment could help the reader to detect the bug.
/** Removes all spaces from the {@code input} */
void compact(String input){
input.trim();
}
Why : The rationale for the current implementation.
Example: Without this comment, the reader will not know the reason for calling this method.
// Remove spaces to comply with IE23.5 formatting rules
compact(input);
How : The explanation for how the code works. This should already be apparent from the code, if the code is self-explanatory. Adding comments to explain the same thing is redundant.
Example:
Bad Reason: Comment explains how the code works.
// return true if both left end and right end are correct or the size has not incremented
return (left && right) || (input.size() == size);
Good Reason: Code refactored to be self-explanatory. Comment no longer needed.
boolean isSameSize = (input.size() == size) ;
return (isLeftEndCorrect && isRightEndCorrect) || isSameSize;
B. Quality of your feature(s)
Evaluates: How good is your Quality Assurance?
Based on:
- bugs you found in the v1.4 Practical Exam
- bugs in your work found by others during the PE
- testability of your feature (you will lose marks if testers feel that your feature is hard to test manually)
- your test code (you will lose marks if you don't meet
our expectations for automated testing ) - our own manual testing (when necessary)
- There is no requirement for a minimum coverage level. Note that in a production environment you are often required to have at least 90% of the code covered by tests. In this project, it can be less. The less coverage you have, the higher the risk of regression bugs, which will cost marks if not fixed before the final submission.
- You must write some tests so that we can evaluate your ability to write tests.
- How much of each type of testing should you do? We expect you to decide. You learned different types of testing and what they try to achieve. Based on that, you should decide how much of each type is required. Similarly, you can decide to what extent you want to automate tests, depending on the benefits and the effort required.
Evaluates: How good are the sections you wrote for the user guide and the developer guide?
Based on: the relevant sections of your project portfolio. Criteria considered:
- Explanation should be clear and written to match the audience.
- Good use of visuals to complement text.
- Use of correct UML notations (where applicable)
A. Process:
Evaluates: How well you did in project management related aspects of the project, as an individual and as a team
Based on: Supervisor observations of project milestones and GitHub data.
Milestones need to be reached the midnight before of the tutorial for it to be counted as achieved. To get a good grade for this aspect, achieve at least 60% of the recommended milestone progress.
Other criteria:
- Good use of GitHub milestones
- Good use of GitHub release mechanism
- Good version control, based on the repo
- Reasonable attempt to use the forking workflow
- Good task definition, assignment and tracking, based on the issue tracker
- Good use of buffers (opposite: everything at the last minute)
- Project done iteratively and incrementally (opposite: doing most of the work in one big burst)
B. Team-tasks:
Evaluates: How much did you contribute to team-tasks?
Based on: peer evaluations and tutor observations
Relevant: [
Here is a non-exhaustive list of team-tasks:
- Necessary general code enhancements e.g.,
- Work related to renaming the product
- Work related to changing the product icon
- Morphing the product into a different product
- Setting up the GitHub, Travis, AppVeyor, etc.
- Maintaining the issue tracker
- Release management
- Updating user/developer docs that are not specific to a feature e.g. documenting the target user profile
- Incorporating more useful tools/libraries/frameworks into the product or the project workflow (e.g. automate more aspects of the project workflow using a GitHub plugin)
Policy on following instructions
When working with others, especially in a large class such as CS2113/T, it is very important that you adhere to standards, policies, and instructions imposed on everyone. Not doing so creates unnecessary headaches for everyone and puts your work attitude in a negative light. That is why we penalize repeated violations of instructions. On the other hand we do understand that humans are liable to make mistakes. That is why we only penalize repeated or frequent mistakes.
We use the TEAMMATES online peer evaluation system to conduct several rounds of peer-evaluations. All peer evaluations will be taken into account when determining your participation marks. The system also allows you to give anonymous feedback to your teammates.
Extra Requirements: [considered for participation marks]
- Submitting peer evaluations is compulsory. If you routinely miss submitting peer evaluations, you can lose participation marks.
- 💡 TEAMMATES normally allows students to access it without using Google login. In this module, we encourage (but not require) you to login to TEAMMATES using your Google account and complete your profile with a
suitable profile photo . Reason: CS2113/T is a big class. This profile helps us to remember you better, even after the module is over.
-
The purpose of the profile photo is for the teaching team to identify you. Therefore, you should choose a recent individual photo showing your face clearly (i.e., not too small) -- somewhat similar to a passport photo. Some examples can be seen in the 'Teaching team' page. Given below are some examples of good and bad profile photos.
-
If you are uncomfortable posting your photo due to security reasons, you can post a lower resolution image so that it is hard for someone to misuse that image for fraudulent purposes. If you are concerned about privacy, you can request permission to omit your photo from the page by writing to prof.
Peer evaluation criteria: professional conduct
- Professional Communication :
- Communicates sufficiently and professionally. e.g. Does not use offensive language or excessive slang in project communications.
- Responds to communication from team members in a timely manner (e.g. within 24 hours).
- Punctuality: Does not cause others to waste time or slow down project progress by frequent tardiness.
- Dependability: Promises what can be done, and delivers what was promised.
- Effort: Puts in sufficient effort to, and tries their best to keep up with the module/project pace. Seeks help from others when necessary.
- Quality: Does not deliver work products that seem to be below the student's competence level i.e. tries their best to make the work product as high quality as possible within her competency level.
- Meticulousness:
- Rarely overlooks submission requirements.
- Rarely misses compulsory module activities such as pre-module survey.
- Teamwork: How willing are you to act as part of a team, contribute to team-level tasks, adhere to team decisions, etc.
Peer evaluation criteria: competency
- Technical Competency: Able to gain competency in all the required tools and techniques.
- Mentoring skills: Helps others when possible. Able to mentor others well.
- Communication skills: Able to communicate (written and spoken) well. Takes initiative in discussions.
Giving constructive feedback to others is a valuable skill for software engineers. It is also an intended learning outcome of this module. Half-hearted/trivial feedback will not earn participation marks.
Here are some things to keep in mind:
- Assume you are giving feedback to a colleague, not a friend. Keep the tone of your feedback reasonably professional. Do not use offensive language or slang.
- The feedback should be honest and consistent. Giving positive qualitative feedback (e.g.
Thanks for all the hard work!
and negative ratings (e.g.Equal share - 40%
) to the same team member is not being honest. - State your expectations early. All too often students give positive/neutral feedback early (hoping that the team member will improve later) and trash the team member in the final evaluation (because the he/she did not improve as expected). However, this could be confusing to the recipient. It is better to give negative feedback early so that the team member gets a clear signal that he/she needs to improve.
Relevant: [
To receive full 10 marks allocated for participation, earn at least 15 participation points.
There are 30+ available points to choose from:
-
Good peer ratings - Criteria for professional conduct (1 point for each criterion, max 7)
- Competency criteria (2 points for each, max 6)
Only those who submit peer evaluations can earn participation points from peer evaluations they receive.
- In-lecture quizzes (1 each, max 10 points)
- Enhanced AB1-AB3 (2 points for AB-1, 3 points each for AB-2 and AB-3)
- Module admin tasks done on time and as instructed
- Peer evaluations (1 points each)
- Pre-module survey (1 points)
If your response in the pre-module survey is not usable, you will not earn points for this exercise.
Relevant: [
Peer evaluation criteria: professional conduct
- Professional Communication :
- Communicates sufficiently and professionally. e.g. Does not use offensive language or excessive slang in project communications.
- Responds to communication from team members in a timely manner (e.g. within 24 hours).
- Punctuality: Does not cause others to waste time or slow down project progress by frequent tardiness.
- Dependability: Promises what can be done, and delivers what was promised.
- Effort: Puts in sufficient effort to, and tries their best to keep up with the module/project pace. Seeks help from others when necessary.
- Quality: Does not deliver work products that seem to be below the student's competence level i.e. tries their best to make the work product as high quality as possible within her competency level.
- Meticulousness:
- Rarely overlooks submission requirements.
- Rarely misses compulsory module activities such as pre-module survey.
- Teamwork: How willing are you to act as part of a team, contribute to team-level tasks, adhere to team decisions, etc.
Peer evaluation criteria: competency
- Technical Competency: Able to gain competency in all the required tools and techniques.
- Mentoring skills: Helps others when possible. Able to mentor others well.
- Communication skills: Able to communicate (written and spoken) well. Takes initiative in discussions.
Relevant: [
There is no midterm.
The final exam has two parts:
- Part 1: MCQ questions (30 minutes, 25 marks)
- Part 2: Essay questions (1 hour 30 min, 30 marks)
Both papers will be given to you at the start but you need to answer Part 1 first (i.e. MCQ paper). It will be collected 1 hour after the exam start time (even if arrived late for the exam). You are free to start part 2 early if you finish Part 1 early.
Final Exam: Part 1 (MCQ)
Each MCQ question gives you a statement to evaluate.
An example statement
Testing is a Q&A activity
Unless stated otherwise, the meaning of answer options are
A: Agree. If the question has multiple statements, agree with all of them.
B: Disagree. If the question has multiple statements, disagree with at least one of them
C, D, E: Not used
The exam paper has 50 questions. All questions carry equal marks.
The weightage of the Part 1 of the final exam is 25 marks out of the total score of 100.
Note that you have slightly more than ½ minute for each question, which means you need to go through the questions fairly quickly.
Given the fast pace required by the paper, to be fair to all students, you will not be allowed to clarify doubts about questions (in Part 1) by talking to invigilators.
- If a question is not clear, you can circle the question number in the question paper and write your doubt in the question paper, near that question.
- If your doubt is justified (e.g. there is a typo in the question) or if many students found the question to be unclear, the examiner may decide to omit that question from grading.
Questions in Part 1 are confidential. You are not allowed to reveal Part 1 content to anyone after the exam. All pages of the assessment paper are to be returned at the end of the exam.
You will be given OCR forms (i.e., bubble sheets) to indicate your answers for Part 1. As each OCR form can accommodate only 50 answers, you will be given 2 OCR forms. Indicate your student number in both OCR forms.
Some questions will use underlines or highlighting to draw your attention to a specific part of the question. That is because those parts are highly relevant to the answer and we don’t want you to miss the relevance of that part.
Consider the statement below:
Technique ABC can be used to generate more test cases.
The word can is underlined because the decision you need to make is whether the ABC can or cannot be used to generate more test cases; the decision is not whether ABC can be used to generate more or better test cases.
The exam paper is open-book: you may bring any printed or written materials to the exam in hard copy format. However, given the fast pace required by Part 1, you will not have time left to refer notes during that part of the exam.
💡 Mark the OCR form as you go, rather than planning to transfer your answers to the OCR form near the end. Reason: Given there are 100 questions, it will be hard to estimate how much time you need to mass-transfer all answers to OCR forms.
💡 Write the answer in the exam paper as well when marking it in the OCR form. Reason: It will reduce the chance of missing a question. Furthermore, in case you missed a question, it will help you correct the OCR form quickly.
💡 We have tried to avoid deliberately misleading/tricky questions. If a question seems to take a very long time to figure out, you are probably over-thinking it.
You will be given a practice exam paper to familiarize yourself with this slightly unusual exam format.
Final Exam: Part 2 (Essay)
Unlike in part 1, you can ask invigilators for clarifications if you found a question to be unclear in part 2.
Yes, you may use pencils when answering part 2.
The weightage of the Part 2 of the final exam is 15 marks out of the total score of 100.
Resources
Past exam papers will be uploaded on IVLE.
Relevant: [
Note that project grading is not competitive (not bell curved). CS2113T projects will be assessed separately from CS2113 projects. Given below is the marking scheme.
Total: 50 marks ( 40 individual marks + 10 team marks)
Evaluates: How well do your features fit together to form a cohesive product (not how many features or how big the features are)?
Based on: user guide and the product demo. The quality of the demo will be factored in as well.
💡 Feature that fit well with the other features will earn more marks.
Evaluates:
A. Code quality/quantity:
How good your implementation is, in terms of the quality and the quantity of the code you have written yourself.
Based on: an inspection of the parts of the code you claim as written by you.
-
Ensure your code has at least some evidence of these (see here for more info)
- logging
- exceptions
- assertions
- defensive coding
-
Ensure there are no coding standard violations e.g. all boolean variables/methods sounds like booleans. Checkstyle can prevent only some coding standard violations; others need to be checked manually.
-
Ensure SLAP is applied at a reasonable level. Long methods or deeply-nested code are symptoms of low-SLAP may be counted against your code quality.
-
Reduce code duplications i.e. if there multiple blocks of code that vary only in minor ways, try to extract out similarities into one place, especially in test code.
-
In addition, try to apply as many of the
code quality guidelines covered in the module as much as you can.
Code Quality
Can explain the importance of code quality
Always code as if the person who ends up maintaining your code will be a violent psychopath who knows where you live. -- Martin Golding
Can explain the importance of readability
Programs should be written and polished until they acquire publication quality. --Niklaus Wirth
Among various dimensions of code quality, such as run-time efficiency, security, and robustness, one of the most important is understandability. This is because in any non-trivial software project, code needs to be read, understood, and modified by other developers later on. Even if we do not intend to pass the code to someone else, code quality is still important because we all become 'strangers' to our own code someday.
The two code samples given below achieve the same functionality, but one is easier to read.
Bad
|
|
Good
|
Bad
|
|
Good
|
Can improve code quality using technique: avoid long methods
Be wary when a method is longer than the computer screen, and take corrective action when it goes beyond 30 LOC (lines of code). The bigger the haystack, the harder it is to find a needle.
Can improve code quality using technique: avoid deep nesting
If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. --Linux 1.3.53 CodingStyle
In particular, avoid arrowhead style code.
Example:
Can improve code quality using technique: avoid complicated expressions
Avoid complicated expressions, especially those having many negations and nested parentheses. If you must evaluate complicated expressions, have it done in steps (i.e. calculate some intermediate values first and use them to calculate the final value).
Example:
Bad
return ((length < MAX_LENGTH) || (previousSize != length)) && (typeCode == URGENT);
Good
boolean isWithinSizeLimit = length < MAX_LENGTH;
boolean isSameSize = previousSize != length;
boolean isValidCode = isWithinSizeLimit || isSameSize;
boolean isUrgent = typeCode == URGENT;
return isValidCode && isUrgent;
Example:
Bad
return ((length < MAX_LENGTH) or (previous_size != length)) and (type_code == URGENT)
Good
is_within_size_limit = length < MAX_LENGTH
is_same_size = previous_size != length
is_valid_code = is_within_size_limit or is_same_size
is_urgent = type_code == URGENT
return is_valid_code and is_urgent
The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague. -- Edsger Dijkstra
Can improve code quality using technique: avoid magic numbers
When the code has a number that does not explain the meaning of the number, we call that a magic number (as in “the number appears as if by magic”). Using a
Example:
Bad
|
|
Good
|
Note: Python does not have a way to make a variable a constant. However, you can use a normal variable with an ALL_CAPS
name to simulate a constant.
Bad
|
|
Good
|
Similarly, we can have ‘magic’ values of other data types.
Bad
"Error 1432" // A magic string!
Can improve code quality using technique: make the code obvious
Make the code as explicit as possible, even if the language syntax allows them to be implicit. Here are some examples:
- [
Java
] Use explicit type conversion instead of implicit type conversion. - [
Java
,Python
] Use parentheses/braces to show grouping even when they can be skipped. - [
Java
,Python
] Useenumerations when a certain variable can take only a small number of finite values. For example, instead of declaring the variable 'state' as an integer and using values 0,1,2 to denote the states 'starting', 'enabled', and 'disabled' respectively, declare 'state' as typeSystemState
and define an enumerationSystemState
that has values'STARTING'
,'ENABLED'
, and'DISABLED'
.
Can improve code quality using technique: structure code logically
Lay out the code so that it adheres to the logical structure. The code should read like a story. Just like we use section breaks, chapters and paragraphs to organize a story, use classes, methods, indentation and line spacing in your code to group related segments of the code. For example, you can use blank lines to group related statements together. Sometimes, the correctness of your code does not depend on the order in which you perform certain intermediary steps. Nevertheless, this order may affect the clarity of the story you are trying to tell. Choose the order that makes the story most readable.
Can improve code quality using technique: do not 'trip up' reader
Avoid things that would make the reader go ‘huh?’, such as,
- unused parameters in the method signature
- similar things look different
- different things that look similar
- multiple statements in the same line
- data flow anomalies such as, pre-assigning values to variables and modifying it without any use of the pre-assigned value
Can improve code quality using technique: practice kissing
As the old adage goes, "keep it simple, stupid” (KISS). Do not try to write ‘clever’ code. For example, do not dismiss the brute-force yet simple solution in favor of a complicated one because of some ‘supposed benefits’ such as 'better reusability' unless you have a strong justification.
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. --Brian W. Kernighan
Programs must be written for people to read, and only incidentally for machines to execute. --Abelson and Sussman
Can improve code quality using technique: avoid premature optimizations
Optimizing code prematurely has several drawbacks:
- We may not know which parts are the real performance bottlenecks. This is especially the case when the code undergoes transformations (e.g. compiling, minifying, transpiling, etc.) before it becomes an executable. Ideally, you should use a profiler tool to identify the actual bottlenecks of the code first, and optimize only those parts.
- Optimizing can complicate the code, affecting correctness and understandability
- Hand-optimized code can be harder for the compiler to optimize (the simpler the code, the easier for the compiler to optimize it). In many cases a compiler can do a better job of optimizing the runtime code if you don't get in the way by trying to hand-optimize the source code.
A popular saying in the industry is make it work, make it right, make it fast which means in most cases getting the code to perform correctly should take priority over optimizing it. If the code doesn't work correctly, it has no value on matter how fast/efficient it it.
Premature optimization is the root of all evil in programming. --Donald Knuth
Note that there are cases where optimizing takes priority over other things e.g. when writing code for resource-constrained environments. This guideline simply a caution that you should optimize only when it is really needed.
Can improve code quality using technique: SLAP hard
Avoid varying the level of
Example:
Bad
readData();
salary = basic*rise+1000;
tax = (taxable?salary*0.07:0);
displayResult();
Good
readData();
processData();
displayResult();
Design → Design Fundamentals → Abstraction →
Abstraction is a technique for dealing with complexity. It works by establishing a level of complexity we are interested in, and suppressing the more complex details below that level.
The guiding principle of abstraction is that only details that are relevant to the current perspective or the task at hand needs to be considered. As most programs are written to solve complex problems involving large amounts of intricate details, it is impossible to deal with all these details at the same time. That is where abstraction can help.
Ignoring lower level data items and thinking in terms of bigger entities is called data abstraction.
Within a certain software component, we might deal with a user data type, while ignoring the details contained in the user data item such as name, and date of birth. These details have been ‘abstracted away’ as they do not affect the task of that software component.
Control abstraction abstracts away details of the actual control flow to focus on tasks at a simplified level.
print(“Hello”)
is an abstraction of the actual output mechanism within the computer.
Abstraction can be applied repeatedly to obtain progressively higher levels of abstractions.
An example of different levels of data abstraction: a File
is a data item that is at a higher level than an array and an array is at a higher level than a bit.
An example of different levels of control abstraction: execute(Game)
is at a higher level than print(Char)
which is at a higher than an Assembly language instruction MOV
.
Abstraction is a general concept that is not limited to just data or control abstractions.
Some more general examples of abstraction:
- An OOP class is an abstraction over related data and behaviors.
- An architecture is a higher-level abstraction of the design of a software.
- Models (e.g., UML models) are abstractions of some aspect of reality.
Can improve code quality using technique: make the happy path prominent
The happy path (i.e. the execution path taken when everything goes well) should be clear and prominent in your code. Restructure the code to make the happy path unindented as much as possible. It is the ‘unusual’ cases that should be indented. Someone reading the code should not get distracted by alternative paths taken when error conditions happen. One technique that could help in this regard is the use of guard clauses.
Example:
Bad
if (!isUnusualCase) { //detecting an unusual condition
if (!isErrorCase) {
start(); //main path
process();
cleanup();
exit();
} else {
handleError();
}
} else {
handleUnusualCase(); //handling that unusual condition
}
In the code above,
- Unusual condition detection is separated from their handling.
- Main path is nested deeply.
Good
if (isUnusualCase) { //Guard Clause
handleUnusualCase();
return;
}
if (isErrorCase) { //Guard Clause
handleError();
return;
}
start();
process();
cleanup();
exit();
In contrast, the above code
- deals with unusual conditions as soon as they are detected so that the reader doesn't have to remember them for long.
- keeps the main path un-indented.
Can explain the need for following a standard
One essential way to improve code quality is to follow a consistent style. That is why software engineers follow a strict coding standard (aka style guide).
The aim of a coding standard is to make the entire code base look like it was written by one person. A coding standard is usually specific to a programming language and specifies guidelines such as the location of opening and closing braces, indentation styles and naming styles (e.g. whether to use Hungarian style, Pascal casing, Camel casing, etc.). It is important that the whole team/company use the same coding standard and that standard is not generally inconsistent with typical industry practices. If a company's coding standards is very different from what is used typically in the industry, new recruits will take longer to get used to the company's coding style.
💡 IDEs can help to enforce some parts of a coding standard e.g. indentation rules.
What is the recommended approach regarding coding standards?
c
What is the aim of using a coding standard? How does it help?
Can follow simple mechanical style rules
Learn basic guidelines of the Java coding standard (by OSS-Generic)
Sample coding standard: PEP 8 Python Style Guide -- by Python.org
Consider the code given below:
import java.util.*;
public class Task {
public static final String descriptionPrefix = "description: ";
private String description;
private boolean important;
List<String> pastDescription = new ArrayList<>(); // a list of past descriptions
public Task(String d) {
this.description = d;
if (!d.isEmpty())
this.important = true;
}
public String getAsXML() { return "<task>"+description+"</task>"; }
/**
* Print the description as a string.
*/
public void printingDescription(){ System.out.println(this); }
@Override
public String toString() { return descriptionPrefix + description; }
}
In what ways the code violate the basic guidelines (i.e., those marked with one ⭐️) of the OSS-Generic Java Coding Standard given here?
Here are three:
descriptionPrefix
is a constant and should be namedDESCRIPTION_PREFIX
- method name
printingDescription()
should be named asprintDescription()
- boolean variable
important
should be named to sound boolean e.g.,isImportant
There are many more.
Can follow intermediate style rules
Go through the provided Java coding standard and learn the intermediate style rules.
According to the given Java coding standard, which one of these is not a good name?
b
Explanation: checkWeight
is an action. Naming variables as actions makes the code harder to follow. isWeightValid
may be a better name.
Repeat the exercise in the panel below but also find violations of intermediate level guidelines.
Consider the code given below:
import java.util.*;
public class Task {
public static final String descriptionPrefix = "description: ";
private String description;
private boolean important;
List<String> pastDescription = new ArrayList<>(); // a list of past descriptions
public Task(String d) {
this.description = d;
if (!d.isEmpty())
this.important = true;
}
public String getAsXML() { return "<task>"+description+"</task>"; }
/**
* Print the description as a string.
*/
public void printingDescription(){ System.out.println(this); }
@Override
public String toString() { return descriptionPrefix + description; }
}
In what ways the code violate the basic guidelines (i.e., those marked with one ⭐️) of the OSS-Generic Java Coding Standard given here?
Here are three:
descriptionPrefix
is a constant and should be namedDESCRIPTION_PREFIX
- method name
printingDescription()
should be named asprintDescription()
- boolean variable
important
should be named to sound boolean e.g.,isImportant
There are many more.
Here's one you are more likely to miss:
* Print the description as a string.
→* Prints the description as a string.
There are more.
Can explain the need for good names in code
Proper naming improves the readability. It also reduces bugs caused by ambiguities regarding the intent of a variable or a method.
There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton
Can improve code quality using technique: use nouns for things and verbs for actions
Every system is built from a domain-specific language designed by the programmers to describe that system. Functions are the verbs of that language, and classes are the nouns. ― Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship
Use nouns for classes/variables and verbs for methods/functions.
Examples:
Name for a | Bad | Good |
---|---|---|
Class | CheckLimit |
LimitChecker |
method | result() |
calculate() |
Distinguish clearly between single-valued and multivalued variables.
Examples:
Good
Person student;
ArrayList<Person> students;
Good
student = Person('Jim')
students = [Person('Jim'), Person('Alice')]
Can improve code quality using technique: use standard words
Use correct spelling in names. Avoid 'texting-style' spelling. Avoid foreign language words, slang, and names that are only meaningful within specific contexts/times e.g. terms from private jokes, a TV show currently popular in your country
Can improve code quality using technique: use name to explain
A name is not just for differentiation; it should explain the named entity to the reader accurately and at a sufficient level of detail.
Examples:
Bad | Good |
---|---|
processInput() (what 'process'?) |
removeWhiteSpaceFromInput() |
flag |
isValidInput |
temp |
If the name has multiple words, they should be in a sensible order.
Examples:
Bad | Good |
---|---|
bySizeOrder() |
orderBySize() |
Imagine going to the doctor's and saying "My eye1 is swollen"! Don’t use numbers or case to distinguish names.
Examples:
Bad | Bad | Good |
---|---|---|
value1 , value2 |
value , Value |
originalValue , finalValue |
Can improve code quality using technique: not too long, not too short
While it is preferable not to have lengthy names, names that are 'too short' are even worse. If you must abbreviate or use acronyms, do it consistently. Explain their full meaning at an obvious location.
Can improve code quality using technique: avoid misleading names
Related things should be named similarly, while unrelated things should NOT.
Example: Consider these variables
colorBlack
: hex value for color blackcolorWhite
: hex value for color whitecolorBlue
: number of times blue is usedhexForRed
: : hex value for color red
This is misleading because colorBlue
is named similar to colorWhite
and colorBlack
but has a different purpose while hexForRed
is named differently but has very similar purpose to the first two variables. The following is better:
hexForBlack
hexForWhite
hexForRed
blueColorCount
Avoid misleading or ambiguous names (e.g. those with multiple meanings), similar sounding names, hard-to-pronounce ones (e.g. avoid ambiguities like "is that a lowercase L, capital I or number 1?", or "is that number 0 or letter O?"), almost similar names.
Examples:
Bad | Good | Reason |
---|---|---|
phase0 |
phaseZero |
Is that zero or letter O? |
rwrLgtDirn |
rowerLegitDirection |
Hard to pronounce |
right left wrong |
rightDirection leftDirection wrongResponse |
right is for 'correct' or 'opposite of 'left'? |
redBooks readBooks |
redColorBooks booksRead |
red and read (past tense) sounds the same |
FiletMignon |
egg |
If the requirement is just a name of a food, egg is a much easier to type/say choice than FiletMignon |
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. Some 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, throw an exception). 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
- Use one variable for one purpose. Do not reuse a variable for a different purpose other than its intended one, just because the data type is the same.
- Do not reuse formal parameters as local variables inside the method.
Bad
double computeRectangleArea(double length, double width) {
length = length * width;
return length;
}
Good
double computeRectangleArea(double length, double width) {
double area;
area = length * width;
return area;
}
Can improve code quality using technique: avoid empty catch blocks
Never write an empty catch
statement. At least give a comment to explain why the catch
block is left empty.
Can improve code quality using technique: delete dead code
We all feel reluctant to delete code we have painstakingly written, even if we have no use for that code any more ("I spent a lot of time writing that code; what if we 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: minimise 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: minimise 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
Supplmentary → Principles →
DRY (Don't Repeat Yourself) Principle: Every piece of knowledge must have a single, unambiguous, authoritative representation within a system The Pragmatic Programmer, by Andy Hunt and Dave Thomas
This principle guards against duplication of information.
The functionality implemented twice is a violation of the DRY principle even if the two implementations are different.
The value a system-wide timeout being defined in multiple places is a violation of DRY.
Can explain the need for commenting minimally but sufficiently
Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’ Improve the code and then document it to make it even clearer. --Steve McConnell, Author of Clean Code
Some think commenting heavily increases the 'code quality'. This is not so. Avoid writing comments to explain bad code. Improve the code to make it self-explanatory.
Can improve code quality using technique: do not repeat the obvious
If the code is self-explanatory, refrain from repeating the description in a comment just for the sake of 'good documentation'.
Bad
// increment x
x++;
//trim the input
trimInput();
Can improve code quality using technique: write to the reader
Do not write comments as if they are private notes to self. Instead, write them well enough to be understood by another programmer. One type of comments that is almost always useful is the header comment that you write for a class or an operation to explain its purpose.
Examples:
Bad Reason: this comment will only make sense to the person who wrote it
// a quick trim function used to fix bug I detected overnight
void trimInput(){
....
}
Good
/** Trims the input of leading and trailing spaces */
void trimInput(){
....
}
Bad Reason: this comment will only make sense to the person who wrote it
# a quick trim function used to fix bug I detected overnight
def trim_input():
...
Good
def trim_input():
"""Trim the input of leading and trailing spaces"""
...
Can improve code quality using technique: explain what and why, not how
Comments should explain what and why aspect of the code, rather than the how aspect.
What : The specification of what the code supposed to do. The reader can compare such comments to the implementation to verify if the implementation is correct
Example: This method is possibly buggy because the implementation does not seem to match the comment. In this case the comment could help the reader to detect the bug.
/** Removes all spaces from the {@code input} */
void compact(String input){
input.trim();
}
Why : The rationale for the current implementation.
Example: Without this comment, the reader will not know the reason for calling this method.
// Remove spaces to comply with IE23.5 formatting rules
compact(input);
How : The explanation for how the code works. This should already be apparent from the code, if the code is self-explanatory. Adding comments to explain the same thing is redundant.
Example:
Bad Reason: Comment explains how the code works.
// return true if both left end and right end are correct or the size has not incremented
return (left && right) || (input.size() == size);
Good Reason: Code refactored to be self-explanatory. Comment no longer needed.
boolean isSameSize = (input.size() == size) ;
return (isLeftEndCorrect && isRightEndCorrect) || isSameSize;
B. Quality of your feature(s)
Evaluates: How good is your Quality Assurance?
Based on:
- bugs you found in the v1.4 Practical Exam
- bugs in your work found by others during the PE
- testability of your feature (you will lose marks if testers feel that your feature is hard to test manually)
- your test code (you will lose marks if you don't meet
our expectations for automated testing ) - our own manual testing (when necessary)
- There is no requirement for a minimum coverage level. Note that in a production environment you are often required to have at least 90% of the code covered by tests. In this project, it can be less. The less coverage you have, the higher the risk of regression bugs, which will cost marks if not fixed before the final submission.
- You must write some tests so that we can evaluate your ability to write tests.
- How much of each type of testing should you do? We expect you to decide. You learned different types of testing and what they try to achieve. Based on that, you should decide how much of each type is required. Similarly, you can decide to what extent you want to automate tests, depending on the benefits and the effort required.
Evaluates: How good are the sections you wrote for the user guide and the developer guide?
Based on: the relevant sections of your project portfolio. Criteria considered:
- Explanation should be clear and written to match the audience.
- Good use of visuals to complement text.
- Use of correct UML notations (where applicable)
A. Process:
Evaluates: How well you did in project management related aspects of the project, as an individual and as a team
Based on: Supervisor observations of project milestones and GitHub data.
Milestones need to be reached the midnight before of the tutorial for it to be counted as achieved. To get a good grade for this aspect, achieve at least 60% of the recommended milestone progress.
Other criteria:
- Good use of GitHub milestones
- Good use of GitHub release mechanism
- Good version control, based on the repo
- Reasonable attempt to use the forking workflow
- Good task definition, assignment and tracking, based on the issue tracker
- Good use of buffers (opposite: everything at the last minute)
- Project done iteratively and incrementally (opposite: doing most of the work in one big burst)
B. Team-tasks:
Evaluates: How much did you contribute to team-tasks?
Based on: peer evaluations and tutor observations
Relevant: [
Here is a non-exhaustive list of team-tasks:
- Necessary general code enhancements e.g.,
- Work related to renaming the product
- Work related to changing the product icon
- Morphing the product into a different product
- Setting up the GitHub, Travis, AppVeyor, etc.
- Maintaining the issue tracker
- Release management
- Updating user/developer docs that are not specific to a feature e.g. documenting the target user profile
- Incorporating more useful tools/libraries/frameworks into the product or the project workflow (e.g. automate more aspects of the project workflow using a GitHub plugin)
To receive full 10 marks allocated for participation, earn at least 15 participation points.
There are 30+ available points to choose from:
-
Good peer ratings - Criteria for professional conduct (1 point for each criterion, max 7)
- Competency criteria (2 points for each, max 6)
Only those who submit peer evaluations can earn participation points from peer evaluations they receive.
- In-lecture quizzes (1 each, max 10 points)
- Enhanced AB1-AB3 (2 points for AB-1, 3 points each for AB-2 and AB-3)
- Module admin tasks done on time and as instructed
- Peer evaluations (1 points each)
- Pre-module survey (1 points)
If your response in the pre-module survey is not usable, you will not earn points for this exercise.
Relevant: [
Peer evaluation criteria: professional conduct
- Professional Communication :
- Communicates sufficiently and professionally. e.g. Does not use offensive language or excessive slang in project communications.
- Responds to communication from team members in a timely manner (e.g. within 24 hours).
- Punctuality: Does not cause others to waste time or slow down project progress by frequent tardiness.
- Dependability: Promises what can be done, and delivers what was promised.
- Effort: Puts in sufficient effort to, and tries their best to keep up with the module/project pace. Seeks help from others when necessary.
- Quality: Does not deliver work products that seem to be below the student's competence level i.e. tries their best to make the work product as high quality as possible within her competency level.
- Meticulousness:
- Rarely overlooks submission requirements.
- Rarely misses compulsory module activities such as pre-module survey.
- Teamwork: How willing are you to act as part of a team, contribute to team-level tasks, adhere to team decisions, etc.
Peer evaluation criteria: competency
- Technical Competency: Able to gain competency in all the required tools and techniques.
- Mentoring skills: Helps others when possible. Able to mentor others well.
- Communication skills: Able to communicate (written and spoken) well. Takes initiative in discussions.
Your tutor will serve as your project supervisor too.
The supervisor's main job is to observe, facilitate self/peer learning, evaluate, and give feedback.
Tutorial time is the main avenue for meeting your supervisor. In addition, you can meet the supervisor before/after the tutorial, or any other time, as many times you need, subject to availability in his/her schedule.
Note that it is not the supervisor’s job to chase you down and give help. It is up to you to get as much feedback from them as you need. You are free to request more feedback from the supervisor as necessary. Similarly, it is not the job of the supervisor to lead your project to success.
What if I don’t carry around a laptop?
If you do not have a laptop or prefer not to bring the laptop, it is up to you to show your work to the tutor in some way (e.g. by connecting to your home PC remotely), without requiring extra time/effort from the tutor or team members.
Reason: As you enjoy the benefits of not bring the laptop; you (not others) should bear the cost too.