Monday, March 08, 2010 

Why you should learn AOP

A few days ago, I've spent some time reading a critic of AOP (The Paradoxical Success of Aspect-Oriented Programming by Friedrich Steimann). As often, I felt compelled to read some of the bibliographical references too, which took me a little more (week-end) time.

Overall, in the last few years I've devoted quite some time to learn, think, and even write a little about AOP. I'm well aware of the problems Steimann describes, and I share some skepticism about the viability of the AOP paradigm as we know it.

Too much literature, for instance, is focused on a small set of pervasive concerns like logging. I believe that as we move toward higher-level concerns, we must make a clear distinction between pervasive concerns and cross-cutting concerns. A concern can be cross-cutting without being pervasive, and in this sense, for instance, I don't really agree that AOP is not for singletons (see my old post Some notes on AOP).
Also, I wouldn't dismiss the distinction between spectators and assistants so easily, especially because many pervasive concerns can be modeled as spectators. Overall, the paradigm seems indeed a little immature when you look at the long-term maintenance effects of aspects as they're known today.

Still, I think the time I've spent pondering on AOP was truly well spent. Actually, I would suggest that you spend some time learning about AOP too, even if you're not planning to use AOP in the foreseeable future.

I don't really mean learning a specific language - unless you want/need to try out a few things. I mean learning the concepts, the AOP perspective, the AOP terminology, the effects and side-effects of an Aspect Oriented solution.

I'm suggesting that you learn all that despite the obvious (or perhaps not so obvious) deficiencies in the current approaches and languages, the excessive hype and the underdeveloped concepts. I'm suggesting that you learn all that because it will make you a better designer.

Why? Because it will expand your mind. It will add a new, alternative perspective through which you can look at your problems. New questions to ask. New concepts. New names. Sometimes, all we need is a name. A beacon in the brainstorm, and a steady hand.

As I've said many times now, as designers we're shaping software. We can choose many shapes, and ideally, we will find a shape that is in frictionless contact with the forcefield. Any given paradigm will suggest a set of privileged shapes, at macro and micro-level. Including the aspect-oriented paradigm in your thinking will expand the set of shapes you can apply and conceive.

Time for a short war story :-). In the past months I've been thinking a lot about some issues in a large CAD system. While shaping a solution, I'm constantly getting back to what I could call aspect-thinking. There are many cross-cutting concerns to be resolved. Not programming-level concerns (like the usual, boring logging stuff). Full-fledged application-domain concerns, that tend to cross-cut the principal decomposition.

Now, you see, even thinking "principal decomposition" and "cross-cutting" is making your first step into aspect-thinking. Then you can think about ways to bring those concerns inside the principal decomposition (if appropriate and/or possible and/or convenient) or think about the best way to keep them outside without code-level tangling. Tangling. Another interesting name, another interesting concept.

Sure, if you ain't using true AOP (for instance, we're using plain old C++), you'll have to give up some oblivousness (another name, another concept!), but it can be done, and it works fine (for a small scale example, see part 1 and part 2 of my "Can AOP inform OOP?")

So far, the candidate shape is causing some discomfort. That's reasonable. It's not a "traditional" solution. Which is fine, because so far, tradition didn't work so well :-). Somehow, I hope the team will get out of this experience with a new mindset. Nobody used to talk about "principal decomposition" or "cross-cutting concern" in the company. And you can't control what you can't name.

I hope they will gradually internalize the new concepts, as well as the tactics we can use inside traditional languages. That would be a major accomplishment. Much more important than the design we're creating, or the tons of code we'll be writing. Well, we'll see...

Labels: , , ,

Friday, April 25, 2008 

Can AOP inform OOP (toward SOA, too? :-) [part 2]

Aspect-oriented programming is still largely code-centric. This is not surprising, as OOP went through the same process: early emphasis was on coding, and it took quite a few years before OOD was ready for prime time. The truth about OOA is that it never really got its share (guess use cases just killed it).

This is not to say that nobody is thinking about the so-called early aspects. A notable work is the Theme Approach (there is also a good book about Theme). Please stay away from the depressing idea that use cases are aspects; as I said a long time ago, it's just too lame.

