Skip to content

Conversation

@theomoondev
Copy link

@theomoondev theomoondev commented Mar 9, 2020

Assignment Submission: Hotel

Congratulations! You're submitting your assignment. Please reflect on the assignment with these questions.

Reflection

Question Answer
What was a design challenge that you encountered on this project? Because I tried to limit dependencies between classes as much as possible, the FrontDesk#find_available_room method was very difficult to code. This method is very expensive because it iterates through two arrays of unknown size (time complexity is O(n^2)) and it creates a new array in memory each time it adds a room to the unavailable_rooms list (space complexity is O(n)). Perhaps if I added a Room class that could look up its Reservations and changed the Block class so it could look up its Reservations and Rooms, I could simply this method.
What was a design decision you made that changed over time over the project? My original design did not have a DateRange class (instead, all of its methods were in the Reservation class). I wanted to learn for myself why it was necessary and whether my design could be changed without too much difficulty. After completing Waves 1 & 2, I realized that I could not complete Wave 3 without it, as I needed to create Blocks that had a DateRange without creating a new Reservation.
What was a concept you gained clarity on, or a learning that you'd like to share? After watching Sandi Metz's talk on the SRP, I gained more clarity on how to avoid dependencies by creating code that is loosely coupled, highly cohesive, easily composable, and context independent. I also tried practicing the idea of "resistance as a resource" by not being attached to my first idea, embracing the friction, and fixing the problem by examining my design. However, I don't think my FrontDesk class follows the SRP very well.
What is an example of a nominal test that you wrote for this assignment? What makes it a nominal case? For Reservation#total_cost, accurately calculates the total cost for single rooms with the default rate. This is a nominal case because it tests a behavior that I expect during most calls of the #total_cost method.
What is an example of an edge case test that you wrote for this assignment? What makes it an edge case? For FrontDesk#find_available_room, throws an exception if there are no rooms available. This is an edge case because it tests a behavior that will only occur when the #find_available_method is called under extreme circumstances (i.e., when all the rooms in the hotel are booked during the given date range).
How do you feel you did in writing pseudocode first, then writing the tests and then the code? I did much better following this process in this project than in OO Ride Share. Following this process helped me complete Waves 1 & 2 without too many snags, although I did find myself adding some edge-case tests after coding the methods. During Wave 3, this process was crucial for completion.

saraleecodes added 30 commits March 2, 2020 13:47
…servations are being returned, refactors tests
…art_date before the range_start, adds test
saraleecodes added 27 commits March 8, 2020 16:27
…ncorrect, comments out erroneous functions and tests
…low for reservation of specific rooms in blocks
…n when a new block includes a date in an existing block
…rown if a new block includes a room from an existing block
…es the cost for rooms reserved from a hotel block
@kaidamasaki
Copy link

Hotel

Section 1: Major Learning Goals

