TL;DR If maintainability & extensibility of your software are among the top priorities, readibility & easy-to-grasp correspondence to functional requirements are far more important than conciseness & technical "aesthetics" (e.g. compliance with patterns) of your code. That's why to build good solutions we need much more expressive power than what any of the programming languages (of the day) offers. At some point we believed NLP will solve this issue & we'll get there with Nth GLs. This is not the case. Instead of building more generic programming languages, we need to get better with building custom DSLs, specific for particular contexts.
Disclaimer: this is NOT a blog post about so-called "one-time code" or "disposable code" - something created usually by software houses / consultants as an outcome of one-off projects - contractual endeavors that don't give a damn about maintainability. Long-term thinking is actually critical from this post's perspective.
All programming languages suck.
The more I work with them, the more they suck.
The more you work with them & I have to read it later - the exponentially more they suck :)
It's not about me or you. Sort-of.
I've learned (the hard way, over the years) that the only way to harness the technical debt within a maintainable, future-proof codebase is to have your code directly corespond to your model. To be precise: model has to be written down as code, documentation has to be a side effect of code compilation - built upon code & accompanying metadata.
But the problem is that all of programming languages I know have very limited abstractions "out-of-the-box": simplistic type systems with abominations like 'null' or 'pointer', basic looping & conditionals, few compositions abstracts ('functions', 'classes', 'namespaces'). Unfortunately 'to keep our code simple' we treat this palette of low level tools as the ultimate toolbox & map all the business rules directly into combinations of these code constructs. This mapping may be semantically correct, but it's "lossy" (not 1:1) - what gets lost "in translation" is full correspondence of code constructs to business rules that originated them (so the final code is not 100% reverseable, as it should be).
C(ontext)++
The only way to avoid this issue is to write programs in ... context-specific DSLs -> one you have to build yourself (with language-specific syntax & idiomatic code constructs) first. Throw out (or rather - hide & don't use) all the "off-the-shelf" building blocks & model your domains as explicitly as possible, using whatever means of expression your language / platform provides:
- if something is a "process", make it a "process" (not a "class" with "Process" suffix) - it should have all the characteristics of processes that are relevant in your context; same applies for "inventories", "snapshots", "policies" & any other terms (/categories) used in your domain (instead of "singletons", "mediators" or "facades")
- if something is an "age", don't pretend it's an "Int32"! it should follow all the rules & constraints of an "age"
- don't optimize for syntax brevity / "cleaniness" just because ReSharper suggests you that you can use "lambda" -> the goal is not to use all the latest/sexiest features of the language, but to have algorithms that can be read (& interpreted & understood) pretty much at the first glance
- re-consider n times applying tools like Lambda Calculus -> have you ever seen functional specification / business requirement expressed with flatmaps, Monads or reducing? ;P
- TDD is absolutely crucial here -> not because of tests, but because of starting with the public interface & validating its design by creating red-flaring tests that rely only upon it
It doesn't mean that so-called "good practices" & patterns of engineering are complete garbage, just to be thrown out of the window. It's about where you need to apply them - deeply hidden "in the bowels" of your DSLs' abstractions, where they solve the technical complexity, but don't contribute to overall functional complexity.
All programming languages suck.
I can barely stand Java itself. More than 20 lines of C# makes my eyes bleed. Each exposure to JavaScript costs me few drops of remaining sanity. Golang pulls to the surface all my wildest (& most violent) primal instincts. Whenever I encounter some functional crap (dialect barely matters - Scala, F#, even Elixir ...) one tormented pixie dies in agony.
Some of them suck more, some of them suck less. But all of them are smart tools that are not meant to solve any problems directly - you need to build a layer of indirection to bridge between the means of technical expression and context-specific terms that represent important aspects of reality.