Thursday, January 26, 2006

Lots of changes coming up!

Yes, yes, I know I haven't updated in a while. We had a lot of "push back" from potential customers who said "Wow! That's an awesome product! I love it! Too bad I can't possibly afford it in a million skillion years!" (I''m paraphrasing a bit here)

The core problem wasn't the cost of the software, of course, but the hardware. Even Pocket PC's were pricy at $200-250 a piece at the low end, while Tablet PC's apparently occupy some rarefied ethereal plane, far beyond the comprehension of those operating within the mundane constraints of a sensible school budget.

So we went back to the drawing board and completely revamped the product. Actually, it's more like we came up with a whole new product which is actually much better than the last one, since it now addresses not only the handwriting niche, but almost every subject taught in our fine educational system. It's new and improved! Better, cheaper, faster! With a new minty-fresh flavor!

Any way, I was in something of a rush to get the first demo ready, so I actually did it real quickly, without taking time to write posts as I went along. If we go through some more iterations before the trade show in March, I'll do my best to put them up though. Definitely some more posts coming soon.

Sunday, November 20, 2005

KLOC

Point of interest: the application currently consists of 181 files containing a total of 25,113 lines of code (i.e. not counting comments and empty lines (according to this handy Line Counter program anyway)). Had I been writing on standard 8.5x11 paper in a 12-point font, thereby fitting 45 lines to a page (according to Word), that would be 25,113 / 45 = 558 pages, or a decent-sized novel.

Sobering, especially considering that I scored higher on my verbal SAT's than on my math (800 vs. 650). But that was a loooong time ago, and my English skills have no doubt long detiorated since. If the readership of this blog is any indication, anyway ...

"End of major coding operations"

My posts have been a bit sparse since I've entered into a stage which hopefully won't progress like its paraphrased namesake. ;) Right now I'm doing some foo-foo design work, creating and collecting some sounds and images to go along with the program, as well as creating the letters and lesson plan which will flesh out the bones of the program - the content if you will. Aside from an occasional jump back into the code of the aptly titled Designer program to add a new labor-saving feature (and the occasional bugfix), the bulk of the program is done.

[Waits for bolt of lightning, meteor strike, etc.]

The last thing to do, and this step really can't start until all the content is in place, is to compile the setup programs and associated batch files (for the apps using MSDE/SQLServerExpress). I can't believe it, but it looks like we just might make our December 1st deadline. Yeeeesss!

Friday, November 18, 2005

VS.NET 2005 RTM

I sort of promised to move to the RTM this week, but circumstances beyond my control have dictated otherwise. To wit, I can't get the thing. I had the impression that since it launched on November 7th it was mine for the ordering, but Amazon et al won't ship it until the 28th. I guess that launch date was for MSDN Universal subscribers only, but that's too rich for my peasant blood.

Oh well. The beta still works just dandy :)

ControlCollection.Clear is stupid

In one point in the code we create a number of controls in a panel, which are later removed by calling the panels Controls.Clear method. The controls have a Dispose method, which I recently noticed was not being called. When I had originally coded it, I had assumed that Controls.Clear actually called Dispose on its internal list, since the documentation didn't state otherwise, and it seemed logical. Consulting the web again, I see that they had updated the docs to warn you that, no you do need to call Dispose - looks like I wasn't the only one with the problem.

I do sort of understand that you may have a reference to the control elsewhere, so calling Dispose isn't always apropos, but an overload like ControlCollection.Clear(bool disposeControls) would have been nice.

I next tried to naively run a foreach or foor loop over the collection, but that only did half of them, literally, because they pop out of existence (and hence disappear from the collection) as you Dispose them. This means that the collection keeps growing shorter, so the iterator variable will be moving too fast through the collection, so to speak.

Currently I'm using the following code, which seems to work, but it looks ugly to me:

while(panel.Controls.Count > 0)
panel.Controls[0].Dispose();

Monday, November 14, 2005

IFunctionalityExtender

A potentially very large customer recently asked us to add some informational screens to our product which are very, very specific to their company - it would probably be a copyright violation if we included it in our generic version, in fact.

While some people disparage the idea of adding features requested by (even large) clients, pointing out that this effectively turns you into a wage slave for another company, if you avoid VC funding (which we did) then you can make a lot more money than a wage-slave ever could without incurring the dreaded slave mentality (which pretty much results from having an overseer breathing over your shoulder).

