DEV Community


Discussion on: 10 rules to code like NASA (applied to interpreted languages)

temporal profile image
Jacek Złydach

Wouldn't overdo it. As others pointed out, those guidelines are tactical-level rules for working with a GC-free, memory-unsafe language in context of embedded realtime systems. This environment requires you to have total control over what the code does, how much time it spends on it, and how much memory it uses. Many of the points are making a trade-off for that control, against increased code complexity.

Some notes:

Point 1b - avoiding recursion. I'd chill out with this a bit; recursion ain't scary once you familiarize itself with it. It's rarely the best thing to do (unless you work with trees a lot), but for some problems, a recursive solution is the cleanest, most readable one. If your language supports Tail Call Optimization, it may even (in some cases) be as fast as iteration and not pose a risk of stack overflow.

Point 3 - avoiding heap allocations - is very much applicable to dynamic languages for performance reasons. Dynamic allocation costs performance (not as much as in C/C++ if your language runtime optimizes memory management for allocation, but still).

You can learn writing so-called "non-consing", i.e. not constructing, not allocating code, but it's tricky - such code may become less readable than the "regular" alternative, and definitely needs to be isolated because it can corrupt your data if references leak. The trick is to learn your language and standard library (and libraries you use), and pay attention to operations that modify its arguments, instead of returning a new value. They're sometimes called "destructive operations".

Consider e.g. following REPL session in Common Lisp:

CL-USER> (remove-if #'evenp (list 1 2 3 4 5 6 7))
(1 3 5 7)
CL-USER> (delete-if #'evenp (list 1 2 3 4 5 6 7))
(1 3 5 7)

The results are seemingly the same; the difference is that remove-if returns a fresh list, while delete-if modifies its list argument. In non-performance-critical places, you should prefer to use the remove-if variant, because it's safer, and GC can get rid of unused data just fine. delete-if is faster, because it only rebinds pointers in a list to drop some nodes, but if that (list 1 2 3 4 5 6 7) was passed in via reference, and someone else would hold that reference, that someone would discover the data changed under them.

To write non-allocating code, you should also use arrays more - they can be allocated once to required size and reused without reallocation. It's especially handy if your language supports unboxed primitives - for things like numbers, you can then rewrite your critical code to perform operations "in place", using preallocated arrays as temporary buffers for calculations.

Point 9b is subtly uncovered (avoid function pointers). In dynamic languages, it would translate to "avoid using functions as values" - avoid higher-order functions, avoid lambda expressions, etc. Applying it would be a huge loss to code clarity at no real win in context of web development (or really any non-embedded development).