Я пишу ПО уже больше двадцати лет и с каждым днём всё больше убеждаюсь, что сильная статическая типизация — это не просто хорошая идея, но и почти всегда верный выбор.

Разумеется, существуют сферы применения нетипизированных языков (или вариантов языков), например, они гораздо удобнее при работе с REPL или для одноразовых скриптов в средах, которые и так уже безнадёжно нетипизированы (например, в шелле). Но почти во всех остальных случаях сильная типизация крайне предпочтительна.

У неиспользования типов есть преимущества, например, более быстрая разработка, но они бледнеют по сравнению со всеми преимуществами типизации. Я говорю так:

Написание ПО без типов позволяет двигаться на полной скорости. На полной скорости к обрыву.

Вопрос о сильной статической типизации прост: выберете ли вы поработать чуть больше, но чтобы инварианты при этом проверялись во время компиляции (или на этапе проверки типов для некомпилируемых языков), или работать чуть меньше, но чтобы они принудительно применялись в среде исполнения или хуже того, не применялись вообще (да, JavaScript, я намекаю на тебя... 1 + "2" == 12).

Получение ошибок в среде исполнения — ужасная идея. Во-первых, это означает, что вы не всегда будете отлавливать их в процессе разработки. Во-вторых, если вы их и отловите, это произойдёт так, как это происходило бы с пользователем. Да, тесты помогают, но, учитывая бесконечное количество вариантов, написание тестов для каждого вероятного параметра функции с ошибочным типом — невозможная задача. Но даже если это реализуемо, с наличием типов гораздо проще работать, чем выполнять тестирование на ошибочные типы.

Использование типов приводит к уменьшению багов

Кроме того, типы обеспечивают аннотации к коду, от которых выигрывают и люди, и машины. Наличие типов — это способ более строгого определения контракта между разными фрагментами кода.

Рассмотрим следующие четыре примера. Все они выполняют абсолютно одинаковую задачу, различия заключаются лишь в разном уровне определения контракта.

// Параметры: имя (строка) и возраст (число).
function birthdayGreeting1(...params) {
    return `${params[0]} is ${params[1]}!`;
}

// Параметры: имя (строка) и возраст (число).
function birthdayGreeting2(name, age) {
    return `${name} is ${age}!`;
}

function birthdayGreeting3(name: string, age: number): string {
    return `${name} is ${age}!`;
}

В первом даже не задаётся количество параметров, поэтому без чтения документации сложно понять, что же он делает. Я думаю, что большинство людей согласится, что первый пример ужасен и так код писать не нужно. Однако эта идея очень схожа с типизацией, она заключается в определении контракта между вызывающей и вызываемой сторонами.

Что касается второго и третьего примеров, то благодаря типизации в третьем понадобится чуть меньше документации. Код проще, но преимущества довольно ограничены. По крайней мере, пока мы не изменим эту функцию...

И во второй, и в третьей функции автор предполагает, что возраст — это число. Поэтому абсолютно приемлемо изменить код так, как показано ниже:

// Параметры: имя (строка) и возраст (число).
function birthdayGreeting2(name, age) {
    return `${name} will turn ${age + 1} next year!`;
}

function birthdayGreeting3(name: string, age: number): string {
    return `${name} will turn ${age + 1} next year!`;
}

Проблема в том, что в некоторых местах, где используется этот код, принимается пользовательский ввод, получаемый из HTML-ввода (то есть всегда строка). И это приведёт к следующему:

> birthdayGreeting2("John", "20")
"John will turn 201 next year!"

В то же время версия с типизацией справедливо откажется компилироваться, потому что функция допускает, что возраст может быть только числом, а не строкой.

Наличие контракта между вызывающей и вызываемой сторонами важно для кодовой базы, чтобы вызывающие стороны знали, когда меняются вызываемые. Это особенно важно для опенсорсной библиотеки, где вызывающие и вызываемые стороны пишутся разными группами людей. При таком контракте невозможно узнать, поменялась ли ситуация.

Типы повышают удобство разработки

Типизация может использоваться IDE и другими инструментами разработки для существенного повышения удобства разработки. Вы получаете уведомление, когда любое из ваших ожиданий ошибочно. Это сильно снижает когнитивную нагрузку. Вам не нужно запоминать типы всех переменных и функцию в контексте. Компилятор поможет вам и сообщит, если что-то не так.

Кроме того, это приводит к ещё одному удобному преимуществу: упрощению рефакторинга. Вы можете доверять компилятору в том, что он сообщит, что вносимые вами изменения (допустим, как в примере выше) нарушают предположения, сделанные в другой части кода.

Ещё типы существенно упрощают освоение кодовой базы или библиотеки новыми инженерами:

  1. Они могут отслеживать определения типов, чтобы понять, где используются разные элементы.

  2. Гораздо проще экспериментировать с отдельными частями, потому что изменения вызывают ошибки компиляции.

Давайте рассмотрим следующие изменения в приведённом выше коде:

class Person {
  name: string;
  age: number;
}

function birthdayGreeting2(person) {
    return `${person.name} will turn ${person.age + 1} next year!`;
}

function birthdayGreeting3(person: Person): string {
    return `${person.name} will turn ${person.age + 1} next year!`;
}

function main() {
  const person: Person = { name: "Hello", age: 12 };

  birthdayGreeting2(person);

  birthdayGreeting3(person);
}

Легко увидеть все места (или использовать IDE для их поиска), где применяется Person. Мы видим, что он инициируется в main и используется в birthdayGreeting3. Однако чтобы узнать, что он используется в birthdayGreeting2, придётся прочитать всю кодовую базу.

У этого есть ещё и оборотная сторона: при взгляде на birthdayGreeting2 сложно понять, что она ожидает в качестве параметра Person. Часть таких вопросов можно решить подробной документацией, но: (1) зачем заморачиваться, если можно достичь большего, используя типы? (2) документация устаревает, а тут сам код становится документацией.

Это очень похоже на то, что вы бы не стали писать код таким образом:

// a - это person
function birthdayGreeting2(a) {
    b = person.name;
    c = person.age;
    return `${b} will turn ${c + 1} next year!`;
}

Нужно стремиться к использованию полезных имён переменных. Типизация — это тоже самое, она как имена переменных, но гораздо мощнее.

Мы кодируем всё в систему типов

Наша компания любит типы. На самом деле, мы стремимся закодировать в систему типов как можно больше информации, чтобы все ошибки, которые можно отловить во время компиляции, отлавливались во время компиляции; к тому же мы стараемся уместить в неё повышение удобства для разработчика.

Например, Redis — это протокол на основе строк без внутренней типизации. Мы используем Redis для кэширования (а также других задач). Проблема в том, что все преимущества типизации теряются в слое Redis, поэтому могут возникать баги.

Рассмотрим следующий фрагмент кода:

pub struct Person {
    pub id: String,
    pub name: String,
    pub age: u16,
}

pub struct Pet {
    pub id: String,
    pub owner: String,
}


let id = "p123";
let person = Person::new("John", 20);
cache.set(format!("person-{id}"), person);
// ...
let pet: Pet = cache.get(format!("preson-{id}"));

В нём есть пара багов:

  1. В имени второго ключа есть опечатка.

  2. Мы пытаемся загрузить person в тип pet.

Чтобы избежать таких проблем, в нашей компании мы делаем две вещи. Во-первых, требуем, чтобы ключ имел определённый тип (не обобщённую строку), а для создания этого типа необходимо вызвать конкретную функцию. Во-вторых, мы принудительно связываем ключ со значением.

То есть приведённый выше пример будет работать как-то так:

pub struct PersonCacheKey(String);

impl PersonCacheKey {
    fn new(id: &str) -> Self { ... }
}

pub struct Person {
    pub id: String,
        pub name: String,
        pub age: u16,
}

pub struct PetCacheKey;

pub struct Pet {
    pub id: String,
        pub owner: String,
}


