Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

My Programming Manifesto

As some of you know, I started a new job this month as a consultant helping clients to adopt Agile practices.  It's been an interesting experience so far.  Working with people who have different philosophies and experiences is always a good opportunity to learn.  I'm getting exposed to different techniques and being forced to examine the things that I believe.  In the end, we all want about the same things — to write quality software in an efficient manner without working too many weekends or late nights.  We all agree on the basic goals, but our philosophy in achieving these goals are different.  I know there's been a few cases where the clients have cocked an eyebrow at some of my opinions, but in every case I've simply had a different set of ideas for how to build software. 

Mostly unrelated, but in the spirit of the infamous Agile Manifesto, I'm writing down my own manifesto in an effort to explain the method to my madness.  Many of you are going to disagree with me, and that's okay.  It's my set of values after all, and I'm happy to hear your comments.  Or better yet, go write your own manifesto.


Let's get this out of the way, and please read this literally:  while I do believe there is some value in the stuff on the right, I think the stuff on the left is more valuable and important.


Unit Testing and Testability over Defensive Coding, Tracing, Debugging, and Paranoid Scoping

I think we can all agree that you can't ship code until you remove enough defects from the code.  While you certainly have to apply a mix of techniques and practices to remove a high percentage of defects, I want to talk about the things we do to our code to wring out the defects.  Off the cuff, I can think of these:

  • Use a debugger to walk through the code to try to uncover the errors through observation
  • Use defensive coding to provide more contextual information about erroneous usage of a section of code
  • Tracing statements in the code to provide debugging information at runtime
  • What I can only call Paranoid Scoping.  Ratcheting up the protection on classes and members to prevent developers that use this code from "harming" themselves.
  • A very high degree of granular unit testing, like that afforded by adopting Test Driven Development.  I'm talking near 100% developer test coverage here. 

An axiom of software development is that problems are cheaper to fix the sooner that the problems are detected.  If you take that axiom at face value, it's easy to see why I put far more weight into comprehensive test-first unit testing because it gives you far more rapid feedback to find problems fast.  Small, isolated unit tests work on very granular pieces of the code, so the number of variables in any single unit test should be small (if it's not, look for a different way to write the code).  You shouldn't even try to run the code as a whole until all the constituent pieces have been validated through unit tests. 

Debugging is not efficient, period.  It's simply not smart to try to debug a lot of moving code parts together if you don't have a high confidence level in any of the parts.  If anything goes wrong you've potentially got a lot of variables to check out all at once.  Yes, you can help yourself out by providing tracing to see what data is passing through the code or defensive programming to short circuit processing when you hit a bad value or provide a more descriptive error message.  But no matter how hard you try to add tracing and defensive coding, I still think unit testing will get you to working code faster than any possible kind of debugging.

Last week one of the developers working on our server side component asked us, the GUI team, to make sure that we had plenty of tracing in our codebase so that integration issues could be adequately diagnosed.  I certainly don't think that's a bad idea, but I'm far more keen on sinking energy into a rigorous set of automated unit tests on both the client in isolation and the server side in isolation before we even attempt to integrate the two pieces.  Since I am a believer in interaction style testing, we'll test our code extensively to prove that our code passes the correct information to the backend through mock objects.

Don't get me wrong here, I do believe in some amount of defensive coding and tracing, I just think that it's not something you rely on as your primary means for removing defects from the code.  My issue with tracing and defensive coding is the often speculative nature of the activity and the negative impact on code readability.  I strongly prefer to use tracing and defensive coding "defensively."  I'll put in specific exception warning for things we hit in testing that are difficult to debug or around external dependencies.  If I had to choose though, I'd rather have a good suite of environment tests to detect configuration or connectivity problems before I even try to execute the code.  In the end I might also say that I just think that you hit a point of diminishing returns with tracing and defensive coding rather quickly.

Oh, and by the way, every time I've come across code that had copious amounts of tracing?  The code was truly awful, failure prone code.  Excessive tracing is a code smell.

