Hal Helms - Real World Object Oriented Development, Sarasota - Day Three
Today was a big day down here in sunny Sarasota, Florida. We spent the morning modeling the domain for a coffee shop complete with UML diagrams. To me, these are always the most fun exercises because we get to really pick apart what it means to be a given object and how that object can fulfill a user's needs - the ultimate test of any piece of software.
As we proceeded through this exercise, we gathered a good list of the actions a user would need to perform. We use this approach - a sort of mock "interface driven architecture" - because in the end, it's the needs of the user that actually provide the software specifications and not necessarily what we as developers assume to be the requirements. As I was contemplating this, I thought about what it means to add new user gestures.
If you accept the fact that user-needs dictate what the software should do, I think you can say that if you need to add a new user-desired feature, you are fundamentally changing what it means to be that particular application. And, if you accept that idea, then I think you can easily accept that when a new user-need crops up, it may very well require updates to the application API and underlying model. But, since the user-needs dictate the application, this update to the API is perfectly acceptable since it is, in essence, scope creep.
While walking down this mental path, a method popped into my head: getByFilter(). The getByFilter() method is a service / gateway method that I have seen in many ColdFusion applications that returns a record set based on a passed-in filterObject. Generally, this filterObject is used to dynamically build SQL statements with optional WHERE and ORDER BY clauses which then executes to gather the target data. I have never liked the idea of this method. And, unfortunately, I have never been able to articulate this feeling well; but certainly, there is a strong rejection in my gut.
As I held the thought of this method in my head along side the idea of user-driven requirements, I think I was finally able to identify why I never liked getByFilter(): the use of the filterObject rather than explicit arguments I think somehow implies that the API for the method is not known ahead of time. But, this goes against the development methodology in which we know the API based on a finite set of user-needs. So, I guess what I'm saying is that I don't like getByFilter() because it seems to solve problems that do not yet exist.
Now, I know what you might say at this point - that the getByFilter() method is used to reduce SQL code duplication; that it is, in fact, a factoring out of the parts of the SQL statements that change (the filterObject) for a given set of use cases (ie. in the context of a given Service class). Ok, but what if we moved that idea up one level to the service class itself? Why not just create a single service class that has a getByFilter() method and then add [table] to the filter object properties? This is a little bit contrived, but isn't this just a further factoring out of the parts of a SQL statement that change?
As I played this mental game, I couldn't help but think of Hal's DWIM tag. At CFUNITED one year, Hal mentioned that he really hoped the next version of ColdFusion would have the DWIM tag. When asked what that was, he replied that DWIM stood for "Do What I Mean." By this, he meant that it would be great if ColdFusion could just know what he wanted rather than making him do everything himself. The point being that this concept is so fantastical in its absurdity that it makes us realize the importance of really understanding the requirements of our applications and being able to thoroughly articulate them in some way.
With all of this stuff swimming around in my head, I started to come to the conclusion that the getByFilter() method was a sort of DWIM tag on a mini-scale. In that I mean that it's a sort of 'do what I mean' SQL execution. And, what's so offensive about this concept is that we are doing it when we actually do know the use cases as dictated by our user-needs; we are acting like we can't articulate our needs when, in fact, we can quite explicitly.
This all came to a head when we started looking at "words of wisdom" within the OOP world. One of the quotes we discussed was something along the lines of:
Always minimize the astonishment factor.
By this, the author meant that you should keep things simple and explicit - that you should prevent the developer as much as possible from ever being astonished by a piece of code. I immediately thought of this getByFilter() method and made a little "astonishment" comparison my head: what is more astonishing:
getByFilter( filterObject )
... or:
getRecentOrdersForUser( userID )
To me, the fact that getByFilter() can return a query of recent orders for a given user has a much greater astonishment factor that getRecentOrdersForUser().
But anyway, I have gone way off topic; I am just glad that the domain modeling we were doing in class put me off on a spin that helped me to articulate what it was that I didn't like about the getByFilter() method. During the rest of the class, we talked about Event Driven Programming (EDP) on the client with some very interesting uses of jQuery. I have to admit that I am having a hard time wrapping my head around event driven programming, but I can see that there are some very cool aspects to it. It is definitely something that I will need to look deeper into. The only concern I have about it right now is that it will distract me too much from further understanding object oriented programming which seems to be a prerequisite of EDP anyway.
With that said, I think I need to vegge out for a bit and let my mind swelling go down.
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
Reader Comments
A typical user requirement I see is an "advanced search" where many inputs are available for a user to search on. In most cases only one or two fields are searched on, allowing for delegation to a specific function. But you have to allow for all fields to have data which necessitates a getByFilter type of function with a dynamic where.
And so if that is going to be there anyway, does it not still make sense to have other more specific functions just let the above function do the actual SQL?
@Matt,
I suppose what I was reacting too was not just the getByFilter() method, but the use of getByFilter() AND the use of the filterObject. The filterObject allows for a lot more astonishment than explicit arguments in the getByFilter() method. There's only a limited number of possible fields that the user could search on, and certainly a limited number of fields to sort on.
Actually, now that I think about it, there's even less astonishment if the method would be called:
searchXYZ()
.. such as searchContacts(). With a name like that its way more obvious what the method does than a getByFilter() method.
I have a getByFilter() base method as it abstracts a number of implementation details. However, usually I'll create a getNewUsers() method or getBlueCars() or whatever. Both describe intent clearly, provide a good api and under the hood call getByFilter() so I can create the methods in one line instead of having a bunch of duplicated code. That said, on occasion I've been known to do a hack like:
RandomProductList = BeanFactory.getBean("ProductService").getByFilter(<criteria>)
It's hacky, but it is very quick when I'm messing with something and then I can go in and add the appropriate API method and controller logic to get this to the view a little more cleanly.
What this all really comes down to is a balance between speed and maintainability. I get a lot of projects where I build something, it goes live, it sits there for four years and then I build them something new. In such apps I've been known to take maintenance shortcuts and it really does allow me to add new features very quickly. For apps that are more likely to be maintained, I'll usually take the extra time to clean up the code and the api's, but it is important to remember that maintainability isn't actually a good in itself. It's simply one of the forces that will drive a particular design and needs to be balanced with performance, scalability, time to market and a bunch of other forces on any given project.
Cool series, btw. Wishing I was down there with you now!
@Peter,
When you think about the "intent" of a given user gesture, I am not sure how much code is actually being re-used in any given query. My guess is that as queries become more reusable, they actually become less specific to the task at hand. Meaning, in one query where I need to get an ID, first name, and last name, is my query returning just that? Or is it returning 15 other columns of data that are not needed by this user gesture (but perhaps are by others).
And yes, the question is always about the balance between speed, maintainability, purity, etc. So, I can see a practice use for the getByFilter() method. However, I would like to see it as always being a private method which is then used internally by other publicly available methods. This way, it is a utility of a given class, not a point of contact - you get the benefit of re-use along with the explicit alignment with your user goals.