📜 ⬆️ ⬇️

Reliable programming in the context of languages ​​- nubobzor. Part 1

Having once again spent two days writing and debugging only four hundred lines of system library code, the thought arose - “it would be good if programs were written in a less painful way”.

And first of all, since debugging takes much more time than writing code - you need protection against a fool (including yourself) at the writing stage. And I would like to get this from the programming language (PL) used.

Of course, we must invent a new, the best YP!
No, first we will try to express our wishes and look at what we have already invented.

So, what would you like to receive:


The area of ​​application is machinery, transport, industrial control systems, IoT, embedded including telephones.

It is hardly necessary for the Web, it is built (so far) on the principle of “threw and restarted” (fire and forget).

Quickly enough, one can come to the conclusion that the language should be compiled (at least Pi-compiled) so that all checks are performed as much as possible at the compilation stage without VS (Versus, hereafter negative opposition) “oh, this object has no such property” in runtime. Even the scripting of the interface descriptions already leads to the necessity of complete coverage of such scripts with tests.

Personally, I don’t want to pay with my own resources (including money for faster, more expensive hardware) for interpretation, therefore, it is desirable to have a minimum of JIT compilation.

So, the requirements.

Resistance to human error


Diligently looking through the Talmuds from PVS-Studio, I found out that the most common mistakes are typos and underwritten copy-paste.

I will also add a little incidents from my experience and encountered in various literature as negative examples. Additionally updated MISRA C rules in memory.

A little later, having thought it over, I came to the conclusion that the linters applied after the fact suffer from the “survivor's mistake”, since in old projects the serious mistakes have already been corrected.

a) getting rid of similar names

- there should be a strict check of the visibility of variables and functions. Having sealed, you can use an identifier from a more general scope, instead of
- use case-insensitive names. (VS) “Let's call a function as a variable, only in Camelcases” and then compare it with something — you can do this in C (get the address of the function, which is quite a number)
- names with a difference of 1 letter should cause a warning (arguably, it can be highlighted in the IDE), but a very common mistake is copy-paste .x, .y, .w, .h.
- we do not allow different entities to be named the same way - if there is a constant with this name, then there should not be a variable of the same name or a type name
- it is extremely desirable to check the naming across all modules of the project - it is easy to confuse, especially if different modules are written by different people

b) Once mentioned - there should be modularity and preferably hierarchical - VS project of 12000 files in one directory - this is hell of a search.
Another modularity is required for descriptions of data exchange structures between different parts (modules, programs) of the same project. VS Encountered an error due to different alignment of numbers in the exchange structure in the receiver and transmitter.

- Eliminate the possibility of duplicate linking (layout).

c) Ambiguities
- There must be a certain order of function calls. When writing X = funcA () + fB () or Fn (funcA (), fB (), callC ()) - it should be understood that the person expects to get the calculations in the written order, (VS) and not how the optimizer thought of himself.
- Exclude similar operators. Not like in C: + ++, <<<, | ||, & &&, = ==
- It is desirable to have a few clear operators and with obvious priority. Hello from the ternary operator.
- Redefinition of operators is rather harmful. You write i: = 2, but (VS) in fact it causes an implicit creation of an object for which there is not enough memory, and the disk crashes when swapping and your satellite falls to Mars :-(

In fact, from personal experience I observed a crash on the string ConnectionString = “DSN”, this turned out to be the setter who opened the database (and the server was not visible on the network).

- Initialization of all variables by default values ​​is necessary.
- Also, the OOP approach saves from the forgetfulness of reassigning all the fields in an object to a new one-hundredth function.
- The type system should be safe - control of the dimensions of the objects to be assigned is needed - protection against memory overwriting, arithmetic overflow like 65535 + 1, loss of accuracy and significance when casting, excluding the comparison of the incomparable - the whole 2 is not 2.0 in the general case.

And even a typical division by 0 can give a well-defined + INF, instead of an error - you need a precise definition of the result.

Input Resistance


- The program should work on any input data and preferably, approximately the same time. (VS) Hello to Android with a response to the button tube from 0.2s to 5s; Well, that is not Android controls automotive ABS.

For example, the program should correctly process both 1Kb of data and 1TB without exhausting the system resources.

- It is highly desirable to have reliable and unambiguous error handling in PL RAII that does not lead to side effects (resource leaks, for example). (VS) A very fun thing - a handles leak, it can manifest itself in many months.
- It would be nice to protect against stack overflow - recursion is prohibited.
- The problem of exceeding the available volume of the required memory, uncontrolled growth of consumption due to fragmentation during dynamic allocation / release. If the language has runtime dependent on the heap, the case is most likely bad - hello STL and Phobos. (VS) There was a story with the old C-runtime from Microsoft, which inadequately returned the memory to the system, because of what msbackup fell on large volumes (for that time).
- We need good and safe work with strings - not limited by resources. This is highly implementation dependent (immutable, COW, R / W arrays)
- Excess system response time, independent of the programmer. This is a typical garbage collector problem. Although they save from some programming errors - they are introduced by others - poorly diagnosable.
- In a certain class of tasks, it turns out, you can do without the dynamic memory at all, or by selecting it once at the start.
- Monitor the output of the array, and it is quite possible to write a runtime warning and ignore. Very often these are noncritical errors.
- To have protection against calls to an uninitialized program memory, including to the null-area, and to another address space.
- Interpreters, JIT - extra layers reduce reliability, there are problems with garbage collection (a very complex subsystem - will introduce its own mistakes), and with guaranteed response time. We exclude, but in principle there is Java Micro Edition (where so much is cut off from Java that only I am left, there was an interesting article dernasherbrezon (sorry, shot) and the .NET Micro Framework with C #.

However, under consideration, these options have disappeared:



- Resilience to multi-threaded work - protection of private data flow, and mechanisms for the guaranteed exchange between threads. A program with 200 threads may not work at all like two.
- Contract programming plus built-in unit tests also help you sleep a lot.

Resistance to damage to the program or data - media failure, hacking


- The program must be fully loaded into memory - without loading modules, especially remotely.
- We clear memory at release (and not just selection)
- Control overflow stack, variable regions, especially strings.
- Restart after failure.

By the way, the approach when rantaym has its own logging, and not only shows that the arctic fox and spectra are very appealing to me.

Languages ​​- and the correspondence table


At first glance, for analysis, we take specially designed safe PLs:

  1. Active oberon
  2. Ada
  3. BetterC (dlang subset)
  4. IEC 61131-3 ST
  5. Safe-c

And go through them in terms of the above criteria.

But this is already a volume for a continuation article, if karma permits.

With the allocation of the aforementioned factors into the table, well, and perhaps, something else will be drawn from the comments.

As for other interesting languages ​​- C ++, Crystal, Go, Delphi, Nim, Red, Rust, Zig (add to taste), I’ll leave those who want to fill in the correspondence table for them.

Disclaimers:

Source: https://habr.com/ru/post/437830/