My personal view on early aspects is quite simple: right now, I mostly look for cross-cutting business rules as candidate aspects. I guess it's quite obvious that the whole "friend gift" concept is a business rule cutting through User and Subscription, and therefore a candidate aspect. Although I'm not saying that all early aspects are cross-cutting business rules (or vice-versa), so far this simple guideline has served me well in a number of cases.

It is interesting to see how early aspects tend to be cross-cutting (that is, they hook into more than one class) but not pervasive. An example of pervasive concern is the ubiquitous logging. Early aspects tend to cut through a few selected classes, and tend to be non-reusable (while a logging aspect can be made highly reusable).
This seems at odd with the idea that "AOP is not for singleton", but I've already expressed my doubt on the validity of this suggestion a long time ago. It seems to me that AOP is still in its infancy when it comes to good principles.

Which brings me to obliviousness. Obliviousness is an interesting concept, but just as it happened with inheritance in the early OOP days, people tend to get carried over.
Remember when white-box inheritance was applied without understanding (for instance) the fragile base class problem?
People may view inheritance as a way to "patch" a base class and change its behaviour in unexpected ways. But truth is, a base class must be designed to be extended, and extension can take place only through well-defined extensions hot-points. It is not a rare occurrence to refactor a base class to make extension safer.

Aspects are not really different. People may view aspects as a way to "patch" existing code and change its behaviour in unexpected ways. But truth is, when you move outside the safe realm of spectators (see my post above for more), your code needs to be designed for interception.
Consider, for instance, the initial idea of patching the User class through aspects, adding a data member, and adding a corresponding data into the database. Can your persistence logic be patched through an aspect? Well, it depends!
Existing AOP languages can't advise any given line: there is a fixed grammar for pointcuts, like method call, data member access, and so on. So if your persistence code was (trivially)

class User
void Save()
// open a transaction
// do your SQL stuff
// close the transaction
there would be no way to participate to the transaction from an aspect. You would have to refactor your code, e.g. by moving the SQL part in a separate method, taking the transaction as a parameter. Can we still call this obliviousness? That's highly debatable! I may not know the details of the advice, but I damn sure know I'm being advised, as I refactored my code to be pointcut-friendly.

Is this really different from exposing a CreateSubscription event? Yeah, well, it's more code to write. But in many data-oriented applications, a well-administered dose of events in the CRUD methods can take you a long way toward a more flexible architecture.

A closing remark on the SOA part. SOA is still much of a buzzword, and many good [design] ideas are still in the decontextualized stage, where people are expected to blindly follow some rule without understanding the impact of what they're doing.

In my view, a crucial step toward SOA is modularity. Modularity has to take place at all levels, even (this will sound like an heresy to some) at the database level. Ideally (this is not a constraint to be forced, but a force to be considered) every service will own its own tables. No more huge SQL statements traversing every tidbit in the database.

Therefore, if you consider the "friend gift" as a separate service, it is only natural to avoid tangling the User class, the Subscription class, and the User table with information that just doesn't belong there. In a nutshell, separating a cross-cutting business rule into an aspect-like class will bring you to a more modular architecture, and modularity is one of the keys to true SOA.

Labels: , , ,

Thursday, April 24, 2008 

Can AOP inform OOP (toward SOA, too? :-) [part 1]

Note: I began tinkering with this idea several months ago, inspired by some design work I was doing on a real-world project. Initially, I thought I could pimp up this stuff into a full-fledged article. Months came by and I didn't, so (in the spirit of my old concept of Blogging as Destructuring) I thought I could just as well say something here instead.

Consider a web site offering some kind of service to users. Users have to register (yeah :-), and they can then buy a subscription to several services. A simple class diagram can model this easily:

In most cases, the underlying database schema wouldn't be much different.

In a real case, there might be different kind of services, each requiring a derived class, but we can ignore this issue right now. Also, there might be several kind of subscription (time-based, pay-per-use, and so on), but again, let's ignore that issue right now, and just concentrate on time-base subscriptions.

A subscription can be renewed at any time. In the object model, this would translate into a Renew method in class Subscription. Renew could take a parameter, like the extension of the renewal. Most likely, it would add a new record to the Subscription table (to keep track of the whole subscription history for that user), and possibly create a new Subscription object. So far, so good.

