One of the major issues in development is what to do with the previous programmer's code. Usually it begins with the naming convention, this is easily solved - get used to it.
The following issue is the separation, or lacking of it, to different files and projects. While being a little nuisance, it's a "don't touch unless you have to".
The major issues arise when coming to fix a bug. What calls what? Which event comes first? What function threw the exception? All of those questions can be answered easily when you wrote the code, though it might take time to refresh your memory when it's an old piece. When it's not your code - spell trouble.
The easiest solution is using the debugger. There are 2 issues with the debugger:
1. Race conditions hardly ever happen when the debugger is attached.
2. Most of the clients I had didn't want the debugger, didn't allow it, and in some cases looked at it as trying to steal their data.
That's where logs come in handily. All you have to do is select the logging method (personally I prefer NLog, but there are many more libraries out there), and add the log lines.
After practicing logging for a long time I reached the conclusion that a some guidelines are needed. Mainly because no one really needs all the logs all of the time, normally you'll need only the errors, sometimes the warnings too. The lower levels are only used when trying to figure out what causes a certain behaviour.
So here are the guidelines (based on NLog):
1. Fatal - errors that forces the application to shutdown. I'm using it in the global trap, the global threaded trap, and when the application's initialization phase didn't complete successfully.
2. Error - anything that failed to execute correctly without an expected explanation. By expected explanation I mean invalid input data, known erroneous response from service and etc.
3. Warn - anything that failed to execute correctly with an expected explanation.
4. Info - any call to a public method, with the relevant parameters (i.e. username, but not the user's password).
5. Debug - Before entering any "interesting" method, and at the end of each method. By interesting I mean anything that might look like a potential problem to the programmer. For the debug pass EVERYTHING, in some cases even the passwords. When you're forced to use the debug, you have a real bug lurking out there and you must catch it ASAP.
Tuesday, December 10, 2013
Thursday, August 15, 2013
Interfaces - when to use, and what's an abuse
Recently I found I have way too many interfaces inside my
code. Don’t get me wrong, interfaces are good, extremely good, and in some
cases a must. It was just that some of the interfaces were “mines in the code”. An interface is a certain boundary which you must cross in order to debug. Placing this boundary incorrectly is like building a window inside the house. It might even look good, but it would be a nightmare to walk in this house, and living there is unimaginable.
Let’s begin with what an interface is:
- Interface is a contract.
- Interface is shared between more than one object.
- Interface promises that a certain method / property will be there.
- Interface does not prevent another interface.
Reading those 4 terms means that an interface is useful
ONLY if you don’t have prior knowledge of the object. In the programmers’ term
- late binding, this is a great blessing when working on a multi- whatever
project that takes a long time to compile.
But with the blessing comes the price: It’s a contract.
You can’t break it without the approval of the other side, or in some cases “sides”.
The end result is that once an interface is out of your machine, it mustn’t be
changed.
Let’s take a real life scenario, one my team had to deal
with. We’re running a local win-service, the service acts as a channel to the
outside world. It retrieves messages and launches the correct responder. But
who is “the correct responder”? I chose to use a simple interface:
interface IWorker {
bool IsMessageForMe(object message);
string ProcessMessage(object message);
}
When a message comes in, each class that implements
IWorker is checked, and ProcessMessage is called if the IsMessageForMe passes.
The reason for returning string is logging, a very important thing when you can’t
attach a debugger.
Another common scenario, one which doesn’t involve
dynamic loading, is usage of more than one type of object on the same mission.
For example – ordering a list of objects by their names:
Interface IHaveName {
String Name {get;}
}
By the simple implementation of this interface to 2 types
of objects (i.e. boys and cows of the farm) I can sort them out. I don’t care
that a cow needs milking and the boy needs Facebook, I just want them in a
single line marching to the county fair.
So far I dealt with the when to use interfaces, how good
they are and etc… But the reason I’m writing this post is the abusage of
interfaces. So from now on I’ll stick to the don’ts. Actually the don’ts are
simple:
If you can write it without interfaces – DO SO! Interfaces
are for the times you can’t achieve the goal without the usage of this degree
of abstraction.
If you can refer to a specific object type – do so!
Abstract class is better than an interface in terms of referring to, it’s
better to refer to the correct type directly, but I do live in the real world.
Subscribe to:
Posts (Atom)