Предлагаем вашему вниманию перевод публикации, посвящённой анализу сходства языков Swift и Kotlin.
Swift и Kotlin. Эти два новых языка имеют ряд общих черт. В самом деле, сходство настолько сильное, что возникает вопрос, не является ли это новой тенденцией в нашей языковом водовороте. Если да, то это тёмный путь.
Оба языка имеют некоторые функциональные характеристики, например, в них есть лямбды. В целом это хорошо. Чем больше мы узнаём о функциональном программировании, тем лучше. Хотя эти языки далеки от звания действительно функциональных языков программирования. Но каждый шаг в этом направлении — хороший шаг.
Смущает избыточная сосредоточенность обоих языков на строгой статической типизации. Кажется, будто Swift и Kotlin вознамерились заткнуть все прорехи типизации своих предков. Родительский язык Swift — это странный, слабо-типизированный гибрид C и Smalltalk, называемый Objective-C. Поэтому акцент на типизацию здесь, пожалуй, понятен. А вот предок Kotlin — это уже довольно сильно-типизированный Java.
Не подумайте, это не камень в огород статически типизированных языков. Нет. Есть определенные преимущества и у динамической, и у статической типизации. И многие охотно используют оба вида. Настораживает факт того, что Swift и Kotlin статически типизированы, а глубина, степень охвата этой статической типизации.
Java вряд ли можно назвать слишком категоричным языком, когда речь идёт о статической типизации. Вы можете создать Java-структуры, точно следующие правилам типизации. Но вы также можете нарушать многие правила, если нужно. Когда вы это делает, язык выражает недовольство, вставляет палки в колеса, но не настолько, чтобы считать его обструкционистом.
Swift и Kotlin же совершенно непреклонны, когда речь заходит об их правилах типизации. Например, в Swift, если вы объявите функцию как
throws
(указывает, что она может генерировать ошибку), то, ей-богу, каждый вызов этой функции, на всех уровнях вложенности, должны быть обернут в блоки do-try
или try!
или try?
. В этом языке нет никакого способа просто пробросить исключение вплоть до самого верхнего уровня; ради этого приходится прокладывать целое шоссе через всё дерево вызовов.Возможно, вы сейчас думаете, что это хорошо. Возможно, вы считаете, что в системах было много ошибок, вызванных некорректными исключениями. Возможно, вы думаете, что исключения, которые не сопровождаются шаг за шагом вверх по стеку вызовов, являются рискованными и способствуют возникновению ошибок. И, разумеется, вы правы. Необъявленные и неуправляемые исключения очень рискованны.
Весь вопрос в том, кто должен управлять этим риском. Это работа языка? Или это работа программиста?
В Kotlin вы не можете наследовать класс или переопределять функцию, если вы не объявили этот класс или функцию с аннотацией
open
. Вы также не можете переопределить функцию, если переопределяющая функция не помечена аннотацией override
. Если open
при объявлении класса не использовалась, то язык не позволит вам наследовать от этого класса.Теперь, возможно, вы думаете, что и это хорошо. Возможно, вы полагаете, что наследование и иерархия производных типов, которой позволили расти без ограничений, является источником ошибок. Или же вы думаете, что мы можем устранить целые классы ошибок, заставив программистов явно объявить их классы как
open
. Может быть вы и правы: создание производных типов и наследование являются рискованными вещами. Многое может пойти не так, когда вы переопределите функцию в производном классе.Вопрос опять в том, кто должен управлять этим риском. Это работа языка? Или это работа программиста?
И в Swift, и в Kotlin включена концепция nullable-типов. Тот факт, что переменная может содержать
null
, становится частью типа этой переменной. Переменная типа String
не может содержать значение null
; она может содержать только строку. С другой стороны, переменная типа String?
(с вопросительным знаком в конце) является nullable-типом и может содержать null
.Правила языка настаивают на том, что когда вы используете переменную, допускающую значение
null
, то должны сначала проверить эту переменную на null
. Итак, если s
является String?
то выражение var l = s.length()
не будет компилироваться. Вместо этого вы должны написать var l = s.length() ?: 0
или var l = if (s! = Null) s.length() else 0
.Возможно, вы думаете, что это хорошо. Возможно, вы видели достаточно много исключений NullPointerException (NPE). Возможно, вы без тени сомнения знаете, что непроверенные null‘ы являются причиной сбоев программного обеспечения на миллиарды и миллиарды долларов. (Документация Kotlin называет NPE «Billion Dollar Bug»). И, конечно, вы правы. Очень рискованно иметь в системе неконтролируемые
null
‘ы.Снова возникает вопрос: кто должен управлять
null
‘ами? Язык? Или программист?Эти языки подобны маленькому голландскому мальчику, заткнувшему пальцем дамбу. Каждый раз, когда появляется новый вид ошибки, то для её предотвращения мы добавляем в язык новую функциональность. И поэтому эти языки накапливают всё больше и больше пальцев в дырах дамб. Проблема в том, что в итоге у вас кончаются пальцы и на руках, и на ногах.
Но прежде чем у вас закончатся пальцы, вы создадите языки, которые содержат десятки ключевых слов, сотни ограничений, запутанный синтаксис и справочное руководство, которое читается как свод законов. И чтобы стать экспертом в этих языках, вы должны быть языковым законником (language lawyer) (термин, который был изобретен в эпоху C ++).
Это неверный путь!
Спросите себя, почему мы пытаемся устранить дефекты программирования средствами языка? Ответ очевиден: потому что они появляются слишком часто.
А почему эти дефекты появляются слишком часто? Если вы ответите: «Потому что наши языки их не предотвращают», то лучше смените профессию и никогда не думайте о том, чтобы снова стать программистом. Ведь дефекты никогда не являются ошибкой языков. Дефект — это ошибка программиста, а не языка.
И что же программисты должны делать для предотвращения дефектов? Я дам вам подсказку. Это глагол. Он начинается с буквы «Т». Да. Вы поняли. ТЕСТИРОВАТЬ!
С помощью тестирования вы убеждаетесь, что ваша система не порождает неожиданные значения
null
. Что она обрабатывает null
на своих входах. Что каждое исключение, которое вы можете бросить, где-то ловится.Почему языки обрастают подобной функциональностью? Потому что программисты не тестируют свой код. И в результате у нас теперь есть языки, которые заставляют нас писать слово
open
перед каждым классом, от которого мы хотим наследовать. Теперь у нас есть языки, которые заставляют нас аннотировать каждую функцию по всему дереву вызовов с помощью try!
. Теперь у нас есть языки, которые накладывают столько ограничений и настолько излишне конкретизированы, что вам нужно спроектировать всю систему заранее, прежде чем вы сможете начать кодить.Вы скажете: Откуда мне знать, класс
open
или нет? Откуда мне знать, что кто-то внизу по дереву вызовов может выбросить исключение? Сколько кода мне придется изменить, когда я наконец узнаю, что кому-то действительно нужно вернуть null
вверх по дереву вызовов?Все эти ограничения, налагаемые языками, предполагают, что программист в совершенстве знает систему ещё до её написания. Они предполагают, что вы знаете, какие классы должны быть
open
, а какие нет. Что вы знаете, какие вызовы будут генерировать исключения, а какие нет. Что вы знаете, какие функции будут порождать null
, а какие нет.И из-за всех этих предположений ограничения наказывают вас, когда вы ошибаетесь. Они вынуждают вас вернуться назад и изменить огромное количество кода, добавив
try!
или ?:
или open
по всему стеку вызовов.И как вам избежать наказания? Есть два пути. Один работает, а другой нет. Тот, что не работает, состоит в том, чтобы спроектировать всё до того, как приступить к написанию кода. А путь, позволяющий избежать наказания, подразумевает отмену всех мер безопасности.
И поэтому вы объявите все свои классы и все свои функции как
open
. Вы никогда не будете использовать исключения. И вы привыкнете к использованию многих и многих символов !
для переопределения проверок на null
, и позволите NPE бесчинствовать в ваших системах.Почему Чернобыльская АЭС взорвалась и оставила обширную территорию непригодной для жизни? Потому что были обойдены все меры безопасности. Так что не полагайтесь на меры безопасности, стараясь предотвратить катастрофу. Вместо этого вам лучше привыкнуть к написанию большого количества тестов, независимо от того, какой язык вы используете.
Поделиться с друзьями