| Is the choice meaningful? Or just something you have to type out? In the casting example, it's meaningful: there are several ways to "round" a float, each having different consequences. As a programmer, you have to make a choice driven by some business requirements, and encode it (preferably only once for a given business concept). Sometimes, however, you can modify the source of the float - push the decision upstream, so that your code always gets an int the way you like. This is one way of reducing glue code, but it's not always available. Anyway, a different type of glue code - I think the most common one, one I call "code bureaucracy" - is reconciling concepts between systems/modules. One system produces data that's almost, but not quite, like what the other system wants to consume. Sometimes it's literally the same data, but named as two separate concepts with separate ownership. This kind of glue code can grow in size rapidly as a consequence of following seemingly good design practices, and often feels pointlessly wasteful. Imagine you're writing a 3D video game. You decide to use a library to load 3D models. You give it a data stream, and get back a list of (x, y, z) points. Unfortunately, despite containing exactly the same data, perhaps in exactly the same memory layout, that list of points is not a proper "list of points" as the rest of your game logic understands it. You're faced with 3 options: 1. Change your game to use library's format for representing 3D data. You're reluctant, because it would tie your system to the API of a third-party component. It would also be difficult if you used another library (perhaps for physics handling) that used its own "list of points" representation. 2. Change the library to use your game's format. Not the work you wanted to do, and the library author won't be interested in it anyway. 3. Write glue code to convert between the formats. Can be as simple as switching out typesystem labels, or as complex as translating each point in a loop. For obvious reasons, everyone will choose option 3. What's curious is, though, that the reasons are more social than technological. You don't want 1. because it will make you dependent on a third-party system. That system's author won't accept 2., because it'll make them dependent on you. Hence, option 3. The exact same thing happens internally, within subsystems developed by different people, and even with code of a single author, across abstraction boundaries. It's an ironic example of Conway's law: system design reflects social structures designers live in. After all, there is another option - one that is rarely thought about, one that's sometimes considered a violation of good design - one that's socially expensive: 4. Get together with the other system's author (and possibly with other users) and agree on a common representation you'll both use. It breaks abstraction layers. It breaks ownership separation. It breaks responsibility division. It also eliminates hell of a lot of glue code. |
Good standards are a wonderful thing, and IMHO we as an industry tend to invest far too little in establishing them now.
I believe programming languages can help a lot here, if you take the view that their standard library should be not just a ready-made toolbox but also a library of standards, in the sense of establishing concepts and (depending on the language) terminology, interfaces, types, symbolic constants and other common foundations for implementations in a specific field.
An example of not doing this is JavaScript, where the paucity of standard data structures and algorithms out of the box leads to huge trees of almost-trivial dependencies and to a culture where almost any data we need to move around just gets shoehorned into objects or transmitted as JSON.
A more positive example is Haskell, where the standard typeclasses capture several broadly useful programming patterns and given them a widely recognisable name and interface. Haskell libraries (standard or otherwise) can then provide algorithms with generic interfaces defined in those terms that are not tied to specific implementations or specific data structures. This results in an ecosystem of data structures and algorithms that are relatively easy to compose even when coming from different sources, which often allows Haskell programmers to express ideas very concisely. A related example is the use of generic programming within the standard library of C++, particularly with the arrival of concepts in C++20 that should serve a similar role to typeclasses in Haskell.
Both of those languages also learned some of these lessons the hard way. Each of them failed to establish a single, standard way to represent something as “simple” as a text string, with the result that many popular libraries did things their own way, and passing text around between things like databases, UIs and remote communications protocols can be a chore to this day.
I think it’s interesting that in some cases, the community around a language has evolved a kind of de facto standard interface for common tasks like accessing a SQL database, so that for example whether you’re talking to Postgres or Maria or SQLite or whatever, the corresponding drivers all provide very similar interfaces and are (mostly) interchangeable as far as calling code is concerned. I think it would be helpful if we did a lot more of that as an industry, within and perhaps even across languages, so we don’t keep reinventing the wheel every time we need to access a relational database or to draw a simple GUI or transmit data using typical Internet protocols or represent mathematical structures beyond the very basic ones.