This portfolio showcases my ability to produce functional code and documentation, for a team-based NUS software engineering project.

PROJECT: NSync


Overview

NSync is a desktop-based utility application used by NUS students, with tailored tools for timetable coordination and notes acquisition, allowing for streamlining of work processes.

Summary of contributions

  • Major enhancement: Notes Deletion

    • What it does: In a single command, the user is able to delete the notes previously downloaded using NSync, allowing him to quickly and conveniently free up storage space.

    • Justification: Because my teammate has already implemented an automated notes downloader, allowing the user to download large folders conveniently, it is important to have an equally convenient method to delete notes. This removes the need for the user to manually go to storage to delete his own notes.

    • Highlights: As this enhancement involves interaction with actual storage, a good understanding of how Model and Storage components work together, is required. Existing file manipulation libraries such as java.io and java nio libraries were incorporated into this enhancement.

  • Minor enhancement: Alphabetised contact list in the address book

    • What it does: When the user adds a new contact to your contact list, the contact is added in an alphabetic order automatically. This is opposed to the original implementation, which simply appends the new contact to the end of the contact list.

    • Justification: Alphabetisation allows the contact list to appear more organized.

    • Highlights: Understanding of time complexity of algorithms is required. For the sorting algorithms, the existing library java.util was incorporated.

  • Code contributed: Functional and test code

  • Other contributions:

    • Project management: Successfully assigned and managed issues for all team members, and matched them to corresponding deadlines.

    • Documentation: Organized the User Guide into a more reader friendly format
      #61

    • Community: Pull requests meticulously reviewed
      #72
      #148

Contributions to the User Guide

The following shows my contributions to the user guide, in relation to functions clearNotes and deleteSelectNotes They showcase my ability to write documentation targeting end-users.

Clearing all downloaded notes : clearNotes

Clears all downloaded notes.
Format: clearNotes

Deleting notes from one or more selected modules : deleteSelectNotes

Deletes the notes that belong to the specified module, from storage
Format: deleteSelectNotes [ENROLLED MODULE]1..

  • Deletes notes of the ENROLLED MODULE.

  • The notes of the ENROLLED MODULE would be deleted, if they were downloaded using the command downloadAllnotes or downloadSelectNotes.

Examples:

  • deleteSelectNotes CS2100
    Deletes the notes belonging to the module CS2100.

  • deleteSelectNotes CS2105 CS2106
    Deletes the notes belongs to the modules CS2105 and CS2106.

  • deleteSelectNotes CS
    Deletes the notes belongs to modules that have "CS" in their names

  • deleteSelectNotes PL3232
    Will not delete anything if "PL3232" does not exist as your downloaded notes.

  • deleteSelectNotes PL3232 CS2106
    Will not delete anything if "PL3232" does not exist as your downloaded notes, however notes belonging to "CS2106" would be deleted.

Contributions to the Developer Guide

The following shows my contributions to the user guide, in relation to the alphabetised contact list enhancement. This showcases my ability to write technical documentation and the technical depth of my contributions to the project.

Storage component

Structure of the Storage Component

StorageClassDiagram

API : Storage.java

The Storage component as shown in the figure above, has the following capabilities.

  • can save UserPref objects in json format and read it back.

  • can save the Address Book data in xml format and read it back.

  • can unzip, organize and delete, a ll notes downloaded by the user using NSync.

Sorting of Contacts Feature

Current Implementation

To make the codebase easy to understand for you as a developer, we implemented the sorting mechanism with UniquePersonListHelper, which is facilitated by UniquePersonList, which keeps a list of unique persons in AddressBook. UniquePersonListHelper sorts the contacts in UniquePersonList in an lexicographical order, according to the person’s name. It implements the following operations:

  • UniquePersonList#add() — Adds a new person to UniquePersonList, and hence the contact list

  • UniquePersonList#remove() — Removes a new person to UniquePersonList, and hence the contact list

  • UniquePersonList#setPerson() — Sets a new person, in place of an existing person, to UniquePersonList, and hence the contact list

  • UniquePersonList#setPersons() — Sets a list of persons, in place of the current list of persons, to UniquePersonList, and hence the contact list

  • UniquePersonList#contains() — Checks if a person is already a part of UniquePersonList, and hence the contact list

These operations are exposed in the Model interface, through ModelManager, then through AddressBook. In Model, they are exposed as Model#addPerson(), Model#deletePerson(), Model#updatePerson(), Model#resetData(), and Model#hasPerson() respectively.

Within ModelManager, the above listed operations are directly exposed as ModelManager#addPerson(), ModelManager#deletePerson(), ModelManager#updatePerson(), ModelManager#resetData(), and ModelManager#hasPerson() respectively.

Within AddressBook, the above listed operations are directly exposed as AddressBook#addPerson(), AddressBook#removePerson(), AddressBook#updatePerson(), AddressBook#setPersons(), and AddressBook#hasPerson() respectively.

Because UniquePersonListHelper stores persons in a treemap, with person name as the key, and person as the value in the key-value pair of the treemap, it is able to automatically sort persons according to their names. Therefore, it is possible to iterate through UniquePersonListHelper, in an in-order depth-first-search, to acquire the sorted order of persons. This sorted order will be copied into UniquePersonList.

Given below is an example usage scenario and how the sorting mechanism behaves at each step.

Step 1. The user launches the application for the first time. The UniquePersonListHelper will be initialized with the saved persons of the application. For this example, let us assume that the UniquePersonList is empty, and hence, there are no saved persons.

