1

I would like to hear arguments for or against a plan I am considering. I have a large network of nodes each of which has vector-valued properties. Accessing these properties (read or write) requires a timestamp.

Plan A: pass the timestamp to every method that will access properties (and every method calling those methods, etc.)

Plan B: have a global variable that is a context stack. Using a context manager, when a context is entered, push a new timestamp onto the stack; pop it on exit. The property access will use the context stack to provide the timestamp.

Pros to plan A: multiple networks can run in parallel easily (although I don't need multiple networks to run). It is easy to see all of the dependencies because there are no hidden dependencies.

Pros to plan B: the code that operates on properties is simpler. It can just use member access to read and write: self.b[2] += self.a instead of self.b.increment(context, self.a, 2).

Which plan should I choose? Did I miss any pros and cons?

3
  • I don't understand your timestamp. surely the property access itself should generate a timestamp as it is accessed, generating the value from the cpu clock via some static/global method, eg DateTime.Now() or by some injected time service?
    – Ewan
    Commented Apr 27, 2016 at 21:53
  • 1
    @ewan: The timestamp is simulation time. it has nothing to do with the clock. every time a property is written, the internal structure appends the new value and the simulation time to a list, which is graphed.
    – Neil G
    Commented Apr 27, 2016 at 21:54
  • possibly you are missing a method where you instantiate the 'node' set its time state, access the property, (timestamped value written to graph), dispose of object (or some other locking method)
    – Ewan
    Commented Apr 27, 2016 at 22:02

2 Answers 2

3

B is harder to parallelize, but you're not doing that. So what, right? Nope, it's still a Bad Thing, and here's why:

That extra parameter you're trying to avoid is there for a reason. It indicates a data dependency which might not otherwise be obvious. This makes your code easier to reason about after time has passed and you are no longer familiar with it. These dependencies simplify refactoring and development of new features, by reducing the amount of "spooky action at a distance" you have to worry about. If something is not a parameter of a given function, then changing that thing should not affect that function. If you want to move or copy a function call to a different part of your codebase, you need only move or copy all of its parameters over to wherever you want to call it from; you don't have to fiddle with some global state to make it behave properly. If you want to coalesce two functions, you need only take the union of their parameters and watch for interference among those parameters; you do not need to worry about how they interact with globals. Every form of refactoring is easier with no globals in play.

Now, a certain amount of global state may be unavoidable, for just about every language other than Haskell, which you are clearly not using. For example, most forms of I/O are global operations since they affect the outside world. Unix has a number of global process parameters that can be difficult to totally avoid (e.g. the current directory, the umask, etc.). But that does not excuse going around inventing new kinds of global state. (Mutable) global state is always guilty until proven innocent.

It may well be that there is a more fundamental design issue with this context object. Perhaps you could make it an instance variable, so that the properties have access to it and you can write simpler property-driven code. But at that point, you probably want to break it down into its constituent parts, rather than just having a single self.context variable. self is supposed to be your "bag of random context"; having a self.context doesn't make much sense. Or perhaps there is some more basic issue which I cannot see without knowing more about your codebase.

1
  • Thanks, I mentioned hidden dependencies, but it's nice to be reminded how you pay the price for hiding them.
    – Neil G
    Commented Apr 27, 2016 at 21:45
1

The primary plan A advantage is testing. You want to run your unit tests in parallel, right? Bingo, multiple networks in parallel. Not to mention just flat out testability problems with the global context.

1
  • +1 this is a good point. I have some testing, and it was a bit gross with the global context setting.
    – Neil G
    Commented Apr 27, 2016 at 21:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.