refactor(device): split DaqifiStreamingDevice into focused collaborators (part of #344) - #422
Conversation
|
/agentic_review |
PR Summary by QodoSplit DaqifiStreamingDevice into internal operation collaborators
AI Description
Diagram
High-Level Assessment
Files changed (8)
|
Code Review by Qodo
1.
|
|
Round 1 addressed — finding accepted, stated mechanism corrected (40707ce). The six members Qodo named were exactly the six that were dead after the extraction, and they're removed. A refactor that leaves orphaned declarations behind isn't finished. They were not breaking the build, though. From-scratch rebuild on the reviewed commit: Also did the wider scan the finding asked for: no other unused members; Device file: 2,029 → 1,973 lines. Statement-multiset verification unchanged — same nine structural differences, zero logic changes, with The WiFi bench leg stays deferred — the bench unit's WiFi needs a power cycle and the hardware is unattended. /agentic_review |
|
WiFi leg complete — the last outstanding item on this PR. Ran over TCP at Kept it to a single connect on purpose. This is a pure refactor with no transport-specific logic, so the WiFi leg only needs to show the delegation didn't disturb the TCP path; the USB run is where the moved code actually gets exercised, including the SD download that came back byte-for-byte identical to the pre-refactor build. PR body updated — the deferred-WiFi caveat is replaced with the result. No code changed since the last review. /agentic_review |
…ors (part of #344) The SD-card, network-configuration, LAN-chip-info and diagnostics blocks move out of DaqifiStreamingDevice into internal collaborators built over the device's text-exchange primitive. The device keeps every public member and forwards. Collaborators reach the device through IDeviceOperationHost, implemented explicitly so it adds nothing to the public API. Every member of that seam forwards to the device member it names, which keeps the virtual ones virtual: subclasses that override ExecuteTextCommandAsync, ExecuteRawCaptureAsync, Send or IsUsbConnection still intercept the operations that moved. Also lifts four SCPI line predicates into the existing ScpiResponseClassifier, which several of the split blocks shared. Pure refactor: no public API change and no behavior change, verified by diffing the normalized statement multiset of the original file against the new ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… device The extraction moved these constants and the download gate into the collaborators but left the originals behind, where nothing references them. Removing them is what finishes the move. Note this was not breaking the build: an unreferenced const has no diagnostic at all, and CS0414 does not fire for a readonly field initialized with an object creation. A from-scratch build reports 0 warnings both before and after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
40707ce to
65c7733
Compare
Why
DaqifiStreamingDevicehad grown to 3,632 lines implementing five interfaces in one class. It's one of the two files in the repo where unrelated changes keep colliding — the SD work in #420 and any streaming change land in the same file for no reason other than that they happen to share a class.This is the device half of #344. The firmware half is #419.
What
The four blocks the issue calls out move into internal collaborators. The device keeps every public member and forwards.
DaqifiStreamingDeviceSdCardOperationsISdCardOperationsand the shared-SPI-bus handoverDeviceDiagnosticsOperationsIDeviceDiagnosticsNetworkConfigurationOperationsINetworkConfigurableIDeviceOperationHostLanChipInfoOperationsILanChipInfoProviderWas: one file, 3,632 lines.
Four SCPI line predicates that several of the split blocks shared also move into the existing
ScpiResponseClassifier, which was already their home for the underlying rules.The one design decision worth a look
The collaborators don't touch the transport. They go through
IDeviceOperationHost, whichDaqifiStreamingDeviceimplements explicitly — so it adds nothing to the public API — and every member of it forwards to the device member it names.That indirection is the whole point.
ExecuteTextCommandAsync,ExecuteRawCaptureAsync,SendandIsUsbConnectionare all virtual, and subclasses override them to intercept device I/O — that's how the entire test suite stands in for hardware, and how an instrumented device would work in the field. A collaborator that reached for_transportdirectly would compile fine and silently step around every one of those overrides. Routing through the device's own virtual members keeps them in the path.The evidence that it worked: the test suite needed no changes at all. Every existing double still intercepts every operation that moved.
How I know nothing changed
Rather than trust a reading of a 3,600-line move, I diffed the normalized statement multiset of the original file against the new ones — comments, usings and blank lines stripped, and the mechanical renames (
_host.prefixes, the classifier qualifications) canonicalized away.Nine statements exist in the original and not in the new files. All nine are structural, and here they are in full:
private statictointernal staticwhen they moved toScpiResponseClassifierIsScpiErrorLine,IsNonResultLine) that existed only to call the classifier, and their 2 bodies — call sites now call the classifier directlyIDeviceOperationHostZero logic statements were added, removed, or changed. Everything in the other direction is declarations, delegating one-liners, and seam forwarders.
The hazard that check does not catch
A statement-level diff says nothing about who raises an event.
LowSdSpaceWarningis part ofISdCardOperations, and the space check that fires it moved into a collaborator — so thesendercould have quietly become the collaborator. Every existing subscriber in the tests discards the sender ((_, e) => ...), so nothing would have failed.OnLowSdSpaceWarningtherefore stays on the device and the collaborator calls back into it. I added a test pinningsenderto the device and mutation-verified it: pointing the raise at the collaborator makes it fail.Bench evidence — Nyquist 1, FW 3.7.2,
/dev/cu.usbmodem1101Example CLI built against this working copy. Everything below runs through a collaborator that didn't exist before this PR.
SdCardOperationsSdCardOperationsSdCardOperationsSdCardOperationsSdCardOperationsDeviceDiagnosticsOperationsLanChipInfoOperationsThe download number is the useful one: 4,639 bytes / 232 samples is byte-for-byte what the same file produced on the pre-refactor build in #420's bench run.
One aside, not caused by this PR: the first download attempt failed after a streaming run, because on FW 3.7.2 a stream re-partitions the buffer pool and leaves the SD read buffer too small. A reboot restores it. That's the firmware limitation documented in #420, and it's visible here only because #420's idle window now reports it in 20 seconds instead of hanging.
WiFi leg
Also run over TCP at
192.168.1.30, after the unit was power-cycled: connect, initialize, stream at 10 Hz for 3 s on channels 0+1, stop, disconnect — clean throughout, with samples arriving at a steady cadence and no errors. Device still answering ping afterwards.That's deliberately a small run. This is a pure refactor with no transport-specific logic in it, so the WiFi leg only has to show the delegation didn't disturb the TCP path; the USB evidence above is what actually exercises the moved code, including a download that came back byte-identical to the pre-refactor build.
What I did not do
The file is 2,029 lines, not under 800. The four blocks named in the acceptance criteria are all extracted and delegating, but they don't add up to that target on their own. What's left is streaming control, the frame-decode pipeline, session tracking/restore, and channel/DIO/PWM/analog-out control.
The obvious next extraction is the channel/DIO/PWM/analog-out block (~680 lines) — it's the same shape of work, mostly command senders. I stopped short of it deliberately: it needs the seam widened to carry channel-collection access and the channels lock, and doing that in the same PR would have meant a much larger diff verified less carefully. The issue asks for one extraction per PR, and this is one. Frame decode is the riskier one after that — it's the hot path and it owns events.
FirmwareUpdateServiceis untouched; that's #419.