Предлагаем вашему вниманию перевод публикации, посвящённой анализу сходства языков 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 бесчинствовать в ваших системах.

Почему Чернобыльская АЭС взорвалась и оставила обширную территорию непригодной для жизни? Потому что были обойдены все меры безопасности. Так что не полагайтесь на меры безопасности, стараясь предотвратить катастрофу. Вместо этого вам лучше привыкнуть к написанию большого количества тестов, независимо от того, какой язык вы используете.
Поделиться с друзьями
-->

Комментарии (0)