Live data from Hacker News

Firewalling your code

lackofimagination.org

1–10 of 82 posts

Re: Firewalling your code

#2
What an curious approach - seems like it would be a mess to maintain in a larger project, but the concept of controlling who/what can call the public functions is an interesting idea.

Re: Firewalling your code

#4
I would think Lint rules are better for encouraging this and has no run time implications. Easy escape hatches as well if you really want to crack the firewall to ship something urgently.

Re: Firewalling your code

#5
Always feels like you're in a dysfunctional place when you have to program this defensively.

I had an acquaintance who was writing library code for a few dozen data engineers in python, and she had to resort to locking down private methods by checking the call stack after engineers repeatedly got hold of private objects, or objects that are only there sometimes (e.g. when not running clustered).

I adopted a similar stance of "you can't abuse what you can't access" in platform engineering too, but I am not the greatest fan of having to do this in the first place. But the alternative always seems to be that someone will change the scope of what's supported by you for you, as soon as someone builds a dependency.

Re: Firewalling your code

#6
The idea (as I understand it...) is to structure the codebase as a directory tree and have position in the tree determine whether some module can call into another or not. Something along the lines of a function can call into one defined in source at the same level or below, but not above.

I did that for a while in C. It does sort of work. Layout amounted to:

  foobar.h
  foobar/foobar.c
  foobar/misc.h
  foobar/misc.c
  foobar/submod.h
  foobar/submod/submod.h
  foobar/submod/other.c
The scheme there was #include to get at peer or submodule source, where not using "../" in the path avoided trying to access parent modules.

Within a simple header/source pair make the declared functions global and all the others static. For the header/directory pair, compile the source files, link them, internalise the symbols that aren't external in the file with the name matching the directory.

I still broadly like that approach. The llvm-link && internalise applied at submodule scope has the side effect of optimising the modules individually. You can start with a header/source pair and convert it to a header/submodule pair without disturbing the rest of the codebase.

What doesn't work so well, and also isn't addressed in the "firewalling" post, is that inevitably the dependency tree wants to pick up a cycle. Some dependency of some module looks like it would be useful to another module and include "../../foobar/misc.h" suddenly appears in the scheme and promptly fails to link because misc.c's symbols are hidden inside foobar.

The "fix" is either to abandon this scheme, start putting functions as static inline in header files as a "temporary" workaround, or to move the multiply-used dependency up through the tree until it is accessible to everything that wants it. "Misc" is prone to ending up at the top level.

I think that pattern died when I disabled the internalise step to help debug something where I wanted to pull in code from elsewhere and then never re-enabled the internalisation. It still seems like a broadly good idea but it does shine a clear light on the dependency structure of the codebase which obstructs the natural descent into a ball of mud. These days I think I'd want the internalise / strict separation enforced in CI builds and not in dev builds (much like unused variables and similar).

Nice to be reminded of that pattern. Thanks for posting the site.

Re: Firewalling your code

#7
How can you ensure that the modules follow these rules? [...] As a proof of concept, I’ve created a Node.js library called firewall-js using JavaScript proxies.

Unless I misunderstand, there's a vastly simpler way to do this:

- use a monorepo;

- put each module in its own package.

Re: Firewalling your code

#8
post #5

Always feels like you're in a dysfunctional place when you have to program this defensively. I had an acquaintance who was writing library code for a few dozen data engineers in python, and she had to resort to locking down private methods by checking the call stack after engineers repeatedly got hold of private objects, or objects that are only there sometimes (e.g. when not running clustered). I adopted a similar s…

"I monkey patched your code and now it doesn't work" would be a deeply irritating bug report. It's directly equivalent to "I forked your codebase, changed the text files, and now it doesn't work".

Re: Firewalling your code

#10
This seems pretty neat for when your layer separation is merely by folders in JavaScript.

It's unfortunate that this doesn't highlight any problems until the code is run as I feel it's always best to find out as soon as possible you're making a mistake, your IDE instantly telling you of a problem as you try write the code is the most ideal time, with the next being compilation. In this case probably even a simple integration test would instantly let you know, which isn't terrible.

There is a run time performance impact too here, but I would expect its cost is reasonable for the value it adds.

In the C# space (and I imagine many other languages have equivalent options), layer segregation is a good reason to split your code into multiple assemblies (or projects) and the compiler can then enforce the layering. Of course we sometimes also split things up to into different assemblies for other reasons too, but for any non-trivial sized code base, I consider that one assembly per layer is the bare minimum.

Post reply on HN