-
Notifications
You must be signed in to change notification settings - Fork 554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Scala 2.13: Refactor LiveBlogCurrentPage.findPageWithBlock()
#25338
Scala 2.13: Refactor LiveBlogCurrentPage.findPageWithBlock()
#25338
Conversation
`LiveBlogCurrentPage.findPageWithBlock()` is responsible for making sure that when a user clicks on a live-blog permalink to a **block**, eg: https://www.theguardian.com/politics/live/2022/aug/01/tory-leadership-race-rishi-sunak-lizz-truss-vote-keir-starmer-uk-politics-live?page=with:block-62e7e89d8f08730a1f7f5579#block-62e7e89d8f08730a1f7f5579 ...the user is taken to the correct 'page' of the live blog - page 3 of 5 in the example above, for instance. The code was was first introduced in January 2016 with #11700, subsequently updated with #13566, etc. The existing code compiles without warnings under Scala 2.12, but the Scala 2.13 compiler gives a warning that not _all_ conceivable cases in the pattern-match are handled: ``` frontend/common/app/model/LiveBlogCurrentPage.scala:190:12: match may not be exhaustive. [warn] It would fail on the following inputs: List(_), Nil [warn] .map { [warn] ^ ``` In this particular case, the warning's unnecessary - the preceding `.sliding(3)`: https://github.com/guardian/frontend/blob/f90c8a58e6f0941c96392d14fe27e61a4dbaf25b/common/app/model/LiveBlogCurrentPage.scala#L188 ...means that the `List` supplied to the case match expression will _always_ have 3 elements, and that's precisely the case that is handled - but there's no way for the compiler to know that - so we as devs need to do _something_ to make the warning go away! Some options: * Use the @unchecked annotation (see https://www.scala-lang.org/api/2.12.7/scala/unchecked.html) This is an option of last resort - turning off compiler checks leaves us exposed to runtime errors and is generally a bad idea! * Use `collect()` rather than `map()` - this accepts a partial function, rather than a total one, so the compiler error will go away. https://www.scala-lang.org/api/2.12.7/scala/collection/immutable/Seq.html#collectFirst[B](pf:PartialFunction[A,B]):Option[B] * Refactor so that the code no longer needs to assume that a `List` type (which can have any length) has length `3`. In the end I decided to go with the refactor, because there were a few things about the existing code that could be tweaked: * Unnecessary work: The method was creating a `LiveBlogCurrentPage` for _every_ page in the liveblog, even though it only ever needed one (the single page that the block actually exists on!), and would eventually throw away the rest. * The logic around padding the front & end of the `pages` List with two `None` entries, to allow extracting the `newer` & `older` pages, totally worked but added an extra step to the code (and therefore a bit of complexity for humans to understand: "what are endedPages?") and could be replaced by just incrementing/decrementing the page index for the one `LiveBlogCurrentPage` we're now creating. * Various variables could be inlined to the point of creation on the `LiveBlogCurrentPage` case class. By using named parameters in constructing the case class, no clarity is lost, and the code is more concise. See also: * The Scala 2.13 upgrade PR: #25190
LiveBlogCurrentPage.findPageWithBlock()
isRequestedBlock = "3", | ||
requestedBlockId = "3", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The old variable name of isRequestedBlock
sounds like a boolean to me, but this value is a string block id, like block-62e81de78f08c00147328d8f
!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a good refactor - requestedBlockId
makes a lot more sense!
filterKeyEvents: Boolean, | ||
topicResult: Option[TopicResult], | ||
): Option[LiveBlogCurrentPage] = { | ||
val pinnedBlock = blocks.find(_.attributes.pinned).map(renamePinnedBlock) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to calculate pinnedBlock
unless we're actually going to return a LiveBlogCurrentPage
instance!
val filteredBlocks = applyFilters(blocks, filterKeyEvents, topicResult) | ||
val (mainPageBlocks, restPagesBlocks) = getPages(pageSize, filteredBlocks) | ||
val newestPage = FirstPage(mainPageBlocks, filterKeyEvents, topicResult) | ||
val pages = newestPage :: restPagesBlocks.zipWithIndex | ||
.map { | ||
case (page, index) => | ||
// page number is index + 2 to account for first page and 0 based index | ||
BlockPage(blocks = page, blockId = page.head.id, pageNumber = index + 2, filterKeyEvents, topicResult) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above chunk was factored out into the new pagesFromBodyBlocks()
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 All of this makes sense & seems to be a good refactor - definitely was easier to understand for me than the existing code!
Thanks! |
Seen on PROD (merged by @rtyley 13 minutes and 13 seconds ago)
|
Deployed and permalinks still work! 👍 Screen.Recording.2022-08-04.at.11.49.21.mov |
What does the code that's being refactored do?
LiveBlogCurrentPage.findPageWithBlock()
is responsible for making sure that a user gets taken to the correct page of a liveblog when they click on a permalink to a liveblog block, eg with:...the user is taken to the correct 'page' of the live blog:
3 of 5
for the example above:The code was first introduced in January 2016 with #11700, subsequently updated with #13566, etc.
What's the problem?
The existing code compiles without warnings under Scala 2.12, but the Scala 2.13 compiler gives a warning that not all conceivable cases in the pattern-match are handled:
The warning is unfortunately unwanted noise - as the preceding
.sliding(3)
:frontend/common/app/model/LiveBlogCurrentPage.scala
Line 188 in f90c8a5
...means that the
List
supplied to the case match expression will always have 3 elements, and that's precisely the case that's handled - but there's no way for the compiler to know that - so we as devs need to do something to make the warning go away!What's the fix?
Some options:
collect()
rather thanmap()
- this accepts a partial function, rather than a total one, so the compiler error will go away.List
type (which could have any length) has length3
.In the end I decided to go with the refactor, because there were a few things about the existing code that could be tweaked:
LiveBlogCurrentPage
for every page in the liveblog, even though it only ever needed one (the single page that the block actually exists on!), and would eventually throw away the rest.pages
List with twoNone
entries, to allow extracting thenewer
&older
pages, adds an extra step to the code (and therefore a bit more complexity for humans to understand: "what areendedPages
?"):frontend/common/app/model/LiveBlogCurrentPage.scala
Lines 181 to 191 in e978942
LiveBlogCurrentPage
we're creating.pinnedBlock
) could be inlined to the point of creation on theLiveBlogCurrentPage
case class. By using named parameters in constructing the case class, clarity's retained while the code becomes more concise.Testing
The code is covered by unit tests in
LiveBlogCurrentPageTest
, with additional test coverage added recently in #25118.Here's a video showing that PROD permalinks still work on CODE when this branch is deployed:
Screen.Recording.2022-08-03.at.17.42.57.mov
See also: