Archive
Abstraction sucks!
No, I’ve not gone mad: having obtained a Master’s Degree in Mathematics precludes me from saying such things in earnest. However, the title does seem to represent a sentiment that’s present throughout a large part of both the software developing community (especially, but not exclusively, including all the non-techie folks) and our community as a whole, which might be summed up as “abstraction means ‘more difficult’, right?“.
Wikipedia defines abstraction as “a process by which higher concepts are derived from the usage and classification of literal (“real” or “concrete”) concepts, first principles and/or other abstractions”. As a consequence, abstraction does not inherently add complexity to a situation; rather, it does quite the opposite: it reduces complexity (usually favoring the incidental part of that, thankfully) by condensing existing usage patterns into more compact wording which carries the same content and level of detail. So, when we do some Refactoring and reduce the overall size of a code base, chances are that we’re abstracting at the same time. Even when the code base grows in size, it might be because you’re introducing classes (e.g., through “Extract Interface”) which represent those higher concepts. In my own programming, I tend to search for usage patterns and other commonality which I can abstract/Refactor away so I can understand the domain better. The benefits for software creation of abstraction are: (1) gaining a better understanding of the domain as well as concepts for the ubiquitous language and (2) productivity gains through “doing more in less code” and more maintainable code, at that.
Now, it seems that there are 10 types of people: those who like abstraction and those who abhor it. That dichotomy seems to extend itself to the world of MDSD tools as well: on the one hand we have tools like Intentional, Xtext, MPS, Acceleo which allow you sculpt your modeling language (or in the case of Intentional: domain) to the n-th degree at the cost of meta-programmers’ hours, on the other hand there are tools like Mendix, BeInformed, Pega and all the BPMS, CASE, 4GL tools of old (and new) which come with their own pre-determined set of concepts and a lot out of the box-functionality (the semantics of those concepts plus a technical architecture) and which seems to have gained quite a bit of popularity, especially among the more business analysis-oriented folks. Having worked with some of these tools myself, I’m always struck by the inability of these tools to introduce new abstractions on top of the existing ones. You’re basically given the one modeling language and when you happen to see a usage pattern: well, you’d better make sure you stick to it. While I’ve reasoned earlier that architecture does have a place in modeling, I didn’t have this slightly mind-numbing variety in mind…
At the same time, there are arguments against abstraction which we’d better know in order to be able to understand why “the regular folks” like those fixed modeling language MDSD tools more than anything “us folks” care to cook up. The first one is that an abstraction has to be really, really good in order to be useful in practice. Joel Spolsky formulated the “Law of Leaky Abstractions” which says that however good your abstraction is, at some point in time you’ll going to have to know what’s going on “underneath”, i.e. the concepts the abstraction was derived from, in order to understand the abstraction itself. This is especially true in software where a minute leak can, and thus will, cause a bug which forces you to step down an abstraction level and learn about everything there before you’re able to solve the bug -which incidentally might very well be harder since you’ll have to solve it “through” the abstraction. (It’s also the reason why I’m not overly fond of internal DSLs: the host language will “shine through” at some point, anyway. Using a subset of UML + profiles also falls prey to this problem.)
The second argument is that, while abstraction can reduce the overall complexity, it does produce a bump in the learning curve: it’s one more term to learn about and remember, especially how it relates to all other concepts, making this a bit of a quadratic process. People generally like to stay in their comfort zones so it’s only natural that they don’t like being dragged out time and time again. The fixed modeling language tools excel in providing concepts and constructs which are at the right level of abstraction for a good deal of the business domains they’re aiming at, so once you’re intimate with those, there’s a lot of business value to be created with only that knowledge so there’s actually no need to be dragged out anyway.
The third argument is that it takes a special kind of software creators (developers/programmers) to go search for abstractions, find the right ones and reify them (implementation in the modeling language, education of modelers, adaptation of code generators/model interpreters, etc.), especially in a running project -this amount to the software equivalent of open heart-surgery where you’re also clobbering together the heart-lung machine on the spot.
The question is whether it’s bad that such different flavors of MDSD tools exist and whether there should be a prevalence for either one. In my opinion, it’s unfortunate that it’s hard to get users of the fixed modeling language tools to acknowledge that the fixed set of constructs and concepts is hurting them. Often, the 80/20-rule is applicable: 80% of the target application can be created in 20% of the time through modeling, while 20% of the application has to be created in a different way and at large costs since the tool doesn’t allow that part to be comfortably modeled. On the other hand, it’s at least as hard to get fans of non-fixed MDSD tools to acknowledge that these are actually not that simple to use.
To me, that is an incentive to see whether I can come up with something that enables easier meta programming (DSL creation, code generator/model interpreter implementation) and is capable of delivering those capabilities to a large audience. Stay tuned…
DSLs “versus” software engineering
(New Year’s resolution: blog more frequently ;))
One aspect of using DSLs for software development which seems to be a bit underplayed (IMHO) is the role of good-ole software engineering, by which I happen to mean the practice of creating software through a systematic approach involving analysis, design, implementation and testing in a controlled and predictable manner. It seems to me there’s something of a sentiment or expectation that with DSLs you don’t have to do software engineering anymore, as if everything that makes creating software difficult somehow instantly disappears when using DSLs to model the software to as large a degree as is desirable and productive.
There are two main reasons for using DSLs:
- Empowering domain stakeholders (and other participants) by establishing an ubiquitous language for communication and providing them (and disciplines downstream) with dedicated tooling for that language;
- Separating the essential complexity (the what and why) from the incidental complexity (the how) by focusing the language on the former and hiding the latter “somewhere else”. (This also means that the software model does the same with less “code”.)
So, how does software engineering come into play then? Well, as I see it, there are two sides to this equation: (1) the DSL itself and the way it’s used and (2) the “meta software”, i.e. the software (parser, editor, tooling, etc.) which brings the DSL to life.
Engineering a DSL
To me, the fundamental value of software engineering is the set of concepts such as Separation of Concerns, Loose Coupling, Strong Coherence, (Unit) Testing etc., which allow us to create quality software. I simply define software quality somewhat non-conventially as “it works as required plus it can be productively changed in case the requirements change”. (It’s interesting to see that Kent Beck defines the concepts Loose Coupling and Strong Coherence in terms of how change spreads through a code base.) A DSL can easily be said to “work” if the two advantages mentioned above are realized: stakeholder empowerment and separating essential from incidental complexity -essentially another incarnation of Separation of Concerns. Unfortunately, it’s almost impossible to make this S.M.A.R.T. so you’ll have to rely on your craftsmanship here.
The most obvious aspect of DSL design which contributes directly to the change part of quality is: modularization. This aspect has two directions: (1) how do you distribute different aspects across parts of the language and (2) how can you cut up the entire domain into manageable pieces. Both of these directions benefit directly from the application of concepts like Separation of Concerns, Loose Coupling and Strong Coherence. As an example, consider a (vertical) DSL for Web application development which would typically address data, screen and interaction modeling: Do you create a separate DSL for the data model? Can you divide that up as well? Do you separate screens and interaction/flow? Do you take the use case as unit of granularity? Etcetera… The answers to all these questions are “It depends…” and you’ll have to address these time and time again for each situation or change to that.
But software engineering on the DSL side doesn’t stop there: the DSL instance, i.e., the model that’s built using the DSL must be viewed as software as well -after all, it’s the center piece for the software creation. E.g., as soon as you can modularize your model, you’ll have to think about how to divide the DSL instance into several pieces. Questions which come into play here: How large should each piece be? What kind of inter-piece dependencies do I want to allow or minimize? (This already depends on how modularization is realized on the language level.) How does the versioning system you use affect these decisions? Again, Separation of Concerns, Loose Coupling and Strong Coherence are key concepts to keep in mind here.
You also might want to think about a way to (unit) test the instance. A famous example is business rules: it is very valuable to be able to test the execution of such rules in different scenarios to validate that the results are what your domain stakeholders are expecting. How you code such tests depend (as ever) on the situation: sometimes it’s better to code them against an the business rules’ execution engine (which is something different than testing that execution engine itself!), sometimes you enhance the DSL (or probably better: create a separate DSL) for this purpose.
Engineering the meta software
By “meta software” I mean all the software which is involved with the DSL and which is not directly contributing to the realization of requirements/business value. This ranges from parser definitions (when using a Parser Generator), parser implementations, model validators and model importers to code generators or model interpreters/execution engines. It’s important to realize that this software in the traditional sense as well -not just a bunch of “utility scripts” lying around. In fact, your meta software has to be at least as good as “regular” software since it typically has a large impact on the rest of the software development because the model does more in the same amount of “code”. Among other things, this warrants that you create automated tests for all components, that these tests are part of the continuous integration (automated build) and that everything is checked in into a versioning system. It also warrants that you design the meta software really well, e.g. with an eye towards Separation of Concerns, both inside components as well as across components. It’s advisable to choose a set of tools/frameworks which integrate well and offer as much IDE and compile-time support as possible, to make meta software development as productive, error- and care-free as possible. (I once did a project in which an UML model was fed directly into a combination of a dynamic language and StringTemplate: in the end you get used to it, but it’s painful all the way…)
If the DSL changes, then it might happen that the current model (DSL instance) breaks and must be repaired -depending on the amount of breakage you could do that manually or expend the effort to devise an automated migration facility. This also means that it should be clear at all times which version of the DSL a model requires: it’s usually best to explicitly version both the DSL as well as the model but you might be able get away with an incremental push from a DSL development branch to the modeling branch. In the latter case, you really need to make sure that you tag the meta software together with releases of the software in order to be able to go back in history reliably.
Using a DSL has a kind of a two- (or even multi-) step nature: the DSL instance is parsed first and then fed to either a code generator or a model interpreter. So, if you change the DSL you’ll probably have to change the second step as well. In fact, changes typically “flow backwards”: changes to or enhancement of the code generator or model interpreter often require a change to the DSL as well, e.g. in the form of a new language construct. This phenomenon is sometimes called coupled evolution. Again, having automated tests for all individual components/steps are really necessary to avoid running into problems. In case you use a model interpreter, it’s usually quite easy to write (unit) tests against that.
Change “versus” code generation
In case you have a code generator things are typically more difficult because (1) the generation often is target language-agnostic since most template engines (such as StringTemplate and Xpand) simply spit out “plain text” and (2) usually there’s non-generated code (either framework code or code behind the Generation Gap) as well which has to be integrated with the generated code. I’ve found that in these cases it’s extremely useful to use a relatively small reference application for the meta software discipline. Such a target application would consists of a smallish DSL instance/model which does, however, touch all language constructs in the DSL (especially the new ones) and a fair amount of combinations of those and non-generated code consisting of framework code, hand-crafted code behind the Generation Gap and –mui importante– unit tests to validate the reference application. As always, the process of generating from the reference model, the building of the reference application from generated + non-generated code and running of the unit tests should be automated to be able to verify the correctness of the meta software reliably and quickly.
Something which is very useful in this scenario, is traceability, i.e. being able to see where generated code came from. In particular, which template produced a particular piece of code and what model element was the primary input for that template. Realizing a modest but already quite useful form of traceability is to generate comments with tracing information along with the real code. This is reminiscent of logging, also because care must be taken that the tracing information is succinct without overly “littering” the actual code.
Wrapping up
I hope I’ve been able to make the case that “old-fashioned” software engineering has a definite merit in the DSL scenario. Of course, there’s a lot more to say on this topic. Personally, I’m hoping that the upcoming book on DSL Engineering by Eelco Visser and Markus Voelter treats this really well.
Going back to the source…
During a recent masterclass in the Netherlands, Neal Ford pointed to an article by Jack W. Reeves aptly titled “What is Software Design?” and even more aptly answering that question. (You can find the article online.) Its basic premise is that software design equals source code. In other words: it’s non-sensical to try and design software in a high-level fashion beforehand and expect that writing the actual source code is something that’s comparable to the ubiquitous factory line. Well, we all know how that turned out to work, right?…
The article touches on a lot of aspects of the software creation process, one of which is the assertion that software should be expressible on all levels of detail: “What we need is a unified design notation suitable for all levels of design. In other words, we need a programming language that is also suitable for capturing high level design concepts.” Obviously, I see a role for MDSD and DSLs here 😉
Now, the funny thing is that it was published (and presumably written) in 1992, but already seems to point towards a lot of “later findings” such as Agile (in terms of “excuses to start coding earlier in the life cycle”) and modeling (in particular the distinction between modeling in the problem and solution spaces). I had the same “déjà vu-in retrospective” feeling when reading Fred Brooks’ “The Mythical Man-month”, which acutely describes the software crisis as we’re still experiencing from a mid-70’s viewpoint based on experiences from the 60’s!
Do these guys have time machines, a crystal ball or is it simply that we haven’t gotten much (or even: any) smarter in 18, resp. 35 years? Why aren’t we much (or even: any) closer to solving the software crisis despite all those advances? Are we so non-rational that even we, as software engineering professionals or as an SE profession, are incapable of learning from the past? Do our managers get prepped in such a way that we’re doomed to repeat SE history over and over again?
What do you think?
Continuous deployment: the case for instant gratification through model interpretation
The concept of continuous deployment is gaining a lot of traction these days, among tech-heads seemingly as much as “cloud” does with the infrastructure and more managerial types. In case you didn’t know: continuous deployment is the practice of deploying a new version of your software to production, at the touch (usually literally) of a button -presumably after it’s been tested sufficiently, of course. It signifies instant gratification at the customer-level since new features and/or fixes for bugs (which might have been introduced along with yesterday’s new features ;)) are “often” pushed to production, “often” regularly meaning several times a day. It also signifies near-instant gratification at the producer-level since validation of the new features (or new bugs) also occurs (almost) instantaneous -this is the reason someone like Eric Ries is pushing this practice.
Another very good reason to implement continuous deployment is that it forces you to automate a full build and deployment cycle. We’ve all been in software projects in which a full build and deployment was rather more like a magician’s spiel than the deterministic and quick process it should have been, right? That means that you’re not only wasting a lot of time on things other than adding features or fixing bugs, you’re also exposing yourself to a huge feedback cycle, not to mention to frustration.
In conclusion: it’d already be very useful for the development practice to have continuous deployment. Tacking on MDSD to the concept is a simple as firing up the generation during the automated build, provided that you consider both the models and everything behind the Generation Gap to be part of your source and that generation is automated as well -but you already did that, didn’t you?
But can we go even further? Can we find things which would benefit from a feedback cycle that’s as short as possible (preferably in the seconds range)? After all, instant feedback means that we can validate our ideas and correct mistakes as efficiently as possible. One thing immediately springs to mind (at least, mine): the generation step. Especially with a large model, generation regularly takes several minutes. And after generation, you still have to wait for the IDE to finish re-building, to fix errors in the code behind the Generation Gap and to re-start the application server. This discourages making changes to the model and encourages “fixing” the non-generated code instead of actually fixing the model, which leads to deterioration of the model quality: details which fit the abstraction level of the model are hidden behind the Generation Gap. In turn, this deteriorates the overall effectiveness of the model, so why were you doing MDSD again?
The same is even more true for MDSD contexts in which the business analysis/requirements gathering practice (I’ll leave it up to yourself to decide whether those are separate practices or not) produces the model: usually these practitioners are not tech-savvy at all, so they’re continually groping in the dark -admittedly less so than when they’d only be producing Word documents instead of models, but still. This means that any lack of feedback on their part is propagated through the development practice to the testers, causing a lot of wasted work in between.
Wouldn’t it be nice when you could immediately see the effects of a model change? Well, that’s where model interpretation comes in: create an tool which directly “runs” the model by reading in the local copy the dev/BA is working on and providing a simple rendition of the situation modeled, e.g. as a (locally-running) Web app. I don’t propose to provide full interpretation which covers everything the official generator does; instead, cover those aspects which together provide a workable impression. The essence is to skip any generation and build steps and go directly to execution.
Take Web applications as an example: I’m sure you agree that it would be very useful for use case writers to be able to validate their use case realizations and even converse with the customer using a mockup of the screens and the flow between them. At the same time, having fully working Data and Service layers underneath this is less useful: it may even be more useful to expose the explicit dependencies of a screen (flow) on data not coming from the Presentation layer through “extra” input fields.
To test this notion, I’ve created a simple Web server which polls a local model file (a textual DSL created with Xtext, of course) for changes and exposes a Web app whose request URLs and subsequent HTML responses are completely determined from the parsed model, which solely deals with screens and the flow between these. After saving the model file, a simple browser refresh is all that’s required to see the changes -even inside a running flow. This was actually not as much work as I thought: it’s perfectly possible to use Xpand as template language for the HTML content (much better than JSP!). Most importantly: it seemed to gain immediate traction with both developer types as well as with business analysis folks, especially the more tech/MDSD-savvy ones. On the other hand, people on the requirements gathering-side of things or people who were used to things like Pega were much less impressed: “This looks an awful lot like programming and we’re not having any of that,” seems to sum up their reaction.
Of course, having a model interpreter of sorts alongside a full generator means that you’ll have to update another artifact with changes to both the syntax ánd semantics of the meta model/DSL, so it’s good to carefully examine which aspects of the model really need that instant gratification. Also, the performance of loading the model plays a key role here: parsing a huge model text remains relatively expensive, so a sensible division of the model into separate files and “smart reloading” (i.e., only re-parse changed model files) will have an immediate pay-off. In fact, both concerns combine to encourage to come up with a good factoring of your domain into aspects.
All in all, I’m convinced that we need to reduce the length of the feedback cycle as much as possible and as early in the process as possible -preferably starting at the customers’ domain stakeholders. Domain modeling and MDSD provide a very good way of achieving this as they allow everyone involved to focus on the essential complexity and the right level of understanding, while current advances on the technological side make continuous deployment or model interpretation/execution completely feasible -as opposed to efforts in the past like “Executable UML”.
But what do you think?
A ditty on unit testing
Confession of the week: I rarely write unit tests… And when I write them, it’s usually only a few common and corner cases…
Blasphemy! Of course, I have good reasons for that:
- I’m lazy (as someone holding a degree in mathematics ought to be);
- I don’t write software with bugs in (yeah, right! :));
- I work hard at perfecting my code to be obviously right, not just without obvious bugs (see also point 2);
- I find writing unit tests for my own code a case of “Wij van WC-Eend adviseren: WC-Eend!” (OK, you have to be familiar with the Dutch language and television commercials for that one).
(I’m pretty sure there is a nice quote in the spirit of point 3 by the likes of Donald Knuth or Edsger Dijkstra, but I can’t find it.) While the first two reasons are obviously rubbish, the last two reasons are quite serious.
Use the source, Luke!
Regarding point 3: I’m very much of the opinion that source code is meant to be readable for humans and not for computers (as long as the compiler doesn’t complain, obviously). That means that it has to be readable at least for yourself (I’m assuming you’re human, here…) and in fact, you have to be able to convince yourself (basically at a glance) that your code is correct. I’ve invested a lot of time in refactoring and/or hardening (both working and non-working) code to have its intentions better reflected, preferably with a reduced “mental footprint” -which cannot measured as “number of characters/lines of code” as anyone who ever tried to read an APL program will concur. This has several advantages:
- refactoring code means that you get an easy but thorough introduction to both its structure, its functionality and the relation between the two (I definitely prefer refactoring over trying to read the usual crappy/out-of-date documentation);
- anyone else who gets in touch with the same (refactored) code has it a bit easier (the possible exception being the culprit who originally wrote the crappy code);
- less, more concise code means less effort required for just about anything: bug fixing, enhancements, migration/re-writing, removing unused code, detecting and removing dead code, etc….
Almost without fail, this has provided me with a good ROI, usually in the form of bug fixes and enhancements taking very little time.
JUoNIeedTesting
Regarding point 4: for the same reason that you can’t review your own code, I think it’s not very effective to be the only one writing unit tests for your code. You simply can’t hope to catch all the bugs which have eluded you while coding, because of “mental entrenchment”: you get entrenched in your thinking very soon, making it harder and harder to “see outside of the box”. Of course, writing your own unit tests is very good for getting up to speed with writing the code in the first place (one click, a few seconds, red means fail), as it gives you a very quick turn-around on checking whether your code is performing correctly in the most basic of senses while at the same time ensuring that your code is testable in the first place.
But, after having created and tested your code and having convinced yourself that it is as it should be, obviously bugs will remain…but they won’t be obvious to you. This is the moment where it’s useful to bring in someone else from the team and have him/her have a look at your stuff. This could be a review but it could also entail writing some extra unit tests which test for cases you hadn’t thought of yourself. This approach has three immediate advantages which a pure review seldom has:
- your code is actually live-exercised by someone who very well might be using your stuff very soon, which doesn’t only help to hunt for the nastier bugs (hardly ever discovered through review) but also validates the way your stuff interacts with other code;
- knowledge on your (and especially its API) is spread throughout the team, on a more “intimate” level than a review can hope to achieve;
- (elaborating on point 2) if the round of writing more unit tests is followed by a round of refactoring by the second person, code ownership is spread around as well;
- a review document is just another “inert” document while unit tests validate through both compilation and execution.
To elaborate the last point: a review document typically is a bunch of rather freeform text, pertaining to various aspects of the code base as well as the project as a whole (most importantly: requirements). It (usually) has no hard/navigable links into the code and it doesn’t keep itself up-to-date nor does it automatically flag itself when it gets out-of-date. Unit tests, on the other hand, at least are intimately intwined with the code they’re supposed to test and are re-executable on the click of a button -and should be executed during the continuous build, of course.
Personally, I think having a “second opinion” in the form of someone writing additional unit tests before doing a traditional code review, is a serious alternative to techniques like pair programming (which may not escape the “mental entrenchment” completely either). So, from now on my unit testing mantra will be “I’ll let you unit test mine, if you let me unit test yours!” 😉