Now the marketing guys come up with a nice idea: whenever you register, you can provide the email address of a friend who has already registered. If you do, everytime you renew a subscription your friend will get some kind of gift, like a free extension or whatever. This may generate a few more leads, meaning a little more business.

From a purely OO mindset, this may lead us to perform a little maintenance on the existing model. We can add a relationship from User to User to model the "friend" relationship, or we could just add a field like friendEmail (possibly left empty). At the database level, adding a field is probably easier.
We can also modify the Renew method to check for the presence of a friend in the subscribing user, and if so, invoke some Gift logic. I won't draw a modified diagram for this scenario: I guess it's just too obvious.

Now, this approach obviously works. However, you may recognize that the whole "friend gift" concept is a cross-cutting concern: it cuts through User (requiring new data) and through Subscription (requiring new logic). More on detecting cross-cutting concerns during analysis and design (and on the difference between cross-cutting and pervasive concerns) next time.

In the AOP world, we could approach the problem differently. We could define a FriendGift aspect. The aspect may add a new data member to the User class (the friendEmail), and intercept the persistence logic to save / load that data from the database. The aspect may also intercept Renew and perform the Gift logic if required.
Actually the aspect doesn't have to modify User (class and table); indeed, it would be better not to, as the persistence logic might not be easy to intercept (more on this next time). The aspect could just use a different database table to store the friend email.

Using an UML-like notation, we could model this approach as:

Interestingly, the database is probably different from the previous scenario: Friend would map to its own table.

What if we are not using an AOP-enabled language? For instance, we might be using plain old C#. Can we still borrow some ideas from the above? I believe so. In the same sense as OO thinking can inform traditional structured programming, AOP thinking can inform traditional object oriented programming.

If we don't have pointcuts, join points and aspects, we may still have events (in C#/.NET, or callback in other languages, and so on). Sure, we have to forego obliviousness (which might not be so bad: more on this next time). But we can come up with a more modular and decoupled design by reasoning along the AOP lines:

No rocket science here :-). Just a different form, same function. Different database, a more general Subscription class, unaffected by a business rule which may change at any time. Also the User class and table are left unaffected.

More on this, and a few comments on the SOA part, in just a few days (I hope :-).

Labels: ,

Tuesday, June 19, 2007 

Client Side-Augmented Web Applications

In the last few posts I've been writing a lot about AOP, and very little about what I'm doing every other day. It's plain impossible to catch up, but here is something that has kept me busy for quite a few days lately: Client Side Augmented Web Applications (I should file for a trademark here :-). What I mean is a regular web application, that can be regularly used as a stand-alone application or, when you install some additional modules on the client, can also interact closely with other applications on the client side (e.g. the usual office suite, and so on).

Naturally, that means web pages must have a way to send data to client side application, and to obtain data from the client side application. For several (good) reasons, we wanted this data exchange to be as transparent as possible to the web app developers. Also, we didn't want to write different web applications (regular and augmented). That would have had a negative impact on development and maintenance times, and it could also have proven to be an inconvenience for the users. This had some impact on page navigation design, which could be an interesting subject for a future post.

Now, I can't get into the specifics of the project, or disclose much about the design of the whole infrastructure I've designed and built (yes, I still enjoy writing code :-). However, I can show you the final result. If you want your ASP.NET page to obtain (e.g.) the filename and title of your Word document, and send back to Word a corporate-wide document number, all you have to do is add a few decorated properties in your .aspx.cs source file, like:

public partial class DocumentProperties : System.Web.UI.Page
  public string Filename
      return filename;
      filename = value;

  [AskClient( "title" )]
  public string Title
      return title; 
      title = value; 

  public string Description
      return description;
      description = value;

  // here goes the usual stuff (methods, data members)

that's it. The attributes define the property name as known on the client side; the invisible infrastructure will take care of everything else. In a sense, communication between the web application and the client side application has been modeled as a virtual machine concern, and the attributes are used to tell the virtual machine where interception is needed. Of course, this is only the tip of the iceberg. There is a lot going on under the hood, also on the client side, as your browser and your favorite application are usually not good friends, and to be honest not even acquaintances.