The paranoid scoping is probably the single biggest area of disagreement I've hit with my client.  They haven't traditionally used TDD or any kind of comprehensive unit testing strategy in the past, so I don't think they understand the negative impact on automated testing caused by this scoping.  Because they depend more on debugging and waterfall testing for removing defects, they're big on using protected scoping on classes to prevent downstream developers from using objects incorrectly.  In several cases already, I've seen that attitude hamper the testability of the code.  Granular unit testing is dependent on an ability to quickly setup the inputs for a little piece of the code and run that code in isolation.  In a specific case, the server side developers have quite purposely protected all of the setters of the entity classes in the system that should only be set by the server side code.  Reasonable right?  Actually, I'd argue that that strategy ends up doing a great deal of harm to us.  In the current architecture, the entity classes can only function with the complete server side connected.  If we can't change that design, it will dramatically slow down the feedback cycle of the user interface development because we can't unit test the presenters and views in isolation.  I'm perfectly happy to make that code more "dangerous" in exchange for being able to write isolated tests more efficiently.  Besides, we are going to prove that we use the server side code correctly by testing the interaction with the backend as well.  In my world view, improving the testability of the code does far more good to remove defects than the reduced protection levels do bad by letting defects in.

Sharp Tools over Coding with the Kid Gloves On

Quoting Bruce Tate:

We need to let our craftsmen work with sharp tools, rather than protect them from themselves.

Recently, Paul Stovell took issue with a presentation from Scott Hanselman suggesting that you aggressively mark classes as internal or sealed to protect the users of your API or framework.  I'm going to very strenuously agree with Paul on this one.  In the week after reading Scott Hanselman's post I bumped into three different cases where the exact functionality in a framework that I needed to either use or override was sealed or internal.  In one case I resorted to reflection to manipulate a private member.  In another case I forked the open source framework and I really, really hate to do that.  The attitude of "seal everything" is just going to limit the usefulness of your code.  Make your framework or code easy to use by providing a good Facade and a bit of documentation (i.e. tell me which class is the Facade!), but leave the little underlying pieces accessible and available for usage and modification.  Remove all that sealed and internal "protection" and I can certainly break something, but I can do more positive things with your code as well.  I'm perfectly willing to be responsible for my own solution.

