This is an HTML rendering of a working paper draft that led to a publication. The publication should always be cited in preference to this draft using the following reference:
“I don’t know what the language of the year 2000 will look like, but I know it will be called Fortran”
— Tony Hoare
Writing code that can run on any platform used to be a golden standard, as attested by the tens of books with the word “portable” in their title. Every day however staying true to the faith of portable code is becoming more challenging as mighty ecosystems amass resources to tempt us into their platform-specific version of heaven. We can write non-portable code out of laziness or ignorance, because we can’t be bothered to verify or check that our code follows a standard. We can also decide to write non-portable code following a pragmatic cost-benefit analysis. Let’s follow this approach and examine portability as a tool, looking at what we gain through it, the price we pay for it, and how we can cope with the challenge of upholding it.
The key reason to favor portable code is that it opens up the selection of resources available to our project. An ideal portable project can be compiled using diverse compilers and libraries, store its data on an arbitrary relational database, get hosted by a variety of application servers and operating systems, which in turn run on several CPU architectures. These choices free us from vendor lock-in, allowing us to select the best technology in each area based on quality and price. In addition, as our project and business evolve we can move from one technology to another to keep the infrastructure we use in sync with our needs. Thus, the first iteration of our project can be based on widely available commercial off-the-shelf components, but when its market share grows we can choose to lower its unit costs by running it on an embedded platform that’s based on a specialized processor and an open-source operating system. Or, conversely, we can initially store our data in the open-source MySQL database, because we could install it for free, but as our performance requirements grow we might decide to dish out for a more powerful commercial offering.
Vendor independence also strengthens our negotiating position. Merely having the option to choose a different vendor, allows us to ask for better pricing, additional functionality, bug fixes, and improved service. Guess what happens when a vendor knows that we’re locked to their offerings. I’ve been there, and, trust me, it’s an ugly place.
Platform neutrality minimizes our project’s technology risks. In our fast-evolving sector, companies and technologies flourish and die at an amazing rate. If you’re wed to a proprietary technology you face the constant risk of a messy unanticipated divorce when the technology’s vendor stops supporting it. In contrast, with portable code you can choose the most beneficial technology at each point of time. Standardized technologies also tend to last longer, supporting your technology investment in the long-term. Consider as examples Fortran and C, versus some of the 1980s proprietary darlings like Clipper, SQLWindows, and NatStar. There’s no magic behind this phenomenon: selection bias ensures that mature technologies get standardized and widely adopted, and thus they outlast proprietary offerings.
Adopting widely used technologies will also help you in other, non-technical areas. You’ll be able to choose co-workers or employees from a deeper pool; advertizing a post for a Java programmer will yield many more candidates, than opening one for an AcmeScript developer. Similarly, you’re more likely to find good books, a vibrant support community, and training courses if you stick to standardized offerings.
Sadly, striving for portability can sometimes be a thankless call. In some domains, like native applications with a graphical user interface, what you can write with portable code is laughable, if not entirely useless. At best you may have to choose between delivering a system that requires platform-specific libraries (as is the case with Java’s SWT) or one that doesn’t quite follow the platform’s native look and feel (think of Java’s Swing). Performance will also suffer, because vendors tend to offer their hottest code through non-portable bindings, like those of Microsoft’s DirectX. As another example, vendor-specific database bindings tend to perform better than vendor-agnostic ODBC/JDBC bridges. Adding insult to injury, portable code can be less expressive than code written using some non-portable extensions. Consider the nifty process and command substitution features of the bash Unix shell. To do the same things with the standard Unix Bourne shell requires ugly contortions involving temporary files and back-tick escaping. Similarly, you can simulate some of Oracle’s analytical database query functions by means of nested queries. However, the result can be unreadable and may well perform worse than a query using the non-portable extensions.
With most systems software implementing a standard and then helpfully also adding the kitchen sink to it, writing portable code can be treacherous. An obvious solution is to disable all extensions; many compilers offer a flag that makes them standards-compliant. When however this is not possible or feasible, another practical solution is to code for one system using as documentation the official standard, or that of another one. For instance, when writing code for MySQL, read the documentation of the corresponding SQLServer commands. To avoid hidden gotchas, strive to continuously compile, run, and test your code on a variety of platforms.
Wearing the portability hair shirt will deprive you and your customers from many benefits. One way around this conundrum is to draw boundaries around the non-portable code to isolate it from the rest of the application. If you’re lucky you may find a library or even a complete platform that offers you the functionality you need. For instance, HTML 5 allows you to deploy sophisticated GUI applications through any modern web browser. If you can’t find a suitable portability layer, you’ll have to do the heavy lifting by hand. Create a separate directory or file for the routines of each platform’s code. If your language supports it, define the code’s interface and implement a separate class for each platform. Often a particular non-portable extension is provided by most vendors using only slightly varying syntax or semantics, so the extra work might not be onerous.
Another approach is to admit defeat and go wild writing code that gives the best native experience. Keep in mind that we program to serve our business and customers, not just to satisfy lofty ideals. This is not as crazy as it sounds, even if you have to support multiple incompatible platforms. Prefer to let each platform’s code base develop separately than introducing the complexity of each platform into a single code base, for a unified base may well become exponentially complex. Worse, when some platforms (inevitably) die you may end up having to maintain their complexity, because it will be difficult to pry away their code from the integrated system.
Choose your side!