This portfolio showcases my ability to produce functional code and documentation, for a team-based NUS software engineering project.
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
andStorage
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:
Contributions to the User Guide
The following shows my contributions to the user guide, in relation to functions |
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..
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
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 toUniquePersonList
, and hence the contact list -
UniquePersonList#remove()
— Removes a new person toUniquePersonList
, and hence the contact list -
UniquePersonList#setPerson()
— Sets a new person, in place of an existing person, toUniquePersonList
, and hence the contact list -
UniquePersonList#setPersons()
— Sets a list of persons, in place of the current list of persons, toUniquePersonList
, and hence the contact list -
UniquePersonList#contains()
— Checks if a person is already a part ofUniquePersonList
, 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.
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.
UniquePersonListHelper
has the sorted order of person, and this sorted order will be copied into
UniquePersonList
. This is shown in the figure below.
The following sequence diagram shows how the UniquePersonList
stays sorted when an add
command is executed:
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.
UniquePersonList
will take reference from UniquePersonListHelper
, as shown in the figure below.
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.
UniquePersonList
will take reference from UniquePersonListHelper
, as shown in the figure below.
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.
UniquePersonList
will take reference from UniquePersonListHelper
, as shown in the figure below.
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.
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.
Therefore, UniquePersonList
will also remain unchanged, as shown in the figure below.
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.
Therefore, UniquePersonList
will also be cleared of all persons, as shown in the figure below.
The following activity diagram summarizes what happens when a user executes a new command:
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 inUniquePersonList
. This is because the helper class is in charge of the actual execution of the program. If the checks for errors are implemented inUniquePersonList
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
andUniquePersonListHelper
.-
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.
-