To put "Sharp Tools" in perspective, let's consider some of the languages I've coded in throughout my career.  Assuming you used "Option Explicit" and pretend for a moment that the reference counting strategy wasn't a huge memory leak, VB6 is the ultimate kiddie glove language.  It shelters developers from a wide range of coding issues by limiting the range of coding constructs.  You can't possibly deny that VB6 was very successful, but it is an extremely limited language in capability.  Limited Object Oriented Programming support and no real multi-threading severely constrained the usage of VB6, especially in server applications (though we certainly tried didn't we?). 

C# and .Net came along with a lot more power and potential to royally screw things up.  I think it's been a great trade.  I routinely build things in C# that weren't possible in VB6 (windows services to name one) and use OOP techniques that weren't supported by VB6 to great advantage.  I'm happy with C#, or was, until I started seeing some of the capabilities of Ruby. 

Ruby is a dynamically typed language that also supports metaprogramming, i.e. code that writes code on the fly.  Metaprogramming can be scary to debug, and there's a tremendous opportunity to thoroughly confuse developers, but it's soooo powerful.  A lot of the eye popping features of Ruby on Rails (think ActiveRecord) are clever utilizations of metaprogramming.

When we worked together, Jeffrey Palermo and I had a running Abbot and Costello routine about static languages (C#) versus dynamic languages (Ruby).  In order, it went something like this:

  1. I gush about something cool from Ruby or Ruby on Rails (migrations, the built in testing support, metaprogramming tricks, etc.)
  2. Jeffrey says "just wait until you inherit a really lousy Ruby codebase without any unit tests. How are you going to support that without compiler checks?
  3. I freely admit that I would panic in that situation, but then I remind Jeffrey of the really lousy C# legacy code we've dealt with that doesn't have any unit tests and say that I don't want to support code without unit tests in any language.

My point being that just like a sharp knife or power tools, "sharp tool" techniques are safe when combined with some proper precautions (TDD).  Of course I've got a little scar on my bicep from shooting myself with a pneumatic nail gun, so what do I know about safety?

Several years ago, and long before it was AJAX and Web 2.0, I used to do a lot of DHTML scripting in JavaScript that heavily utilized JavaScript's "Expando" typing and a bit with "eval()."  It worked, but as I recall the debugging was nasty and it wasn't really supportable.  This summer I was on big Ruby and Ruby on Rails kick.  As an experiment and a way to work in a familiar problem domain before I dived into Rails, I decided to go back to some of the JavaScript controls I wrote 4+ years ago and recreate them using Rubyesque coding techniques and Test Driven Development throughout.  Using JSUnit and Prototype, I wrote most of a TreeView control and a sortable, pageable grid.  Granted, I've got much more experience this time around, but even so it went much, much faster.  I was able to shrink the code down considerably compared to the previous versions.  Since I was using TDD I really didn't have to use the debugger very often.  With duck-typing and expando members, mocks and stubs are almost trivial.  The very features that make dynamic languages "unsafe," also lead to easier mechanics for TDD, which in my book is more valuable for "safety" anyway.


Writing Maintainable Code over Architecting for Reuse and the Future

A couple weeks ago one of the architects at my client asked us to make sure a piece of functionality we're building is reusable later.  I started to object on the grounds of violating YAGNI before I realized that I completely agree with him — on the ends if not the means.  Some of the other developers are worried about making sure that the code meets future needs and maximizes code refuse.  I think there's an underlying, unspoken assumption that the code cannot efficiently be changed later.  It has to be designed "right" upfront.

I think that the code needs to be able to change to meet future needs, whatever those turn out to be.  I want our code to be reusable as well.  What I don't believe is that the way to meet these goals is to prematurely generalize the solution or speculatively build in unused hooks that "we'll need in the future."*  If we spend a substantial amount of effort building infrastructure for future needs we risk delaying the initial release.  Even worse, what if the specific infrastructure turns out to be unnecessary?  That turns into waste.  Additionally, I've often observed systems where the biggest impediment to extensibility was actually the very extensibility mechanisms put into place early in the project!

What I believe in is meticulously creating loosely-coupled, highly cohesive code backed up by comprehensive test and build automation that can accept change.  I think reuse is best accomplished by well factored code, not by specific intention.  If I can really create code that can be efficiently changed, and I firmly believe this is possible, I can add infrastructure and functionality to the code at a later date.  I want my code to be able to safely accommodate a broad range of change because you never really know what the future will bring.  We think we know what the future needs are, but one of the sales guys could easily swoop in with something completely off the wall.  Call me naive, but I really do think that you effectively flatten the change curve through an emphasis on very solid, maintainable code.  I want to be able to build the sales guy's wacky ideas that we don't even know about yet, not just the future features that we "know" we'll need.

I'm very dubious about a lot of the unbridled SOA enthusiasm I've seen practiced on projects.  In particular, I think the idea of exposing pieces of a new system as web services upfront for a single consumer to be borderline idiotic.  If there's only a single consumer, and the team owns both sides, just write the "service" in process.  The extra cost of writing and debugging the out of process integration point is a complete waste of effort if there isn't a second consumer.  You've simply taken on additional technical risk on the chance that that exact service point will be useful to another consumer at a later point. 

But wait Jeremy, what if I do need web service later?  What do I do now smart guy?  I told you I should have written the web service.  Well, if the code is really maintainable, i.e it can be safely changed, adding the web service wrapper to expose the existing code base remotely should be strictly an exercise in writing additional code, not changing existing code.  I very honestly, and sincerely, believe that high quality, maintainable code goes a long way towards obviating the need for SOA.**

I've been working up a long-ish post on writing maintainable code that will expound on this point quite a bit.


Explicit Code over Design Time Wizards

My team has been coding for about three weeks on a WinForms client using copious amounts of Visual Studio.Net's design time support.  I'm already thinking it's time that we, the .Net community, take a hard critical look at the RAD tools in Visual Studio.  What I'm often seeing is that a little bit of explicit, hand-rolled coding leads to less duplication, better understandability, and vastly better testability than leaning too heavily on the wizard magic.  The effort it takes to create code isn't just the writing of the initial code, it's also the effort it takes to test the code and modify that code later.  In specific, I'm starting to become disenchanted with Data Binding.  I'm really not convinced that the design time support for configuring the data binding justifies itself.  I'm really seeing that pushing user interface behavior into Supervising Controllers with explicit code leads to a more maintainable system than depending on design time configuration of that same behavior.

Again, this circles back to the idea of making code work through testing and therefore seeking to enhance testability.

Software Engineering over Computer Science

I through this in here just to get some flames.  It's a huge pet peeve of mine to see teams agonize over incremental improvements in sorting algorithms or caching or optimizing the number of IL instructions while neglecting to use sound Software Engineering practices.  The success of a system and the staying power of a codebase are rooted in solid configuration and build management practices.  Your algorithm might be fast, but you aren't going to reap much reward for it if you can't consistently get your code to install correctly.  Computer Science knowledge comes into play in some projects.  Good software engineering practices are valuable in every single software development project.

Here's a longer explanation of the coding ecosystem I believe in.


* How's that for loaded language?

** Of course, the natural state of code tends towards entropy, so I think SOA's future is perfectly safe. 

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in Maintainability. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • paweł paul

    Very, very interesting topic. I’ve found your blog by accident, but it was very fortunate coincidence. you need informations about Well written. Thanks:) currency exchange rates calculator

  • foobar

    RE: Dynamic typing. If your reason to use a dynamically typed language is to save on typing, you need to seriously reconsider your decision.

    RE: Unit testing. I’ve always thought that using unit tests to prove the efficacy of a

    Know what’s funny? I’ve never seen an application built with Agile that’s been any better than one written by sheer luck. Even Fogbugz seems to have bug fixes every now and then, and there’s some horribly implemented functionality in it that seems to have been thought through by a monkey.

    Also, Agile scales terribly. Everytime I read something about how great Agile is and everything else is crap, all it does is reveal to me how small the project they’ve worked on are.

  • http://www.cxpraxis.com/dabbler/ jhaapasa

    Michael Lang: I agree about UML. UML sketching can be a great tool to do some just-in-time design, on some portion of the project codebase. You can easily and collaboratively reason about groups of classes, to make sure the approach you are contemplating for a certain feature is not unnecessarily limiting in the near future. Some of the classes that appear on the whiteboard, or in Sparx EA (a great, low-cost UML tool), will end up being implemented completely, others partially, and others not at all.

    You don’t have to design frameworks or over-generalize, just map out the surroundings of a particular feature’s implementation, so you do not run into a wall at the next feature, the next sprint or (yes) the next product (we do not throw away our multi-threading game entity update code in between similar projects). I like to think of this as “agile architecture” work. Even if you implement only parts now, the overall bit of design remains as a roadmap in case you continue working in the immediate area, and as a proof-sketch-of-soundness of the approach you took for that one feature. A wiki is a good place to put these mini-roadmaps, for further reference if needed.

  • test


  • http://www.mblmsoftware.com Michael Lang

    Nice post. A couple of comments about some things you’ve said…

    “What I believe in is meticulously creating loosely-coupled, highly cohesive code backed up by comprehensive test and build automation that can accept change.”

    That’s a fantastic objective to have but you didn’t actually state your definition of cohesive or loosely coupled code. Here’s my definition:-

    A cohesive object oriented system is one broken up into a number of classes that work together to provide a space in which non-monolithic development can take place.

    A loosely coupled object oriented system is one in which you avoid complex associations between classes in your system. By complex I mean cases in which a single association between two classes becomes complex or in situations where the number of associations between classes in your system generates complexity.

    When you said ‘What I don’t believe is that the way to meet these goals is to prematurely generalize the solution or speculatively build in unused hooks that “we’ll need in the future.” ‘

    I found myself agreeing with you to some extent, and disagreeing with you to another.

    The last part of your statement is a no brainer. Having hooks, unnecessary classes, methods, properties and code that is not necessary for achieving your immediate business requirements, is definitely a waste of time and money. If it takes longer to code something for extensibility purposes than it takes your boss, or customer to change his mind about the requirements, you’re definitely setting yourself up for misery.

    However the first part of your statement is a little more interesting.

    At what point is it premature to generalize a system and how much generalization is over cooking it?

    Achieving a loosely coupled, highly cohesive system sometimes requires some elegance; it doesn’t just happen, especially when the scope of the system is large. The interesting question to ask here is – How do you achieve loosely coupled, highly cohesive systems?

    Agile proponents will say “refactor, refactor refactor”. But I think every project reaches a tipping point where major refactoring just can not be justified in business terms, either blowing the budget or causing delivery delays that lead to missed business opportunities. At that stage to some extent you’re pretty much stuck with what you’ve got. At that point if you can’t get to the finish line with the architecture you have without major refactoring, your project, even with heroes and miracle workers, is heading for a melt down.

    If you want to avoid having a project slip into a complex mesh of highly coupled classes, or one in which one or two classes end up becoming a monolithic monsters; at some point in the project there has to be a little fore-thought. A strategy has to be devised that will ensure your project’s architecture remains loosely coupled and cohesive as it evolves.

    How do you go about formulating this strategy before reaching the tipping point?

    This is where modelling comes in to play. UML modelling is a really cheap way to explore a problem domain, far cheaper than exploring it in code. It can take weeks to code something that you can represent in UML in minutes. It needn’t be an exercise in over generalizations; it’s simply a way of quickly illustrating a possible strategy for organising an object oriented system.

    Basically I think you can go crazy with any methodology, the key is to be pragmatic know all the approaches people are taking and utilise what is useful as opposed to taking dogmatic approaches which exclude you from opportunities.

    The last comment I would make is that I heartily agree with your comments on SOA.
    You’ll find a thread here from someone on a project with a SOA fixation and my thoughts on it which echo your own.


  • http://jaysonknight.com/blog jayson knight

    Well done Jeremy…good way to break down into basics.

  • http://www.dotnettricks.com/ Fregas

    Oh, and you have my sympathy with you greatly on the GUI/Server team split. Ouch!!!

  • http://www.dotnettricks.com Fregas

    So when you run the tests, does it point you immediately to where you mispelled things in your test or code? I’m harping on this because it happened to a friend of mine who was trying Ruby w/ TDD, and it took him 15 minutes to figure out it was a spelling error, whereas in C#/VS.NET you would immediately know because it would tell you the line that caused the compile error.

  • http://codebetter.com/blogs/jeremy.miller jmiller


    I really don’t think splitting a team on horizontal layers is a good idea either, but sometimes you have to play the cards you’re dealt. I absolutely agree with everything you say about working vertically.

    Yes, the compiler is a form of defensive coding, but the very real downside of static typing is, well, a lot more typing. Ruby is strongly typed, just dynamically typed. VB classic was loosely typed.

    do you ever run into problems where you’ve mispelled something in a test, and you don’t catch it for a long time because ot the “loosely typedness”

    — You run the tests immediately, so the feedback cycle between code and test is very short. You could easily mispell a phrase in both test and code, but you could do that in C# or Java as well.

  • http://www.dotnettricks.com/ Fregas

    One thing that bothered me in your article. You mentioned a “GUI Team” and a “Server component team.” This sounds scary, like you have one group of programmers working on the Business/Data layer and another working on a desktop/presentation layer (windows forms, web forms, etc.) Maybe i’m misunderstanding you and i dont’ know what project you’re working on, but I think you catch problems a lot sooner if you “Work Verically”, that is, a developer works on one piece of funtionality from top to bottom: Database, Data Access, Business and Presentation and hooks it all together. I would NEVER put a whole team on just the GUI or just the Business layer. There’s just way less integration issues.

    Not to start the religious war but without a strongly typed language, what is there to help you refactor or test your tests? I’m new to both Ruby and TDD, but do you ever run into problems where you’ve mispelled something in a test, and you don’t catch it for a long time because ot the “loosely typedness” ? Also, isn’t strong typedness kind of a form of defensive coding built into the compiler? I.e.: “This method must accept this kind of object/interface as a parameter and no other.” That sounds defensive to me…

  • http://blogs.msdn.com/gblock Glenn Block

    I myself was a big skeptic of TDD, until at my last company we made a decision on our framework team to go TDD lock stock and barrel. I really became a believer when I started to see just how many bugs you catch when you execute your unit tests right before checking in. It saved expoential time, and expoential headache that would have been caused and was caused in the past as buggy code was checked in. From that point on I was hooked. After tha experience there were a few times when I said, oh let’s do it the grunge way. And believe me, I paid for it…every time.

  • http://codebetter.com/blogs/jeremy.miller jmiller
  • http://codebetter.com/blogs/jeremy.miller jmiller


    I certainly didn’t say zero tracing or debugging and please don’t read the article as black and white. Read the preface “value on the right, but more on the left” literally. I just think unit testing is more valuable and I therefore put much more energy to unit testing early (and integration testing and acceptance testing and environment testing) than I do on comprehensive tracing. What I have consistently experienced is that very solid unit testing greatly reduces the time spent in debugging. I don’t put loads of tracing in my code because I don’t find it necessary in most cases. That being said, I sink quite a bit of time into interaction testing via mocks at the boundary conditions between my code and 3rd party components or external services.

    “but a big assumption a lot of people make when evangelizing unit testing is that all their tests are correct and covering the correct spots.”

    I think that’s a bit silly to say. Sooner or later it always comes down to paying attention to what you’re doing, plus that little “skill and knowledge” thing comes into play. Driving code through TDD isn’t any different. How do you know that your code works correctly Eric? If you can’t get a unit test right you probably can’t get the code right either.

    One of the keys is to write small, simple unit tests to the point where it’s easy to spot problems in your unit tests by visual inspection. One of the reasons that granular unit testing can reduce the need for the debugger & tracing is that very little code is being exercised at any one time.

    If you’re doing TDD in a disciplined matter, you’re writing the test before any little piece of code, so what’s the “correct spots” that’s missing? Even simple code needs to be covered in tests. You might not buy into this, but a big chunk of TDD is creating a specification for the way the code is supposed to work. A specification that “bites back” anytime the code stops meeting that specification. We’ve clashed on this before, and I know you’re not a believer in or a practitioner of TDD, but the benefits I’m describing don’t largely accrue until a very large percentage of the code, trivial or not, is pinned down by unit tests. You can quite happily make mistakes in simple code as well as harder code.

  • http://www.codebetter.com/blogs/eric.wise ewise

    If you don’t include any tracing/debugging etc. How do you handle the situation where your unit test is wrong? Don’t get me wrong, I like this article, but a big assumption a lot of people make when evangelizing unit testing is that all their tests are correct and covering the correct spots.

    To me it’s more a matter of knowing where the hot points in a system live, and using more thorough techniques in those sections rather than wasting time and resources tracing/debugging something as a simple select list from a database, for example.

  • http://weblogs.asp.net/fbouma Frans Bouma

    Why do you think fiddling with il instructions or trying to squeeze some cycles out of code is computer science?

    Computer Science’s outcomings feed what you use in the field and use as Software Engineering principles. Computer Science isn’t about IL instructions etc. That’s nittpicking and microoptimization and in general a signal the person doesn’t get software engineering nor what 30+ years of computer science have taught us.

  • http://weblogs.asp.net/bsimser bsimser

    Awesome post. About the explicit code, it’s frustrates me to no end when I look at something and there’s oodles of functionality and business decisions being made the code behind of a web page or the class for a form or the properties of a UI control. Even something simple like is a control or button visible to a user is a business rule and needs a test around it. Having a property arbitrarily set in some auto-generated code is something nobody will find. Having a test and something like an MVP pattern in front of this makes it come to the forefront and easy to spot, fix, and adapt.

  • http://codebetter.com/blogs/sam.gentile SamGentile

    This is a brilliant post. More later.

  • http://TomOpgenorth Tom Opgenorth

    Well written. I’ll definately bookmark this for the next contract I’m on and pitch my case for TDD.

  • http://www.jeffreypalermo.com jpalermo

    Great post. Regarding the Ruby/compiled section: I’ve since formed the opinion that no matter what language/platform, there will be good programmers and bad, good software and bad. I’ve seen some really good code in C# and really bad code in C#, so I don’t think dynamic, static plays into my thinking anymore. Compiling isn’t that useful because it really doesn’t ensure much about the compiled code at all. Good craftsmanship and tests ensure that the software actually works regardless of the language/platform. You’ve won me over. :)