Is my abstraction leaky? Sure it is. To make all that stuff working, you also have to drop a non-visual component into your page at design time. That component, if you follow the virtual machine metaphor, is indeed the implementation of the (server-side) virtual machine layer that will deal with the communication concern.
If you don't drop the component into the page, the virtual machine layer just won't kick in, and your attributes will stay there silently, stone cold. This is a functional leak that I'm aware of, and that as a designer I have accepted. In fact, all the alternatives I've considered to avoid this leak had some undesirable consequences, and keeping that leak brought the best overall balance. Besides, there are ways to somehow hide the need to drop the control into the page (like using page inheritance or a master page), so it's really no big deal.

A final reflection. I do not believe that something like this could come out of refactoring code that was simply meant "to work". It's relatively trivial to make a web application and a client-side application talk. It's quite different to make this talk transparent. Having prototyped the first option (just to make sure a few ideas could actually work) I can honestly say that without the necessary design effort (and skill), it's extremely unlikely to come close to the final result I got.

Time to drop a few numbers: overall, I've spent roughly 20% of total development time experimenting with ideas (throwaway code), 35% designing, 30% coding (this includes some unit testing), 10% doing end-to-end testing, 5% debugging. Given the relative novelty of several techniques I adopted, I should actually consider the 20% prototyping an inherent portion of the design activity: you can't effectively design much beyond the extent of your knowledge. Sometimes, the best way to gather more knowledge is to talk; sometimes, to read; sometimes, to write code. Of course, I was looking for knowledge, not for code to keep and cherish, so I happily scrapped it away to build much better code. In the end, that's often the difference between intentional and accidental architecture.

Labels: , , ,

Sunday, June 10, 2007 

Metaprogramming, OOP, AOP (Part 3)

We can now take a look at localization from the AOP perspective. While doing so, I'll use the Virtual Machine Metaphor I introduced in a previous post. Just like before, my goal is not to solve the problem per se, but to explore the designer's reasoning when a specific perspective (or metaphor) is adopted.

AOP, like OOP, is not a universal solution to programming problems. There is a set of problems that can be tackled more effectively using AOP, just like there is a set of problems that can be tackled more effectively using OOP.
When we design the OOP way, for instance, we often try to capture polymorphic behaviour (functional and non-functional behaviour), so to maximize the extensibility of the resulting design.
We often hear that AOP is mostly about non-functional, cross-cutting behaviour, like tracing, exception handling, transactions. The problem with the functional/non functional distinction, however, is that sometimes the distinction is a little blurred (I mentioned QoS in a previous post).
Now, is localization non-functional? I would say so, but this could be open for debate. Is localization cross-cutting? As we saw in the previous post, it usually is, although with some effort we can encapsulate it neatly in a LocalizedString class (assuming we're designing the base framework; we can't retrofit this solution without changing the source code). Again, it seems a little blurry.

Let's try the Virtual Machine Metaphor and see if it gets any clearer. It's really quite simple: all you have to do is ask yourself, is it theoretically conceivable to build a virtual machine that, when running my program, will take care localization? If your guts answer is yes, then you can go further with the metaphor, without getting down to the gory details of pointcuts and advices (or other implementation techniques) too soon.

A virtual machine, to be any useful, must intercept some events and add (or change) behaviour. For instance, your operating system is acting as a virtual machine when it provides you with page-based virtual memory.
Therefore, to make localization a virtual machine concern, the virtual machine needs a way to intercept either your (read) access to localizable strings, or the graphical rendering of localizable strings. In both cases, it has to return (or render) the localized string instead.

Here we see need for the first AOP tenet: quantification; see the paper from Filman and Friedman that I mentioned in Some notes on AOP. We must teach the virtual machine where interception is needed; in other words, the localization behaviour of our virtual machine must be quantified over the type system. This can be done (basically) in three ways:

