Reduce cyclomatic complexity in clustering, encoding, and leader_election

Simplify complex functions to improve readability and maintainability:
- clustering.ex: Convert case statement to map lookup for cluster radii
- encoding.ex: Extract codepoint validation into separate function clauses
- leader_election.ex: Extract PID liveness checking and cleanup logic into helpers

Co-Authored-By: Graham <noreply@anthropic.com>
This commit is contained in:
Graham McIntire 2026-02-09 11:57:50 -06:00
parent 48be64583a
commit d48a3a291f
No known key found for this signature in database
3 changed files with 69 additions and 70 deletions

View file

@ -186,53 +186,53 @@ defmodule Aprsme.Cluster.LeaderElection do
defp cleanup_stale_registrations do
case :global.whereis_name(@election_key) do
:undefined ->
# No registration exists
:ok
pid when is_pid(pid) ->
pid_node = node(pid)
connected_nodes = [node() | Node.list()]
# Check if the PID's node is still connected
if pid_node in connected_nodes do
# Node is connected, try to check if process is alive
try do
if pid_node == node() do
# Local PID - use Process.alive?
if Process.alive?(pid) do
:ok
else
Logger.info("Cleaning up stale leader registration for dead local process #{inspect(pid)}")
:global.unregister_name(@election_key)
end
else
# Remote PID - use RPC to check if alive
case :rpc.call(pid_node, Process, :alive?, [pid]) do
true ->
:ok
false ->
Logger.info("Cleaning up stale leader registration for dead remote process #{inspect(pid)}")
:global.unregister_name(@election_key)
{:badrpc, _reason} ->
Logger.info("Cleaning up stale leader registration for unreachable process #{inspect(pid)}")
:global.unregister_name(@election_key)
end
end
rescue
_error ->
Logger.info("Cleaning up stale leader registration for problematic process #{inspect(pid)}")
:global.unregister_name(@election_key)
end
else
# Node is disconnected - clean up the registration
Logger.info("Cleaning up stale leader registration for disconnected node #{pid_node}")
:global.unregister_name(@election_key)
end
check_and_cleanup_registration(pid)
end
end
defp check_and_cleanup_registration(pid) do
pid_node = node(pid)
connected_nodes = [node() | Node.list()]
if pid_node in connected_nodes do
check_pid_liveness(pid, pid_node)
else
cleanup_registration("disconnected node #{pid_node}")
end
end
defp check_pid_liveness(pid, pid_node) do
if is_pid_alive?(pid, pid_node) do
:ok
else
reason = if pid_node == node(), do: "dead local process", else: "dead remote process"
cleanup_registration("#{reason} #{inspect(pid)}")
end
rescue
_error ->
cleanup_registration("problematic process #{inspect(pid)}")
end
defp is_pid_alive?(pid, pid_node) when pid_node == node() do
Process.alive?(pid)
end
defp is_pid_alive?(pid, pid_node) do
if :rpc.call(pid_node, Process, :alive?, [pid]) do
true
else
false
end
end
defp cleanup_registration(reason) do
Logger.info("Cleaning up stale leader registration for #{reason}")
:global.unregister_name(@election_key)
end
defp get_cluster_wide_status do
all_nodes = [node() | Node.list()]

View file

@ -155,25 +155,21 @@ defmodule Aprsme.Encoding do
@spec valid_grapheme?(String.grapheme()) :: boolean()
defp valid_grapheme?(grapheme) do
case String.to_charlist(grapheme) do
[cp] ->
# Allow tab (0x09), newline (0x0A), carriage return (0x0D)
# Remove other control characters
case cp do
9 -> true
10 -> true
13 -> true
c when c >= 0 and c <= 31 -> false
127 -> false
c when c >= 128 and c <= 159 -> false
_ -> true
end
_ ->
# Multi-codepoint grapheme, keep it
true
[cp] -> valid_codepoint?(cp)
_ -> true
end
end
# Allow tab (0x09), newline (0x0A), carriage return (0x0D)
# Remove other control characters
defp valid_codepoint?(9), do: true
defp valid_codepoint?(10), do: true
defp valid_codepoint?(13), do: true
defp valid_codepoint?(127), do: false
defp valid_codepoint?(c) when c >= 0 and c <= 31, do: false
defp valid_codepoint?(c) when c >= 128 and c <= 159, do: false
defp valid_codepoint?(_), do: true
@spec find_invalid_byte_position(binary()) :: non_neg_integer() | nil
defp find_invalid_byte_position(input) do
input

View file

@ -46,21 +46,24 @@ defmodule Aprsme.Packets.Clustering do
Calculates the clustering radius in degrees based on zoom level.
Lower zoom levels get larger radii for more aggressive clustering.
"""
# Clustering radius lookup table by zoom level
# More aggressive scaling for better separation at higher zooms
# Zoom 1: ~5 degrees, Zoom 5: ~0.3 degrees, Zoom 8: ~0.04 degrees
@cluster_radii %{
1 => 5.0,
2 => 2.5,
3 => 1.25,
4 => 0.625,
5 => 0.3125,
6 => 0.15625,
7 => 0.078125,
8 => 0.0390625
}
@default_radius 0.0390625
@spec calculate_cluster_radius(integer()) :: float()
def calculate_cluster_radius(zoom) do
# More aggressive scaling for better separation at higher zooms
# Zoom 1: ~5 degrees, Zoom 5: ~0.3 degrees, Zoom 8: ~0.04 degrees
case zoom do
1 -> 5.0
2 -> 2.5
3 -> 1.25
4 -> 0.625
5 -> 0.3125
6 -> 0.15625
7 -> 0.078125
8 -> 0.0390625
_ -> 0.0390625
end
Map.get(@cluster_radii, zoom, @default_radius)
end
# Filter packets with valid lat/lon coordinates