Summary
A user is designing a JavaScript function to extract and filter data from HTML tables and is encountering difficulty managing multiple input types and filtering strategies without creating a confusing API. The core problem is not a runtime failure, but a software design anti-pattern: attempting to handle four distinct data transformation modes through a single function with an enumerated switch statement. This leads to tight coupling, poor readability, and a fragile interface that violates the principle of Single Responsibility.
Root Cause
The root cause is the overloading of a single function signature to accommodate fundamentally different behaviors:
- Identity transformation (return raw data).
- Mapping transformation (apply a function to rows).
- Boolean filtering (select columns via boolean mask).
- Index-based extraction (select columns via numeric index or CSV string).
The implementation relies on switch statements and implicit type checking (e.g., typeof tableIdOrEl == "String", which is incorrect in JS—it should be === "string" or check instanceof). This creates a “god function” that requires the caller to understand internal magic numbers (e.g., filterOption=3) and complex parameter expectations (passing a mapper function that sometimes acts as a boolean mask, other times as a column mapper).
Why This Happens in Real Systems
This pattern commonly emerges during rapid prototyping or when developers attempt to build a “Universal Utility” to handle all variations of a known task (e.g., “scraping a table”).
- Ambiguity of Inputs: The
mapperparameter is reused for different purposes (transformation function, boolean array, numeric array), making the function signature ambiguous. - Lack of Type Safety: In dynamically typed languages like JavaScript, developers often skip rigorous type checking, leading to the assumption that any input will work, which inevitably results in runtime errors when unexpected types are passed.
- Cognitive Overload: The developer prioritizes “getting it to work” over “making it readable,” resulting in a
switchcase that expands indefinitely as new requirements are added.
Real-World Impact
- Maintenance Nightmare: Adding a new filtering strategy requires modifying the core function, increasing the risk of breaking existing logic (Regression).
- Poor Developer Experience: Consumers of this function must read the source code to understand what
filterOption=2does. This violates the Principle of Least Astonishment. - Fragile Usage: If a user passes a CSV string (Option 4) but the table header parsing fails, the function returns
undefinedor crashes silently because the logic insidecase 4assumesdata[0]contains valid header mappings. - Testing Complexity: Unit tests must cover every permutation of
filterOptioncombined with valid/invalidmapperarguments, leading to combinatorial explosion in test cases.
Example or Code
The current implementation contains several bugs and design flaws. Specifically, the type check typeof tableIdOrEl == "String" is invalid (it should be "string"), and the reuse of the mapper variable in case 4 shadows the parameter, potentially causing logic errors.
Current Anti-Pattern (The “Switch” God Function):
// DO NOT USE: Contains bugs and poor design
function extractHtmlTableData(tableIdOrEl = "", filterOption = 0, mapper = (data) => { return data }) {
var myTab, data;
// BUG: typeof returns 'string', not 'String'. 'TABLE' check is case-sensitive.
if (typeof tableIdOrEl == "String") {
myTab = document.getElementById(tableIdOrEl); // Fixed variable reference
} else if (tableIdOrEl.nodeName == "TABLE") {
myTab = tableIdOrEl;
} else {
return null;
}
if (myTab) {
data = [...myTab.rows].map((r) => [...r.cells].map((c) => c.innerText));
}
// ANTI-PATTERN: Switch over behavior types
switch (filterOption) {
case 0: return data; // Identity
case 1: return data.map(mapper); // Row Mapping
case 2: return filter2DWithBooleanAry(data, mapper); // Boolean Mask
case 3: return filter2DWithNumberedAry(data, mapper); // Index Array
// BUG: 'mapper' is redefined here, shadowing the parameter.
case 4:
var mapper = filter2DWithNumberedAry(data[0], mapper);
return filter2DWithNumberedAry(data, mapper);
}
}
How Senior Engineers Fix It
Senior engineers resolve this by decoupling the logic into distinct, composable functions using functional programming principles and Strategy Pattern.
- Separate Extraction from Transformation: Create a base function that only extracts the 2D array from the HTML table.
- Create Strategy Functions: Create specific, named functions for each filtering/mapping strategy.
- Composition: Allow the user to pipe the data through these functions.
- Type Inference: Instead of asking the user to specify a
filterOptioncode, infer the behavior based on the argument type (polymorphism).
Refactored Solution:
// 1. Core Extraction (Pure function)
const getTableData = (tableIdOrEl) => {
const table = typeof tableIdOrEl === 'string'
? document.getElementById(tableIdOrEl)
: tableIdOrEl;
if (!table || table.nodeName !== 'TABLE') return [];
return Array.from(table.rows).map(row =>
Array.from(row.cells).map(cell => cell.innerText)
);
};
// 2. Strategy: Boolean Mask (Columns)
const filterColumnsByMask = (data, mask) => {
return data.map(row => row.filter((_, i) => mask[i]));
};
// 3. Strategy: Index Selection
const selectColumnsByIndex = (data, indices) => {
return data.map(row => indices.map(i => row[i]));
};
// 4. Strategy: CSV Titles to Indices
const selectColumnsByHeader = (data, headerCsv) => {
if (data.length === 0) return [];
const headers = data[0];
const targetHeaders = headerCsv.split(',').map(h => h.trim());
const indices = targetHeaders
.map(h => headers.indexOf(h))
.filter(i => i !== -1);
return selectColumnsByIndex(data, indices);
};
// 5. Strategy: Custom Mapper
const mapRows = (data, mapperFn) => data.map(mapperFn);
// Usage (Composed):
const rawData = getTableData('myTable');
const result = filterColumnsByMask(rawData, [1, 0, 1]); // Explicit intent
Why Juniors Miss It
Junior developers often view the problem as “writing code to handle inputs” rather than “designing an interface for humans.”
- Procedural Thinking: They rely heavily on
if/elseorswitchstatements to route logic, mimicking how the computer processes the data, rather than abstracting the intent. - Fear of Redundancy: Juniors often try to DRY (Don’t Repeat Yourself) code too early, merging distinct behaviors into a single function to avoid writing “extra” lines of code, not realizing that clarity is more important than brevity.
- Lack of Pattern Recognition: They may not recognize that an “Array of Booleans” and a “Function Mapper” are fundamentally different strategies that deserve their own named functions.
- Implicit Assumptions: They assume the user will “just know” that
filterOption=2expects a boolean array, rather than forcing the code to check the input type and adapt or throw a descriptive error.