Anyway, since this code would be specific to this client, and really didn't belong in the generic version, I decided to implement it in a separate DLL, in a class which implements a new interface called IFunctionalityExtender. How does the DLL get loaded? The generic program looks in its config file for an entry called IFunctionalityExtender. Typically this will be blank, and the program well then just move on. But when we ship the code to this client, we'll modify the config file in the installer to point to the full name of the class, and the DLL. Then the program can find and instantiate the class using Type.GetType and Activator.CreateInstance, cast it to IFunctionalityExtender, and call the appropriate methods (just StartUp and ShutDown for now) to bring the new functionality online. Since the custom DLL has no problem referencing the DLL's of the main program, it can easily hook into whatever events it needs to work.

Not only does this client get their desired functionality, but the pattern is general enough that it could be extended to many more customizations which future clients will hopefully ask for. A good day :)

Sunday, November 13, 2005

Subtly speedier

I'd forgotten just how quick Delphi could be. Wow! It's faster to hit F9 (took a while to queue up that muscle memory) and have it just show me the errors than to read through the line and look for it. VS.NET looks positively sluggish by comparison, but in subtle kinds of ways, like an almost imperceptible pause when moving around in the property editor, to really perceptible pauses when asking it to do anything refactoring-related.

In the interest of full disclosure, I should confess I'm still using Beta 2. I'll do a follow-up post after I move to the RTM this week.

Saturday, November 12, 2005

Delphi

So I have to produce some output using a font we received from an external source (yes, I'm being evasive!). The first problem was that it was in Mac format. After poking around with the very excellent CrossFont, and the not-as-excellent Adobe Type Manager, we finally got it at installed. And then our problems began.

Word can use the new font. Paintbrush can use it. Bloody freaking NOTEPAD can use it. But .NET? The fancy shmancy new system everyone's raving about? The 2.0 version, even? No it can't use it.

At first I thought I was just missing some setting on the FontDialog component that was preventing it from displaying. No matter that changing every single setting didn't do anything - could be a oversight, like they left out CF_BOTH in the CHOOSEFONT structure or something (the aforementioned Word-where-it-works, which said it was a printer font and might not display well on screen, indicating that the vagaries of the Apple to Windows transition may have marked it as such). So I try just passing in the name to the Font constructor. Boom, it falls back to the default MS Sans Barf.

Some googling indictes that the GDI+-reliant part of .NET which I'm using doesn't support embedded printer fonts - OK let's be honest, .NET itself doesn't support it because I don't call sending sending control codes back and forth from the printer support. I'm not even sure if I'm diagnosing this right, since this font doesn't technically reside on the printer - at this point it's an OpenType font with permanent residence on my hard drive. Either way it looks like a leaky abstraction to me?

But anyway the point of this post is that it's good to have more than one tool in your toolbox (if you have only one tool, why did you even bother with the toolbox, actually?). So I went to the dusty catacomb where my forlorn copy of Delphi had been sitting, abandoned and alone while I cavorted about with that C# strumpet, took a jackhammer to the door, swept her up in my arms, and ... took the analogy a bit too far.

Anyway, I'm using Delphi for this part of the program (so long as its Anders, eh?) and I couldn't be happier. Took a while to get back into the habit of using := and declaring all my procedures(/functions) before I write them, but other than that it's not bad. Nice to have back a lot of functions that I missed, like the ever-handy SelectDirectory

I do wish I it had some better intellisense, but that's not a fair complaint - I'm like 5 versions behind or something, and I'm sure they're at least on par with Microsoft's offering by now. Delphi was the first programmang language I fell in love with, and I think that no matter what I'm using in the future (like LINQ, oh-my-God-is-it-cool) my first infatuation will always have a special place in that tiny dark thing I call my heart.

Wednesday, November 09, 2005

Solving the webservice type-incompatability problem

After some consideration, I've decided to change the format of this blog. Instead of a blow-by-blow description of every little change to the program - which I now realize was tedious (for me) and unhelpful (for you) - I'm going to try to provide a more in-depth description of a select number of the more interesting problems (and sometimes solutions) I come across.

Problem: There are two target markets for our software, one for home and one for schools. The home version uses a local database (like MSDE or SQLServerCE - or the new SQLServer 2005 Express or Mobile), while the school version is a client which stores its data on a server via WebServices - the particular choice of a server will then vary according to the school's infrastructure and budget, from a full-blown SQLServer to MySQL to even MSDE or SQL Server 2005 Express again. That way the school student can move from device to device and find all their data (device here could be a PocketPC, so let's not talking about roaming profiles, eh?) Not a problem yet.

