Skip to content
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

pass local values in bulk in bus code #789

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft

Conversation

panentheos
Copy link
Collaborator

Summary of changes

The sign update loop follows a common pattern of:

  1. Fetch a bunch of data
  2. Compute sign messages from the data
  3. Apply the messages

The message algorithm is fairly complex, so it's broken into several functions to make it easier to manage. However, this requires passing the relevant data through those functions, which can be verbose, especially in the bus code.

This introduces a map of "locals" to contain the relevant data, which is then passed through the function tree in bulk. The idea is similar to the conn data structure from the plug library, acting as an accumulator for temporary, contextual data. Call sites are significantly cleaner, and destructuring in the function definitions provides an indication of what's actually in use at each level.

Copy link

Coverage of commit a831667

Summary coverage rate:
  lines......: 73.4% (2061 of 2808 lines)
  functions..: 75.3% (568 of 754 functions)
  branches...: no data found

Files changed coverage rate:
                                                             |Lines       |Functions  |Branches    
  Filename                                                   |Rate     Num|Rate    Num|Rate     Num
  =================================================================================================
  lib/signs/bus.ex                                           |84.0%    294|95.7%    46|    -      0

Download coverage report

Copy link
Contributor

@digitalcora digitalcora left a comment

Choose a reason for hiding this comment

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

Interesting approach. Normally I'd object to passing more data into a function than it strictly needs, but in this context it's a pretty weak objection — I agree there's a decent readability improvement here. Curious what others think.

lib/signs/bus.ex Outdated Show resolved Hide resolved
lib/signs/bus.ex Outdated
@@ -168,22 +178,24 @@ defmodule Signs.Bus do
|> filter_predictions(current_time, state)
|> Enum.group_by(&{&1.stop_id, &1.route_id, &1.direction_id})

locals = %{
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm trying to think of a more descriptive name for this... "locals" sort of tells you what its function is, but not what it is. Maybe something like MessageContext — it's context used to create sign messages.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I went back and forth on this a bunch. Ultimately it's not just "messages", but also "announcements", and perhaps other things. context works, but it's really generic. locals felt decent because it describes the lifecycle of the data, without being prescriptive about what's in there.

Copy link
Contributor

@digitalcora digitalcora Jun 28, 2024

Choose a reason for hiding this comment

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

I think part of the reason I'm not a fan of locals is because it implies "local variables", which are a specific thing, and this isn't quite that thing (you could say it's... non-local variables?). context, while similarly generic, doesn't have this overlap. But don't feel like you have to change it on my preference alone 🙂

lib/signs/bus.ex Outdated
state
) do
@spec static_text_content(locals(), t()) :: content_values()
defp static_text_content(%{config: config} = locals, state) do
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think of extracting the functions which accept this "locals" value into their own module? Sort of like a "view" for which the main module is the "controller". I think having some split of responsibilities here would make both parts easier to understand (and easier to test?) vs. all being in one mega-module, even if the "view" logic is still a high proportion. (I'm not sure off-hand how much of the state value the "view" functions use, but maybe the parts that are needed could be copied into the "locals" value to reduce coupling between the modules.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Part of the motivation for streamlining this was to avoid the temptation to move things around. By their nature, these functions are very tightly coupled together with the run_loop, via the data flow (which is why this pattern works at all). Moving some of them to another file would just mean having to jump around to multiple places to follow the logic. By the same token, the particular function boundaries are kind of arbitrary, so I'd hesitate to crystallize those internal APIs with individual test cases. The fact that the bus tests all exercise the full run_loop is what made it possible to rearrange the guts without having to rewrite a bunch of tests as well.

Copy link

Coverage of commit 07e0133

Summary coverage rate:
  lines......: 73.4% (2061 of 2809 lines)
  functions..: 75.3% (568 of 754 functions)
  branches...: no data found

Files changed coverage rate:
                                                             |Lines       |Functions  |Branches    
  Filename                                                   |Rate     Num|Rate    Num|Rate     Num
  =================================================================================================
  lib/signs/bus.ex                                           |83.7%    295|95.7%    46|    -      0

Download coverage report

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