Set up a project in an IDE [LO-IdeSetup]

Exercise: Setup project in IntelliJ

Part A:

  • Create a new project in IntelliJ and write a small HelloWorld program.

Part B:

The AddressBook.java code is rather long, which makes it cumbersome to navigate by scrolling alone. Navigating code using IDE shortcuts is a more efficient option. For example, CTRL+B will navigate to the definition of the method/field at the cursor.

Exercise: Learn to navigate code using shortcuts

  • Use Intellij basic code navigation features to navigate code of this project.

Use a debugger [LO-Debugging]

Exercise: Learn to step through code using the debugger

Prerequisite: [LO-IdeSetup]

Demonstrate your debugging skills using the AddressBook code.

Here are some things you can do in your demonstration:

  1. Set a 'break point'

  2. Run the program in debug mode

  3. 'Step through' a few lines of code while examining variable values

  4. 'Step into', and 'step out of', methods as you step through the code

  5. …​

Automate CLI testing [LO-AutomatedCliTesting]

Exercise: Practice automated CLI testing

  • Run the tests as explained in the Testing section.

  • Examine the test script to understand how the script works.

  • Add a few more tests to the input.txt. Run the tests. It should fail. Modify EXPECTED.TXT to make the tests pass again.

  • Edit the AddressBook.java to modify the behavior slightly and modify tests to match.

Use Collections [LO-Collections]

Note how the AddressBook class uses ArrayList<> class (from the Java Collections library) to store a list of String or String[] objects.

Exercise: Use HashMap

Currently, a person’s details are stored as a String[]. Modify the code to use a HashMap<String, String> instead. A sample code snippet is given below.

private static final String PERSON_PROPERTY_NAME = "name";
private static final String PERSON_PROPERTY_EMAIL = "email";
...
HashMap<String,String> john = new HashMap<>();
john.put(PERSON_PROPERTY_NAME, "John Doe");
john.put(PERSON_PROPERTY_EMAIL, "john.doe@email.com");
//etc.

Use Enums [LO-Enums]

Exercise: Use HashMap + Enum

Similar to the exercise in the LO-Collections section, but also bring in Java enum feature.

private enum PersonProperty  {NAME, EMAIL, PHONE};
...
HashMap<PersonProperty,String> john = new HashMap<>();
john.put(PersonProperty.NAME, "John Doe");
john.put(PersonProperty.EMAIL, "john.doe@email.com");
//etc.

Use Varargs [LO-Varargs]

Note how the showToUser method uses Java Varargs feature.

Exercise: Use Varargs

Modify the code to remove the use of the Varargs feature. Compare the code with and without the varargs feature.

Follow a coding standard [LO-CodingStandard]

The given code follows the coding standard for the most part.

This learning outcome is covered by the exercise in [LO-Refactor].

Apply coding best practices [LO-CodingBestPractices]

Most of the given code follows the best practices mentioned here.

This learning outcome is covered by the exercise in [LO-Refactor]

Refactor code [LO-Refactor]

Resources:

Exercise: Refactor the code to make it better

Note: this exercise covers two other Learning Outcomes: [LO-CodingStandard], [LO-CodingBestPractices]

  • Improve the code in the following ways,

    • Fix coding standard violations.

    • Fix violations of the best practices given in in this document.

    • Any other change that you think will improve the quality of the code.

  • Try to do the modifications as a combination of standard refactorings given in this catalog

  • As far as possible, use automated refactoring features in IntelliJ.

  • If you know how to use Git, commit code after each refactoring. In the commit message, mention which refactoring you applied. Example commit messages: Extract variable isValidPerson, Inline method isValidPerson()

  • Remember to run the test script after each refactoring to prevent regressions.

Abstract methods well [LO-MethodAbstraction]

Notice how most of the methods in AddressBook are short and focused (does only one thing and does it well).

Case 1. Consider the following three lines in the main method.

    String userCommand = getUserInput();
    echoUserCommand(userCommand);
    String feedback = executeCommand(userCommand);

If we include the code of echoUserCommand(String) method inside the getUserInput() (resulting in the code given below), the behavior of AddressBook remains as before. However, that is a not a good approach because now the getUserInput() is doing two distinct things. A well-abstracted method should do only one thing.

    String userCommand = getUserInput(); //also echos the command back to the user
    String feedback = executeCommand(userCommand);

Case 2. Consider the method removePrefixSign(String s, String sign). While it is short, there are some problems with how it has been abstracted.

  1. It contains the term sign which is not a term used by the AddressBook vocabulary.

    A method adds a new term to the vocabulary used to express the solution. Therefore, it is not good when a method name contains terms that are not strictly necessary to express the solution (e.g. there is another term already used to express the same thing) or not in tune with the solution (e.g. it does not go well with the other terms already used).

  2. Its implementation is not doing exactly what is advertised by the method name and the header comment. For example, the code does not remove only prefixes; it removes sign from anywhere in the s.

  3. The method can be more general and more independent from the rest of the code. For example, the method below can do the same job, but is more general (works for any string, not just parameters) and is more independent from the rest of the code (not specific to AddressBook)

    /**
     * Removes prefix from the given fullString if prefix occurs at the start of the string.
     */
     private static String removePrefix(String fullString, String prefix) { ... }

    If needed, a more AddressBook-specific method that works on parameter strings only can be defined. In that case, that method can make use of the more general method suggested above.

Exercise: Improve abstraction of method

Refactor the method removePrefixSign as suggested above.

Follow SLAP [LO-SLAP]

Notice how most of the methods in AddressBook are written at a single level of abstraction (cf se-edu/se-book:SLAP)

Here is an example:

    public static void main(String[] args) {
        showWelcomeMessage();
        processProgramArgs(args);
        loadDataFromStorage();
        while (true) {
            userCommand = getUserInput();
            echoUserCommand(userCommand);
            String feedback = executeCommand(userCommand);
            showResultToUser(feedback);
        }
    }

Exercise 1: Reduce SLAP of method

In the main method, replace the processProgramArgs(args) call with the actual code of that method. The main method no longer has SLAP. Notice how mixing low level code with high level code reduces readability.

Exercise 2: Refactor the code to make it worse!

Sometimes, going in the wrong direction can be a good learning experience too. In this exercise, we explore how low code qualities can go.

  • Refactor the code to make the code as bad as possible. i.e. How bad can you make it without breaking the functionality while still making it look like it was written by a programmer (but a very bad programmer :-)).

  • In particular, inlining methods can worsen the code quality fast.

Work in a 1kLoC code base [LO-1KLoC]

Exercise: Enhance the code

Enhance the AddressBook to prove that you can work in a codebase of 1KLoC. Remember to change code in small steps, update/run tests after each change, and commit after each significant change.

Some suggested enhancements:

  • Make the find command case insensitive e.g. find john should match John

  • Add a sort command that can list the persons in alphabetical order

  • Add an edit command that can edit properties of a specific person

  • Add an additional field (like date of birth) to the person record