1) Through a direct mapping with a type: this is akin to have a LocalizableString class. The virtual machine would then intercept any attempt to read a LocalizableString object, recover enough context, access a dictionary, and return the localized string (we still share the same problem with "localization context" that we had with OOP; I'll get back to this later.). It may seem more difficult to apply this strategy at the rendering level, as there is an unbound number of classes that will render text to the GUI, through an unbound set of arbitrarily named methods. As we did in the OOP case, however, we could try to look at a finer granularity: if there is a bound set of classes/methods in the supporting library where text can be rendered, the virtual machine could intercept those calls instead. Note, however, that at this point the virtual machine would have no way to know whether the text must be localized or not. Trace-based AOP systems may help, but when you have an hard time picturing a viable interception strategy for your virtual machine, it's probably better to look at one of the following alternatives.

2) Through decorations inside the application code. Examples of decorations are .NET attributes, Java annotations, but also the old-fashioned marker interfaces or plain ugly naming standards :-). The virtual machine would then intercept any attempt to read a regular string object, provided it has been tagged with some decoration. Note the subtle difference with above. You don't need to specify a type, but you do need to somehow tell your virtual machine where interception is needed. You keep your type system "clean" from the localization concern at the class level, but not at the data member level. Again, this strategy is fine for string access, but not so much for rendering.

3) Through explicit interception instructions, outside the application (functional) code. These instructions tells the virtual machine where a change in behaviour is required. They can be encoded in a separate XML file, or written in an AOP language like AspectJ. These instructions, in turn, can use some universal quantification (mostly through wildcards and regular expressions) or explicit naming of nonconforming instances. For instance, we may want to intercept read access to all string properties in all classes derived from a specific form type, plus read access in a finite set of classes, except a few properties we don't want to localize. The more powerful the quantification language you have, the better and more concisely you can define the set of points where interception is needed. Note that once again, intercepting rendering seems easy, but it's nowhere trivial to understand when localization is needed (more on this below). This may suggest, again without much low-level thinking, that intercepting string access is more viable than intercepting text rendering.

Each way has its own merits, and we can now look at them under the second AOP tenet: obliviousness.

Strategy (1), direct mapping to a type, allows for callee obliviousness (the callee being, in this case, the virtual machine). Indeed (still ignoring context), the virtual machine would have no need to know anything about the caller: it will just provide a uniform service every time a LocalizableString is accessed. There won't be, however, caller obliviousness, as the caller will have to use the appropriate type to enable localization.

Strategy (2) is very similar to (1), as far as obliviousness is concerned. The caller has to add decoration (no obliviousness). The callee (again, the virtual machine) will enjoy complete obliviousness: every tagged string must be localized with uniform behaviour.

Strategy (3) leads to caller obliviousness, and as such, is particularly interesting for legacy code (more on this on a future post about AOP, but if you compare the OOP and AOP approach about localization, this should strike you as the biggest difference!). The callee can still be oblivious. The explicit instructions, however, won't be. They are highly dependent on the caller, and if what you got is a legacy application, you're bound to have a non-uniform set of interception points. We may decide to go with low-level text rendering interception, trying to get caller, callee, and interception instructions obliviousness. This is not going to work. You'll need trace-based interception to discriminate when translation is needed, and the trace will be highly caller-dependent.

Just a few words on context: if you consider (for instance) the form name as the localization context, every strategy will need to recover the form name as well. I'll leave this as the dreaded exercise for the reader :-).

So here it is: by thinking about how a virtual machine could provide a service, we can quickly evaluate the fitness of AOP as a candidate solution for a problem; you can also reason about different interception flavors (depending on the implementation language, you may have more than one option) and evaluate pros and cons. The same metaphor can be applied in mixed situations (e.g. attributes or annotations + reflection), where we accept a few explicit calls in exchange for largely oblivious caller/callee in a traditional OOP language.

As we noted, the biggest difference with good ol' OOP lies in strategy (3), where no knowledge is stored at the type level (or data member level). There, we can appreciate a significant contribution of AOP to the evolution of existing systems, that's often forgotten in language design, but which is probably more interesting than factoring out logging and transactions :-). Stay tuned!

Labels: ,

Wednesday, May 16, 2007 

Metaprogramming, OOP, AOP (Part 1)

In a previous post, I introduced the concept of [Layered] Virtual Machines a a useful metaphor for aspect oriented design. I've further elaborated the concept in a later post, but I understand it's slightly elusive :-), so a realistic example could be beneficial.

In this post, and in the following 2, I'll take a look at the same problem under the metaprogramming, OOP, and AOP perspective. While doing so, my aim won't be to evaluate the respective merits of each approach. Right now, I'm more concerned about how any given approach will influence the reasoning process. Also, for AOP, I'd like to show how the Virtual Machine metaphor could be useful to think at the appropriate abstraction level.

