06.19.09
Posted in General development at 12:39 am by Kyoryu
Robustness is one of those things that we can chase forever. Many developers think that “robustness” means never crashing. A more experienced developer will realize that there are many, many things worse than crashing. Continuing to run while in an invalid state is a much worse option, as it opens up the possibility of corrupted data – a far, far worse problem than a simple crash.
Even past that, we have to look at error conditions that can occur, what compensating actions we can take, and what the impact to the user is.
There seems to be a few general levels of robustness in applications.
- In cases where no system failure occurs, and all input data is correct, the system should work. This is the basic level of correctness. Now, the catch here is knowing what the system should do for any set of valid input…
- User input should be appropriately validated and sanitized to prevent failure. Again, sometimes you can’t just nicely recover, and the only thing you can do is throw an exception or other error code. That’s fine.
- The program should continue to work in case of reasonable system failures – a file being open unexpectedly, a remote system not being available.
- The program recovers in the case of extreme faillures – out of memory, full hard drive, hard drive unexpectedly removed. In many cases, catching these failures may not be worth the effort. It is unlikely that you can do any reasonable recovery, and so doing minimal recovery to try to not corrupt any data, and then get out. If you don’t know that you can even do minimal recovery, just fail and hope for the best.
- In the case of users undermining the system by deleting files you require, I don’t know that it’s even worth bothering. If something you require is gone, you’re broken. Don’t even try to run, exit as quickly as you possibly can to prevent data loss in the future. This scenario is no different than somebody deliberately deleting files from the Windows directory.
And, that’s my view. I’m sure some will disagree, but that’s fine. Attempting to recover from an unrecoverable scenario that is unlikely to ever happen in reality, and if it does, will almost certainly be accompanied with other failures has little value. It is likely that the time spent could be spent doing other things that will have a higher value to your consumers.
Permalink
06.12.09
Posted in Uncategorized at 2:43 pm by Kyoryu
Change happens. We’re not perfect. We don’t know everything. We invariably learn something about the project we’re doing while we’re doing it.
So, code that can easily be changed is better than code that can’t be changed. But how do we know how easy it is to change code?
Difficulty of changing code is best measured at the class/interface level.
If you have a lot of classes that are each easily changed, you will be able to change your code easily. If you have a few classes that are each difficult to change, it will be difficult to change your code. Even if the total work done is the same.
The primary measure of difficulty of changing code is the number of consumers it has.
The more things that know about your code, the harder it is to change. This is a reason why the universally-loved concept of “the one place that does all X’ almost always fails.
It is easier to change an interface that has one consumer than one with three consumers. It is easier to change an interface with three consumers than one with twenty consumers. And if you have one hundred consumers, forget about it.
By consumers, I do not necessarily refer to applications or individuals, rather I refer to classes that refer to any individual class.
It’s easier to change internal code than public-facing code.
This is a restatement of the first point. Published, public-facing code has an infinite number of consumers, making it nearly impossible to change.
The more implementation details you expose, the harder it is to change your code.
Public-facing code should, as much as possible, not leak implementation details. It should reflect the user-facing concepts that are being exposed, and not the implementation details of those concepts.
Not exposing raw types is a good way to do this as well. If you have a user ID that’s an int right now, it may be somewhat painful to change it to a long later. However, if you wrap the int in a UserID class, changing it to use a long or even a GUID will become much, much easier.
The more scenarios you support, the harder it is to change
If you support a large number of scenarios, it is almost inevitable that assumptions needed for one will spill over into others.
The more well-defined your code is, the easier it is to change
If your code has well-defined inputs and outputs, and doesn’t have side effects, it is much easier to change. While the public entry points may remain difficult to change if they have many consumers, any internal details can be changed arbitrarily, and the correctness of the final code can be verified.
On the other hand, if the behavior of the code is not well-defined, then changing it can become extremely difficult, as consumers may be relying upon existing behavior that is either in error, undocumented (and so likely to change if you touch the implementation), or simply a side effect of the “real” work being done.
Permalink
06.09.09
Posted in Uncategorized at 11:57 pm by Kyoryu
Interview with Anders Hejlsberg
This seems to be somewhat of a controversial subject.
On the one hand, we have Java, which forces exceptions to be caught and potentially rethrown. This is, certainly, something of a pain.
On the other hand, C# doesn’t require anything, and any method can potentially throw any kind of exception.
I can see the points on both sides. Nothing is uglier than a bunch of arbitrary try/catch statements in code that do nothing more than rethrow exceptions. And just blindly swallowing exceptions is even worse.
On the other hand, not really knowing what a method might throw in C# can be really, really annoying at times.
Let’s start with versioning, because the issues are pretty easy to see there. Let’s say I create a method foo that declares it throws exceptions A, B, and C. In version two of foo, I want to add a bunch of features, and now foo might throw exception D. It is a breaking change for me to add D to the throws clause of that method, because existing caller of that method will almost certainly not handle that exception.
Well, that’s certainly reasonable. But, I have to wonder if it’s the right answer? If you add a bunch of functionality to a class, is it perhaps better to make some new ReallySpiffyFoo class that contains the new functionality, and leave the existing class as it is?
Then again, I’m not a huge fan of growing classes over time – in most cases, I believe you’re better off leaving a well-defined class as-is except for bug fixes, and putting new functionality into a new class (which might internally use the old one).
Now, each time you walk up the ladder of aggregation, you have this exponential hierarchy below you of exceptions you have to deal with. You end up having to declare 40 exceptions that you might throw. And once you aggregate that with another subsystem you’ve got 80 exceptions in your throws clause. It just balloons out of control.
Another reasonable point. However, I’d tend to believe that in a case like that, you’ve got a bigger design issue at play. Why in the world would a business object throw a FileNotFoundException or the like? At most, it should throw something like a CouldNotLoadDataException. The fact that the data was to be loaded from a file is completely irrelevant at that level.
I also suspect that Anders is looking at this mostly from the viewpoint of a language and framework developer. As a framework developer, he expects code he writes to be called by other people, and they can certainly look up what exceptions are being thrown. That’s reasonable.
However, if I’m using an interface as an extensibility point, it’s a slightly different story. Now I’m importing someone else’s code into my application, and I have no idea of what it might throw when I call it. If that’ doesn’t sound scary, I don’t know what would. At this point my options are either let my app crash when I make any arbitrary call, or catch Exception directly. Neither of those are, in my mind, really good solutions.
What I’d like to see is defined exceptions, but not necessarily checked exceptions. I’d like to know what exceptions a method may throw, but I don’t want to necessarily be forced to catch them. In my mind, the exceptions you throw are effectively part of your API, especially when looked at from role-based interfaces for extensibility rather than header-style interfaces.
If I define an operation in an interface, I’m basically saying that I expect to be able to make this call, with certain parameters, and get a certain type of result back. As part of that, saying that I expect to throw (or will throw) certain exceptions is part of the definition of my API.
What I don’t see much value in is checked exceptions as in Java. To me, there is absolutely no value in putting in boilerplate code to just rethrow exceptions that I’ve caught just to satisfy a compiler restriction. But, knowing what exceptions can be thrown is, to me, extremely valuable.
Permalink