CS235 Unit 05 Exercise: Exception handling

Table of Contents


1. Review topics

1.1. Class declarations

A class declaration looks like this:

class NAME
{
  public:
  private:
};

Declarations go in the .h file.

1.2. Private member variables

Member variables of a class should almost always be private. I often prefix my member variables with m_.

We make variables private in order to protect our data. We can:

  • Add error checking before overwriting a variable's value.
  • Add formatting to assemble and format data before returning it to the user.
  • Protect the variables from that intern that keeps messing up all the code. :)

1.3. Get and Set functions

Each private member variable that we want to be able to access outside of the class is going to need a Get function and a Set function.

Let's say we have the private member variable:

CLASSNAME.h

TYPE m_var;

Its basic get/set functions will be:

CLASSNAME.cpp

TYPE CLASSNAME::GetVar()
{
  return  m_var;
}

void CLASSNAME::SetVar( TYPE newValue )
{
  m_var = newValue;
}

So for integer variables:

EXAMPLE.h

// .h file, within the clsas declaration:
private:
int m_id;

EXAMPLE.cpp

// .cpp file:
int CLASSNAME::GetId()
{
  return  m_id;
}

void CLASSNAME::SetId( int newValue )
{
  m_id = newValue;
}

And string variables:

EXAMPLE.h

// .h file, within the class declaration:
private:
std::string m_name;

EXAMPLE.cpp

// .cpp file:
std::string CLASSNAME::GetName()
{
  return m_name;
}

void CLASSNAME::SetName( std::string newValue )
{
  m_name = newValue;
}

The Get and Set functions will need to be implemented for each private member variable in the ticket.

You will also be adding if statements to check for errors and throw exceptions where relevant (see rest of documentation).

1.4. Namespaces

Each sectino of the program has its own namespace. A namespace is a way we can mark regions of code together, as well as to help protect from naming conflicts.

For your .h and .cpp file you'll need both to have:

namespace NAMESPACE
{
}

Surrounding both your class declaration AND your function definitions. AREA is going to be related to whether you're working in the Music, Podcast, or Audiobook region.

(So, namespace Music, namespace Podcast, or namespace Audiobook.)

Within the MenuApp file, you will declare your variable like this:

std::vector< NAMESPACE::CLASSNAME > m_listOfThings;

So, if I were implementing a vector of Playlist objects, with the Playlist being part of the User namespace, it would look like this:

std::vector< User::Podcast > m_podcasts;

1.5. #include-ing your code in MenuApp.h

Since our code is within folders, you will need to point the MenuApp.h file back a directory to get to your namespace folder and your file.

For instance:

#include "../Namespace_Users/Playlist.h"

1.6. Figuring out what to do

Look at existing code to help you figure out what to do and how to do it. In particular, I've already implemented the User class and added it to the MenuApp.

Relevant files to look at:

  • Namespace_User/User.h
  • Namespace_User/User.cpp
  • Namespace_Application/MenuApp.h
  • Namespace_Application/MenuApp.cpp

Also make sure to watch the video step-through below.


2. Working with the codebase

Each student has been assigned to one of several codebases. They're all the same base application - a music app, similar to Spotify (sorry, it won't actually play music, we're just managing data), but four clones to accommodate the amount of students in the course.

Fall 2023 teams

Within each codebase there are 9 main areas, each student will take ownership of one area:

Category Subcategory
Music Album
Music Track
Music Artist
Audiobook Book
Audiobook Chapter
Audiobook Author
Podcast Show
Podcast Episode
Podcast Creator
How to choose your area?
Most students will want to choose one area and stick to it, though it is also possible to jump between different areas of the codebase as you take on different tickets. Make sure to coordinate with your teammates to see if anyone wants to trade areas - You don't want to eat anybody's lunch. :)
More information about the codebase and workflow
All the documentation about the repository, workflow, and codebase will be updated here: https://moosadee.gitlab.io/courses/wip_exercises/c3_NavigatingCodebase.html

3. Video step-throughs


4. Finding a ticket for this assignment

