- In order for toString overrides to appear in jest tests and
console.log(...)
calls, theget [Symbol.toStringTag]() {...}
getter should be implemented. - Implementing
public toString: string() {...}
does not work in many cases. - When using Jest's
it.each(...)(name, fn, timeout)
test approach, thename
argument is parsed using printf formatting. To show the output of theget [Symbol.toStringTag]() {...}
getter,%s
should be substituted into thename
argument (not%p
).
- In JS/TS,
undefined
is the default type and value when an argument is not passed to an optional parameter. I chose to replace the typicalnull
checking I'd perform when coding in other languages with checks againstundefined
for this reason. - Following the MDN definition, I chose to use
null
only when I needed to indicate the deliberate absence of a value (which is rare). Unlikeundefined
, type checking preventsnull
from appearing as an argument in a function call unless it is explicitly allowed.
- When performing numeric calculations and comparisons with variables that may be undefined, extra care needs to be taken to check that the value is neither
undefined
norNaN
. When a numeric calculation fails to evaluate to a number,NaN
is returned instead ofundefined
. Unfortunately,NaN
cannot be compared toundefined
and(variable with value NaN) === undefined
will evaluate to false.x === undefined
,isNaN(x)
, andNumber.isNaN(x)
will all fail in one way or another to detect thatx
is eitherundefined
orNaN
; instead,isNaN(Number(x))
must be used.
isNaN(Number(x))
will returnfalse
forx === Infinity
andx === -Infinity
. In contexts where infinity and negative infinity are possible values from a calculation ((non-zero number) / 0
,log(0)
, extremely large numbers), you can check thatx !== Infinity && x !== -Infinity && !isNaN(x)
usingNumber.isFinite(x)
(note, this is not the same asNumber.isFinite(Number(x))
in TypeScript).Number.isFinite(Number(x))
covers everything, checking thatx
is notInfinity
,-Infinity
,undefined
, andNaN
.- In the context that you are storing
number | undefined
in some variable, and would like the sum of that variable and a number to equalundefined
if the variable is alreadyundefined
, it's possible to simplify the expression and remove the need for anundefined
check by usingInfinity
instead, sinceInfinity + (any finite number) === Infinity
. However, it may not be immediately obvious to other programmers what you are doing.
For cases where I need to enforce that a number x
is neither undefined
nor NaN
, I'm opting to use Number.isFinite(Number(x))
, unless Infinity
and -Infinity
are explicitly allowed, in which case I will use !isNaN(Number(x))
. For the purpose of maintaining clean, readable, and maintainable code, I have implemented these checks into utility functions with better names.
- Setting the value of a property to undefined is not the same as calling
delete
on that property. delete
is meant to be called on object properties, and it severs the reference between the object and the value of the property.- The garbage collector will only collect objects which have no references to any other objects.
- In the situation that Object A is linked to Object B by a property (e.g.
A.prop = B
) and then you delete that property (delete A.prop
), Object B is only guaranteed to be garbage collected if it lacks any references to other objects (if all of its properties are deleted).
- In TypeScript projects, StrykerJS will not detect TypeScript compilation errors and will erroneously show mutants that do not actually survive compilation unless the Stryker TypeScript Checker plugin is installed.
- StrykerJS seems to be more likely to report false mutants when Jest's
it.each()
testing method is used. (#TODO)
- Prefer the
it.each(...)
approach for multiple test cases with the same format (shared "arrange/act/assert" steps). When using this approach, avoid putting the test cases into separate objects and referencing them. Doing so requires additional type checking and seems to reduce the test code's maintainability, all while not providing much benefit over specifying the test cases within theeach
call.
- For this implementation, I set up my doubly linked list class in a particular way to make sure that there cannot be a node before the root node (either by the root node having a
previous
property or by other nodes accepting a root node as theirnext
property). If I did not do that, then it would have been possible for the root node on the list object to be deleted viaremoveAfter
while causing a memory leak, or for the root node to get out of sync with the start of the list.
- In hindsight, you should go in thinking that each list operation (insert, remove, at, contains) will require a variant of the skip list traversal algorithm. The traversal method is what allows skip lists to be efficient (averaged time complexity).
- The length operation is more straightforward, and could be implemented with either a descent to the bottom layer followed by a count or by maintaining a count in an instance variable.
- Nodes that were descended from during the insertion operation should be tracked for use during randomized promotion.
- Skip list traversal should proceed from the "top left" to the "bottom right", or from the head node to the end of the bottommost layer. Traversal should occur across the row/layer first, descending only when necessary.
- Skip list operations have a wide variety of edge cases; especially when implementing indexed skip lists. Multiple examples should be written out and worked through to determine the correct ways to implement operations while updating link widths.
- Even with a large base, collisions can still occur due to the pigeonhole principle: if you hash more strings than there are possible hash values, some strings must have the same hash value. This is true regardless of the hash function used.
- When writing recursive algorithms, always check for possible infinite loops with collections of sizes 0 through 2.
- When attempting to get a chosen digit of an integer using math functions (division by a power of 10 followed by a floor or rounding operation, etc.), you may get incorrect results for certain input numbers due to floating point errors.
- JavaScript numbers are double-precision floating point numbers. While BigInt is available, it also has a memory tradeoff and would require a modulo-based approach.
- The easiest way to get the digit of a number (though not ideal from a performance perspective) is to convert the number to a string and then access the digit using the string indexer, parsing it back into an integer.
- If additional initialized assignments are needed in a for loop (for example, setting the length of a string to be compared against the index in the condition step), you can specify those assignments in comma-separated format prior to the semicolon.
// Definition
for (initialization; condition; afterthought) {
statement;
}
// The following examples are all valid javascript
for (let i = 0, len = str.length; i < len; i++) {
// ...
}
for (let i = 0; i < 10; i++) {
// ...
}
for (let i = 0; ; i++) {
if(i >= 10) {
break;
}
}
let iTimesJ = 0;
for (let i = 0; i < 10; i++, j++, iTimesJ = i * j);