To support this we have an interface (IUploadLocator) which is implemented by two very different looking classes in two different dll's, one for the home version (LocalLib), one for the school (ClientLib). Each is driven by the same class (StudentManager, in a different shared library) and then each communicates hrough its respective channels to the same DBInterface class which knows how to talk to any underlying SQL DB (a story for another day). Is this a problem? Let's examine the chain:

StudentManager -> LocalLib -> DBInterface -> Local SQL DB
StudentManager -> ClientLib -> ASP.NET WebService -> DBInterface -> Server SQL DB

Looks OK, right? Well it was until I threw this into the mix. There are two methods which take a record (StudentsPet.UploadLetterRecord) from StudentManager to feed into DBInterface. Now, when LocalLib sends that to DBInterface, all is well. But when ClientLib tries to send it, ASP.NET screws everything up. It publishes the record from DBInterface as its own type (StudentsPet.Student.localhost.UploadLetterRecord) instead of the common type defined in the DBInterface .dll, as God intended it.

Proposed Solutions: Now I could just unwrap this records into a number of individual parameters, but that creates a a manageability nightmare considering how many layers it traverses (I actually simplified it above), and the fact that I might want to add to the records in the future. Using a DataSet, which somehow manages to flit across ASP.NET boundaries with its type intact, is out both for performance and ugliness reasons.

There must be some way to explain to ASP.NET, perhaps on the server, perhaps on the client, that this type is really that type, no? Applying attributes such as XmlType and SoapType didn't seem to do a thing. I tried changing the namespace, but then I realized that wasn't going to work because it really is a different class.