On the Repository Page on GitLab there is a section called Issues, where you can find requirements tickets.

4.1. Assigning a ticket to yourself

Go to the Issues page on your team's repository and find a ticket that has not yet been assigned to anybody.

  1. Click on the ticket to open it.
  2. Under the "0 Assignees" section on the right-hand pane, click on the assign yourself link.

5. Getting ready to work on the assignment - Creating a clean branch

When working on a new assignment, make sure to do the following:

  1. Make sure you've ADDED, COMMITTED, AND PUSHED all changes to your current branch, as applicable.
  2. git checkout main to checkout the main branch. ( NOTE: Your repository might use master as the primary branch instead! )
  3. git pull to grab the latest changes from the server.
  4. Everything merged and updated? Good, now continue…
  5. git checkout -b name_assignment to create a new branch to work on for the assignment. For example: rsingh13_u07ex.
  6. Now you're good to start working!

6. About: Exception handling

Make sure you've gone over the reading and video resources on Exceptions to understand them better. But here's a summary:

throw
If a function can cause problems such as a crash, you will want to use an if statement to check for an invalid state.

If that invalid state is detected then we throw an exception. For example: throw invalid_argument( "Cannot divide by 0!" );

try
In a different location in the code, we will be calling that "problematic function". In this case, we want to start listening for any exceptions it might throw. To do this, we wrap the function call within a try{} block.
catch
Immediately after the try{} block, we will add one or more catch blocks. These catch statements will listen for specific exceptions, and its internal code will deal with resolving the invalid state.

6.1. Example 1: Don't divide by 0!

float Divide( float num, float denom )
    {
        if ( denom == 0 )
        {
            throw std::invalid_argument( "Division by 0 not allowed!" );
        }

        return num / denom;
    }

void Program1()
  {
    cout << "Enter numerator and denominator: ";
    float num, denom;
    cin >> num >> denom;

    try
    {
      quotient = Divide(numerator, denominator);
      cout << "Quotient: " << quotient << endl;
    }
    catch (const std::invalid_argument& ex)
    {
      cout << "invalid_argument Exception: " << ex.what() << endl;
    }
  }

6.2. Example 2: Don't go out of bounds in an array!

  void Display(std::vector<std::string> arr, int index)
  {
      if (index < 0 || index >= arr.size())
      {
          throw std::out_of_range("Invalid index!");
      }

      std::cout << "Item at index " << index << " is " << arr[index] << std::endl;
  }

void Program2()
{
  std::vector<std::string> myArray = { "cat", "bat", "rat", "gnat", "goat" };

  cout << "Display item at which index? ";
  int index;
  cin >> index;

  try
  {
    Display(myArray, index);
  }
  catch (const std::out_of_range& ex)
  {
    cout << "out_of_range Exception: " << ex.what() << endl;
  }
}

7. Working on the assignment

The ticket will ask you to create a Class as well as to implement some functionality within the MenuApplication class, adding menus to interface with your class objects.

Each ticket has two main parts - creating a class, and implementing some basic functionality to store class objects in the program. Here's some more information about doing this.

7.1. Example ticket text

Here is an example of the ticket text, but for a different class.

Within the Namespace_User/ directory, work with the `User.h` and `User.cpp` files to create a User class.

Attributes:

  • user id (integer)
  • name (string)
  • playlist ids (vector of ints)

Functionality:

  • Get and Set functionality for the ID and name attributes.
  • AddPlaylistId functionality to add a new id to the author ids list
  • GetPlaylistIds to retrieve the vector of integers of ids

Also, implement this class into the program, under Namespace_Application/ in `MenuApp.h` and `MenuApp.cpp`:

  • Update the `MenuApplication` class to contain a vector of User objects.
  • Create one or more submenu(s) to allow the user to do the following:
    • Create a new User.
    • View a list of all User in the vector.
    • Update an existing User.

Example output:

CREATE USER
Enter name of user: RW
User created at ID 1


EDIT USERS
Here are the available users:
0. RaiS, PLAYLISTS: 3
1. RW, PLAYLISTS: 0

Edit which ID? 1

