Profilo di BenNo Brain, No PainFotoBlogElenchiAltro ![]() | Guida |
|
30 ottobre Be wary of layersI was actually planning to write about layers in the next couple days, but I found this article on layers referenced on Scott Allen's blog, so I figured I'd share some of my thoughts on software layers now, instead of later.
Layers are a massively powerful abstraction tool, but come with an extremely high cost--each time a new layer is introduced, initial development effort roughly doubles, since non-trivial, problem domain-specific data will have to be coded to pass through both the existing code and the new layer.
Worse still is that the ability to debug and maintain code seems to go down exponentially with each additional layer. I'm sure most developers have played the "trace the layers" game while trying to fix a software bug--you start at the view layer, since that's where the bug manifests itself, potentially go through the controller layer (if you have one of those), then through the business objects layer, then the data layer, before finally discovering that someone put the wrong column name in a query in a stored procedure. But hey, look at the good side--you've spent your whole afternoon on it, so now it's time to go home! :-p
The one layer I unequivocally support is dividing any business logic from the view code--I have never, ever seen an instance where tying business logic in the form code resulted in a better piece of software. On the flip, I have seen dozens of examples where business logic hard-coded in the view layer needed to be accessed by code in other parts of the application.
Beyond that, I get sceptical. I'm still not totally convinced that a separate data layer is a sure-win design decision, which always draws blank stares from other developers. Yes, I agree that compartmentalizing the data access code in a single place is definitely a good thing, but is that enough to justify its own layer? I think so, but just barely.
Ack, I can almost hear someone out there saying, "We need a data layer for abstraction, in case the database changes from Oracle to SQL Server..." My answer to that is 1) businesses only rarely change databases post-implementation for a given product and 2) ADO and ADO.NET already provide a database abstraction layer.
In the end, my suggestion is this: start with a view and business layer, and only create additional layers when you are confident that the substantial increase in code complexity is outweighed by the logical advantages of the new layer.
Ben Stepping back a secondThis is actually getting pretty funny here, I've started and erased this entry about seven times now. The problem I'm having is that there's a bunch of interconnected software thingies on my mind, but I can't figure out which one should come first--every time I think I know, I find myself wanting to reference a different topic, so I'd erase everything and restart, only to realize that I now needed to reference another topic, and so on, and so on. Then it hit me that maybe I should bring up the event that really got me really taking an objective approach to software design and process. It's funny, since it was a pretty trivial encounter, but many years later it still has a big impact on me. Anyway, at the time (I think around 1998?) I was employed at Geneer, working on a large multilayered Delphi project when a change request came through to make a trivial change to the program, something on the order of adding a field for "middle initial" on an order entry screen. I'm not sure who came up with the time estimate, but it came back as 16 hours or so, a two day change. I don't remember why the issue got escalated, but in the end Doug (the Geneer CEO) burst in my office, asking "why the hell does it take two days to make a simple change?" So I justified the time estimate, pointing out that we'd have to change the data table, then the stored procs that save/retrieve the data, the data access layer, the data broker layer (a caching scheme we were using), the form, the controller, blah blah blah. And Doug looked at me and asked, "Isn't all this stuff supposed to make development easier?" I just stood there, dumbfounded. He was right, the software we had created was a maintenance nightmare, with simple changes taking days to complete and substantial changes introducing so many obscure bugs that it took weeks to weed them all out. But the irony of it all was the software had been carefully architected and designed to be more maintainable! Our design doucmentation certainly looked tidy and elegant, but when it was actually coded up, it ended up achieving the opposite. I came away with three main things from that conversation:
Ben 26 ottobre Seriously, is it 1999 again?Recently I've been getting the feeling that some folks are forgetting the lessons of the past, and returning to the "irrational exuberance" that marked the late 90's.
Case in point, for today: Oracle downloads Red Hat Linux from the Red Hat FTP site, strips out all the Red Hat branding, recompiles it and *bingo* announces they'll support it for half the cost of Red Hat.
Now, I have to admit that's pretty darn funny in itself, but even funnier is Red Hat's response. Um yeah, right... having the same product sold for half the cost by a company with over 45 times (!!!) your revenue is a good thing. Seriously guys, that school of thought was proved a fallacy back in 2001.
Unfortunately for Red Hat, the rest of the investor community didn't quite agree with them, wiping out $895M of shareholder value in 24 hours.
I'm impressed.
Ben 25 ottobre Things that make me laughDo an internet search for "reusable framework" ;)
So many reusable frameworks. So little reuse. I wonder why...
Ben Is Windows Workflow a disaster? Part IIHopes... fading... quickly...
In my previous post on Windows Workflow, I complained about the lack of worthwhile tutorials... I started thinking about what would make a good tutorial, and immediately a helpdesk ticket application came to mind, since it's typically and extremely simple, well-defined process.
So, I start searching for WF helpdesk examples/tutorials, and come across this one, by Dino Esposito. Score! I've read Dino's articles in the past, and have always been impressed.
Unfortunately, my impression of Windows Workflow took another hit :( Here's my issue: in the article, they define an ultra-simplistic example of a helpdesk ticket with a few basic parameters, and three basic states--created, closed, or escalated. If I were writing this, I'd probably do something on the order of the following:
enum TicketState {Created, Closed, Escalated};
class HelpDeskTicket
{
public readonly ProblemDescription;
public readonly UserName;
private TicketState currentState;
public TicketState CurrentState
{
get {return currentState};
}
public HelpDesk(string userName, string problemDescription)
{
this.ProblemDescription = problemDescription; this.UserName = userName; this.currentState = Created;
}
public void Escalate()
{
this.currentState = Escalated;
}
public void Close()
{
this.currentState = Closed;
}
}
Downright simplistic, no?
In the article they don't quite create a ticket class as such, but nonetheless implement similar functionality (CreateTicket, UpdateTicket) that create/update rows in the database. But, they also create two new entities, the workflow itself and a "glue" interface, IHelpDeskService. So, in very broad terms, they've tripled the number of classes I would have written.
If I'm tripling the number of classes I'm writing, I sure-as-heck better be getting a huge return on functionality. But I'm not sure what the return is... Yes, you get a graphical view of the workflow, it's cute and all, but do they really think that it's something non-developers would use? For one, I'm still struggling to grasp exactly how to use it and two, if they think it's something where "we can modify the workflow without writing any code!", umm... seriously, are you serious?
For an example of my latter point, let's say the users decide to add a new state, "On Hold". Of course, they'll have to modify the workflow in the designer. Then they'll have to modify the IHelpDeskService interface. Then they'll have to implement support for the new event in the HelpDeskService class. So much for "no programming", eh?
My request to Microsoft: create a simplistic helpdesk ticket application with WF that reduces code, not increases it. And, if you can't, redesign WF so it does reduce code... Otherwise, what's the point?
To reiterate--this is not a criticism of Dino, I think he's outstanding. Rather, I'm saying that if he can't crank out a convincing WF demo, then something's massively amiss.
Ben 24 ottobre Minor Descent updateHey hey, this marks the fifth consecutive evening I've worked on my Descent code. Started the evening finding and fixing a bug where the ship would bounce while sliding along a wall, and also got basic lasers working again.
Actually, getting lasers working again was pretty fun as I *thought* I had some reasonable code behind them before. Ha ha ha, my memory had failed me as the old laser code I had was hackneyed beyond belief.
The new code is decent, I have a list of what I call "live objects", which now includes robots, powerups, and laser bolts. I have to admit, even though I'm not a real big object-oriented guy (I was at one time, but my opinions have changed some over time), I find using inheritence much, much cleaner than what the Descent guys originally did with structs and unions. Not faulting them at all; I'm sure they stuck with C for speed, but a few good ideas came out of the whole OO movement. :) See, I've admitted it!
One thing that is frustrating me a bit is that something is up with my orientation matrices--I had to swap the rows I thought were for the look-at and right-hand vectors. While I'm very comfortable with some of the 3D math (dot products, cross products), I still don't have a good understanding of how the orientation matrices actually work in practice. :-/ Need to do some hand calcs with them.
Ben Is Windows Workflow a disaster?The last several days I've been looking into the .NET 3.0 Windows Workflow stuff, and the more I dig, the more I realize that it's really, really ugly. Perhaps as ugly as OLE was (you do implement the OLE persistance interface whenever you write data to disk, right? :-p)
The first problem I'm running in to is that the samples and tutorials out there are just worthless--they're so dumbed-down that it's not obvious in the least how one would apply Windows Workflow to an existing, real-world process. To me, it's even worse that the Direct3D examples out there, where I found it pretty tough to figure out how render a Descent level from an example showing how to render a single spinning triangle. Btw, this book is what got me over the hump on that.
Here's the part that's getting me real nervous--I'm running into blogs where people are trying to justify the complexity of Windows Workflow, saying that since the problem domain is complex, Windows Workflow is necessarily complex.
Umm, sorry, but that's BS.
For example, this introduction discusses the workflow of processing an order at a pizza place. There is no possible way you could ever convince me this is a difficult problem domain. Thousands of teenagers (very likely half at below-average intelligence!) take orders and make pizzas every day across the US. I'm sure I could sit down with my 11-year old and within 30 minutes she would have a strong handle on the process of taking an order, making the pizza, getting paid for it, etc.
Yet, it's not obvious in the least to me how I would apply Windows Workflow to this process. I can identify the sequence and activities, but I keep drawing a blank when it comes to how I would actually implement this process in code. And this is about as simple a process as you can have...
It doesn't seem like I'm alone with this problem, either. I'm seeing a bunch of blogs by people far above average intelligence, who appear to be struggling with Windows Workflow.
My first thought is that Microsoft picked the wrong places for abstraction, and then over-abstracted them to boot. But, that's just an initial impression after looking at Windows Workflow for a couple days.
One of my pet peaves is complaining about something without having a suggestion for improvement, so I'll through out a rough first take at what I think would be a better solution:
1) Create an interface that implements methods such as EnumerateAllStates() and GetCurrentState()
2) Have objects that participate in a workflow implement this interface
3) In the workflow runtime, define the processes that move objects from one state to another.
Obviously, there's a lot to flesh out, but I think the paradigm is much easier to understand--we're just adding explicit state to our objects.
Hopefully over the next couple days I'll get a better handle on WF and will follow up. 20 ottobre Stupid stuff that's just... stupidWhoever at Microsoft came up with ".NET" as name for their managed framework should be fired. To me, it would seem to be a basic concept that a product name should be compatible with the company's file system.
I was just going through my IE favorites and wanted to group a bunch of links in a folder seemingly appropriately named ".NET 3.0 resources," which results in a "You must type a file name" error. So alternatively I have to come up with a stupid workaround name, like "DotNet 3.0 resources." Brilliant!
Actually, I'm just jealous that the person who came up with the .NET name probably makes a hell of a lot more money than I do...
Ben 18 ottobre The Holy Grail of Software DevelopmentAs I might have mentioned before, I have a lot of thoughts on how software could be "better." Last night I was trying to come up with the most logical sequence to present them, when it dawned on me that they're all kinda pointless unless I could demonstrate that they indeed make software "better." To do that, though, you'll need some sort of metric that you could use for comparison.
First, though, a bit of background. I'm sure most developers have seen software projects go horribly wrong: over budget, behind schedule, buggy, slow, bloated. But every now and then I'd see a disasterous project like this, yet there'd be a group of people who would extol the project like it was the greatest thing since sliced bread. Typically these people would be executives, but often the developers themselves would convince themselves that writing 1,327 lines of code to display a string from a database onto a web page was perfectly normal. And usually behind it all was an incompetent architect with outstanding persuasive speech skills (admittedly, a pet peeve of mine, but mainly because my persuasive skills are... lacking).
This confounded me, though; how could these people not see what a mess the project was? Then it hit me--how would they know better? The answer is they can't--unless they have a way to quantify how good, overall, a piece of software is. Thus I propose to you: The Software Goodness Metric.
Probably my favorite example metric is a car's fuel efficiency, expressed in miles per gallon of gasoline. It has many of the hallmarks of a great metric: simple definition, easy to calculate, easy to verify. It's also easy to use and effective. For example, if you're buying a car to drive to work and back, which one will have lower gasoline costs? The car that gets 40MPG or the car that gets 15MPG?
Like all metrics, it's not perfect. For instance, what's potentially more efficient: A 2-seat car that gets 30MPG, or a 5-seat car that gets 28MPG? But, overall, it's an excellent, useful metric.
So, back to the Software Goodness Metric. I propose a number that represents the five attributes:
Let's also define that metric such that the metric increases in value as software gets "better". So, we want to maximize functionality, performance, and quality, and we want to minimize the development effort. In other words: SoftwareGoodness = ((amount of functionality) * (performance factor) * (quality factor)) / (development effort) Wow, that was easy! Only one problem left: how do you come up with numbers for each of the terms on the right-hand side of the equation? I think "development effort" is the easiest one to quantity, as you could use total man-hours as a reasonably good value for this. It's not perfect, but I think it's certainly good enough. Beyond that, though, things start getting really nebulous. I think you could define "quality factor" as something like the percentage of working functionality compared to total functionality, perhaps measured by the number of successful test cases divided by the total number of test cases. I don't like that, though--it seems far too subject to the quality and quantity of test cases, not the actual software itself. Unfortunately, I currently don't have a better answer. "Performance factor" is another tough one. For user-facing applications, it probably should be the reciprical of response time, in seconds. For back-end applications, though, it would be transactions per second. But how do you normalize these values such that you could use the same metric to compare a back-end processing with a user interface application? Currently, I don't know. "Amount of functionality" is also really difficult. A decade-some ago the rage was function points, but it turned out those weren't as accurate as the people who wrote the books said they were. :-p I keep wondering about scanning through the code and counting up the number of operations, but really this value needs to be a macro one--what the system operator sees, not the micro value obtained by scanning through code. I'll be honest, I'm drawing a real blank with this one. So, what's the bottom line? I think the concept is good, but a method for calculating actual, objective values for the metric is still a long way away. If someone could come up with the calculation, though, I think it would revolutionize software development. I know it's something I'll be tinkering with for the rest of my life. In the meantime, though, not all is lost. Although we can't calculate objective values, we can still use the metric subjectively--that is, when looking at two different software solutions to the same problem, we can ask ourselves, "which will have higher performance?" or "which would take less time to develop?". Ben 16 ottobre Thoughts on software qualityHere's something I've always meant to get down in print, but never actually got around to it. And, since I don't have a ton to do at the moment (still waiting for a PC at work, on a borrowed laptop for the time being), now's as good a time as any ;)
One of the things I've (along with everyone else) struggled with is software quality. Clearly, the goal is to write bug-free software, but even though that's everyone's intention, it certainly doesn't occur as often as we'd like.
Several years back I was trying to think of things I could do to improve the bug-free-ness of my code, and it got me thinking back to what a coworker (specifically, Paul Jurgens) had mentioned to me at my first job. I forget the exact conversation we were having, but I do remember one statement he made: "a C program is just a state machine."
That's certainly an obvious statement, but it was enough to remind me that you can always look at a program strictly as a series of states, with operations that move from one state to another. Not having a formal CS background sometimes makes things like this an epiphany for me :-o
So, thinking in terms of a program as a state machine, how do you define a bug? Simple: a bug occurs when a program enters a state for which the code was not programmed to handle. Actually, there's two types of bugs: the other type is where the state is handled, but the logic is incorrect. For example you need to add two numbers, but subtract them instead. I'm not as concerned of this latter type because 1) I think they are discovered pretty quickly and 2) I have no clue how you'd minimize errors like those ;-)
The basic goal of software testing is this: get a program into as many states as possible, in order to weed out the states have haven't been programmed correctly. And, as I think any software QA person would tell you, that is not a simple task. Any program beyond a simple Hello, World program is bound to have hundreds, if not thousands, of possible states. That's what makes software testing so tough--it takes a huge amount of effort becuase there's so many potential states to test, and even then, there's no way to guarentee that you've actually hit every possible program state.
Luckily, I'm not trying to solve that monster. Rather, I was concerned with how to eliminate as many potential bugs as possible when writing code. And, to that end, I came up with the following thought:
By minimizing the number of states a program can be in, you minimize the number of potential defects.
Here's an example: consider the following code: class Employee
{
public string FirstName;
public string LastName;
public void PrintName()
{
Console.WriteLine(FirstName + " " + LastName);
}
}
Obviously, a very simple class. But consider the following code that utilizes it:
public void SomeFunction()
{
Employee emp = new Employee();
emp.PrintName();
}
Doh! The PrintName() call throws an "object reference cannot be null" exception.
Just for fun, let's count the number of potential states this class can be in. It turns out that this isn't quite trivial, as each variable could have several potential states, depending on the code logic such as:
1) Uninitialized (not a worry in C# and other languages, but certainly an issue with C programs)
2) Initialized on the low side of valid values (e.g. a count that should be between 1 and 5, but set to -1)
3) Initialized on the high side of valid values (e.g. setting the count from the previous state to 7)
4) Initialized to a valid value.
For our example, though, we'll consider a valid FirstName or LastName value as anything that's non-null and has a length greater than 0. That gives us the following potential class states:
1) Both FirstName and LastName are null.
2) FirstName is non-null, but zero length; LastName is null.
3) FirstName is non-null and length greater than zero; LastName is null.
4) FirstName is null; LastName is non-null but length zero.
5) FirstName is null; LastName is non-null and length greater than zero.
6) Both FirstName and LastName are non-null, and each has a length greater than zero (this is the only correct state!)
Yikes, that's a lot of potential states for a class is about as simple as possible. Imagine the number of states if the object contained a dozen or more attributes!
One way to address the situation would be to modify the PrintName() function as such:
public void PrintName()
{
if (FirstName == null)
throw new Exception("FirstName cannot be null");
if (FirstName.Length == 0)
throw new Exception("FirstName cannot be blank");
if (LastName == null)
throw new Exception("LastName cannot be null");
if (LastName.Length == 0)
throw new Exception("LastName cannot be blank");
Console.WriteLine(FirstName + " " + LastName);
}
Certainly, this will work, but it's not a great solution--mainly because any time you want to use either FirstName or LastName, you'll have to validate them. And, if you ever forget to validate one of them: Bingo! You've just created a bug!
Instead, let's consider how we could reduce the number of states the class can be in. What if we refactored the class a bit and added a constructor:
class Employee
{
private string FirstName;
private string LastName;
Employee(string firstName, string lastName)
{
if (firstName == null)
throw new Exception("FirstName cannot be null");
if (firstName.Length == 0)
throw new Exception("FirstName cannot be blank");
if (lastName == null)
throw new Exception("LastName cannot be null");
if (lastName.Length == 0)
throw new Exception("LastName cannot be blank");
this.FirstName = firstName;
this.LastName = lastName;
} public void PrintName()
{
Console.WriteLine(FirstName + " " + LastName);
}
}
By making the attributes private and adding the constructor, we've reduced the number of possible states the class can be in from 6 to 1! Better still, the one possible state the class can now exist in is valid (as far as our definition that FirstName and LastName must be non-null and have a length greater than zero). So, you'll never have to worry about bugs caused by an invalid FirstName or LastName.
Not bad...
Ben
13 ottobre First day at the new jobSo, today was my first day at Riverside Publishing. Pretty typical first day, not a lot to do other than try to remember peoples' names, which, of course, I can't do for the love of God.
Things look promising, though. It's a drastically different environment than CDW was, that's for sure.
The good news is that one of they guys who works there was a big Descent fan, so hopefully he'll keep me inspired to continue working on the code.
Ben
11 ottobre ALMS addendumTwo things I meant to mention in yesterday's post, but forgot to (hey, it was late!):
First, I scored Ron Fellows' autograph on my Covette Racing cap, woohoo! I caught him after the Speed GT race and he was still in his Cadillac driver's suit, but he was extremely pleasant. Perhaps it turned out to be good luck for him, because he won his class in the ALMS race a couple of hours later. :)
And, the other thing that struck me at the race was all the IMSA "World Class" patches on everyone's driving suits--they really live up to their "World Class" billing. Fantastic drivers, beautiful (and loud!) cars (unlike the ugly Grand Am DP cars), spiffy paddock area: it all added up to a great day of racing.
Do you get the impression I really enjoyed the ALMS race? haha
Ben Catching up someActually meant to keep up with posts this summer, but didn't :) Surprise, surprise :-p
Anyway, I'll go roughly in order. Back in late August, I drove up to Road America with my friend Kurt to check out the ALMS race.
I was impressed. I will come back. ;)
Probably the neatest part of the race was the sounds of the various cars that made up the field--the Audi R10 was almost completely silent, while the rest of the LMP1 cars had an atypical muted turbo sound. Other cars were deafening loud, with the Porsche RS Spyders and the BMW inline-6 at the top of the decibel chart (using my build-in "gosh darn my ears really hurt" monitor). Of course, my favorite race was between the GT1 Corvettes and Aston-Martins, and that was cool as heck with the Aston V-12 just screaming by, chased by the low rumble of the 7 litre Corvettes. I was a bit surprised, I figured Corvettes would scream, too, but apparently they keep them under 5500 RPM. I bet the old Corvette Challenge races with the L-98s didn't sound all that different.
More suprising was that the Ford-powered Panoz were even more rumble-y than the Corvettes. Hmmm... I gotta find out more about the ALMS rules.
The crowd was a bit different than the Champ Car crowd I'm more familiar with at Road America. For starters, the Corvette Corral fills completely up, as opposed to the 1/3-to-1/2 full it gets for Champ Car. I guess there is some truth to the "win on Sunday, sell on Monday" saying... I guess it just took me a bit by surprise because I just go out to races to watch the competition, regardless what's being driven or who's driving. But, apparently a lot of people like to support their chosen marque more than I. :) I will say the amount of Corvette shirts, jackets, and hats seen around the track was amazing.
In the end, the Audi R10's won, which was a bit anti-climatic. The Dyson guys were pushing like mad, but I got the impression the R10 guys were running more like 8/10ths--and keeping ahead of the Dysons. :( What I don't understand--how can VW/Audi win Le Mans year after year, but they can't make a reliable road car to save their life? :-p My Jetta was in the shop 7 (or was that 8?) times in 2.5 years, one of my friends bought a used GTI last month, it's already in the shop, another guy at work as a TT which racks up repair bills so much it's staggering.
Oh well, I digress. ALMS is good stuff, if you like auto racing at all, go to an ALMS race. You'll like it :)
Ben |
|
|