UniquePersonList will also be initialized, and will read inputs from UniquePersonListHelper. Since UniquePersonListHelper is empty, UniquePersonList will also be empty. This is shown in the figure below.

SortedListStartingStateListAndTreeDiagram

Step 2. The user executes add n/David …​ command, which calls Model#addPerson(), to add a new person. The new person will be added to UniquePersonListHelper, and UniquePersonList will take reference from UniquePersonListHelper. This is shown in the figure below.

SortedListCommand1TreeStateDiagram

UniquePersonListHelper has the sorted order of person, and this sorted order will be copied into UniquePersonList. This is shown in the figure below.

SortedListCommand1ListStateDiagram

The following sequence diagram shows how the UniquePersonList stays sorted when an add command is executed:

SortedListSequenceDiagramCommandAdd

Step 3. The user executes add n/Aaron …​, which also calls Model#addPerson(), to add a new person. Like step 2, the new person will be added to UniquePersonListHelper. This is shown in the figure below.

SortedListCommand2TreeStateDiagram

UniquePersonList will take reference from UniquePersonListHelper, as shown in the figure below.

SortedListCommand2ListStateDiagram

Step 4. The user executes add n/Bella …​, which also calls Model#addPerson(), to add a new person. Because lexicographically, "B" comes before "D", person Bella, will be placed between Aaron and David. UniquePersonListHelper stores persons in a treemap, and the red-black tree underlying data structure of treemap, is able to handle this. The new person will be added to UniquePersonListHelper in a sorted order, as shown in the figure below.

SortedListCommand3TreeStateDiagram

UniquePersonList will take reference from UniquePersonListHelper, as shown in the figure below.

SortedListCommand3ListStateDiagram

Step 5. The user now decides that adding the person Bella was a mistake. Person Bella should not be in the AddressBook. The user wishes to delete the person Bella, by executing the delete 2 command. This calls Model#deletePerson(). The delete 2 command will check if Bella is a valid person, and if so, will delete the person Bella.

The red-black tree which is the underlying data structure of treemap, is able to handle this operation. It simply replaces the node it is about to delete, with the in-order successor. More operations will be done to ensure a balanced tree, within the underlying red-black tree. This is shown in the figure below.

SortedListCommand5TreeStateDiagram

UniquePersonList will take reference from UniquePersonListHelper, as shown in the figure below.

SortedListCommand5ListStateDiagram
If the Bella does not exist in UniquePersonListHelper,UniquePersonListHelper will return an error, and the delete command will not be executed.

The following sequence diagram shows how the UniquePersonList stays sorted when an delete command is executed: It is very similar to that of the add command.

SortedListSequenceDiagramCommandDelete

Step 6. The user then decides to execute the command list. Commands that do not modify the address book, such as list, will usually not call Model#addPerson(), Model#deletePerson(), Model#updatePerson(), Model#resetData(), or Model#hasPerson(). Thus the state of UniquePersonListHelper will remain unchanged. This is shown in the figure below.

SortedListCommand6TreeStateDiagram

Therefore, UniquePersonList will also remain unchanged, as shown in the figure below.

SortedListCommand6ListStateDiagram

Step 7. The user executes clear, which calls Model#resetData(). This replaces all data in the address book with an empty address book. Hence, UniquePersonListHelper will be cleared of all persons. This is shown in the figure below.

SortedListCommand7TreeStateDiagram

Therefore, UniquePersonList will also be cleared of all persons, as shown in the figure below.

SortedListCommand7ListStateDiagram

The following activity diagram summarizes what happens when a user executes a new command:

SortedListActivityDiagram

Design Considerations

Aspect: How the list is sorted
  • Alternative 1 (current choice): Implement a helper class, UniquePersonListHelper, which uses a treemap to sort the names. Clears the UniquePersonList every time a change is made, and iterates through the UniquePersonListHelper, to build a new UniquePersonList.

    • Pros: Easy to implement. Allows for minimal and compartmentalised changes throughout the code base. Fast overall time complexity of O(N).

    • Cons: May have performance issues in terms of memory usage, which can be complicated for you as a developer to rectify.

  • Alternative 2: Implement a comparator in the current UniquePersonList.

    • Pros: Will use less memory, because there is no need for a helper class or data structure.

    • Cons: It has a time complexity of O(N log N), which is slower than our chosen implementation.

Aspect: Defensive programming practices for helper class
  • Alternative 1 (current choice): Implement all checks for errors in the helper class, UniquePersonListHelper and none in UniquePersonList. This is because the helper class is in charge of the actual execution of the program. If the checks for errors are implemented in UniquePersonList only, it is possible for a new developer to accidentally bypass the checks.

    • Pros: Prevents unnecessary checks and hence, potentially confusing code for you as a developer.

    • Cons: If any changes are made to the helper class in the future, e.g. removing the helper class, you as a developer will have to remember to implement your own checks.

  • Alternative 2: Implement all checks for errors in both UniquePersonList and UniquePersonListHelper.

    • Pros: This would add an additional layer of defence to possible careless mistakes by developers in the future. E.g. If you were to make your own version of the helper class but forget to implement their own checks for errors, UniquePersonList would still have backup checks.

    • Cons: Introducing redundant checks, which would be misleading, This makes code harder to understand. Redundant checks might also incorrectly encourage careless programing habits for you as a developer.