Do which of the following?
1. Edit name
2. Add playlist ID


VIEW USERS
NAME: RaiS
ID: 0
PLAYLIST IDs: 454, 139

(etc.)

7.2. Implementing the example class

Task: Reference the ticket text to create a new class (or edit the stub class). Add the appropriate variables and functions. You can add additional "helper" functions as needed.

Note that you aren't implementing the User class for this assignment! The class and functionality you implement will be based on the ticket you pull!

My implementation of the User class looks like the following:

#ifndef _USER
#define _USER

#include <string>
#include <vector>

namespace User
{

class User
{
public:
    void SetId( int newId );
    void SetName( std::string newName );
    void AddPlaylistId( int newPlaylistId );

    int GetId();
    std::string GetName();
    std::vector<int> GetPlaylistIds();

private:
    int m_userId;
    std::string m_name;
    std::vector<int> m_playlistIds;

    friend class UserTester;
};

}

#endif

The required attributes were the user id, name, and playlist IDs, which are the private member variables I've created. Then, I've also created Get/Set functions for ID and Name. Finally, since the class stores a vector of playlist IDs - basically, the playlists that the user has created - there is functionality to Add a new Playlist ID to that list, or to Get all Playlist IDs.

Then the implementation of the functions look like this:

#include "User.h"

namespace User
{

void User::SetId( int newId )
{
    m_userId = newId;
}

void User::SetName( std::string newName )
{
    m_name = newName;
}

void User::AddPlaylistId( int newPlaylistId )
{
    m_playlistIds.push_back( newPlaylistId );
}

int User::GetId()
{
    return m_userId;
}

std::string User::GetName()
{
    return m_name;
}

std::vector<int> User::GetPlaylistIds()
{
    return m_playlistIds;
}

}

The implementation of the basic features here are pretty simple. However, this is not complete! You will need to also add Exception throwing in the functions where appropriate.

Task: Add exception handling: Any functions that deal with IDs passed in as parameters, make sure that ID is NOT negative. If it is negative, then throw an invalid_argument exception.

7.3. Adding the example class to the program

Task: Add a vector of your class item to the MenuApp class. You will also add the required functionality (add, edit, view) as per outlined in the ticket. Make sure to also update Menu_Main() to create menu options for the user to access the new functionality.

Next, we need to add the class to the MenuApplication, which is located within Namespace_Application/MenuApp.h and the corresponding .cpp file.

Within the class declaration, we will need to have a vector of the class we're working with, as well as new functions to handle the required functionality:

class MenuApplication
{
public:
  void Run();
  void Setup();
  void Cleanup();

private:
  void Menu_Main();

  std::vector<User::User> m_users;
  void Menu_Users_Add();
  void Menu_Users_Edit();
  void Menu_Users_View();
};

We will also need to update the definition of the Menu_Main() function so that we can access our new menus via the menu:

void MenuApplication::Menu_Main()
{
    bool menuDone = false;
    while ( !menuDone )
    {
        Utilities::Helper::Header( "Main menu" );

        // OPTIONS
        std::cout << "0. QUIT" << std::endl;
        std::cout << "1. Users - Add new" << std::endl;
        std::cout << "2. Users - Edit existing" << std::endl;
        std::cout << "3. Users - View all" << std::endl;

        // Get user selection, make sure to set the MIN and MAX values.
        int choice = Utilities::Helper::GetIntInput( 0, 3 );

        switch( choice )
        {
          case 0: menuDone = true; break;
          case 1: Menu_Users_Add(); break;
          case 2: Menu_User_Edit(); break;
          case 3: Menu_Users_View(); break;
        }
    }
}


void MenuApplication::Menu_Users_Add()
{
  Utilities::Helper::Header( "Users - Add" );

  std::string name;
  std::cout << "Enter name of user: ";
  std::cin.ignore();
  getline( std::cin, name );

  User::User newUser;
  newUser.SetName( name );
  newUser.SetId( m_users.size() );
  m_users.push_back( newUser );

  std::cout << "New user created at ID " << newUser.GetId() << std::endl;
}

