We make assumptions all the time. How often do we step back and examine those assumptions and how deep they go? If I press a button on a black box and it causes a light to flash, how many times in a row do I need that to happen before I conclude that pushing the button causes the light to flash. OK, the more times you repeat the experiment of pressing the button, and the more times the light comes on as a consequence (in time), the higher the statistical chance of a causal link.
But how many more times do I need to press it before I conclude that pressing the button will always cause the light to flash? What if on the 101st attempt it caused the light to stay on? How can I ever know that the next time I press the button that the same thing will happen? Repeating the experiment brings makes the hypothesis no more likely to be correct.
I find it interesting that we generally do not treat other people as deterministic entities. For example, if a given action generates a specific response, I think that the likely internalization would be to increase that actions “score” or probability of success for the future. I think that it would be unlikely for one to assume that because a person responded in a certain way once, they will always respond in that way (imagine simply saying “hello” to someone, over and over again). I suppose this is because we (correctly) view other people as complex systems which means a fuzzy approach is required. Something I think is interesting is the way in which a positive response adds to the “score“ not only for that action in whatever the given situation was, but the score propagates to both similar situations and similar actions. In this way it is possible to cope with and handle new situations without having prior experience of stimulus/response.
Now obviously within a fully defined system, you can reason based upon the axioms of the system, without having to make any assumptions. Fully defining a system is very hard though for anything other than trivial systems. Arithmetic for example is provably impossible to define. http://en.wikipedia.org/wiki/Gödel’s_incompleteness_theorems
So in conclusion to avoid making potentially damaging assumptions for a given system we need to do one of two things:
1) Fully understand and reduce systems to their axiomatic bases before reasoning about them
or
2) Take the view that it is impossible to reason in a binary manner about decisions within the system, and fuzzy logic is instead required to prevent logical error. Note that errors will still occur, but they are expected. Errors which are expected can be handled.
Now this poses a problem for me., because as a software engineer nearly everything I touch is an abstraction. Even when programming in assembly on an 8 bit microprocessor one is working with a system which is far too complex to fully define. Digital logic itself may be provable, but any physical implementation in silicon is only an approximation of that. Yes, hardware comes with working ranges in order to prevent analogue effects from influencing the digital system, but it is still possible for cosmic rays to flip bits at random or for a given logic path to be incorrect (testing complex integrated circuits comprehensively, unsurprisingly, is also a very hard problem). My point is that when you perform 1+1, you are incorrectly assuming that the answer will be 2.
Modern hardware is great however and has error checking and tolerance to these things so the chances of being affected by this is very low indeed. So we move up a layer into the operating system, or to the platform library, the compiler, any dependencies etc and it is obvious that the only way to handle all of this information is to deal with abstractions. But as I have shown, it is impossible to reason with these abstractions, since it is impossible to fully understand their internals. All we can really do is make a best guess as to what is going on.
So where does that leave us? With several options:
1) We never use anything we don’t fully understand (good luck with this one, unless you are a carpenter or something)
2) We hope to god that all of the assumptions we have made are correct. (Generally this involves testing the software and seeing if it works or not. The more scenarios you can test, the more confident you can be. Don’t try and work out a confidence percentage though since you can never actually leave 0%).
or my favourite option
3) We can use specifications to blame other people when it goes wrong.
By providing a specification for a black box, we are making a contract which says, “when you press this button, the light will flash for 1 second”. If, when someone presses the button the light ever doesn’t flash, or stays on, or does anything other than flash for 1 second, we lied and it is our fault not the pressers fault.
This is our get out clause. This is the way in which we can create a simple system which we can reason about without having to understand everything in the world. This is what allows us to actually achieve anything at all with a degree of confidence in our work. Obviously the more specific the wording of the specification, the less work you have to do to ensure that you don’t lie about it. So when you write a module and you don’t write a specification you are really just making more work for yourself and making it harder for yourself to be confident that it is doing what it is supposed to do.
I won’t go into specifics about how to write specifications, because everyone knows how to do it. I think some people in the world just need some gentle persuasion that it is in fact worth their time.