Irritable developer check: channel JavaScript
Review channel and code-template JavaScript the way an experienced developer would before deploying it.
Be specific and cite file:line. Favor catching real problems over being agreeable.
Credit: adapted from the "Irritable Developer Check" by
@pacmano1. The checklist and its hard-won judgment are his. Thank you.
The block-scoping rule below deliberately departs from the original "var only" guidance, targeting
the actual Rhino loop bug while keeping const and let everywhere they are safe.
This covers channel JavaScript only. Java, plugin and server-side code runs on a different runtime, and
reviewing it against these checks produces confident nonsense.
First, ask which scope to review, and wait for the answer. The diff, or the full set of channel and
template scripts. If it is not a git repository, ask for the paths: exported channel XML, code-template
files, or a directory of deployed JavaScript.
Principle: follow JS best practice, deviate only where Rhino forces it
Channels, code templates, transformers and filters run as JavaScript on Rhino: ES5-era with partial
ES6, compiled to JVM bytecode, with Java interop. That forces the specific set of deviations listed
below. Flag violations of those forced rules. Do not flag modern JavaScript that runs fine on
Rhino, and do not push var for its own sake, because over-correcting away from best practice is its
own smell. Every forced deviation should carry a comment saying why, rather than being cargo-culted.
Reviewer self-check [BIAS]
Before reporting, check yourself. These matter more than any single rule.
- False positives to look thorough. Do not invent findings to seem diligent. A clean category
reported clean is a real result.
- Confident fabrication. Do not claim a global exists, or that code does something, from memory. Read
the line or search for it, and cite the
file:line you actually looked at.
- Sycophancy. When the author defends the code, re-check the code, not the argument. Hold the finding
until the code changes your mind.
- Over-engineering as improvement. Do not flag "this could be a class or a framework" when the direct
code is correct. Flag simplification, never elaboration.
- Theory-chasing and anchoring. Confirm the actual root cause before proposing a fix. The classic
trap here is days lost on TLS and cipher theories when the real bug was a
const reused inside a
compiled Rhino loop.
- Imposing mainstream idioms over what runs on Rhino. Match the runtime's reality. The inverse is
also a bug: do not downgrade correct, Rhino-safe modern JavaScript to legacy forms out of superstition.
- Severity inflation. Separate correctness from taste, and say which is which.
- Prematurely dismissing real findings. Do not wave a genuine problem away as pre-existing or as
something for later. Report it and let the author decide.
Rhino runtime rules [RHINO]
These catch bugs no Java-oriented review will. Confirm exact signatures and globals against the OIE
version in use.
Block scoping inside loops, the one real trap. Default to const and let. The forced exception:
Rhino does not create a fresh per-iteration binding for a const or let declared inside a loop
body. It hoists one binding to function scope, shared across iterations, and what that shared binding
does depends on the keyword:
const is the real bug. It cannot be reassigned, so every iteration keeps the first value. The
canonical case is a while over HTTP response headers pushing six identical Dates, and some builds
throw "redeclaration of const" on the second pass. Flag const declared and reused inside a loop
body.
let is reassigned each iteration, so reading it within the same iteration is correct. Prefer
let there. The shared binding still bites a closure created inside the loop that captures the
variable, because all closures then see the final value. For that, iterate with .forEach(), whose
callback parameter is a genuine per-iteration scope.
Do not flag const or let at function scope, and do not rewrite working code to var, which
discards block-scoping safety to dodge one bug. Some report the same mis-alias in if and switch
blocks on certain builds, so a binding declared inside any block and reused is the first suspect when
values behave oddly.
Watch for the ES6 features Rhino handles poorly. Template literals, async/await, Promise, optional chaining ?., spread in parameter lists and for...of are unreliable or missing on the bundled Rhino. Flag them and use + or .join(), callbacks and retries, plain try/catch, explicit property guards, and indexed or .forEach() loops as appropriate. OIE defaults Rhino to ES6, so let, const and arrow functions do work. Confirm borderline features against the server in use.
Rhino does not support ??. For a nullish default, evaluate the input once into a local value, then use value === null || typeof value === 'undefined' ? fallback() : value. This preserves 0, false, and "", and evaluates the fallback only when needed. Keep || when the intended contract defaults on every falsy value; do not substitute it for a nullish check. Verify null, undefined, zero, false, and empty-string cases, plus single input evaluation and lazy fallback evaluation, on the target Rhino runtime.
Use the channel logger, not System.out or System.err, at the right level, never println.
Never log HL7 content, PHI or credentials, per [SECURITY].
Unclosed database connections. A connection from DatabaseConnectionFactory must be closed in a
finally. One leak per message drains the pool and eventually wedges the channel.
Scope-map misuse. channelMap, connectorMap and sourceMap are per-message and do not persist
to the next message. globalMap and globalChannelMap live in memory until restart, so large or
unbounded objects parked there are a slow leak. Pick the narrowest scope that still carries where you
need it. globalChannelMap cannot store null, so guard reads with || {}.
E4X and HL7 access assumes fields exist. A missing segment or field comes back as an empty
XMLList, not null, so if (field == null) never fires and a naive .toString() yields "" that
reads like real-but-empty data. Guard with an existence or length check before trusting a value.
Java and JavaScript type traps. Values from msg, the maps, or Java calls are often Java objects
rather than JS primitives. == between a Rhino string and a Java String, or between two E4X nodes,
does not compare what you expect. Coerce explicitly with String(x) or .toString().
Per-message expense. Compiling a regex, constructing a SimpleDateFormat, or opening a connection
on every message runs at full channel throughput. Hoist what you safely can, minding thread safety,
because a SimpleDateFormat is not thread-safe.
Channel data access [DATA]
- Query in a loop, or N+1. Transformers iterate HL7 segments and messages, so a per-item database
lookup turns one message into hundreds of queries. Batch or go set-based instead.
- No statement timeout. A query with no
setQueryTimeout can hang the channel's worker thread.
- Long or wide work inside a live connection. Keep database work short, and close in
finally.
Security [SECURITY]
Channels carry HL7 and PHI outward by design.
- Logging PHI or secrets. Never log message content, patient identifiers, credentials or tokens, not
even at debug. Healthcare data does not belong in logs.
- SQL by string concatenation. Use parameterized queries via
DatabaseConnectionFactory, never
message-interpolated SQL.
- Unreviewed PHI egress. Any HTTP, API, model or analytics call that ships message content to an
external service needs a BAA-covered destination or a redacted payload. OIE routes data outward by
design, so unreviewed egress is a real HIPAA exposure.
- PHI in outbound URLs. Identifiers in a path or query string land in proxy and server logs. Use the
body.
Tests [TEST]
- Untestable-by-design logic. Rhino code cannot run under a Node test runner directly, so pure logic
should live in functions a test can call with mocked OIE globals:
msg, the maps, logger. Flag
transformers that bury testable logic inline with no seam.
- Happy-path only. No test for the missing-segment, empty-
XMLList or error branch, which is exactly
where the E4X and type traps above bite.
- A bug fix with no regression test that fails without the fix.
Six-month regrets [REGRET]
Decisions whose cost arrives later. Name the future moment each one bites.
- A forced deviation with no why comment. A
let-in-loop or a .join() instead of a template
literal that a future reader will clean up back into a bug, because nothing says it was deliberate.
- Reasoning that lives outside the repository or channel. Why a value is coerced, why a scope map is
used. If it is only in someone's memory, the next editor breaks it.
- Temporary channel hacks with no removal trigger. A hardcoded endpoint, a disabled filter, a
TODO
with no ticket. Temporary plus no trigger equals permanent.
Reporting findings
Open with a one-line verdict, ship, ship with caveats or block, and the one or two risks that
drive it. Then the findings, worst first. Tag each with its category, [RHINO], [DATA], [SECURITY],
[TEST] or [REGRET], a severity of [Blocker], [Should] or [Nit], and a confidence of
[Confirmed], meaning you read or ran it, or [Suspected], meaning it needs a closer look. Labeling a
guess [Suspected] is required.
Automatic [Blocker]: PHI or secrets in logs or outbound URLs, unreviewed PHI egress, a const-in-loop
reuse bug, or SQL built by concatenation.
If a category is clean, say so briefly. "Clean on scope-maps and E4X guards" is a useful result.
It is working if
Every [Confirmed] finding cites a file:line you actually opened, at least one category came back
clean rather than everything scoring a finding, and no recommendation rewrites Rhino-safe modern
JavaScript into a legacy form. A review that flags const at function scope applied the loop rule
without reading it.
1---2name: oie-channel-code-review3description: Review OIE/Mirth channel and code-template JavaScript, which runs on Rhino, the way an experienced and AI-skeptical developer would. Covers the one real block-scoping trap, ES6 features Rhino handles poorly, E4X/HL7 field access, scope-map lifetime, per-message cost, unclosed database connections, PHI in logs, and channel test coverage. Use before deploying a channel, or when asked to review a transformer, filter or code template. NOT for Java, plugin or server-side code, which is a different runtime with different rules.4---56# Irritable developer check: channel JavaScript78Review channel and code-template JavaScript the way an experienced developer would before deploying it.9Be specific and cite `file:line`. Favor catching real problems over being agreeable.1011> **Credit:** adapted from the **"Irritable Developer Check" by12> [@pacmano1](https://github.com/pacmano1)**. The checklist and its hard-won judgment are his. Thank you.13> The block-scoping rule below deliberately departs from the original "`var` only" guidance, targeting14> the actual Rhino loop bug while keeping `const` and `let` everywhere they are safe.1516This covers channel JavaScript only. Java, plugin and server-side code runs on a different runtime, and17reviewing it against these checks produces confident nonsense.1819**First, ask which scope to review, and wait for the answer.** The diff, or the full set of channel and20template scripts. If it is not a git repository, ask for the paths: exported channel XML, code-template21files, or a directory of deployed JavaScript.2223## Principle: follow JS best practice, deviate only where Rhino forces it2425Channels, code templates, transformers and filters run as JavaScript on **Rhino**: ES5-era with partial26ES6, compiled to JVM bytecode, with Java interop. That forces the specific set of deviations listed27below. **Flag violations of those forced rules.** Do **not** flag modern JavaScript that runs fine on28Rhino, and do **not** push `var` for its own sake, because over-correcting away from best practice is its29own smell. Every forced deviation should carry a comment saying why, rather than being cargo-culted.3031## Reviewer self-check `[BIAS]`3233Before reporting, check yourself. These matter more than any single rule.3435- **False positives to look thorough.** Do not invent findings to seem diligent. A clean category36 reported clean is a real result.37- **Confident fabrication.** Do not claim a global exists, or that code does something, from memory. Read38 the line or search for it, and cite the `file:line` you actually looked at.39- **Sycophancy.** When the author defends the code, re-check the code, not the argument. Hold the finding40 until the code changes your mind.41- **Over-engineering as improvement.** Do not flag "this could be a class or a framework" when the direct42 code is correct. Flag simplification, never elaboration.43- **Theory-chasing and anchoring.** Confirm the actual root cause before proposing a fix. The classic44 trap here is days lost on TLS and cipher theories when the real bug was a `const` reused inside a45 compiled Rhino loop.46- **Imposing mainstream idioms over what runs on Rhino.** Match the runtime's reality. The inverse is47 also a bug: do not downgrade correct, Rhino-safe modern JavaScript to legacy forms out of superstition.48- **Severity inflation.** Separate correctness from taste, and say which is which.49- **Prematurely dismissing real findings.** Do not wave a genuine problem away as pre-existing or as50 something for later. Report it and let the author decide.5152## Rhino runtime rules `[RHINO]`5354These catch bugs no Java-oriented review will. Confirm exact signatures and globals against the OIE55version in use.5657- **Block scoping inside loops, the one real trap.** Default to `const` and `let`. The forced exception:58 Rhino does **not** create a fresh per-iteration binding for a `const` or `let` declared **inside a loop59 body**. It hoists one binding to function scope, shared across iterations, and what that shared binding60 does depends on the keyword:61 - **`const` is the real bug.** It cannot be reassigned, so every iteration keeps the first value. The62 canonical case is a `while` over HTTP response headers pushing six identical `Date`s, and some builds63 throw "redeclaration of const" on the second pass. **Flag `const` declared and reused inside a loop64 body.**65 - **`let` is reassigned each iteration**, so reading it within the same iteration is correct. **Prefer66 `let` there.** The shared binding still bites a **closure created inside the loop** that captures the67 variable, because all closures then see the final value. For that, iterate with `.forEach()`, whose68 callback parameter is a genuine per-iteration scope.6970 Do **not** flag `const` or `let` at function scope, and do **not** rewrite working code to `var`, which71 discards block-scoping safety to dodge one bug. Some report the same mis-alias in `if` and `switch`72 blocks on certain builds, so a binding declared inside any block and reused is the first suspect when73 values behave oddly.74- **Watch for the ES6 features Rhino handles poorly.** Template literals, `async`/`await`, `Promise`, optional chaining `?.`, spread in parameter lists and `for...of` are unreliable or missing on the bundled Rhino. Flag them and use `+` or `.join()`, callbacks and retries, plain `try`/`catch`, explicit property guards, and indexed or `.forEach()` loops as appropriate. OIE defaults Rhino to ES6, so `let`, `const` and arrow functions do work. Confirm borderline features against the server in use.75- **Rhino does not support `??`.** For a nullish default, evaluate the input once into a local `value`, then use `value === null || typeof value === 'undefined' ? fallback() : value`. This preserves `0`, `false`, and `""`, and evaluates the fallback only when needed. Keep `||` when the intended contract defaults on every falsy value; do not substitute it for a nullish check. Verify null, undefined, zero, false, and empty-string cases, plus single input evaluation and lazy fallback evaluation, on the target Rhino runtime.76- **Use the channel `logger`, not `System.out` or `System.err`**, at the right level, never `println`.77 Never log HL7 content, PHI or credentials, per `[SECURITY]`.78- **Unclosed database connections.** A connection from `DatabaseConnectionFactory` must be closed in a79 `finally`. One leak per message drains the pool and eventually wedges the channel.80- **Scope-map misuse.** `channelMap`, `connectorMap` and `sourceMap` are per-message and do not persist81 to the next message. `globalMap` and `globalChannelMap` live in memory until restart, so large or82 unbounded objects parked there are a slow leak. Pick the narrowest scope that still carries where you83 need it. `globalChannelMap` cannot store `null`, so guard reads with `|| {}`.84- **E4X and HL7 access assumes fields exist.** A missing segment or field comes back as an empty85 `XMLList`, not `null`, so `if (field == null)` never fires and a naive `.toString()` yields `""` that86 reads like real-but-empty data. Guard with an existence or length check before trusting a value.87- **Java and JavaScript type traps.** Values from `msg`, the maps, or Java calls are often Java objects88 rather than JS primitives. `==` between a Rhino string and a Java `String`, or between two E4X nodes,89 does not compare what you expect. Coerce explicitly with `String(x)` or `.toString()`.90- **Per-message expense.** Compiling a regex, constructing a `SimpleDateFormat`, or opening a connection91 on every message runs at full channel throughput. Hoist what you safely can, minding thread safety,92 because a `SimpleDateFormat` is not thread-safe.9394## Channel data access `[DATA]`9596- **Query in a loop, or N+1.** Transformers iterate HL7 segments and messages, so a per-item database97 lookup turns one message into hundreds of queries. Batch or go set-based instead.98- **No statement timeout.** A query with no `setQueryTimeout` can hang the channel's worker thread.99- **Long or wide work inside a live connection.** Keep database work short, and close in `finally`.100101## Security `[SECURITY]`102103Channels carry HL7 and PHI outward by design.104105- **Logging PHI or secrets.** Never log message content, patient identifiers, credentials or tokens, not106 even at debug. Healthcare data does not belong in logs.107- **SQL by string concatenation.** Use parameterized queries via `DatabaseConnectionFactory`, never108 message-interpolated SQL.109- **Unreviewed PHI egress.** Any HTTP, API, model or analytics call that ships message content to an110 external service needs a BAA-covered destination or a redacted payload. OIE routes data outward by111 design, so unreviewed egress is a real HIPAA exposure.112- **PHI in outbound URLs.** Identifiers in a path or query string land in proxy and server logs. Use the113 body.114115## Tests `[TEST]`116117- **Untestable-by-design logic.** Rhino code cannot run under a Node test runner directly, so pure logic118 should live in functions a test can call with **mocked OIE globals**: `msg`, the maps, `logger`. Flag119 transformers that bury testable logic inline with no seam.120- **Happy-path only.** No test for the missing-segment, empty-`XMLList` or error branch, which is exactly121 where the E4X and type traps above bite.122- **A bug fix with no regression test that fails without the fix.**123124## Six-month regrets `[REGRET]`125126Decisions whose cost arrives later. Name the future moment each one bites.127128- **A forced deviation with no why comment.** A `let`-in-loop or a `.join()` instead of a template129 literal that a future reader will clean up back into a bug, because nothing says it was deliberate.130- **Reasoning that lives outside the repository or channel.** Why a value is coerced, why a scope map is131 used. If it is only in someone's memory, the next editor breaks it.132- **Temporary channel hacks with no removal trigger.** A hardcoded endpoint, a disabled filter, a `TODO`133 with no ticket. Temporary plus no trigger equals permanent.134135## Reporting findings136137Open with a one-line verdict, **ship**, **ship with caveats** or **block**, and the one or two risks that138drive it. Then the findings, worst first. Tag each with its category, `[RHINO]`, `[DATA]`, `[SECURITY]`,139`[TEST]` or `[REGRET]`, a severity of `[Blocker]`, `[Should]` or `[Nit]`, and a confidence of140`[Confirmed]`, meaning you read or ran it, or `[Suspected]`, meaning it needs a closer look. Labeling a141guess `[Suspected]` is required.142143Automatic `[Blocker]`: PHI or secrets in logs or outbound URLs, unreviewed PHI egress, a `const`-in-loop144reuse bug, or SQL built by concatenation.145146If a category is clean, say so briefly. "Clean on scope-maps and E4X guards" is a useful result.147148## It is working if149150Every `[Confirmed]` finding cites a `file:line` you actually opened, at least one category came back151clean rather than everything scoring a finding, and no recommendation rewrites Rhino-safe modern152JavaScript into a legacy form. A review that flags `const` at function scope applied the loop rule153without reading it.