let id = "p123";
let person = Person::new(id, "John", 20);
cache.set(PersonCacheKey::new(id), person);
// ...
// В следующей строке компиляция завершится ошибкой
let pet: Pet = cache.get(PersonCacheKey::new(id));

Это уже намного лучше и не позволяет возникнуть перечисленным выше багам. Но можно сделать ещё лучше!

Рассмотрим следующую функцию:

pub fn do_something(id: String) {
    let person: Person = cache.get(PersonCacheKey::new(id));
    // ...
}

В ней есть пара проблем. Во-первых, не очень понятно, для какого id она должна использоваться. Это person? Или pet? Очень легко случайно вызвать её с неверным параметром, как в этом примере:

let pet = ...;
do_something(pet.id); // <-- здесь должно быть pet.owner!

Во-вторых, мы теряем понятность. Довольно сложно понять, что Pet имеет взаимосвязь с Person.

У нашей команды есть особый тип для каждого id, позволяющий обеспечить отсутствие ошибок. Изменённый код выглядит примерно так:

pub struct PersonId(String);
pub struct PetId(String);

pub struct Person {
    pub id: PersonId,
    pub name: String,
    pub age: u16,
}

pub struct Pet {
    pub id: PetId,
    pub owner: PersonId,
}

И это на самом деле намного лучше, чем предыдущий пример.

Но всё равно остаётся одна проблема. Если мы принимаем id от API, то как нам понять, что они валидны? Например все  id pet в нашей компании имеют префикс pet_, за которым идёт Ksuid: pet_25SVqQSCVpGZh5SmuV0A7X0E3rw.

Мы хотим иметь возможность сообщить пользователям, что они передают в API неверный id, например, id person, когда нужен id pet. Простое решение заключалось бы в валидации, но можно легко забыть валидировать это везде, где используются эти данные.

Поэтому мы принудительно делаем так, чтобы PetId не мог создаваться без предварительной валидации. Благодаря этому мы знаем, что все пути выполнения кода, создающие PetId, сначала проверяют его валидность. Это значит, что когда мы возвращаем пользователю 404 Not Found, потому что pet не найден в базе данных, то можно быть уверенными, что это действительно валидный id, который не найден в базе данных. Если бы это не был валидный id, то мы бы уже вернули 422 или 400 при его передаче обработчикам API.

Так почему же не все любят типы?

Основная аргументация против типов заключается в следующем:

  1. Скорость разработки

  2. Кривая обучения и сложность типов

  3. Объём требуемых усилий и бойлерплейта

Во-первых, я бы сказал, что даже если бы всё вышеперечисленное было истиной, описанные в статье преимущества стоят дополнительных усилий. Впрочем, я всё-таки не согласен с этими пунктами.

Первое — скорость разработки. Прототипирование без типов определённо выполняется гораздо быстрее. Можно превращать фрагменты кода в комментарии, и компилятор не будет на это жаловаться. Можно задавать неверные значения для некоторых полей, пока вы не разберётесь, какие значения нужны, и так далее.

Однако, как я уже говорил выше: «Написание ПО без типов позволяет двигаться на полной скорости. На полной скорости к обрыву». Проблема в том, что это просто агрессивный и ненужный технический долг. Вам многократно придётся выплачивать его, когда вы будете отлаживать неработающий код (или локально, в наборе тестов, или в продакшене).

Что касается кривой обучения: да, для изучения дополнительных понятий требуется время. Однако я не сказал бы, что большинству людей нужно быть специалистами в типизации. Они вполне могут обойтись очень простыми описывающими тип выражениями, а при возникновении сложностей задавать вопросы. Однако если вы не усложняете без надобности, то, вероятно, никогда со сложностями не столкнётесь.

Кроме того, людям и так нужно учиться писать код, изучать фреймворки (React, Axum и т.д.), а также многое другое. Не думаю, что бремя изучения настолько существенно, как его пытаются представить.

И последнее о кривой обучения: я уверен, что преимущества спрямления кривой обучения благодаря неиспользованию типов гораздо меньше, чем преимущества использования type script для освоения конкретной кодовой базы. И в особенности потому, что изучение типов — это одноразовое вложение труда.

Последний пункт касается объёма усилий и бойлерплейта для использования типов в кодовой базе. Я считаю, что объём усилий на самом деле меньше, чем объём усилий, необходимый при неиспользовании типов.

Неиспользование типов требует подробной документации и тестирования, чтобы достичь хотя бы базового уровня работоспособности. Документация может устаревать, как и тесты; в любом случае они требуют больше усилий, чем простое добавление нужных типов. Читать код с типами тоже проще, потому что типы встроены в код, а не записаны в документации к функции, где они находятся в несогласованном формате со множеством лишнего шума.

Да, типизация может быть мучительной в языках, не поддерживающих вывода типов, например, в Java:

Дополнение: похоже, в Java уже есть вывод типов. Благодарю pron за исправление на HackerNews.

Person person1 = newPerson();
Person person2 = newPerson();
Person child = makeChild(person1, person2);

однако языки с выводом типов (например, Rust) гораздо удобнее:

let person1 = new_person();
let person2 = new_person();
let child = make_child(person1, person2);

То есть наличие подходящих инструментов определённо помогает.

Кстати об инструментах: чтобы пожинать плоды преимуществ типизации, вам, вероятно, нужно использовать редактор кода (или IDE), поддерживающий современное автозавершение кода со знанием языка.

В завершение

Я вижу обе стороны аргументации во многих спорах, например vim против emacs, табы против пробелов, и даже в гораздо более противоречивых. Однако в данном случае затраты настолько малы по сравнению с преимуществами, что я просто не могу понять, почему вообще кто-то может решить не использовать типы.