One system which worked was to extend the partial class in another file (so it won't keep getting wiped out when the webservice updates, duh) with an implicit conversion function. This worked, but it could introduce subtle errors if I, for instance, add a field to the real version and forget to copy it over in the converter.

Solution: In the end I used a helper method in the record together with a delegate to handle the unwrapping for me, but it doesn't read nicely (taking the method name as a parameter? What is this, Lisp?) and it still has some manageability issues - none which are likely to introduce subtle errors, but issues none the less. I don't like it, but it's the best I have for now.

That was my first post under the new system. Rather inauspicious in that the solution was less than satisfactory, but I think it will be able to get me back into the context quickly if I need to review this code again in the future.

Friday, November 04, 2005

Backtracking

After adding the maxwarn/maxcrash feature, I promptly realized it had a major flaw. Since the decision to make the student repeat the lesson was being made on the server, it was completely transparent to the student on the client-side. As far as he was concerned, the stupid program was broken, repeating the same lesson over and over again for no reason. Not good UI.

I'm going to change it to decide on the client-side if the lesson was satisfactory after it finishes, which has the additional benefit of isolating maxwarn/maxcrash to that one config file - it was a bit confusing in two places anyway, wasn't it? Then it will tell the student if he needs to repeat it again or not, and store the satisfactory bit on the server so it knows as well and will supply the lesson again.

Wednesday, November 02, 2005

FileNameSupplier

Very cool. I got to create this nifty little internal class (FileNameSupplier) which implements IEnumerator and treats all existing matching filenames as a list of sorts, so it could be foreached when loading and deleting. In retrospect a search function could have done it better than my Counter++/String.Format calls for finding old instances. On the other hand the code to get a new, unused filename would have to include most of the code I wrote anyway, so no big savings that way.

It's a bit hard testing race conditions between multiple program instances, but then I doubt the scenario will appear many times. Good enough for version 1.0.

Enough of these piddling little tasks. Tomorrow I've got to tackle one of the biggies, either the reports or the alternative data store. The latter is more fun technically, but the former is more important to the project. Hmm, hmm, hmmm.

Single instance limit?

I started writing up code to limit the app to run as a single-instance, when I realized that I couldn't figure out why I was doing it. Let's see.

On the PocketPC it kind of makes sense because you're memory-constrained. But you don't need to do any work there, because I think you actually can only run instance of an application by default anyway. There are all sorts of crazy restrictions on processes in that space, like only 32 at a time, memory sharing, tiny address spaces, paging out when going into the background - I can't wait until we have full-fledged Windows desktop in that form factor (I know they have such things, but I mean cheaply).

On tha Tablet it doesn't make sense at all. You can tell at a glance how many instances are running, and I think it just annoys users to not be able to launch as many as they want at a time.

The only rub is that my program doesn't currently handle the situation well. Specifically, if the program is disconnected from the server when it is closing, it tries to save a log of all the not-yet-uploaded letters to RecordUploadQueue.dat, where the can be loaded from later. But since all instances would use the same filename, they would be overwriting each other - not good!

What are my options? I could have them check for the existence of the file and save to something else if it's already there (like RUQ-1.dat, etc.). Then I would have to look for any files which could fit that profile when starting up and load them. Not a bad option. The program has to do more work, but it's less inconvenient to the user who might just want multiple instances. OK, let's do that.

Handle resizing and screen-orientation switching mid-letter

This is probably going to involve the tablet again for testing.

Actually, I'm going to start by reading up on how to best monitor these changes. Maybe I'll work on something else in the meantime.

Erase good lines with back of pen, part 3

Thought I'd forgotten about this, eh?

This was really easy to write, anyway. The code currently just iterates through the sections, finds the first one that matches the (inverted) pen coordinates, breaks out and erases it. I refactored this into a little function which takes the list of sections as a param, so that I could use the same code for warn and good sections alike (crash sections don't need to be erased manually since they are always temporary and disappear when the pen is lifted up).

Testing was tricky. since I can't invert the mouse, and it seemed silly to simulate something so easy. So I fired up our tablet (Toshiba M200 - one day I have to figure out how to use the accelerometers programmatically). I first tried remote debugging, but it couldn't seem to connect. I started the Remote Debugging Service, and checked the Firewall, but it still couldn't connect. No matter, I'd installed the VS2005 beta on the tablet as well. Source control ensured we're all on the same page, and I started it up.

I quickly squashed a stupid bug left over from poor refactoring (I'm still getting a hang of the VS tools), and it worked beautifully! I also tested with ShowBadLines set to true, to ensure it could erase both warn and good lines alike.

The biggest annoyance was that I hadn't set up source control to check-in automatically on the tablet, so I couldn't figure out why the changes weren't being reflect on my desktop each time I closed VS. This went on for like 3 minutes straight before I figured it out! ;)

Wrap-up

Sorry for the messed-up formatting on the last post. Next time I'll do a screen-cap so it's legible.

Anyway, success on all counts!

On the server-side, updated the INSERT method with a few copy-and-pastes from the CompletedLetter which was right next to it to cover the Satisfactory bit as well, and ... done. Nice.

On the client-side, I added a Satisfactory property to the LetterRecorder, which returns true only if Warns and Crashes are less than the MaxWarns and MaxCrashes specified in the letter config. Oh yeah, had to add MaxWarns and MaxCrashes to the config class, with the same defaults is in the earlier post (of course I have my own config class. There is actually a very good set of rationalizations for this seemingly frivolous and redundant ego-gratification junket, which I promise to explore in another post). Er, also had to fix up some binary-serialization code. Actually. this section did turn out to be a lot of work, but nothing strenuous :)

Check out this SQL

Thought the programmer's in the audience would appreciate the hideousness:

select top 1 LessonRef from SyllabusEntries
where SyllabusRef =
(select ISNULL(Students.SyllabusOverrideRef,
(select DefaultSyllabusRef from Grades where GradeID = Students.GradeRef))
from Students where StudentID = @StudentRef )
group by LessonRef, SyllabusRef
having COUNT(LessonRef) >
(select ISNULL(
(select COUNT(LessonRef) from LessonRecords
where CompletedLesson = 1
AND StudentRef = @StudentRef
AND GradeRef = @GradeRef
AND LessonRef = SyllabusEntries.LessonRef
AND EXISTS
(select * from LetterRecords where
LessonRecordRef = LessonRecords.LessonRecordID
AND CompletedLetter = 1
AND Satisfactory = 1
AND Warns < (select MaxWarns from Syllabuses where SyllabusID = SyllabusEntries.SyllabusRef) AND Crashes < (select MaxCrashes from Syllabuses where SyllabusID = SyllabusEntries.SyllabusRef)) group by LessonRef), 0)) order by MIN(LessonOrder)
Ah, who am I kidding? There is no audience.

Last post (the one with a long title so I decided to leave it out ... hey, this title is long now also), continued

First I added the new columns (Satisfactory to LetterRecords, MaxWarns and MaxCrashes to Syllabuses), which went smooth as silk. Defaults set to 1 (true), 5, and 3 respectively for now.

After totally freaking out in looking at a 15-line SQL statement, I parsed through it fairly quickly and inserted a simple new EXISTING condition into the LessonRecords WHERE clause, with a subquery which looked up the LetterRecords. This failed because it turns out the Syllabuses table wasn't included in the earlier nested queries, and I need it for MaxWarns and MaxCrashes. D'oh! So I added two more nested queries, one for each of MaxWarns and MaxCrashes, each doing the same basic lookup inside the Syllabuses table using the SyllabusEntries to supply SyllabusRef, which fortunately had been included before.

Well, sort of. SyllabusEntries was included, but not SyllabusRef, so I just threw it into an earlier GROUP BY clause. God alone knows why GROUP BY helps for that, but it says it does and it does.

Yes, it worked! I know this wasn't the most elegant SQL, but I was going for functionality not prettiness at the moment. I can fix it up later. The only real nasty thing is the two subqueries into Syllabuses (I guess I could cache it in a variable, or include the whole table earlier), but honestly I don't think it matters performancewise. SQL is certainly smart enough to cache for me, and anyway Syllabuses is a small table. Ditto for the EXISTS employing (select * from ...), it seems logical that they wouldn't really return all columns if they know it's only for an EXISTS clause. I have more faith in the SQL Server programmers than that.

So far so good! Next, the local client-side override in the config file.

Determine number of warns/crashes allowed and still advance to next lesson

The tricky thing about this feature is determining the granulity. Putting it in the global config file, with the same setting applied to every user, is almost certainly too coarse. Per-syllabus could work, but I could also see wanting individual lessons or even letters to have their own requirements. What to do, what to do? Why let's do both!

The determination of what lesson to suggest next is done server-side (currently: in the future I plan to factor that code into a separate library which can be loaded either server-side or client-side, to better accommodate standalone home users (who had not been planned for originally)). This code uses probably the only complicated SQL statement in the whole system! Currently it just uses the CompletedLesson column in the LessonRecords table to determine if the user has completed a particular lesson. It should be simple to change this code to also check the LetterRecords table associated with that LessonRecord entry (by the LetterRecords.LessonRecordRef column) to make sure there aren't too many Crashes and Warns (columns in the LetterRecords table, again).

To override, the letter-config file (which I also plan to allow to be stored per-lesson in the future, killing two birds with one stone) will be equipped with "MaxCrashes" and "MaxWarns", each with a default of Int32.Infinite (or maybe 50 or something; we should have some standards). When >0, these values will set a new column called, I don't know, "Unsatisfactory" on the server, which the server-side code could then check to see if it should proceed. This way it doesn't have to load and process the whole letter (and user-side config may not even be available to the server).

Let's see if it pans out as prognosticated.

Tuesday, November 01, 2005

Irony, definition of

Sorry for abandoning my blog right after posting how it finally did something productive for me! It's still working out great. Here's the scoop.

With the help of EvaluatorEvaluator, I finally realized that the real problem was that, after entering the warn state because the user had strayed off-path according to the warn parameter values, the higher tolerance of the crash parameter values went and revalidated the position. Then the greedy nature of the algorithm allowed it to zoom right through all the missed checkpoints, bring it right back up to whatever point the stylus was up to just then. So of course, the only time it would stay in the warn state for more than one point was just before it was about to pass over into crash territory. And since there is a minimum of two points before a line can be drawn (basic geometry, people!) I never even saw the warn state during a visual inspection.

The new algorithm basically doesn't let the position move forward at all when in the crash state. It virtually solved all my problems in a second. It's beautiful now. And having a set of repeatable, visual tests has made me more confident about the evaluator algorithm than I've been since it was written. The XP guys are onto something with all their confidence talk (though I'm not sure this is quite what they had in mind: I understand they dislike tests based on external data files. Tough.).

Monday, October 31, 2005

EvaluatorEvaluator

I'm reaping the first tangible results from having started this blog! Having the flimsy rationalization at the end of my last post staring me in the face has compelled me to actually create something I should have had months ago: EvaluatorEvaluator, a program to evaluate new evaluator algorithms (I suppose a program to test the EvaluatorEvaluator would be called EvaluatorEvaluatorEvaluator...)

So far I'm thinking of having it look in a directory for union-files, each file combining a .trc file (one of the tracing models, used to determine what should be drawn) with another file containing a record of mouse motions (easily extractable from DB where they are stored right now after each use). One nice idea would be to specify which .dll to use, each .dll containing a different implementation. I'll look into what .NET has on that.

I'm interrupting the current bug fix to work on this because I think it will help me fix the bug even better once it's done.

As an aside (post-post this time), we fnally have our cable back up! The cable guy came by as I was carrying half of a tree out to the curb (it fell into, and completely demolished, a screened porch). I actually got to help bring the Internet back to my little corner of the world, directing traffic and pulling cables like some high-tech hunchbacked bellringer. I rarely have to stifle the urge to hug middle-aged men, but I am glad to say I rose to the stifling. It was a good thing for both of our sakes.

FoundMatchForwards fix

Aside: Another interesting morning! We've been making a conscious effort to help our neighbors in need to be friends in deed (or something like that - just try to not have them hate us). Here's the story of one of them who just left. He had gotten a chunk of local vegetation lodged in his hand while trying to pull a collapsed tree of his power line. This developed into a nasty infected finger which necessitated a trip to the emergency room, who bandaged him up but then informed him that they were plumb out of tetanus shots! Seems he wasn't the only one to come in with similar maladies, big surprise. Anyway, he had to go see his "primary care physician" (are there secondary ones too?), and had left a message to make an appointment, when his cell phone ran out of juice. A tragedy of errors. He left the cell to charge, and we fixed him up with some delicious cereal and milk for the road. Now, back to work!

I hate our evaluator algorithm. I find its performance to be erratic, nonlinear, and unpredictable, and the code even worse. This is not a good state of affairs, because I wrote the thing, and my bias is supposed to flow in the other direction. Partially this is not my fault (you knew that was coming): it started as a simple algorithm, which then had so many additional features and edge-case-handling code stapled onto it, that it remains unrecognizable. Somewhere underthere is a simple algorithm yearning to be free, and I'm going to stage a jailbreak.

The first decision is that I'm going to keep the class hierarchy above the algorithm. Currently all evaluators descend from BaseEvaluator, and at run time the static BaseEvaluator.GetEvaluator method returns the appropriate descendant instance using
Activator.CreateInstance. Actually, there is a bit more indirection using a factory so that I don't need to make so many costly Activator.CreateInstance calls, but whatever. While this is great because it lets me swap out the algorithm (that was some GoF pattern, I think) by changing the config file, the gain looks minor at the moment because only one evaluator is really functional, i.e. the TracingEvaluator. AngleEvaluator didn't meet great applause, and NeuralNetworkEvaluator is v2 pipe-dream (needs a lot more training data). But I'm keeping it anyway because it's already written and it isn't hurting anyone.

First I did a quick rename of two config values which should have had the TracingEvaluator prefix a while ago, but didn't for historical reasons; and a poorly named high-level method (i.e., back in the BaseEvaluator). Recompiled successfully. A good start.

The meat of the problem seems to be the angle check. The difference between a warn and a crash is supposed to be the distance tolerance, and the angle check was supposed to be a minor modifier (see below). But in fact, the angle check has turned out to be the primary reason a warn or crash fails, which means they both (almost always) occur at exactly the same time. Now, the angle check is there because the algorithm is greedy in gobbling up checkpoints. It is greedy in case the user goes slightly off-path, and we need to bring him up to speed quickly to stay in synch. But this has the side effect of advancing past the checkpoint the user should be up to, sometimes all the way to the end of the line. To prevent that, an angle check was added which ensures that when going forward we don't go ahead of an imaginary line drawn perpendicular to the expected direction of the expected line, provided the expected line is predictable (i.e., fairly straight, not a curve). Currently this code is ... screwed up.

I'm going to fix it and play around with it, but I really won't know that it's performing right until I can run a repeatable set of tests around it. The tricky thing is, I never sat down to design a comprehensive set of mistakes to make for various levels, and where and when these mistakes should be reported. They're also subject to various config parameters which would dramatically impact when an error would be reported, if at all. I'm just not sure if there is a right answer, and if there is, I doubt if I personally know it. We probably need a handwriting OT pro on staff. But that's another story.

Sunday, October 30, 2005

Erasable with back of pen, part 2

The first thing I found out was that, with the current default distances chosen for the warn and crash parameters, it is very tricky to input a line which the evaluator algorithm decides has "warned" but not "crashed", and hence will stick around to be erased later. However, when I did find the sweet spot, the lines erased just fine.

Upon further examination, it turns out that the user at the show wanted the student to be able to erase good lines, not just bad ones. Our original spec hadn't expected that, so the program was actually performing as expected. Fortunately, communication on our team is usually pretty fast because we're pretty small (though it a little slower today since I'm working on Sunday, and I had to wait for my co-founder to get back from his boating trip ...), and we quickly decided to provide an option to let users erase good lines in addition to bad ones.

Unfortunately, this changed my foray from a bug-fix into a feature-addition ... which means my self-imposed limit won't allow me to continue right now. :)

Along the way, I actually uncovered another minor bug in the evaluator algorithm (where the backwards crash value was being used to allow the warn event not to trigger), so I think I'll start pursuing that.

Erasable with back of pen

As an aside, I lost the first version of this post, ironically when I hit the "Save as Draft" button, because our internet connection was temporarily down. Due to Hurricane Wilma knocking out our cable, we've been reduced to dial-up on NetZero, which hangs up occasionally for no discernable reason. Not that I'm complaining, mind you. We weathered the storm a lot better than our neighbors, 50% of whom don't have electricity back yet.

It is typically considered better practice to fix bugs before adding new features, even if those new features seem more important (and certainly if just more fun :)) than the bug. One rationale is that allowing bugs to accumulate can have unintended side-effects elsewhere when they are already fixed. Another is that solving the bug earlier means the code is fresher in your mind.

But what if the bug seems to be rather isolated, and highly unlikely to effect other parts of the system? And what if the bug was actually coded a long time ago anyway, and was only noticed recently, so it's as forgotten as it's going to get? The current situation happens to meet both those criteria, but I'm fixing it first anyway because ... well, because it's been sitting at the top of my todo list for a while and it's starting to "bug" me. Good a reason as any.

This bug was noted during our last trade show (also our first trade show - I'll have to write that up some time), when someone asked if the student could erase the lines. The person manning the booth said sure, just turn the pen upside down (many Tablet PC pens have eraser functionality when inverted, and it is a sensible model, but for some reason no one ever assumes it's there - the new hardware makes it unintuitive I guess). With the dark god of product demo's looking over their shoulders, the customer of course turned the pen upside down, rubbed it over the tablet, and was very impressed when nothing happened. Hey, at least it didn't *crash*.

The unit-testing fanboys would probably bemoan this whole state of affairs, but it's arguably a UI functionality bug, and I've never been impressed with the UI testing/screen-scraping toolkits. So, today I'm diagnosing it the old-fashioned way, by trial and error.

First thing is of course to replicate the bug. I don't think I can do that on my development desktop, so I guess I'll go fire up the Tablet PC now. Let's see what happens in the next post.

Saturday, October 29, 2005

First Post!

Sorry, couldn't resist a bit of Slashdot humor :)

Hi, my name is Aryeh Holzer. I'm the co-founder of FirstHand Software, a small technology company developing penmanship instruction software for the consumer and school markets, for use on Tablet PC's and Pocket PC's. We think we have a rather unique approach to this field, which is outlined in more detail on our website.

The software has actually been under development for a few months now, but we've kept it quiet since we didn't want anyone to bring something similar to market until most of it is done. I hate to start you off in medias res because of what may be paranoia, but hey, it's our livelihoods on the line here. And on top of all that, we also filed a patent on some of the bits we think are rather innovative (I know, I know, let's not get into software patents - let me just say I was overruled and leave it at that).

My expectations from this blog are quadruple-fold:
  • Forcing myself to provide a (hopefully) daily report on my progress will make sure I don't slack off. I do have some other projects which can distract from time to time.

  • Making some of my decisions open to public scrutiny (at least in theory), might compel me to really think them out better. The very act of talking things out sometimes reveals underlying flaws, in addition to its intrinsically therapeutic qualities.

  • Having a living document of the torturous path the architecture has taken might be helpful to future programmers - at the moment I am the only developer on "staff" (and I use the term rather loosely).

  • All the cool kids have a blog. Some of the not cool ones too. I feel left out.


What might you gain from this blog? Maybe nothing. I'm an autodidactic programmer (as I am in many other areas), so I don't know that I've learned to do anything the "right" way. On the other hand, I've been programming for 8 years now, and my work seems to be not altogether shoddy. So who knows? Worst case, maybe you'll see what *not* to do.

Well, I think that's enough words for now. I'll start in earnest tomorrow (the war cry of dieters everywhere). Seeyaround! :)