I bring to your attention a translation of the original article by Robert S. Martin.
Over the past few months I have tried two new languages. Swift and Kotlin . These two languages have a number of common features. Indeed, the similarity is so strong that I wondered if this was not a new trend in our mixer . If this is true, then this is the dark path .
Both languages include some functional features. For example, in both of them there are lambdas. Overall, this is a good thing. The more we learn about functional programming, the better. These languages are far from a truly functional programming language; but every step in this direction is a good step.
The problem is that both languages have relied on strong static typing. It seems both intend to plug every hole in their own language. In the case of Swift
, this is a strange hybrid of C
and Smalltalk
, called Objective-C ; therefore, perhaps the emphasis on typing is understandable. As for Kotlin, its ancestor is already rather strongly typed Java.
I don't want you to think that I am against statically typed languages. I'm not against. There are certain advantages for both dynamic and static languages; and I enjoy using both. I prefer dynamic typing, and therefore I sometimes use Clojure
. On the other hand, I probably write more Java
than Clojure
. Therefore, you can consider me bi-typical. I walk on both sides of the street - if I may say so.
The point is not that I am concerned about the static typing of Swift
and Kotlin
. Rather, I'm worried about the depth of static typing.
I would not call Java
very stubborn language when it comes to static typing. You can create structures in Java
that follow well the type rules; but you can also break many type rules whenever you want or need. The tongue complains a little when you do it, and creates appropriate barriers to it, but not so much as to be an obstructionist.
Swift
and Kotlin
, on the other hand, become absolutely adamant when it comes to their type rules. For example, in Swift
, if you declare a function that throws an exception, then every call to this function, right up to the beginning of the call tree , must be wrapped in a do-try
block, or try!
or try?
. In this language, there is no way to silently throw an exception down to the top level, without passing through the whole tree of calls. (You can see how Justin and I fight this in our video footage Mobile Application Case Study )
Perhaps you think it is good. You may think that there were many errors in the systems caused by incorrect exceptions. You may think that exceptions that are not accompanied, step by step, up the call stack, are risky and error prone. And, of course, you would be right. Undeclared and unmanaged exceptions are very risky.
In Kotlin
, you cannot inherit from a class, or override a function, until you mark this class or function with the open
keyword. You also cannot override a function if it is not marked with the override
. If you do not declare a class as open for inheritance, the language will not allow you to inherit from such a class.
Perhaps you think it is good. You may think that inheritance hierarchies, which are allowed to grow without restrictions, are a source of error and risk. You may think that you can eliminate entire classes of errors by forcing programmers to explicitly declare their classes open for inheritance. And you may be right. Inheritance is a risky thing. Something may go wrong when redefining a function in a descendant class.
Both languages, Swift
and Kotlin
, include the concept of nullable types . The fact that a variable can contain null
becomes part of the type of this variable. A variable of type String
cannot contain the value null
, it can contain only a specific string. On the other hand, a variable of type String
? is nullable and may contain null
.
The language rules insist that when you use a variable that admits the value of null
, you must first check this variable for null
. So if s
is a String?
then var l = s.length
will not compile. Instead, you should write like this: var l = s?.length ?: 0
or var l = if (s != null) s.length else 0
.
Perhaps you think it is good. You may have seen quite a few NPEs in your life. You may know, without a shadow of a doubt, that unverified null`
are causing software failures of billions and billions of dollars. (Indeed, the documentation Kotlin
calls NPE "Billion Dollar Bug"). And, of course, you are right. It is very risky to have uncontrollable null`
everywhere.
null`
? Tongue? Or is it a programmer's job?These tongues are like a little Dutch boy, plugging holes in the dam with his fingers . Every time a new error occurs, a new function is added to the language to prevent such an error. And therefore these languages accumulate more and more fingers in holes in dams. The problem is that eventually the fingers and toes will run out.
But until the fingers and toes are over, we are creating languages that contain dozens of keywords, hundreds of restrictions, a tortuous syntax and reference manual that reads like a book of the law. Indeed, to become an expert in these languages, you must become a language lawyer (a term that was invented in the C++
era).
Ask yourself why we are trying to fix defects with language functions. The answer should be obvious. We try to correct these defects, because these defects happen too often.
Now ask yourself why these defects happen too often. If you answer that our languages do not interfere with them, I strongly advise you to quit your job and never think about becoming a programmer again. Because defects are never a mistake of our languages. Defects are a programming error. It is programmers who create defects, not languages.
And what should programmers do to prevent defects? I will make you a riddle. Here are a couple of tips. This is a verb. It starts with the letter "T". Yes. Did you understand. TEST!
You write tests so that your system does not return unexpected null
values. You write tests so that your system processes null
in all input data. You write tests so that every exception you can throw is processed somewhere.
Why do these languages use all these functions? Because programmers do not cover their code with tests . And since programmers do not test their code, we now have languages that force us to put the word open
in front of each class from which we want to inherit. Now we have languages that force us to wrap each function, through the entire call tree, in a try!
block try!
. Now we have languages that are so limited and so redefined that you need to design the entire system in advance before you start coding.
Consider an example. How to find out if a class is open for inheritance or not? How do I know that somewhere down the call tree someone can throw an exception? How much code do I have to change when I finally find out that someone really needs to return null
in the call tree?
All these restrictions imposed by these languages suggest that the programmer has a perfect knowledge of the system before writing it . They assume that you know which classes should be open for inheritance and which should not. They assume that you know which calls will generate exceptions and which ones will not. They assume that you know which functions will return null
and which ones will not.
And because of all this, there is reason to believe that they punish you when you are wrong. They make you go back and change a huge amount of code by adding try!
or ?:
or open
through the entire call tree.
And how do you avoid this punishment? There are two ways. One that works, and one that does not work. The one that doesn’t work is to design everything before writing the code. And the one who avoids punishment should override all precautions .
And therefore, you declare all your classes and all your functions open for inheritance. You never use exceptions. And you get used to using a large number of characters !
to override checks for null
and allow NPEs to breed in their systems.
Why did the nuclear power plant in Chernobyl burn, melt, destroy a small town and leave a large area uninhabitable? They redefined all precautions . So, do not rely on security to prevent a catastrophe. Instead, it is better to get used to writing a large number of tests, no matter what language you use!
Source: https://habr.com/ru/post/324122/
All Articles