I like your idea, and I think there's room for improved introspection in debugging.
At the same time, I think that if a program can't be understood without being executed, then it's already lost (in terms of readability).
Certainly, for example, if you have a threaded program, you need to be able to visually inspect and verify that there will be no deadlocks or race conditions when the program is run, because you won't be able to test every possible condition in a debugger.
It might partly be a personality-type thing. I can't imagine visually inspecting and verifying anything if I didn't write it to begin with. I wouldn't know where to begin thinking of possible ways to break it. On the other hand, it seems far more natural for the author to say, "here are the race conditions I considered, these points where a context switch would be maximally inconvenient. And still lo the program works." This way the reader doesn't have to recreate such situations from scratch. He or she just has to verify the provided situations, or notice a scenario that was missed. That seems like an easier ask from someone new to the project.
(Mu scenarios can't insert context switches yet, but it's planned.)
Perhaps it would help to think of the dichotomy as between the rules and the state space of inputs they handle, rather than between reading and running. Seemingly simple code can often hide surprising subtleties. Why is this line written just like so and not thus? How does everything turn out just right in this one situation? Tests help to record the right questions for the reader to ask.
> I can't imagine visually inspecting and verifying anything if I didn't write it to begin with.
It's a skill, you need to develop it. You develop it by doing it :)
> I wouldn't know where to begin thinking of possible ways to break it.
For deadlocks, look at every single lock used. Show that one of the Coffman conditions doesn't apply, and you've proven that there can never be a deadlock.
For race conditions, look at every piece of shared memory (or other shared resource). That is where race conditions always occur.
For understanding a program, the key is to understand the structure. That is why people put things in subdirectories, to help communicate the structure of the program, and show which things are closely related.
Do you really look at every single piece of shared memory? When you inherit a large codebase from someone? How do you reconcile that with having to deliver changes in the first weeks? Everywhere I've been, people half-ass these things, with inevitable bugs. I think you're under-estimating the possibility that you're just a better programmer than me :/
> Do you really look at every single piece of shared memory?
Yes
> When you inherit a large codebase from someone?
If the codebase has lots of threading errors, then yes, it's the only way. If it's a large program, it can take months to go through and check every piece of shared memory (and removing threads along the way, removing shared stuff); but the alternative could take years.
In one case, I moved every single lock/unlock to the top of a file so I could quickly see all the locks and unlocks, and that each lock had a matching unlock, even in error conditions.
How did you get into such issues? Were there classes where you got started, or was it all on the job?
I think I'm not concerned so much about inheriting a codebase with lots of threading errors. It's more about a codebase that's almost perfectly right, but where I'm scared to change anything because I don't have a big-picture model of the concurrency in my head..
Do you have any code samples I can try to learn from? For example, I'm not sure how you would move lock/unlock pairs to the top of a file from different functions/scopes. Unless you were doing some sort of literate programming as well?
> How did you get into such issues? Were there classes where you got started, or was it all on the job?
I took the usual college classes dealing with concurrency (in my school it was in the OS class), but it took several years in the industry to really feel confident with threads (it took less time to feel confident with networking). I wrote down my knowledge (for what it's worth) in this book: http://www.amazon.com/dp/0996193308
> I'm not sure how you would move lock/unlock pairs to the top of a file from different functions/scopes.
That solution might not work in every case. It worked in the particular case I was referring to.
> It's more about a codebase that's almost perfectly right, but where I'm scared to change anything because I don't have a big-picture model of the concurrency in my head..
Hmmmmm, that's an interesting question. Usually when the codebase is written, the person who wrote it had an idea in his head that, "this is how things will be locked to avoid problems." I try to figure out what that idea was.
Sometimes there is like a critical 'zone,' where a thread acquires the lock when it enters, and releases when it leaves. For example, it could acquire the lock when it enters a class method, and releases it when it leaves the class. Then the class becomes the critical zone.
Maybe learning to think of 'critical zones' is the most important skill to understanding the big picture?
I like your idea, and I think there's room for improved introspection in debugging.
At the same time, I think that if a program can't be understood without being executed, then it's already lost (in terms of readability).
Certainly, for example, if you have a threaded program, you need to be able to visually inspect and verify that there will be no deadlocks or race conditions when the program is run, because you won't be able to test every possible condition in a debugger.