Maintainable code is desirable for both the product and programmers: faster response to domain changes and less time for (new) programmers/developers to understand the system.
Maintainability is likely best gotten through the use of principles. Contrary to popular 'Design Patterns', Principles like SOLID, YAGNI, TDD, DRY and others offer a creative and flexible manner of introducing maintainability.
Another factor increasing maintainability is the use of OOP: it is the current standard, fits the human mental model and has a rich literature with visual modelling available.
Combining these factors to create a new programming/language can be highly positive to Maintainable Programming/Development. The language would resemble most FP ideas (declarative and composition), while instead having Objects as the atom of operations. Oniun is the name of the 'language stack' specification I am working on. Comparable with the HTML/CSS/JS stack, it defines multiple 'layer specific languages' that adhere to the principles and are designed for simplicity and reuse. The goal is allowing creation of many non-trivial and maintainable programs, apps and libraries.
While still in development, the process is steady and simple because of the non-'monolithic' architecture.
OOP (Object-Oriented Programming): A programming language paradigm where 'objects' play a central role, in order to more easily translate real-world concepts into software/code.
Object: A programming concept. An object is an imaginary model for live and executable or running code. The code has been generated from source code where the Object was defined using other (proto-)Objects, data and either abstract commands or executable code. Key here is the combination of data and code: Objects are modeled such to match real world actors. For example, the shopkeeper that has both knowledge about where products are stored (data), offers a selling service (code) and might have co-workers to delegate work to (other objects).
Maintainable: ISO-25010 , though for this article we will mostly neglect Analysability.
Let us first lay down why we want to create maintainable software and what broad ideas take us there.
Maintainable software is easy to modify and easy to understand, generally. Being able to modify quickly helps in resolving bugs quicker and delivering new features. A few studies have pointed out how maintainability can lower software development costs, mainly an effect of the less time required.
Now a large part of maintainability is context dependent. Having to adhere to certain policies/standards, or code being passed by reviews or even having to build a system based on a (large) pre-determined plan will make things different. From here onwards this article will be discussing the maintainability without context (for as far as that is even possible).
It can be easier for new developers to get into as well. A 'new developer', can also be YOU a few months or years down the line :).
Coming back to software where a bug has been found after a long time is not uncommon at all.
The last point is reuse, which isn't always applicable, but especially for open-source being able to easily reuse your own (or other peoples!) software can help a lot.
Now how will Developers and Programmers actually get to create maintainable software?
Let us first look at the paradigm. OOP fits our understanding of the world well. It's close to the general System Theory. Software is a system consisting of smaller systems (objects) that have complex interactions.
Despite that humans can generally follow OOP code well, it is not so complex that a computer cannot understand it. As it is, computers cannot turn regularly written requirements into software yet. OOP tries to capture the ability to express complex systems as natural language can.
Historically this seems to be the idea as well: Simula, Modula, Eiffel and Smalltalk (very early OOP adopters/pioneers, do check them out!) were created for being able to model complex systems. The alternative, being able to offload math to a machine, is not the aim these days anymore. 50 years ago, when scientists were looking for automated calculations and banks wanted make precise banking software, a paradigm like Imperative was more than enough to model all of the math work and processes. Nowadays focus has shifted to creating software that solves complex problems (Medical and other Physics related problems, videogames as well), creation tools (website builders etc.) and data analysis and related problems. What most problems share is a need to explore and define what the exact problem is, especially in the rapidly changing modern world. Often we developers find existing tools and methods that only require us to bind or glue it together. The rise of Microservices is one such example. I believe that in the current general software climate OOP is the best practice. Objects could encapsulate existing solutions. These objects can be glued and bridged further to shape the desired software.
(I do not disprove the use of FP in development at all, only providing arguments for why OOP would be the best practice right now. I can discuss this in more detail in a future article.)
With all those theories and writings about maintainability and quality code, we can surely go beyond just assessing the most maintainable paradigm. TDD, Composition over Inheritance and SOLID seem good candidates.
These three concepts are often referred to as 'principles'. Which is to say, you cannot take the idea and translate it directly into code, unlike '(design) patterns'.
My case for principles is that there are a lot of languages out there, in a lot of contexts and I think principles would be a more fitting tool to the developer.
Here is how I think how these principles meet the ISO standard, and more importantly, what is experienced as maintainable to most developers:
TDD, Test Driven Development is the principle of making tests BEFORE you write the code that will actually compile into the software to be build. There is a bit more to it. In TDD one should strive for high or at least effective test coverage. That means, the compiled code that gets used a lot has always passed an automated test. TDD in short, increases Testability and Analysability directly.
For those who are not fully familiar with testing in software:
Software can be tested either by hand or automated. Automation has some limitations and the one making the automated test has to supply the computer with exact requirements to make the automated test work. Then we can also split testing into 'scopes'. From lowest to highest scope:
- Unit (testing a very small portion of code that only requires memory and processing time to run. Reading files, sending data over the internet, is not what a Unit test is about)
- Integration (testing larger parts of code that bind many 'units' together and/or use external resources like files)
- System (testing a fully compiled program for large problems)
- Acceptance (testing for the exact requirements of the software, often done by hand) There are more models out there but this gives a simple overview.
In TDD the development ideally starts at the initial idea/goal of the software. From there developers work their way down refining elements and defining their behaviour in a test.
SOLID is a principle I am not going to explain in detail here. Adhering to it along with TDD will make sure that Tests only depend on a single behaviour (set). In Addition to SRP, it works well with DIP and Open/closed. Tests ensure 'API's' that can only be expanded. Through designing top-down DIP (dependency Inversion) can be easily met as well. This affects Modularity, Reusability and Modifiability.
Now the last principle, Composition over Inheritance brings us to the remaining SOLID principles.
Taking this as a maxim, OOP would only require the inheritance between 'interfaces' and 'implementations'. Depending mostly on composition would automatically increase the code that can be reused.
To summarize: Maintainability is desirable and at least valuably measurable. Many principles have arose to make software efficiently as well as maintainable. In the current day and age, OOP with the SOLID, TDD and composition principles are one of the best bets in creating maintainable software, in most cases anyway.
Armed with this knowledge however, there is still a lot of room for unmaintainable software to be created. Human communication (about requirements for instance) is beyond this article. Code reviews and linting/static checks obviously help… what is it the code is written in to begin with however? Can we create a programming language that decreases the possibility of creating unmaintainable software?
Kneading these principle so thoroughly through the language that it starts checking off ISO aspects? In this last section of the article we go over this idea.
Another common principle, even outside of maintainability is YAGNI (You aint gonna need it; or another version of Ockham's Razor): do not ship products with features that go beyond reasonable specifications. For instance: don't create a car with equipped with buoys and a propeller for the chance it ever falls in the water… don't create a hammer with a beer opener because the carpenter might decide to drink something.
For maintainability, the principles should be in every fibre of the software, including the programming language it was written in. YAGNI will be useful to keep the bells and whistles out of it.
In direct relation with this is the amount of keywords of a language, its complexity and the complexity of the resulting software.
The simpler the language stays, the better we might argue casually.
Languages that will serve maintainable software only meet the minimum of the SOLID, Composition and TDD. The more principles we add, the more complexity eventually could end up in the language. This was the reason to be cherry pick a few principles.
Examples of redundant features?
Deep inheritance, a staple in OOP languages, can be thrown out to streamline languages. Embedding TDD can decrease complexity by not having for instance many different frameworks to reach automated testing.
Going back to keywords, languages that have as little keywords as possible are preferable. This would also increase the space for DSLs to be made, which have been also advertised as the optimal way to develop software.
Things can be taken further. The only 'types' really required for any software to be made are booleans and a 'collection/array' type. See the Turing Machine and theories on computability.
Developers and programmers would be free to build increasingly complex and emergent objects on top of these crude basic concepts to effectively reflect the real world.
All we have now is a language that:
- Allows 'types' (interfaces/contracts) to be declared, with 'methods' as behaviour.
- Allows 'classes' (implementations) to be declared, that implement a 'type' by implementing functions as relations between objects and stating 'properties': objects required for instantiation.
- Allows 'objects' to be created from 'classes'.
- The basic 'types' are boolean and collection/array.
It does not have math, or control flow. It cannot really do anything on it's own. Yet, we can take away more and knead the principles further into this language.
Splitting the language into multiple languages.
Most software can be defined according to a few layers:
Driver code (calling into modules setting up data)
Modules (large chunks of features/functionality)
Units (atomic features such as a math function)
Grasping (no pun intended) back on Single Responsibility Principle, everything in software should do one job, and do it well.
To split the layers of software into distinct languages (An LSL, Layer Specific Language if you will) can be the final act into pushing the pre-coding material to be maintainable as possible.
What we reach is simply a stack of languages (comparable to HTML/CSS/JS) that makes it very easy for a developer or programmer to create maintainable software.
The layers I would suggest are:
- Modules/Driver (code that can make object instance from classes, call/import modules and defines the data to be put into the former)
- Unit: Interfaces (code that defines atomic features)
- Unit: Composition (code that functionaly relates objects together to result into new objects)
- Unit: Tests (code that mirrors Implementations)
And then the last layer. Up until now the language stack we are creating cannot actually do anything. Again, no control flow, no math. It can represent these things, but does not define how a compiler/interpreter should make it executable.
The last layer is where developers/programmers actually define this… using another language entirely.
- Unit: Implementation (code that wraps another language that DOES have control flow etc).
The new language completely delegates details like logic to another system. This is the final trick into making maintainable software: splitting low-level logic from high level relations.
Never will this language contain a for-loop in the middle of a unit test; Not a single if-statement will pollute constructors: we design the new language so that this becomes impossible.
This is also probably the hardest concept to actually start working with. We might scratch our heads and don't see at first how to even write functional code inside such a system.
The solution to that being, that one doesn't. It merely relates executable code with each other.
Let's have an example of some imaginary chat application. It supports sending audio files, mainly as voice messages. It accepts all manner of formats, the underlying database and playback however only handle AAC format audio. Let's just assume that a library for conversion from format X to AAC does not yet exist. Basic libraries for the AAC format and for format X however do exist. The libraries can take care of reading audio files and play them on the host computer or expose them as crude objects (or structs even). Building this bridge is exactly the kind of task that fits for the new language. Instead of however writing imperative code on how to convert X into AAC, a relation is given. Perhaps we will model an intermediate or more generic audio object format. Just an example of the top of my head: An audio object consists of the maximum loudness value and an array of some sort of sample object. This sample object contains a map where the keys are the track names and the values are the sample numeric value. In addition, the sample contains a numeric value that represents the time between it and the previous sample (in order to model variable sample rate lets say). Continuing our crude example, now one might employ map/filter/reduce to relate the generic audio object to an AAC object.
This is, at a quick glance the aim. To think declaratively using objects and processes to get the desired results.
The system I am creating does all of the above and more; I dub it Oniun. It is a platform to build many languages on top of, that meet strict maintainable criteria. It has been designed such that there is no reliance on hardware nor natural language. While the pilot implementations are based around Java and English, there is nothing stopping new implementations to work in Chinese on a purely visual interface (instead of text-based code), while also not being based on the traditional byte system (for those who do not know: the 8-bit byte seems natural nowadays but was not before the 1970s, before the release of the Z80 and intel 8008 8-bit CPU's. C was one of the programming languages to follow the hardware trend eventually leading to the byte as it is known today: the atom of programming).
The main idea of Oniun is to transpile all of the LSL's onto the 'host language'. This is Java in the case of the pilot implementation.
A side effect from the layered nature of Oniun is ease of development. Not only maintainance, but novel development as well.
Maintainability is an important aspect to most non-trivial software development. Code and software becomes easier to reuse and more easy to bug fix. One of the ways to get to maintainable software is following principles. This article proposes SOLID and TDD as effective principles. In addition to that, OOP is proposed as an effective paradigm, since it forms both models that humans can easily understand, yet is not too complex for the computer to handle.
Instead of enforcing these ideas in existing languages, it is possible to create new ones that adhere to them by design. This in itself leads to more maintainable languages. The benefits of this will be both for language maintainers and language users. A comparison was made with the HTML stack, how general-purpose language can copy this stack trait to create a more maintainable environment. In imagination we looked at what this would look like. I concluded by presenting my own work-in-progress attempt at creating a system like it.
Many ideas in this article have been copied or inspired by other software writers. To credit them properly I include a non-exhaustive list of reading material that contributed to the development of Oniun and this article.
- Yegor Bugayenko , currently developing a unique take on OOP in the form of EOLang.
- no-kill-switch, had some interesting ideas not limited to DSLs being superior to 3rd gen programming languages.
- Programmatically Speaking
- Pragmatic Objects
- The Clean Coder Blog/Uncle Bob/Robert C. Martin, further reading on FP and TDD, the importance thereof.
- Building Maintainable Software by Joost Visser and the SIG (formed the basis of BetterCodeHub and in general did large studies on enterprise maintainability)
- Unit Testing by Roy Osherove (was my best introduction on how and why Unit Testing)
- Nature of Code by Daniel Shiffman (while not really about maintainable code persay, drives some ideas about AI and structure in systems)
- Object Oriented Software Construction by Betrand Meyer (Meyers ideas are still relevant today and a few concepts really helped Oniun)
- Mental models and software maintenance
- A few more that I will not list here right now.
- Vexing exceptions, was a huge help in one of the earliest maintainability projects I did.
- Testing Object Oriented Code
- Unmaintainable code, both inspiring and paradoxically helps one to spot bad code.
- Chef, an Esolang I have a fondness for but also showed how programming languages can be easily readable and appear in many 'natural language' forms or subjects.