Мне бы хотелось узнать, возможно, я что-то упускаю, но пока буду защищать сильную статическую типизацию до последнего.

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


  1. Zara6502
    05.10.2023 06:03
    +2

    1. Скорость разработки

    А можно какой-то конкретный пример?

    Мне вот кажется это скорее не вопрос типизации, а проблема (фича) архитектуры самого ЯП.


    1. iliazeus
      05.10.2023 06:03
      +14

      Типизация, по моему опыту, как раз очень сильно помогает при прототипировании, когда в голове нет до конца оформленной картины того, как именно код будет выглядеть.

      Есть у меня, к примеру, код:

      class Person {
        name: string;
        // ...
      }
      
      class Book {
        name: string;
        // ...
      }
      

      И уже есть код, использующий оба этих класса. А потом - прототипирование же - я понимаю, что Book.name- это не лучший выбор имени, и что лучше это будет Book.title. Авторефакторинг, если он знает о типах, может мне одним нажатием F2 переименовать везде Book.name, но оставить нетронутым Person.name.

      Аналогично - если я вдруг понял, что мне нужно иметь отдельно Person.firstName и Person.lastName - я удаляю старое поле, добавляю новое, а затем прохожусь по всем местам, где компилятор говорит мне про ошибку "поле Person.name не найдено", и правлю их.

      А в старом коде типы аналогичным образом очень помогают при рефакторингах. Я не знаю, как в языке без типов можно не бояться переименовывать поля. Можно обвешаться юнит-тестами на доступ к каждому публичному полю и методу, да - но как раз скорость разработки от этого, кажется, очень просядет.


      1. whoisking
        05.10.2023 06:03
        +3

        Не думаю, что в случае рефакторинга нейминга property класса будут какие-то проблемы при отсутствии типов.


        1. iliazeus
          05.10.2023 06:03
          +7

          Как без типов и без ручного просмотра всего кода переименовать Book.name так, чтобы не задеть Person.name? Вот в таком коде, например (специально привел без типов):

          function formatRating(bestsellers) {
            return `The top bestseller is: ${bestsellers[0].name}`;
          }
          
          function formatFamily(brother, sister) {
            return `${brother.name} + ${sister.name} are brother and sister`;
          }
          


          1. whoisking
            05.10.2023 06:03

            Я из кода выше думал что к этому должны иметь какое-то отношение типы у проперти класса, а вы в данном контексте я так понял имеете вещи вроде типов у аргументов функций, тогда вопросов нет, вы правы.


            1. iliazeus
              05.10.2023 06:03
              +1

              Аналогичный пример, где важны именно типы полей, тоже несложно придумать. Например, я хочу поменять Person.name: string на Person.name: { first: string, last: string }. Или Book.title: string на Book.title: { translated: string, original: string }.


      1. Zara6502
        05.10.2023 06:03
        -2

        абсолютно не понял связи переименования и типизации. Тип - это int, string, float и т.п.

        В одном ЯП вы пишете

        int a = 1;

        в другом

        a = 1;

        или

        let a = 1;

        или

        var a = 1;

        При этом в типизированных ЯП строка

        a += 0.1;

        сразу перевозбудит компилятор, а в нетипизированном это будет работать без проблем.


        1. iliazeus
          05.10.2023 06:03
          +8

          абсолютно не понял связи переименования и типизации.

          Привел пример в комменте выше.

          При этом в типизированных ЯП строка
          a += 0.1;
          сразу перевозбудит компилятор, а в нетипизированном это будет работать без проблем.

          Я не уверен, что здесь значит "без проблем". Если вы заявляете, что в переменной может быть только целое число, а потом записываете туда дробное - это явная логическая ошибка. Если же это переменная допускает дробные числа - тогда просто не нужно объявлять ее как целую.

          Или вы жалуетесь на то, что автовывод типов не понял по var a = 1, что эта переменная может быть дробной? Так и я не понял, на самом деле.


          1. Zara6502
            05.10.2023 06:03
            -12

            Привел пример в комменте выше.

            Мне пример ничем не поможет, так как он не относится к предмету разговора.

            Я не уверен, что здесь значит "без проблем"

            Это значит что компилятор не увидит здесь никакой ошибки

            Если вы заявляете, что в переменной может быть только целое число, а потом записываете туда дробное - это явная логическая ошибка

            В типизированном ЯП это ошибка, в нетипизированном - это само собой разумеющееся явление - оно так работает из коробки.

            Если же это переменная допускает дробные числа - тогда просто не нужно объявлять ее как целую

            В нетипизированных ЯП нет вообще понятия "целое", есть просто переменная в которую можно писать всё что угодно.

            Так и я не понял, на самом деле

            У мня в целом ощущение что вы не понимаете что такое типизация.


            1. iliazeus
              05.10.2023 06:03
              +13

              Мне пример ничем не поможет, так как он не относится к предмету разговора.

              Так ответьте на вопрос: как сделать такое переименование поля автоматически без статических типов?

              В типизированном ЯП это ошибка, в нетипизированном - это само собой разумеющееся явление - оно так работает из коробки.

              Это логическая ошибка в вашем коде. Если вы заявляете (типами, если нет типов - комментами или названием переменной), что у нее один набор возможных значений, а потом присваиваете ей другие - это логическая ошибка.

              В нетипизированных ЯП нет вообще понятия "целое", есть просто переменная в которую можно писать всё что угодно.

              Это понятие все равно существует на уровне логики вашей программы. Если в переменную записывается буквально "что угодно", то это повод поругаться на это на код-ревью, потому что обычно в конкретной переменной ожидают достаточно определенный набор значений. Вряд ли кто-то будет ожидать 1.5 в переменной count, например. Или юзера в переменной order.


              1. Zara6502
                05.10.2023 06:03
                -12

                Так ответьте на вопрос: как сделать такое переименование поля автоматически без статических типов?

                Без понятия, у меня никогда не возникало такой задачи и я не представляю как эта задача и статья с этим связаны.

                Это логическая ошибка в вашем коде. Если вы заявляете (типами, если нет типов - комментами или названием переменной), что у нее один набор возможных значений, а потом присваиваете ей другие - это логическая ошибка

                Вот вам два примера работающего кода:

                1)

                10 A=1

                20 B=A

                25 PRINT(B)

                30 B=B/A+0.1

                40 PRINT (B)

                >1

                >1.1

                2)

                a = 4

                print(a)

                a = "hello"

                print(a)

                >4

                >hello

                В первом случае язык из 80-х, во втором - современный. И где тут какая-то логическая ошибка?

                Это понятие все равно существует на уровне логики вашей программы

                А мы про это сейчас не говорим.

                Если в переменную записывается буквально "что угодно", то это повод поругаться на это на код-ревью, потому что обычно в конкретной переменной ожидают достаточно определенный набор значений

                Мы не говорим о качестве кода.

                Вряд ли кто-то будет ожидать 1.5 в переменной count, например

                Когда я почти 30 лет назад изучал "теорию и технологию программирования", то одна из вещей, которые на мой взгляд меня и сформировали как программиста, это то, что я обязан рассматривать все случае, например вот такие:

                bool a = true;

                case(a)

                true: break;

                false: break;

                default: break;

                А ожидание через именование это вообще капец конечно. Еще скажите что count не имеет права быть ничем кроме int.

                Или юзера в переменной order

                Это вообще не имет отношения к типизации и разговору. Ну и я как хочу так и именую переменные и нет никого на планете кто мог бы мне навязать свою точку зрения в этом вопросе.


                1. iliazeus
                  05.10.2023 06:03
                  +12

                  Без понятия, у меня никогда не возникало такой задачи

                  Вы ни разу не рефакторили код? Или всегда делали это исключительно вручную, даже на больших кодовых базах?

                  я не представляю как эта задача и статья с этим связаны.

                  В языках с типами алгоритм автоматического рефакторинга "переименовать поле T.a в T.b" выглядит, грубо говоря, так: найти все выражения "доступ к полю объекта", где объект имеет тип T, а поле - имя a, и заменить в них a на b.

                  Как это сделать в языках без типов (без вывода этих самых типов самим алгоритмом рефакторинга) - я не представляю.

                  Пример привел потому, что в этом примере такой рефакторинг невозможно сделать простой автозаменой.

                  Вот вам два примера работающего кода (...) И где тут какая-то логическая ошибка?

                  Я не могу сказать, потому что я не понимаю, какая логика задумывалась в этом коде. Логическая ошибка - это ошибка именно в построении алгоритма.

                  А мы про это сейчас не говорим.
                  Мы не говорим о качестве кода.

                  Статическая типизация буквально используется как средство поддержания качества кода. Как средство переложить часть задач по проверке логических ошибок на компилятор.

                  А ожидание через именование это вообще капец конечно.

                  Так в том и дело, что в языках без типов для формирования ожиданий есть только имя переменной и комментарии.

                  Еще скажите что count не имеет права быть ничем кроме int.
                  я обязан рассматривать все случае, например вот такие
                  я как хочу так и именую переменные и нет никого на планете кто мог бы мне навязать свою точку зрения в этом вопросе.

                  Я очень рад, что мне не придется читать ваш код.

                  Вашу точку зрения понял. Свою, думаю, уже объяснил достаточно хорошо. Не уверен, что смогу вас переубедить, поэтому спор дальше продолжать нет смысла :)


                  1. Zara6502
                    05.10.2023 06:03
                    -16

                    Вы ни разу не рефакторили код?

                    Скорее нет чем да.

                    Или всегда делали это исключительно вручную, даже на больших кодовых базах?

                    Я не в курсе что этот процесс можно автоматизировать. Большое это сколько строк кода? У меня это экранов 10 максимум, там сколько 300-400 строк наверное.

                    В языках с типами алгоритм автоматического рефакторинга "переименовать поле T.a в T.b" выглядит, грубо говоря, так: найти все выражения "доступ к полю объекта", где объект имеет тип T, а поле - имя a, и заменить в них a на b.

                    Теперь я понимаю о чем вы.

                    Логическая ошибка - это ошибка именно в построении алгоритма

                    Тип - это часть ЯП, а не алгоритма. Максимум можно говорить о реализации алгоритма на языке Х, в любом случае если в ЯП нет типов, то в чем тут логическая ошибка?

                    Я очень рад, что мне не придется читать ваш код

                    Код, который я пишу для себя и тот который удобен мне - никто и никогда не будет читать, а когда я что-то пишу для других, то я переоформляю так чтобы и школьник прочитал. В любом случае право что и как называть я оставляю за собой.

                    поэтому спор дальше продолжать нет смысла

                    Ну я не смог сразу понять то что вы пишете, возможно вам достаточно той информации, но не мне.


                    1. 1dNDN
                      05.10.2023 06:03
                      +1

                      Тип - это часть ЯП, а не алгоритма

                      1. В алгоритме "найти первые N" чисел фибоначчи аргумент N имеет тип "неотрицательное число" вне зависимости от используемого ЯП. И даже если вы руками будете считать эти числа - у N всегда будет тип "неотрицательное число". Если вы подадите на вход алгоритма переменную типа "строка" или переменную типа "пользователь" - это будет логическая ошибка

                      2. Типы вы можете определять сами, это не обязательно только части ЯП. Например, вы можете самостоятельно определить тип "пользователь"


                    1. D7ILeucoH
                      05.10.2023 06:03
                      +1

                      Чувак, ты не прав, с каждым ответом всё больше - посмотри на минусы, они объективны.

                      Компиляторы бывают разные, в основном они могут автоматически распознать тип только при инициализации переменных.

                      Нет проблемы провести явное преобразование типов, но в этом и суть - ты его полностью контролируешь. Абстрактный пример с 1+"1" отлично передаёт суть...

                      Отвратительно называть переменные, участвующие в бизнес логике именами "a" и "b", когда их суть совсем иная. И также отвратительно класть в переменную image изображение, а в некоторых случаях - ошибку... Мы такое можем позволить в строгой типизации, но тогда у нас image будет интерфейсом с описанными реализациями.

                      Так повелось что динамические программисты не очень любят создавать классы


      1. ednersky
        05.10.2023 06:03
        -1

        может мне одним нажатием F2

        Но ведь это исключительно вопросы к вашему редактору. Мой вот по F2 не делает ничего. Ваш с типами умеет так, но без типов так не умеет. Редактор 3 будет уметь так и с типами и без оных. Получается качество редактора кода определяет ответ на вопрос: "что лучше типы или без типов?".


  1. aamonster
    05.10.2023 06:03
    +25

    Очередное ненужное разъяснение.

    Есть ровно один рабочий довод в пользу статической типизации: личный опыт. Статическую типизацию начинаешь любить, когда боль от динамической превышает какой-то порог.

    Ну и наличие в языке нормального вывода типов и нормальных сообщений об ошибках типизации сильно уменьшает неудобства статической типизации.


  1. ggo
    05.10.2023 06:03
    +5

    а я не буду никого защищать ;)

    буду просто использовать инструменты по назначению, иногда статическая типизация рулит, иногда динамическая.

    хорошо же, когда есть выбор, и понимание сильных и слабых сторон инструментов


  1. panzerfaust
    05.10.2023 06:03
    +23

    У нас есть замшелый легаси-сервис на ноде (а все остальное уже на котлине). Где-то 100-150 килострок. Залазить туда - дураков нет. Разбор любого бага - минимум день с рисованием на бумажке цепочек вызовов и типов. Любое вмешательство в код - это когда полчаса пишешь код и потом до конца дня проверяешь, что ничего не сломалось.

    В общем я не знаю, что должно произойти, чтобы я сказал "да, слабая типизация на бэкенде - отличная идея"


    1. NixGuy
      05.10.2023 06:03
      +2

      Тот или иной вид типизации никоим образом не спасает от написания плохого кода, что, скорей всего, и случилось с Вашим проектом. На языках с сильной статической типизацией тоже можно наговнякать всякого, и многие с этим успешно справляются.


      1. panzerfaust
        05.10.2023 06:03
        +38

        Ну как сказать. На джаве я видал лютейший говнокод, но ни разу не попадал в такую ситуацию, где я просто не понимаю, что принимает данная функция - и мне нужно размотать весь стек до эндпоинта, а то и пойти в вызывающий сервис, чтобы увидеть тип там. А тут это просто рядовая ситуация.


        1. funca
          05.10.2023 06:03

          где я просто не понимаю, что принимает данная функция

          Вообще есть ещё два варианта: заглянуть в документацию или подсмотреть в реализацию функции. Проекты на динамических языках без документации почти бесполезны. Ну и отчасти поэтому мощность языков разметки документации там зачастую сопоставима с возможностями основного языка.


          1. calculator212
            05.10.2023 06:03

            заглянуть в документацию или подсмотреть в реализацию функции

            Я попадал в такие ситуации, когда разбирал легаси на питоне. Документация просто отсутствовала для 90% кода, а по реализации было всё равно довольно трудно понять что это за объекты, но на самом деле понять код например в плюсах иногда может быть более сложной задачей. В целом хорошие инструменты для навигации по коду спасают в таких ситуациях.


          1. AstarothAst
            05.10.2023 06:03
            +2

            Явная типизация уже сама по себе документация, и в отличие от документации — эта всегда актуальна.


            1. ZirakZigil
              05.10.2023 06:03
              +1

              "У нас отличная документация: она вся на Си" (с)


            1. funca
              05.10.2023 06:03

              Где описывать типы это вопрос синтаксиса на самом деле. Долгое время в python для этого использовали docstrings и комментарии. Потом добавили отдельную нотацию. Но сам питон туда практически не смотрит. То есть вы можете указывать какие угодно типы и на работоспособность программы это ни как не влияет.


      1. yrub
        05.10.2023 06:03
        +4

        статическая типизация спасает вас от написания кода, который в принципе не работает ;) и сообщает вам об этом прямо на этапе компиляции.

        не могу понять эту тягу к попыткам сращения ужа и жабы и гордым заявлениям типа "у меня все под контролем". на примитивных типа наверно не сложно писать код, но как только вы заводите сложные типы данных то без документации будут проблемы. а типы работают как документация прямо в коде. 

        А, еще не могу понять, почему адепты динамической типизации дают всякие словесные имена переменным, можно ж v1, v2 - столько лишних букв на наборе можно сэкономить!


        1. blind_oracle
          05.10.2023 06:03
          +7

          Я вот как представлю как рефакторить большой проект на каком-нить Питоне или ЖС, где нужно будет изменить какое-то API или переименовать что-либо.

          Малая надежда на IDE и всякие языковые сервера в ней, которые пройдут по всему этому аду и скажут где что сломалось.

          Но по факту этого не будет, есть куча всякой динамики, которую они просчитать не смогут и всё упадёт потом в рантайме (ну, или в тестах, если они есть на это).

          Храни бог статическую типизацию: поменял - попытался собрать - увидел всё что сломалось.


          1. idd451289
            05.10.2023 06:03

            Ну это вы прямо сильно

            Тот же js уже приобрел typescript и нынче это почти стандарт


      1. qandak
        05.10.2023 06:03
        +1

        Известная всем "мантра" в данном контексте вообще не к месту.

        При средних и больших объемах самую наговняконную программу со статической типизацией рефакторить и дебажить всегда будет не так больно, как условно хорошую - без.


    1. dema
      05.10.2023 06:03
      +1

      Можно попробовать постепенно переползти в этом сервисе с js на ts, расставляя типы прямо по ходу работы с тикетами. Я так переводил огромное реактовое приложение с 150K+ строк с flow на typescript через JS. Т.к. не было нормального конвертера flow→typescript, я сначала убрал полностью все типы и потом прямо по ходу работы постепенно переименовывал файлики .js→.tsx и расставлял везде типы. За полгода перелез :)


    1. ednersky
      05.10.2023 06:03

      У нас есть замшелый легаси-сервис на ноде (а все остальное уже на
      котлине). Где-то 100-150 килострок. Залазить туда - дураков нет

      А типы ли (или их отсутствие) причина тех бед, что вы описываете?
      Я много раз натыкался на подобные сервисы, так вот ТОП моих причин подобных проблем включал в себя

      • Архизапутанный ООП

      • Странные (с современной точки зрения) интерфейсы функций и классов

      • Отсутствие автоматических тестов

      • Очень плохое логгирование

      • В дополнение к отсутствию тестов сложность частичного тестирования

      • Любовь автора к каким-то малоизвестным особенностям языка ("о, так можно, это ж круто! Будем вставлять такие конструкции повсюду!")


      1. panzerfaust
        05.10.2023 06:03

        Добавьте к этому списку непонятки кто кого вызывает (т.к. понятия интерфейса или трейта нет) и кто кому что передает (т.к. типы просто нигде не описаны). Понятно, что дело не только в типах. Они просто финальный гвоздь в крышке этого гроба.


  1. NeoCode
    05.10.2023 06:03
    +5

    Полностью согласен. Было дело, понадобилось написать что-то на php. После С/С++ это было настоящим мучением. Как можно работать без типов? Хуже может быть только работа без явного объявления переменных (это когда в любом месте кода можно присвоить любому новому имени люое значение, и все будет ОК).


    1. ftc
      05.10.2023 06:03
      +3

      Справедливости ради, нынче в PHP завезли типы (и чем дальше, тем активнее их завозят). И это чёрт возьми очень удобно.


      1. kavaynya
        05.10.2023 06:03
        +2

        На php не все так ужасно, как на том же python. После kotlin/java думал что на php будет совсем боль из-за типизации, но оказалось, что все не так уж и плохо, даже захотелось в kotlin возможность вернуть из функции два разных типа данных aka string|bool. А возможности phpstorm просто невероятно помогают разобраться в чужом и своем коде, после vscode, который ощущался просто текстовым редактором с подсветкой.


    1. Yrsafam
      05.10.2023 06:03
      +1

      Так в c/c++ тоже слабая типизация, а не сильная. Поэтому это явно не те языки, которые является эталонными на мой взгляд ????


      1. ptr128
        05.10.2023 06:03

        Ну её хотя бы можно сделать почти сильной при помощи-Werror

        А вот остальные случаи, которые используются для избежания копирования данных в памяти (union, приведение указателей или reinterpret_cast) все же необходимость, которой просто нужно пользоваться с умом.


  1. 6ap
    05.10.2023 06:03
    +2

    Спасибо, теперь мне не придётся это объяснять! Достаточно будет ссылки, чтобы люди прочувствовали, что их ждёт, когда решат реализовывать бизнес логику заказчика на динамических типах.


  1. ionicman
    05.10.2023 06:03
    -3

    Молоток - очень крутая штука, я реально не понимаю людей, которые используют отвертку! Ведь молотком можно и гвозь забивать и шуруп!

    Очередная статья из серии "Кто сильнее Акула или Медведь?".

    Это все - инструменты. Умение их использовать открывает вам соответсвующие двери и наоборот.

    Правильно настроенный линтер на языке со слабой типизацией сводит практически все описанные ошибки на нет. Скорость разработки POCов или каких-либо конверторов данных для разовых операций идет гораздо быстрее на языках со слабой типизацией - один из сильных вариантов их использования.

    Тяжелый проект, сторящийся на века и поддерживаемый командой - да, нужно писать с типизацией - на дает соотвествующую выгоду, расплата за это - время написания кода.

    Вот и все - но нет, опять появляются остроконечники vs тупоконечники.

    Еще раз - языки - это инструменты, универсального нет - выбирая один, ты всегда в чем-то выигрываешь, а в чем-то проигрываешь.


    1. insighter
      05.10.2023 06:03

      откуда скорость разработки берется когда без типизации, объясните?


      1. ionicman
        05.10.2023 06:03
        -1

        Оттуда, что не надо описывать дополнительно типы, следить за ними и их использованием.

        Кроме того, когда пишешь фии и еще неизвестно что она должна возвращать (POC например) - с этим тоже можно не заморачиваться и дописать позже и тд.

        Вобщем как раз из-за того, что при использовании типов ты как бы два рилма в голове держишь - рилм кода бизнес-логики и рилм типов, этот код обслуживающий.

        Надеюсь понятно изложил.


        1. ZirakZigil
          05.10.2023 06:03
          +4

          не надо описывать дополнительно типы

          Написание кода, именно набор текста, занимает довольно небольшую долю времени погромиста. Дописывание всевозможных int, double, auto, my_cool_type увеличит суммарные времязатраты на задачу дай бог на процент.

          следить за ними и их использованием

          Компилятор следит.

          и дописать позже

          Компилятор выводит.

          два рилма в голове держишь

          Типизация "настраивается" 1 раз, а дальше оно само. Мне не нужно знать, какой там именно контейнер, мне достаточно знать, что у него есть begin и end (или аналоги). А если уж нужно знать, по проичине производительности, например, то тут слабая типизация никак не поможет, придётся этот второй "рилм" подтягивать.


          1. ionicman
            05.10.2023 06:03
            +1

            Написание кода, именно набор текста, занимает довольно небольшую долю времени погромиста.

            Кроме просто написания, это нужно еще продумать.

            Компилятор следит.

            Компилятор следит за верностью использования, а не за бизнес-логикой, "следить" в этом случае - это использовать согласно задаче, а не в плане правильно/неправильно

            Типизация "настраивается" 1 раз, а дальше оно само.

            Нет, само оно только подсказывает типы, если они уже написаны, никто за вас автоматом типы в функцию или класс, который еще не написан, как и его составлюящие, не подставит, если он состоит из новых сущностей.

            Т.е. программирование таки разделяется на две вещи, как я уже выше писал - создание/изменение типов там где нужно, и сама программа, это использующая , для решения задачи бизнеса.

            Грубо говоря, если вам надо описать что-то из чего-то - вы вынуждены будете описать тип того, что хотите в контейнер пооложить, а потом объявить контейнер соответствующего типа. Никто за вас это не сделает. В языка со слабой типизацией вы просто объявили контейнер и кладете туда все что хотите.

            И еще раз - мой посыл не в том, что слабая или сильная типизация плохи или хороши, а в том, что они дают разные преимущества.


            1. ZirakZigil
              05.10.2023 06:03
              +2

              это нужно еще продумать.

              Так никто не мешает. Мне очень сложно представить, что существуют люди, занимающиеся погромированием дольше получаса, и у которых возникают дополнительные трудности, если вместо fetch_some_entries() им нужно написать container_type<entry_type> fetch_some_entries() и т.п.

              Компилятор следит за верностью использования

              Замечательно же! Про бизнес-логику не понял: да, не следит (хотя если бизнес-логику выразить в типах, то будет). А в динамически типизированных языках кто-то следит что ли? В чём их преимущество конкретно в этом вопросе?

              Нет, само оно только подсказывает типы

              Ну да. 1 раз "настроили" (описали всё), а дальше всё работает (подсказывает, если угодно).

              никто за вас автоматом типы в функцию или класс, который еще не написан, как и его составлюящие, не подставит, если он состоит из новых сущностей

              В общем случае это не так. Если класс полностью параметризуемый, то описать нужно не типы, а ограничения. А дальше именно что всё автоматически подставится (или не скомпилируется, если попытаться нарушить ограничения).

              В языка со слабой типизацией вы просто объявили контейнер и кладете туда все что хотите

              Не задумываясь о том, что и куда? В конечном счёте вам же нужно не "что-то" положить в контейнер, а вполне конкретную нужную вещь. В этот момент вы автоматически "подгружаете" в мозг тип загружаемого. А дальше разница в десятке символов на объявление контейнера с типом хранимого вместо просто контейнера.


            1. 1dNDN
              05.10.2023 06:03
              +2

              В языка со слабой типизацией вы просто объявили контейнер и кладете туда все что хотите.

              А когда нужно прочитать из контейнера - блуждаете по коду в попытках понять, что же конкретно лежит в этом контейнере. Это же гораздо проще, чем посмотреть на описание типа


        1. FruTb
          05.10.2023 06:03
          +1

          Ээээ. Почему 2 рилма? Если типы правильно использовать они являются прямым отражением домена бизнес-сущностей и их связей. Технические типы всяких библиотек живут на самой окраине и безнес-код вообще про них не в курсе.

          Да, я сторонник DDD и DSL


    1. AnthonyMikh
      05.10.2023 06:03
      +6

      Правильно настроенный линтер на языке со слабой типизацией сводит практически все описанные ошибки на нет.

      А знаете, как называется достаточно продвинутый линтер?


      Тайпечекер.


      И почему-то они есть у всех используемых на практике "динамически типизированных" ЯП.


      1. eton65
        05.10.2023 06:03

        И почему-то они есть у всех используемых на практике "динамически типизированных" ЯП

        Побуду адвокатом дьявола динамической типизации: что мешает создателям ЯП добавить переключатель в компиляторе с динамической типизации на статическую?


        1. bromzh
          05.10.2023 06:03
          +1

          Не для всех языков это возможно. Java и C# хранят типы в рантайме, там такое провернуть затруднительно. Хаскель и typescript избавляются от типов после компиляции, но если typescript «без типов» - это JavaScript, то для динамического хаскеля сложно будет найти применение


          1. Free_ze
            05.10.2023 06:03

            В C# для этого есть специальный тип dynamic.


  1. Alban44
    05.10.2023 06:03
    +1

    Конечно, тут можно поспорить, но в целом согласен с Вами


  1. hardtop
    05.10.2023 06:03
    +2

    Как говорил агент Смит из Матрицы: "Мы здесь, потому что нас лишили свободы". Выбрали game-dev и дорога вам в Unity или Unreal. Решили заниматься front-endом - привет, javascript! Конечно, есть ts - он помогает, но коварен, ибо soundness.

    На заре интернета динамические языки были удобны для веба. Писать url-encoded парсер для на с++ было утомительно, на perl это буквально 2 строки. Исторически так сложилось, что php и Битрикс до сих пор живут (кто-то же должен поддерживать legacy). Пока остался на python (django, fastapi): довольно гибко, и типизацию какую-никакую подвезли.

    Вот что сейчас выбрать для full-stack веба, если откинуть java и c#? Пробывал rust+rocket - ну сложный для меня раст. Go тоже "не зашёл".


    1. domix32
      05.10.2023 06:03
      +4

      Rocket не был хорошим фреймворком в своё время. Попробуйте раст посвежее. Какой-нибудь axum возьмите, например.


      1. hardtop
        05.10.2023 06:03

        Спасибо!


    1. PrinceKorwin
      05.10.2023 06:03
      +3

      Я для себя определился со следующим стеком:

      CSS - Bulma

      Frontend - Vue (на JS компонентах, без TS)

      Backend - Rust

      Быстро и сердито. Сначала отбиваю всю логику и модель данных на фронте.

      А после уже за пару проходов добавляю бек.

      Как начал так делать, то скорость выхода на MVP сильно выросла.


      1. ptr128
        05.10.2023 06:03
        +1

        Выписывать и поддерживать сложную бизнес-логику на Rust - не лучшая идея. C# или Java тут все же больше к месту. Причем не столько возможностями языка, сколько широким выбором уже готовых пакетов. Хотя бывают исключения, когда GS становится поперек горла и приходится переходить на тот же Rust или C++.

        Сама инфраструктура вокруг Rust еще недостаточно развита. Базовые классы есть. А вот если копнуть глубже, то даже адекватной реализации фильтра Калмана не нашел, когда понадобилась.

        Да даже с базой проблемы, когда родной драйвер к PostgreSQL молча(!) превращает DECIMAL с единичкой в 30-ом знаке после запятой в ноль. Можете считать это придиркой, но раз сам PostgreSQL поддерживает до 16383 знаков после запятой, то логично требовать если не такой же поддержки и от его драйвера, то хотя бы генерации исключения при переполнении.


        1. PrinceKorwin
          05.10.2023 06:03

          Не всякий backend содержит сложную бизнес логику.

          Мне возможностей стека на Rust хватает. После Java, конечно, бедновато, но работать можно.

          В Java мне не нравится его жадность до памяти и CPU.


      1. hardtop
        05.10.2023 06:03

        Интересно как, а я обычно стартую именно от структуры данных. А что используете в качестве фреймворка для раста? И отдаёте только json для vue? Шаблонизатор не используете? (Не смог найти нормальный типограф для русского языка на расте)


        1. PrinceKorwin
          05.10.2023 06:03

          Отдаю json. Если нужен шаблон, то tera.

          Веб фреймворк Actix.

          Тоже раньше делал бд, потом бек.

          Но, обычно, когда начинается UI приходит понимание, что все не так юзер френдли и надо переделать. И переделать весь стек выходит дольше, чем если сначала на мокап-данных сделать UI.


        1. PrinceKorwin
          05.10.2023 06:03

          Интересно как, а я обычно стартую именно от структуры данных

          Так я тоже. Сначала набросок модели данных. После делаю UI по нему.

          И тут сразу получается понимание какая модель нужна для UI, а какая для БД. Они разные в общем случае.


          1. ptr128
            05.10.2023 06:03

            Прикольно. А я с архитектуры решения начинаю крупным планом. Модели данных уже возникают потом, когда определились с видами БД (реляционные, OLAP, NoSQL, кеширующие), брокерами сообщений, видами контрактов и интеграцией со внешними системами.


            1. PrinceKorwin
              05.10.2023 06:03

              Не боитесь сразу заоверинженирить? :)


              1. ptr128
                05.10.2023 06:03

                Ваши предложения? Делать что ли модель данных под реляционные СУБД, а потом уже по ходу разработки растаскивать ее части, модифицируя, например, под Cassandra, Redis, ClickHouse?


                1. funca
                  05.10.2023 06:03

                  В теории СУБД выбирается под модель данных, когда становится понятно, что за данные и какие запросы. Но на практике нередко приходится подстраиваться под ограничения существующей инфраструктуры.


                  1. ptr128
                    05.10.2023 06:03

                    В теории СУБД модели данных для реляционной, OLAP и NoSQL БД различаются принципиально. И дело далеко не в инфраструктуре, а в том, что, для примера, если для реляционной БД модель данных делается нормализованной, то для OLAP - наоборот, модель денормализуется для более эффективной работы COLUMNSTORE. Аналогично и в NoSQL (Cassandra или Redis) модель данных принципиально отличается от реляционной.

                    когда становится понятно, что за данные и какие запросы

                    Вот именно об этом я и написал:

                    А я с архитектуры решения начинаю крупным планом

                    И только после этого решается какие данные где и как хранить, какие запросы какой сервис должен выполнять и какие БД ему для этого нужны.


                    1. PrinceKorwin
                      05.10.2023 06:03

                      Ну я бы начинал не с HLA, а со сбора требований в том числе не функциональных.


                      1. ptr128
                        05.10.2023 06:03

                        Подразумевается, что это уже произошло в рамках предпроектного обследования или указано в тендерной документации. Иначе откуда проект?


    1. ogost
      05.10.2023 06:03

      Пробывал

      От слова "быть". Слово, которое вы ищете - "пробовать"


      1. hardtop
        05.10.2023 06:03

        Само собой!


  1. vagon333
    05.10.2023 06:03
    +1

    Не спорю, сам за статическую типизацию, но заголовок тронул:

    Я до последнего буду защищать ...

    До последнего кого или чего? :)


    1. lazy_val
      05.10.2023 06:03

      В оригинале:

      Strong static typing, a hill I'm willing to die on...

      Красиво, а?


  1. agoncharov
    05.10.2023 06:03
    +10

    У неиспользования типов есть преимущества

    Мы всегда используем типы, вопрос лишь в том, держим мы их в коде или только в голове


  1. sshikov
    05.10.2023 06:03
    +1

    они гораздо удобнее при работе с REPL

    Странное утверждение. Скажем, скала статически типизированный язык (с выводом типов). При этом у нее более чем приличный REPL, которому наличие типов нифига не мешает. И все там естественно.


    Кто-то может пояснить, как наличие типов вообще может мешать REPL, или их отсутствие упрощать работу?


    1. envy12
      05.10.2023 06:03
      -1

      Думаю здесь имеются в виду реплы лиспов, которые на порядок лучше аналогов в статически типизированных языках


      1. sshikov
        05.10.2023 06:03
        +1

        Так я и пытаюсь просянить, чем именно они лучше? Скажем прямо, я давненько не работал с реплом лиспа (и вообще кажется работал лишь с одним из них, который встроен в AutoCAD, и который ну как бы не совсем стандартный лисп. И я не могу вспомнить, что там такого прямо хорошего, чего я в скале не найду. Ну разве что — исполнение кода, который я тут же и сгенерировал, т.е. мета- или макро программирование. Так это не факт что прямое следствие отсутствия типов или другой системы типов. Для статически типизированных языков вполне существуют системы макропрограммирования.


        1. envy12
          05.10.2023 06:03
          -1

          Выполнение блоков кода прямо в редакторе, без необходимости копировать в репл. Кроме того, clojure flowstorm debugger может делать не только step in/into, но и еще step back.


          1. sshikov
            05.10.2023 06:03
            +2

            Выполнение блоков кода прямо в редакторе
            не только step in/into, но и еще step back.

            Это все несомненно удобно, но не очень ясно, как это связано с типами или их отсутствием? Для выполнения блоков кода в редакторе все что нужно — это поднятый runtime языка, и в режиме отладки IDEA это умеет для Java. Ну то есть, тут скорее вопрос в том, в каком контексте выполняется этот ваш код в редакторе, что за окружение ему доступно в широком смысле? Когда мы в отладчике — то мы например остановились на какой-то строке кода в процессе выполнения, и с контекстом все более-менее ясно.


            Возможность step back, на первый взгляд, скорее определяется тем, что рантайм наш четко знает, какие (побочные) эффекты вызывает выполнение каждой строки кода, чтобы откатить их. То есть, тут (опять же на первый взгляд) скорее полезны чистые функции в языке, нежели отсутствие типов. Если у нас только чистые функции — то мы четко знаем, как сделать шаг назад в отладчике, потому что знаем, в чем эффект. А если мы скажем строку в файл записали, или в сокет что-то кинули — то какой тут может быть шаг назад, когда уже все, поезд ушел?


            Ну то есть, скорее всего связь между удобством REPL и языком есть, но она какая-то не слишком очевидная.


            1. envy12
              05.10.2023 06:03

              Не знаю, какая там связь, но думаю, что с типами добиться аналогичного результата не получиться. Поэтому и нет подобных реплов ни для джавы, ни для остальных языков, тк одного runtime не достаточно


              1. bromzh
                05.10.2023 06:03
                -1

                Просто в lisp принято разрабатывать через REPL, потому что по-другому сложно. Это особенность среды - там есть ядро языка, и мы его расширяем. Поэтому и удобно интерактивно это делать и тестить в репле. И сама программа по-сути всегда целое ядро языка + наши функции. В smalltalk тоже так, там по-сути IDE выполняет роль интерпретатора и среды запуска программ, они без него не могут жить.

                В остальных языках оно почти никогда не надо, потому что на выходе артефакт - бинарник для ос или байткод, который гоняется на виртуальной машине. Так-то REPL и в хаскеле есть.


                1. Rigidus
                  05.10.2023 06:03
                  +1

                  Не потому что "по другому сложно". Просто это один из удобных путей, на котором многие делают акцент, потому что в сравнении с другими языками это новое и необычное. Классическая разработка через файлы вполне типична в лиспах


  1. envy12
    05.10.2023 06:03
    +2

    impl<SinkT, ServerRpcVersion, ClientRpcVersion, T> Handler<SendClientRpc<ClientRpcVersion, T>> for Session<SinkT, ServerRpcVersion, ClientRpcVersion>
    where
        SinkT: Sink<SinkItem = Bytes, SinkError = std::io::Error> + 'static,
        ServerRpcVersion: rpc::deserializer::ServerProtocol,
        ClientRpcVersion: rpc::serializer::ClientProtocol,
        T: Into<ClientRpcVersion::ClientRpcKind>,
    {
        type Result = std::io::Result<()>;
    
        fn handle(&mut self, rpc: SendClientRpc<ClientRpcVersion, T>, _ctx: &mut Context<Self>) -> Self::Result {
            self.send(rpc.data).map(|_| ())
        }
    }

    Оставлю это здесь как аргумент против типов. Желающие могут попытаться найти ошибку


    1. blind_oracle
      05.10.2023 06:03
      +5

      Ну, упарываться в дженерики в расте очень любят, я бы не сказал что это прям аргумент против типов. Писать можно по-разному.

      Меня в расте больше смущает "обратное" выведение типизации, когда ты там создаёшь какой-либо, ну допустим Vec::new() , без указания типа, и где-то там сильно дальше в него что-либо пихаешь. И это определяет его тип, снизу вверх.

      Это выведение типов несколько мешает читать и понимать код. Ну и это простой случай, а бывает совсем жопа.


      1. JustForFun88
        05.10.2023 06:03

        Ну так IDE в помощь, хотя это конечно не дело объявлять вектор сильно выше использования, за такое надо бить по рукам.

        Правда rust-analyzer иногда конечно тупит на сильно сложных выводах, с associated types, GAT и т.д. Особенно когда идет цепочка GAT ????

        И автоматический вывод типов на уровне функции очень удобен с поддержкой IDE. Ведь случаи описанные вами крайне редки. Функции обычно стараются делать маленькими, либо разбивать на блоки. Ну а входные и выходные данные функции должны быть прописаны точно


        1. blind_oracle
          05.10.2023 06:03

          Правда rust-analyzer иногда конечно тупит на сильно сложных выводах, с associated types, GAT и т.д. Особенно когда идет цепочка GAT

          Да, а особенно тупит на достаточно больших монорепах (у нас около 7к файлов), вплоть до невозможности использования.

          При этом из-за всех этих особенностей Растом крайне сложно пользоваться без этих подсветок от IDE, в отличии от многих других языков.

          Функции обычно стараются делать маленькими, либо разбивать на блоки.

          В идеальном мире - да... :)


    1. WQS100
      05.10.2023 06:03
      +1

      Не имею ни малейшего представления, что тут происходит, но как минимум бросается в глаза, что у ServerRpcVersion стоит deserializer, а у ClientRpcVersion -- serializer, хотя из схожести названий интуитивно кажется, что и типы должны совпадать.


    1. JustForFun88
      05.10.2023 06:03
      +1

      В чем же проблема? Ведь явно прописаны все ИНТЕРФЕЙСЫ типов. Прописывание конкретного типа ничего не изменит. Пользователь и так точно знает что может делать тот или иной входной аргумент.

      Несовместимые типы и аргументы, также как их неправильное использование сразу даст ошибку компиляции, ну или в случае с Rust, начнет жаловаться rust-analyzer. Искать ошибку самому нет необходимости.


    1. Cerberuser
      05.10.2023 06:03
      +4

      Желающие могут попытаться найти ошибку

      Ошибку, которая не сводится к тому, что где-то не сошлись какие-то два типа, я так понимаю? Потому что иначе непонятно, зачем её искать нам, а не компилятору.


  1. eton65
    05.10.2023 06:03

    Просто есть люди, которые фигачат в продакшен лишь бы работало. Они никогда не поймут, зачем нужны шашечки статические/строгие типы.


    1. xSVPx
      05.10.2023 06:03

      Что значит "есть"? "Их тысячи...."


  1. funca
    05.10.2023 06:03
    -1

    изучение типов — это одноразовое вложение труда.

    Это изучение безтипного синтаксиса вещь одноразовая. k = k + 1 во многих языках вглядит одинаково. Выучив один раз, сразу открывается куча применений: TypeScript, С++, Python, Java, Go, ... вплоть до разной экзотики вроде Haskell или OCaml. Короче весь мир у ваших ног.

    Чего нельзя сказать о типах. Мне кажется сколько языков, столько систем типов, со своими особенностями. Не могу придумать ни одного примера, где два разных языка используют одинаковый синтаксис и правила. Поэтому изучать типы вы будете всякий раз заново. Где-то достаточно просто здравого смысла (но и толку от них тоже немного), а где-то придется зарываться в дебри какой-нибудь линеарной логики или теорката.


    1. JustForFun88
      05.10.2023 06:03
      +1

      Ну строго говоря типы учить как то особо не надо. Просто используешь их и все.

      Например в python есть классы, и их надо как то объявить, так и здесь тоже самое.

      Ну есть у тебя функция и она принимает миллиметры. Просто создаешь тип миллиметр и прописываешь что он является входным аргументом. И тебе понятно, что ожидает эта функция и пользователям понятно. И через год тебе тоже будет понятно, а не придётся вспоминать смотря на вызовы функции.

      Можно пойти дальше. Ты объявляешь что функция принимает что угодно, если она реализует вон тот интерфейс (грубо говоря у нее есть какая-то определённая функция, которая возвращает определённое значение). Это уже дженерики.


      1. funca
        05.10.2023 06:03

        Ну строго говоря типы учить как то особо не надо. Просто используешь их и все.

        Ну вообще вы наверное правы, хотя проблем на самом деле хватает даже в python https://github.com/python/typing/issues. Т.е на практике интуитивно понятные вещи здесь довольно быстро заканчиваются.


  1. BigKote
    05.10.2023 06:03
    +2

    После 10 лет работы программистом 1С(нет строгой типизации) фантомная боль в отстреленный ногах заставил перейти в Java\Kotlin, вроде потихоньку регенерирую. Когда общаюсь друзьями на той стороне, всегда интересно послушать как люди поддерживают решения на 3+ млн строк кода без типизации и тестов, какие у них новые веселые ситуации случаются.


  1. heart
    05.10.2023 06:03

    Программист С++:


  1. faiwer
    05.10.2023 06:03
    +1

    После многих лет программирования на Typescript не представляю себе программирование на JS/PHP/Ruby/Python. Это будет (для меня) невыносимая боль. Но могу побыть адвокатом дьявола, ведь не всё так просто. Скажем, я, поставь предо мной выбор, язык с примитивной статической типизацией VS язык с динамической, точно выберу второе. Т.е. я бы не моргнув взял бы JS, а не Java. Но Haskel/TS/Flow/Kotlin, а не JS. Почему? Потому что языки с продвинутой системой типов позволяют взять лучшее из двух миров:


    • дают вам вменяемые гарантии надёжности вашего кода
    • огромные плюшки по рефакторингу
    • оставляют сопоставимую с дин. языками гибкость и скорость разработки

    Приведу пример самого вкусного. Тесты. На Typescript вы можете не использовать DI, в тех случаях, когда он у вас был только ради mock-ания в тестах. Почему? Потому что мокать можно import-ы из файлов. И при замене подставлять минимально-приемлемый огрызок от заменяемой сущности. В сравнении с полноценным DI у вас и проект куда проще, и тесты писать можно одной левой. Тесты при этом не потеряли своей функциональности. Они всё также тестируют, всё то, что вы хотели. Но они дались вам, в каком-то смысле, бесплатно. Языки же с sound static типизацией заставят вас очень много приседать. Что скорее всего сведётся к тому, что если сущность, не сильно важная, то обойдётся она и без тестов.


    Ну и ложка дёгтя. Sound static type языки гораздо быстрее в runtime-е. По очевидным причинам. Компилятор гарантировано знает о вашей программе всё, и может применить все мыслимые и немыслимые оптимизации. В случае со структурной типизацией, компилятор всегда будет исходить из "тут всё сложно, мои полномочия на этом всё".


    1. funca
      05.10.2023 06:03

      Sound static type языки

      Можете привести несколько примеров таких языков?


      1. faiwer
        05.10.2023 06:03

        C++, Java, Rust, Pascal и пр. Понятно, что абсолютной soundness они не предоставляют (например позволяют обращаться к произвольному элементу в массиве, не проверяя его существования), но разумная доза soundness-ти там есть. Что позволяет компилятору, скажем, обращение к конкретному полю объекта в коде превратить в прямой переход по сдвигу в памяти.


    1. alhimik45
      05.10.2023 06:03
      +1

      Haskel/TS/Flow/Kotlin

      А в Kotlin есть прям что-то принципиально более крутое чем в Java? Всегда считал что он больше про практичный сахар для всё той же java/c#-ной ООП парадигмы, нежели что-то фундаментально другое, как Scala.


      1. faiwer
        05.10.2023 06:03
        +1

        Это надо котлинистов спрашивать. Я просто жертва их пропаганды :-) Знакомые android-разработчики очень нахваливали. И правда стоило указать Scala, вылетело из головы.


  1. ptr128
    05.10.2023 06:03
    +1

    Все же как-то обошли стороной случаи, когда грубая сила (union, приведение типов указателей/reinterpret_cast) все же необходима. И если язык это не поддерживает вообще и никак, то некоторые задачи на нем эффективно не реализуешь.


    1. funca
      05.10.2023 06:03
      -1

      На одной типизации все равно до конца не доедешь - рано или поздно придётся расставлять костыли и писать тесты. Потому, что в пределе логика упирается в Теорему Гёделя о неполноте: любая формальная система либо неполна, либо противоречива. На практике лучше иметь дело с неполнотой. Это значит, что какие-то утверждения во всякой непротиворечивой системе типов будут невыводимыми.


    1. AnthonyMikh
      05.10.2023 06:03

      И если язык это не поддерживает вообще и никак, то некоторые задачи на нем эффективно не реализуешь.

      Это какие, например?


      1. ptr128
        05.10.2023 06:03

        Вы серьезно? Никогда, например, не приходилось работать со страницами БД? Особенно если они уже закешированы в оперативке.

        В общем случае, эффективная (без излишнего копирования или побайтового разбора) работа с данными пакетной или страничной организации выполняется только через type punning.


    1. Anarchist
      05.10.2023 06:03

      Грубая сила бывает необходима, но хорошо, когда она применяется точечно и явно. На какой-нибудь instanceOf на ревью всегда стойка: может случиться непоправимое.
      Проблема, когда грубая сила неявно присутствует везде.


      1. ptr128
        05.10.2023 06:03

        Это я как раз поддерживаю. Всему свое место. Я лишь указал, что не следует забывать о таких редких местах, где хорошо то, что в подавляющем большинстве случаев - плохо.


  1. Folko85
    05.10.2023 06:03
    +1

    Вижу статью в поддержку строгой типизации - ставлю плюс.


  1. Pardych
    05.10.2023 06:03
    -1

    Сколько языков вы знаете, поддерживающих одновременно и сильную и статическую? Я вот всего два. И только один практикую. В упомянутой жабе - типизация статическая, но слабая. Однако автобрекетинг и прочее неявное приведение не делает ее менее пригодной для промышленного применения чем сильные но динамические пхп и жс, а выведения у нее появились для тех кто не залип на мобайле (>1.8).

    Так вот собственно вопрос. Что именно вы все-таки будете защищать до последнего? Явные типы в компайл-тайме или отсутствие неявных приведений? Потому что оба двое - редкое явление.


    1. ptr128
      05.10.2023 06:03
      -1

      Если у языка статическая слабая типизация, обычно, только ключами компилятора это легко изменить, сохраняя при этом возможности type punning. А вот если язык не поддерживает статическую типизацию - это только частично можно исправить костылями через статическую типизацию в комментариях.


  1. Anarchist
    05.10.2023 06:03

    Как только дело выходит за рамки рест апишечек и dsl, отсутствие статической типизации становится проблемой, если писать что-то сложнее хеллоуворлд. Нетипизированные языки ведут себя частенько контринтуитивно.