I've chosen a problem that I hope will be familiar to many programmers: localization. To keep the discussion focused, I've further restricted the problem to GUI localization, and within that, only to text localization (leaving aside, for instance, bitmaps that may need to be localized as well).


As Gregor Kiczales said, The key property that all metaprograms share is that they explicitly manipulate other programs or program representations. Examples of metaprogramming include a code-generating IDE, so let's take a look at how Visual Studio 2005 is dealing with localization in .NET (Windows Form).

When you add controls to a Windows Form application, Visual Studio generates code (that's, of course, metaprogramming). If you add (for instance), a button, a listbox with a few preloaded values, and a listview with a few column headers, you'll get code like:

this.button1 = new System.Windows.Forms.Button();
this.listBox1 = new System.Windows.Forms.ListBox();
this.listView1 = new System.Windows.Forms.ListView();
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
this.columnHeader3 = new System.Windows.Forms.ColumnHeader();
// button1
this.button1.Text = "button1";
// listBox1
this.listBox1.Items.AddRange( new object[] {
"value3"} );
// column headers
this.columnHeader1.Text = "Column1";
this.columnHeader2.Text = "Column2";
this.columnHeader3.Text = "Column3";

String literals are placed directly into the code. That makes the code itself fast and lean, but won't help much with localization. In a while, we'll take a look at how the code changes if you set the Localizable property to true in the container (usually the form). The code changes, because in Visual Studio / Windows Forms, localization is being dealt with at the metaprogramming level. Is that a sound approach? As I said, that's out of scope, but to keep a long story short, metaprogramming at the IDE level has some nice properties: most notably, very high integration with the development tool itself. Assuming, of course, that you consider localization as an integral part of software development (I do not necessarily agree with that).

Leaving the soundness question aside, if you're a designer thiking about providing metaprogramming-based support for localization, you'll have to look into a few common metaprogramming issues:

- the programmer may inadvertendly tamper with the generated code. Microsoft answer was to introduce partial classes in .NET, which are obviously just a partial solution, but not that bad anyway.

- the programmer may have problems debugging some (possibly) obscure, machine-generated code. This was certainly an issue for ugly macro-based, forward-only code generators like the old style ATL COM wizards. For the relatively simple job of static GUI creation, the issue is rather moot.

- extensibility: what if the programmer rolls his/her own GUI controls? How will they participate in code generation, or ultimately, in string localization?

The latest issue is probably the single most important problem affecting many metaprogramming tools. When we design a metaprogramming environment, we're basically building a code generator. The input to the code generator is a language in itself, possibily a visual language. In this case, the input is the (visual) selection of a control from a palette of tools, plus a set of properties.

In the most naive approach, the code generator needs to "know" the GUI, that is, each control and its localizable properties. It needs to know that a button has a Text property that must be localized. It needs to know that a listbox may have design-time values that must be localized. It needs to know that a listview may have headers, and that they in turn have a text, which must be localized.

Under the naive approach, the designer of the metaprogramming environment is thinking about a closed world: the set of supported controls is fixed, because knowledge about the localization of each control is built inside the IDE itself. Needless to say, that's quite easy to do, but very limiting.

Once you open up to user-defined controls, you can basically follow one of the following approaches:

- The programmatic approach. Here the component takes part in code generation. Each component with localizable properties will have to implement some "code generation" interface, defined in what can be considered a metaprogramming framework. The IDE will call the appropriate functions, but the component will ultimately be responsible for code generation. This approach is very flexible, but uncomfortable. There is also the nontrivial issue of having code generated in a specific, high-level language (like C#; but what if I'm using the component in a VB.NET form?) or directly into a neutral, platform defined, low-level language (like MSIL).

- the declarative approach. Here the component will "tell" the metaprogram (through some mechanism yet to be defined) which properties need to be localized. The metaprogram will then work uniformously over all those properties, perhaps guided by type (e.g. a string Vs. a collection of strings). If you're familiar with .NET, attributes may come to mind as a way to "tell" the metaprogram about localizable properties.

Let's take a look at how Visual Studio 2005 deals with localized strings. Here is the former example, with localization turned on:

this.button1 = new System.Windows.Forms.Button();
this.listBox1 = new System.Windows.Forms.ListBox();
this.listView1 = new System.Windows.Forms.ListView();
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
this.columnHeader3 = new System.Windows.Forms.ColumnHeader();
// button1
resources.ApplyResources( this.button1, "button1" );
// listBox1
this.listBox1.Items.AddRange( new object[] {
resources.GetString("listBox1.Items2")} );
resources.ApplyResources( this.listBox1, "listBox1" );
// listView1
resources.ApplyResources( this.listView1, "listView1" );
// column headers
resources.ApplyResources( this.columnHeader1, "columnHeader1" );
resources.ApplyResources( this.columnHeader2, "columnHeader2" );
resources.ApplyResources( this.columnHeader3, "columnHeader3" );

The underlying idea is a mixture of reflection and metaprogramming. Metaprogramming is applied as, for each control, a call to ApplyResources is generated. Also, through metaprogramming, strings are not stored inside code anymore. Without going into much details, strings will be placed in resource-only DLLs (known as satellite assemblies), one for each language. Note that the choice of storing the strings into DLLs has nothing to do with the metaprogramming approach - they could have chosen to store strings into an XML file (I would have appreciated that).
Reflection is then applied inside a unique (uniform) ApplyResources method of ComponentResourceManager . This method looks for properties tagged with the Localizable attribute, then gets the localized text from the satellite assembly. If you want to find out more, you can read Improvements to Localization in Visual Studio 2005 and Localization in Whidbey. The latter describes also the original localization model for Visual Studio, which was based on design-time reflection, not on run-time reflection.
That's not the full story, however: note how the Items collection is being directly localized into the generated code, not through reflection. This seems more like a quirk, so let's skip that for now. Also, notice how ApplyResources is needlessy called on the listview as well, although there is nothing to be localized - only the headers really need to be localized. Again, this seems more like a small quirk, something that could be easily fixed.

At the end of the day, the declarative approach to metaprogramming extensibility is quite handy: you only have to tag your localizable properties with the right attribute, and the metaprogram will do the rest (supported by the run-time framework, especially under the run-time reflection model).
As usual, you can only get that far with a declarative model. Here, the need to localize a finite set of types (like string) is a huge simplification. Again, a good designer will look for the right balance of extensibility and rigidity, possibly providing alternative ways to deal with the most complex situations.

Just a final comment: there is a common problem with the adoption of a metaprogramming (or AOP with static weavers) approach to -ilities. Since the code dealing with the -ility (like "localizability") is either generated or woven as part of the mainstream code, the application will always be "bloated" by the ility, even if you don't need it.
Consider an application that will be used 90% of the cases in English-speaking countries, 10% in others. You want to provide localization, but ideally, you shouldn't pay for it when not needed (90% of the cases). In fact, a popular paper from David Parnas was "Designing software for ease of extension and contraction". Somehow, most people got the "extension" part, but just a few got the "contraction" part. Moore's law did the rest :-).

Next time, I'll take a look at how we could approach the same problem under a strictly object-oriented approach - no metaprogramming allowed. Then we'll move to AOP and the Virtual Machine metaphor.

Labels: ,

Monday, April 30, 2007 

Metaphors and Design

I've been arguing for a while that good architects (not software architects) are usually familiar with the work of other good, renowned architects. Not only do they know about their works, they often know the inner vision behind those works, mostly because many famous architects have taken the time to talk and write about their inner vision, their approach with materials, with space, with shape. If they didn't, some critics did (a profession we seem to lack in software design).

This careful scrutiny of works and visions is something we seldom do in the software realm. Many software engineers are too concerned with the latest newfangled technology to give a damn about what some guy said 50 years ago (or 5, or 1). That has rather dramatic consequences on the overall maturity of the field. Good design requires vision. Vision (almost by definition :-) won't come from blindly following a trendy technology.

Unfortunately, even if we do want to learn from great software thinkers, there is a dearth of valuable material. I've often suggested to get acquainted with the works of David Parnas, whose influence on modern programming paradigms is undisputed. I can also recommend to read some of the Edsger W. Dijkstra manuscripts.
A nice start could be My recollections of operating system design. Sure, you won't find anything about the latest trends, but hey, here he's talking about inventing the concept of interrupt, the difficulty of separation of concern, early concurrency, and most importantly, about a discipline of thinking.

An interesting passage: "[...] created a host of new problems, so new that we didn't really know how to think and talk about them and what metaphors to use".
Abstraction is a cornerstone of software development (once you outgrow small programs, where programming on the metal is often the best thing). Yet abstraction is not enough: we need to find the right metaphors; poor metaphors are poor thinking tools.

It is interesting to see how people often try to sidestep metaphors, looking for a more machine-oriented way of thinking. This can be useful when you are trying to get familiar with some new concepts: for many years, people have found it useful to understand the mechanics behind inheritance, as a way to learn some OOP concepts. Of course, you can't effectively think at that level all the time: sooner or later, the whole inheritance+polymorphism metaphor must kick in, freeing you from low-level concerns.

This concept is also central in a short paper by Gregor Kiczales (It's Not Metaprogramming) about a "direct semantics" for AOP.
Kiczales is right claiming that although both metaprogramming and AOP might be seen as ways to manipulate code (through code generation or by method interception), this is a low-level view, and we should move toward more abstract thinking to design AO systems.
Unfortunately, it seems to me that the usual AOP concepts are still somewhat too low-level to engage in abstract thinking and modeling, and that some progress is still needed to find the appropriate metaphors. Maybe they'll emerge as AOP patterns, as the AOP dictionary seems quite established by now.

Curiously enough, Kiczales talks about virtual machines as a metaprogramming concept, while I see them as valuable AOD metaphors (for some problems - it's not a universal metaphor). I think that the main distinction is that he's talking about building a virtual machine at the extra-linguistic level, while I'm talking about building a virtual machine (so to speak) at the programming language level. I'll soon elaborate this concepts in a few posts.

Labels: , ,

Sunday, April 22, 2007 

AOP and Layered Virtual Machines

I've been thinking about AOP and virtual machines for a while now. I don't mean adapting virtual machines to better support AOP: that's a relatively straightforward concept, and the subject of a wealth of research. I mean using virtual machines as a metaphor to think about AOP (or, better, AOD).

Let's start looking at the issue from a different angle. Unless you're working on a small embedded system, chances are you're already running your application on a virtual machine. I don't mean something like the JVM or the .NET CLR (although that might be the case). The operating system itself is presenting you with a virtual machine.

In many cases (virtual memory, I/O virtualization, etc) the virtual machine is intercepting your code at a very small granularity, doing some pre/post processing or routing your call to some piece of code. In those cases, your code is totally oblivious (caller obliviousness). Being the OS a general-purpose virtual machine, we also have callee obliviousness (see also my post, Some notes on AOP for a little more on obliviousness).

Let's stay with this concept a little more. The OS is also offering you a set of services which must be explicitly invoked: they can't be hidden through mere virtualization. If I want to open a file and write something, I've to open the file and write something :-). The OS can abstract away a lot of concerns (the file system implementation, the disk driver implementation, etc) but this is not a "ility" that can be somehow injected - it's a functional concern.

Or maybe it is. Maybe it's just a matter of abstraction. Maybe you don't really want to open a file and write something. Maybe you just want to persist an object model. Lo and behold, persistence can be easily modeled as an hidden service, but in a different virtual machine: one that is not working at the OS level, but at the object model level (like the ubiquitous JVM).

In practice, we always have a hierarchy of virtual machines, at different layers and with different granularity. What we often miss is the ability to modify the behaviour of existing VMs (which was the idea behind the Meta Object Protocol concept) or to easily create new (application-level) VMs.

When you look at AOP from this perspective, what a set of pointcuts and advices often does is to create a specialized virtual machine at the application level. Recently, this perspective helped me get a better grip on the design heuristics I'm using in mixed OOP-AOP environments. I'm not completely satisfied, and I've no claims of universality, but it was worth sharing.

On a side note, I did some research to check if the idea had already been explored. I didn't find much, with one notable exception: Concerned about separation by Mili, Sahraoui, Lounis (FASE/ETAPS, 2006). Although the focus is different, the authors elaborate on the idea that some non-functional concerns at the application level can be seen as a functional concern at the virtual machine level. I like this idea quite a bit.

Labels: , ,