void MenuApplication::Menu_Users_Edit()
{
  Utilities::Helper::Header( "Users - Edit" );

  for ( size_t i = 0; i < m_users.size(); i++ )
  {
    std::cout << i << ". " << m_users[i].GetName() << ", PLAYLISTS: " << m_users[i].GetPlaylistIds().size() << std::endl;
  }

  std::cout << std::endl << "Edit which ID? ";
  int id;
  std::cin >> id;

  std::cout << "Do which of the following? ";
  std::cout << "1. Edit name" << std::endl << "2. Add playlist ID" << std::endl;
  int choice;
  std::cin >> choice;

  if ( choice == 1 )
  {
    std::string name;
    std::cout << "Enter new name: ";
    getline( std::cin, name );
    m_users[id].SetName( name );
  }
  else if ( choice == 2 )
  {
    int playlistId;
    std::cout << "Entert playlist ID: ";
    std::cin >> playlistId;
    m_users[id].AddPlaylistId( playlistId );
  }
}

void MenuApplication::Menu_Users_View()
{
  Utilities::Helper::Header( "Users - View" );
  for ( size_t i = 0; i < m_users.size(); i++ )
  {
    std::cout << i << ". " << m_users[i].GetName() << ", PLAYLISTS: " << m_users[i].GetPlaylistIds().size() << std::endl;
  }
}

You will also need to add try/catch statements within this class' functions, where appropriate.

Task: Given your class and whichever functions contain a throw statement, from within the MenuApp functions, wrap those class function calls in a try{} block and add a catch to listen to the related exception type.


8. Getting the assignment ready for turn-in - Creating a merge request

Before turning in the assignment you will need to do the following:

Once you're done working on your part of the assignment, make sure you've pushed your latest changes to your branch:

  • ON PC (Git Bash):
    1. git add .
    2. git commit -m "description"
    3. git push -u origin BRANCHNAME

Then go to the GitLab repository page. There is usually a box that will pop up and ask if you want to create a Merge Request, or you can do the following:

  • ON WEB (GitLab):
    1. Select Merge requests.
    2. Select New merge request.
    3. Under Source branch, select your branch. Under Target branch, select main
    4. Select Compare branches and continue.
    5. On the New merge request screen, do the following:
      1. Set a descriptive Title - what is the assignment? What area of the codebase did you update?
      2. Add a Description to help your classmates understand what changed. ALSO ADD THE URL TO THE RELATED ISSUE
      3. Click Create merge request.
    6. Once you've created a merge request, copy the URL to this page and ask a classmate to review your code.
    7. Get at least one person to sign off on your code before submitting the assignment to Canvas! (Copy the URL to the merge request and submit it as your submission on Canvas.)

c3_NavigatingCodebase_MergeRequest.png


9. Helping out - Reviewing someone else's merge request

c3_NavigatingCodebase_Commenting.png

Please make sure to review another student's merge request when they ask, whenever you have time. Once you have a URL to a merge request, do the following:

  • ON WEB (GitLab):
    1. Click on the Changes tab to view all the file changes.
    2. Navigate to any relevant files, such as C++ source files (ignore project files!)
    3. Skim through the code and take note of:
      1. Does it look clean and easy to read?
      2. Does the logic seem to be valid?
    4. Make comments on parts of the code as-needed. You can click on a line of the code and a comment box will pop up, which will help everyone see what you're talking about.
    5. Suggested changes: If you think something should be fixed or cleaned up in the teammate's code, make a note of it and let them know you've reviewed it. Don't click on approve.
    6. No suggested changes: Make sure to leave a comment of approval, or click the Approve button if it is available.
    7. Let your teammate know when you're done reviewing it.

10. Turning in your work on Canvas

After you've gotten an approved code review to your merge request, then do the following:

  1. Copy the URL to your merge request.
  2. On Canvas, locate the Unit 05 Exercise for Exceptions. Open it up and click Start Assignment.
  3. Paste your merge request URL in and then Submit Assignment.

Author: Rachel Wil Sha Singh

Created: 2023-09-27 Wed 17:42

Validate