Criteria yes/no, and optionally any details/lines of code to reference
Practices SRP by having at least two separate classes with distinct responsibilities, and test files for these two classes ✔️
Overall, demonstrates understanding instance variables vs. local variables. (There aren't unnecessarily too many instance variables, when it should be a local variable) ✔️
For each test file, tests demonstrate an understanding of instantiating objects properly, and using Arrange-Act-Assert ✔️
Practices pseudocode and TDD, and reflected on it by filling out the reflection questions ✔️
Practices git with at least 15 small commits and meaningful commit messages ✔️ (Very much so, great job!)

Section 2: Code Review and Testing Requirements

Criteria yes/no, and optionally any details/lines of code to reference
There is a class that represents a reservation, and a second class that holds/manages a collection of reservations through composition (instance variable) ✔️
The logic for checking if a reservation's date overlaps with another reservation's date is complex logic that is separated into method(s) (and potentially class(es)) ✔️
The logic for checking if a reservation's date overlaps with another reservation's date has unit tests ✔️
All of the given tests run and pass ✔️
A test coverage tool is installed and used, and shows 95% test coverage ✔️ 🎉 100% 🎉

Section 3: Feature Requirements

Feature Requirement: There is a method that... yes/no
gives back a list of rooms, and it's tested ✔️
creates a specific reservation for a room for a given date range, and it has nominal test cases ✔️
creates a specific reservation for a room for a given date range, and it tests an edge case, such as no available room, or invalid date range No edge case test. (You had an edge case test for reserve_block but not reserve_room.
gives back a list of reservations on a given date. Its tests include a test that makes several reservations for a given date ✔️
calculates the total price for a reservation ✔️
gives back a list of available rooms for a given date range, and it has nominal test cases ✔️
gives back a list of available rooms for a given date range, and it has edge test cases, such as no available room, or invalid date range ✔️
creates a block of rooms ✔️
reserves a room from a block ✔️

Overall Feedback

Overall Feedback Criteria yes/no
Green (Meets/Exceeds Standards) 14+ total in all sections ✔️
Yellow (Approaches Standards) 9-13 total in all sections
Red (Not at Standard) 0-8 total in all sections, or assignment is breaking/doesn’t run with less than 5 minutes of debugging

Additional Feedback

Great work overall! You've built your first project with minimal starting code. This represents an incredible milestone in your journey, and you should be proud of yourself!

I am particularly impressed by the way that you put so much thought into your design, that was really apparent from your answers to the comprehension questions. I was also impressed by how you set up your unit tests.

I do see some room for improvement around being a little too clever for your own good sometimes. Clever solutions are great but first make sure that a simple one won't do.

Code Style Bonus Awards

Was the code particularly impressive in code style for any of these reasons (or more...?)

Quality Yes?
Perfect Indentation
Elegant/Clever
Descriptive/Readable
Concise
Logical/Organized

Copy link

@kaidamasaki kaidamasaki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job! I've noted a few small things you can fix but this is an incredibly solid solution overall.

@@ -1,3 +1,4 @@
.DS_Store

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

end_date: end_date
)

raise ArgumentError.new("Block must contain between 2 and 5 rooms!") if @rooms.length < 2 || @rooms.length > 5

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd generally recommend raising errors like this before initializing fields. It doesn't matter here but can be helpful as constructors get more complex.

Also, it's helpful to include the bad argument when you raise an ArgumentError:

Suggested change
raise ArgumentError.new("Block must contain between 2 and 5 rooms!") if @rooms.length < 2 || @rooms.length > 5
raise ArgumentError.new("Block must contain between 2 and 5 rooms! Got: #{@rooms}") if @rooms.length < 2 || @rooms.length > 5

@@ -0,0 +1,17 @@
module Hotel
class Block

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too much indentation. You should configure VS Code to indent by two spaces.

Comment on lines +21 to +35
low = 0
high = self.nights

while low <= high
mid = (low + high) / 2
if (@start_date + mid) == date
return true
elsif (@start_date + mid) > date
high = mid - 1
elsif (@start_date + mid) < date
low = mid + 1
end
end

return false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cool but I'm pretty sure binary search is overkill here.

I think you can just do something like return @start_date < date && date < @end_date (after fixing the off by one error that's probably there...)

Comment on lines +27 to +38
def find_available_room(date_range)
unavailable_rooms = @reservations.map { |reservation|
reservation.room if reservation.date_range.start_date < date_range.end_date && reservation.date_range.end_date > date_range.start_date
}
@blocks.each { |block|
(block.rooms).each { |room| unavailable_rooms << room } if block.date_range.start_date < date_range.end_date && block.date_range.end_date > date_range.start_date
} # This method is very expensive. See refactors.txt for details.
available_rooms = @rooms - unavailable_rooms

raise ArgumentError.new("No rooms available for that date range!") if available_rooms.empty?
return available_rooms
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method isn't as expensive as you think. Or at least it's not much worse than an optimal solution.

For space complexity you can't do better than O(n) (because you might need to return an array of every room if they're all available).

For time complexity it's a little trickier. Using a clever data structure you can get individual reservation comparison down to O(log(n)) but that still only gets you O(n*log(n)).

If you use a data structure that O(r) where r is the number of days in all reservations then you can get the time down to O(rd) where d is the number of days in the input range but that uses a lot of space.

Off the top of my head I can't think of a solution that performs well in both space and time so this overall seems pretty reasonable. Some operations are just inherently expensive no matter how well you code your solution.

Comment on lines +45 to +46
start_date: date_range.start_date,
end_date: date_range.end_date

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably would have just stored the DateRange object itself here instead of breaking it apart.

end_date: end_date
)

raise ArgumentError.new("Invalid rate!") if rate.class != Float

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're going to check types you should probably use kind_of? to allow subclassing.

Suggested change
raise ArgumentError.new("Invalid rate!") if rate.class != Float
raise ArgumentError.new("Invalid rate!") unless rate.kind_of?(Float)

Comment on lines +19 to +28
it "is set up for specific attributes and data types" do
[:id, :rooms, :rate, :date_range].each do |attribute|
expect(@block).must_respond_to attribute
end

expect(@block.id).must_be_kind_of Integer
expect(@block.rooms).must_be_kind_of Array
expect(@block.rate).must_be_kind_of Float
expect(@block.date_range).must_be_kind_of Hotel::DateRange
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something confusing happened with your indentation here.

Comment on lines +16 to +23
it "is set up for specific attributes and data types" do
[:start_date, :end_date].each do |attribute|
expect(@date_range).must_respond_to attribute
end

expect(@date_range.start_date).must_be_kind_of Date
expect(@date_range.end_date).must_be_kind_of Date
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I think that you're mixing tabs and spaces and that's why weird things are happening.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've been having trouble with this for a while... I have the Tab Size set to 2 in VS Code, so it looks fine there. But when I commit my files, they are always formatted like this.

Oh wait, I just poked around in the VS Code settings and found a checkbox that says it'll override tabs with spaces. Hopefully that'll do the trick!

@theomoondev
Copy link
Author

theomoondev commented Mar 16, 2020

Great job! I've noted a few small things you can fix but this is an incredibly solid solution overall.

@kaidamasaki Thank you for all your feedback! By the way, your comment about making sure that there isn't a simpler solution first is so spot on. I tend to create extra challenges for myself in order to keep my mind engaged, so it's been a tough habit to break. I really appreciate your thoughtfulness and will remind myself of that in future projects!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants