Miscellaneous Debris

Avner Kashtan's Frustrations and Exultations

Over-Abstraction? A debate.

I've been having an argument with a co-worker of mine about the KeyboardHook code I recently published here.

His claim - by exposing a KeyboardHook object and letting users add Filters for specific keystrokes, I am exposing too much of my inner implementation that shouldn't interest the user of my library. Properly abstracted and refactored, the user should treat each KeyboardHook object as a specific keystroke-capture object, not have to deal with two different objects here. Various implementation details like explicitly calling Install() should be implicitly called whenever the event is hooked up to an event handler, rather than being left to the innocent caller.

While my code uses this:

KeyboardHook hook = new KeyboardHook();
hook.AddFilter(Keys.F, true, false, true);
hook.AddFilter(Keys.F6, false, true, false);
hook.KeyPressed +=new KeyboardHookEventHandler(hook_KeyPressed);
hook.Install();

He believes it should be more like this:

KeyboardHook firstHook = KeyboardHook.Create(Keys.F, true, false, true);
KeyboardHook secondHook = KeyboardHook.Create(Keys.F6, false, true, false);
firstHook.KeyPressed +=new KeyboardHookEventHandler(firstHook_KeyPressed);
secondHook.KeyPressed +=new KeyboardHookEventHandler(secondHook_KeyPressed);

My counter-claim is that while this may be more abstract and refactored, it may be taking it a bit too far. A Windows hook is an actual operating system object, not just a logical construct I create in my code, and there can be serious performance implications for defining one of them for each keystroke rather than defining just one generic hook for all strokes.  Furthermore, I want the users of my library to be aware of this difference, rather than hiding this information. I want them to be aware of the implications of having a hook installed, and allow them to install and uninstall it selectively.

His counter-counter-claim was that I can implement all my static methods to hide the actual system hook and have all my KeyboardHook objects actually receive their events from that one OS object. I can have a reference counting mechanism set up to allow me to disable individual hooks, and only uninstall the inner hook when all stroke-captures are disabled.

My prompt reply was that this might, indeed, produce rather clean and elegant code. However, it would also involve rewriting most of the existing code for this library for a relatively small and theoretical gain for the library user, and I'll be damned if I go and do that any time soon, and he's welcome to do it himself if he-thinks-he-knows-so-much.

The conversation generally deteriorated from that point onwards.

So, anyone have any comments on the current design or the proposed changes, the benefits and shortcomings of both?

Posted: Oct 06 2005, 04:12 PM by AvnerK | with 13 comment(s)
Filed under:

Comments

Omer van Kloeten said:

I think your design is more consistent with other design choices made in Windows Forms, for instance, the KeyPressed (and suchlike) events: There's only one event for any key that might have been pressed and you're supposed to sit there and call a switch on the key code.

I would, however, change the behaviour to a Singleton so that you could simply do this:

KeyboardHook[Keys.F6].KeyPressed += new KeyboardHookEventHandler(hook_KeyPressed);

And underneath it, I would have any implementation needed.

This way, you would not have to create objects and also have only one point of installation (instead of adding filters, event handlers and calling Install).
# October 6, 2005 9:54 AM

Avner Kashtan said:

First of all, this opens up the syntax hassle of having an indexer with multiple parameters:
KeyboardHook[Keys.F6,true,false,false].KeyPressed += ...

Secondly, this isn't really a singleton - the indexer would return 'this', I assume, the single KeyboardHook instance, while doing some plumbing beforehand to add a new filter for the given combination. This seems to be a problem, since a user expects an indexer to return an instance out of a collection, not return the collection instance itself while performing some side-effect code behind the scenes.
# October 6, 2005 10:03 AM

Avner Kashtan said:

To be a bit clearer - in your code, this:
KeyboardHook[Keys.F6] == KeyboardHook[Keys.F];
would return true - and I don't think that should be appropriate behavior.
# October 6, 2005 10:03 AM

James said:

I'd tend to agree with your co-worker that in theory his abstraction is preferable. With your implementation, logic has to be inserted into the event handling method to switch based on the key pressed, whereas with his implementation, there can be one handler method per logical event (i.e. F8_Pressed(object sender, EventArgs e), F10_Pressed(object sender, EventArgs e)). This would generally lead to easier-to-read code.

That being said, it is only a minor issue, nothing worth spending a lot of time changing.
# October 6, 2005 11:19 AM

Omer van Kloeten said:

First off, worst-case, it's just one more parameter for the indexer - a Flags enumeration containing Ctrl, Shift and Alt.

What I was thinking of is a redesign of the interface. I hadn't looked at the whole source before posting (meaning I had no idea what the 3 booleans were for :P), but now that I have, you could just create this:

KeyboardHook[Modifiers.None, Keys.F6] += new KeyboardHookEventHandler(hook_KeyPressed);

The exposed object would be a delegate (KeyboardHook is a delegate collection). When a key is pressed, you call the specific delegate, etc.
# October 6, 2005 11:29 AM

Rick Scott said:

Your co-worker that wants to take retardedly simple code and simplify it is...ummm...retarded.

You abstract when it will make things simpler. This is already REALLY simple to read, code and maintain and any more abstraction is intellectual masturbation.

Multiple Indexers? Reference counting mechanisms? I'm VERY glad I don't have to maintain the code of the folks that want to abstract further then your original code.

http://www.joelonsoftware.com/articles/fog0000000018.html
# October 6, 2005 12:02 PM

Dotan Dimet said:

Why separate calls for AddFilter/Create and setting the callback method?
The one advantage I see in your friend's method is that the user has a distinct callback for each key stroke he adds (alt_shift_F_keypressed_hook, for example) rather than a single callback that has to handle all the different keystrokes (presumably in some switch statement?)
Otherwise, it doesn't gain the user much (still two calls), and by messing about behind the scenes with reference counting instead of letting the user do his own resource management (calling KeyboardHook::new and KeyboardHook::install), you increase the chance of a problem in your code while decreasing the ability of the user to find it.
# October 6, 2005 6:56 PM

Scott Allen said:

I prefer your original implementation - the hook itself is the primary abstraction, and a single hook can have multiple filters. It makes instant sense to me, whereas the proposed change has me looking hard to see what is happening.
# October 9, 2005 12:35 AM

Co-worker said:

# October 8, 2006 6:58 AM

Miscellaneous Debris said:

This one was suggested by a coworker leery of accidental recursion. Too many people aren't used to thinking

# June 3, 2007 7:34 AM

Miscellaneous Debris said:

This one was suggested by a coworker leery of accidental recursion. Too many people aren't used to thinking

# June 3, 2007 7:34 AM

Thekla said:

Give please. Order is not pressure which is imposed on society from without, but an equilibrium which is set up from within. Help me! I can not find sites on the: Noah ark baby bedding. I found only this - <a href="baby-bedding.net/.../">toddler baby bedding</a>. Bedding, provides, dave bergerdave: provide you for your occurrence. Bedding, tourism is far few in discovery bay with a download of bodybuilding reasons. Thanks :confused:. Thekla from Bhutan.

# March 24, 2010 9:12 PM

weblogs.asp.net said:

426743.. Slap-up :)

# May 27, 2011 3:01 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)